@ -0,0 +1,27 @@
|
||||
{ |
||||
"presets": [ |
||||
["env", { |
||||
"loose": true, |
||||
"debug": false, |
||||
"useBuiltIns": true, |
||||
"targets": { |
||||
"browsers": [ "ie > 8", "last 2 version", "safari >= 9" ] |
||||
}, |
||||
"production": { |
||||
"plugins": ["transform-remove-console"] |
||||
} |
||||
}] |
||||
], |
||||
"plugins": [ |
||||
[ "transform-runtime", { |
||||
"helpers": false, |
||||
"polyfill": false, |
||||
"regenerator": true } ], |
||||
[ "transform-class-properties", { "spec": true } ], |
||||
[ "transform-object-rest-spread", { "useBuiltIns": true } ], |
||||
[ "transform-vue-jsx" ], |
||||
[ "syntax-dynamic-import" ] |
||||
], |
||||
"comments": false |
||||
} |
||||
|
@ -0,0 +1,21 @@
|
||||
# editorconfig.org |
||||
# author: axin |
||||
root = true |
||||
|
||||
[*] |
||||
indent_style = space |
||||
indent_size = 2 |
||||
end_of_line = lf |
||||
charset = utf-8 |
||||
trim_trailing_whitespace = true |
||||
insert_final_newline = true |
||||
|
||||
[*.py] |
||||
indent_style = space |
||||
indent_size = 4 |
||||
|
||||
[*.md] |
||||
trim_trailing_whitespace = false |
||||
|
||||
[COMMIT_EDITMSG] |
||||
max_line_length = 80 |
@ -0,0 +1,6 @@
|
||||
|
||||
# 后端接口地址 |
||||
API_BASE = http://192.168.xx.xx:12345 |
||||
|
||||
# 本地开发如需ip访问项目把"#"号去掉 |
||||
#DEV_HOST = 192.168.xx.xx |
@ -0,0 +1,6 @@
|
||||
globals: |
||||
$: true |
||||
expect: true |
||||
rules: |
||||
"no-new": "off" |
||||
"no-labels": [2, {"allowLoop": true}] |
@ -0,0 +1,115 @@
|
||||
/* |
||||
* Licensed to the Apache Software Foundation (ASF) under one or more |
||||
* contributor license agreements. See the NOTICE file distributed with |
||||
* this work for additional information regarding copyright ownership. |
||||
* The ASF licenses this file to You under the Apache License, Version 2.0 |
||||
* (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
var fs = require('fs') |
||||
var path = require('path') |
||||
var request = require('request') |
||||
var cdnUrl = 'https://s1.analysys.cn/libs/??' |
||||
var version = '1.0.0' |
||||
|
||||
// js集合
|
||||
var jslibs = { |
||||
'es5': [ |
||||
'es5-shim/4.5.7/es5-shim.min.js', |
||||
'es5-shim/4.5.7/es5-sham.min.js' |
||||
], |
||||
'3rd': [ |
||||
'vue/2.5.2/vue.js', |
||||
'vue-router/2.7.0/vue-router.min.js', |
||||
'vuex/3.0.0/vuex.min.js', |
||||
'jquery/3.3.1/jquery.min.js', |
||||
'lodash.js/4.17.5/lodash.min.js', |
||||
'jqueryui/1.12.1/jquery-ui.min.js', |
||||
'twitter-bootstrap/3.3.7/js/bootstrap.min.js', |
||||
'jsPlumb/2.8.5/js/jsplumb.min.js', |
||||
'clipboard.js/2.0.1/clipboard.min.js', |
||||
'd3/3.3.6/d3.min.js', |
||||
'echarts/4.1.0/echarts.min.js', |
||||
'dayjs/1.7.8/dayjs.min.js', |
||||
'codemirror/5.43.0/codemirror.min.js', |
||||
'codemirror/5.43.0/mode/sql/sql.min.js', |
||||
'codemirror/5.43.0/addon/hint/show-hint.min.js', |
||||
'codemirror/5.43.0/addon/hint/sql-hint.min.js', |
||||
'codemirror/5.43.0/mode/textile/textile.min.js', |
||||
'codemirror/5.43.0/mode/shell/shell.min.js', |
||||
'codemirror/5.43.0/mode/python/python.min.js', |
||||
'codemirror/5.43.0/addon/hint/xml-hint.min.js', |
||||
'codemirror/5.43.0/mode/xml/xml.min.js', |
||||
'html2canvas/0.5.0-beta4/html2canvas.min.js', |
||||
'canvg/1.5/canvg.min.js' |
||||
], |
||||
'local': [] |
||||
} |
||||
|
||||
// css集合
|
||||
csslibs = { |
||||
'base': [ |
||||
'normalize/7.0.0/normalize.min.css', |
||||
'twitter-bootstrap/3.3.7/css/bootstrap.min.css', |
||||
'-/@analysys/reset.css@1.0.1', |
||||
'-/@vue/animate.css@' |
||||
], |
||||
'3rd': [ |
||||
'highlight.js/9.13.1/styles/vs.min.css', |
||||
'jsPlumb/2.8.5/css/jsplumbtoolkit-defaults.min.css', |
||||
'codemirror/5.43.0/codemirror.min.css', |
||||
'codemirror/5.20.0/theme/mdn-like.min.css', |
||||
'codemirror/5.43.0/addon/hint/show-hint.min.css' |
||||
] |
||||
} |
||||
|
||||
// 创建文件夹目录
|
||||
var dirPath = path.resolve(__dirname, '..', 'src/combo/' + version) |
||||
|
||||
if (!fs.existsSync(dirPath)) { |
||||
fs.mkdirSync(dirPath) |
||||
console.log('文件夹创建成功') |
||||
} else { |
||||
console.log('文件夹已存在') |
||||
} |
||||
|
||||
var jsKeys = Object.keys(jslibs) |
||||
var jsUrl = jsKeys.map(v => { |
||||
return jslibs[v].join() |
||||
}) |
||||
|
||||
jsUrl.forEach((v, i) => { |
||||
var url = cdnUrl + v |
||||
console.log(url) |
||||
var stream = fs.createWriteStream(path.join(dirPath, jsKeys[i] + '.js'), { encoding: 'utf-8' }) |
||||
request(url).pipe(stream).on('close', function (err) { |
||||
if (!err) { |
||||
console.log('文件[' + version + '/' + jsKeys[i] + '.js' + ']下载完毕') |
||||
} |
||||
}) |
||||
}) |
||||
|
||||
var cssKeys = Object.keys(csslibs) |
||||
var cssUrl = cssKeys.map(v => { |
||||
return csslibs[v].join() |
||||
}) |
||||
|
||||
cssUrl.forEach((v, i) => { |
||||
var url = cdnUrl + v |
||||
console.log(url) |
||||
var stream = fs.createWriteStream(path.join(dirPath, cssKeys[i] + '.css'), { encoding: 'utf-8' }) |
||||
request(url).pipe(stream).on('close', function (err) { |
||||
if (!err) { |
||||
console.log('文件[' + version + '/' + cssKeys[i] + '.css' + ']下载完毕') |
||||
} |
||||
}) |
||||
}) |
@ -0,0 +1,208 @@
|
||||
/* |
||||
* Licensed to the Apache Software Foundation (ASF) under one or more |
||||
* contributor license agreements. See the NOTICE file distributed with |
||||
* this work for additional information regarding copyright ownership. |
||||
* The ASF licenses this file to You under the Apache License, Version 2.0 |
||||
* (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
const path = require('path') |
||||
const glob = require('globby') |
||||
const webpack = require('webpack') |
||||
const HtmlWebpackPlugin = require('html-webpack-plugin') |
||||
const HtmlWebpackExtPlugin = require('html-webpack-ext-plugin') |
||||
const isProduction = process.env.NODE_ENV !== 'development' |
||||
|
||||
const resolve = dir => path.join(__dirname, '..', dir) |
||||
|
||||
const assetsDir = resolve('src') |
||||
const distDir = resolve('dist') |
||||
const viewDir = resolve('src/view') |
||||
|
||||
|
||||
function moduleName (modules) { |
||||
let filename = path.basename(modules) |
||||
let parts = filename.split('.') |
||||
parts.pop() |
||||
filename = parts.join('.') |
||||
return path.dirname(modules) + '/' + filename |
||||
} |
||||
|
||||
const jsEntry = (() => { |
||||
const obj = {} |
||||
const files = glob.sync(['js/conf/*/!(_*).js'], { cwd: assetsDir }) |
||||
files.forEach(val => { |
||||
let parts = val.split(/[\\/]/) |
||||
parts.shift() |
||||
parts.shift() |
||||
let modules = parts.join('/') |
||||
let entry = moduleName(modules) |
||||
obj[entry] = val |
||||
}) |
||||
return obj |
||||
})() |
||||
|
||||
const minifierConfig = isProduction ? { |
||||
removeComments: true, |
||||
removeCommentsFromCDATA: true, |
||||
collapseWhitespace: true, |
||||
collapseBooleanAttributes: true, |
||||
removeRedundantAttributes: true, |
||||
useShortDoctype: true, |
||||
minifyJS: true, |
||||
removeScriptTypeAttributes: true, |
||||
maxLineLength: 1024 |
||||
} : false |
||||
|
||||
const getPageEntry = view => jsEntry[view] ? view : '' |
||||
|
||||
// 重新定向输出页面
|
||||
const pageRewriter = { |
||||
'view/home/index.*': 'index.html' |
||||
} |
||||
|
||||
const isEmpty = o => { |
||||
for (let k in o) { |
||||
if (o.hasOwnProperty(k)) { |
||||
return |
||||
} |
||||
} |
||||
return true |
||||
} |
||||
|
||||
const unixPath = v => v.replace(/\\/g, '/') |
||||
|
||||
const rewriterPath = p => { |
||||
if (isEmpty(pageRewriter)) { |
||||
return |
||||
} |
||||
|
||||
for (let k in pageRewriter) { |
||||
let regx = new RegExp(k) |
||||
|
||||
if (regx.test(unixPath(p))) { |
||||
return pageRewriter[k] |
||||
} |
||||
} |
||||
} |
||||
|
||||
const pages = glob.sync(['*/!(_*).html'], { cwd: viewDir }).map(p => { |
||||
let pagePath = `${path.join(viewDir, p)}` |
||||
let newPagePath = rewriterPath(pagePath) |
||||
|
||||
let entry = getPageEntry(p.replace('.html', '')) |
||||
let chunks = ['common'] |
||||
if (entry) { |
||||
chunks.push(entry) |
||||
} |
||||
return new HtmlWebpackPlugin({ |
||||
filename: newPagePath || path.join('view', p), |
||||
template: `html-loader?min=false!${path.join(viewDir, p)}`, |
||||
cache: true, |
||||
inject: true, |
||||
chunks: chunks, |
||||
minify: minifierConfig |
||||
}) |
||||
}) |
||||
|
||||
const baseConfig = { |
||||
entry: jsEntry, |
||||
output: { |
||||
path: distDir, |
||||
publicPath: '/', |
||||
filename: 'js/[name].[chunkhash:7].js' |
||||
}, |
||||
devServer: { |
||||
historyApiFallback: true, |
||||
hot: true, |
||||
inline: true, |
||||
progress: true |
||||
}, |
||||
module: { |
||||
rules: [ |
||||
{ |
||||
test: /\.js$/, |
||||
exclude: /(node_modules|bower_components)/, |
||||
use: [ |
||||
{ |
||||
loader: 'babel-loader', |
||||
options: { |
||||
cacheDirectory: true, |
||||
cacheIdentifier: true |
||||
} |
||||
} |
||||
] |
||||
}, |
||||
{ |
||||
test: /\.(png|jpe?g|gif|svg|cur)(\?.*)?$/, |
||||
loader: 'file-loader', |
||||
options: { |
||||
name: 'images/[name].[ext]?[hash]' |
||||
} |
||||
}, |
||||
{ |
||||
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, |
||||
loader: 'url-loader', |
||||
options: { |
||||
limit: 10000, |
||||
// publicPath: distDir,
|
||||
name: 'font/[name].[hash:7].[ext]' |
||||
} |
||||
} |
||||
] |
||||
}, |
||||
resolve: { |
||||
modules: [ |
||||
resolve('node_modules'), |
||||
resolve('src'), |
||||
resolve('src/js') |
||||
], |
||||
alias: { |
||||
'@': resolve('src/js'), |
||||
'~': resolve('src/lib') |
||||
}, |
||||
extensions: ['.js', 'json', '.vue', '.scss'] |
||||
}, |
||||
externals: { |
||||
'vue': 'Vue', |
||||
'vuex': 'Vuex', |
||||
'vue-router': 'VueRouter', |
||||
'jquery': '$', |
||||
'lodash': '_', |
||||
'bootstrap': 'bootstrap', |
||||
'd3': 'd3', |
||||
'canvg': 'canvg', |
||||
'html2canvas': 'html2canvas', |
||||
'./jsplumb': 'jsPlumb', |
||||
'./highlight.js': 'highlight.js', |
||||
'./clipboard': 'clipboard', |
||||
'./codemirror': 'CodeMirror' |
||||
}, |
||||
plugins: [ |
||||
new webpack.ProvidePlugin({ vue: 'Vue', _: 'lodash' }), |
||||
new HtmlWebpackExtPlugin({ |
||||
cache: true, |
||||
delimiter: '$', |
||||
locals: { |
||||
NODE_ENV:isProduction |
||||
} |
||||
}), |
||||
...pages |
||||
] |
||||
} |
||||
|
||||
module.exports = { |
||||
isProduction, |
||||
assetsDir, |
||||
distDir, |
||||
baseConfig |
||||
} |
@ -0,0 +1,87 @@
|
||||
/* |
||||
* Licensed to the Apache Software Foundation (ASF) under one or more |
||||
* contributor license agreements. See the NOTICE file distributed with |
||||
* this work for additional information regarding copyright ownership. |
||||
* The ASF licenses this file to You under the Apache License, Version 2.0 |
||||
* (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
const fs = require('fs'); |
||||
const path = require('path') |
||||
const glob = require('globby') |
||||
|
||||
function moduleName (modules) { |
||||
let filename = path.basename(modules) |
||||
let parts = filename.split('.') |
||||
parts.pop() |
||||
filename = parts.join('.') |
||||
return path.dirname(modules) + '/' + filename |
||||
} |
||||
|
||||
const jsEntry = () => { |
||||
const obj = {} |
||||
const files = glob.sync([ |
||||
'./src/js/conf/login/**/*.vue', |
||||
'./src/js/conf/login/**/*.js', |
||||
'./src/js/conf/home/**/**/**/**/**/**/**/**/*.vue', |
||||
'./src/js/conf/home/**/**/**/**/**/**/**/**/*.js', |
||||
'./src/js/module/**/**/**/**/**/*.vue', |
||||
'./src/js/module/**/**/**/**/**/*.js' |
||||
]) |
||||
files.forEach(val => { |
||||
let parts = val.split(/[\\/]/) |
||||
parts.shift() |
||||
parts.shift() |
||||
let modules = parts.join('/') |
||||
let entry = moduleName(modules) |
||||
obj[entry] = val |
||||
}) |
||||
return obj |
||||
} |
||||
/* eslint-disable */ |
||||
let reg = /\$t\([\w,""''“”~\-\s.?!,。:;《》、\+\/<>()?!\u4e00-\u9fa5]*\)/g |
||||
let map = {} |
||||
let entryPathList = '' |
||||
let matchPathList = '' |
||||
let jsEntryObj = jsEntry() |
||||
|
||||
for (let i in jsEntryObj) { |
||||
entryPathList += jsEntryObj[i] + '\n' |
||||
let data = fs.readFileSync(path.join(jsEntryObj[i]), 'utf-8') |
||||
if (reg.test(data)) { |
||||
matchPathList += jsEntryObj[i] + '\n' |
||||
let str = data.replace(/[""'']/g, '') |
||||
str.replace(reg, function () { |
||||
if (arguments && arguments[0]) { |
||||
let key = arguments[0] |
||||
key = key.substring(3, key.length - 1) |
||||
map[key] = key |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
|
||||
let outPath = path.join(__dirname, '../src/js/module/i18n/locale/zh_CN.js') |
||||
fs.unlink(outPath, (err) => { |
||||
if (err) { |
||||
console.error('删除zh_CN.js文件出错 -- \n', err) |
||||
} else { |
||||
console.log('删除zh_CN.js文件成功') |
||||
} |
||||
}) |
||||
fs.writeFile(outPath, 'export default ' + JSON.stringify(map, null, 2), function (err) { |
||||
if (err) { |
||||
console.error('写入zh_CN.js文件出错 -- \n', err) |
||||
} else { |
||||
console.log('写入zh_CN.js文件成功') |
||||
} |
||||
}) |
@ -0,0 +1,112 @@
|
||||
/* |
||||
* Licensed to the Apache Software Foundation (ASF) under one or more |
||||
* contributor license agreements. See the NOTICE file distributed with |
||||
* this work for additional information regarding copyright ownership. |
||||
* The ASF licenses this file to You under the Apache License, Version 2.0 |
||||
* (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
const webpack = require('webpack') |
||||
const merge = require('webpack-merge') |
||||
const { assetsDir, baseConfig } = require('./config') |
||||
const ExtractTextPlugin = require('extract-text-webpack-plugin') |
||||
const ProgressPlugin = require('./../src/lib/@fedor/progress-webpack-plugin') |
||||
const getEnv = require('env-parse').getEnv |
||||
|
||||
const config = merge.smart(baseConfig, { |
||||
devtool: 'eval-source-map', |
||||
output: { |
||||
filename: 'js/[name].js' |
||||
}, |
||||
module: { |
||||
rules: [ |
||||
{ |
||||
test: /\.vue$/, |
||||
loader: 'vue-loader', |
||||
options: { |
||||
hotReload: true // 开启热重载
|
||||
} |
||||
}, |
||||
{ |
||||
test: /\.css$/, |
||||
loader: ExtractTextPlugin.extract({ |
||||
use: [ |
||||
'css-loader', |
||||
{ |
||||
loader: 'postcss-loader', |
||||
options: { |
||||
plugins: (loader) => [ |
||||
require('autoprefixer')({ |
||||
'browsers': [ '> 1%', 'last 3 versions', 'ie >= 9' ] |
||||
}), |
||||
require('cssnano') |
||||
] |
||||
} |
||||
} |
||||
], |
||||
fallback: ['vue-style-loader'] |
||||
}) |
||||
}, |
||||
{ |
||||
test: /\.scss$/, |
||||
loader: ExtractTextPlugin.extract({ |
||||
use: [ |
||||
'css-loader', |
||||
'sass-loader', |
||||
{ |
||||
loader: 'postcss-loader', |
||||
options: { |
||||
plugins: (loader) => [ |
||||
require('autoprefixer')({ |
||||
'browsers': [ '> 1%', 'last 3 versions', 'ie >= 9' ] |
||||
}), |
||||
require('cssnano') |
||||
] |
||||
} |
||||
} |
||||
], |
||||
fallback: ['vue-style-loader'] |
||||
}) |
||||
} |
||||
] |
||||
}, |
||||
devServer: { |
||||
hot: true, |
||||
contentBase: assetsDir, |
||||
publicPath: baseConfig.output.publicPath, |
||||
port: getEnv('DEV_PORT', 8888), |
||||
host: getEnv('DEV_HOST', 'localhost'), |
||||
noInfo: false, |
||||
historyApiFallback: true, |
||||
disableHostCheck: true, |
||||
proxy: { |
||||
'/escheduler': { |
||||
target: getEnv('API_BASE', 'http://local.dev:8080/backend'), |
||||
changeOrigin: true |
||||
} |
||||
}, |
||||
progress: false, |
||||
quiet: false, |
||||
stats: { |
||||
colors: true |
||||
}, |
||||
clientLogLevel: 'none' |
||||
}, |
||||
plugins: [ |
||||
new ProgressPlugin(), |
||||
new webpack.HotModuleReplacementPlugin(), |
||||
new ExtractTextPlugin({ filename: 'css/[name].css', allChunks: true }), |
||||
new webpack.optimize.CommonsChunkPlugin({ name: 'common', filename: 'js/[name].js' }), |
||||
new webpack.optimize.OccurrenceOrderPlugin() |
||||
] |
||||
}) |
||||
|
||||
module.exports = config |
@ -0,0 +1,132 @@
|
||||
/* |
||||
* Licensed to the Apache Software Foundation (ASF) under one or more |
||||
* contributor license agreements. See the NOTICE file distributed with |
||||
* this work for additional information regarding copyright ownership. |
||||
* The ASF licenses this file to You under the Apache License, Version 2.0 |
||||
* (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
const path = require('path') |
||||
const webpack = require('webpack') |
||||
const merge = require('webpack-merge') |
||||
const CopyWebpackPlugin = require('copy-webpack-plugin') |
||||
const { baseConfig } = require('./config') |
||||
const ExtractTextPlugin = require('extract-text-webpack-plugin') |
||||
const UglifyJSPlugin = require('uglifyjs-webpack-plugin') |
||||
const OptimizeCssAssetsPlugin = require('optimize-css-assets-webpack-plugin') |
||||
const ProgressPlugin = require('./../src/lib/@fedor/progress-webpack-plugin') |
||||
|
||||
const resolve = dir => |
||||
path.resolve(__dirname, '..', dir) |
||||
|
||||
const config = merge.smart(baseConfig, { |
||||
devtool: 'source-map', |
||||
output: { |
||||
filename: 'js/[name].[chunkhash:7].js' |
||||
}, |
||||
module: { |
||||
rules: [ |
||||
{ |
||||
test: /\.vue$/, |
||||
loader: 'vue-loader', |
||||
options: { |
||||
hotReload: false // 开启热重载
|
||||
} |
||||
}, |
||||
{ |
||||
test: /\.css$/, |
||||
loader: ExtractTextPlugin.extract({ |
||||
use: [ |
||||
'css-loader', |
||||
{ |
||||
loader: 'postcss-loader', |
||||
options: { |
||||
plugins: (loader) => [ |
||||
require('autoprefixer')({ |
||||
'browsers': [ '> 1%', 'last 3 versions', 'ie >= 9' ] |
||||
}), |
||||
require('cssnano') |
||||
] |
||||
} |
||||
} |
||||
], |
||||
fallback: ['vue-style-loader'] |
||||
}) |
||||
}, |
||||
{ |
||||
test: /\.scss$/, |
||||
loader: ExtractTextPlugin.extract({ |
||||
use: [ |
||||
'css-loader', |
||||
'sass-loader', |
||||
{ |
||||
loader: 'postcss-loader', |
||||
options: { |
||||
plugins: (loader) => [ |
||||
require('autoprefixer')({ |
||||
'browsers': [ '> 1%', 'last 3 versions', 'ie >= 9' ] |
||||
}), |
||||
require('cssnano') |
||||
] |
||||
} |
||||
} |
||||
], |
||||
fallback: ['vue-style-loader'] |
||||
}) |
||||
} |
||||
] |
||||
}, |
||||
plugins: [ |
||||
new ProgressPlugin(), |
||||
new ExtractTextPlugin({ filename: 'css/[name].[contenthash:7].css', allChunks: true }), |
||||
new webpack.optimize.CommonsChunkPlugin({ name: 'common', filename: 'js/[name].[hash:7].js' }), |
||||
new webpack.optimize.OccurrenceOrderPlugin(), |
||||
new OptimizeCssAssetsPlugin({ |
||||
assetNameRegExp: /\.css$/g, |
||||
cssProcessor: require('cssnano'), |
||||
cssProcessorOptions: { discardComments: { removeAll: true } }, |
||||
canPrint: true |
||||
}), |
||||
new UglifyJSPlugin({ |
||||
parallel: true, |
||||
sourceMap: true, |
||||
uglifyOptions: { |
||||
compress: { |
||||
drop_console: true, |
||||
drop_debugger: true |
||||
}, |
||||
comments: function (n, c) { |
||||
/*! IMPORTANT: Please preserve 3rd-party library license info, inspired from @allex/amd-build-worker/config/jsplumb.js */ |
||||
var text = c.value, type = c.type |
||||
if (type === 'comment2') { |
||||
return /^!|@preserve|@license|@cc_on|MIT/i.test(text) |
||||
} |
||||
} |
||||
} |
||||
}), |
||||
new CopyWebpackPlugin([ |
||||
{ |
||||
from: resolve('src/combo'), |
||||
to: resolve('dist/combo') |
||||
}, |
||||
{ |
||||
from: resolve('src/lib'), |
||||
to: resolve('dist/lib') |
||||
}, |
||||
{ |
||||
from: resolve('src/images'), |
||||
to: resolve('dist/images') |
||||
}, |
||||
]), |
||||
] |
||||
}) |
||||
|
||||
module.exports = config |
@ -0,0 +1,88 @@
|
||||
/* |
||||
* Licensed to the Apache Software Foundation (ASF) under one or more |
||||
* contributor license agreements. See the NOTICE file distributed with |
||||
* this work for additional information regarding copyright ownership. |
||||
* The ASF licenses this file to You under the Apache License, Version 2.0 |
||||
* (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
const webpack = require('webpack') |
||||
const merge = require('webpack-merge') |
||||
const { baseConfig } = require('./config') |
||||
const ExtractTextPlugin = require('extract-text-webpack-plugin') |
||||
|
||||
const config = merge.smart(baseConfig, { |
||||
devtool: 'inline-source-map', |
||||
output: { |
||||
filename: 'js/[name].js' |
||||
}, |
||||
module: { |
||||
rules: [ |
||||
{ |
||||
test: /\.vue$/, |
||||
loader: 'vue-loader', |
||||
options: { |
||||
hotReload: true // 开启热重载
|
||||
} |
||||
}, |
||||
{ |
||||
test: /\.css$/, |
||||
loader: ExtractTextPlugin.extract({ |
||||
use: [ |
||||
'css-loader', |
||||
{ |
||||
loader: 'postcss-loader', |
||||
options: { |
||||
plugins: (loader) => [ |
||||
require('autoprefixer')({ |
||||
'browsers': [ '> 1%', 'last 3 versions', 'ie >= 9' ] |
||||
}), |
||||
require('cssnano') |
||||
] |
||||
} |
||||
} |
||||
], |
||||
fallback: ['vue-style-loader'] |
||||
}) |
||||
}, |
||||
{ |
||||
test: /\.scss$/, |
||||
loader: ExtractTextPlugin.extract({ |
||||
use: [ |
||||
'css-loader', |
||||
'sass-loader', |
||||
{ |
||||
loader: 'postcss-loader', |
||||
options: { |
||||
plugins: (loader) => [ |
||||
require('autoprefixer')({ |
||||
'browsers': [ '> 1%', 'last 3 versions', 'ie >= 9' ] |
||||
}), |
||||
require('cssnano') |
||||
] |
||||
} |
||||
} |
||||
], |
||||
fallback: ['vue-style-loader'] |
||||
}) |
||||
} |
||||
] |
||||
}, |
||||
externals: '', |
||||
plugins: [ |
||||
new webpack.HotModuleReplacementPlugin(), |
||||
new ExtractTextPlugin({ filename: 'css/[name].css', allChunks: true }), |
||||
new webpack.optimize.OccurrenceOrderPlugin() |
||||
] |
||||
}) |
||||
|
||||
module.exports = config |
@ -0,0 +1,97 @@
|
||||
{ |
||||
"name": "escheduler", |
||||
"version": "1.0.0", |
||||
"description": "调度平台前端项目", |
||||
"author": "gongzijian <gongzijian@analysys.com.cn>", |
||||
"scripts": { |
||||
"build": "npm run clean && cross-env NODE_ENV=production webpack --config ./build/webpack.config.prod.js", |
||||
"dev": "cross-env NODE_ENV=development webpack-dev-server --config ./build/webpack.config.dev.js", |
||||
"clean": "rimraf dist", |
||||
"lint": "standard \"**/*.{js,vue}\"", |
||||
"lint:fix": "standard \"**/*.{js,vue}\" --fix", |
||||
"start": "npm run dev", |
||||
"combo": "node ./build/combo.js" |
||||
}, |
||||
"dependencies": { |
||||
"autoprefixer": "^9.1.0", |
||||
"babel-core": "^6.25.0", |
||||
"babel-eslint": "^8.2.2", |
||||
"babel-helper-vue-jsx-merge-props": "^2.0.2", |
||||
"babel-loader": "^7.1.1", |
||||
"babel-plugin-syntax-dynamic-import": "^6.18.0", |
||||
"babel-plugin-syntax-jsx": "^6.18.0", |
||||
"babel-plugin-transform-class-properties": "^6.24.1", |
||||
"babel-plugin-transform-object-rest-spread": "^6.26.0", |
||||
"babel-plugin-transform-remove-console": "^6.9.4", |
||||
"babel-plugin-transform-runtime": "^6.23.0", |
||||
"babel-plugin-transform-vue-jsx": "^3.5.0", |
||||
"babel-preset-env": "^1.6.1", |
||||
"babel-runtime": "^6.26.0", |
||||
"bootstrap": "3.3.7", |
||||
"canvg": "1.5", |
||||
"clipboard": "^2.0.1", |
||||
"codemirror": "^5.43.0", |
||||
"copy-webpack-plugin": "^4.5.2", |
||||
"cross-env": "^5.2.0", |
||||
"css-loader": "^0.28.8", |
||||
"cssnano": "4.1.10", |
||||
"d3": "^3.5.17", |
||||
"dayjs": "^1.7.8", |
||||
"echarts": "^4.1.0", |
||||
"env-parse": "^1.0.5", |
||||
"extract-text-webpack-plugin": "^3.0.2", |
||||
"file-loader": "^1.1.11", |
||||
"ghooks": "^2.0.4", |
||||
"globby": "^8.0.1", |
||||
"html-loader": "^0.5.5", |
||||
"html-webpack-ext-plugin": "^1.0.0", |
||||
"html-webpack-plugin": "^3.2.0", |
||||
"html2canvas": "^0.5.0-beta4", |
||||
"jsplumb": "^2.8.6", |
||||
"lodash": "^4.17.11", |
||||
"node-sass": "^4.9.2", |
||||
"optimize-css-assets-webpack-plugin": "3.2.0", |
||||
"postcss-loader": "^2.1.6", |
||||
"rimraf": "^2.6.2", |
||||
"sass-loader": "^7.1.0", |
||||
"uglifyjs-webpack-plugin": "^1.2.7", |
||||
"url-loader": "^0.5.9", |
||||
"vue-loader": "^13.7.0", |
||||
"vue-style-loader": "^4.1.1", |
||||
"vue-template-compiler": "^2.5.16", |
||||
"vuex-router-sync": "^4.1.2", |
||||
"webpack": "^3.12.0", |
||||
"webpack-dev-server": "^2.11.2", |
||||
"webpack-merge": "^4.1.4" |
||||
}, |
||||
"standard": { |
||||
"parser": "babel-eslint", |
||||
"ignore": [ |
||||
"src/~/**", |
||||
"src/view/docs/**", |
||||
"src/font/*", |
||||
"src/combo/**", |
||||
"dist/**", |
||||
"src/lib/**", |
||||
"build/**" |
||||
] |
||||
}, |
||||
"config": { |
||||
"ghooks": {} |
||||
}, |
||||
"devDependencies": { |
||||
"jasmine-core": "^3.2.1", |
||||
"jquery": "1.12.4", |
||||
"karma": "^3.0.0", |
||||
"karma-browserstack-launcher": "^1.3.0", |
||||
"karma-chrome-launcher": "^2.2.0", |
||||
"karma-coverage": "^1.1.2", |
||||
"karma-jasmine": "^1.1.2", |
||||
"karma-sourcemap-loader": "^0.3.7", |
||||
"karma-spec-reporter": "^0.0.32", |
||||
"karma-webpack": "^3.0.0", |
||||
"vue": "^2.5.17", |
||||
"vue-router": "2.7.0", |
||||
"vuex": "^3.0.0" |
||||
} |
||||
} |
After Width: | Height: | Size: 434 KiB |
@ -0,0 +1,539 @@
|
||||
/* Logo 字体 */ |
||||
@font-face { |
||||
font-family: "iconfont logo"; |
||||
src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834'); |
||||
src: url('https://at.alicdn.com/t/font_985780_km7mi63cihi.eot?t=1545807318834#iefix') format('embedded-opentype'), |
||||
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.woff?t=1545807318834') format('woff'), |
||||
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.ttf?t=1545807318834') format('truetype'), |
||||
url('https://at.alicdn.com/t/font_985780_km7mi63cihi.svg?t=1545807318834#iconfont') format('svg'); |
||||
} |
||||
|
||||
.logo { |
||||
font-family: "iconfont logo"; |
||||
font-size: 160px; |
||||
font-style: normal; |
||||
-webkit-font-smoothing: antialiased; |
||||
-moz-osx-font-smoothing: grayscale; |
||||
} |
||||
|
||||
/* tabs */ |
||||
.nav-tabs { |
||||
position: relative; |
||||
} |
||||
|
||||
.nav-tabs .nav-more { |
||||
position: absolute; |
||||
right: 0; |
||||
bottom: 0; |
||||
height: 42px; |
||||
line-height: 42px; |
||||
color: #666; |
||||
} |
||||
|
||||
#tabs { |
||||
border-bottom: 1px solid #eee; |
||||
} |
||||
|
||||
#tabs li { |
||||
cursor: pointer; |
||||
width: 100px; |
||||
height: 40px; |
||||
line-height: 40px; |
||||
text-align: center; |
||||
font-size: 16px; |
||||
border-bottom: 2px solid transparent; |
||||
position: relative; |
||||
z-index: 1; |
||||
margin-bottom: -1px; |
||||
color: #666; |
||||
} |
||||
|
||||
|
||||
#tabs .active { |
||||
border-bottom-color: #f00; |
||||
color: #222; |
||||
} |
||||
|
||||
.tab-container .content { |
||||
display: none; |
||||
} |
||||
|
||||
/* 页面布局 */ |
||||
.main { |
||||
padding: 30px 100px; |
||||
width: 960px; |
||||
margin: 0 auto; |
||||
} |
||||
|
||||
.main .logo { |
||||
color: #333; |
||||
text-align: left; |
||||
margin-bottom: 30px; |
||||
line-height: 1; |
||||
height: 110px; |
||||
margin-top: -50px; |
||||
overflow: hidden; |
||||
*zoom: 1; |
||||
} |
||||
|
||||
.main .logo a { |
||||
font-size: 160px; |
||||
color: #333; |
||||
} |
||||
|
||||
.helps { |
||||
margin-top: 40px; |
||||
} |
||||
|
||||
.helps pre { |
||||
padding: 20px; |
||||
margin: 10px 0; |
||||
border: solid 1px #e7e1cd; |
||||
background-color: #fffdef; |
||||
overflow: auto; |
||||
} |
||||
|
||||
.icon_lists { |
||||
width: 100% !important; |
||||
overflow: hidden; |
||||
*zoom: 1; |
||||
} |
||||
|
||||
.icon_lists li { |
||||
width: 100px; |
||||
margin-bottom: 10px; |
||||
margin-right: 20px; |
||||
text-align: center; |
||||
list-style: none !important; |
||||
cursor: default; |
||||
} |
||||
|
||||
.icon_lists li .code-name { |
||||
line-height: 1.2; |
||||
} |
||||
|
||||
.icon_lists .icon { |
||||
display: block; |
||||
height: 100px; |
||||
line-height: 100px; |
||||
font-size: 42px; |
||||
margin: 10px auto; |
||||
color: #333; |
||||
-webkit-transition: font-size 0.25s linear, width 0.25s linear; |
||||
-moz-transition: font-size 0.25s linear, width 0.25s linear; |
||||
transition: font-size 0.25s linear, width 0.25s linear; |
||||
} |
||||
|
||||
.icon_lists .icon:hover { |
||||
font-size: 100px; |
||||
} |
||||
|
||||
.icon_lists .svg-icon { |
||||
/* 通过设置 font-size 来改变图标大小 */ |
||||
width: 1em; |
||||
/* 图标和文字相邻时,垂直对齐 */ |
||||
vertical-align: -0.15em; |
||||
/* 通过设置 color 来改变 SVG 的颜色/fill */ |
||||
fill: currentColor; |
||||
/* path 和 stroke 溢出 viewBox 部分在 IE 下会显示 |
||||
normalize.css 中也包含这行 */ |
||||
overflow: hidden; |
||||
} |
||||
|
||||
.icon_lists li .name, |
||||
.icon_lists li .code-name { |
||||
color: #666; |
||||
} |
||||
|
||||
/* markdown 样式 */ |
||||
.markdown { |
||||
color: #666; |
||||
font-size: 14px; |
||||
line-height: 1.8; |
||||
} |
||||
|
||||
.highlight { |
||||
line-height: 1.5; |
||||
} |
||||
|
||||
.markdown img { |
||||
vertical-align: middle; |
||||
max-width: 100%; |
||||
} |
||||
|
||||
.markdown h1 { |
||||
color: #404040; |
||||
font-weight: 500; |
||||
line-height: 40px; |
||||
margin-bottom: 24px; |
||||
} |
||||
|
||||
.markdown h2, |
||||
.markdown h3, |
||||
.markdown h4, |
||||
.markdown h5, |
||||
.markdown h6 { |
||||
color: #404040; |
||||
margin: 1.6em 0 0.6em 0; |
||||
font-weight: 500; |
||||
clear: both; |
||||
} |
||||
|
||||
.markdown h1 { |
||||
font-size: 28px; |
||||
} |
||||
|
||||
.markdown h2 { |
||||
font-size: 22px; |
||||
} |
||||
|
||||
.markdown h3 { |
||||
font-size: 16px; |
||||
} |
||||
|
||||
.markdown h4 { |
||||
font-size: 14px; |
||||
} |
||||
|
||||
.markdown h5 { |
||||
font-size: 12px; |
||||
} |
||||
|
||||
.markdown h6 { |
||||
font-size: 12px; |
||||
} |
||||
|
||||
.markdown hr { |
||||
height: 1px; |
||||
border: 0; |
||||
background: #e9e9e9; |
||||
margin: 16px 0; |
||||
clear: both; |
||||
} |
||||
|
||||
.markdown p { |
||||
margin: 1em 0; |
||||
} |
||||
|
||||
.markdown>p, |
||||
.markdown>blockquote, |
||||
.markdown>.highlight, |
||||
.markdown>ol, |
||||
.markdown>ul { |
||||
width: 80%; |
||||
} |
||||
|
||||
.markdown ul>li { |
||||
list-style: circle; |
||||
} |
||||
|
||||
.markdown>ul li, |
||||
.markdown blockquote ul>li { |
||||
margin-left: 20px; |
||||
padding-left: 4px; |
||||
} |
||||
|
||||
.markdown>ul li p, |
||||
.markdown>ol li p { |
||||
margin: 0.6em 0; |
||||
} |
||||
|
||||
.markdown ol>li { |
||||
list-style: decimal; |
||||
} |
||||
|
||||
.markdown>ol li, |
||||
.markdown blockquote ol>li { |
||||
margin-left: 20px; |
||||
padding-left: 4px; |
||||
} |
||||
|
||||
.markdown code { |
||||
margin: 0 3px; |
||||
padding: 0 5px; |
||||
background: #eee; |
||||
border-radius: 3px; |
||||
} |
||||
|
||||
.markdown strong, |
||||
.markdown b { |
||||
font-weight: 600; |
||||
} |
||||
|
||||
.markdown>table { |
||||
border-collapse: collapse; |
||||
border-spacing: 0px; |
||||
empty-cells: show; |
||||
border: 1px solid #e9e9e9; |
||||
width: 95%; |
||||
margin-bottom: 24px; |
||||
} |
||||
|
||||
.markdown>table th { |
||||
white-space: nowrap; |
||||
color: #333; |
||||
font-weight: 600; |
||||
} |
||||
|
||||
.markdown>table th, |
||||
.markdown>table td { |
||||
border: 1px solid #e9e9e9; |
||||
padding: 8px 16px; |
||||
text-align: left; |
||||
} |
||||
|
||||
.markdown>table th { |
||||
background: #F7F7F7; |
||||
} |
||||
|
||||
.markdown blockquote { |
||||
font-size: 90%; |
||||
color: #999; |
||||
border-left: 4px solid #e9e9e9; |
||||
padding-left: 0.8em; |
||||
margin: 1em 0; |
||||
} |
||||
|
||||
.markdown blockquote p { |
||||
margin: 0; |
||||
} |
||||
|
||||
.markdown .anchor { |
||||
opacity: 0; |
||||
transition: opacity 0.3s ease; |
||||
margin-left: 8px; |
||||
} |
||||
|
||||
.markdown .waiting { |
||||
color: #ccc; |
||||
} |
||||
|
||||
.markdown h1:hover .anchor, |
||||
.markdown h2:hover .anchor, |
||||
.markdown h3:hover .anchor, |
||||
.markdown h4:hover .anchor, |
||||
.markdown h5:hover .anchor, |
||||
.markdown h6:hover .anchor { |
||||
opacity: 1; |
||||
display: inline-block; |
||||
} |
||||
|
||||
.markdown>br, |
||||
.markdown>p>br { |
||||
clear: both; |
||||
} |
||||
|
||||
|
||||
.hljs { |
||||
display: block; |
||||
background: white; |
||||
padding: 0.5em; |
||||
color: #333333; |
||||
overflow-x: auto; |
||||
} |
||||
|
||||
.hljs-comment, |
||||
.hljs-meta { |
||||
color: #969896; |
||||
} |
||||
|
||||
.hljs-string, |
||||
.hljs-variable, |
||||
.hljs-template-variable, |
||||
.hljs-strong, |
||||
.hljs-emphasis, |
||||
.hljs-quote { |
||||
color: #df5000; |
||||
} |
||||
|
||||
.hljs-keyword, |
||||
.hljs-selector-tag, |
||||
.hljs-type { |
||||
color: #a71d5d; |
||||
} |
||||
|
||||
.hljs-literal, |
||||
.hljs-symbol, |
||||
.hljs-bullet, |
||||
.hljs-attribute { |
||||
color: #0086b3; |
||||
} |
||||
|
||||
.hljs-section, |
||||
.hljs-name { |
||||
color: #63a35c; |
||||
} |
||||
|
||||
.hljs-tag { |
||||
color: #333333; |
||||
} |
||||
|
||||
.hljs-title, |
||||
.hljs-attr, |
||||
.hljs-selector-id, |
||||
.hljs-selector-class, |
||||
.hljs-selector-attr, |
||||
.hljs-selector-pseudo { |
||||
color: #795da3; |
||||
} |
||||
|
||||
.hljs-addition { |
||||
color: #55a532; |
||||
background-color: #eaffea; |
||||
} |
||||
|
||||
.hljs-deletion { |
||||
color: #bd2c00; |
||||
background-color: #ffecec; |
||||
} |
||||
|
||||
.hljs-link { |
||||
text-decoration: underline; |
||||
} |
||||
|
||||
/* 代码高亮 */ |
||||
/* PrismJS 1.15.0 |
||||
https://prismjs.com/download.html#themes=prism&languages=markup+css+clike+javascript */ |
||||
/** |
||||
* prism.js default theme for JavaScript, CSS and HTML |
||||
* Based on dabblet (http://dabblet.com) |
||||
* @author Lea Verou |
||||
*/ |
||||
code[class*="language-"], |
||||
pre[class*="language-"] { |
||||
color: black; |
||||
background: none; |
||||
text-shadow: 0 1px white; |
||||
font-family: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; |
||||
text-align: left; |
||||
white-space: pre; |
||||
word-spacing: normal; |
||||
word-break: normal; |
||||
word-wrap: normal; |
||||
line-height: 1.5; |
||||
|
||||
-moz-tab-size: 4; |
||||
-o-tab-size: 4; |
||||
tab-size: 4; |
||||
|
||||
-webkit-hyphens: none; |
||||
-moz-hyphens: none; |
||||
-ms-hyphens: none; |
||||
hyphens: none; |
||||
} |
||||
|
||||
pre[class*="language-"]::-moz-selection, |
||||
pre[class*="language-"] ::-moz-selection, |
||||
code[class*="language-"]::-moz-selection, |
||||
code[class*="language-"] ::-moz-selection { |
||||
text-shadow: none; |
||||
background: #b3d4fc; |
||||
} |
||||
|
||||
pre[class*="language-"]::selection, |
||||
pre[class*="language-"] ::selection, |
||||
code[class*="language-"]::selection, |
||||
code[class*="language-"] ::selection { |
||||
text-shadow: none; |
||||
background: #b3d4fc; |
||||
} |
||||
|
||||
@media print { |
||||
|
||||
code[class*="language-"], |
||||
pre[class*="language-"] { |
||||
text-shadow: none; |
||||
} |
||||
} |
||||
|
||||
/* Code blocks */ |
||||
pre[class*="language-"] { |
||||
padding: 1em; |
||||
margin: .5em 0; |
||||
overflow: auto; |
||||
} |
||||
|
||||
:not(pre)>code[class*="language-"], |
||||
pre[class*="language-"] { |
||||
background: #f5f2f0; |
||||
} |
||||
|
||||
/* Inline code */ |
||||
:not(pre)>code[class*="language-"] { |
||||
padding: .1em; |
||||
border-radius: .3em; |
||||
white-space: normal; |
||||
} |
||||
|
||||
.token.comment, |
||||
.token.prolog, |
||||
.token.doctype, |
||||
.token.cdata { |
||||
color: slategray; |
||||
} |
||||
|
||||
.token.punctuation { |
||||
color: #999; |
||||
} |
||||
|
||||
.namespace { |
||||
opacity: .7; |
||||
} |
||||
|
||||
.token.property, |
||||
.token.tag, |
||||
.token.boolean, |
||||
.token.number, |
||||
.token.constant, |
||||
.token.symbol, |
||||
.token.deleted { |
||||
color: #905; |
||||
} |
||||
|
||||
.token.selector, |
||||
.token.attr-name, |
||||
.token.string, |
||||
.token.char, |
||||
.token.builtin, |
||||
.token.inserted { |
||||
color: #690; |
||||
} |
||||
|
||||
.token.operator, |
||||
.token.entity, |
||||
.token.url, |
||||
.language-css .token.string, |
||||
.style .token.string { |
||||
color: #9a6e3a; |
||||
background: hsla(0, 0%, 100%, .5); |
||||
} |
||||
|
||||
.token.atrule, |
||||
.token.attr-value, |
||||
.token.keyword { |
||||
color: #07a; |
||||
} |
||||
|
||||
.token.function, |
||||
.token.class-name { |
||||
color: #DD4A68; |
||||
} |
||||
|
||||
.token.regex, |
||||
.token.important, |
||||
.token.variable { |
||||
color: #e90; |
||||
} |
||||
|
||||
.token.important, |
||||
.token.bold { |
||||
font-weight: bold; |
||||
} |
||||
|
||||
.token.italic { |
||||
font-style: italic; |
||||
} |
||||
|
||||
.token.entity { |
||||
cursor: help; |
||||
} |
After Width: | Height: | Size: 4.2 KiB |
@ -0,0 +1,15 @@
|
||||
<template> |
||||
<m-layout> |
||||
<m-nav slot="top"></m-nav> |
||||
<router-view slot="bottom"></router-view> |
||||
</m-layout> |
||||
</template> |
||||
|
||||
<script> |
||||
import mLayout from '@/module/components/layout/layout' |
||||
import mNav from '@/module/components/nav/nav' |
||||
export default { |
||||
name: 'app', |
||||
components: { mLayout, mNav } |
||||
} |
||||
</script> |
@ -0,0 +1,85 @@
|
||||
/* |
||||
* Licensed to the Apache Software Foundation (ASF) under one or more |
||||
* contributor license agreements. See the NOTICE file distributed with |
||||
* this work for additional information regarding copyright ownership. |
||||
* The ASF licenses this file to You under the Apache License, Version 2.0 |
||||
* (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
// The Vue build version to load with the `import` command
|
||||
// (runtime-only or standalone) has been set in webpack.base.conf with an alias.
|
||||
import $ from 'jquery' |
||||
import Vue from 'vue' |
||||
import App from './App' |
||||
import router from './router' |
||||
import store from './store' |
||||
import i18n from '@/module/i18n' |
||||
import { sync } from 'vuex-router-sync' |
||||
import Chart from '~/@analysys/ana-charts' |
||||
import themeData from '@/module/echarts/themeData.json' |
||||
import Permissions from '@/module/permissions' |
||||
import '~/@analysys/ans-ui/lib/ans-ui.min.css' |
||||
import ans from '~/@analysys/ans-ui/lib/ans-ui.min' |
||||
import en_US from '~/@analysys/ans-ui/lib/locale/en' // eslint-disable-line
|
||||
import 'sass/conf/home/index.scss' |
||||
|
||||
// Component internationalization
|
||||
let useOpt = i18n.globalScope.LOCALE === 'en_US' ? { locale: en_US } : {} |
||||
|
||||
// Vue.use(ans)
|
||||
Vue.use(ans, useOpt) |
||||
|
||||
sync(store, router) |
||||
|
||||
Vue.config.devtools = true |
||||
Vue.config.productionTip = true |
||||
Vue.config.silent = true |
||||
|
||||
Chart.config({ |
||||
theme: { |
||||
name: 'themeName', |
||||
data: themeData, |
||||
default: true |
||||
} |
||||
}) |
||||
|
||||
/* eslint-disable no-new */ |
||||
Permissions.request().then(res => { |
||||
// instance
|
||||
new Vue({ |
||||
el: '#app', |
||||
router, |
||||
store, |
||||
render: h => h(App), |
||||
mounted () { |
||||
document.addEventListener('click', (e) => { |
||||
$('#contextmenu').css('visibility', 'hidden') |
||||
}) |
||||
}, |
||||
methods: { |
||||
initApp () { |
||||
$('.global-loading').hide() |
||||
let bootstrapTooltip = $.fn.tooltip.noConflict() |
||||
$.fn.tooltip = bootstrapTooltip |
||||
$('body').tooltip({ |
||||
selector: '[data-toggle="tooltip"]', |
||||
trigger: 'hover' |
||||
}) |
||||
// init
|
||||
i18n.init() |
||||
} |
||||
}, |
||||
created () { |
||||
this.initApp() |
||||
} |
||||
}) |
||||
}) |
@ -0,0 +1,284 @@
|
||||
/* |
||||
* Licensed to the Apache Software Foundation (ASF) under one or more |
||||
* contributor license agreements. See the NOTICE file distributed with |
||||
* this work for additional information regarding copyright ownership. |
||||
* The ASF licenses this file to You under the Apache License, Version 2.0 |
||||
* (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
import i18n from '@/module/i18n' |
||||
import Permissions from '@/module/permissions' |
||||
|
||||
/** |
||||
* Operation bar config |
||||
* @code code |
||||
* @icon icon |
||||
* @disable disable |
||||
* @desc tooltip |
||||
*/ |
||||
const toolOper = (dagThis) => { |
||||
let disabled = Permissions.getAuth() === false ? false : !dagThis.$store.state.dag.isDetails |
||||
return [ |
||||
{ |
||||
code: 'pointer', |
||||
icon: '', |
||||
disable: disabled, |
||||
desc: `${i18n.$t('拖动节点和选中项')}` |
||||
}, |
||||
{ |
||||
code: 'line', |
||||
icon: '', |
||||
disable: disabled, |
||||
desc: `${i18n.$t('选择线条连接')}` |
||||
}, |
||||
{ |
||||
code: 'remove', |
||||
icon: '', |
||||
disable: disabled, |
||||
desc: `${i18n.$t('删除选中的线或节点')}` |
||||
}, |
||||
{ |
||||
code: 'download', |
||||
icon: '', |
||||
disable: !!dagThis.type, |
||||
desc: `${i18n.$t('下载')}` |
||||
}, |
||||
{ |
||||
code: 'screen', |
||||
icon: '', |
||||
disable: disabled, |
||||
desc: `${i18n.$t('全屏')}` |
||||
} |
||||
] |
||||
} |
||||
|
||||
/** |
||||
* Post status |
||||
* @id Front end definition id |
||||
* @desc tooltip |
||||
* @code Backend definition identifier |
||||
*/ |
||||
let publishStatus = [ |
||||
{ |
||||
id: 0, |
||||
desc: `${i18n.$t('未发布')}`, |
||||
code: 'NOT_RELEASE' |
||||
}, |
||||
{ |
||||
id: 1, |
||||
desc: `${i18n.$t('上线')}`, |
||||
code: 'ONLINE' |
||||
}, |
||||
{ |
||||
id: 2, |
||||
desc: `${i18n.$t('下线')}`, |
||||
code: 'OFFLINE' |
||||
} |
||||
] |
||||
|
||||
/** |
||||
* Operation type |
||||
* @desc tooltip |
||||
* @code identifier |
||||
*/ |
||||
let runningType = [ |
||||
{ |
||||
desc: `${i18n.$t('启动工作流')}`, |
||||
code: 'START_PROCESS' |
||||
}, |
||||
{ |
||||
desc: `${i18n.$t('从当前节点开始执行')}`, |
||||
code: 'START_CURRENT_TASK_PROCESS' |
||||
}, |
||||
{ |
||||
desc: `${i18n.$t('恢复被容错的工作流')}`, |
||||
code: 'RECOVER_TOLERANCE_FAULT_PROCESS' |
||||
}, |
||||
{ |
||||
desc: `${i18n.$t('恢复暂停流程')}`, |
||||
code: 'RECOVER_SUSPENDED_PROCESS' |
||||
}, |
||||
{ |
||||
desc: `${i18n.$t('从失败节点开始执行')}`, |
||||
code: 'START_FAILURE_TASK_PROCESS' |
||||
}, |
||||
{ |
||||
desc: `${i18n.$t('补数')}`, |
||||
code: 'COMPLEMENT_DATA' |
||||
}, |
||||
{ |
||||
desc: `${i18n.$t('调度执行')}`, |
||||
code: 'SCHEDULER' |
||||
}, |
||||
{ |
||||
desc: `${i18n.$t('重跑')}`, |
||||
code: 'REPEAT_RUNNING' |
||||
}, |
||||
{ |
||||
desc: `${i18n.$t('暂停')}`, |
||||
code: 'PAUSE' |
||||
}, |
||||
{ |
||||
desc: `${i18n.$t('停止')}`, |
||||
code: 'STOP' |
||||
}, |
||||
{ |
||||
desc: `${i18n.$t('恢复等待线程')}`, |
||||
code: 'RECOVER_WAITTING_THREAD' |
||||
} |
||||
] |
||||
|
||||
/** |
||||
* Task status |
||||
* @key key |
||||
* @id id |
||||
* @desc tooltip |
||||
* @color color |
||||
* @icoUnicode iconfont |
||||
* @isSpin is loading (Need to execute the code block to write if judgment) |
||||
*/ |
||||
let tasksState = { |
||||
'SUBMITTED_SUCCESS': { |
||||
id: 0, |
||||
desc: `${i18n.$t('提交成功')}`, |
||||
color: '#A9A9A9', |
||||
icoUnicode: '', |
||||
isSpin: false |
||||
}, |
||||
'RUNNING_EXEUTION': { |
||||
id: 1, |
||||
desc: `${i18n.$t('正在执行')}`, |
||||
color: '#0097e0', |
||||
icoUnicode: '', |
||||
isSpin: true |
||||
}, |
||||
'READY_PAUSE': { |
||||
id: 2, |
||||
desc: `${i18n.$t('准备暂停')}`, |
||||
color: '#07b1a3', |
||||
icoUnicode: '', |
||||
isSpin: false |
||||
}, |
||||
'PAUSE': { |
||||
id: 3, |
||||
desc: `${i18n.$t('暂停')}`, |
||||
color: '#057c72', |
||||
icoUnicode: '', |
||||
isSpin: false |
||||
}, |
||||
'READY_STOP': { |
||||
id: 4, |
||||
desc: `${i18n.$t('准备停止')}`, |
||||
color: '#FE0402', |
||||
icoUnicode: '', |
||||
isSpin: false |
||||
}, |
||||
'STOP': { |
||||
id: 5, |
||||
desc: `${i18n.$t('停止')}`, |
||||
color: '#e90101', |
||||
icoUnicode: '', |
||||
isSpin: false |
||||
}, |
||||
'FAILURE': { |
||||
id: 6, |
||||
desc: `${i18n.$t('失败')}`, |
||||
color: '#000000', |
||||
icoUnicode: '', |
||||
isSpin: false |
||||
}, |
||||
'SUCCESS': { |
||||
id: 7, |
||||
desc: `${i18n.$t('成功')}`, |
||||
color: '#33cc00', |
||||
icoUnicode: '', |
||||
isSpin: false |
||||
}, |
||||
'NEED_FAULT_TOLERANCE': { |
||||
id: 8, |
||||
desc: `${i18n.$t('需要容错')}`, |
||||
color: '#FF8C00', |
||||
icoUnicode: '', |
||||
isSpin: false |
||||
}, |
||||
'KILL': { |
||||
id: 9, |
||||
desc: `${i18n.$t('kill')}`, |
||||
color: '#a70202', |
||||
icoUnicode: '', |
||||
isSpin: false |
||||
}, |
||||
'WAITTING_THREAD': { |
||||
id: 10, |
||||
desc: `${i18n.$t('等待线程')}`, |
||||
color: '#912eed', |
||||
icoUnicode: '', |
||||
isSpin: false |
||||
}, |
||||
'WAITTING_DEPEND': { |
||||
id: 11, |
||||
desc: `${i18n.$t('等待依赖')}`, |
||||
color: '#5101be', |
||||
icoUnicode: '', |
||||
isSpin: false |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Node type |
||||
* @key key |
||||
* @desc tooltip |
||||
* @color color (tree and gantt) |
||||
*/ |
||||
let tasksType = { |
||||
'SHELL': { |
||||
desc: 'SHELL', |
||||
color: '#646464' |
||||
}, |
||||
'SUB_PROCESS': { |
||||
desc: 'SUB_PROCESS', |
||||
color: '#0097e0' |
||||
}, |
||||
'PROCEDURE': { |
||||
desc: 'PROCEDURE', |
||||
color: '#525CCD' |
||||
}, |
||||
'SQL': { |
||||
desc: 'SQL', |
||||
color: '#7A98A1' |
||||
}, |
||||
'SPARK': { |
||||
desc: 'SPARK', |
||||
color: '#E46F13' |
||||
}, |
||||
'MR': { |
||||
desc: 'MapReduce', |
||||
color: '#A0A5CC' |
||||
}, |
||||
'PYTHON': { |
||||
desc: 'PYTHON', |
||||
color: '#FED52D' |
||||
}, |
||||
'DEPENDENT': { |
||||
desc: 'DEPENDENT', |
||||
color: '#2FBFD8' |
||||
} |
||||
} |
||||
|
||||
|
||||
export { |
||||
toolOper, |
||||
publishStatus, |
||||
runningType, |
||||
tasksState, |
||||
tasksType |
||||
} |
@ -0,0 +1,119 @@
|
||||
/* |
||||
* Licensed to the Apache Software Foundation (ASF) under one or more |
||||
* contributor license agreements. See the NOTICE file distributed with |
||||
* this work for additional information regarding copyright ownership. |
||||
* The ASF licenses this file to You under the Apache License, Version 2.0 |
||||
* (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
import _ from 'lodash' |
||||
import { jsPlumb } from 'jsplumb' |
||||
import JSP from './plugIn/jsPlumbHandle' |
||||
import DownChart from './plugIn/downChart' |
||||
import store from '@/conf/home/store' |
||||
|
||||
/** |
||||
* Prototype method |
||||
*/ |
||||
let Dag = function () { |
||||
this.dag = {} |
||||
this.instance = {} |
||||
} |
||||
|
||||
/** |
||||
* init |
||||
* @dag dag vue instance |
||||
*/ |
||||
Dag.prototype.init = function ({ dag, instance }) { |
||||
this.dag = dag |
||||
this.instance = instance |
||||
} |
||||
|
||||
/** |
||||
* set init config |
||||
*/ |
||||
Dag.prototype.setConfig = function (o) { |
||||
JSP.setConfig(o) |
||||
} |
||||
|
||||
/** |
||||
* create dag |
||||
*/ |
||||
Dag.prototype.create = function () { |
||||
jsPlumb.ready(() => { |
||||
JSP.init({ |
||||
dag: this.dag, |
||||
instance: this.instance |
||||
}) |
||||
|
||||
// init event
|
||||
JSP.handleEvent() |
||||
|
||||
// init draggable
|
||||
JSP.draggable() |
||||
}) |
||||
} |
||||
|
||||
/** |
||||
* Action event on the right side of the toolbar |
||||
*/ |
||||
Dag.prototype.toolbarEvent = function ({ item, code, is }) { |
||||
switch (code) { |
||||
case 'pointer': |
||||
JSP.handleEventPointer(is) |
||||
break |
||||
case 'line': |
||||
JSP.handleEventLine(is) |
||||
break |
||||
case 'remove': |
||||
JSP.handleEventRemove() |
||||
break |
||||
case 'screen': |
||||
JSP.handleEventScreen({ item, is }) |
||||
break |
||||
case 'download': |
||||
DownChart.download({ |
||||
dagThis: this.dag |
||||
}) |
||||
break |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Echo data display |
||||
*/ |
||||
Dag.prototype.backfill = function () { |
||||
jsPlumb.ready(() => { |
||||
JSP.init({ |
||||
dag: this.dag, |
||||
instance: this.instance |
||||
}) |
||||
// Backfill
|
||||
JSP.jspBackfill({ |
||||
// connects
|
||||
connects: _.cloneDeep(store.state.dag.connects), |
||||
// Node location information
|
||||
locations: _.cloneDeep(store.state.dag.locations), |
||||
// Node data
|
||||
largeJson: _.cloneDeep(store.state.dag.tasks) |
||||
}) |
||||
}) |
||||
} |
||||
|
||||
/** |
||||
* Get dag storage format data |
||||
*/ |
||||
Dag.prototype.saveStore = function () { |
||||
return JSP.saveStore() |
||||
} |
||||
|
||||
export default new Dag() |
@ -0,0 +1,506 @@
|
||||
|
||||
.dag-model { |
||||
background: url("../img/dag_bg.png"); |
||||
height: calc(100vh - 100px); |
||||
::selection { |
||||
background:transparent; |
||||
} |
||||
::-moz-selection { |
||||
background:transparent; |
||||
} |
||||
::-webkit-selection { |
||||
background:transparent; |
||||
} |
||||
.jsplumb-connector { |
||||
z-index: 1; |
||||
} |
||||
.endpoint-tasks { |
||||
margin-top:22px; |
||||
} |
||||
.draggable { |
||||
> span { |
||||
text-align: center; |
||||
display: block; |
||||
margin-top: -4px; |
||||
padding: 0 4px; |
||||
width: 200px; |
||||
margin-left: -81px; |
||||
position: absolute; |
||||
left: 0; |
||||
bottom: -12px; |
||||
} |
||||
.fa { |
||||
display: inline-block; |
||||
position: absolute; |
||||
right: -8px; |
||||
top: -8px; |
||||
z-index: 2; |
||||
cursor: pointer; |
||||
} |
||||
.icos { |
||||
display: inline-block; |
||||
cursor: pointer; |
||||
} |
||||
&.active-tasks { |
||||
span { |
||||
color: #0296DF; |
||||
} |
||||
} |
||||
} |
||||
.icos { |
||||
width: 32px; |
||||
height: 32px; |
||||
margin: 2px; |
||||
border-radius: 3px; |
||||
position: relative; |
||||
z-index: 9; |
||||
} |
||||
.icos-SHELL { |
||||
background: url("../img/toolbar_SHELL.png") no-repeat 50% 50%; |
||||
} |
||||
.icos-SUB_PROCESS { |
||||
background: url("../img/toolbar_SUB_PROCESS.png") no-repeat 50% 50%; |
||||
} |
||||
.icos-PROCEDURE { |
||||
background: url("../img/toolbar_PROCEDURE.png") no-repeat 50% 50%; |
||||
} |
||||
.icos-SQL { |
||||
background: url("../img/toolbar_SQL.png") no-repeat 50% 50%; |
||||
} |
||||
.icos-SPARK { |
||||
background: url("../img/toolbar_SPARK.png") no-repeat 50% 50%; |
||||
} |
||||
.icos-MR { |
||||
background: url("../img/toolbar_MR.png") no-repeat 50% 50%; |
||||
} |
||||
.icos-PYTHON { |
||||
background: url("../img/toolbar_PYTHON.png") no-repeat 50% 50%; |
||||
} |
||||
.icos-DEPENDENT { |
||||
background: url("../img/toolbar_DEPENDENT.png") no-repeat 50% 50%; |
||||
} |
||||
.toolbar { |
||||
width: 60px; |
||||
height: 100%; |
||||
background: #F2F3F7; |
||||
float: left; |
||||
border-radius: 0 0 0 3px; |
||||
.title { |
||||
height: 40px; |
||||
line-height: 40px; |
||||
background: #40434C; |
||||
text-align: center; |
||||
border-radius: 3px 0 0 0; |
||||
span { |
||||
font-size: 14px; |
||||
color: #fff; |
||||
font-weight: bold; |
||||
} |
||||
} |
||||
.toolbar-btn { |
||||
overflow: hidden; |
||||
padding: 11px 11px 0 11px; |
||||
.bar-box { |
||||
width: 36px; |
||||
height: 36px; |
||||
float: left; |
||||
margin-bottom: 11px; |
||||
border-radius: 3px; |
||||
.disabled { |
||||
.icos { |
||||
opacity: .6; |
||||
-webkit-filter: grayscale(100%); |
||||
-moz-filter: grayscale(100%); |
||||
-ms-filter: grayscale(100%); |
||||
-o-filter: grayscale(100%); |
||||
filter: grayscale(100%); |
||||
filter: gray; |
||||
} |
||||
} |
||||
&:nth-child(odd) { |
||||
margin-right: 6px; |
||||
} |
||||
&.active { |
||||
background: #e1e2e3; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
.dag-contect { |
||||
float: left; |
||||
width: calc(100% - 60px); |
||||
height: 100%; |
||||
.dag-toolbar { |
||||
height: 40px; |
||||
background: #F2F3F7; |
||||
position: relative; |
||||
border-radius: 0 3px 0 0; |
||||
.assist-btn { |
||||
position: absolute; |
||||
left: 10px; |
||||
top: 8px; |
||||
>.name { |
||||
padding-left: 6px; |
||||
vertical-align: middle; |
||||
} |
||||
} |
||||
.save-btn { |
||||
position: absolute; |
||||
right: 8px; |
||||
top: 6px; |
||||
.operation { |
||||
overflow: hidden; |
||||
display: inline-block; |
||||
margin-right: 10px; |
||||
a { |
||||
float: left; |
||||
width: 28px; |
||||
height: 28px; |
||||
text-align: center; |
||||
line-height: 28px; |
||||
margin-left: 6px; |
||||
border-radius: 3px; |
||||
vertical-align: middle; |
||||
i { |
||||
color: #333; |
||||
} |
||||
&.active { |
||||
background: #e1e2e3; |
||||
i { |
||||
color: #2d8cf0; |
||||
} |
||||
} |
||||
&.disable { |
||||
i { |
||||
color: #bbb; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
.dag-container { |
||||
height: calc(100% - 40px); |
||||
overflow-x: auto; |
||||
&::-webkit-scrollbar{ |
||||
width: 9px; |
||||
} |
||||
} |
||||
} |
||||
.tools-model { |
||||
height: 60px; |
||||
background: #F4F5F4; |
||||
border-radius: 3px 3px 0px 0px; |
||||
} |
||||
} |
||||
|
||||
.v-modal-custom-log { |
||||
z-index: 101; |
||||
} |
||||
|
||||
svg path:hover { |
||||
cursor: pointer; |
||||
} |
||||
|
||||
#chart-container .ui-selecting { |
||||
span { |
||||
color: #0296DF; |
||||
} |
||||
} |
||||
#chart-container .ui-selected { |
||||
span { |
||||
color: #0296DF; |
||||
} |
||||
} |
||||
|
||||
.contextmenu { |
||||
position: fixed; |
||||
width: 90px; |
||||
background: #fff; |
||||
border-radius: 3px; |
||||
box-shadow: 0 2px 4px 1px rgba(0, 0, 0, 0.1); |
||||
padding: 4px 0; |
||||
visibility:hidden; |
||||
a { |
||||
height: 30px; |
||||
line-height: 28px; |
||||
display: block; |
||||
i { |
||||
font-size: 16px; |
||||
vertical-align: middle; |
||||
margin-left: 10px; |
||||
} |
||||
span { |
||||
vertical-align: middle; |
||||
font-size: 12px; |
||||
color: #666; |
||||
padding-left: 2px; |
||||
} |
||||
&:hover { |
||||
background: #f6faff; |
||||
} |
||||
&#startRunning { |
||||
i { |
||||
color: #35cd75; |
||||
} |
||||
} |
||||
&#editNodes { |
||||
i { |
||||
color: #0097e0; |
||||
} |
||||
} |
||||
&#removeNodes { |
||||
i { |
||||
color: #f04d4e; |
||||
} |
||||
} |
||||
&#copyNodes { |
||||
i { |
||||
color: #FABC05; |
||||
} |
||||
} |
||||
&.disbled { |
||||
i,span { |
||||
color: #aaa !important; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
.jtk-demo { |
||||
//min-width: calc(100% - 220px); |
||||
width: 8000px; |
||||
height: 5000px; |
||||
position: relative; |
||||
svg:not(:root){ |
||||
z-index: 11; |
||||
} |
||||
} |
||||
|
||||
.jtk-demo-canvas { |
||||
height: 100%; |
||||
} |
||||
|
||||
.jtk-bootstrap { |
||||
min-height: 100vh; |
||||
display: flex; |
||||
flex-direction: column; |
||||
} |
||||
|
||||
.jtk-bootstrap .jtk-page-container { |
||||
display: flex; |
||||
width: 100vw; |
||||
justify-content: center; |
||||
flex: 1; |
||||
} |
||||
|
||||
.jtk-bootstrap .jtk-container { |
||||
width: 60%; |
||||
max-width: 800px; |
||||
} |
||||
|
||||
.jtk-bootstrap-wide .jtk-container { |
||||
width: 80%; |
||||
max-width: 1187px; |
||||
} |
||||
|
||||
.jtk-demo-main { |
||||
position: relative; |
||||
margin-top: 98px; |
||||
} |
||||
|
||||
.jtk-demo-main .description { |
||||
font-size: 13px; |
||||
margin-top: 25px; |
||||
padding: 13px; |
||||
margin-bottom: 22px; |
||||
background-color: #f4f5ef; |
||||
} |
||||
|
||||
.jtk-demo-main .description li { |
||||
list-style-type: disc !important; |
||||
} |
||||
|
||||
.jtk-demo-canvas { |
||||
display: flex; |
||||
} |
||||
|
||||
.canvas-wide { |
||||
padding-top: 10px; |
||||
margin-left: 0; |
||||
-ms-transition: all .1s ease-out; |
||||
-moz-transition: all .1s ease-out; |
||||
-webkit-transition: all .1s ease-out; |
||||
-o-transition: all .1s ease-out; |
||||
} |
||||
|
||||
.jtk-demo-dataset { |
||||
text-align: left; |
||||
max-height: 600px; |
||||
overflow: auto; |
||||
} |
||||
|
||||
.demo-title { |
||||
float: left; |
||||
font-size: 18px; |
||||
} |
||||
|
||||
.controls { |
||||
top: 25px; |
||||
color: #FFF; |
||||
margin-right: 10px; |
||||
position: absolute; |
||||
left: 25px; |
||||
z-index: 1; |
||||
} |
||||
|
||||
.controls i { |
||||
background-color: #3E7E9C; |
||||
border-radius: 4px; |
||||
cursor: pointer; |
||||
margin-right: 0; |
||||
padding: 4px; |
||||
} |
||||
.w { |
||||
position: absolute; |
||||
z-index: 4; |
||||
font-size: 11px; |
||||
-webkit-transition: background-color 0.25s ease-in; |
||||
-moz-transition: background-color 0.25s ease-in; |
||||
transition: background-color 0.25s ease-in; |
||||
border: 7px solid transparent; |
||||
border-bottom: 30px solid transparent; |
||||
.icos { |
||||
width: 50px; |
||||
height: 50px; |
||||
box-shadow: 2px 2px 19px #e0e0e0; |
||||
-o-box-shadow: 2px 2px 19px #e0e0e0; |
||||
-webkit-box-shadow: 2px 2px 19px #e0e0e0; |
||||
-moz-box-shadow: 2px 2px 19px #e0e0e0; |
||||
-moz-border-radius: 8px; |
||||
border-radius: 8px; |
||||
opacity: 0.8; |
||||
cursor: move; |
||||
background-color: #fff; |
||||
} |
||||
.name-p { |
||||
position: absolute; |
||||
left: 50%; |
||||
bottom: -24px; |
||||
width: 200px; |
||||
text-align: center; |
||||
margin-left: -100px; |
||||
} |
||||
.state-p { |
||||
width: 20px; |
||||
height: 20px; |
||||
position: absolute; |
||||
top: -20px; |
||||
left: 18px; |
||||
text-align: center; |
||||
cursor: pointer; |
||||
b { |
||||
font-weight: normal; |
||||
} |
||||
} |
||||
} |
||||
|
||||
.aLabel { |
||||
-webkit-transition: background-color 0.25s ease-in; |
||||
-moz-transition: background-color 0.25s ease-in; |
||||
transition: background-color 0.25s ease-in; |
||||
} |
||||
|
||||
.aLabel.jtk-hover, |
||||
.jtk-source-hover, |
||||
.jtk-target-hover { |
||||
.icos { |
||||
background-color: #333; |
||||
color: #333; |
||||
-ms-transition: all 0.6s ease-out; |
||||
-moz-transition: all 0.6s ease-out; |
||||
-webkit-transition: all 0.6s ease-out; |
||||
-o-transition: all 0.6s ease-out; |
||||
} |
||||
} |
||||
|
||||
.jtk-tasks-active { |
||||
.icos { |
||||
background-color: #2db7f5; |
||||
color: #0097e0; |
||||
-ms-transition: all 0.6s ease-out; |
||||
-moz-transition: all 0.6s ease-out; |
||||
-webkit-transition: all 0.6s ease-out; |
||||
-o-transition: all 0.6s ease-out; |
||||
} |
||||
} |
||||
|
||||
.aLabel { |
||||
background-color: white; |
||||
opacity: 0.8; |
||||
padding: 0.3em; |
||||
border-radius: 0.5em; |
||||
border: 1px solid #346789; |
||||
cursor: pointer; |
||||
} |
||||
|
||||
.jtk-ep { |
||||
.ep { |
||||
display: block; |
||||
} |
||||
} |
||||
|
||||
.ep { |
||||
position: absolute; |
||||
top: -4%; |
||||
right: -1px; |
||||
width: 1em; |
||||
height: 1em; |
||||
z-index: 12; |
||||
background-color: orange; |
||||
cursor: pointer; |
||||
box-shadow: 0 0 2px black; |
||||
-webkit-transition: -webkit-box-shadow 0.25s ease-in; |
||||
-moz-transition: -moz-box-shadow 0.25s ease-in; |
||||
transition: box-shadow 0.25s ease-in; |
||||
border-radius:100%; |
||||
display: none; |
||||
} |
||||
|
||||
.ep:hover { |
||||
box-shadow: 0 0 6px black; |
||||
} |
||||
|
||||
.statemachine-demo .jtk-endpoint { |
||||
z-index: 3; |
||||
} |
||||
|
||||
#canvas { |
||||
.dot-style { |
||||
opacity: 0; |
||||
} |
||||
} |
||||
|
||||
.from-mirror { |
||||
width: 100%; |
||||
position: relative; |
||||
z-index: 0; |
||||
.CodeMirror { |
||||
height:auto; |
||||
min-height: 72px; |
||||
} |
||||
|
||||
.CodeMirror-scroll { |
||||
height:auto; |
||||
min-height: 72px; |
||||
overflow-y: hidden; |
||||
overflow-x: auto; |
||||
} |
||||
} |
||||
|
||||
.ans-drawer.ans-drawer-right { |
||||
width: 628px; |
||||
left: auto; |
||||
} |
||||
|
||||
|
@ -0,0 +1,478 @@
|
||||
<template> |
||||
<div class="clearfix dag-model" > |
||||
<div class="toolbar"> |
||||
<div class="title"><span>{{$t('工具栏')}}</span></div> |
||||
<div class="toolbar-btn"> |
||||
<div class="bar-box roundedRect jtk-draggable jtk-droppable jtk-endpoint-anchor jtk-connected" |
||||
:class="v === dagBarId ? 'active' : ''" |
||||
:id="v" |
||||
v-for="(item,v) in tasksTypeList" |
||||
@mousedown="_getDagId(v)"> |
||||
<div data-toggle="tooltip" :title="item.desc" :class="_isDetails"> |
||||
<div class="icos" :class="'icos-' + v" ></div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<div class="dag-contect"> |
||||
<div class="dag-toolbar"> |
||||
<div class="assist-btn"> |
||||
<x-button |
||||
style="vertical-align: middle;" |
||||
data-toggle="tooltip" |
||||
:title="$t('查看变量')" |
||||
data-container="body" |
||||
type="primary" |
||||
size="xsmall" |
||||
:disabled="$route.name !== 'projects-instance-details'" |
||||
@click="_toggleView" |
||||
icon="fa fa-code"> |
||||
</x-button> |
||||
<span class="name">{{name}}</span> |
||||
</div> |
||||
<div class="save-btn"> |
||||
<div class="operation" style="vertical-align: middle;"> |
||||
<a href="javascript:" |
||||
v-for="(item,$index) in toolOperList" |
||||
:class="_operationClass(item)" |
||||
:id="item.code" |
||||
@click="_ckOperation(item,$event)"> |
||||
<i class="iconfont" v-html="item.icon" data-toggle="tooltip" :title="item.desc" ></i> |
||||
</a> |
||||
</div> |
||||
<x-button |
||||
data-toggle="tooltip" |
||||
:title="$t('刷新DAG状态')" |
||||
data-container="body" |
||||
style="vertical-align: middle;" |
||||
icon="fa fa-refresh" |
||||
type="primary" |
||||
:loading="isRefresh" |
||||
v-if="type === 'instance'" |
||||
@click="!isRefresh && _refresh()" |
||||
size="xsmall" > |
||||
</x-button> |
||||
<x-button |
||||
v-if="isRtTasks" |
||||
style="vertical-align: middle;" |
||||
type="primary" |
||||
size="xsmall" |
||||
icon="fa fa-reply" |
||||
@click="_rtNodesDag" > |
||||
{{$t('返回上一节点')}} |
||||
</x-button> |
||||
<x-button |
||||
style="vertical-align: middle;" |
||||
type="primary" |
||||
size="xsmall" |
||||
:loading="spinnerLoading" |
||||
v-ps="['GENERAL_USER']" |
||||
@click="_saveChart" |
||||
icon="fa fa-save" |
||||
:disabled="isDetails"> |
||||
{{spinnerLoading ? 'Loading...' : $t('保存')}} |
||||
</x-button> |
||||
</div> |
||||
</div> |
||||
<div class="scrollbar dag-container"> |
||||
<div class="jtk-demo" id="jtk-demo"> |
||||
<div class="jtk-demo-canvas canvas-wide statemachine-demo jtk-surface jtk-surface-nopan jtk-draggable" id="canvas" ></div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
<script> |
||||
import _ from 'lodash' |
||||
import Dag from './dag' |
||||
import mUdp from './udp/udp' |
||||
import i18n from '@/module/i18n' |
||||
import { jsPlumb } from 'jsplumb' |
||||
import { allNodesId } from './plugIn/util' |
||||
import { toolOper, tasksType } from './config' |
||||
import mFormModel from './formModel/formModel' |
||||
import { formatDate } from '@/module/filter/filter' |
||||
import { findComponentDownward } from '@/module/util/' |
||||
import disabledState from '@/module/mixin/disabledState' |
||||
import { mapActions, mapState, mapMutations } from 'vuex' |
||||
|
||||
let eventModel |
||||
|
||||
export default { |
||||
name: 'dag-chart', |
||||
data () { |
||||
return { |
||||
tasksTypeList: tasksType, |
||||
toolOperList: toolOper(this), |
||||
dagBarId: null, |
||||
toolOperCode: '', |
||||
spinnerLoading: false, |
||||
urlParam: { |
||||
id: this.$route.params.id || null |
||||
}, |
||||
isRtTasks: false, |
||||
isRefresh: false, |
||||
isLoading: false, |
||||
taskId: null |
||||
} |
||||
}, |
||||
mixins: [disabledState], |
||||
props: { |
||||
type: String, |
||||
releaseState: String |
||||
}, |
||||
methods: { |
||||
...mapActions('dag', ['saveDAGchart', 'updateInstance', 'updateDefinition', 'getTaskState']), |
||||
...mapMutations('dag', ['addTasks', 'resetParams', 'setIsEditDag', 'setName']), |
||||
init () { |
||||
if (this.tasks.length) { |
||||
Dag.backfill() |
||||
// Process instances can view status |
||||
if (this.type === 'instance') { |
||||
this._getTaskState(false).then(res => {}) |
||||
// Round robin acquisition status |
||||
this.setIntervalP = setInterval(() => { |
||||
this._getTaskState(true).then(res => {}) |
||||
}, 90000) |
||||
} |
||||
} else { |
||||
Dag.create() |
||||
} |
||||
}, |
||||
/** |
||||
* Get state interface |
||||
* @param isReset Whether to manually refresh |
||||
*/ |
||||
_getTaskState (isReset) { |
||||
return new Promise((resolve, reject) => { |
||||
this.getTaskState(this.urlParam.id).then(res => { |
||||
let data = res.list |
||||
let state = res.processInstanceState |
||||
let taskList = res.taskList |
||||
let idArr = allNodesId() |
||||
const titleTpl = (item, desc) => { |
||||
let $item = _.filter(taskList, v => v.name === item.name)[0] |
||||
return `<div style="text-align: left">${i18n.$t('名称')}:${$item.name}</br>${i18n.$t('状态')}:${desc}</br>${i18n.$t('类型')}:${$item.taskType}</br>${i18n.$t('host')}:${$item.host || '-'}</br>${i18n.$t('重试次数')}:${$item.retryTimes}</br>${i18n.$t('提交时间')}:${formatDate($item.submitTime)}</br>${i18n.$t('开始时间')}:${formatDate($item.startTime)}</br>${i18n.$t('结束时间')}:${$item.endTime ? formatDate($item.endTime) : '-'}</br></div>` |
||||
} |
||||
data.forEach(v1 => { |
||||
idArr.forEach(v2 => { |
||||
if (v2.name === v1.name) { |
||||
let dom = $(`#${v2.id}`) |
||||
let state = dom.find('.state-p') |
||||
dom.attr('data-state-id', v1.stateId) |
||||
dom.attr('data-dependent-result', v1.dependentResult || '') |
||||
state.html('') |
||||
state.append(`<b class="iconfont ${v1.isSpin ? 'fa fa-spin' : ''}" style="color:${v1.color}" data-toggle="tooltip" data-html="true" data-container="body">${v1.icoUnicode}</b>`) |
||||
state.find('b').attr('title', titleTpl(v2, v1.desc)) |
||||
} |
||||
}) |
||||
}) |
||||
if (state === 'PAUSE' || state === 'STOP' || state === 'FAILURE' || this.state === 'SUCCESS') { |
||||
// Manual refresh does not regain large json |
||||
if (isReset) { |
||||
findComponentDownward(this.$root, `${this.type}-details`)._reset() |
||||
} |
||||
} |
||||
resolve() |
||||
}) |
||||
}) |
||||
}, |
||||
/** |
||||
* Get the action bar id |
||||
* @param item |
||||
*/ |
||||
_getDagId (v) { |
||||
if (this.isDetails) { |
||||
return |
||||
} |
||||
this.dagBarId = v |
||||
}, |
||||
/** |
||||
* operating |
||||
*/ |
||||
_ckOperation (item) { |
||||
let is = true |
||||
let code = '' |
||||
|
||||
if (!item.disable) { |
||||
return |
||||
} |
||||
|
||||
if (this.toolOperCode === item.code) { |
||||
this.toolOperCode = '' |
||||
code = item.code |
||||
is = false |
||||
} else { |
||||
this.toolOperCode = item.code |
||||
code = this.toolOperCode |
||||
is = true |
||||
} |
||||
|
||||
// event type |
||||
Dag.toolbarEvent({ |
||||
item: item, |
||||
code: code, |
||||
is: is |
||||
}) |
||||
}, |
||||
_operationClass (item) { |
||||
if (item.disable) { |
||||
return this.toolOperCode === item.code ? 'active' : '' |
||||
} else { |
||||
return 'disable' |
||||
} |
||||
}, |
||||
/** |
||||
* Storage interface |
||||
*/ |
||||
_save (sourceType) { |
||||
return new Promise((resolve, reject) => { |
||||
this.spinnerLoading = true |
||||
// Storage store |
||||
Dag.saveStore().then(res => { |
||||
if (this.urlParam.id) { |
||||
/** |
||||
* 编辑 |
||||
* @param saveInstanceEditDAGChart => Process instance editing |
||||
* @param saveEditDAGChart => Process definition editing |
||||
*/ |
||||
this[this.type === 'instance' ? 'updateInstance' : 'updateDefinition'](this.urlParam.id).then(res => { |
||||
this.$message.success(res.msg) |
||||
this.spinnerLoading = false |
||||
resolve() |
||||
}).catch(e => { |
||||
this.$message.error(e.msg || '') |
||||
this.spinnerLoading = false |
||||
reject(e) |
||||
}) |
||||
} else { |
||||
// New |
||||
this.saveDAGchart().then(res => { |
||||
this.$message.success(res.msg) |
||||
this.spinnerLoading = false |
||||
// source @/conf/home/pages/dag/_source/editAffirmModel/index.js |
||||
if (sourceType !== 'affirm') { |
||||
// Jump process definition |
||||
this.$router.push({ name: 'projects-definition-list' }) |
||||
} |
||||
resolve() |
||||
}).catch(e => { |
||||
this.$message.error(e.msg || '') |
||||
this.setName('') |
||||
this.spinnerLoading = false |
||||
reject(e) |
||||
}) |
||||
} |
||||
}) |
||||
}) |
||||
}, |
||||
/** |
||||
* Global parameter |
||||
* @param Promise |
||||
*/ |
||||
_udpTopFloorPop () { |
||||
return new Promise((resolve, reject) => { |
||||
let modal = this.$modal.dialog({ |
||||
closable: false, |
||||
showMask: true, |
||||
escClose: true, |
||||
className: 'v-modal-custom', |
||||
transitionName: 'opacityp', |
||||
render (h) { |
||||
return h(mUdp, { |
||||
on: { |
||||
onUdp () { |
||||
modal.remove() |
||||
resolve() |
||||
}, |
||||
close () { |
||||
modal.remove() |
||||
} |
||||
} |
||||
}) |
||||
} |
||||
}) |
||||
}) |
||||
}, |
||||
/** |
||||
* Save chart |
||||
*/ |
||||
_saveChart () { |
||||
// Verify node |
||||
if (!this.tasks.length) { |
||||
this.$message.warning(`${i18n.$t('未创建节点保存失败')}`) |
||||
return |
||||
} |
||||
|
||||
// Global parameters (optional) |
||||
this._udpTopFloorPop().then(() => { |
||||
return this._save() |
||||
}) |
||||
}, |
||||
/** |
||||
* Return to the previous child node |
||||
*/ |
||||
_rtNodesDag () { |
||||
let getIds = this.$route.query.subProcessIds |
||||
let idsArr = getIds.split(',') |
||||
let ids = idsArr.slice(0, idsArr.length - 1) |
||||
let id = idsArr[idsArr.length - 1] |
||||
let query = {} |
||||
|
||||
if (id !== idsArr[0]) { |
||||
query = { subProcessIds: ids.join(',') } |
||||
} |
||||
let $name = this.$route.name.split('-') |
||||
this.$router.push({ path: `/${$name[0]}/${$name[1]}/list/${id}`, query: query }) |
||||
}, |
||||
/** |
||||
* Subprocess processing |
||||
* @param subProcessId Subprocess ID |
||||
*/ |
||||
_subProcessHandle (subProcessId) { |
||||
let subProcessIds = [] |
||||
let getIds = this.$route.query.subProcessIds |
||||
if (getIds) { |
||||
let newId = getIds.split(',') |
||||
newId.push(this.urlParam.id) |
||||
subProcessIds = newId |
||||
} else { |
||||
subProcessIds.push(this.urlParam.id) |
||||
} |
||||
let $name = this.$route.name.split('-') |
||||
this.$router.push({ path: `/${$name[0]}/${$name[1]}/list/${subProcessId}`, query: { subProcessIds: subProcessIds.join(',') } }) |
||||
}, |
||||
/** |
||||
* Refresh data |
||||
*/ |
||||
_refresh () { |
||||
this.isRefresh = true |
||||
this._getTaskState(false).then(res => { |
||||
setTimeout(() => { |
||||
this.isRefresh = false |
||||
this.$message.success(`${i18n.$t('刷新状态成功')}`) |
||||
}, 2200) |
||||
}) |
||||
}, |
||||
/** |
||||
* View variables |
||||
*/ |
||||
_toggleView () { |
||||
findComponentDownward(this.$root, `assist-dag-index`)._toggleView() |
||||
}, |
||||
/** |
||||
* Create a node popup layer |
||||
* @param Object id |
||||
*/ |
||||
_createNodes ({ id, type }) { |
||||
let self = this |
||||
|
||||
if (eventModel) { |
||||
eventModel.remove() |
||||
} |
||||
|
||||
const removeNodesEvent = (fromThis) => { |
||||
// Manually destroy events inside the component |
||||
fromThis.$destroy() |
||||
// Close the popup |
||||
eventModel.remove() |
||||
} |
||||
|
||||
this.taskId = id |
||||
|
||||
eventModel = this.$drawer({ |
||||
closable: false, |
||||
direction: 'right', |
||||
escClose: true, |
||||
render: h => h(mFormModel, { |
||||
on: { |
||||
addTaskInfo ({ item, fromThis }) { |
||||
self.addTasks(item) |
||||
setTimeout(() => { |
||||
removeNodesEvent(fromThis) |
||||
}, 100) |
||||
}, |
||||
close ({ flag, fromThis }) { |
||||
// Edit status does not allow deletion of nodes |
||||
if (flag) { |
||||
jsPlumb.remove(id) |
||||
} |
||||
|
||||
removeNodesEvent(fromThis) |
||||
}, |
||||
onSubProcess ({ subProcessId, fromThis }) { |
||||
removeNodesEvent(fromThis) |
||||
self._subProcessHandle(subProcessId) |
||||
} |
||||
}, |
||||
props: { |
||||
id: id, |
||||
taskType: type || self.dagBarId, |
||||
self: self |
||||
} |
||||
}) |
||||
}) |
||||
} |
||||
}, |
||||
watch: { |
||||
'tasks': { |
||||
deep: true, |
||||
handler () { |
||||
console.log('+++++ save dag params +++++') |
||||
// Edit state does not allow deletion of node a... |
||||
this.setIsEditDag(true) |
||||
} |
||||
} |
||||
}, |
||||
created () { |
||||
// Edit state does not allow deletion of node a... |
||||
this.setIsEditDag(false) |
||||
|
||||
if (this.$route.query.subProcessIds) { |
||||
this.isRtTasks = true |
||||
} |
||||
|
||||
Dag.init({ |
||||
dag: this, |
||||
instance: jsPlumb.getInstance({ |
||||
Endpoint: [ |
||||
'Dot', { radius: 1, cssClass: 'dot-style' } |
||||
], |
||||
Connector: 'Straight', |
||||
PaintStyle: { lineWidth: 2, stroke: '#456' }, // Connection style |
||||
ConnectionOverlays: [ |
||||
[ |
||||
'Arrow', |
||||
{ |
||||
location: 1, |
||||
id: 'arrow', |
||||
length: 12, |
||||
foldback: 0.8 |
||||
} |
||||
] |
||||
], |
||||
Container: 'canvas' |
||||
}) |
||||
}) |
||||
}, |
||||
mounted () { |
||||
this.init() |
||||
}, |
||||
beforeDestroy () { |
||||
this.resetParams() |
||||
|
||||
// Destroy round robin |
||||
clearInterval(this.setIntervalP) |
||||
}, |
||||
destroyed () { |
||||
}, |
||||
computed: { |
||||
...mapState('dag', ['tasks', 'locations', 'connects', 'isEditDag', 'name']) |
||||
}, |
||||
components: {} |
||||
} |
||||
</script> |
||||
|
||||
<style lang="scss" rel="stylesheet/scss"> |
||||
@import "dag"; |
||||
</style> |
@ -0,0 +1,101 @@
|
||||
<template> |
||||
<x-select |
||||
style="width: 170px;" |
||||
:disabled="isDetails" |
||||
@on-change="_onChange" |
||||
v-model="value"> |
||||
<x-input |
||||
ref="input" |
||||
slot="trigger" |
||||
v-if="isInput" |
||||
:disabled="isDetails" |
||||
slot-scope="{ selectedModel }" |
||||
maxlength="4" |
||||
@on-blur="_onBlur" |
||||
:placeholder="$t('请选择')" |
||||
:value="selectedModel === null ? '0' : selectedModel.value" |
||||
style="width: 100%;" |
||||
@on-click-icon.stop="_ckIcon"> |
||||
<i slot="suffix" class="fa fa-times-circle" style="font-size: 15px;cursor: pointer;" v-show="!isIconState"></i> |
||||
<i slot="suffix" class="ans-icon-arrow-down" style="font-size: 12px;" v-show="isIconState"></i> |
||||
</x-input> |
||||
<x-option |
||||
v-for="city in list" |
||||
:key="city" |
||||
:value="city" |
||||
:label="city"> |
||||
</x-option> |
||||
</x-select> |
||||
</template> |
||||
<script> |
||||
import _ from 'lodash' |
||||
import i18n from '@/module/i18n' |
||||
import disabledState from '@/module/mixin/disabledState' |
||||
|
||||
export default { |
||||
name: 'select-input', |
||||
data () { |
||||
return { |
||||
isIconState: false, |
||||
isInput: true |
||||
} |
||||
}, |
||||
mixins: [disabledState], |
||||
props: { |
||||
value: String, |
||||
list: Array |
||||
}, |
||||
model: { |
||||
prop: 'value', |
||||
event: 'valueEvent' |
||||
}, |
||||
methods: { |
||||
_onChange (o) { |
||||
this.$emit('valueEvent', +o.value) |
||||
this._setIconState(+o.value) |
||||
}, |
||||
_setIconState (value) { |
||||
// Whether there is a list |
||||
this.isIconState = _.includes(this.list, parseInt(value)) |
||||
}, |
||||
_ckIcon () { |
||||
if (this.isDetails) { |
||||
return |
||||
} |
||||
this.isInput = false |
||||
this.$emit('valueEvent', +this.list[0]) |
||||
this.isIconState = true |
||||
// Refresh instance |
||||
setTimeout(() => { |
||||
this.isInput = true |
||||
}, 1) |
||||
}, |
||||
_onBlur () { |
||||
let val = $(this.$refs['input'].$el).find('input')[0].value |
||||
if (this._validation(val)) { |
||||
this.$emit('valueEvent', val) |
||||
this._setIconState(val) |
||||
} |
||||
}, |
||||
_validation (val) { |
||||
if (val === '0') return true |
||||
|
||||
if (!(/(^[0-9]*[1-9][0-9]*$)/.test(val))) { |
||||
this.$message.warning(`${i18n.$t('请输入正整数')}`) |
||||
// init |
||||
this._ckIcon() |
||||
return false |
||||
} |
||||
return true |
||||
} |
||||
}, |
||||
watch: { |
||||
}, |
||||
created () { |
||||
this._setIconState(this.value) |
||||
}, |
||||
mounted () { |
||||
}, |
||||
components: {} |
||||
} |
||||
</script> |
@ -0,0 +1,113 @@
|
||||
<template> |
||||
<div class="timeout-alarm-model"> |
||||
<div class="clearfix list"> |
||||
<div class="text-box"> |
||||
<span>{{$t('任务超时告警')}}</span> |
||||
</div> |
||||
<div class="cont-box"> |
||||
<label class="label-box"> |
||||
<div style="padding-top: 5px;"> |
||||
<x-switch v-model="enable" @on-click="_onSwitch" :disabled="isDetails"></x-switch> |
||||
</div> |
||||
</label> |
||||
</div> |
||||
</div> |
||||
<div class="clearfix list" v-if="enable"> |
||||
<div class="text-box"> |
||||
<span>{{$t('超时策略')}}</span> |
||||
</div> |
||||
<div class="cont-box"> |
||||
<label class="label-box"> |
||||
<div style="padding-top: 6px;"> |
||||
<x-checkbox-group v-model="strategy"> |
||||
<x-checkbox label="WARN" :disabled="isDetails">{{$t('超时告警')}}</x-checkbox> |
||||
<x-checkbox label="FAILED" :disabled="isDetails">{{$t('超时失败')}}</x-checkbox> |
||||
</x-checkbox-group> |
||||
</div> |
||||
</label> |
||||
</div> |
||||
</div> |
||||
<div class="clearfix list" v-if="enable"> |
||||
<div class="text-box"> |
||||
<span>{{$t('超时时长')}}</span> |
||||
</div> |
||||
<div class="cont-box"> |
||||
<label class="label-box"> |
||||
<x-input v-model="interval" style="width: 128px;" :disabled="isDetails"> |
||||
<span slot="append">{{$t('分')}}</span> |
||||
</x-input> |
||||
</label> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
<script> |
||||
import _ from 'lodash' |
||||
import disabledState from '@/module/mixin/disabledState' |
||||
|
||||
export default { |
||||
name: 'timeout-alarm', |
||||
data () { |
||||
return { |
||||
// Timeout display hiding |
||||
enable: false, |
||||
// Timeout strategy |
||||
strategy: [], |
||||
// Timeout period |
||||
interval: null |
||||
} |
||||
}, |
||||
mixins: [disabledState], |
||||
props: { |
||||
backfillItem: Object |
||||
}, |
||||
methods: { |
||||
_onSwitch (is) { |
||||
// Timeout strategy |
||||
this.strategy = is ? ['WARN'] : [] |
||||
// Timeout period |
||||
this.interval = is ? 30 : null |
||||
}, |
||||
_verification () { |
||||
// Verification timeout policy |
||||
if (this.enable && !this.strategy.length) { |
||||
this.$message.warning(`${this.$t('超时策略必须选一个')}`) |
||||
return false |
||||
} |
||||
// Verify timeout duration Non 0 positive integer |
||||
if (this.enable && !parseInt(this.interval) && !_.isInteger(this.interval)) { |
||||
this.$message.warning(`${this.$t('超时时长必须为正整数')}`) |
||||
return false |
||||
} |
||||
this.$emit('on-timeout', { |
||||
strategy: (() => { |
||||
// Handling checkout sequence |
||||
let strategy = this.strategy |
||||
if (strategy.length === 2 && strategy[0] === 'FAILED') { |
||||
return [strategy[1], strategy[0]].join(',') |
||||
} else { |
||||
return strategy.join(',') |
||||
} |
||||
})(), |
||||
interval: parseInt(this.interval), |
||||
enable: this.enable |
||||
}) |
||||
return true |
||||
} |
||||
}, |
||||
watch: { |
||||
}, |
||||
created () { |
||||
let o = this.backfillItem |
||||
// Non-null objects represent backfill |
||||
if (!_.isEmpty(o) && o.timeout) { |
||||
this.enable = o.timeout.enable || false |
||||
this.strategy = _.split(o.timeout.strategy, ',') || ['WARN'] |
||||
this.interval = o.timeout.interval || null |
||||
} |
||||
}, |
||||
mounted () { |
||||
}, |
||||
components: {} |
||||
} |
||||
</script> |
@ -0,0 +1,554 @@
|
||||
<template> |
||||
<div class="form-model-model" v-clickoutside="_handleClose"> |
||||
<div class="title-box"> |
||||
<span class="name">{{$t('当前节点设置')}}</span> |
||||
<span class="go-subtask"> |
||||
<!-- Component can't pop up box to do component processing --> |
||||
<m-log :item="backfillItem"> |
||||
<template slot="history"><a href="javascript:" @click="_seeHistory" ><i class="iconfont"></i><em>{{$t('查看历史')}}</em></a></template> |
||||
<template slot="log"><a href="javascript:"><i class="iconfont"></i><em>{{$t('查看日志')}}</em></a></template> |
||||
</m-log> |
||||
<a href="javascript:" @click="_goSubProcess" v-if="_isGoSubProcess"><i class="iconfont"></i><em>{{$t('进入该子节点')}}</em></a> |
||||
</span> |
||||
</div> |
||||
<div class="content-box" v-if="isContentBox"> |
||||
<div class="from-model"> |
||||
|
||||
<!-- Node name --> |
||||
<div class="clearfix list"> |
||||
<div class="text-box"><span>{{$t('节点名称')}}</span></div> |
||||
<div class="cont-box"> |
||||
<label class="label-box"> |
||||
<x-input |
||||
type="text" |
||||
v-model="name" |
||||
:disabled="isDetails" |
||||
:placeholder="$t('请输入name(必填)')" |
||||
maxlength="100" |
||||
@on-blur="_verifName()" |
||||
autocomplete="off"> |
||||
</x-input> |
||||
</label> |
||||
</div> |
||||
</div> |
||||
|
||||
<!-- Running sign --> |
||||
<div class="clearfix list"> |
||||
<div class="text-box"><span>{{$t('运行标志')}}</span></div> |
||||
<div class="cont-box"> |
||||
<label class="label-box"> |
||||
<x-radio-group v-model="runFlag" > |
||||
<x-radio :label="'NORMAL'" :disabled="isDetails">{{$t('正常')}}</x-radio> |
||||
<x-radio :label="'FORBIDDEN'" :disabled="isDetails">{{$t('禁止执行')}}</x-radio> |
||||
</x-radio-group> |
||||
</label> |
||||
</div> |
||||
</div> |
||||
|
||||
<!-- desc --> |
||||
<div class="clearfix list"> |
||||
<div class="text-box"> |
||||
<span>{{$t('描述')}}</span> |
||||
</div> |
||||
<div class="cont-box"> |
||||
|
||||
<label class="label-box"> |
||||
<x-input |
||||
resize |
||||
:autosize="{minRows:2}" |
||||
type="textarea" |
||||
:disabled="isDetails" |
||||
v-model="desc" |
||||
:placeholder="$t('请输入desc')" |
||||
autocomplete="off"> |
||||
</x-input> |
||||
</label> |
||||
</div> |
||||
</div> |
||||
|
||||
<!-- Task priority --> |
||||
<div class="clearfix list"> |
||||
<div class="text-box"> |
||||
<span>{{$t('任务优先级')}}</span> |
||||
</div> |
||||
<div class="cont-box"> |
||||
<label class="label-box"> |
||||
<m-priority v-model="taskInstancePriority" style="width: 180px;"></m-priority> |
||||
</label> |
||||
</div> |
||||
</div> |
||||
|
||||
<!-- Number of failed retries --> |
||||
<div class="clearfix list" v-if="taskType !== 'SUB_PROCESS'"> |
||||
<div class="text-box"> |
||||
<span>{{$t('失败重试次数')}}</span> |
||||
</div> |
||||
<div class="cont-box"> |
||||
<m-select-input v-model="maxRetryTimes" :list="[0,1,2,3,4]"> |
||||
</m-select-input> |
||||
<span>({{$t('次')}})</span> |
||||
<span class="text-b">{{$t('失败重试间隔')}}</span> |
||||
<m-select-input v-model="retryInterval" :list="[1,10,30,60,120]"> |
||||
</m-select-input> |
||||
<span>({{$t('分')}})</span> |
||||
</div> |
||||
</div> |
||||
|
||||
<!-- Task timeout alarm --> |
||||
<m-timeout-alarm |
||||
ref="timeout" |
||||
:backfill-item="backfillItem" |
||||
@on-timeout="_onTimeout"> |
||||
</m-timeout-alarm> |
||||
|
||||
<!-- shell node --> |
||||
<m-shell |
||||
v-if="taskType === 'SHELL'" |
||||
@on-params="_onParams" |
||||
ref="SHELL" |
||||
:backfill-item="backfillItem"> |
||||
</m-shell> |
||||
<!-- sub_process node --> |
||||
<m-sub-process |
||||
v-if="taskType === 'SUB_PROCESS'" |
||||
@on-params="_onParams" |
||||
@on-set-process-name="_onSetProcessName" |
||||
ref="SUB_PROCESS" |
||||
:backfill-item="backfillItem"> |
||||
</m-sub-process> |
||||
<!-- procedure node --> |
||||
<m-procedure |
||||
v-if="taskType === 'PROCEDURE'" |
||||
@on-params="_onParams" |
||||
ref="PROCEDURE" |
||||
:backfill-item="backfillItem"> |
||||
</m-procedure> |
||||
<!-- sql node --> |
||||
<m-sql |
||||
v-if="taskType === 'SQL'" |
||||
@on-params="_onParams" |
||||
ref="SQL" |
||||
:backfill-item="backfillItem"> |
||||
</m-sql> |
||||
<!-- spark node --> |
||||
<m-spark |
||||
v-if="taskType === 'SPARK'" |
||||
@on-params="_onParams" |
||||
ref="SPARK" |
||||
:backfill-item="backfillItem"> |
||||
</m-spark> |
||||
<!-- mr node --> |
||||
<m-mr |
||||
v-if="taskType === 'MR'" |
||||
@on-params="_onParams" |
||||
ref="MR" |
||||
:backfill-item="backfillItem"> |
||||
</m-mr> |
||||
<!-- python node --> |
||||
<m-python |
||||
v-if="taskType === 'PYTHON'" |
||||
@on-params="_onParams" |
||||
ref="PYTHON" |
||||
:backfill-item="backfillItem"> |
||||
</m-python> |
||||
<!-- dependent node --> |
||||
<m-dependent |
||||
v-if="taskType === 'DEPENDENT'" |
||||
@on-dependent="_onDependent" |
||||
ref="DEPENDENT" |
||||
:backfill-item="backfillItem"> |
||||
</m-dependent> |
||||
|
||||
</div> |
||||
</div> |
||||
<div class="bottom-box"> |
||||
<div class="submit" style="background: #fff;"> |
||||
<x-button type="text" @click="close()"> {{$t('取消')}} </x-button> |
||||
<x-button type="primary" shape="circle" :loading="spinnerLoading" @click="ok()" :disabled="isDetails" v-ps="['GENERAL_USER']">{{spinnerLoading ? 'Loading...' : $t('确认添加')}} </x-button> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
<script> |
||||
import _ from 'lodash' |
||||
import mLog from './log' |
||||
import mMr from './tasks/mr' |
||||
import mSql from './tasks/sql' |
||||
import i18n from '@/module/i18n' |
||||
import mShell from './tasks/shell' |
||||
import mSpark from './tasks/spark' |
||||
import mPython from './tasks/python' |
||||
import { isNameExDag } from './../plugIn/util' |
||||
import JSP from './../plugIn/jsPlumbHandle' |
||||
import mProcedure from './tasks/procedure' |
||||
import mDependent from './tasks/dependent' |
||||
import mSubProcess from './tasks/sub_process' |
||||
import mSelectInput from './_source/selectInput' |
||||
import mTimeoutAlarm from './_source/timeoutAlarm' |
||||
import clickoutside from '@/module/util/clickoutside' |
||||
import disabledState from '@/module/mixin/disabledState' |
||||
import mPriority from '@/module/components/priority/priority' |
||||
|
||||
export default { |
||||
name: 'form-model', |
||||
data () { |
||||
return { |
||||
// loading |
||||
spinnerLoading: false, |
||||
// node name |
||||
name: ``, |
||||
// desc |
||||
desc: '', |
||||
// Node echo data |
||||
backfillItem: {}, |
||||
// Resource(list) |
||||
resourcesList: [], |
||||
// dependence |
||||
dependence: {}, |
||||
// Current node params data |
||||
params: {}, |
||||
// Running sign |
||||
runFlag: 'NORMAL', |
||||
// The second echo problem caused by the node data is specifically which node hook caused the unfinished special treatment |
||||
isContentBox: false, |
||||
// Number of failed retries |
||||
maxRetryTimes: '0', |
||||
// Failure retry interval |
||||
retryInterval: '1', |
||||
// Task timeout alarm |
||||
timeout: {}, |
||||
// Task priority |
||||
taskInstancePriority: 'MEDIUM' |
||||
} |
||||
}, |
||||
/** |
||||
* Click on events that are not generated internally by the component |
||||
*/ |
||||
directives: { clickoutside }, |
||||
mixins: [disabledState], |
||||
props: { |
||||
id: Number, |
||||
taskType: String, |
||||
self: Object |
||||
}, |
||||
methods: { |
||||
/** |
||||
* depend |
||||
*/ |
||||
_onDependent (o) { |
||||
this.dependence = Object.assign(this.dependence, {}, o) |
||||
}, |
||||
/** |
||||
* Task timeout alarm |
||||
*/ |
||||
_onTimeout (o) { |
||||
this.timeout = Object.assign(this.timeout, {}, o) |
||||
}, |
||||
/** |
||||
* Click external to close the current component |
||||
*/ |
||||
_handleClose () { |
||||
this.close() |
||||
}, |
||||
/** |
||||
* Jump to task instance |
||||
*/ |
||||
_seeHistory () { |
||||
this.self.$router.push({ |
||||
name: 'task-instance-list', |
||||
query: { |
||||
processInstanceId: this.self.$route.params.id, |
||||
taskName: this.backfillItem.name |
||||
} |
||||
}) |
||||
this.$modal.destroy() |
||||
}, |
||||
/** |
||||
* Enter the child node to judge the process instance or the process definition |
||||
* @param type = instance |
||||
*/ |
||||
_goSubProcess () { |
||||
if (_.isEmpty(this.backfillItem)) { |
||||
this.$message.warning(`${i18n.$t('新创建子工作流还未执行,不能进入子工作流')}`) |
||||
return |
||||
} |
||||
if (this.router.history.current.name === 'projects-instance-details') { |
||||
let stateId = $(`#${this.id}`).attr('data-state-id') || null |
||||
if (!stateId) { |
||||
this.$message.warning(`${i18n.$t('该任务还未执行,不能进入子工作流')}`) |
||||
return |
||||
} |
||||
this.store.dispatch('dag/getSubProcessId', { taskId: stateId }).then(res => { |
||||
this.$emit('onSubProcess', { |
||||
subProcessId: res.data.subProcessInstanceId, |
||||
fromThis: this |
||||
}) |
||||
}).catch(e => { |
||||
this.$message.error(e.msg || '') |
||||
}) |
||||
} else { |
||||
this.$emit('onSubProcess', { |
||||
subProcessId: this.backfillItem.params.processDefinitionId, |
||||
fromThis: this |
||||
}) |
||||
} |
||||
}, |
||||
/** |
||||
* return params |
||||
*/ |
||||
_onParams (o) { |
||||
this.params = Object.assign(this.params, {}, o) |
||||
}, |
||||
/** |
||||
* verification name |
||||
*/ |
||||
_verifName () { |
||||
if (!_.trim(this.name)) { |
||||
this.$message.warning(`${i18n.$t('请输入名称(必填)')}`) |
||||
return false |
||||
} |
||||
if (this.name === this.backfillItem.name) { |
||||
return true |
||||
} |
||||
// Name repeat depends on dom backfill dependent store |
||||
if (isNameExDag(this.name, _.isEmpty(this.backfillItem) ? 'dom' : 'backfill')) { |
||||
this.$message.warning(`${i18n.$t('名称已存在请重新输入')}`) |
||||
return false |
||||
} |
||||
return true |
||||
}, |
||||
/** |
||||
* Global verification procedure |
||||
*/ |
||||
_verification () { |
||||
// Verify name |
||||
if (!this._verifName()) { |
||||
return |
||||
} |
||||
// Verify task alarm parameters |
||||
if (!this.$refs['timeout']._verification()) { |
||||
return |
||||
} |
||||
// Verify node parameters |
||||
if (!this.$refs[this.taskType]._verification()) { |
||||
return |
||||
} |
||||
|
||||
$(`#${this.id}`).find('span').text(this.name) |
||||
|
||||
// Store the corresponding node data structure |
||||
this.$emit('addTaskInfo', { |
||||
item: { |
||||
type: this.taskType, |
||||
id: this.id, |
||||
name: this.name, |
||||
params: this.params, |
||||
desc: this.desc, |
||||
runFlag: this.runFlag, |
||||
dependence: this.dependence, |
||||
maxRetryTimes: this.maxRetryTimes, |
||||
retryInterval: this.retryInterval, |
||||
timeout: this.timeout, |
||||
taskInstancePriority: this.taskInstancePriority |
||||
}, |
||||
fromThis: this |
||||
}) |
||||
}, |
||||
/** |
||||
* Sub-workflow selected node echo name |
||||
*/ |
||||
_onSetProcessName (name) { |
||||
this.name = name |
||||
}, |
||||
/** |
||||
* Submit verification |
||||
*/ |
||||
ok () { |
||||
this._verification() |
||||
}, |
||||
/** |
||||
* Close and destroy component and component internal events |
||||
*/ |
||||
close () { |
||||
let flag = false |
||||
// Delete node without storage |
||||
if (!this.backfillItem.name) { |
||||
flag = true |
||||
} |
||||
this.isContentBox = false |
||||
// flag Whether to delete a node this.$destroy() |
||||
this.$emit('close', { |
||||
flag: flag, |
||||
fromThis: this |
||||
}) |
||||
} |
||||
}, |
||||
watch: {}, |
||||
created () { |
||||
// Unbind copy and paste events |
||||
JSP.removePaste() |
||||
// Backfill data |
||||
let taskList = this.store.state.dag.tasks |
||||
let o = {} |
||||
if (taskList.length) { |
||||
taskList.forEach(v => { |
||||
if (v.id === this.id) { |
||||
o = v |
||||
this.backfillItem = v |
||||
} |
||||
}) |
||||
// Non-null objects represent backfill |
||||
if (!_.isEmpty(o)) { |
||||
this.name = o.name |
||||
this.taskInstancePriority = o.taskInstancePriority |
||||
this.runFlag = o.runFlag || 'NORMAL' |
||||
this.desc = o.desc |
||||
this.maxRetryTimes = o.maxRetryTimes |
||||
this.retryInterval = o.retryInterval |
||||
} |
||||
} |
||||
this.isContentBox = true |
||||
}, |
||||
mounted () {}, |
||||
updated () { |
||||
}, |
||||
beforeDestroy () { |
||||
}, |
||||
destroyed () { |
||||
}, |
||||
computed: { |
||||
/** |
||||
* Child workflow entry show/hide |
||||
*/ |
||||
_isGoSubProcess () { |
||||
return this.taskType === 'SUB_PROCESS' && this.name |
||||
} |
||||
}, |
||||
components: { |
||||
mMr, |
||||
mShell, |
||||
mSubProcess, |
||||
mProcedure, |
||||
mSql, |
||||
mLog, |
||||
mSpark, |
||||
mPython, |
||||
mDependent, |
||||
mSelectInput, |
||||
mTimeoutAlarm, |
||||
mPriority |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style lang="scss" rel="stylesheet/scss"> |
||||
.form-model-model { |
||||
width: 720px; |
||||
position: relative; |
||||
.title-box { |
||||
height: 61px; |
||||
border-bottom: 1px solid #DCDEDC; |
||||
position: relative; |
||||
.name { |
||||
position: absolute; |
||||
left: 24px; |
||||
top: 18px; |
||||
font-size: 16px; |
||||
} |
||||
.go-subtask { |
||||
position: absolute; |
||||
right: 30px; |
||||
top: 17px; |
||||
a { |
||||
font-size: 14px; |
||||
color: #0097e0; |
||||
margin-left: 10px; |
||||
i.iconfont { |
||||
font-size: 18px; |
||||
vertical-align: middle; |
||||
} |
||||
em { |
||||
color: #333; |
||||
vertical-align: middle; |
||||
font-style: normal; |
||||
vertical-align: middle; |
||||
padding-left: 2px; |
||||
} |
||||
&:hover { |
||||
em { |
||||
text-decoration: underline; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
.bottom-box { |
||||
position: absolute; |
||||
bottom: 0; |
||||
left: 0; |
||||
width: 100%; |
||||
text-align: right; |
||||
height: 60px; |
||||
line-height: 60px; |
||||
border-top: 1px solid #DCDEDC; |
||||
background: #fff; |
||||
.submit { |
||||
padding-right: 20px; |
||||
position: relative; |
||||
z-index: 9; |
||||
} |
||||
} |
||||
.content-box { |
||||
overflow-y: scroll; |
||||
height: calc(100vh - 61px); |
||||
padding-bottom: 60px; |
||||
} |
||||
} |
||||
.from-model { |
||||
padding-top: 26px; |
||||
>div { |
||||
clear: both; |
||||
} |
||||
.list { |
||||
position: relative; |
||||
margin-bottom: 10px; |
||||
.text-box { |
||||
width: 110px; |
||||
float: left; |
||||
text-align: right; |
||||
margin-right: 10px; |
||||
>span { |
||||
font-size: 14px; |
||||
color: #777; |
||||
display: inline-block; |
||||
padding-top: 6px; |
||||
} |
||||
} |
||||
.cont-box { |
||||
width: 580px; |
||||
float: left; |
||||
.label-box { |
||||
width: 100%; |
||||
} |
||||
.text-b { |
||||
font-size: 14px; |
||||
color: #777; |
||||
display: inline-block; |
||||
padding:0 6px 0 20px; |
||||
} |
||||
} |
||||
.add { |
||||
line-height: 32px; |
||||
a { |
||||
color: #0097e0; |
||||
} |
||||
} |
||||
&:hover { |
||||
} |
||||
.list-t { |
||||
width: 50%; |
||||
float: left; |
||||
} |
||||
} |
||||
} |
||||
</style> |
@ -0,0 +1,357 @@
|
||||
<template> |
||||
<span class="log-model"> |
||||
<span v-if="stateId && item.type !== 'SUB_PROCESS'"> |
||||
<slot name="history"></slot> |
||||
<span @click="_ckLog"> |
||||
<slot name="log" ></slot> |
||||
</span> |
||||
</span> |
||||
<transition name="fade"> |
||||
<div v-show="isLog || source === 'list'" class="log-pop"> |
||||
<div class="log-box" > |
||||
<div class="title"> |
||||
<span>{{$t('查看日志')}}</span> |
||||
<div class="full-screen"> |
||||
<a href="javascript:" @click="_downloadLog" data-container="body" data-toggle="tooltip" :title="$t('下载日志')"> |
||||
<i class="iconfont" style="font-size: 20px"></i> |
||||
</a> |
||||
<a href="javascript:" class="refresh-log" :class="loading ? 'active' :''" @click="!loading && _refreshLog()" data-container="body" data-toggle="tooltip" :title="$t('刷新日志')"> |
||||
<i class="fa iconfont"></i> |
||||
</a> |
||||
<a href="javascript:" @click="_screenOpen" v-show="!isScreen" data-container="body" data-toggle="tooltip" :title="$t('进入全屏')"> |
||||
<i class="iconfont"></i> |
||||
</a> |
||||
<a href="javascript:" @click="_screenClose" v-show="isScreen" data-container="body" data-toggle="tooltip" :title="$t('取消全屏')"> |
||||
<i class="iconfont"></i> |
||||
</a> |
||||
</div> |
||||
</div> |
||||
<div class="content"> |
||||
<div class="content-log-box" > |
||||
<textarea class="textarea-ft" id="textarea-log" style="width: 100%" spellcheck="false" ></textarea> |
||||
</div> |
||||
</div> |
||||
<div class="operation"> |
||||
<x-button type="primary" shape="circle" @click="close"> {{$t('关闭')}} </x-button> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</transition> |
||||
</span> |
||||
</template> |
||||
<script> |
||||
import _ from 'lodash' |
||||
import i18n from '@/module/i18n' |
||||
import store from '@/conf/home/store' |
||||
import router from '@/conf/home/router' |
||||
import { downloadFile } from '@/module/download' |
||||
|
||||
/** |
||||
* Calculate text size |
||||
*/ |
||||
const handerTextareaSize = (isH = 0) => { |
||||
$('body').find('.tooltip.fade.top.in').remove() |
||||
return $('.textarea-ft').css({ 'height': `${$('.content-log-box').height() - isH}px` }) |
||||
} |
||||
|
||||
let content = '' |
||||
|
||||
export default { |
||||
name: 'log', |
||||
data () { |
||||
return { |
||||
store, |
||||
router, |
||||
isLog: false, |
||||
stateId: $(`#${this.item.id}`).attr('data-state-id') || null, |
||||
isScreen: false, |
||||
loadingIndex: 0, |
||||
isData: true, |
||||
loading: false |
||||
} |
||||
}, |
||||
props: { |
||||
item: { |
||||
type: Object, |
||||
default: {} |
||||
}, |
||||
source: { |
||||
type: String, |
||||
default: 'from' |
||||
}, |
||||
logId: Number |
||||
}, |
||||
methods: { |
||||
_refreshLog () { |
||||
this.loading = true |
||||
this.store.dispatch('dag/getLog', this._rtParam).then(res => { |
||||
setTimeout(() => { |
||||
this.loading = false |
||||
if (res.data) { |
||||
this.$message.success(`${i18n.$t('更新日志成功')}`) |
||||
} else { |
||||
this.$message.warning(`${i18n.$t('暂无更多日志')}`) |
||||
} |
||||
}, 1500) |
||||
// Handling text field size |
||||
handerTextareaSize().html('').text(res.data || `${i18n.$t('暂无日志')}`) |
||||
}).catch(e => { |
||||
this.$message.error(e.msg || '') |
||||
this.loading = false |
||||
}) |
||||
}, |
||||
_ckLog () { |
||||
this.isLog = true |
||||
this.store.dispatch('dag/getLog', this._rtParam).then(res => { |
||||
this.$message.destroy() |
||||
|
||||
if (!res.data) { |
||||
this.isData = false |
||||
setTimeout(() => { |
||||
this.$message.warning(`${i18n.$t('暂无更多日志')}`) |
||||
}, 1000) |
||||
// Handling text field size |
||||
handerTextareaSize().html('').text(content || `${i18n.$t('暂无日志')}`) |
||||
} else { |
||||
this.isData = true |
||||
content = res.data |
||||
// Handling text field size |
||||
handerTextareaSize().html('').text(content || `${i18n.$t('暂无日志')}`) |
||||
|
||||
setTimeout(() => { |
||||
$('#textarea').scrollTop(2) |
||||
}, 800) |
||||
} |
||||
}).catch(e => { |
||||
this.$message.error(e.msg || '') |
||||
this.$message.destroy() |
||||
}) |
||||
}, |
||||
_screenOpen () { |
||||
this.isScreen = true |
||||
let $logBox = $('.log-box') |
||||
let winW = $(window).width() - 40 |
||||
let winH = $(window).height() - 40 |
||||
$logBox.css({ |
||||
width: winW, |
||||
height: winH, |
||||
marginLeft: `-${parseInt(winW / 2)}px`, |
||||
marginTop: `-${parseInt(winH / 2)}px` |
||||
}) |
||||
$logBox.find('.content').animate({ scrollTop: 0 }, 0) |
||||
// Handling text field size |
||||
handerTextareaSize().html('').text(content) |
||||
}, |
||||
_screenClose () { |
||||
this.isScreen = false |
||||
let $logBox = $('.log-box') |
||||
$logBox.attr('style', '') |
||||
$logBox.find('.content').animate({ scrollTop: 0 }, 0) |
||||
// Handling text field size |
||||
handerTextareaSize().html('').text(content) |
||||
}, |
||||
/** |
||||
* Download log |
||||
*/ |
||||
_downloadLog () { |
||||
downloadFile('/escheduler/log/download-log', { |
||||
taskInstId: this.stateId || this.logId |
||||
}) |
||||
}, |
||||
/** |
||||
* up |
||||
*/ |
||||
_onUp: _.debounce(function () { |
||||
this.loadingIndex = this.loadingIndex - 1 |
||||
this._ckLog() |
||||
}, 1000, { |
||||
'leading': false, |
||||
'trailing': true |
||||
}), |
||||
/** |
||||
* down |
||||
*/ |
||||
_onDown: _.debounce(function () { |
||||
this.loadingIndex = this.loadingIndex + 1 |
||||
this._ckLog() |
||||
}, 1000, { |
||||
'leading': false, |
||||
'trailing': true |
||||
}), |
||||
/** |
||||
* Monitor scroll bar |
||||
*/ |
||||
_onTextareaScroll () { |
||||
let self = this |
||||
$('#textarea-log').scroll(function () { |
||||
let $this = $(this) |
||||
// Listen for scrollbar events |
||||
if (($this.scrollTop() + $this.height()) === $this.height()) { |
||||
if (self.loadingIndex > 0) { |
||||
self.$message.loading({ |
||||
content: `${i18n.$t('正在努力请求日志中...')}`, |
||||
duration: 0, |
||||
closable: false |
||||
}) |
||||
self._onUp() |
||||
} |
||||
} |
||||
// Listen for scrollbar events |
||||
if ($this.get(0).scrollHeight === ($this.height() + $this.scrollTop())) { |
||||
// No data is not requested |
||||
if (self.isData) { |
||||
self.$message.loading({ |
||||
content: `${i18n.$t('正在努力请求日志中...')}`, |
||||
duration: 0, |
||||
closable: false |
||||
}) |
||||
self._onDown() |
||||
} |
||||
} |
||||
}) |
||||
}, |
||||
/** |
||||
* close |
||||
*/ |
||||
close () { |
||||
$('body').find('.tooltip.fade.top.in').remove() |
||||
this.isScreen = false |
||||
this.isLog = false |
||||
content = '' |
||||
this.$emit('close') |
||||
} |
||||
}, |
||||
watch: {}, |
||||
created () { |
||||
// Source is a task instance |
||||
if (this.source === 'list') { |
||||
this.$message.loading({ |
||||
content: `${i18n.$t('正在努力请求日志中...')}`, |
||||
duration: 0, |
||||
closable: false |
||||
}) |
||||
this._ckLog() |
||||
} |
||||
}, |
||||
mounted () { |
||||
this._onTextareaScroll() |
||||
}, |
||||
updated () { |
||||
}, |
||||
computed: { |
||||
_rtParam () { |
||||
return { |
||||
taskInstId: this.stateId || this.logId, |
||||
skipLineNum: parseInt(`${this.loadingIndex ? this.loadingIndex + '0000' : 0}`), |
||||
limit: parseInt(`${this.loadingIndex ? this.loadingIndex + 1 : 1}0000`) |
||||
} |
||||
} |
||||
}, |
||||
components: { } |
||||
} |
||||
</script> |
||||
|
||||
<style lang="scss" rel="stylesheet/scss"> |
||||
.log-pop { |
||||
position: fixed; |
||||
left: 0; |
||||
top: 0; |
||||
width: 100%; |
||||
height: 100%; |
||||
background: rgba(0,0,0,.4); |
||||
z-index: 10; |
||||
.log-box { |
||||
width: 660px; |
||||
height: 520px; |
||||
background: #fff; |
||||
border-radius: 3px; |
||||
position: absolute; |
||||
left:50%; |
||||
top: 50%; |
||||
margin-left: -340px; |
||||
margin-top: -250px; |
||||
.title { |
||||
height: 50px; |
||||
border-bottom: 1px solid #dcdedc; |
||||
span { |
||||
font-size: 16px; |
||||
color: #333; |
||||
padding-left: 20px; |
||||
display: inline-block; |
||||
padding-top: 16px; |
||||
} |
||||
.full-screen { |
||||
position: absolute; |
||||
right: 20px; |
||||
top: 12px; |
||||
a{ |
||||
color: #0097e0; |
||||
font-size: 12px; |
||||
margin-left: 10px; |
||||
i { |
||||
vertical-align: middle; |
||||
} |
||||
} |
||||
.clock { |
||||
>i { |
||||
font-size: 20px; |
||||
vertical-align: middle; |
||||
transform: scale(1); |
||||
} |
||||
} |
||||
.refresh-log { |
||||
>i { |
||||
font-size: 24px; |
||||
vertical-align: middle; |
||||
transform: scale(1); |
||||
} |
||||
&.active { |
||||
>i { |
||||
-webkit-transition-property: -webkit-transform; |
||||
-webkit-transition-duration: 1s; |
||||
-moz-transition-property: -moz-transform; |
||||
-moz-transition-duration: 1s; |
||||
-webkit-animation: rotateloading .4s linear infinite; |
||||
-moz-animation: rotateloading .4s linear infinite; |
||||
-o-animation: rotateloading .4s linear infinite; |
||||
animation: rotateloading .4s linear infinite; |
||||
transform: scale(.4); |
||||
color: #999; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
.content { |
||||
height: calc(100% - 100px); |
||||
background: #002A35; |
||||
padding:6px 2px; |
||||
.content-log-box { |
||||
width: 100%; |
||||
height: 100%; |
||||
word-break:break-all; |
||||
textarea { |
||||
background: none; |
||||
color: #9CABAF; |
||||
border: 0; |
||||
font-family: 'Microsoft Yahei,Arial,Hiragino Sans GB,tahoma,SimSun,sans-serif'; |
||||
font-weight: bold; |
||||
resize:none; |
||||
line-height: 1.6; |
||||
padding: 6px; |
||||
} |
||||
} |
||||
} |
||||
.operation { |
||||
text-align: right; |
||||
height: 50px; |
||||
line-height: 44px; |
||||
border-top: 1px solid #dcdedc; |
||||
padding-right: 20px; |
||||
background: #fff; |
||||
position: relative; |
||||
} |
||||
} |
||||
} |
||||
|
||||
</style> |
@ -0,0 +1,199 @@
|
||||
|
||||
import i18n from '@/module/i18n' |
||||
|
||||
/** |
||||
* cycle |
||||
*/ |
||||
const cycleList = [ |
||||
{ |
||||
value: 'month', |
||||
label: `${i18n.$t('月')}` |
||||
}, |
||||
{ |
||||
value: 'week', |
||||
label: `${i18n.$t('周')}` |
||||
}, |
||||
{ |
||||
value: 'day', |
||||
label: `${i18n.$t('日')}` |
||||
}, |
||||
{ |
||||
value: 'hour', |
||||
label: `${i18n.$t('时')}` |
||||
} |
||||
] |
||||
|
||||
/** |
||||
* cycle value |
||||
*/ |
||||
const dateValueList = { |
||||
'hour': [ |
||||
{ |
||||
value: 'last1Hour', |
||||
label: `${i18n.$t('前1小时')}` |
||||
}, |
||||
{ |
||||
value: 'last2Hours', |
||||
label: `${i18n.$t('前2小时')}` |
||||
}, |
||||
{ |
||||
value: 'last3Hours', |
||||
label: `${i18n.$t('前3小时')}` |
||||
} |
||||
], |
||||
'day': [ |
||||
{ |
||||
value: 'last1Days', |
||||
label: `${i18n.$t('昨天')}` |
||||
}, |
||||
{ |
||||
value: 'last2Days', |
||||
label: `${i18n.$t('前两天')}` |
||||
}, |
||||
{ |
||||
value: 'last3Days', |
||||
label: `${i18n.$t('前三天')}` |
||||
}, |
||||
{ |
||||
value: 'last7Days', |
||||
label: `${i18n.$t('前七天')}` |
||||
} |
||||
], |
||||
'week': [ |
||||
{ |
||||
value: 'lastWeek', |
||||
label: `${i18n.$t('上周')}` |
||||
}, |
||||
{ |
||||
value: 'lastMonday', |
||||
label: `${i18n.$t('上周一')}` |
||||
}, |
||||
{ |
||||
value: 'lastTuesday', |
||||
label: `${i18n.$t('上周二')}` |
||||
}, |
||||
{ |
||||
value: 'lastWednesday', |
||||
label: `${i18n.$t('上周三')}` |
||||
}, |
||||
{ |
||||
value: 'lastThursday', |
||||
label: `${i18n.$t('上周四')}` |
||||
}, |
||||
{ |
||||
value: 'lastFriday', |
||||
label: `${i18n.$t('上周五')}` |
||||
}, |
||||
{ |
||||
value: 'lastSaturday', |
||||
label: `${i18n.$t('上周六')}` |
||||
}, |
||||
{ |
||||
value: 'lastSunday', |
||||
label: `${i18n.$t('上周日')}` |
||||
} |
||||
], |
||||
'month': [ |
||||
{ |
||||
value: 'lastMonth', |
||||
label: `${i18n.$t('上月')}` |
||||
}, |
||||
{ |
||||
value: 'lastMonthBegin', |
||||
label: `${i18n.$t('上月初')}` |
||||
}, |
||||
{ |
||||
value: 'lastMonthEnd', |
||||
label: `${i18n.$t('上月末')}` |
||||
} |
||||
] |
||||
} |
||||
|
||||
/** |
||||
* direct |
||||
*/ |
||||
const directList = [ |
||||
{ |
||||
id: 1, |
||||
code: 'IN', |
||||
disabled: false |
||||
}, |
||||
{ |
||||
id: 2, |
||||
code: 'OUT', |
||||
disabled: false |
||||
} |
||||
] |
||||
|
||||
/** |
||||
* type |
||||
*/ |
||||
const typeList = [ |
||||
{ |
||||
id: 1, |
||||
code: 'VARCHAR', |
||||
disabled: false |
||||
}, |
||||
{ |
||||
id: 2, |
||||
code: 'INTEGER', |
||||
disabled: false |
||||
}, |
||||
{ |
||||
id: 3, |
||||
code: 'LONG', |
||||
disabled: false |
||||
}, |
||||
{ |
||||
id: 4, |
||||
code: 'FLOAT', |
||||
disabled: false |
||||
}, |
||||
{ |
||||
id: 5, |
||||
code: 'DOUBLE', |
||||
disabled: false |
||||
}, |
||||
{ |
||||
id: 6, |
||||
code: 'DATE', |
||||
disabled: false |
||||
}, |
||||
{ |
||||
id: 7, |
||||
code: 'TIME', |
||||
disabled: false |
||||
}, |
||||
{ |
||||
id: 8, |
||||
code: 'TIMESTAMP', |
||||
disabled: false |
||||
}, |
||||
{ |
||||
id: 9, |
||||
code: 'BOOLEAN', |
||||
disabled: false |
||||
} |
||||
] |
||||
|
||||
/** |
||||
* sqlType |
||||
*/ |
||||
const sqlTypeList = [ |
||||
{ |
||||
id: 0, |
||||
code: `${i18n.$t('查询')}` |
||||
}, |
||||
{ |
||||
id: 1, |
||||
code: `${i18n.$t('非查询')}` |
||||
} |
||||
] |
||||
|
||||
export { |
||||
cycleList, |
||||
dateValueList, |
||||
typeList, |
||||
directList, |
||||
sqlTypeList |
||||
} |
@ -0,0 +1,133 @@
|
||||
<template> |
||||
<div class="datasource-model"> |
||||
<div class="select-listpp"> |
||||
<x-select v-model="type" |
||||
style="width: 160px;" |
||||
@on-change="_handleTypeChanged" |
||||
:disabled="isDetails"> |
||||
<x-option |
||||
v-for="city in typeList" |
||||
:key="city.code" |
||||
:value="city.code" |
||||
:label="city.code"> |
||||
</x-option> |
||||
</x-select> |
||||
<x-select :placeholder="$t('请选择数据源')" |
||||
v-model="datasource" |
||||
style="width: 288px;" |
||||
:disabled="isDetails"> |
||||
<x-option |
||||
v-for="city in datasourceList" |
||||
:key="city.id" |
||||
:value="city" |
||||
:label="city.code"> |
||||
</x-option> |
||||
</x-select> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
<script> |
||||
import _ from 'lodash' |
||||
import i18n from '@/module/i18n' |
||||
import disabledState from '@/module/mixin/disabledState' |
||||
|
||||
export default { |
||||
name: 'datasource', |
||||
data () { |
||||
return { |
||||
// Data source type |
||||
type: '', |
||||
// Data source type(List) |
||||
typeList: [], |
||||
// data source |
||||
datasource: {}, |
||||
// data source(List) |
||||
datasourceList: [] |
||||
} |
||||
}, |
||||
mixins: [disabledState], |
||||
props: { |
||||
data: Object, |
||||
supportType: Array |
||||
}, |
||||
methods: { |
||||
/** |
||||
* Verify data source |
||||
*/ |
||||
_verifDatasource () { |
||||
if (!this.datasource) { |
||||
this.$message.warning(`${i18n.$t('请选择数据源')}`) |
||||
return false |
||||
} |
||||
this.$emit('on-dsData', { |
||||
type: this.type, |
||||
datasource: this.datasource.id |
||||
}) |
||||
return true |
||||
}, |
||||
/** |
||||
* Get the corresponding datasource data according to type |
||||
*/ |
||||
_getDatasourceData () { |
||||
return new Promise((resolve, reject) => { |
||||
this.store.dispatch('dag/getDatasourceList', this.type).then(res => { |
||||
this.datasourceList = _.map(res.data, v => { |
||||
return { |
||||
id: v.id, |
||||
code: v.name, |
||||
disabled: false |
||||
} |
||||
}) |
||||
resolve() |
||||
}) |
||||
}) |
||||
}, |
||||
/** |
||||
* Brush type |
||||
*/ |
||||
_handleTypeChanged ({ value }) { |
||||
this.type = value |
||||
this._getDatasourceData().then(res => { |
||||
this.datasource = this.datasourceList.length && this.datasourceList[0] || {} |
||||
this.$emit('on-dsData', { |
||||
type: this.type, |
||||
datasource: this.datasource.id |
||||
}) |
||||
}) |
||||
} |
||||
}, |
||||
watch: {}, |
||||
created () { |
||||
let supportType = this.supportType || [] |
||||
this.typeList = _.cloneDeep(this.store.state.dag.dsTypeListS) |
||||
// Have a specified data source |
||||
if (supportType.length) { |
||||
let is = (type) => { |
||||
return !!_.filter(supportType, v => v === type).length |
||||
} |
||||
this.typeList = _.filter(this.typeList, v => is(v.code)) |
||||
} |
||||
|
||||
this.type = _.cloneDeep(this.data.type) || this.typeList[0].code |
||||
// init data |
||||
this._getDatasourceData().then(res => { |
||||
if (_.isEmpty(this.data)) { |
||||
this.$nextTick(() => { |
||||
this.datasource = this.datasourceList[0] |
||||
}) |
||||
} else { |
||||
this.$nextTick(() => { |
||||
this.datasource = _.filter(this.datasourceList, v => v.id === this.data.datasource)[0] |
||||
}) |
||||
} |
||||
this.$emit('on-dsData', { |
||||
type: this.type |
||||
}) |
||||
}) |
||||
}, |
||||
mounted () { |
||||
|
||||
}, |
||||
components: { } |
||||
} |
||||
</script> |
@ -0,0 +1,247 @@
|
||||
<template> |
||||
<div class="dep-list-model"> |
||||
<div v-for="(el,$index) in dependItemList" class="list" @click="itemIndex = $index"> |
||||
<x-select filterable :style="{width:isInstance ? '140px' : '162px'}" :disabled="isDetails" v-model="el.definitionId" @on-change="_onChangeDefinitionId"> |
||||
<x-option v-for="item in definitionList" :key="item.value" :value="item.value" :label="item.label"> |
||||
</x-option> |
||||
</x-select> |
||||
<x-select filterable :style="{width:isInstance ? '144px' : '156px'}" :disabled="isDetails" v-model="el.depTasks"> |
||||
<x-option v-for="item in el.depTasksList || []" :key="item" :value="item" :label="item"> |
||||
</x-option> |
||||
</x-select> |
||||
<x-select style="width: 80px;" v-model="el.cycle" :disabled="isDetails" @on-change="_onChangeCycle"> |
||||
<x-option v-for="item in cycleList" :key="item.value" :value="item.value" :label="item.label"> |
||||
</x-option> |
||||
</x-select> |
||||
<x-select style="width: 116px;" v-model="el.dateValue" :disabled="isDetails"> |
||||
<x-option v-for="item in el.dateValueList || []" :key="item.value" :value="item.value" :label="item.label"> |
||||
</x-option> |
||||
</x-select> |
||||
<template v-if="isInstance"> |
||||
<span class="instance-state"> |
||||
<i class="iconfont" :class="'icon-' + el.state" v-if="el.state === 'SUCCESS'" data-toggle="tooltip" data-container="body" :title="$t('成功')"></i> |
||||
<i class="iconfont" :class="'icon-' + el.state" v-if="el.state === 'WAITING'" data-toggle="tooltip" data-container="body" :title="$t('等待')"></i> |
||||
<i class="iconfont" :class="'icon-' + el.state" v-if="el.state === 'FAILED'" data-toggle="tooltip" data-container="body" :title="$t('失败')"></i> |
||||
</span> |
||||
</template> |
||||
<span class="operation"> |
||||
<a href="javascript:" class="delete" @click="!isDetails && _remove($index)"> |
||||
<i class="iconfont" :class="_isDetails" data-toggle="tooltip" data-container="body" :title="$t('删除')" ></i> |
||||
</a> |
||||
<a href="javascript:" class="add" @click="!isDetails && _add()" v-if="$index === (dependItemList.length - 1)"> |
||||
<i class="iconfont" :class="_isDetails" data-toggle="tooltip" data-container="body" :title="$t('添加')"></i> |
||||
</a> |
||||
</span> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
<script> |
||||
import _ from 'lodash' |
||||
import { cycleList, dateValueList } from './commcon' |
||||
import disabledState from '@/module/mixin/disabledState' |
||||
export default { |
||||
name: 'dep-list', |
||||
data () { |
||||
return { |
||||
list: [], |
||||
definitionList: [], |
||||
cycleList: cycleList, |
||||
isInstance: false, |
||||
itemIndex: null |
||||
} |
||||
}, |
||||
mixins: [disabledState], |
||||
props: { |
||||
dependItemList: Array, |
||||
index: Number |
||||
}, |
||||
model: { |
||||
prop: 'dependItemList', |
||||
event: 'dependItemListEvent' |
||||
}, |
||||
methods: { |
||||
/** |
||||
* add task |
||||
*/ |
||||
_add () { |
||||
// btn loading |
||||
this.isLoading = true |
||||
// dependItemList index |
||||
let is = (value) => _.some(this.dependItemList, { definitionId: value }) |
||||
let noArr = _.filter(this.definitionList, v => !is(v.value)) |
||||
let value = noArr[0] && noArr[0].value || null |
||||
let val = value || this.definitionList[0].value |
||||
// add task list |
||||
this._getDependItemList(val).then(depTasksList => { |
||||
this.$nextTick(() => { |
||||
this.$emit('dependItemListEvent', _.concat(this.dependItemList, this._rtNewParams(val, depTasksList))) |
||||
}) |
||||
}) |
||||
// remove tooltip |
||||
this._removeTip() |
||||
}, |
||||
/** |
||||
* remove task |
||||
*/ |
||||
_remove (i) { |
||||
this.dependItemList.splice(i, 1) |
||||
this._removeTip() |
||||
|
||||
if (!this.dependItemList.length) { |
||||
this.$emit('on-delete-all', { |
||||
index: this.index |
||||
}) |
||||
} |
||||
}, |
||||
/** |
||||
* get processlist |
||||
*/ |
||||
_getProcessList () { |
||||
return new Promise((resolve, reject) => { |
||||
this.definitionList = _.map(_.cloneDeep(this.store.state.dag.processListS), v => { |
||||
return { |
||||
value: v.id, |
||||
label: v.name |
||||
} |
||||
}) |
||||
resolve() |
||||
}) |
||||
}, |
||||
/** |
||||
* get dependItemList |
||||
*/ |
||||
_getDependItemList (ids, is = true) { |
||||
return new Promise((resolve, reject) => { |
||||
if (is) { |
||||
this.store.dispatch('dag/getProcessTasksList', { processDefinitionId: ids }).then(res => { |
||||
resolve(['ALL'].concat(_.map(res, v => v.name))) |
||||
}) |
||||
} else { |
||||
this.store.dispatch('dag/getTaskListDefIdAll', { processDefinitionIdList: ids }).then(res => { |
||||
resolve(res) |
||||
}) |
||||
} |
||||
}) |
||||
}, |
||||
/** |
||||
* change process get dependItemList |
||||
*/ |
||||
_onChangeDefinitionId ({ value }) { |
||||
// get depItem list data |
||||
this._getDependItemList(value).then(depTasksList => { |
||||
let item = this.dependItemList[this.itemIndex] |
||||
// init set depTasks All |
||||
item.depTasks = 'ALL' |
||||
// set dependItemList item data |
||||
this.$set(this.dependItemList, this.itemIndex, this._rtOldParams(value, depTasksList, item)) |
||||
}) |
||||
}, |
||||
_onChangeCycle ({ value }) { |
||||
let list = _.cloneDeep(dateValueList[value]) |
||||
this.$set(this.dependItemList[this.itemIndex], 'dateValue', list[0].value) |
||||
this.$set(this.dependItemList[this.itemIndex], 'dateValueList', list) |
||||
}, |
||||
_rtNewParams (value, depTasksList) { |
||||
return { |
||||
definitionId: value, |
||||
depTasks: 'ALL', |
||||
depTasksList: depTasksList, |
||||
cycle: 'day', |
||||
dateValue: 'last1Days', |
||||
dateValueList: _.cloneDeep(dateValueList['day']), |
||||
state: '' |
||||
} |
||||
}, |
||||
_rtOldParams (value, depTasksList, item) { |
||||
return { |
||||
definitionId: value, |
||||
depTasks: item.depTasks || 'ALL', |
||||
depTasksList: depTasksList, |
||||
cycle: item.cycle, |
||||
dateValue: item.dateValue, |
||||
dateValueList: _.cloneDeep(dateValueList[item.cycle]), |
||||
state: item.state |
||||
} |
||||
}, |
||||
/** |
||||
* remove tip |
||||
*/ |
||||
_removeTip () { |
||||
$('body').find('.tooltip.fade.top.in').remove() |
||||
} |
||||
}, |
||||
watch: { |
||||
}, |
||||
beforeCreate () { |
||||
}, |
||||
created () { |
||||
// is type projects-instance-details |
||||
this.isInstance = this.router.history.current.name === 'projects-instance-details' |
||||
// get processlist |
||||
this._getProcessList().then(() => { |
||||
if (!this.dependItemList.length) { |
||||
let value = this.definitionList[0].value |
||||
this._getDependItemList(value).then(depTasksList => { |
||||
this.$emit('dependItemListEvent', _.concat(this.dependItemList, this._rtNewParams(value, depTasksList))) |
||||
}) |
||||
} else { |
||||
// get definitionId ids |
||||
let ids = _.map(this.dependItemList, v => v.definitionId).join(',') |
||||
// get item list |
||||
this._getDependItemList(ids, false).then(res => { |
||||
_.map(this.dependItemList, (v, i) => { |
||||
this.$set(this.dependItemList, i, this._rtOldParams(v.definitionId, ['ALL'].concat(_.map(res[v.definitionId] || [], v => v.name)), v)) |
||||
}) |
||||
}) |
||||
} |
||||
}) |
||||
}, |
||||
mounted () { |
||||
}, |
||||
components: {} |
||||
} |
||||
</script> |
||||
|
||||
<style lang="scss" rel="stylesheet/scss"> |
||||
.dep-list-model { |
||||
position: relative; |
||||
min-height: 32px; |
||||
.list { |
||||
margin-bottom: 6px; |
||||
.operation { |
||||
padding-left: 4px; |
||||
a { |
||||
i { |
||||
font-size: 18px; |
||||
vertical-align: middle; |
||||
} |
||||
} |
||||
.delete { |
||||
color: #ff0000; |
||||
} |
||||
.add { |
||||
color: #0097e0; |
||||
} |
||||
} |
||||
} |
||||
.instance-state { |
||||
display: inline-block; |
||||
width: 24px; |
||||
.iconfont { |
||||
font-size: 20px; |
||||
vertical-align: middle; |
||||
cursor: pointer; |
||||
margin-left: 6px; |
||||
&.icon-SUCCESS { |
||||
color: #33cc00; |
||||
} |
||||
&.icon-WAITING { |
||||
color: #888888; |
||||
} |
||||
&.icon-FAILED { |
||||
color: #F31322; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
</style> |
@ -0,0 +1,46 @@
|
||||
<template> |
||||
<div class="clearfix list"> |
||||
<div class="text-box"> |
||||
<span><slot name="text"></slot></span> |
||||
</div> |
||||
<div class="cont-box"> |
||||
<div class="label-box"> |
||||
<slot name="content"></slot> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
<script> |
||||
export default { |
||||
name: 'list-box' |
||||
} |
||||
</script> |
||||
|
||||
<style lang="scss" rel="stylesheet/scss"> |
||||
.cont-box { |
||||
.label-box { |
||||
.ans-radio-group { |
||||
margin-top: 7px; |
||||
} |
||||
} |
||||
} |
||||
|
||||
.v-checkbox-wrapper { |
||||
&.v-checkbox-wrapper-disabled { |
||||
color: #999 ; |
||||
.v-checkbox { |
||||
.v-checkbox-inner{ |
||||
border-color:#dddee1; |
||||
background: #f7f7f7; |
||||
color: #bbbec4 ; |
||||
&:after{ |
||||
border: 2px solid #ddd; |
||||
border-top: 0; |
||||
border-left: 0; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
} |
||||
|
||||
</style> |
@ -0,0 +1,209 @@
|
||||
<template> |
||||
<div class="user-def-params-model"> |
||||
<div class="select-listpp" |
||||
v-for="(item,$index) in localParamsList" |
||||
:key="item.id" |
||||
@click="_getIndex($index)"> |
||||
<x-input |
||||
:disabled="isDetails" |
||||
type="text" |
||||
v-model="localParamsList[$index].prop" |
||||
:placeholder="$t('prop(必填)')" |
||||
maxlength="64" |
||||
@on-blur="_verifProp()" |
||||
style="width: 164px;"> |
||||
</x-input> |
||||
<x-select |
||||
style="width: 80px;" |
||||
@change="_handleDirectChanged" |
||||
v-model="localParamsList[$index].direct" |
||||
:disabled="isDetails || !hide"> |
||||
<x-option |
||||
v-for="city in directList" |
||||
:key="city.code" |
||||
:value="city.code" |
||||
:label="city.code"> |
||||
</x-option> |
||||
</x-select> |
||||
<x-select |
||||
style="width: 118px;" |
||||
@change="_handleTypeChanged" |
||||
v-model="localParamsList[$index].type" |
||||
:disabled="isDetails || !hide"> |
||||
<x-option |
||||
v-for="city in typeList" |
||||
:key="city.code" |
||||
:value="city.code" |
||||
:label="city.code"> |
||||
</x-option> |
||||
</x-select> |
||||
<x-input |
||||
:disabled="isDetails" |
||||
type="text" |
||||
v-model="localParamsList[$index].value" |
||||
:placeholder="$t('value(选填)')" |
||||
maxlength="64" |
||||
@on-blur="_handleValue()" |
||||
style="width: 150px;position: relative;margin-bottom: -2px;"> |
||||
</x-input> |
||||
<span class="lt-add"> |
||||
<a href="javascript:" style="color:red;" @click="!isDetails && _removeUdp($index)" > |
||||
<i class="iconfont" :class="_isDetails" data-toggle="tooltip" :title="$t('删除')" ></i> |
||||
</a> |
||||
</span> |
||||
<span class="add" v-if="$index === (localParamsList.length - 1)"> |
||||
<a href="javascript:" @click="!isDetails && _addUdp()" > |
||||
<i class="iconfont" :class="_isDetails" data-toggle="tooltip" :title="$t('添加')"></i> |
||||
</a> |
||||
</span> |
||||
</div> |
||||
<span class="add" v-if="!localParamsList.length"> |
||||
<a href="javascript:" @click="!isDetails && _addUdp()" > |
||||
<i class="iconfont" :class="_isDetails" data-toggle="tooltip" :title="$t('添加')"></i> |
||||
</a> |
||||
</span> |
||||
</div> |
||||
</template> |
||||
<script> |
||||
import _ from 'lodash' |
||||
import i18n from '@/module/i18n' |
||||
import { directList, typeList } from './commcon' |
||||
import disabledState from '@/module/mixin/disabledState' |
||||
export default { |
||||
name: 'user-def-params', |
||||
data () { |
||||
return { |
||||
// Direct data Custom parameter type support IN |
||||
directList: directList, |
||||
// Type data Custom parameter type support OUT |
||||
typeList: typeList, |
||||
// Increased data |
||||
localParamsList: [], |
||||
// Current execution index |
||||
localParamsIndex: null |
||||
} |
||||
}, |
||||
mixins: [disabledState], |
||||
props: { |
||||
udpList: Array, |
||||
// hide direct/type |
||||
hide: { |
||||
type: Boolean, |
||||
default: true |
||||
} |
||||
}, |
||||
methods: { |
||||
/** |
||||
* Current index |
||||
*/ |
||||
_getIndex (index) { |
||||
this.localParamsIndex = index |
||||
}, |
||||
/** |
||||
* handle direct |
||||
*/ |
||||
_handleDirectChanged () { |
||||
this._verifProp('value') |
||||
}, |
||||
/** |
||||
* handle type |
||||
*/ |
||||
_handleTypeChanged () { |
||||
this._verifProp('value') |
||||
}, |
||||
/** |
||||
* delete item |
||||
*/ |
||||
_removeUdp (index) { |
||||
this.localParamsList.splice(index, 1) |
||||
this._verifProp('value') |
||||
}, |
||||
/** |
||||
* add |
||||
*/ |
||||
_addUdp () { |
||||
this.localParamsList.push({ |
||||
prop: '', |
||||
direct: 'IN', |
||||
type: 'VARCHAR', |
||||
value: '' |
||||
}) |
||||
}, |
||||
/** |
||||
* blur verification |
||||
*/ |
||||
_handleValue () { |
||||
this._verifProp('value') |
||||
}, |
||||
/** |
||||
* Verify that the value exists or is empty |
||||
*/ |
||||
_verifProp (type) { |
||||
let arr = [] |
||||
let flag = true |
||||
_.map(this.localParamsList, v => { |
||||
arr.push(v.prop) |
||||
if (!v.prop) { |
||||
flag = false |
||||
} |
||||
}) |
||||
if (!flag) { |
||||
if (!type) { |
||||
this.$message.warning(`${i18n.$t('prop不能为空')}`) |
||||
} |
||||
return false |
||||
} |
||||
let newArr = _.cloneDeep(_.uniqWith(arr, _.isEqual)) |
||||
if (newArr.length !== arr.length) { |
||||
if (!type) { |
||||
this.$message.warning(`${i18n.$t('prop中有重复')}`) |
||||
} |
||||
return false |
||||
} |
||||
this.$emit('on-local-params', _.cloneDeep(this.localParamsList)) |
||||
return true |
||||
} |
||||
}, |
||||
watch: { |
||||
// Monitor data changes |
||||
udpList () { |
||||
this.localParamsList = this.udpList |
||||
} |
||||
}, |
||||
created () { |
||||
this.localParamsList = this.udpList |
||||
}, |
||||
mounted () { |
||||
}, |
||||
components: { } |
||||
} |
||||
</script> |
||||
|
||||
<style lang="scss" rel="stylesheet/scss"> |
||||
.user-def-params-model { |
||||
.select-listpp { |
||||
margin-bottom: 6px; |
||||
.lt-add { |
||||
padding-left: 4px; |
||||
a { |
||||
.iconfont { |
||||
font-size: 18px; |
||||
vertical-align: middle; |
||||
margin-bottom: -2px; |
||||
display: inline-block; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
.add { |
||||
a { |
||||
.iconfont { |
||||
font-size: 18px; |
||||
vertical-align: middle; |
||||
display: inline-block; |
||||
margin-top: 1px; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
</style> |
@ -0,0 +1,100 @@
|
||||
<template> |
||||
<div class="resource-list-model"> |
||||
<x-select multiple |
||||
v-model="value" |
||||
filterable |
||||
:disabled="isDetails" |
||||
:placeholder="$t('请选择资源')" |
||||
style="width: 100%;"> |
||||
<x-option |
||||
v-for="city in resList" |
||||
:key="city.code" |
||||
:value="city.code" |
||||
:label="city.code"> |
||||
</x-option> |
||||
</x-select> |
||||
</div> |
||||
</template> |
||||
<script> |
||||
import _ from 'lodash' |
||||
import disabledState from '@/module/mixin/disabledState' |
||||
|
||||
export default { |
||||
name: 'resourceList', |
||||
data () { |
||||
return { |
||||
// Resource(List) |
||||
resList: [], |
||||
// Resource |
||||
value: [] |
||||
} |
||||
}, |
||||
mixins: [disabledState], |
||||
props: { |
||||
resourceList: Array |
||||
}, |
||||
methods: { |
||||
/** |
||||
* Verify data source |
||||
*/ |
||||
_verifResources () { |
||||
this.$emit('on-resourcesData', _.map(this.value, v => { |
||||
return { |
||||
res: v |
||||
} |
||||
})) |
||||
return true |
||||
} |
||||
}, |
||||
watch: { |
||||
// Listening data source |
||||
resourceList (a) { |
||||
this.value = _.map(_.cloneDeep(a), v => v.res) |
||||
} |
||||
}, |
||||
created () { |
||||
this.resList = _.map(_.cloneDeep(this.store.state.dag.resourcesListS), v => { |
||||
return { |
||||
code: v.alias |
||||
} |
||||
}) |
||||
|
||||
if (this.resourceList.length) { |
||||
this.value = _.map(_.cloneDeep(this.resourceList), v => v.res) |
||||
} |
||||
}, |
||||
mounted () { |
||||
|
||||
}, |
||||
components: { } |
||||
} |
||||
</script> |
||||
|
||||
<style lang="scss" rel="stylesheet/scss"> |
||||
.resource-list-model { |
||||
.select-listpp { |
||||
margin-bottom: 6px; |
||||
.lt-add { |
||||
padding-left: 4px; |
||||
a { |
||||
.iconfont { |
||||
font-size: 18px; |
||||
vertical-align: middle; |
||||
margin-bottom: -2px; |
||||
display: inline-block; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
>.add { |
||||
a { |
||||
.iconfont { |
||||
font-size: 18px; |
||||
vertical-align: middle; |
||||
display: inline-block; |
||||
margin-top: 1px; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
</style> |
@ -0,0 +1,57 @@
|
||||
<template> |
||||
<div class="sql-type-model"> |
||||
<x-select |
||||
v-model="sqlTypeId" |
||||
:disabled="isDetails" |
||||
@on-change="_handleSqlTypeChanged" |
||||
style="width: 90px;"> |
||||
<x-option |
||||
v-for="city in sqlTypeList" |
||||
:key="city.id" |
||||
:value="city" |
||||
:label="city.code"> |
||||
</x-option> |
||||
</x-select> |
||||
</div> |
||||
</template> |
||||
<script> |
||||
import _ from 'lodash' |
||||
import { sqlTypeList } from './commcon' |
||||
import disabledState from '@/module/mixin/disabledState' |
||||
export default { |
||||
name: 'sql-type', |
||||
data () { |
||||
return { |
||||
// sql(List) |
||||
sqlTypeList: sqlTypeList, |
||||
// sql |
||||
sqlTypeId: {} |
||||
} |
||||
}, |
||||
mixins: [disabledState], |
||||
props: { |
||||
sqlType: Number |
||||
}, |
||||
methods: { |
||||
/** |
||||
* return sqlType |
||||
*/ |
||||
_handleSqlTypeChanged (val) { |
||||
this.$emit('on-sqlType', val.value.id) |
||||
} |
||||
}, |
||||
watch: { |
||||
}, |
||||
created () { |
||||
this.$nextTick(() => { |
||||
if (this.sqlType !== null) { |
||||
this.sqlTypeId = _.filter(this.sqlTypeList, v => v.id === this.sqlType)[0] |
||||
} else { |
||||
this.sqlTypeId = this.sqlTypeList[0] |
||||
} |
||||
}) |
||||
}, |
||||
mounted () { |
||||
} |
||||
} |
||||
</script> |
@ -0,0 +1,116 @@
|
||||
<template> |
||||
<div class="udfs-model"> |
||||
<x-select multiple |
||||
v-model="udfsStr" |
||||
:disabled="isDetails" |
||||
style="width: 100%;"> |
||||
<x-option |
||||
v-for="city in udfsList" |
||||
:key="city.id" |
||||
:value="city" |
||||
:label="city.code"> |
||||
</x-option> |
||||
</x-select> |
||||
</div> |
||||
</template> |
||||
<script> |
||||
import _ from 'lodash' |
||||
import disabledState from '@/module/mixin/disabledState' |
||||
|
||||
export default { |
||||
name: 'udfs', |
||||
data () { |
||||
return { |
||||
// UDFS Function |
||||
udfsStr: [], |
||||
// UDFS Function(List) |
||||
udfsList: [] |
||||
} |
||||
}, |
||||
mixins: [disabledState], |
||||
props: { |
||||
udfs: String, |
||||
type: String |
||||
}, |
||||
methods: { |
||||
/** |
||||
* verification |
||||
*/ |
||||
_verifUdfs () { |
||||
this.$emit('on-udfsData', _.map(this.udfsStr, v => v.id).join(',')) |
||||
return true |
||||
}, |
||||
/** |
||||
* Get UDFS function data |
||||
*/ |
||||
_getUdfList () { |
||||
this.udfsList = [] |
||||
this.store.dispatch('dag/getUdfList', { type: this.type }).then(res => { |
||||
this.udfsList = _.map(res.data, v => { |
||||
return { |
||||
id: v.id, |
||||
code: v.funcName |
||||
} |
||||
}) |
||||
|
||||
let udfs = _.cloneDeep(this.udfs.split(',')) |
||||
if (udfs.length) { |
||||
let arr = [] |
||||
_.map(udfs, v => { |
||||
_.map(this.udfsList, v1 => { |
||||
if (parseInt(v) === v1.id) { |
||||
arr.push(v1) |
||||
} |
||||
}) |
||||
}) |
||||
this.$nextTick(() => { |
||||
this.udfsStr = arr |
||||
}) |
||||
} |
||||
}) |
||||
} |
||||
}, |
||||
watch: { |
||||
type (a) { |
||||
// The props parameter needs to be changed due to the scene. |
||||
this.udfs = '' |
||||
if (a === 'HIVE') { |
||||
this._getUdfList() |
||||
} else { |
||||
this.udfsList = [] |
||||
} |
||||
} |
||||
}, |
||||
created () { |
||||
this._getUdfList() |
||||
}, |
||||
mounted () { |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style lang="scss" rel="stylesheet/scss"> |
||||
.udfs-model { |
||||
.add-udfs { |
||||
color: #0097e0; |
||||
} |
||||
.t-list { |
||||
margin-bottom: 14px; |
||||
.v-btn { |
||||
width: 208px; |
||||
} |
||||
&:hover { |
||||
} |
||||
.delect-btn { |
||||
display: inline-block; |
||||
width: 54px !important; |
||||
vertical-align: middle; |
||||
background: #ff0000; |
||||
border-color: #ff0000; |
||||
span { |
||||
vertical-align: middle !important; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
</style> |
@ -0,0 +1,237 @@
|
||||
<template> |
||||
<div class="dependence-model"> |
||||
<m-list-box> |
||||
<div slot="text">{{$t('添加依赖')}}</div> |
||||
<div slot="content"> |
||||
<div class="dep-opt"> |
||||
<a href="javascript:" |
||||
@click="!isDetails && _addDep()" |
||||
class="add-dep"> |
||||
<i v-if="!isLoading" class="iconfont" :class="_isDetails" data-toggle="tooltip" :title="$t('添加')"> |
||||
 |
||||
</i> |
||||
<i v-if="isLoading" class="iconfont fa fa-spin" data-toggle="tooltip" :title="$t('添加')"> |
||||
 |
||||
</i> |
||||
</a> |
||||
</div> |
||||
<div class="dep-box"> |
||||
<span |
||||
class="dep-relation" |
||||
@click="!isDetails && _setGlobalRelation()" |
||||
v-if="dependTaskList.length"> |
||||
{{relation === 'AND' ? $t('且') : $t('或')}} |
||||
</span> |
||||
<div class="dep-list" v-for="(el,$index) in dependTaskList"> |
||||
<span class="dep-line-pie" |
||||
v-if="el.dependItemList.length" |
||||
@click="!isDetails && _setRelation($index)"> |
||||
{{el.relation === 'AND' ? $t('且') : $t('或')}} |
||||
</span> |
||||
<i class="iconfont dep-delete" |
||||
data-toggle="tooltip" |
||||
data-container="body" |
||||
:class="_isDetails" |
||||
@click="!isDetails && _deleteDep($index)" |
||||
:title="$t('删除')" > |
||||
 |
||||
</i> |
||||
<m-depend-item-list |
||||
v-model="el.dependItemList" |
||||
@on-delete-all="_onDeleteAll" |
||||
:index="$index"> |
||||
</m-depend-item-list> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</m-list-box> |
||||
</div> |
||||
</template> |
||||
<script> |
||||
import _ from 'lodash' |
||||
import mListBox from './_source/listBox' |
||||
import mDependItemList from './_source/dependItemList' |
||||
import disabledState from '@/module/mixin/disabledState' |
||||
|
||||
export default { |
||||
name: 'dependence', |
||||
data () { |
||||
return { |
||||
relation: 'AND', |
||||
dependTaskList: [], |
||||
isLoading: false |
||||
} |
||||
}, |
||||
mixins: [disabledState], |
||||
props: { |
||||
backfillItem: Object |
||||
}, |
||||
methods: { |
||||
_addDep () { |
||||
if (!this.isLoading) { |
||||
this.isLoading = true |
||||
this.dependTaskList.push({ |
||||
dependItemList: [], |
||||
relation: 'AND' |
||||
}) |
||||
} |
||||
}, |
||||
_deleteDep (i) { |
||||
// remove index dependent |
||||
this.dependTaskList.splice(i, 1) |
||||
|
||||
// remove tootip |
||||
$('body').find('.tooltip.fade.top.in').remove() |
||||
}, |
||||
_onDeleteAll (i) { |
||||
this._deleteDep(i) |
||||
}, |
||||
_setGlobalRelation () { |
||||
this.relation = this.relation === 'AND' ? 'OR' : 'AND' |
||||
}, |
||||
_setRelation (i) { |
||||
this.dependTaskList[i].relation = this.dependTaskList[i].relation === 'AND' ? 'OR' : 'AND' |
||||
}, |
||||
_verification () { |
||||
this.$emit('on-dependent', { |
||||
relation: this.relation, |
||||
dependTaskList: _.map(this.dependTaskList, v => { |
||||
return { |
||||
relation: v.relation, |
||||
dependItemList: _.map(v.dependItemList, v1 => _.omit(v1, ['depTasksList', 'state', 'dateValueList'])) |
||||
} |
||||
}) |
||||
}) |
||||
return true |
||||
} |
||||
}, |
||||
watch: { |
||||
dependTaskList () { |
||||
setTimeout(() => { |
||||
this.isLoading = false |
||||
}, 600) |
||||
} |
||||
}, |
||||
beforeCreate () { |
||||
}, |
||||
created () { |
||||
let o = this.backfillItem |
||||
let dependentResult = $(`#${o.id}`).data('dependent-result') || {} |
||||
// Does not represent an empty object backfill |
||||
if (!_.isEmpty(o)) { |
||||
this.relation = _.cloneDeep(o.dependence.relation) || 'AND' |
||||
this.dependTaskList = _.cloneDeep(o.dependence.dependTaskList) || [] |
||||
let defaultState = this.isDetails ? 'WAITING' : '' |
||||
// Process instance return status display matches by key |
||||
_.map(this.dependTaskList, v => _.map(v.dependItemList, v1 => v1.state = dependentResult[`${v1.definitionId}-${v1.depTasks}-${v1.cycle}-${v1.dateValue}`] || defaultState)) |
||||
} |
||||
}, |
||||
mounted () { |
||||
}, |
||||
destroyed () { |
||||
}, |
||||
computed: {}, |
||||
components: { mListBox, mDependItemList } |
||||
} |
||||
</script> |
||||
|
||||
<style lang="scss" rel="stylesheet/scss"> |
||||
.dependence-model { |
||||
margin-top: -10px; |
||||
.dep-opt { |
||||
margin-bottom: 10px; |
||||
padding-top: 3px; |
||||
line-height: 24px; |
||||
.add-dep { |
||||
color: #0097e0; |
||||
margin-right: 10px; |
||||
i { |
||||
font-size: 18px; |
||||
vertical-align: middle; |
||||
} |
||||
} |
||||
} |
||||
.dep-list { |
||||
margin-bottom: 16px; |
||||
position: relative; |
||||
border-left: 1px solid #eee; |
||||
padding-left: 16px; |
||||
margin-left: -16px; |
||||
transition: all 0.2s ease-out; |
||||
padding-bottom: 20px; |
||||
&:hover{ |
||||
border-left: 1px solid #0097e0; |
||||
transition: all 0.2s ease-out; |
||||
.dep-line-pie { |
||||
transition: all 0.2s ease-out; |
||||
border: 1px solid #0097e0; |
||||
background: #0097e0; |
||||
color: #fff; |
||||
} |
||||
} |
||||
.dep-line-pie { |
||||
transition: all 0.2s ease-out; |
||||
position: absolute; |
||||
width: 20px; |
||||
height: 20px; |
||||
border: 1px solid #e2e2e2; |
||||
text-align: center; |
||||
top: 50%; |
||||
margin-top: -20px; |
||||
z-index: 1; |
||||
left: -10px; |
||||
border-radius: 10px; |
||||
background: #fff; |
||||
font-size: 12px; |
||||
cursor: pointer; |
||||
&::selection { |
||||
background:transparent; |
||||
} |
||||
&::-moz-selection { |
||||
background:transparent; |
||||
} |
||||
&::-webkit-selection { |
||||
background:transparent; |
||||
} |
||||
} |
||||
.dep-delete { |
||||
position: absolute; |
||||
bottom: -6px; |
||||
left: 14px; |
||||
font-size: 18px; |
||||
color: #ff0000; |
||||
cursor: pointer; |
||||
} |
||||
} |
||||
.dep-box { |
||||
border-left: 4px solid #eee; |
||||
margin-left: -46px; |
||||
padding-left: 42px; |
||||
position: relative; |
||||
.dep-relation { |
||||
position: absolute; |
||||
width: 20px; |
||||
height: 20px; |
||||
border: 1px solid #e2e2e2; |
||||
text-align: center; |
||||
top: 50%; |
||||
margin-top: -10px; |
||||
z-index: 1; |
||||
left: -12px; |
||||
border-radius: 10px; |
||||
background: #fff; |
||||
font-size: 12px; |
||||
cursor: pointer; |
||||
&::selection { |
||||
background:transparent; |
||||
} |
||||
&::-moz-selection { |
||||
background:transparent; |
||||
} |
||||
&::-webkit-selection { |
||||
background:transparent; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
</style> |
@ -0,0 +1,264 @@
|
||||
<template> |
||||
<div class="spark-model"> |
||||
<m-list-box> |
||||
<div slot="text">{{$t('程序类型')}}</div> |
||||
<div slot="content"> |
||||
<x-select v-model="programType" :disabled="isDetails" style="width: 100px;"> |
||||
<x-option |
||||
v-for="city in programTypeList" |
||||
:key="city.code" |
||||
:value="city.code" |
||||
:label="city.code"> |
||||
</x-option> |
||||
</x-select> |
||||
</div> |
||||
</m-list-box> |
||||
<m-list-box v-if="programType !== 'PYTHON'"> |
||||
<div slot="text">{{$t('主函数的class')}}</div> |
||||
<div slot="content"> |
||||
<x-input |
||||
:disabled="isDetails" |
||||
type="input" |
||||
v-model="mainClass" |
||||
:placeholder="$t('请输入mainClass')" |
||||
autocomplete="off"> |
||||
</x-input> |
||||
</div> |
||||
</m-list-box> |
||||
<m-list-box> |
||||
<div slot="text">{{$t('主jar包')}}</div> |
||||
<div slot="content"> |
||||
<x-select |
||||
style="width: 100%;" |
||||
:placeholder="$t('请选择主jar包')" |
||||
v-model="mainJar" |
||||
filterable |
||||
:disabled="isDetails"> |
||||
<x-option |
||||
v-for="city in mainJarList" |
||||
:key="city.code" |
||||
:value="city.code" |
||||
:label="city.code"> |
||||
</x-option> |
||||
</x-select> |
||||
</div> |
||||
</m-list-box> |
||||
<m-list-box> |
||||
<div slot="text">{{$t('命令行参数')}}</div> |
||||
<div slot="content"> |
||||
<x-input |
||||
:autosize="{minRows:2}" |
||||
:disabled="isDetails" |
||||
type="textarea" |
||||
v-model="mainArgs" |
||||
:placeholder="$t('请输入命令行参数')" |
||||
autocomplete="off"> |
||||
</x-input> |
||||
</div> |
||||
</m-list-box> |
||||
<m-list-box> |
||||
<div slot="text">{{$t('其他参数')}}</div> |
||||
<div slot="content"> |
||||
<x-input |
||||
:disabled="isDetails" |
||||
:autosize="{minRows:2}" |
||||
type="textarea" |
||||
v-model="others" |
||||
:placeholder="$t('请输入其他参数')" |
||||
autocomplete="off"> |
||||
</x-input> |
||||
</div> |
||||
</m-list-box> |
||||
<m-list-box> |
||||
<div slot="text">{{$t('资源')}}</div> |
||||
<div slot="content"> |
||||
<m-resources |
||||
ref="refResources" |
||||
@on-resourcesData="_onResourcesData" |
||||
:resource-list="resourceList"> |
||||
</m-resources> |
||||
</div> |
||||
</m-list-box> |
||||
<m-list-box> |
||||
<div slot="text">{{$t('自定义参数')}}</div> |
||||
<div slot="content"> |
||||
<m-local-params |
||||
ref="refLocalParams" |
||||
@on-local-params="_onLocalParams" |
||||
:udp-list="localParams" |
||||
:hide="false"> |
||||
</m-local-params> |
||||
</div> |
||||
</m-list-box> |
||||
</div> |
||||
</template> |
||||
<script> |
||||
import _ from 'lodash' |
||||
import i18n from '@/module/i18n' |
||||
import mListBox from './_source/listBox' |
||||
import mResources from './_source/resources' |
||||
import mLocalParams from './_source/localParams' |
||||
import disabledState from '@/module/mixin/disabledState' |
||||
export default { |
||||
name: 'mr', |
||||
data () { |
||||
return { |
||||
// Main function class |
||||
mainClass: '', |
||||
// Master jar package |
||||
mainJar: null, |
||||
// Main jar package (List) |
||||
mainJarList: [], |
||||
// Resource(list) |
||||
resourceList: [], |
||||
// Custom parameter |
||||
localParams: [], |
||||
// Command line argument |
||||
mainArgs: '', |
||||
// Other parameters |
||||
others: '', |
||||
// Program type |
||||
programType: 'JAVA', |
||||
// Program type(List) |
||||
programTypeList: [{ code: 'JAVA' }, { code: 'PYTHON' }] |
||||
} |
||||
}, |
||||
props: { |
||||
backfillItem: Object |
||||
}, |
||||
mixins: [disabledState], |
||||
methods: { |
||||
/** |
||||
* return localParams |
||||
*/ |
||||
_onLocalParams (a) { |
||||
this.localParams = a |
||||
}, |
||||
/** |
||||
* return resourceList |
||||
*/ |
||||
_onResourcesData (a) { |
||||
this.resourceList = a |
||||
}, |
||||
/** |
||||
* verification |
||||
*/ |
||||
_verification () { |
||||
if (this.programType !== 'PYTHON' && !this.mainClass) { |
||||
this.$message.warning(`${i18n.$t('请填写主函数的class')}`) |
||||
return false |
||||
} |
||||
|
||||
if (!this.mainJar) { |
||||
this.$message.warning(`${i18n.$t('请选择主jar包')}`) |
||||
return false |
||||
} |
||||
|
||||
if (!this.$refs.refResources._verifResources()) { |
||||
return false |
||||
} |
||||
|
||||
// localParams Subcomponent verification |
||||
if (!this.$refs.refLocalParams._verifProp()) { |
||||
return false |
||||
} |
||||
|
||||
// storage |
||||
this.$emit('on-params', { |
||||
mainClass: this.mainClass, |
||||
mainJar: { |
||||
res: this.mainJar |
||||
}, |
||||
resourceList: this.resourceList, |
||||
localParams: this.localParams, |
||||
mainArgs: this.mainArgs, |
||||
others: this.others, |
||||
programType: this.programType |
||||
}) |
||||
return true |
||||
}, |
||||
/** |
||||
* Get resource data |
||||
*/ |
||||
_getResourcesList () { |
||||
return new Promise((resolve, reject) => { |
||||
let isJar = (alias) => { |
||||
return alias.substring(alias.lastIndexOf('.') + 1, alias.length) !== 'jar' |
||||
} |
||||
this.mainJarList = _.map(_.cloneDeep(this.store.state.dag.resourcesListS), v => { |
||||
return { |
||||
id: v.id, |
||||
code: v.alias, |
||||
disabled: isJar(v.alias) |
||||
} |
||||
}) |
||||
resolve() |
||||
}) |
||||
} |
||||
}, |
||||
watch: { |
||||
/** |
||||
* monitor |
||||
*/ |
||||
programType (type) { |
||||
if (type === 'PYTHON') { |
||||
this.mainClass = '' |
||||
} |
||||
} |
||||
}, |
||||
created () { |
||||
this._getResourcesList().then(() => { |
||||
let o = this.backfillItem |
||||
|
||||
// Non-null objects represent backfill |
||||
if (!_.isEmpty(o)) { |
||||
this.mainClass = o.params.mainClass || '' |
||||
this.mainJar = o.params.mainJar.res || '' |
||||
this.mainArgs = o.params.mainArgs || '' |
||||
this.others = o.params.others |
||||
this.programType = o.params.programType || 'JAVA' |
||||
|
||||
// backfill resourceList |
||||
let resourceList = o.params.resourceList || [] |
||||
if (resourceList.length) { |
||||
this.resourceList = resourceList |
||||
} |
||||
|
||||
// backfill localParams |
||||
let localParams = o.params.localParams || [] |
||||
if (localParams.length) { |
||||
this.localParams = localParams |
||||
} |
||||
} |
||||
}) |
||||
}, |
||||
mounted () { |
||||
|
||||
}, |
||||
components: { mLocalParams, mListBox, mResources } |
||||
} |
||||
</script> |
||||
|
||||
<style lang="scss" rel="stylesheet/scss"> |
||||
.spark-model { |
||||
.list-box-4p { |
||||
.list { |
||||
margin-bottom: 14px; |
||||
.sp1 { |
||||
float: left; |
||||
width: 112px; |
||||
text-align: right; |
||||
margin-right: 10px; |
||||
font-size: 14px; |
||||
color: #777; |
||||
display: inline-block; |
||||
padding-top: 6px; |
||||
} |
||||
.sp2 { |
||||
float: left; |
||||
margin-right: 4px; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
</style> |
@ -0,0 +1,134 @@
|
||||
<template> |
||||
<div class="procedure-model"> |
||||
<m-list-box> |
||||
<div slot="text">{{$t('数据源')}}</div> |
||||
<div slot="content"> |
||||
<m-datasource |
||||
ref="refDs" |
||||
@on-dsData="_onDsData" |
||||
:supportType="['MYSQL','POSTGRESQL']" |
||||
:data="{ type:type,datasource:datasource }"> |
||||
</m-datasource> |
||||
</div> |
||||
</m-list-box> |
||||
<m-list-box> |
||||
<div slot="text">{{$t('方法')}}</div> |
||||
<div slot="content"> |
||||
<x-input |
||||
type="input" |
||||
:disabled="isDetails" |
||||
v-model="method" |
||||
:placeholder="$t('请输入method(选填)')" |
||||
autocomplete="off"> |
||||
</x-input> |
||||
</div> |
||||
</m-list-box> |
||||
<m-list-box> |
||||
<div slot="text">{{$t('自定义参数')}}</div> |
||||
<div slot="content"> |
||||
<m-local-params |
||||
ref="refLocalParams" |
||||
@on-local-params="_onLocalParams" |
||||
:udp-list="localParams"> |
||||
</m-local-params> |
||||
</div> |
||||
</m-list-box> |
||||
</div> |
||||
</template> |
||||
<script> |
||||
import _ from 'lodash' |
||||
import i18n from '@/module/i18n' |
||||
import mListBox from './_source/listBox' |
||||
import mDatasource from './_source/datasource' |
||||
import mLocalParams from './_source/localParams' |
||||
import disabledState from '@/module/mixin/disabledState' |
||||
|
||||
export default { |
||||
name: 'procedure', |
||||
data () { |
||||
return { |
||||
// method |
||||
method: '', |
||||
// Custom parameter |
||||
localParams: [], |
||||
// Data source type |
||||
type: '', |
||||
// data source |
||||
datasource: '' |
||||
} |
||||
}, |
||||
mixins: [disabledState], |
||||
props: { |
||||
backfillItem: Object |
||||
}, |
||||
methods: { |
||||
/** |
||||
* return type or datasource |
||||
*/ |
||||
_onDsData (o) { |
||||
this.type = o.type |
||||
this.datasource = o.datasource |
||||
}, |
||||
/** |
||||
* return udp |
||||
*/ |
||||
_onLocalParams (a) { |
||||
}, |
||||
/** |
||||
* verification |
||||
*/ |
||||
_verification () { |
||||
// datasource Subcomponent verification |
||||
if (!this.$refs.refDs._verifDatasource()) { |
||||
return false |
||||
} |
||||
|
||||
// Verification function |
||||
if (!this.method) { |
||||
this.$message.warning(`${i18n.$t('请输入方法')}`) |
||||
return false |
||||
} |
||||
|
||||
// localParams Subcomponent verification |
||||
if (!this.$refs.refLocalParams._verifProp()) { |
||||
return false |
||||
} |
||||
// storage |
||||
this.$emit('on-params', { |
||||
type: this.type, |
||||
datasource: this.datasource, |
||||
method: this.method, |
||||
localParams: this.localParams |
||||
}) |
||||
return true |
||||
} |
||||
}, |
||||
watch: {}, |
||||
created () { |
||||
let o = this.backfillItem |
||||
|
||||
// Non-null objects represent backfill |
||||
if (!_.isEmpty(o)) { |
||||
this.name = o.name |
||||
this.desc = o.desc |
||||
|
||||
// backfill |
||||
this.type = o.params.type || '' |
||||
this.datasource = o.params.datasource || '' |
||||
this.method = o.params.method || '' |
||||
|
||||
// backfill localParams |
||||
let localParams = o.params.localParams || [] |
||||
if (localParams.length) { |
||||
this.localParams = localParams |
||||
} |
||||
} |
||||
}, |
||||
mounted () { |
||||
}, |
||||
components: { mListBox, mDatasource, mLocalParams } |
||||
} |
||||
</script> |
||||
|
||||
<style lang="scss" rel="stylesheet/scss"> |
||||
</style> |
@ -0,0 +1,161 @@
|
||||
<template> |
||||
<div class="shell-model"> |
||||
<m-list-box> |
||||
<div slot="text">{{$t('脚本')}}</div> |
||||
<div slot="content"> |
||||
<div class="from-mirror"> |
||||
<textarea id="code-python-mirror" name="code-python-mirror" style="opacity: 0;"> |
||||
</textarea> |
||||
</div> |
||||
</div> |
||||
</m-list-box> |
||||
<m-list-box> |
||||
<div slot="text">{{$t('资源')}}</div> |
||||
<div slot="content"> |
||||
<m-resources |
||||
ref="refResources" |
||||
@on-resourcesData="_onResourcesData" |
||||
:resource-list="resourceList"> |
||||
</m-resources> |
||||
</div> |
||||
</m-list-box> |
||||
|
||||
<m-list-box> |
||||
<div slot="text">{{$t('自定义参数')}}</div> |
||||
<div slot="content"> |
||||
<m-local-params |
||||
ref="refLocalParams" |
||||
@on-local-params="_onLocalParams" |
||||
:udp-list="localParams" |
||||
:hide="false"> |
||||
</m-local-params> |
||||
</div> |
||||
</m-list-box> |
||||
</div> |
||||
</template> |
||||
<script> |
||||
import _ from 'lodash' |
||||
import i18n from '@/module/i18n' |
||||
import mListBox from './_source/listBox' |
||||
import mResources from './_source/resources' |
||||
import mLocalParams from './_source/localParams' |
||||
import disabledState from '@/module/mixin/disabledState' |
||||
import codemirror from '@/conf/home/pages/resource/pages/file/pages/_source/codemirror' |
||||
|
||||
let editor |
||||
|
||||
export default { |
||||
name: 'python', |
||||
data () { |
||||
return { |
||||
// script |
||||
rawScript: '', |
||||
// Custom parameter |
||||
localParams: [], |
||||
// resource(list) |
||||
resourceList: [] |
||||
} |
||||
}, |
||||
mixins: [disabledState], |
||||
props: { |
||||
backfillItem: Object |
||||
}, |
||||
methods: { |
||||
/** |
||||
* return localParams |
||||
*/ |
||||
_onLocalParams (a) { |
||||
this.localParams = a |
||||
}, |
||||
/** |
||||
* return resourceList |
||||
*/ |
||||
_onResourcesData (a) { |
||||
this.resourceList = a |
||||
}, |
||||
/** |
||||
* verification |
||||
*/ |
||||
_verification () { |
||||
// rawScript 验证 |
||||
if (!editor.getValue()) { |
||||
this.$message.warning(`${i18n.$t('请输入rawScript(必填)')}`) |
||||
return false |
||||
} |
||||
|
||||
if (!this.$refs.refResources._verifResources()) { |
||||
return false |
||||
} |
||||
|
||||
// localParams Subcomponent verification |
||||
if (!this.$refs.refLocalParams._verifProp()) { |
||||
return false |
||||
} |
||||
|
||||
// storage |
||||
this.$emit('on-params', { |
||||
resourceList: this.resourceList, |
||||
localParams: this.localParams, |
||||
rawScript: editor.getValue() |
||||
}) |
||||
return true |
||||
}, |
||||
/** |
||||
* Processing code highlighting |
||||
*/ |
||||
_handlerEditor () { |
||||
// editor |
||||
editor = codemirror('code-python-mirror', { |
||||
mode: 'python', |
||||
readOnly: this.isDetails |
||||
}) |
||||
|
||||
this.keypress = () => { |
||||
if (!editor.getOption('readOnly')) { |
||||
editor.showHint({ |
||||
completeSingle: false |
||||
}) |
||||
} |
||||
} |
||||
|
||||
// Monitor keyboard |
||||
editor.on('keypress', this.keypress) |
||||
|
||||
editor.setValue(this.rawScript) |
||||
|
||||
return editor |
||||
} |
||||
}, |
||||
watch: {}, |
||||
created () { |
||||
let o = this.backfillItem |
||||
|
||||
// Non-null objects represent backfill |
||||
if (!_.isEmpty(o)) { |
||||
this.rawScript = o.params.rawScript |
||||
|
||||
// backfill resourceList |
||||
let resourceList = o.params.resourceList || [] |
||||
if (resourceList.length) { |
||||
this.resourceList = resourceList |
||||
} |
||||
|
||||
// backfill localParams |
||||
let localParams = o.params.localParams || [] |
||||
if (localParams.length) { |
||||
this.localParams = localParams |
||||
} |
||||
} |
||||
}, |
||||
mounted () { |
||||
setTimeout(() => { |
||||
this._handlerEditor() |
||||
}, 200) |
||||
}, |
||||
destroyed () { |
||||
editor.toTextArea() // Uninstall |
||||
editor.off($('.code-python-mirror'), 'keypress', this.keypress) |
||||
}, |
||||
components: { mLocalParams, mListBox, mResources } |
||||
} |
||||
</script> |
@ -0,0 +1,165 @@
|
||||
<template> |
||||
<div class="shell-model"> |
||||
<m-list-box> |
||||
<div slot="text">{{$t('脚本')}}</div> |
||||
<div slot="content"> |
||||
<div class="from-mirror"> |
||||
<textarea |
||||
id="code-shell-mirror" |
||||
name="code-shell-mirror" |
||||
style="opacity: 0"> |
||||
</textarea> |
||||
</div> |
||||
</div> |
||||
</m-list-box> |
||||
<m-list-box> |
||||
<div slot="text">{{$t('资源')}}</div> |
||||
<div slot="content"> |
||||
<m-resources |
||||
ref="refResources" |
||||
@on-resourcesData="_onResourcesData" |
||||
:resource-list="resourceList"> |
||||
</m-resources> |
||||
</div> |
||||
</m-list-box> |
||||
<m-list-box> |
||||
<div slot="text">{{$t('自定义参数')}}</div> |
||||
<div slot="content"> |
||||
<m-local-params |
||||
ref="refLocalParams" |
||||
@on-local-params="_onLocalParams" |
||||
:udp-list="localParams" |
||||
:hide="false"> |
||||
</m-local-params> |
||||
</div> |
||||
</m-list-box> |
||||
</div> |
||||
</template> |
||||
<script> |
||||
import _ from 'lodash' |
||||
import i18n from '@/module/i18n' |
||||
import mListBox from './_source/listBox' |
||||
import mResources from './_source/resources' |
||||
import mLocalParams from './_source/localParams' |
||||
import disabledState from '@/module/mixin/disabledState' |
||||
import codemirror from '@/conf/home/pages/resource/pages/file/pages/_source/codemirror' |
||||
|
||||
let editor |
||||
|
||||
export default { |
||||
name: 'shell', |
||||
data () { |
||||
return { |
||||
// script |
||||
rawScript: '', |
||||
// Custom parameter |
||||
localParams: [], |
||||
// resource(list) |
||||
resourceList: [] |
||||
} |
||||
}, |
||||
mixins: [disabledState], |
||||
props: { |
||||
backfillItem: Object |
||||
}, |
||||
methods: { |
||||
/** |
||||
* return localParams |
||||
*/ |
||||
_onLocalParams (a) { |
||||
this.localParams = a |
||||
}, |
||||
/** |
||||
* return resourceList |
||||
*/ |
||||
_onResourcesData (a) { |
||||
this.resourceList = a |
||||
}, |
||||
/** |
||||
* verification |
||||
*/ |
||||
_verification () { |
||||
// rawScript verification |
||||
if (!editor.getValue()) { |
||||
this.$message.warning(`${i18n.$t('请输入rawScript(必填)')}`) |
||||
return false |
||||
} |
||||
|
||||
if (!this.$refs.refResources._verifResources()) { |
||||
return false |
||||
} |
||||
|
||||
// localParams Subcomponent verification |
||||
if (!this.$refs.refLocalParams._verifProp()) { |
||||
return false |
||||
} |
||||
|
||||
// storage |
||||
this.$emit('on-params', { |
||||
resourceList: this.resourceList, |
||||
localParams: this.localParams, |
||||
rawScript: editor.getValue() |
||||
}) |
||||
return true |
||||
}, |
||||
/** |
||||
* Processing code highlighting |
||||
*/ |
||||
_handlerEditor () { |
||||
// editor |
||||
editor = codemirror('code-shell-mirror', { |
||||
mode: 'shell', |
||||
readOnly: this.isDetails |
||||
}) |
||||
|
||||
this.keypress = () => { |
||||
if (!editor.getOption('readOnly')) { |
||||
editor.showHint({ |
||||
completeSingle: false |
||||
}) |
||||
} |
||||
} |
||||
|
||||
// Monitor keyboard |
||||
editor.on('keypress', this.keypress) |
||||
|
||||
editor.setValue(this.rawScript) |
||||
|
||||
return editor |
||||
} |
||||
}, |
||||
watch: {}, |
||||
created () { |
||||
let o = this.backfillItem |
||||
|
||||
// Non-null objects represent backfill |
||||
if (!_.isEmpty(o)) { |
||||
this.rawScript = o.params.rawScript |
||||
|
||||
// backfill resourceList |
||||
let resourceList = o.params.resourceList || [] |
||||
if (resourceList.length) { |
||||
this.resourceList = resourceList |
||||
} |
||||
|
||||
// backfill localParams |
||||
let localParams = o.params.localParams || [] |
||||
if (localParams.length) { |
||||
this.localParams = localParams |
||||
} |
||||
} |
||||
}, |
||||
mounted () { |
||||
setTimeout(() => { |
||||
this._handlerEditor() |
||||
}, 200) |
||||
}, |
||||
destroyed () { |
||||
if (editor) { |
||||
editor.toTextArea() // Uninstall |
||||
editor.off($('.code-shell-mirror'), 'keypress', this.keypress) |
||||
} |
||||
}, |
||||
components: { mLocalParams, mListBox, mResources } |
||||
} |
||||
</script> |
@ -0,0 +1,400 @@
|
||||
<template> |
||||
<div class="spark-model"> |
||||
<m-list-box> |
||||
<div slot="text">{{$t('程序类型')}}</div> |
||||
<div slot="content"> |
||||
<x-select |
||||
style="width: 130px;" |
||||
v-model="programType" |
||||
:disabled="isDetails"> |
||||
<x-option |
||||
v-for="city in programTypeList" |
||||
:key="city.code" |
||||
:value="city.code" |
||||
:label="city.code"> |
||||
</x-option> |
||||
</x-select> |
||||
</div> |
||||
</m-list-box> |
||||
<m-list-box v-if="programType !== 'PYTHON'"> |
||||
<div slot="text">{{$t('主函数的class')}}</div> |
||||
<div slot="content"> |
||||
<x-input |
||||
:disabled="isDetails" |
||||
type="input" |
||||
v-model="mainClass" |
||||
:placeholder="$t('请输入mainClass')" |
||||
autocomplete="off"> |
||||
</x-input> |
||||
</div> |
||||
</m-list-box> |
||||
<m-list-box> |
||||
<div slot="text">{{$t('主jar包')}}</div> |
||||
<div slot="content"> |
||||
<x-select |
||||
style="width: 100%;" |
||||
:placeholder="$t('请选择主jar包')" |
||||
v-model="mainJar" |
||||
filterable |
||||
:disabled="isDetails"> |
||||
<x-option |
||||
v-for="city in mainJarList" |
||||
:key="city.code" |
||||
:value="city.code" |
||||
:label="city.code"> |
||||
</x-option> |
||||
</x-select> |
||||
</div> |
||||
</m-list-box> |
||||
<m-list-box> |
||||
<div slot="text">{{$t('部署方式')}}</div> |
||||
<div slot="content"> |
||||
<x-radio-group v-model="deployMode"> |
||||
<x-radio :label="'cluster'" :disabled="isDetails"></x-radio> |
||||
<x-radio :label="'client'" :disabled="isDetails"></x-radio> |
||||
<x-radio :label="'local'" :disabled="isDetails"></x-radio> |
||||
</x-radio-group> |
||||
</div> |
||||
</m-list-box> |
||||
<div class="list-box-4p"> |
||||
<div class="clearfix list"> |
||||
<span class="sp1">{{$t('Driver内核数')}}</span> |
||||
<span class="sp2"> |
||||
<x-input |
||||
:disabled="isDetails" |
||||
type="input" |
||||
v-model="driverCores" |
||||
:placeholder="$t('请输入Driver内核数')" |
||||
style="width: 200px;" |
||||
autocomplete="off"> |
||||
</x-input> |
||||
</span> |
||||
<span class="sp1 sp3">{{$t('Driver内存数')}}</span> |
||||
<span class="sp2"> |
||||
<x-input |
||||
:disabled="isDetails" |
||||
type="input" |
||||
v-model="driverMemory" |
||||
:placeholder="$t('请输入Driver内存数')" |
||||
style="width: 186px;" |
||||
autocomplete="off"> |
||||
</x-input> |
||||
</span> |
||||
</div> |
||||
<div class="clearfix list"> |
||||
<span class="sp1">{{$t('Executor数量')}}</span> |
||||
<span class="sp2"> |
||||
<x-input |
||||
:disabled="isDetails" |
||||
type="input" |
||||
v-model="numExecutors" |
||||
:placeholder="$t('请输入Executor数量')" |
||||
style="width: 200px;" |
||||
autocomplete="off"> |
||||
</x-input> |
||||
</span> |
||||
<span class="sp1 sp3">{{$t('Executor内存数')}}</span> |
||||
<span class="sp2"> |
||||
<x-input |
||||
:disabled="isDetails" |
||||
type="input" |
||||
v-model="executorMemory" |
||||
:placeholder="$t('请输入Executor内存数')" |
||||
style="width: 186px;" |
||||
autocomplete="off"> |
||||
</x-input> |
||||
</span> |
||||
</div> |
||||
<div class="clearfix list"> |
||||
<span class="sp1">{{$t('Executor内核数')}}</span> |
||||
<span class="sp2"> |
||||
<x-input |
||||
:disabled="isDetails" |
||||
type="input" |
||||
v-model="executorCores" |
||||
:placeholder="$t('请输入Executor内核数')" |
||||
style="width: 200px;" |
||||
autocomplete="off"> |
||||
</x-input> |
||||
</span> |
||||
</div> |
||||
</div> |
||||
<m-list-box> |
||||
<div slot="text">{{$t('命令行参数')}}</div> |
||||
<div slot="content"> |
||||
<x-input |
||||
:autosize="{minRows:2}" |
||||
:disabled="isDetails" |
||||
type="textarea" |
||||
v-model="mainArgs" |
||||
:placeholder="$t('请输入命令行参数')" |
||||
autocomplete="off"> |
||||
</x-input> |
||||
</div> |
||||
</m-list-box> |
||||
<m-list-box> |
||||
<div slot="text">{{$t('其他参数')}}</div> |
||||
<div slot="content"> |
||||
<x-input |
||||
:disabled="isDetails" |
||||
:autosize="{minRows:2}" |
||||
type="textarea" |
||||
v-model="others" |
||||
:placeholder="$t('请输入其他参数')"> |
||||
</x-input> |
||||
</div> |
||||
</m-list-box> |
||||
<m-list-box> |
||||
<div slot="text">{{$t('资源')}}</div> |
||||
<div slot="content"> |
||||
<m-resources |
||||
ref="refResources" |
||||
@on-resourcesData="_onResourcesData" |
||||
:resource-list="resourceList"> |
||||
</m-resources> |
||||
</div> |
||||
</m-list-box> |
||||
<m-list-box> |
||||
<div slot="text">{{$t('自定义参数')}}</div> |
||||
<div slot="content"> |
||||
<m-local-params |
||||
ref="refLocalParams" |
||||
@on-local-params="_onLocalParams" |
||||
:udp-list="localParams" |
||||
:hide="false"> |
||||
</m-local-params> |
||||
</div> |
||||
</m-list-box> |
||||
</div> |
||||
</template> |
||||
<script> |
||||
import _ from 'lodash' |
||||
import i18n from '@/module/i18n' |
||||
import mLocalParams from './_source/localParams' |
||||
import mListBox from './_source/listBox' |
||||
import mResources from './_source/resources' |
||||
import disabledState from '@/module/mixin/disabledState' |
||||
|
||||
export default { |
||||
name: 'spark', |
||||
data () { |
||||
return { |
||||
// Main function class |
||||
mainClass: '', |
||||
// Master jar package |
||||
mainJar: null, |
||||
// Master jar package(List) |
||||
mainJarList: [], |
||||
// Deployment method |
||||
deployMode: 'cluster', |
||||
// Resource(list) |
||||
resourceList: [], |
||||
// Custom function |
||||
localParams: [], |
||||
// Driver Number of cores |
||||
driverCores: 1, |
||||
// Driver Number of memory |
||||
driverMemory: '512M', |
||||
// Executor Number |
||||
numExecutors: 2, |
||||
// Executor Number of memory |
||||
executorMemory: '2G', |
||||
// Executor Number of cores |
||||
executorCores: 2, |
||||
// Command line argument |
||||
mainArgs: '', |
||||
// Other parameters |
||||
others: '', |
||||
// Program type |
||||
programType: 'SCALA', |
||||
// Program type(List) |
||||
programTypeList: [{ code: 'JAVA' }, { code: 'SCALA' }, { code: 'PYTHON' }] |
||||
} |
||||
}, |
||||
props: { |
||||
backfillItem: Object |
||||
}, |
||||
mixins: [disabledState], |
||||
methods: { |
||||
/** |
||||
* return localParams |
||||
*/ |
||||
_onLocalParams (a) { |
||||
this.localParams = a |
||||
}, |
||||
/** |
||||
* return resourceList |
||||
*/ |
||||
_onResourcesData (a) { |
||||
this.resourceList = a |
||||
}, |
||||
/** |
||||
* verification |
||||
*/ |
||||
_verification () { |
||||
if (this.programType !== 'PYTHON' && !this.mainClass) { |
||||
this.$message.warning(`${i18n.$t('请填写主函数的class')}`) |
||||
return false |
||||
} |
||||
|
||||
if (!this.mainJar) { |
||||
this.$message.warning(`${i18n.$t('请选择主jar包')}`) |
||||
return false |
||||
} |
||||
|
||||
if (!this.numExecutors) { |
||||
this.$message.warning(`${i18n.$t('请填写Executor数量')}`) |
||||
return false |
||||
} |
||||
|
||||
if (!Number.isInteger(parseInt(this.numExecutors))) { |
||||
this.$message.warning(`${i18n.$t('Executor数量为正整数')}`) |
||||
return false |
||||
} |
||||
|
||||
if (!this.executorMemory) { |
||||
this.$message.warning(`${i18n.$t('请填写Executor内存数')}`) |
||||
return false |
||||
} |
||||
|
||||
if (!this.executorMemory) { |
||||
this.$message.warning(`${i18n.$t('请填写Executor内存数')}`) |
||||
return false |
||||
} |
||||
|
||||
if (!_.isNumber(parseInt(this.executorMemory))) { |
||||
this.$message.warning(`${i18n.$t('内存数为数字')}`) |
||||
return false |
||||
} |
||||
|
||||
if (!this.executorCores) { |
||||
this.$message.warning(`${i18n.$t('请填写Executor内核数')}`) |
||||
return false |
||||
} |
||||
|
||||
if (!Number.isInteger(parseInt(this.executorCores))) { |
||||
this.$message.warning(`${i18n.$t('内核数为正整数')}`) |
||||
return false |
||||
} |
||||
|
||||
if (!this.$refs.refResources._verifResources()) { |
||||
return false |
||||
} |
||||
|
||||
// localParams Subcomponent verification |
||||
if (!this.$refs.refLocalParams._verifProp()) { |
||||
return false |
||||
} |
||||
|
||||
// storage |
||||
this.$emit('on-params', { |
||||
mainClass: this.mainClass, |
||||
mainJar: { |
||||
res: this.mainJar |
||||
}, |
||||
deployMode: this.deployMode, |
||||
resourceList: this.resourceList, |
||||
localParams: this.localParams, |
||||
driverCores: this.driverCores, |
||||
driverMemory: this.driverMemory, |
||||
numExecutors: this.numExecutors, |
||||
executorMemory: this.executorMemory, |
||||
executorCores: this.executorCores, |
||||
mainArgs: this.mainArgs, |
||||
others: this.others, |
||||
programType: this.programType |
||||
}) |
||||
return true |
||||
}, |
||||
/** |
||||
* get resources list |
||||
*/ |
||||
_getResourcesList () { |
||||
return new Promise((resolve, reject) => { |
||||
let isJar = (alias) => { |
||||
return alias.substring(alias.lastIndexOf('.') + 1, alias.length) !== 'jar' |
||||
} |
||||
this.mainJarList = _.map(_.cloneDeep(this.store.state.dag.resourcesListS), v => { |
||||
return { |
||||
id: v.id, |
||||
code: v.alias, |
||||
disabled: isJar(v.alias) |
||||
} |
||||
}) |
||||
resolve() |
||||
}) |
||||
} |
||||
}, |
||||
watch: { |
||||
// Listening type |
||||
programType (type) { |
||||
if (type === 'PYTHON') { |
||||
this.mainClass = '' |
||||
} |
||||
} |
||||
}, |
||||
created () { |
||||
this._getResourcesList().then(() => { |
||||
let o = this.backfillItem |
||||
|
||||
// Non-null objects represent backfill |
||||
if (!_.isEmpty(o)) { |
||||
this.mainClass = o.params.mainClass || '' |
||||
this.mainJar = o.params.mainJar.res || '' |
||||
this.deployMode = o.params.deployMode || '' |
||||
this.driverCores = o.params.driverCores || 1 |
||||
this.driverMemory = o.params.driverMemory || '512M' |
||||
this.numExecutors = o.params.numExecutors || 2 |
||||
this.executorMemory = o.params.executorMemory || '2G' |
||||
this.executorCores = o.params.executorCores || 2 |
||||
this.mainArgs = o.params.mainArgs || '' |
||||
this.others = o.params.others |
||||
this.programType = o.params.programType || 'SCALA' |
||||
|
||||
// backfill resourceList |
||||
let resourceList = o.params.resourceList || [] |
||||
if (resourceList.length) { |
||||
this.resourceList = resourceList |
||||
} |
||||
|
||||
// backfill localParams |
||||
let localParams = o.params.localParams || [] |
||||
if (localParams.length) { |
||||
this.localParams = localParams |
||||
} |
||||
} |
||||
}) |
||||
}, |
||||
mounted () { |
||||
|
||||
}, |
||||
components: { mLocalParams, mListBox, mResources } |
||||
} |
||||
</script> |
||||
|
||||
<style lang="scss" rel="stylesheet/scss"> |
||||
.spark-model { |
||||
.list-box-4p { |
||||
.list { |
||||
margin-bottom: 14px; |
||||
.sp1 { |
||||
float: left; |
||||
width: 112px; |
||||
text-align: right; |
||||
margin-right: 10px; |
||||
font-size: 14px; |
||||
color: #777; |
||||
display: inline-block; |
||||
padding-top: 6px; |
||||
} |
||||
.sp2 { |
||||
float: left; |
||||
margin-right: 4px; |
||||
} |
||||
.sp3 { |
||||
width: 176px; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
</style> |
@ -0,0 +1,267 @@
|
||||
<template> |
||||
<div class="sql-model"> |
||||
<m-list-box> |
||||
<div slot="text">{{$t('数据源')}}</div> |
||||
<div slot="content"> |
||||
<m-datasource |
||||
ref="refDs" |
||||
@on-dsData="_onDsData" |
||||
:data="{ type:type,datasource:datasource }"> |
||||
</m-datasource> |
||||
</div> |
||||
</m-list-box> |
||||
<m-list-box> |
||||
<div slot="text">{{$t('sql类型')}}</div> |
||||
<div slot="content"> |
||||
<div style="display: inline-block;"> |
||||
<m-sql-type |
||||
@on-sqlType="_onSqlType" |
||||
:sql-type="sqlType"> |
||||
</m-sql-type> |
||||
</div> |
||||
<div v-if="!sqlType" style="display: inline-block;padding-left: 10px;margin-top: 2px;"> |
||||
<x-checkbox-group v-model="showType"> |
||||
<x-checkbox :label="'TABLE'" :disabled="isDetails">{{$t('表格')}}</x-checkbox> |
||||
<x-checkbox :label="'ATTACHMENT'" :disabled="isDetails">{{$t('附件')}}</x-checkbox> |
||||
</x-checkbox-group> |
||||
</div> |
||||
</div> |
||||
</m-list-box> |
||||
<m-list-box v-show="type === 'HIVE'"> |
||||
<div slot="text">{{$t('sql参数')}}</div> |
||||
<div slot="content"> |
||||
<x-input |
||||
:disabled="isDetails" |
||||
type="input" |
||||
v-model="connParams" |
||||
:placeholder="$t('请输入格式为') + ' key1=value1;key2=value2...'" |
||||
autocomplete="off"> |
||||
</x-input> |
||||
</div> |
||||
</m-list-box> |
||||
<m-list-box> |
||||
<div slot="text">{{$t('sql语句')}}</div> |
||||
<div slot="content"> |
||||
<div class="from-mirror"> |
||||
<textarea |
||||
id="code-sql-mirror" |
||||
name="code-sql-mirror" |
||||
style="opacity: 0;"> |
||||
</textarea> |
||||
</div> |
||||
</div> |
||||
</m-list-box> |
||||
<m-list-box v-if="type === 'HIVE'"> |
||||
<div slot="text">{{$t('UDF函数')}}</div> |
||||
<div slot="content"> |
||||
<m-udfs |
||||
ref="refUdfs" |
||||
@on-udfsData="_onUdfsData" |
||||
:udfs="udfs" |
||||
:type="type"> |
||||
</m-udfs> |
||||
</div> |
||||
</m-list-box> |
||||
<m-list-box> |
||||
<div slot="text">{{$t('自定义参数')}}</div> |
||||
<div slot="content"> |
||||
<m-local-params |
||||
ref="refLocalParams" |
||||
@on-udpData="_onUdpData" |
||||
:udp-list="localParams"> |
||||
</m-local-params> |
||||
</div> |
||||
</m-list-box> |
||||
</div> |
||||
</template> |
||||
<script> |
||||
import _ from 'lodash' |
||||
import i18n from '@/module/i18n' |
||||
import mUdfs from './_source/udfs' |
||||
import mListBox from './_source/listBox' |
||||
import mSqlType from './_source/sqlType' |
||||
import mDatasource from './_source/datasource' |
||||
import mLocalParams from './_source/localParams' |
||||
import disabledState from '@/module/mixin/disabledState' |
||||
import codemirror from '@/conf/home/pages/resource/pages/file/pages/_source/codemirror' |
||||
|
||||
let editor |
||||
|
||||
export default { |
||||
name: 'sql', |
||||
data () { |
||||
return { |
||||
// Data source type |
||||
type: '', |
||||
// data source |
||||
datasource: '', |
||||
// Return to the selected data source |
||||
rtDatasource: '', |
||||
// Sql statement |
||||
sql: '', |
||||
// Custom parameter |
||||
localParams: [], |
||||
// UDF function |
||||
udfs: '', |
||||
// Sql type |
||||
sqlType: 0, |
||||
// Form/attachment |
||||
showType: ['TABLE'], |
||||
// Sql parameter |
||||
connParams: '' |
||||
} |
||||
}, |
||||
mixins: [disabledState], |
||||
props: { |
||||
backfillItem: Object |
||||
}, |
||||
methods: { |
||||
/** |
||||
* return sqlType |
||||
*/ |
||||
_onSqlType (a) { |
||||
this.sqlType = a |
||||
}, |
||||
/** |
||||
* return udfs |
||||
*/ |
||||
_onUdfsData (a) { |
||||
this.udfs = a |
||||
}, |
||||
/** |
||||
* return Custom parameter |
||||
*/ |
||||
_onUdpData (a) { |
||||
this.localParams = a |
||||
}, |
||||
/** |
||||
* return data source |
||||
*/ |
||||
_onDsData (o) { |
||||
this.type = o.type |
||||
this.rtDatasource = o.datasource |
||||
}, |
||||
/** |
||||
* verification |
||||
*/ |
||||
_verification () { |
||||
if (!editor.getValue()) { |
||||
this.$message.warning(`${i18n.$t('请输入sql语句(必填)')}`) |
||||
return false |
||||
} |
||||
|
||||
// datasource Subcomponent verification |
||||
if (!this.$refs.refDs._verifDatasource()) { |
||||
return false |
||||
} |
||||
|
||||
// udfs Subcomponent verification Verification only if the data type is HIVE |
||||
if (this.type === 'HIVE') { |
||||
if (!this.$refs.refUdfs._verifUdfs()) { |
||||
return false |
||||
} |
||||
} |
||||
|
||||
// localParams Subcomponent verification |
||||
if (!this.$refs.refLocalParams._verifProp()) { |
||||
return false |
||||
} |
||||
|
||||
// storage |
||||
this.$emit('on-params', { |
||||
type: this.type, |
||||
datasource: this.rtDatasource, |
||||
sql: editor.getValue(), |
||||
udfs: this.udfs, |
||||
sqlType: this.sqlType, |
||||
showType: (() => { |
||||
/** |
||||
* Special processing return order TABLE,ATTACHMENT |
||||
* Handling checkout sequence |
||||
*/ |
||||
let showType = this.showType |
||||
if (showType.length === 2 && showType[0] === 'ATTACHMENT') { |
||||
return [showType[1], showType[0]].join(',') |
||||
} else { |
||||
return showType.join(',') |
||||
} |
||||
})(), |
||||
localParams: this.localParams, |
||||
connParams: this.connParams |
||||
}) |
||||
return true |
||||
}, |
||||
/** |
||||
* Processing code highlighting |
||||
*/ |
||||
_handlerEditor () { |
||||
// editor |
||||
editor = codemirror('code-sql-mirror', { |
||||
mode: 'sql', |
||||
readOnly: this.isDetails |
||||
}) |
||||
|
||||
this.keypress = () => { |
||||
if (!editor.getOption('readOnly')) { |
||||
editor.showHint({ |
||||
completeSingle: false |
||||
}) |
||||
} |
||||
} |
||||
|
||||
// Monitor keyboard |
||||
editor.on('keypress', this.keypress) |
||||
|
||||
editor.setValue(this.sql) |
||||
|
||||
return editor |
||||
} |
||||
}, |
||||
watch: { |
||||
// Listening to sqlType |
||||
sqlType (val) { |
||||
if (val) { |
||||
this.showType = [] |
||||
} |
||||
}, |
||||
// Listening data source |
||||
type (val) { |
||||
if (val !== 'HIVE') { |
||||
this.connParams = '' |
||||
} |
||||
} |
||||
}, |
||||
created () { |
||||
let o = this.backfillItem |
||||
|
||||
// Non-null objects represent backfill |
||||
if (!_.isEmpty(o)) { |
||||
// backfill |
||||
this.type = o.params.type || '' |
||||
this.datasource = o.params.datasource || '' |
||||
this.sql = o.params.sql || '' |
||||
this.udfs = o.params.udfs || '' |
||||
this.sqlType = o.params.sqlType |
||||
this.connParams = o.params.connParams || '' |
||||
this.localParams = o.params.localParams || [] |
||||
this.showType = o.params.showType.split(',') || [] |
||||
} |
||||
}, |
||||
mounted () { |
||||
setTimeout(() => { |
||||
this._handlerEditor() |
||||
}, 200) |
||||
}, |
||||
destroyed () { |
||||
/** |
||||
* Destroy the editor instance |
||||
*/ |
||||
if (editor) { |
||||
editor.toTextArea() // Uninstall |
||||
editor.off($('.code-sql-mirror'), 'keypress', this.keypress) |
||||
} |
||||
}, |
||||
computed: {}, |
||||
components: { mListBox, mDatasource, mLocalParams, mUdfs, mSqlType } |
||||
} |
||||
</script> |
@ -0,0 +1,102 @@
|
||||
<template> |
||||
<div class="sub_process-model"> |
||||
<div class="clearfix list"> |
||||
<div class="text-box"> |
||||
<span>{{$t('子节点')}}</span> |
||||
</div> |
||||
<div class="cont-box"> |
||||
<div class="label-box"> |
||||
<x-select |
||||
style="width: 100%;" |
||||
filterable |
||||
v-model="wdiCurr" |
||||
:disabled="isDetails" |
||||
@on-change="_handleWdiChanged"> |
||||
<x-option |
||||
v-for="city in processDefinitionList" |
||||
:key="city.code" |
||||
:value="city.id" |
||||
:label="city.code"> |
||||
</x-option> |
||||
</x-select> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
<script> |
||||
import _ from 'lodash' |
||||
import i18n from '@/module/i18n' |
||||
import disabledState from '@/module/mixin/disabledState' |
||||
|
||||
export default { |
||||
name: 'sub_process', |
||||
data () { |
||||
return { |
||||
// Process definition(List) |
||||
processDefinitionList: [], |
||||
// Process definition |
||||
wdiCurr: null |
||||
} |
||||
}, |
||||
mixins: [disabledState], |
||||
props: { |
||||
backfillItem: Object |
||||
}, |
||||
methods: { |
||||
/** |
||||
* Node unified authentication parameters |
||||
*/ |
||||
_verification () { |
||||
if (!this.wdiCurr) { |
||||
this.$message.warning(`${i18n.$t('请选择子工作流')}`) |
||||
return false |
||||
} |
||||
this.$emit('on-params', { |
||||
processDefinitionId: this.wdiCurr |
||||
}) |
||||
return true |
||||
}, |
||||
/** |
||||
* The selected process defines the upper component name padding |
||||
*/ |
||||
_handleWdiChanged (o) { |
||||
this.$emit('on-set-process-name', this._handleName(o.value)) |
||||
}, |
||||
/** |
||||
* Return the name according to the process definition id |
||||
*/ |
||||
_handleName (id) { |
||||
return _.filter(this.processDefinitionList, v => id === v.id)[0].code |
||||
} |
||||
}, |
||||
watch: {}, |
||||
created () { |
||||
let processListS = _.cloneDeep(this.store.state.dag.processListS) |
||||
let id = this.router.history.current.params.id || null |
||||
this.processDefinitionList = (() => { |
||||
let a = _.map(processListS, v => { |
||||
return { |
||||
id: v.id, |
||||
code: v.name, |
||||
disabled: false |
||||
} |
||||
}) |
||||
return _.filter(a, v => +v.id !== +id) |
||||
})() |
||||
|
||||
let o = this.backfillItem |
||||
// Non-null objects represent backfill |
||||
if (!_.isEmpty(o)) { |
||||
this.wdiCurr = o.params.processDefinitionId |
||||
} else { |
||||
if (this.processDefinitionList.length) { |
||||
this.wdiCurr = this.processDefinitionList[0]['id'] |
||||
this.$emit('on-set-process-name', this._handleName(this.wdiCurr)) |
||||
} |
||||
} |
||||
}, |
||||
mounted () { |
||||
} |
||||
} |
||||
</script> |
@ -0,0 +1,107 @@
|
||||
|
||||
import Vue from 'vue' |
||||
import mAffirm from './jumpAffirm' |
||||
import store from '@/conf/home/store' |
||||
import router from '@/conf/home/router' |
||||
import { uuid, findComponentDownward } from '@/module/util/' |
||||
|
||||
let Affirm = {} |
||||
let $root = {} |
||||
let $routerType = '' |
||||
let $isPop = true |
||||
|
||||
/** |
||||
* Listen for route changes |
||||
*/ |
||||
router.beforeEach((to, from, next) => { |
||||
if (from.name === 'projects-definition-details' || from.name === 'projects-instance-details' || from.name === 'definition-create') { |
||||
if (!Affirm.paramVerification(from.name)) { |
||||
Affirm.isPop(() => { |
||||
next() |
||||
}) |
||||
} else { |
||||
next() |
||||
} |
||||
} else { |
||||
next() |
||||
} |
||||
}) |
||||
|
||||
/** |
||||
* Get judgment initialization data |
||||
*/ |
||||
Affirm.init = (root) => { |
||||
$isPop = true |
||||
$root = root |
||||
$routerType = router.history.current.name |
||||
} |
||||
|
||||
/** |
||||
* Parameter verification |
||||
*/ |
||||
Affirm.paramVerification = (name) => { |
||||
if (!$isPop) { |
||||
return true |
||||
} |
||||
let dagStore = store.state.dag |
||||
let flag = false |
||||
if ($routerType === 'definition-create') { |
||||
// No nodes jump out directly
|
||||
if (dagStore.tasks.length) { |
||||
if (!dagStore.name) { |
||||
store.commit('dag/setName', `${uuid('dag_')}${uuid() + uuid()}`) |
||||
} |
||||
flag = false |
||||
} else { |
||||
flag = true |
||||
} |
||||
} else { |
||||
// View history direct jump
|
||||
flag = name === 'projects-instance-details' ? true : !dagStore.isEditDag |
||||
} |
||||
return flag |
||||
} |
||||
|
||||
/** |
||||
* Pop-up judgment |
||||
*/ |
||||
Affirm.isPop = (fn) => { |
||||
Vue.$modal.dialog({ |
||||
closable: false, |
||||
showMask: true, |
||||
escClose: true, |
||||
className: 'v-modal-custom', |
||||
transitionName: 'opacityp', |
||||
render (h) { |
||||
return h(mAffirm, { |
||||
on: { |
||||
ok () { |
||||
// save
|
||||
findComponentDownward($root, 'dag-chart')._save('affirm').then(() => { |
||||
fn() |
||||
Vue.$modal.destroy() |
||||
}).catch(() => { |
||||
fn() |
||||
Vue.$modal.destroy() |
||||
}) |
||||
}, |
||||
close () { |
||||
fn() |
||||
Vue.$modal.destroy() |
||||
} |
||||
}, |
||||
props: { |
||||
} |
||||
}) |
||||
} |
||||
}) |
||||
} |
||||
|
||||
/** |
||||
* Whether the external setting pops up |
||||
*/ |
||||
Affirm.setIsPop = (is) => { |
||||
$isPop = is |
||||
} |
||||
|
||||
export default Affirm |
@ -0,0 +1,39 @@
|
||||
<template> |
||||
<div class="affirm-model"> |
||||
<m-popup :ok-text="$t('确认保存')" |
||||
:nameText="$t('是否保存DAG图')" |
||||
@close="_close" |
||||
@ok="_ok"> |
||||
</m-popup> |
||||
</div> |
||||
</template> |
||||
<script> |
||||
import mPopup from '@/module/components/popup/popup' |
||||
|
||||
export default { |
||||
name: 'affirm', |
||||
methods: { |
||||
_ok () { |
||||
this.$emit('ok') |
||||
}, |
||||
_close () { |
||||
this.$emit('close') |
||||
} |
||||
}, |
||||
components: { mPopup } |
||||
} |
||||
</script> |
||||
|
||||
<style lang="scss" rel="stylesheet/scss"> |
||||
.affirm-model { |
||||
.popup-model { |
||||
.top-p { |
||||
height: 50px; |
||||
} |
||||
.content-p { |
||||
min-height: 0px; |
||||
min-width: 250px; |
||||
} |
||||
} |
||||
} |
||||
</style> |
@ -0,0 +1,122 @@
|
||||
/* |
||||
* Licensed to the Apache Software Foundation (ASF) under one or more |
||||
* contributor license agreements. See the NOTICE file distributed with |
||||
* this work for additional information regarding copyright ownership. |
||||
* The ASF licenses this file to You under the Apache License, Version 2.0 |
||||
* (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
import _ from 'lodash' |
||||
import canvg from 'canvg' |
||||
import { tasksAll } from './util' |
||||
import html2canvas from 'html2canvas' |
||||
import { findComponentDownward } from '@/module/util/' |
||||
|
||||
let DownChart = function () { |
||||
this.dag = {} |
||||
} |
||||
|
||||
/** |
||||
* Get interception location information |
||||
*/ |
||||
DownChart.prototype.maxVal = function () { |
||||
return new Promise((resolve, reject) => { |
||||
// All nodes
|
||||
let tasksAllList = tasksAll() |
||||
let dom = $('.dag-container') |
||||
let y = parseInt(_.maxBy(tasksAllList, 'y').y + 60) |
||||
let x = parseInt(_.maxBy(tasksAllList, 'x').x + 100) |
||||
|
||||
resolve({ |
||||
width: x > 600 ? x : dom.width(), |
||||
height: (y > 500 ? y : dom.height()) + 100 |
||||
}) |
||||
}) |
||||
} |
||||
|
||||
/** |
||||
* Download to image |
||||
*/ |
||||
DownChart.prototype.download = function ({ dagThis }) { |
||||
this.dag = dagThis |
||||
|
||||
this.maxVal().then(({ width, height }) => { |
||||
// Dom to save
|
||||
const copyDom = $('#canvas') |
||||
// gain
|
||||
const scale = 1 |
||||
// svg handle
|
||||
const nodesToRecover = [] |
||||
const nodesToRemove = [] |
||||
// divReport is the id of the dom that needs to be intercepted into a picture
|
||||
const svgElem = copyDom.find('svg') |
||||
svgElem.each((index, node) => { |
||||
let parentNode = node.parentNode |
||||
let svg = node.outerHTML.trim() |
||||
let canvas = document.createElement('canvas') |
||||
canvg(canvas, svg) |
||||
if (node.style.position) { |
||||
canvas.style.position += node.style.position |
||||
canvas.style.left += node.style.left |
||||
canvas.style.top += node.style.top |
||||
} |
||||
nodesToRecover.push({ |
||||
parent: parentNode, |
||||
child: node |
||||
}) |
||||
parentNode.removeChild(node) |
||||
nodesToRemove.push({ |
||||
parent: parentNode, |
||||
child: canvas |
||||
}) |
||||
parentNode.appendChild(canvas) |
||||
}) |
||||
|
||||
const canvas = document.createElement('canvas') |
||||
// canvas width
|
||||
canvas.width = width * scale |
||||
// canvas height
|
||||
canvas.height = height * scale |
||||
|
||||
const content = canvas.getContext('2d') |
||||
content.scale(scale, scale) |
||||
// Get the offset of the element relative to the inspection
|
||||
const rect = copyDom.get(0).getBoundingClientRect() |
||||
// Set the context position, the value is a negative value relative to the window offset, let the picture reset
|
||||
content.translate(-rect.left, -rect.top) |
||||
|
||||
html2canvas(copyDom[0], { |
||||
dpi: window.devicePixelRatio * 2, |
||||
scale: scale, |
||||
width: width, |
||||
canvas: canvas, |
||||
heigth: height, |
||||
useCORS: true // Enable cross-domain configuration
|
||||
}).then((canvas) => { |
||||
let name = `${this.dag.name}.png` |
||||
let url = canvas.toDataURL('image/png', 1) |
||||
setTimeout(() => { |
||||
let triggerDownload = $('<a>').attr('href', url).attr('download', name).appendTo('body') |
||||
triggerDownload[0].click() |
||||
triggerDownload.remove() |
||||
}, 100) |
||||
|
||||
// To refresh the dag instance, otherwise you can't re-plot
|
||||
setTimeout(() => { |
||||
// Refresh current dag
|
||||
findComponentDownward(this.dag.$root, `${this.dag.type}-details`).init() |
||||
}, 500) |
||||
}) |
||||
}) |
||||
} |
||||
|
||||
export default new DownChart() |
@ -0,0 +1,40 @@
|
||||
import $ from 'jquery' |
||||
import d3 from 'd3' |
||||
/* |
||||
* Licensed to the Apache Software Foundation (ASF) under one or more |
||||
* contributor license agreements. See the NOTICE file distributed with |
||||
* this work for additional information regarding copyright ownership. |
||||
* The ASF licenses this file to You under the Apache License, Version 2.0 |
||||
* (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
const DragZoom = function () { |
||||
this.element = {} |
||||
this.zoom = {} |
||||
this.scale = 1 |
||||
} |
||||
|
||||
DragZoom.prototype.init = function () { |
||||
let $canvas = $('#canvas') |
||||
this.element = d3.select('#canvas') |
||||
this.zoom = d3.behavior.zoom() |
||||
.scaleExtent([0.5, 2]) |
||||
.on('zoom', () => { |
||||
this.scale = d3.event.scale |
||||
$canvas.css('transform', 'translate(' + d3.event.translate[0] + 'px,' + d3.event.translate[1] + 'px) scale(' + this.scale + ')') |
||||
$canvas.css('transform-origin', '0 0') |
||||
}) |
||||
this.element.call(this.zoom).on('dblclick.zoom', null) |
||||
} |
||||
|
||||
|
||||
export default new DragZoom() |
@ -0,0 +1,761 @@
|
||||
/* |
||||
* Licensed to the Apache Software Foundation (ASF) under one or more |
||||
* contributor license agreements. See the NOTICE file distributed with |
||||
* this work for additional information regarding copyright ownership. |
||||
* The ASF licenses this file to You under the Apache License, Version 2.0 |
||||
* (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
import Vue from 'vue' |
||||
import $ from 'jquery' |
||||
import _ from 'lodash' |
||||
import i18n from '@/module/i18n' |
||||
import { jsPlumb } from 'jsplumb' |
||||
import DragZoom from './dragZoom' |
||||
import store from '@/conf/home/store' |
||||
import router from '@/conf/home/router' |
||||
import Permissions from '@/module/permissions' |
||||
import { uuid, findComponentDownward } from '@/module/util/' |
||||
import { |
||||
tasksAll, |
||||
rtTasksTpl, |
||||
setSvgColor, |
||||
saveTargetarr, |
||||
rtTargetarrArr } from './util' |
||||
import mStart from '@/conf/home/pages/projects/pages/definition/pages/list/_source/start' |
||||
|
||||
let JSP = function () { |
||||
this.dag = {} |
||||
this.selectedElement = {} |
||||
|
||||
this.config = { |
||||
// Whether to drag
|
||||
isDrag: true, |
||||
// Whether to allow connection
|
||||
isAttachment: false, |
||||
// Whether to drag a new node
|
||||
isNewNodes: true, |
||||
// Whether to support double-click node events
|
||||
isDblclick: true, |
||||
// Whether to support right-click menu events
|
||||
isContextmenu: true, |
||||
// Whether to allow click events
|
||||
isClick: false |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* dag init |
||||
*/ |
||||
JSP.prototype.init = function ({ dag, instance }) { |
||||
// Get the dag component instance
|
||||
this.dag = dag |
||||
// Get jsplumb instance
|
||||
this.JspInstance = instance |
||||
// Register jsplumb connection type and configuration
|
||||
this.JspInstance.registerConnectionType('basic', { |
||||
anchor: 'Continuous', |
||||
connector: 'Straight' // Line type
|
||||
}) |
||||
|
||||
// Initial configuration
|
||||
this.setConfig({ |
||||
isDrag: !store.state.dag.isDetails, |
||||
isAttachment: false, |
||||
isNewNodes: Permissions.getAuth() === false ? false : !store.state.dag.isDetails, |
||||
isDblclick: true, |
||||
isContextmenu: true, |
||||
isClick: false |
||||
}) |
||||
|
||||
// Monitor line click
|
||||
this.JspInstance.bind('click', e => { |
||||
if (this.config.isClick) { |
||||
this.connectClick(e) |
||||
} |
||||
}) |
||||
|
||||
// Drag and drop
|
||||
if (this.config.isNewNodes) { |
||||
DragZoom.init() |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* set config attribute |
||||
*/ |
||||
JSP.prototype.setConfig = function (o) { |
||||
this.config = Object.assign(this.config, {}, o) |
||||
} |
||||
|
||||
/** |
||||
* Node binding event |
||||
*/ |
||||
JSP.prototype.tasksEvent = function (selfId) { |
||||
let tasks = $(`#${selfId}`) |
||||
// Bind right event
|
||||
tasks.on('contextmenu', e => { |
||||
this.tasksContextmenu(e) |
||||
return false |
||||
}) |
||||
|
||||
// Binding double click event
|
||||
tasks.find('.icos').bind('dblclick', e => { |
||||
this.tasksDblclick(e) |
||||
}) |
||||
|
||||
// Binding click event
|
||||
tasks.on('click', e => { |
||||
this.tasksClick(e) |
||||
}) |
||||
} |
||||
|
||||
/** |
||||
* Dag node drag and drop processing |
||||
*/ |
||||
JSP.prototype.draggable = function () { |
||||
if (this.config.isNewNodes) { |
||||
let selfId |
||||
let self = this |
||||
$('.toolbar-btn .roundedRect').draggable({ |
||||
scope: 'plant', |
||||
helper: 'clone', |
||||
containment: $('.dag-model'), |
||||
stop: function (e, ui) { |
||||
self.tasksEvent(selfId) |
||||
|
||||
// Dom structure is not generated without pop-up form form
|
||||
if ($(`#${selfId}`).html()) { |
||||
// dag event
|
||||
findComponentDownward(self.dag.$root, 'dag-chart')._createNodes({ |
||||
id: selfId |
||||
}) |
||||
} |
||||
}, |
||||
drag: function () { |
||||
$('body').find('.tooltip.fade.top.in').remove() |
||||
} |
||||
}) |
||||
|
||||
$('#canvas').droppable({ |
||||
scope: 'plant', |
||||
drop: function (ev, ui) { |
||||
let id = 'tasks-' + Math.ceil(Math.random() * 100000) // eslint-disable-line
|
||||
// Get mouse coordinates
|
||||
let left = parseInt(ui.offset.left - $(this).offset().left) |
||||
let top = parseInt(ui.offset.top - $(this).offset().top) - 10 |
||||
if (top < 25) { |
||||
top = 25 |
||||
} |
||||
// Generate template node
|
||||
$('#canvas').append(rtTasksTpl({ |
||||
id: id, |
||||
name: id, |
||||
x: left, |
||||
y: top, |
||||
isAttachment: self.config.isAttachment, |
||||
taskType: findComponentDownward(self.dag.$root, 'dag-chart').dagBarId |
||||
})) |
||||
|
||||
// Get the generated node
|
||||
let thisDom = jsPlumb.getSelector('.statemachine-demo .w') |
||||
|
||||
// Generating a connection node
|
||||
self.JspInstance.batch(() => { |
||||
self.initNode(thisDom[thisDom.length - 1]) |
||||
}) |
||||
selfId = id |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Echo json processing and old data structure processing |
||||
*/ |
||||
JSP.prototype.jsonHandle = function ({ largeJson, locations }) { |
||||
_.map(largeJson, v => { |
||||
// Generate template
|
||||
$('#canvas').append(rtTasksTpl({ |
||||
id: v.id, |
||||
name: v.name, |
||||
x: locations[v.id]['x'], |
||||
y: locations[v.id]['y'], |
||||
targetarr: locations[v.id]['targetarr'], |
||||
isAttachment: this.config.isAttachment, |
||||
taskType: v.type |
||||
})) |
||||
|
||||
// contextmenu event
|
||||
$(`#${v.id}`).on('contextmenu', e => { |
||||
this.tasksContextmenu(e) |
||||
return false |
||||
}) |
||||
|
||||
// dblclick event
|
||||
$(`#${v.id}`).find('.icos').bind('dblclick', e => { |
||||
this.tasksDblclick(e) |
||||
}) |
||||
|
||||
// click event
|
||||
$(`#${v.id}`).bind('click', e => { |
||||
this.tasksClick(e) |
||||
}) |
||||
}) |
||||
} |
||||
|
||||
/** |
||||
* Initialize a single node |
||||
*/ |
||||
JSP.prototype.initNode = function (el) { |
||||
// Whether to drag
|
||||
if (this.config.isDrag) { |
||||
this.JspInstance.draggable(el, { |
||||
containment: 'dag-container' |
||||
}) |
||||
} |
||||
|
||||
// Node attribute configuration
|
||||
this.JspInstance.makeSource(el, { |
||||
filter: '.ep', |
||||
anchor: 'Continuous', |
||||
connectorStyle: { |
||||
stroke: '#555', |
||||
strokeWidth: 2, |
||||
outlineStroke: 'transparent', |
||||
outlineWidth: 4 |
||||
}, |
||||
// This place is leaking
|
||||
// connectionType: "basic",
|
||||
extract: { |
||||
action: 'the-action' |
||||
}, |
||||
maxConnections: -1 |
||||
}) |
||||
|
||||
// Node connection property configuration
|
||||
this.JspInstance.makeTarget(el, { |
||||
dropOptions: { hoverClass: 'dragHover' }, |
||||
anchor: 'Continuous', |
||||
allowLoopback: false // Forbid yourself to connect yourself
|
||||
}) |
||||
this.JspInstance.fire('jsPlumbDemoNodeAdded', el) |
||||
} |
||||
|
||||
/** |
||||
* Node right click menu |
||||
*/ |
||||
JSP.prototype.tasksContextmenu = function (event) { |
||||
if (this.config.isContextmenu) { |
||||
let routerName = router.history.current.name |
||||
// state
|
||||
let isOne = routerName === 'projects-definition-details' && this.dag.releaseState !== 'NOT_RELEASE' |
||||
// hide
|
||||
let isTwo = store.state.dag.isDetails |
||||
|
||||
let html = [ |
||||
`<a href="javascript:" id="startRunning" class="${isOne ? '' : 'disbled'}"><i class="iconfont"></i><span>${i18n.$t('开始运行')}</span></a>`, |
||||
`<a href="javascript:" id="editNodes" class="${isTwo ? 'disbled' : ''}"><i class="iconfont"></i><span>${i18n.$t('编辑节点')}</span></a>`, |
||||
`<a href="javascript:" id="copyNodes" class="${isTwo ? 'disbled' : ''}"><i class="iconfont"></i><span>${i18n.$t('复制节点')}</span></a>`, |
||||
`<a href="javascript:" id="removeNodes" class="${isTwo ? 'disbled' : ''}"><i class="iconfont"></i><span>${i18n.$t('删除节点')}</span></a>` |
||||
] |
||||
|
||||
let operationHtml = () => { |
||||
return html.splice(',') |
||||
} |
||||
|
||||
let e = event |
||||
let $id = e.currentTarget.id |
||||
let $contextmenu = $('#contextmenu') |
||||
let $name = $(`#${$id}`).find('.name-p').text() |
||||
let $left = e.pageX + document.body.scrollLeft - 5 |
||||
let $top = e.pageY + document.body.scrollTop - 5 |
||||
$contextmenu.css({ |
||||
left: $left, |
||||
top: $top, |
||||
visibility: 'visible' |
||||
}) |
||||
// Action bar
|
||||
$contextmenu.html('').append(operationHtml) |
||||
|
||||
if (isOne) { |
||||
// start run
|
||||
$('#startRunning').on('click', () => { |
||||
let id = router.history.current.params.id |
||||
store.dispatch('dag/getStartCheck', { processDefinitionId: id }).then(res => { |
||||
let modal = Vue.$modal.dialog({ |
||||
closable: false, |
||||
showMask: true, |
||||
escClose: true, |
||||
className: 'v-modal-custom', |
||||
transitionName: 'opacityp', |
||||
render (h) { |
||||
return h(mStart, { |
||||
on: { |
||||
onUpdate () { |
||||
modal.remove() |
||||
}, |
||||
close () { |
||||
modal.remove() |
||||
} |
||||
}, |
||||
props: { |
||||
item: { |
||||
id: id |
||||
}, |
||||
startNodeList: $name, |
||||
sourceType: 'contextmenu' |
||||
} |
||||
}) |
||||
} |
||||
}) |
||||
}).catch(e => { |
||||
Vue.$message.error(e.msg || '') |
||||
}) |
||||
}) |
||||
} |
||||
if (!isTwo) { |
||||
// edit node
|
||||
$(`#editNodes`).click(ev => { |
||||
findComponentDownward(this.dag.$root, 'dag-chart')._createNodes({ |
||||
id: $id, |
||||
type: $(`#${$id}`).attr('data-tasks-type') |
||||
}) |
||||
}) |
||||
// delete node
|
||||
$('#removeNodes').click(ev => { |
||||
this.removeNodes($id) |
||||
}) |
||||
|
||||
// copy node
|
||||
$('#copyNodes').click(res => { |
||||
this.copyNodes($id) |
||||
}) |
||||
} |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Node double click event |
||||
*/ |
||||
JSP.prototype.tasksDblclick = function (e) { |
||||
// Untie event
|
||||
if (this.config.isDblclick) { |
||||
let id = $(e.currentTarget.offsetParent).attr('id') |
||||
|
||||
findComponentDownward(this.dag.$root, 'dag-chart')._createNodes({ |
||||
id: id, |
||||
type: $(`#${id}`).attr('data-tasks-type') |
||||
}) |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Node click event |
||||
*/ |
||||
JSP.prototype.tasksClick = function (e) { |
||||
let $id |
||||
let self = this |
||||
let $body = $(`body`) |
||||
if (this.config.isClick) { |
||||
let $connect = this.selectedElement.connect |
||||
$('.w').removeClass('jtk-tasks-active') |
||||
$(e.currentTarget).addClass('jtk-tasks-active') |
||||
if ($connect) { |
||||
setSvgColor($connect, '#555') |
||||
this.selectedElement.connect = null |
||||
} |
||||
this.selectedElement.id = $(e.currentTarget).attr('id') |
||||
|
||||
// Unbind copy and paste events
|
||||
$body.unbind('copy').unbind('paste') |
||||
// Copy binding id
|
||||
$id = self.selectedElement.id |
||||
|
||||
$body.bind({ |
||||
copy: function () { |
||||
$id = self.selectedElement.id |
||||
}, |
||||
paste: function () { |
||||
$id && self.copyNodes($id) |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* Remove binding events |
||||
* paste |
||||
*/ |
||||
JSP.prototype.removePaste = function () { |
||||
let $body = $(`body`) |
||||
// Unbind copy and paste events
|
||||
$body.unbind('copy').unbind('paste') |
||||
// Remove selected node parameters
|
||||
this.selectedElement.id = null |
||||
// Remove node selection effect
|
||||
$('.w').removeClass('jtk-tasks-active') |
||||
} |
||||
|
||||
/** |
||||
* Line click event |
||||
*/ |
||||
JSP.prototype.connectClick = function (e) { |
||||
// Set svg color
|
||||
setSvgColor(e, '#0097e0') |
||||
let $id = this.selectedElement.id |
||||
if ($id) { |
||||
$(`#${$id}`).removeClass('jtk-tasks-active') |
||||
this.selectedElement.id = null |
||||
} |
||||
this.selectedElement.connect = e |
||||
} |
||||
|
||||
/** |
||||
* toolbarEvent |
||||
* @param {Pointer} |
||||
*/ |
||||
JSP.prototype.handleEventPointer = function (is) { |
||||
let wDom = $('.w') |
||||
this.setConfig({ |
||||
isClick: is, |
||||
isAttachment: false |
||||
}) |
||||
wDom.removeClass('jtk-ep') |
||||
if (!is) { |
||||
wDom.removeClass('jtk-tasks-active') |
||||
this.selectedElement = {} |
||||
_.map($('#canvas svg'), v => { |
||||
if ($(v).attr('class')) { |
||||
_.map($(v).find('path'), v1 => { |
||||
$(v1).attr('fill', '#555') |
||||
$(v1).attr('stroke', '#555') |
||||
}) |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* toolbarEvent |
||||
* @param {Line} |
||||
*/ |
||||
JSP.prototype.handleEventLine = function (is) { |
||||
let wDom = $('.w') |
||||
this.setConfig({ |
||||
isAttachment: is |
||||
}) |
||||
is ? wDom.addClass('jtk-ep') : wDom.removeClass('jtk-ep') |
||||
} |
||||
|
||||
/** |
||||
* toolbarEvent |
||||
* @param {Remove} |
||||
*/ |
||||
JSP.prototype.handleEventRemove = function () { |
||||
let $id = this.selectedElement.id || null |
||||
let $connect = this.selectedElement.connect || null |
||||
if ($id) { |
||||
this.removeNodes(this.selectedElement.id) |
||||
} else { |
||||
this.removeConnect($connect) |
||||
} |
||||
|
||||
// Monitor whether to edit DAG
|
||||
store.commit('dag/setIsEditDag', true) |
||||
} |
||||
|
||||
/** |
||||
* Delete node |
||||
*/ |
||||
JSP.prototype.removeNodes = function ($id) { |
||||
// Delete node processing(data-targetarr)
|
||||
_.map(tasksAll(), v => { |
||||
let targetarr = v.targetarr.split(',') |
||||
if (targetarr.length) { |
||||
let newArr = _.filter(targetarr, v1 => v1 !== $id) |
||||
$(`#${v.id}`).attr('data-targetarr', newArr.toString()) |
||||
} |
||||
}) |
||||
// delete node
|
||||
this.JspInstance.remove($id) |
||||
} |
||||
|
||||
/** |
||||
* Delete connection |
||||
*/ |
||||
JSP.prototype.removeConnect = function ($connect) { |
||||
if (!$connect) { |
||||
return |
||||
} |
||||
// Remove connections and remove node and node dependencies
|
||||
let targetId = $connect.targetId |
||||
let sourceId = $connect.sourceId |
||||
let targetarr = rtTargetarrArr(targetId) |
||||
if (targetarr.length) { |
||||
targetarr = _.filter(targetarr, v => v !== sourceId) |
||||
$(`#${targetId}`).attr('data-targetarr', targetarr.toString()) |
||||
} |
||||
this.JspInstance.deleteConnection($connect) |
||||
|
||||
this.selectedElement = {} |
||||
} |
||||
|
||||
/** |
||||
* Copy node |
||||
*/ |
||||
JSP.prototype.copyNodes = function ($id) { |
||||
let newNodeInfo = _.cloneDeep(_.find(store.state.dag.tasks, v => v.id === $id)) |
||||
let newNodePors = store.state.dag.locations[$id] |
||||
// Unstored nodes do not allow replication
|
||||
if (!newNodePors) { |
||||
return |
||||
} |
||||
// Generate random id
|
||||
let newUuId = `${uuid() + uuid()}` |
||||
let id = newNodeInfo.id.length > 8 ? newNodeInfo.id.substr(0, 7) : newNodeInfo.id |
||||
let name = newNodeInfo.name.length > 8 ? newNodeInfo.name.substr(0, 7) : newNodeInfo.name |
||||
|
||||
// new id
|
||||
let newId = `${id || ''}-${newUuId}` |
||||
// new name
|
||||
let newName = `${name || ''}-${newUuId}` |
||||
// coordinate x
|
||||
let newX = newNodePors.x + 100 |
||||
// coordinate y
|
||||
let newY = newNodePors.y + 40 |
||||
|
||||
// Generate template node
|
||||
$('#canvas').append(rtTasksTpl({ |
||||
id: newId, |
||||
name: newName, |
||||
x: newX, |
||||
y: newY, |
||||
isAttachment: this.config.isAttachment, |
||||
taskType: newNodeInfo.type |
||||
})) |
||||
|
||||
// Get the generated node
|
||||
let thisDom = jsPlumb.getSelector('.statemachine-demo .w') |
||||
|
||||
// Copy node information
|
||||
newNodeInfo = Object.assign(newNodeInfo, { |
||||
id: newId, |
||||
name: newName |
||||
}) |
||||
|
||||
// Add new node
|
||||
store.commit('dag/addTasks', newNodeInfo) |
||||
// Add node location information
|
||||
store.commit('dag/setLocations', { |
||||
[newId]: { |
||||
name: newName, |
||||
targetarr: '', |
||||
x: newX, |
||||
y: newY |
||||
} |
||||
}) |
||||
|
||||
// Generating a connection node
|
||||
this.JspInstance.batch(() => { |
||||
this.initNode(thisDom[thisDom.length - 1]) |
||||
// Add events to nodes
|
||||
this.tasksEvent(newId) |
||||
}) |
||||
} |
||||
/** |
||||
* toolbarEvent |
||||
* @param {Screen} |
||||
*/ |
||||
JSP.prototype.handleEventScreen = function ({ item, is }) { |
||||
let screenOpen = true |
||||
if (is) { |
||||
item.icon = '' |
||||
screenOpen = true |
||||
} else { |
||||
item.icon = '' |
||||
screenOpen = false |
||||
} |
||||
let $mainLayoutModel = $('.main-layout-model') |
||||
if (screenOpen) { |
||||
$mainLayoutModel.addClass('dag-screen') |
||||
} else { |
||||
$mainLayoutModel.removeClass('dag-screen') |
||||
} |
||||
} |
||||
/** |
||||
* save task |
||||
* @param tasks |
||||
* @param locations |
||||
* @param connects |
||||
*/ |
||||
JSP.prototype.saveStore = function () { |
||||
return new Promise((resolve, reject) => { |
||||
let connects = [] |
||||
let locations = {} |
||||
let tasks = [] |
||||
|
||||
let is = (id) => { |
||||
return !!_.filter(tasksAll(), v => v.id === id).length |
||||
} |
||||
|
||||
// task
|
||||
_.map(_.cloneDeep(store.state.dag.tasks), v => { |
||||
if (is(v.id)) { |
||||
let preTasks = [] |
||||
let id = $(`#${v.id}`) |
||||
let tar = id.attr('data-targetarr') |
||||
let idDep = tar ? id.attr('data-targetarr').split(',') : [] |
||||
if (idDep.length) { |
||||
_.map(idDep, v1 => { |
||||
preTasks.push($(`#${v1}`).find('.name-p').text()) |
||||
}) |
||||
} |
||||
|
||||
let tasksParam = _.assign(v, { |
||||
preTasks: preTasks |
||||
}) |
||||
|
||||
// Sub-workflow has no retries and interval
|
||||
if (v.type === 'SUB_PROCESS') { |
||||
tasksParam = _.omit(tasksParam, ['maxRetryTimes', 'retryInterval']) |
||||
} |
||||
|
||||
tasks.push(tasksParam) |
||||
} |
||||
}) |
||||
|
||||
_.map(this.JspInstance.getConnections(), v => { |
||||
connects.push({ |
||||
'endPointSourceId': v.sourceId, |
||||
'endPointTargetId': v.targetId |
||||
}) |
||||
}) |
||||
|
||||
_.map(tasksAll(), v => { |
||||
locations[v.id] = { |
||||
name: v.name, |
||||
targetarr: v.targetarr, |
||||
x: v.x, |
||||
y: v.y |
||||
} |
||||
}) |
||||
|
||||
// Storage node
|
||||
store.commit('dag/setTasks', tasks) |
||||
// Store coordinate information
|
||||
store.commit('dag/setLocations', locations) |
||||
// Storage line dependence
|
||||
store.commit('dag/setConnects', connects) |
||||
|
||||
resolve({ |
||||
connects: connects, |
||||
tasks: tasks, |
||||
locations: locations |
||||
}) |
||||
}) |
||||
} |
||||
/** |
||||
* Event processing |
||||
*/ |
||||
JSP.prototype.handleEvent = function () { |
||||
this.JspInstance.bind('beforeDrop', function (info) { |
||||
let sourceId = info['sourceId']// 出
|
||||
let targetId = info['targetId']// 入
|
||||
|
||||
/** |
||||
* Recursive search for nodes |
||||
*/ |
||||
let recursiveVal |
||||
const recursiveTargetarr = (arr, targetId) => { |
||||
for (var i in arr) { |
||||
if (arr[i] === targetId) { |
||||
recursiveVal = targetId |
||||
} else { |
||||
let recTargetarrArr = rtTargetarrArr(arr[i]) |
||||
if (recTargetarrArr.length) { |
||||
recursiveTargetarr(recTargetarrArr, targetId) |
||||
} else { |
||||
return recursiveTargetarr(targetId) |
||||
} |
||||
} |
||||
} |
||||
return recursiveVal |
||||
} |
||||
|
||||
// Connection to connected nodes is not allowed
|
||||
if (_.findIndex(rtTargetarrArr(targetId), v => v === sourceId) !== -1) { |
||||
return false |
||||
} |
||||
|
||||
// Recursive form to find if the target Targetarr has a sourceId
|
||||
if (recursiveTargetarr(rtTargetarrArr(sourceId), targetId)) { |
||||
// setRecursiveVal(null)
|
||||
return false |
||||
} |
||||
|
||||
// Storage node dependency information
|
||||
saveTargetarr(sourceId, targetId) |
||||
|
||||
// Monitor whether to edit DAG
|
||||
store.commit('dag/setIsEditDag', true) |
||||
|
||||
return true |
||||
}) |
||||
} |
||||
/** |
||||
* Backfill data processing |
||||
*/ |
||||
JSP.prototype.jspBackfill = function ({ connects, locations, largeJson }) { |
||||
// Backfill nodes
|
||||
this.jsonHandle({ |
||||
largeJson: largeJson, |
||||
locations: locations |
||||
}) |
||||
|
||||
let wNodes = jsPlumb.getSelector('.statemachine-demo .w') |
||||
|
||||
// Backfill line
|
||||
this.JspInstance.batch(() => { |
||||
for (let i = 0; i < wNodes.length; i++) { |
||||
this.initNode(wNodes[i]) |
||||
} |
||||
_.map(connects, v => { |
||||
let sourceId = v.endPointSourceId.split('-') |
||||
let targetId = v.endPointTargetId.split('-') |
||||
if (sourceId.length === 4 && targetId.length === 4) { |
||||
sourceId = `${sourceId[0]}-${sourceId[1]}-${sourceId[2]}` |
||||
targetId = `${targetId[0]}-${targetId[1]}-${targetId[2]}` |
||||
} else { |
||||
sourceId = v.endPointSourceId |
||||
targetId = v.endPointTargetId |
||||
} |
||||
|
||||
this.JspInstance.connect({ |
||||
source: sourceId, |
||||
target: targetId, |
||||
type: 'basic', |
||||
paintStyle: { strokeWidth: 2, stroke: '#555' } |
||||
}) |
||||
}) |
||||
}) |
||||
|
||||
jsPlumb.fire('jsPlumbDemoLoaded', this.JspInstance) |
||||
|
||||
// Connection monitoring
|
||||
this.handleEvent() |
||||
|
||||
// Drag and drop new nodes
|
||||
this.draggable() |
||||
} |
||||
|
||||
export default new JSP() |
@ -0,0 +1,131 @@
|
||||
/* |
||||
* Licensed to the Apache Software Foundation (ASF) under one or more |
||||
* contributor license agreements. See the NOTICE file distributed with |
||||
* this work for additional information regarding copyright ownership. |
||||
* The ASF licenses this file to You under the Apache License, Version 2.0 |
||||
* (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
import _ from 'lodash' |
||||
import $ from 'jquery' |
||||
import store from '@/conf/home/store' |
||||
|
||||
/** |
||||
* 节点,转数组 |
||||
*/ |
||||
const rtTargetarrArr = (id) => { |
||||
let a = $(`#${id}`).attr('data-targetarr') |
||||
return a ? a.split(',') : [] |
||||
} |
||||
|
||||
/** |
||||
* 存储节点id到targetarr |
||||
*/ |
||||
const saveTargetarr = (valId, domId) => { |
||||
let $target = $(`#${domId}`) |
||||
let targetStr = $target.attr('data-targetarr') ? $target.attr('data-targetarr') + `,${valId}` : `${valId}` |
||||
$target.attr('data-targetarr', targetStr) |
||||
} |
||||
|
||||
/** |
||||
* 返回节点html |
||||
*/ |
||||
const rtTasksTpl = ({ id, name, x, y, targetarr, isAttachment, taskType }) => { |
||||
let tpl = `` |
||||
tpl += `<div class="w jtk-draggable jtk-droppable jtk-endpoint-anchor jtk-connected ${isAttachment ? 'jtk-ep' : ''}" data-targetarr="${targetarr || ''}" data-tasks-type="${taskType}" id="${id}" style="left: ${x}px; top: ${y}px;">` |
||||
tpl += `<div>` |
||||
tpl += `<div class="state-p"></div>` |
||||
tpl += `<div class="icos icos-${taskType}"></div>` |
||||
tpl += `<span class="name-p">${name}</span>` |
||||
tpl += `</div>` |
||||
tpl += `<div class="ep"></div>` |
||||
tpl += `</div>` |
||||
return tpl |
||||
} |
||||
|
||||
/** |
||||
* 获取所有tasks节点 |
||||
*/ |
||||
const tasksAll = () => { |
||||
let a = [] |
||||
$('#canvas .w').each(function (idx, elem) { |
||||
let e = $(elem) |
||||
a.push({ |
||||
id: e.attr('id'), |
||||
name: e.find('.name-p').text(), |
||||
targetarr: e.attr('data-targetarr') || '', |
||||
x: parseInt(e.css('left'), 10), |
||||
y: parseInt(e.css('top'), 10) |
||||
}) |
||||
}) |
||||
return a |
||||
} |
||||
|
||||
/** |
||||
* 判断 name 是否在当前的dag图中 |
||||
* rely dom / backfill dom元素 回填 |
||||
*/ |
||||
const isNameExDag = (name, rely) => { |
||||
if (rely === 'dom') { |
||||
return _.findIndex(tasksAll(), v => v.name === name) !== -1 |
||||
} else { |
||||
return _.findIndex(store.state.dag.tasks, v => v.name === name) !== -1 |
||||
} |
||||
} |
||||
|
||||
/** |
||||
* 更改svg线条颜色 |
||||
*/ |
||||
const setSvgColor = (e, color) => { |
||||
// 遍历 清除所有颜色
|
||||
$('.jtk-connector').each((i, o) => { |
||||
_.map($(o)[0].childNodes, v => { |
||||
$(v).attr('fill', '#555').attr('stroke', '#555').attr('stroke-width', 2) |
||||
}) |
||||
}) |
||||
|
||||
// 给选择的添加颜色
|
||||
_.map($(e.canvas)[0].childNodes, (v, i) => { |
||||
$(v).attr('fill', color).attr('stroke', color) |
||||
if ($(v).attr('class')) { |
||||
$(v).attr('stroke-width', 2) |
||||
} |
||||
}) |
||||
} |
||||
|
||||
/** |
||||
* 获取所有节点id |
||||
*/ |
||||
const allNodesId = () => { |
||||
let idArr = [] |
||||
$('.w').each((i, o) => { |
||||
let $obj = $(o) |
||||
let $span = $obj.find('.name-p').text() |
||||
if ($span) { |
||||
idArr.push({ |
||||
id: $obj.attr('id'), |
||||
name: $span |
||||
}) |
||||
} |
||||
}) |
||||
return idArr |
||||
} |
||||
|
||||
export { |
||||
rtTargetarrArr, |
||||
saveTargetarr, |
||||
rtTasksTpl, |
||||
tasksAll, |
||||
isNameExDag, |
||||
setSvgColor, |
||||
allNodesId |
||||
} |
@ -0,0 +1,187 @@
|
||||
<template> |
||||
<div class="udp-model"> |
||||
<div class="scrollbar contpi-boxt"> |
||||
<div class="title"> |
||||
<span>{{$t('设置DAG图名称')}}</span> |
||||
</div> |
||||
<div> |
||||
<x-input |
||||
type="text" |
||||
v-model="name" |
||||
:disabled="router.history.current.name === 'projects-instance-details'" |
||||
:placeholder="$t('请输入name(必填)')"> |
||||
</x-input> |
||||
</div> |
||||
<template v-if="router.history.current.name !== 'projects-instance-details'"> |
||||
<div style="padding-top: 12px;"> |
||||
<x-input |
||||
type="textarea" |
||||
v-model="desc" |
||||
:autosize="{minRows:2}" |
||||
:placeholder="$t('请输入desc(选填)')" |
||||
autocomplete="off"> |
||||
</x-input> |
||||
</div> |
||||
</template> |
||||
<div class="title" style="padding-top: 6px;"> |
||||
<span>{{$t('设置全局')}}</span> |
||||
</div> |
||||
<div class="content"> |
||||
<div> |
||||
<m-local-params |
||||
ref="refLocalParams" |
||||
@on-local-params="_onLocalParams" |
||||
:udp-list="udpList" |
||||
:hide="false"> |
||||
</m-local-params> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<div class="bottom"> |
||||
<div class="submit"> |
||||
<template v-if="router.history.current.name === 'projects-instance-details'"> |
||||
<div class="lint-pt"> |
||||
<x-checkbox v-model="syncDefine">{{$t('是否更新流程定义')}}</x-checkbox> |
||||
</div> |
||||
</template> |
||||
<x-button type="text" @click="close()"> {{$t('取消')}} </x-button> |
||||
<x-button type="primary" shape="circle" @click="ok()" v-ps="['GENERAL_USER']" >{{$t('添加')}}</x-button> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
<script> |
||||
import _ from 'lodash' |
||||
import i18n from '@/module/i18n' |
||||
import mLocalParams from '../formModel/tasks/_source/localParams' |
||||
import disabledState from '@/module/mixin/disabledState' |
||||
import Affirm from '../jumpAffirm' |
||||
|
||||
export default { |
||||
name: 'udp', |
||||
data () { |
||||
return { |
||||
// dag name |
||||
name: '', |
||||
// dag desc |
||||
desc: '', |
||||
// Global custom parameters |
||||
udpList: [], |
||||
// Whether to update the process definition |
||||
syncDefine: true |
||||
} |
||||
}, |
||||
mixins: [disabledState], |
||||
props: { |
||||
}, |
||||
methods: { |
||||
/** |
||||
* udp data |
||||
*/ |
||||
_onLocalParams (a) { |
||||
this.udpList = a |
||||
}, |
||||
/** |
||||
* submit |
||||
*/ |
||||
ok () { |
||||
if (!this.name) { |
||||
this.$message.warning(`${i18n.$t('DAG图名称不能为空')}`) |
||||
return |
||||
} |
||||
|
||||
let _verif = () => { |
||||
// verification udf |
||||
if (!this.$refs.refLocalParams._verifProp()) { |
||||
return |
||||
} |
||||
// Storage global globalParams |
||||
this.store.commit('dag/setGlobalParams', _.cloneDeep(this.udpList)) |
||||
this.store.commit('dag/setName', _.cloneDeep(this.name)) |
||||
this.store.commit('dag/setDesc', _.cloneDeep(this.desc)) |
||||
this.store.commit('dag/setSyncDefine', this.syncDefine) |
||||
Affirm.setIsPop(false) |
||||
this.$emit('onUdp') |
||||
} |
||||
|
||||
// Edit => direct storage |
||||
if (this.store.state.dag.name) { |
||||
_verif() |
||||
} else { |
||||
// New First verify that the name exists |
||||
this.store.dispatch('dag/verifDAGName', this.name).then(res => { |
||||
_verif() |
||||
}).catch(e => { |
||||
this.$message.error(e.msg || '') |
||||
}) |
||||
} |
||||
}, |
||||
/** |
||||
* Close the popup |
||||
*/ |
||||
close () { |
||||
this.$emit('close') |
||||
} |
||||
}, |
||||
watch: { |
||||
}, |
||||
created () { |
||||
this.udpList = this.store.state.dag.globalParams |
||||
this.name = this.store.state.dag.name |
||||
this.desc = this.store.state.dag.desc |
||||
this.syncDefine = this.store.state.dag.syncDefine |
||||
}, |
||||
mounted () {}, |
||||
components: { mLocalParams } |
||||
} |
||||
</script> |
||||
|
||||
<style lang="scss" rel="stylesheet/scss"> |
||||
.udp-model { |
||||
width: 616px; |
||||
min-height: 326px; |
||||
background: #fff; |
||||
border-radius: 3px; |
||||
padding:20px 0 ; |
||||
position: relative; |
||||
.contpi-boxt { |
||||
max-height: 600px; |
||||
overflow-y: scroll; |
||||
padding:0 20px; |
||||
} |
||||
.title { |
||||
line-height: 36px; |
||||
padding-bottom: 10px; |
||||
span { |
||||
font-size: 16px; |
||||
color: #333; |
||||
} |
||||
} |
||||
.bottom{ |
||||
position: absolute; |
||||
bottom: 0; |
||||
left: 0; |
||||
width: 100%; |
||||
text-align: right; |
||||
height: 56px; |
||||
line-height: 56px; |
||||
border-top: 1px solid #DCDEDC; |
||||
background: #fff; |
||||
.submit { |
||||
padding-right: 20px; |
||||
margin-top: -4px; |
||||
} |
||||
.lint-pt { |
||||
position: absolute; |
||||
left: 20px; |
||||
top: -2px; |
||||
>label { |
||||
font-weight: normal; |
||||
} |
||||
} |
||||
} |
||||
.content { |
||||
padding-bottom: 50px; |
||||
} |
||||
} |
||||
</style> |
@ -0,0 +1,24 @@
|
||||
<template> |
||||
<div class="assist-dag-model"> |
||||
<template v-if="isView"> |
||||
<m-variables-view></m-variables-view> |
||||
</template> |
||||
</div> |
||||
</template> |
||||
<script> |
||||
import mVariablesView from './variablesView' |
||||
export default { |
||||
name: 'assist-dag-index', |
||||
data () { |
||||
return { |
||||
isView: false |
||||
} |
||||
}, |
||||
methods: { |
||||
_toggleView () { |
||||
this.isView = !this.isView |
||||
} |
||||
}, |
||||
components: { mVariablesView } |
||||
} |
||||
</script> |
@ -0,0 +1,141 @@
|
||||
<template> |
||||
<div class="variable-model"> |
||||
<div class="list"> |
||||
<div class="name"><i class="fa fa-code"></i><b style="padding-top: 3px;display: inline-block">{{$t('全局参数')}}</b></div> |
||||
<div class="var-cont"> |
||||
<x-button size="xsmall" type="ghost" v-for="(item,$index) in list.globalParams" @click="_copy('gbudp-' + $index)" :data-clipboard-text="item.prop + ' = ' +item.value" :class="'gbudp-' + $index"><b style="color: #2A455B;">{{item.prop}}</b> = {{item.value}}</x-button> |
||||
</div> |
||||
</div> |
||||
<div class="list" style="height: 30px;"> |
||||
<div class="name"><i class="fa fa-code"></i><b style="padding-top: 3px;display: inline-block">{{$t('局部参数')}}</b></div> |
||||
<div class="var-cont"> |
||||
|
||||
</div> |
||||
</div> |
||||
<div class="list list-t" v-for="(item,key,$index) in list.localParams"> |
||||
<div class="task-name">Task({{$index}}):{{key}}</div> |
||||
<div class="var-cont" v-if="item.length"> |
||||
<template v-for="(el,index) in item"> |
||||
<x-button size="xsmall" type="ghost" @click="_copy('copy-part-' + index)" :data-clipboard-text="_rtClipboard(el)" :class="'copy-part-' + index"> |
||||
<span v-for="(e,k,i) in el"><b style="color: #2A455B;">{{k}}</b> = {{e}} </span> |
||||
</x-button> |
||||
</template> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
<script> |
||||
import i18n from '@/module/i18n' |
||||
import { mapActions } from 'vuex' |
||||
import Clipboard from 'clipboard' |
||||
|
||||
export default { |
||||
name: 'variables-view', |
||||
data () { |
||||
return { |
||||
list: {} |
||||
} |
||||
}, |
||||
props: {}, |
||||
methods: { |
||||
...mapActions('dag', ['getViewvariables']), |
||||
/** |
||||
* Get variable data |
||||
*/ |
||||
_getViewvariables () { |
||||
this.getViewvariables({ |
||||
processInstanceId: this.$route.params.id |
||||
}).then(res => { |
||||
this.list = res.data |
||||
}).catch(e => { |
||||
this.$message.error(e.msg || '') |
||||
}) |
||||
}, |
||||
/** |
||||
* Click to copy |
||||
*/ |
||||
_copy (className) { |
||||
let clipboard = new Clipboard(`.${className}`) |
||||
clipboard.on('success', e => { |
||||
this.$message.success(`${i18n.$t('复制成功')}`) |
||||
// Free memory |
||||
clipboard.destroy() |
||||
}) |
||||
clipboard.on('error', e => { |
||||
// Copy is not supported |
||||
this.$message.warning(`${i18n.$t('该浏览器不支持自动复制')}`) |
||||
// Free memory |
||||
clipboard.destroy() |
||||
}) |
||||
}, |
||||
/** |
||||
* Copyed text processing |
||||
*/ |
||||
_rtClipboard (el) { |
||||
let arr = [] |
||||
Object.keys(el).forEach((key) => { |
||||
arr.push(`${key}=${el[key]}`) |
||||
}) |
||||
return arr.join(' ') |
||||
} |
||||
}, |
||||
watch: {}, |
||||
created () { |
||||
this._getViewvariables() |
||||
}, |
||||
mounted () { |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style lang="scss" rel="stylesheet/scss"> |
||||
.variable-model { |
||||
padding: 10px; |
||||
padding-bottom: 5px; |
||||
.list { |
||||
position: relative; |
||||
min-height: 30px; |
||||
.var-cont { |
||||
padding-left: 19px; |
||||
>button { |
||||
margin-bottom: 6px; |
||||
margin-right: 6px; |
||||
} |
||||
} |
||||
.name { |
||||
padding-bottom: 10px; |
||||
font-size: 16px; |
||||
>.fa { |
||||
font-size: 16px; |
||||
color: #0097e0; |
||||
margin-right: 4px; |
||||
vertical-align: middle; |
||||
font-weight: bold; |
||||
} |
||||
>b { |
||||
vertical-align: middle; |
||||
} |
||||
} |
||||
>span{ |
||||
height: 28px; |
||||
line-height: 28px; |
||||
padding: 0 8px; |
||||
background: #2d8cf0; |
||||
display: inline-block; |
||||
margin-bottom: 8px; |
||||
color: #fff; |
||||
} |
||||
} |
||||
.list-t { |
||||
padding-left: 0px; |
||||
margin-bottom: 10px; |
||||
.task-name { |
||||
padding-left: 19px; |
||||
padding-bottom: 8px; |
||||
font-size: 12px; |
||||
font-weight: bold; |
||||
color: #0097e0; |
||||
} |
||||
} |
||||
} |
||||
</style> |
@ -0,0 +1,82 @@
|
||||
<template> |
||||
<div class="home-main index-model"> |
||||
<m-dag v-if="!isLoading" :type="'definition'" :release-state="releaseState"></m-dag> |
||||
<m-spin :is-spin="isLoading" ></m-spin> |
||||
</div> |
||||
</template> |
||||
<script> |
||||
import mDag from './_source/dag.vue' |
||||
import mSpin from '@/module/components/spin/spin' |
||||
import Affirm from './_source/jumpAffirm' |
||||
import disabledState from '@/module/mixin/disabledState' |
||||
import { mapActions, mapMutations } from 'vuex' |
||||
|
||||
export default { |
||||
name: 'definition-details', |
||||
data () { |
||||
return { |
||||
// loading |
||||
isLoading: true, |
||||
// state |
||||
releaseState: '' |
||||
} |
||||
}, |
||||
mixins: [disabledState], |
||||
props: {}, |
||||
methods: { |
||||
...mapMutations('dag', ['resetParams', 'setIsDetails']), |
||||
...mapActions('dag', ['getProcessList', 'getResourcesList', 'getProcessDetails']), |
||||
/** |
||||
* init |
||||
*/ |
||||
init () { |
||||
this.isLoading = true |
||||
// Initialization parameters |
||||
this.resetParams() |
||||
// Promise Get node needs data |
||||
Promise.all([ |
||||
// Node details |
||||
this.getProcessDetails(this.$route.params.id), |
||||
// get process definition |
||||
this.getProcessList(), |
||||
// get resource |
||||
this.getResourcesList() |
||||
]).then((data) => { |
||||
let item = data[0] |
||||
this.setIsDetails(item.releaseState === 'ONLINE') |
||||
this.releaseState = item.releaseState |
||||
this.isLoading = false |
||||
// Whether to pop up the box? |
||||
Affirm.init(this.$root) |
||||
}).catch(() => { |
||||
this.isLoading = false |
||||
}) |
||||
}, |
||||
/** |
||||
* Redraw (refresh operation) |
||||
*/ |
||||
_reset () { |
||||
this.getProcessDetails(this.$route.params.id).then(res => { |
||||
let item = res |
||||
this.setIsDetails(item.releaseState === 'ONLINE') |
||||
this.releaseState = item.releaseState |
||||
}) |
||||
} |
||||
}, |
||||
watch: { |
||||
// Listening for routing changes |
||||
'$route': { |
||||
deep: true, |
||||
handler () { |
||||
this.init() |
||||
} |
||||
} |
||||
}, |
||||
created () { |
||||
this.init() |
||||
}, |
||||
mounted () { |
||||
}, |
||||
components: { mDag, mSpin } |
||||
} |
||||
</script> |
After Width: | Height: | Size: 5.0 KiB |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 3.6 KiB |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 3.5 KiB |
After Width: | Height: | Size: 3.2 KiB |
After Width: | Height: | Size: 4.1 KiB |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 2.3 KiB |
@ -0,0 +1,65 @@
|
||||
<template> |
||||
<div class="home-main index-model"> |
||||
<m-dag v-if="!isLoading"></m-dag> |
||||
<m-spin :is-spin="isLoading"></m-spin> |
||||
</div> |
||||
</template> |
||||
<script> |
||||
import mDag from './_source/dag.vue' |
||||
import { mapActions, mapMutations } from 'vuex' |
||||
import mSpin from '@/module/components/spin/spin' |
||||
import Affirm from './_source/jumpAffirm' |
||||
import disabledState from '@/module/mixin/disabledState' |
||||
|
||||
export default { |
||||
name: 'create-index', |
||||
data () { |
||||
return { |
||||
// loading |
||||
isLoading: true |
||||
} |
||||
}, |
||||
// mixins |
||||
mixins: [disabledState], |
||||
props: {}, |
||||
methods: { |
||||
...mapMutations('dag', ['resetParams']), |
||||
...mapActions('dag', ['getProcessList', 'getResourcesList']), |
||||
/** |
||||
* init |
||||
*/ |
||||
init () { |
||||
this.isLoading = true |
||||
// Initialization parameters |
||||
this.resetParams() |
||||
// Promise Get node needs data |
||||
Promise.all([ |
||||
// get process definition |
||||
this.getProcessList(), |
||||
// get resource |
||||
this.getResourcesList() |
||||
]).then((data) => { |
||||
this.isLoading = false |
||||
// Whether to pop up the box? |
||||
Affirm.init(this.$root) |
||||
}).catch(() => { |
||||
this.isLoading = false |
||||
}) |
||||
} |
||||
}, |
||||
watch: { |
||||
'$route': { |
||||
deep: true, |
||||
handler () { |
||||
this.init() |
||||
} |
||||
} |
||||
}, |
||||
created () { |
||||
this.init() |
||||
}, |
||||
mounted () { |
||||
}, |
||||
components: { mDag, mSpin } |
||||
} |
||||
</script> |
@ -0,0 +1,92 @@
|
||||
<template> |
||||
<div class="home-main index-model"> |
||||
<m-variable></m-variable> |
||||
<m-dag v-if="!isLoading" :type="'instance'"></m-dag> |
||||
<m-spin :is-spin="isLoading"></m-spin> |
||||
</div> |
||||
</template> |
||||
<script> |
||||
import mDag from './_source/dag.vue' |
||||
import { mapActions, mapMutations } from 'vuex' |
||||
import mSpin from '@/module/components/spin/spin' |
||||
import mVariable from './_source/variable' |
||||
import Affirm from './_source/jumpAffirm' |
||||
import disabledState from '@/module/mixin/disabledState' |
||||
|
||||
export default { |
||||
name: 'instance-details', |
||||
data () { |
||||
return { |
||||
// loading |
||||
isLoading: true |
||||
} |
||||
}, |
||||
mixins: [disabledState], |
||||
props: {}, |
||||
methods: { |
||||
...mapMutations('dag', ['setIsDetails', 'resetParams']), |
||||
...mapActions('dag', ['getProcessList', 'getResourcesList', 'getInstancedetail']), |
||||
/** |
||||
* init |
||||
*/ |
||||
init () { |
||||
this.isLoading = true |
||||
// Initialization parameters |
||||
this.resetParams() |
||||
// Promise Get node needs data |
||||
Promise.all([ |
||||
// Process instance details |
||||
this.getInstancedetail(this.$route.params.id), |
||||
// get process definition |
||||
this.getProcessList(), |
||||
// get resources |
||||
this.getResourcesList() |
||||
]).then((data) => { |
||||
let item = data[0] |
||||
let flag = false |
||||
if (item.state !== 'WAITTING_THREAD' && item.state !== 'SUCCESS' && item.state !== 'PAUSE' && item.state !== 'FAILURE' && item.state !== 'STOP') { |
||||
flag = true |
||||
} else { |
||||
flag = false |
||||
} |
||||
this.setIsDetails(flag) |
||||
this.isLoading = false |
||||
|
||||
// Whether to pop up the box? |
||||
Affirm.init(this.$root) |
||||
}).catch(() => { |
||||
this.isLoading = false |
||||
}) |
||||
}, |
||||
/** |
||||
* Redraw (refresh operation) |
||||
*/ |
||||
_reset () { |
||||
this.getInstancedetail(this.$route.params.id).then(res => { |
||||
let item = res |
||||
let flag = false |
||||
if (item.state !== 'WAITTING_THREAD' && item.state !== 'SUCCESS' && item.state !== 'PAUSE' && item.state !== 'FAILURE' && item.state !== 'STOP') { |
||||
flag = true |
||||
} else { |
||||
flag = false |
||||
} |
||||
this.setIsDetails(flag) |
||||
}) |
||||
} |
||||
}, |
||||
watch: { |
||||
'$route': { |
||||
deep: true, |
||||
handler () { |
||||
this.init() |
||||
} |
||||
} |
||||
}, |
||||
created () { |
||||
this.init() |
||||
}, |
||||
mounted () { |
||||
}, |
||||
components: { mDag, mSpin, mVariable } |
||||
} |
||||
</script> |
@ -0,0 +1,8 @@
|
||||
<template> |
||||
<router-view></router-view> |
||||
</template> |
||||
<script> |
||||
export default { |
||||
name: 'datasource-index' |
||||
} |
||||
</script> |
@ -0,0 +1,337 @@
|
||||
<template> |
||||
<div class="datasource-popup-model"> |
||||
<div class="top-p"> |
||||
<span>{{item ? `${$t('编辑')}` : `${$t('创建')}`}}{{`${$t('数据源')}`}}</span> |
||||
</div> |
||||
<div class="content-p"> |
||||
<div class="create-datasource-model"> |
||||
<m-list-box-f> |
||||
<template slot="name"><b>*</b>{{$t('数据源')}}</template> |
||||
<template slot="content"> |
||||
<x-radio-group v-model="type" size="small"> |
||||
<x-radio :label="'MYSQL'">MYSQL</x-radio> |
||||
<x-radio :label="'POSTGRESQL'">POSTGRESQL</x-radio> |
||||
<x-radio :label="'HIVE'">HVIE</x-radio> |
||||
<x-radio :label="'SPARK'">SPARK</x-radio> |
||||
</x-radio-group> |
||||
</template> |
||||
</m-list-box-f> |
||||
<m-list-box-f> |
||||
<template slot="name"><b>*</b>{{$t('数据源名称')}}</template> |
||||
<template slot="content"> |
||||
<x-input |
||||
type="input" |
||||
v-model="name" |
||||
:placeholder="$t('请输入数据源名称')" |
||||
autocomplete="off"> |
||||
</x-input> |
||||
</template> |
||||
</m-list-box-f> |
||||
<m-list-box-f> |
||||
<template slot="name">{{$t('描述')}}</template> |
||||
<template slot="content"> |
||||
<x-input |
||||
type="textarea" |
||||
v-model="note" |
||||
:placeholder="$t('请输入描述')" |
||||
autocomplete="off"> |
||||
</x-input> |
||||
</template> |
||||
</m-list-box-f> |
||||
<m-list-box-f> |
||||
<template slot="name"><b>*</b>{{$t('IP主机名')}}</template> |
||||
<template slot="content"> |
||||
<x-input |
||||
type="input" |
||||
v-model="host" |
||||
:placeholder="$t('请输入IP主机名')" |
||||
autocomplete="off"> |
||||
</x-input> |
||||
</template> |
||||
</m-list-box-f> |
||||
<m-list-box-f> |
||||
<template slot="name"><b>*</b>{{$t('端口')}}</template> |
||||
<template slot="content"> |
||||
<x-input |
||||
type="input" |
||||
v-model="port" |
||||
:placeholder="$t('请输入端口')" |
||||
autocomplete="off"> |
||||
</x-input> |
||||
</template> |
||||
</m-list-box-f> |
||||
<m-list-box-f> |
||||
<template slot="name"><b>*</b>{{$t('用户名')}}</template> |
||||
<template slot="content"> |
||||
<x-input |
||||
type="input" |
||||
v-model="userName" |
||||
:placeholder="$t('请输入用户名')" |
||||
autocomplete="off"> |
||||
</x-input> |
||||
</template> |
||||
</m-list-box-f> |
||||
<m-list-box-f> |
||||
<template slot="name">{{$t('密码')}}</template> |
||||
<template slot="content"> |
||||
<x-input |
||||
type="password" |
||||
v-model="password" |
||||
:placeholder="$t('请输入密码')" |
||||
autocomplete="off"> |
||||
</x-input> |
||||
</template> |
||||
</m-list-box-f> |
||||
<m-list-box-f> |
||||
<template slot="name"><b>*</b>{{$t('数据库名')}}</template> |
||||
<template slot="content"> |
||||
<x-input |
||||
type="input" |
||||
v-model="database" |
||||
:placeholder="$t('请输入数据库名')" |
||||
autocomplete="off"> |
||||
</x-input> |
||||
</template> |
||||
</m-list-box-f> |
||||
<m-list-box-f> |
||||
<template slot="name">{{$t('jdbc连接参数')}}</template> |
||||
<template slot="content"> |
||||
<x-input |
||||
type="textarea" |
||||
v-model="other" |
||||
:autosize="{minRows:2}" |
||||
:placeholder="_rtOtherPlaceholder()" |
||||
autocomplete="off"> |
||||
</x-input> |
||||
</template> |
||||
</m-list-box-f> |
||||
</div> |
||||
</div> |
||||
<div class="bottom-p"> |
||||
<x-button type="text" @click="_close()"> {{$t('取消')}} </x-button> |
||||
<x-button type="success" shape="circle" @click="_testConnect()" :loading="testLoading">{{testLoading ? 'Loading...' : $t('测试连接')}}</x-button> |
||||
<x-button type="primary" shape="circle" :loading="spinnerLoading" @click="_ok()">{{spinnerLoading ? 'Loading...' :item ? `${$t('确认编辑')}` : `${$t('确认提交')}`}} </x-button> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
<script> |
||||
import i18n from '@/module/i18n' |
||||
import store from '@/conf/home/store' |
||||
import { isJson } from '@/module/util/util' |
||||
import mPopup from '@/module/components/popup/popup' |
||||
import mListBoxF from '@/module/components/listBoxF/listBoxF' |
||||
|
||||
export default { |
||||
name: 'create-datasource', |
||||
data () { |
||||
return { |
||||
store, |
||||
// btn loading |
||||
spinnerLoading: false, |
||||
// Data source type |
||||
type: 'MYSQL', |
||||
// name |
||||
name: '', |
||||
// desc |
||||
note: '', |
||||
// host |
||||
host: '', |
||||
// port |
||||
port: '', |
||||
// data storage name |
||||
database: '', |
||||
// database username |
||||
userName: '', |
||||
// Database password |
||||
password: '', |
||||
// Jdbc connection parameter |
||||
other: '', |
||||
// btn test loading |
||||
testLoading: false |
||||
} |
||||
}, |
||||
props: { |
||||
item: Object |
||||
}, |
||||
methods: { |
||||
_rtOtherPlaceholder () { |
||||
return `${i18n.$t('请输入格式为')} {"key1":"value1","key2":"value2"...} ${i18n.$t('连接参数')}` |
||||
}, |
||||
/** |
||||
* submit |
||||
*/ |
||||
_ok () { |
||||
if (this._verification()) { |
||||
this._verifName().then(res => { |
||||
this._submit() |
||||
}) |
||||
} |
||||
}, |
||||
/** |
||||
* close |
||||
*/ |
||||
_close () { |
||||
this.$emit('close') |
||||
}, |
||||
/** |
||||
* return param |
||||
*/ |
||||
_rtParam () { |
||||
return { |
||||
type: this.type, |
||||
name: this.name, |
||||
note: this.note, |
||||
host: this.host, |
||||
port: this.port, |
||||
database: this.database, |
||||
userName: this.userName, |
||||
password: this.password, |
||||
other: this.other |
||||
} |
||||
}, |
||||
/** |
||||
* test connect |
||||
*/ |
||||
_testConnect () { |
||||
if (this._verification()) { |
||||
this.testLoading = true |
||||
this.store.dispatch('datasource/connectDatasources', this._rtParam()).then(res => { |
||||
setTimeout(() => { |
||||
this.$message.success(res.msg) |
||||
this.testLoading = false |
||||
}, 800) |
||||
}).catch(e => { |
||||
this.$message.error(e.msg || '') |
||||
this.testLoading = false |
||||
}) |
||||
} |
||||
}, |
||||
/** |
||||
* Verify that the data source name exists |
||||
*/ |
||||
_verifName () { |
||||
return new Promise((resolve, reject) => { |
||||
if (this.name === this.item.name) { |
||||
resolve() |
||||
return |
||||
} |
||||
this.store.dispatch('datasource/verifyName', { name: this.name }).then(res => { |
||||
resolve() |
||||
}).catch(e => { |
||||
this.$message.error(e.msg || '') |
||||
reject(e) |
||||
}) |
||||
}) |
||||
}, |
||||
/** |
||||
* verification |
||||
*/ |
||||
_verification () { |
||||
if (!this.name) { |
||||
this.$message.warning(`${i18n.$t('请输入资源名称')}`) |
||||
return false |
||||
} |
||||
if (!this.host) { |
||||
this.$message.warning(`${i18n.$t('请输入IP/主机名')}`) |
||||
return false |
||||
} |
||||
if (!this.port) { |
||||
this.$message.warning(`${i18n.$t('请输入端口')}`) |
||||
return false |
||||
} |
||||
if (!this.userName) { |
||||
this.$message.warning(`${i18n.$t('请输入用户名')}`) |
||||
return false |
||||
} |
||||
|
||||
if (!this.database) { |
||||
this.$message.warning(`${i18n.$t('请输入数据库名')}`) |
||||
return false |
||||
} |
||||
if (this.other) { |
||||
if (!isJson(this.other)) { |
||||
this.$message.warning(`${i18n.$t('jdbc连接参数不是一个正确的JSON格式')}`) |
||||
return false |
||||
} |
||||
} |
||||
|
||||
return true |
||||
}, |
||||
/** |
||||
* submit => add/update |
||||
*/ |
||||
_submit () { |
||||
this.spinnerLoading = true |
||||
let param = this._rtParam() |
||||
// edit |
||||
if (this.item) { |
||||
param.id = this.item.id |
||||
} |
||||
this.store.dispatch(`datasource/${this.item ? `updateDatasource` : `createDatasources`}`, param).then(res => { |
||||
this.$message.success(res.msg) |
||||
this.spinnerLoading = false |
||||
this.$emit('onUpdate') |
||||
}).catch(e => { |
||||
this.$message.error(e.msg || '') |
||||
this.spinnerLoading = false |
||||
}) |
||||
}, |
||||
/** |
||||
* Get modified data |
||||
*/ |
||||
_getEditDatasource () { |
||||
this.store.dispatch('datasource/getEditDatasource', { id: this.item.id }).then(res => { |
||||
this.type = res.type |
||||
this.name = res.name |
||||
this.note = res.note |
||||
this.host = res.host |
||||
this.port = res.port |
||||
this.database = res.database |
||||
this.userName = res.userName |
||||
this.password = res.password |
||||
this.other = JSON.stringify(res.other) === '{}' ? '' : JSON.stringify(res.other) |
||||
}).catch(e => { |
||||
this.$message.error(e.msg || '') |
||||
}) |
||||
} |
||||
}, |
||||
watch: {}, |
||||
created () { |
||||
// Backfill |
||||
if (this.item.id) { |
||||
this._getEditDatasource() |
||||
} |
||||
}, |
||||
mounted () { |
||||
}, |
||||
components: { mPopup, mListBoxF } |
||||
} |
||||
</script> |
||||
|
||||
<style lang="scss" rel="stylesheet/scss"> |
||||
.datasource-popup-model { |
||||
background: #fff; |
||||
border-radius: 3px; |
||||
|
||||
.top-p { |
||||
height: 70px; |
||||
line-height: 70px; |
||||
border-radius: 3px 3px 0 0; |
||||
padding: 0 20px; |
||||
>span { |
||||
font-size: 20px; |
||||
} |
||||
} |
||||
.bottom-p { |
||||
text-align: right; |
||||
height: 72px; |
||||
line-height: 72px; |
||||
border-radius: 0 0 3px 3px; |
||||
padding: 0 20px; |
||||
} |
||||
.content-p { |
||||
min-width: 500px; |
||||
min-height: 100px; |
||||
} |
||||
} |
||||
</style> |
@ -0,0 +1,160 @@
|
||||
<template> |
||||
<div class="list-model"> |
||||
<div class="table-box"> |
||||
<table class="fixed"> |
||||
<tr> |
||||
<th> |
||||
<span>{{$t('编号')}}</span> |
||||
</th> |
||||
<th> |
||||
<span>{{$t('数据源名称')}}</span> |
||||
</th> |
||||
<th width="120"> |
||||
<span>{{$t('数据源类型')}}</span> |
||||
</th> |
||||
<th width="100"> |
||||
<span>{{$t('数据源参数')}}</span> |
||||
</th> |
||||
<th> |
||||
<span>{{$t('描述')}}</span> |
||||
</th> |
||||
<th width="150"> |
||||
<span>{{$t('创建时间')}}</span> |
||||
</th> |
||||
<th width="150"> |
||||
<span>{{$t('更新时间')}}</span> |
||||
</th> |
||||
<th width="80"> |
||||
<span>{{$t('操作')}}</span> |
||||
</th> |
||||
</tr> |
||||
<tr v-for="(item, $index) in list" :key="$index"> |
||||
<td> |
||||
<span>{{parseInt(pageNo === 1 ? ($index + 1) : (($index + 1) + (pageSize * (pageNo - 1))))}}</span> |
||||
</td> |
||||
<td> |
||||
<span class="ellipsis"> |
||||
<a href="javascript:" class="links">{{item.name}}</a> |
||||
</span> |
||||
</td> |
||||
<td> |
||||
<span>{{item.type}}</span> |
||||
</td> |
||||
<td> |
||||
<m-tooltips-JSON :JSON="JSON.parse(item.connectionParams)" :id="item.id"> |
||||
<span slot="reference"> |
||||
<a href="javascript:" class="links" style="font-size: 12px;">{{$t('点击查看')}}</a> |
||||
</span> |
||||
</m-tooltips-JSON> |
||||
</td> |
||||
<td><span class="ellipsis">{{item.note}}</span></td> |
||||
<td><span>{{item.createTime | formatDate}}</span></td> |
||||
<td><span>{{item.updateTime | formatDate}}</span></td> |
||||
<td> |
||||
<x-button |
||||
type="info" |
||||
shape="circle" |
||||
size="xsmall" |
||||
data-toggle="tooltip" |
||||
v-ps="['GENERAL_USER']" |
||||
:title="$t('编辑')" |
||||
icon="iconfont icon-bianjixiugai" |
||||
@click="_edit(item)"> |
||||
</x-button> |
||||
<x-poptip |
||||
:ref="'poptip-delete-' + $index" |
||||
placement="bottom-end" |
||||
width="90"> |
||||
<p>{{$t('确定删除吗?')}}</p> |
||||
<div style="text-align: right; margin: 0;padding-top: 4px;"> |
||||
<x-button type="text" size="xsmall" shape="circle" @click="_closeDelete($index)">{{$t('取消')}}</x-button> |
||||
<x-button type="primary" size="xsmall" shape="circle" @click="_delete(item,$index)">{{$t('确定')}}</x-button> |
||||
</div> |
||||
<template slot="reference"> |
||||
<x-button |
||||
type="error" |
||||
shape="circle" |
||||
size="xsmall" |
||||
icon="iconfont icon-shanchu" |
||||
data-toggle="tooltip" |
||||
:title="$t('删除')" |
||||
v-ps="['GENERAL_USER']"> |
||||
</x-button> |
||||
</template> |
||||
</x-poptip> |
||||
</td> |
||||
</tr> |
||||
</table> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
<script> |
||||
import { mapActions } from 'vuex' |
||||
import '@/module/filter/formatDate' |
||||
import { findComponentDownward } from '@/module/util/' |
||||
import mTooltipsJSON from '@/module/components/tooltipsJSON/tooltipsJSON' |
||||
|
||||
export default { |
||||
name: 'datasource-list', |
||||
data () { |
||||
return { |
||||
// list |
||||
list: [] |
||||
} |
||||
}, |
||||
props: { |
||||
// External incoming data |
||||
datasourcesList: Array, |
||||
// current page number |
||||
pageNo: Number, |
||||
// Total number of articles |
||||
pageSize: Number |
||||
}, |
||||
methods: { |
||||
...mapActions('datasource', ['deleteDatasource']), |
||||
/** |
||||
* Close delete popup layer |
||||
*/ |
||||
_closeDelete (i) { |
||||
this.$refs[`poptip-delete-${i}`][0].doClose() |
||||
}, |
||||
/** |
||||
* Delete current line |
||||
*/ |
||||
_delete (item, i) { |
||||
this.$refs[`poptip-delete-${i}`][0].doClose() |
||||
this.deleteDatasource({ |
||||
id: item.id |
||||
}).then(res => { |
||||
this.list.splice(i, 1) |
||||
this.$message.success(res.msg) |
||||
}).catch(e => { |
||||
this.$message.error(e.msg || '') |
||||
}) |
||||
}, |
||||
/** |
||||
* edit |
||||
*/ |
||||
_edit (item) { |
||||
findComponentDownward(this.$root, 'datasource-indexP')._create(item) |
||||
} |
||||
}, |
||||
watch: { |
||||
/** |
||||
* Monitor external data changes |
||||
*/ |
||||
datasourcesList (a) { |
||||
this.list = [] |
||||
setTimeout(() => { |
||||
this.list = a |
||||
}) |
||||
} |
||||
}, |
||||
created () { |
||||
this.list = this.datasourcesList |
||||
}, |
||||
mounted () { |
||||
}, |
||||
components: { mTooltipsJSON } |
||||
} |
||||
</script> |
@ -0,0 +1,125 @@
|
||||
<template> |
||||
<m-list-construction :title="$t('数据源中心')"> |
||||
<template slot="conditions"> |
||||
<m-conditions @on-conditions="_onConditions"> |
||||
<template slot="button-group"> |
||||
<x-button type="ghost" size="small" @click="_create('')" v-ps="['GENERAL_USER']">{{$t('创建数据源')}}</x-button> |
||||
</template> |
||||
</m-conditions> |
||||
</template> |
||||
<template slot="content"> |
||||
<template v-if="datasourcesList.length"> |
||||
<m-list :datasources-list="datasourcesList" :page-no="pageNo" :page-size="pageSize"></m-list> |
||||
<div class="page-box"> |
||||
<x-page :current="pageNo" :total="total" show-elevator @on-change="_page"></x-page> |
||||
</div> |
||||
</template> |
||||
<template v-if="!datasourcesList.length"> |
||||
<m-no-data></m-no-data> |
||||
</template> |
||||
<m-spin :is-spin="isLoading" :is-left="false"> |
||||
</m-spin> |
||||
</template> |
||||
</m-list-construction> |
||||
</template> |
||||
<script> |
||||
import { mapActions } from 'vuex' |
||||
import mList from './_source/list' |
||||
import mSpin from '@/module/components/spin/spin' |
||||
import mNoData from '@/module/components/noData/noData' |
||||
import mCreateDataSource from './_source/createDataSource' |
||||
import mConditions from '@/module/components/conditions/conditions' |
||||
import mListConstruction from '@/module/components/listConstruction/listConstruction' |
||||
|
||||
export default { |
||||
name: 'datasource-indexP', |
||||
data () { |
||||
return { |
||||
// loading |
||||
isLoading: true, |
||||
// Number of pages per page |
||||
pageSize: 10, |
||||
// Number of pages |
||||
pageNo: 1, |
||||
// Total number of articles |
||||
total: 20, |
||||
// Search value |
||||
searchVal: '', |
||||
// data sources(List) |
||||
datasourcesList: [] |
||||
} |
||||
}, |
||||
props: {}, |
||||
methods: { |
||||
...mapActions('datasource', ['getDatasourcesListP']), |
||||
/** |
||||
* create data source |
||||
*/ |
||||
_create (item) { |
||||
let self = this |
||||
let modal = this.$modal.dialog({ |
||||
closable: false, |
||||
showMask: true, |
||||
escClose: true, |
||||
className: 'v-modal-custom', |
||||
transitionName: 'opacityp', |
||||
render (h) { |
||||
return h(mCreateDataSource, { |
||||
on: { |
||||
onUpdate () { |
||||
self._getDatasourcesListP('false') |
||||
modal.remove() |
||||
}, |
||||
close () { |
||||
modal.remove() |
||||
} |
||||
}, |
||||
props: { |
||||
item: item |
||||
} |
||||
}) |
||||
} |
||||
}) |
||||
}, |
||||
/** |
||||
* page |
||||
*/ |
||||
_page (val) { |
||||
this.pageNo = val |
||||
this._getDatasourcesListP() |
||||
}, |
||||
/** |
||||
* conditions event |
||||
*/ |
||||
_onConditions (o) { |
||||
this.searchVal = o.searchVal |
||||
this.pageNo = 1 |
||||
this._getDatasourcesListP('false') |
||||
}, |
||||
/** |
||||
* get data(List) |
||||
*/ |
||||
_getDatasourcesListP (flag) { |
||||
this.isLoading = !flag |
||||
this.getDatasourcesListP({ |
||||
pageSize: this.pageSize, |
||||
pageNo: this.pageNo, |
||||
searchVal: this.searchVal |
||||
}).then(res => { |
||||
this.datasourcesList = res.totalList |
||||
this.total = res.total |
||||
this.isLoading = false |
||||
}).catch(e => { |
||||
this.isLoading = false |
||||
}) |
||||
} |
||||
}, |
||||
watch: {}, |
||||
created () { |
||||
this._getDatasourcesListP() |
||||
}, |
||||
mounted () { |
||||
}, |
||||
components: { mList, mConditions, mSpin, mListConstruction, mNoData } |
||||
} |
||||
</script> |
@ -0,0 +1,16 @@
|
||||
<template> |
||||
<m-list-construction :title="$t('首页')"> |
||||
<template slot="content"> |
||||
<m-project-chart :id="0"></m-project-chart> |
||||
</template> |
||||
</m-list-construction> |
||||
</template> |
||||
|
||||
<script> |
||||
import mProjectChart from '@/conf/home/pages/projects/pages/index/_source/projectChart' |
||||
import mListConstruction from '@/module/components/listConstruction/listConstruction' |
||||
export default { |
||||
name: 'home', |
||||
components: { mProjectChart, mListConstruction } |
||||
} |
||||
</script> |
@ -0,0 +1,40 @@
|
||||
<template> |
||||
<router-view></router-view> |
||||
</template> |
||||
<script> |
||||
export default { |
||||
name: 'projects-index' |
||||
} |
||||
</script> |
||||
|
||||
<style lang="scss" rel="stylesheet/scss"> |
||||
.toolbar-color-sp,.state-tasks-color-sp { |
||||
>a { |
||||
display: inline-block; |
||||
margin-right: 10px; |
||||
cursor:default; |
||||
&:hover { |
||||
span { |
||||
color: #333; |
||||
} |
||||
} |
||||
>i { |
||||
border-radius: 10px; |
||||
display: inline-block; |
||||
vertical-align: middle; |
||||
font-size: 14px; |
||||
} |
||||
span { |
||||
vertical-align: middle; |
||||
font-size: 12px; |
||||
} |
||||
} |
||||
} |
||||
.toolbar-color-sp { |
||||
>a { |
||||
>i { |
||||
font-size: 15px; |
||||
} |
||||
} |
||||
} |
||||
</style> |
@ -0,0 +1,8 @@
|
||||
<template> |
||||
<router-view></router-view> |
||||
</template> |
||||
<script> |
||||
export default { |
||||
name: 'process-definition-index' |
||||
} |
||||
</script> |
@ -0,0 +1,14 @@
|
||||
<template> |
||||
<div class="main-layout-box"> |
||||
<m-secondary-menu :type="'projects'"></m-secondary-menu> |
||||
<m-create-dag></m-create-dag> |
||||
</div> |
||||
</template> |
||||
<script> |
||||
import mCreateDag from '@/conf/home/pages/dag/index' |
||||
import mSecondaryMenu from '@/module/components/secondaryMenu/secondaryMenu' |
||||
export default { |
||||
name: 'definition-create-index', |
||||
components: { mCreateDag, mSecondaryMenu } |
||||
} |
||||
</script> |
@ -0,0 +1,14 @@
|
||||
<template> |
||||
<div class="main-layout-box"> |
||||
<m-secondary-menu :type="'projects'"></m-secondary-menu> |
||||
<m-definition-details></m-definition-details> |
||||
</div> |
||||
</template> |
||||
<script> |
||||
import mSecondaryMenu from '@/module/components/secondaryMenu/secondaryMenu' |
||||
import mDefinitionDetails from '@/conf/home/pages/dag/definitionDetails.vue' |
||||
export default { |
||||
name: 'definition-details-index', |
||||
components: { mDefinitionDetails, mSecondaryMenu } |
||||
} |
||||
</script> |
@ -0,0 +1,338 @@
|
||||
<template> |
||||
<div class="ans-input email-model"> |
||||
<div class="clearfix input-element"> |
||||
<span class="tag-wrapper" v-for="(item,$index) in activeList" :class="activeIndex === $index ? 'active' : ''"> |
||||
<span class="tag-text">{{item}}</span> |
||||
<i class="remove-tag ans-icon-close" @click="_del($index)"></i> |
||||
</span> |
||||
<x-poptip |
||||
placement="bottom-start" |
||||
:append-to-body="true" |
||||
:visible-arrow="false" |
||||
v-model="isEmail" |
||||
trigger="manual"> |
||||
<div class="email-list-model"> |
||||
<div class="ans-scroller" style=" max-height: 300px;"> |
||||
<div class="scroll-area-wrapper scroll-transition"> |
||||
<ul class="dropdown-container"> |
||||
<li class="ans-option" v-for="(item,$index) in emailList" @click="_selectEmail($index + 1)"> |
||||
<span class="default-option-class" :class="index === ($index + 1) ? 'active' : ''">{{item}}</span> |
||||
</li> |
||||
</ul> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<span class="label-wrapper" slot="reference" > |
||||
<!--@keydown.tab="_emailTab"--> |
||||
<input |
||||
class="email-input" |
||||
ref="emailInput" |
||||
:style="{width:emailWidth + 'px'}" |
||||
type="text" |
||||
v-model="email" |
||||
:placeholder="$t('请输入邮箱')" |
||||
@keydown.tab="_emailTab" |
||||
@keyup.delete="_emailDelete" |
||||
@keyup.enter="_emailEnter" |
||||
@keyup.up="_emailKeyup('up')" |
||||
@keyup.down="_emailKeyup('down')"> |
||||
</span> |
||||
</x-poptip> |
||||
|
||||
</div> |
||||
</div> |
||||
</template> |
||||
<script> |
||||
import _ from 'lodash' |
||||
import i18n from '@/module/i18n' |
||||
import emailList from '~/localData/email' |
||||
import { isEmial, fuzzyQuery } from './util' |
||||
|
||||
export default { |
||||
name: 'email', |
||||
data () { |
||||
return { |
||||
tagModel: false, |
||||
email: '', |
||||
activeIndex: null, |
||||
emailList: [], |
||||
index: 0, |
||||
emailWidth: 100, |
||||
isCn: false |
||||
} |
||||
}, |
||||
props: { |
||||
activeList: Array, |
||||
repeatData: Array |
||||
}, |
||||
model: { |
||||
prop: 'activeList', |
||||
event: 'valueEvent' |
||||
}, |
||||
methods: { |
||||
/** |
||||
* Manually add a mailbox |
||||
*/ |
||||
_manualEmail () { |
||||
let email = this.email |
||||
|
||||
let is = (n) => { |
||||
return _.some(_.cloneDeep(this.repeatData).concat(_.cloneDeep(this.activeList)), v => v === n) |
||||
} |
||||
|
||||
if (isEmial(email)) { |
||||
if (!is(email)) { |
||||
this.emailWidth = 0 |
||||
this.activeList.push(email) |
||||
this.email = '' |
||||
this._handlerEmailWitch() |
||||
} else { |
||||
this.$message.warning(`${i18n.$t('邮箱已存在!收件人和抄送人不能重复')}`) |
||||
} |
||||
} else { |
||||
this.$message.warning(`${i18n.$t('邮箱输入不合法')}`) |
||||
} |
||||
}, |
||||
/** |
||||
* Processing mailbox |
||||
*/ |
||||
_handlerEmail (val) { |
||||
if (!val) { |
||||
this.emailList = [] |
||||
this.isEmail = false |
||||
} else { |
||||
let a = _.cloneDeep(this.repeatData).concat(_.cloneDeep(this.activeList)) |
||||
let b = a.concat(emailList) |
||||
let list = fuzzyQuery(b, val) |
||||
this.emailList = _.uniqWith(list.length && list, _.isEqual) |
||||
this.isEmail = !!list.length |
||||
if (this.emailList.length) { |
||||
this.index = 1 |
||||
} |
||||
} |
||||
}, |
||||
/** |
||||
* Carriage return |
||||
*/ |
||||
_emailEnter () { |
||||
// 没有list 手填 |
||||
if (!this.emailList.length) { |
||||
this._manualEmail() |
||||
return |
||||
} |
||||
this._selectEmail(this.index) |
||||
}, |
||||
/** |
||||
* delete email |
||||
*/ |
||||
_emailDelete () { |
||||
// 输入法中文情况下禁止删除 |
||||
if (!this.isCn) { |
||||
this.emailWidth = 0 |
||||
if (_.isInteger(this.activeIndex)) { |
||||
this.activeList.pop() |
||||
this.activeIndex = null |
||||
} else { |
||||
if (!this.email) { |
||||
this.activeIndex = this.activeList.length - 1 |
||||
} |
||||
} |
||||
this._handlerEmailWitch() |
||||
} |
||||
}, |
||||
/** |
||||
* click delete |
||||
*/ |
||||
_del (i) { |
||||
this.emailWidth = 0 |
||||
this.activeList.splice(i, 1) |
||||
this._handlerEmailWitch() |
||||
}, |
||||
/** |
||||
* keyup Up/down event processing |
||||
*/ |
||||
_emailKeyup (type) { |
||||
let emailList = this.emailList.length |
||||
if (emailList === 1) { |
||||
this.index = 1 |
||||
return |
||||
} |
||||
if (emailList) { |
||||
if (type === 'up') { |
||||
this.index = ((i) => { |
||||
let num |
||||
if (i === 0 || i === 1) { |
||||
num = emailList |
||||
} else { |
||||
num = i - 1 |
||||
} |
||||
return num |
||||
})(this.index) |
||||
} else { |
||||
this.index = ((i) => { |
||||
let num |
||||
if (i === 0 || i === emailList) { |
||||
num = 1 |
||||
} else { |
||||
num = i + 1 |
||||
} |
||||
return num |
||||
})(this.index) |
||||
} |
||||
} |
||||
}, |
||||
/** |
||||
* Check mailbox processing |
||||
*/ |
||||
_selectEmail (i) { |
||||
let item = this.emailList[i - 1] |
||||
this.isEmail = false |
||||
this.email = '' |
||||
|
||||
// Non-existing data |
||||
if (_.filter(_.cloneDeep(this.repeatData).concat(_.cloneDeep(this.activeList)), v => v === item).length) { |
||||
this.$message.warning(`${i18n.$t('邮箱已存在!收件人和抄送人不能重复')}`) |
||||
return |
||||
} |
||||
// Width initialization |
||||
this.emailWidth = 0 |
||||
// Insert data |
||||
this.activeList.push(item) |
||||
// Calculated width |
||||
this._handlerEmailWitch() |
||||
// Check mailbox index initialization |
||||
this.activeIndex = null |
||||
setTimeout(() => { |
||||
// Focus position |
||||
this.$refs.emailInput.focus() |
||||
}, 100) |
||||
}, |
||||
/** |
||||
* Processing width |
||||
*/ |
||||
_handlerEmailWitch () { |
||||
setTimeout(() => { |
||||
this.emailWidth = parseInt(688 - $(this.$refs.emailInput).position().left - 20) |
||||
if (this.emailWidth < 80) { |
||||
this.emailWidth = 200 |
||||
} |
||||
}, 100) |
||||
}, |
||||
/** |
||||
* Tab event processing |
||||
*/ |
||||
_emailTab () { |
||||
// Data processing |
||||
this._emailEnter() |
||||
// Focus acquisition |
||||
setTimeout(() => { |
||||
// Focus position |
||||
this.$refs.emailInput.focus() |
||||
}, 100) |
||||
} |
||||
}, |
||||
watch: { |
||||
email (val) { |
||||
this._handlerEmail(val) |
||||
// Check mailbox index initialization |
||||
this.activeIndex = null |
||||
}, |
||||
activeList (val) { |
||||
this.$emit('valueEvent', val) |
||||
} |
||||
}, |
||||
created () { |
||||
|
||||
}, |
||||
mounted () { |
||||
setTimeout(() => { |
||||
// Processing width |
||||
this._handlerEmailWitch() |
||||
}, 500) |
||||
|
||||
// Input method judgment |
||||
$('.email-input').on('input', function () { |
||||
// Chinese input is not truncated |
||||
if ($(this).prop('comStart')) return |
||||
this.isCn = false |
||||
}).on('compositionstart', () => { |
||||
$(this).prop('comStart', true) |
||||
// Check mailbox index initialization |
||||
this.activeIndex = null |
||||
// console.log('中文输入:开始'); |
||||
this.isCn = true |
||||
}).on('compositionend', () => { |
||||
$(this).prop('comStart', false) |
||||
// Check mailbox index initialization |
||||
this.activeIndex = null |
||||
// console.log('中文输入:结束'); |
||||
this.isCn = false |
||||
}) |
||||
} |
||||
} |
||||
</script> |
||||
|
||||
<style lang="scss" rel="stylesheet/scss"> |
||||
.email-model { |
||||
width: 688px; |
||||
.input-element { |
||||
min-height: 32px; |
||||
padding: 1px 8px; |
||||
.tag-wrapper { |
||||
display: inline-block; |
||||
height: 22px; |
||||
margin: 3px 8px 3px 0px; |
||||
padding: 0 10px; |
||||
background: #ebf8ff; |
||||
border-radius: 2px; |
||||
cursor: default; |
||||
&.active { |
||||
background: #E2EFF9 |
||||
} |
||||
.tag-text { |
||||
margin-right: 8px; |
||||
font-size: 12px; |
||||
line-height: 22px; |
||||
color: #666; |
||||
cursor: text; |
||||
} |
||||
.remove-tag { |
||||
font-size: 12px; |
||||
-webkit-transform: scale(.8); |
||||
-ms-transform: scale(.8); |
||||
transform: scale(.8); |
||||
color: #666; |
||||
cursor: pointer; |
||||
&:hover { |
||||
color: #000; |
||||
} |
||||
} |
||||
} |
||||
.label-wrapper { |
||||
margin-left: -6px; |
||||
input { |
||||
height: 29px; |
||||
line-height: 29px; |
||||
border: 0; |
||||
padding-left: 4px; |
||||
} |
||||
} |
||||
} |
||||
} |
||||
.email-list-model { |
||||
margin: -10px -13px; |
||||
.ans-scroller { |
||||
width: 230px; |
||||
overflow-y: scroll; |
||||
.default-option-class { |
||||
width: 230px; |
||||
overflow: hidden; |
||||
text-overflow:ellipsis; |
||||
white-space: nowrap; |
||||
} |
||||
} |
||||
.ans-option .default-option-class.active { |
||||
background: #ebf8ff; |
||||
} |
||||
} |
||||
</style> |
@ -0,0 +1,238 @@
|
||||
<template> |
||||
<div class="list-model"> |
||||
<div class="table-box"> |
||||
<table class="fixed"> |
||||
<tr> |
||||
<th> |
||||
<span>{{$t('编号')}}</span> |
||||
</th> |
||||
<th> |
||||
<span>{{$t('工作流名称')}}</span> |
||||
</th> |
||||
<th width="50"> |
||||
<span>{{$t('状态')}}</span> |
||||
</th> |
||||
<th width="140"> |
||||
<span>{{$t('创建时间')}}</span> |
||||
</th> |
||||
<th width="140"> |
||||
<span>{{$t('更新时间')}}</span> |
||||
</th> |
||||
<th> |
||||
<span>{{$t('描述')}}</span> |
||||
</th> |
||||
<th width="90"> |
||||
<span>{{$t('定时状态')}}</span> |
||||
</th> |
||||
<th width="220"> |
||||
<span>{{$t('操作')}}</span> |
||||
</th> |
||||
</tr> |
||||
<tr v-for="(item, $index) in list" :key="item.id"> |
||||
<td> |
||||
<span>{{parseInt(pageNo === 1 ? ($index + 1) : (($index + 1) + (pageSize * (pageNo - 1))))}}</span> |
||||
</td> |
||||
<td> |
||||
<span class="ellipsis"> |
||||
<router-link :to="{ path: '/projects/definition/list/' + item.id}" tag="a" class="links"> |
||||
{{item.name}} |
||||
</router-link> |
||||
</span> |
||||
</td> |
||||
<td><span>{{_rtPublishStatus(item.releaseState)}}</span></td> |
||||
<td> |
||||
<span v-if="item.createTime">{{item.createTime | formatDate}}</span> |
||||
<span v-if="!item.createTime">-</span> |
||||
</td> |
||||
<td> |
||||
<span>{{item.updateTime | formatDate}}</span> |
||||
</td> |
||||
<td><span class="ellipsis">{{item.desc}}</span></td> |
||||
<td> |
||||
<span v-if="item.scheduleReleaseState === 'OFFLINE'">{{$t('下线')}}</span> |
||||
<span v-if="item.scheduleReleaseState === 'ONLINE'">{{$t('上线')}}</span> |
||||
<span v-if="!item.scheduleReleaseState">-</span> |
||||
</td> |
||||
<td> |
||||
<x-button type="info" shape="circle" size="xsmall" data-toggle="tooltip" :title="$t('编辑')" @click="_edit(item)" :disabled="item.releaseState === 'ONLINE'" v-ps="['GENERAL_USER']" icon="iconfont icon-bianji"><!--{{$t('编辑')}}--></x-button> |
||||
<x-button type="success" shape="circle" size="xsmall" data-toggle="tooltip" :title="$t('启动')" @click="_start(item)" :disabled="item.releaseState !== 'ONLINE'" v-ps="['GENERAL_USER']" icon="iconfont icon-qidong"><!--{{$t('启动')}}--></x-button> |
||||
<x-button type="info" shape="circle" size="xsmall" data-toggle="tooltip" :title="$t('定时')" @click="_timing(item)" :disabled="item.releaseState !== 'ONLINE' || item.scheduleReleaseState !== null" v-ps="['GENERAL_USER']" icon="iconfont icon-timer"><!--{{$t('定时')}}--></x-button> |
||||
<x-button type="error" shape="circle" size="xsmall" data-toggle="tooltip" :title="$t('下线')" @click="_downline(item)" v-if="item.releaseState === 'ONLINE'" v-ps="['GENERAL_USER']" icon="iconfont icon-erji-xiaxianjilu"><!--{{$t('下线')}}--></x-button> |
||||
<x-button type="warning" shape="circle" size="xsmall" data-toggle="tooltip" :title="$t('上线')" @click="_poponline(item)" v-if="item.releaseState === 'OFFLINE'" v-ps="['GENERAL_USER']" icon="iconfont icon-erji-xiaxianjilu-copy"><!--{{$t('上线')}}--></x-button> |
||||
<x-button type="info" shape="circle" size="xsmall" data-toggle="tooltip" :title="$t('定时管理')" @click="_timingManage(item)" :disabled="item.releaseState !== 'ONLINE'" v-ps="['GENERAL_USER']" icon="iconfont icon-paibanguanli"><!--{{$t('定时管理')}}--></x-button> |
||||
<x-button type="info" shape="circle" size="xsmall" data-toggle="tooltip" :title="$t('树形图')" @click="_treeView(item)" icon="iconfont icon-juxingkaobei"><!--{{$t('树形图')}}--></x-button> |
||||
</td> |
||||
</tr> |
||||
</table> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
<script> |
||||
import _ from 'lodash' |
||||
import mStart from './start' |
||||
import mTiming from './timing' |
||||
import { mapActions } from 'vuex' |
||||
import '@/module/filter/formatDate' |
||||
import { publishStatus } from '@/conf/home/pages/dag/_source/config' |
||||
|
||||
export default { |
||||
name: 'definition-list', |
||||
data () { |
||||
return { |
||||
list: [] |
||||
} |
||||
}, |
||||
props: { |
||||
processList: Array, |
||||
pageNo: Number, |
||||
pageSize: Number |
||||
}, |
||||
methods: { |
||||
...mapActions('dag', ['editProcessState', 'getStartCheck', 'getReceiver']), |
||||
_rtPublishStatus (code) { |
||||
return _.filter(publishStatus, v => v.code === code)[0].desc |
||||
}, |
||||
_treeView (item) { |
||||
this.$router.push({ path: `/projects/definition/tree/${item.id}` }) |
||||
}, |
||||
/** |
||||
* Start |
||||
*/ |
||||
_start (item) { |
||||
this.getStartCheck({ processDefinitionId: item.id }).then(res => { |
||||
let self = this |
||||
let modal = this.$modal.dialog({ |
||||
closable: false, |
||||
showMask: true, |
||||
escClose: true, |
||||
className: 'v-modal-custom', |
||||
transitionName: 'opacityp', |
||||
render (h) { |
||||
return h(mStart, { |
||||
on: { |
||||
onUpdate () { |
||||
self._onUpdate() |
||||
modal.remove() |
||||
}, |
||||
close () { |
||||
modal.remove() |
||||
} |
||||
}, |
||||
props: { |
||||
item: item |
||||
} |
||||
}) |
||||
} |
||||
}) |
||||
}).catch(e => { |
||||
this.$message.error(e.msg || '') |
||||
}) |
||||
}, |
||||
/** |
||||
* get emial |
||||
*/ |
||||
_getReceiver (id) { |
||||
return new Promise((resolve, reject) => { |
||||
this.getReceiver({ processDefinitionId: id }).then(res => { |
||||
resolve({ |
||||
receivers: res.receivers && res.receivers.split(',') || [], |
||||
receiversCc: res.receiversCc && res.receiversCc.split(',') || [] |
||||
}) |
||||
}) |
||||
}) |
||||
}, |
||||
/** |
||||
* timing |
||||
*/ |
||||
_timing (item) { |
||||
let self = this |
||||
this._getReceiver(item.id).then(res => { |
||||
let modal = this.$modal.dialog({ |
||||
closable: false, |
||||
showMask: true, |
||||
escClose: true, |
||||
className: 'v-modal-custom', |
||||
transitionName: 'opacityp', |
||||
render (h) { |
||||
return h(mTiming, { |
||||
on: { |
||||
onUpdate () { |
||||
self._onUpdate() |
||||
modal.remove() |
||||
}, |
||||
close () { |
||||
modal.remove() |
||||
} |
||||
}, |
||||
props: { |
||||
item: item, |
||||
receiversD: res.receivers, |
||||
receiversCcD: res.receiversCc |
||||
} |
||||
}) |
||||
} |
||||
}) |
||||
}) |
||||
}, |
||||
/** |
||||
* Timing manage |
||||
*/ |
||||
_timingManage (item) { |
||||
this.$router.push({ path: `/projects/definition/list/timing/${item.id}` }) |
||||
}, |
||||
/** |
||||
* edit |
||||
*/ |
||||
_edit (item) { |
||||
this.$router.push({ path: `/projects/definition/list/${item.id}` }) |
||||
}, |
||||
/** |
||||
* Offline |
||||
*/ |
||||
_downline (item) { |
||||
this._upProcessState({ |
||||
processId: item.id, |
||||
releaseState: 0 |
||||
}) |
||||
}, |
||||
/** |
||||
* online |
||||
*/ |
||||
_poponline (item) { |
||||
this._upProcessState({ |
||||
processId: item.id, |
||||
releaseState: 1 |
||||
}) |
||||
}, |
||||
/** |
||||
* Edit state |
||||
*/ |
||||
_upProcessState (o) { |
||||
this.editProcessState(o).then(res => { |
||||
this.$message.success(res.msg) |
||||
$('body').find('.tooltip.fade.top.in').remove() |
||||
this._onUpdate() |
||||
}).catch(e => { |
||||
this.$message.error(e.msg || '') |
||||
}) |
||||
}, |
||||
_onUpdate () { |
||||
this.$emit('on-update') |
||||
} |
||||
}, |
||||
watch: { |
||||
processList (a) { |
||||
this.list = [] |
||||
setTimeout(() => { |
||||
this.list = a |
||||
}) |
||||
} |
||||
}, |
||||
created () { |
||||
}, |
||||
mounted () { |
||||
this.list = this.processList |
||||
}, |
||||
components: { } |
||||
} |
||||
</script> |
@ -0,0 +1,298 @@
|
||||
<template> |
||||
<div class="start-process-model"> |
||||
<div class="title-box"> |
||||
<span>{{$t('启动前请先设置参数')}}</span> |
||||
</div> |
||||
<div class="clearfix list"> |
||||
<div class="text"> |
||||
{{$t('失败策略')}} |
||||
</div> |
||||
<div class="cont"> |
||||
<x-radio-group v-model="failureStrategy" style="margin-top: 7px;"> |
||||
<x-radio :label="'CONTINUE'">{{$t('继续')}}</x-radio> |
||||
<x-radio :label="'END'">{{$t('结束')}}</x-radio> |
||||
</x-radio-group> |
||||
</div> |
||||
</div> |
||||
<div class="clearfix list" v-if="sourceType === 'contextmenu'"> |
||||
<div class="text"> |
||||
{{$t('节点执行')}} |
||||
</div> |
||||
<div class="cont"> |
||||
<x-radio-group v-model="taskDependType"> |
||||
<x-radio :label="'TASK_POST'">{{$t('向后执行')}}</x-radio> |
||||
<x-radio :label="'TASK_PRE'">{{$t('向前执行')}}</x-radio> |
||||
<x-radio :label="'TASK_ONLY'">{{$t('仅执行当前节点')}}</x-radio> |
||||
</x-radio-group> |
||||
</div> |
||||
</div> |
||||
<div class="clearfix list"> |
||||
<div class="text"> |
||||
{{$t('通知策略')}} |
||||
</div> |
||||
<div class="cont"> |
||||
<x-select style="width: 200px;" v-model="warningType"> |
||||
<x-option |
||||
v-for="city in warningTypeList" |
||||
:key="city.id" |
||||
:value="city" |
||||
:label="city.code"> |
||||
</x-option> |
||||
</x-select> |
||||
</div> |
||||
</div> |
||||
<div class="clearfix list"> |
||||
<div class="text"> |
||||
{{$t('流程优先级')}} |
||||
</div> |
||||
<div class="cont"> |
||||
<m-priority v-model="processInstancePriority"></m-priority> |
||||
</div> |
||||
</div> |
||||
<div class="clearfix list"> |
||||
<div class="text"> |
||||
{{$t('通知组')}} |
||||
</div> |
||||
<div class="cont"> |
||||
<x-select |
||||
style="width: 200px;" |
||||
v-model="warningGroupId" |
||||
:disabled="!notifyGroupList.length"> |
||||
<x-input slot="trigger" slot-scope="{ selectedModel }" readonly :placeholder="$t('请选择通知组')" :value="selectedModel ? selectedModel.label : ''" style="width: 200px;" @on-click-icon.stop="warningGroupId = {}"> |
||||
<i slot="suffix" class="fa fa-times-circle" style="font-size: 15px;cursor: pointer;" v-show="warningGroupId.id"></i> |
||||
<i slot="suffix" class="ans-icon-arrow-down" style="font-size: 12px;" v-show="!warningGroupId.id"></i> |
||||
</x-input> |
||||
<x-option |
||||
v-for="city in notifyGroupList" |
||||
:key="city.id" |
||||
:value="city" |
||||
:label="city.code"> |
||||
</x-option> |
||||
</x-select> |
||||
</div> |
||||
</div> |
||||
<div class="clearfix list"> |
||||
<div class="text"> |
||||
{{$t('收件人')}} |
||||
</div> |
||||
<div class="cont"> |
||||
<m-email v-model="receivers" :repeat-data="receiversCc"></m-email> |
||||
</div> |
||||
</div> |
||||
<div class="clearfix list"> |
||||
<div class="text"> |
||||
{{$t('抄送人')}} |
||||
</div> |
||||
<div class="cont"> |
||||
<m-email v-model="receiversCc" :repeat-data="receivers"></m-email> |
||||
</div> |
||||
</div> |
||||
<div class="clearfix list"> |
||||
<div class="text"> |
||||
{{$t('补数')}} |
||||
</div> |
||||
<div class="cont"> |
||||
<div style="padding-top: 6px;"> |
||||
<x-checkbox v-model="execType">{{$t('是否补数')}}</x-checkbox> |
||||
</div> |
||||
</div> |
||||
</div> |
||||
<template v-if="execType"> |
||||
<div class="clearfix list" style="margin:-6px 0 16px 0"> |
||||
<div class="text"> |
||||
{{$t('执行方式')}} |
||||
</div> |
||||
<div class="cont"> |
||||
<x-radio-group v-model="runMode" style="margin-top: 7px;"> |
||||
<x-radio :label="'RUN_MODE_SERIAL'">{{$t('串行执行')}}</x-radio> |
||||
<x-radio :label="'RUN_MODE_PARALLEL'">{{$t('并行执行')}}</x-radio> |
||||
</x-radio-group> |
||||
</div> |
||||
</div> |
||||
<div class="clearfix list"> |
||||
<div class="text"> |
||||
{{$t('时间')}} |
||||
</div> |
||||
<div class="cont"> |
||||
<x-datepicker |
||||
style="width: 360px;" |
||||
:panel-num="2" |
||||
placement="bottom-start" |
||||
@on-change="_datepicker" |
||||
:value="scheduleTime" |
||||
type="daterange" |
||||
:placeholder="$t('选择日期区间')" |
||||
format="YYYY-MM-DD HH:mm:ss"> |
||||
</x-datepicker> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
<div class="submit"> |
||||
<x-button type="text" @click="close()"> {{$t('取消')}} </x-button> |
||||
<x-button type="primary" shape="circle" :loading="spinnerLoading" @click="ok()" v-ps="['GENERAL_USER']">{{spinnerLoading ? 'Loading...' : $t('启动')}} </x-button> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
<script> |
||||
import _ from 'lodash' |
||||
import dayjs from 'dayjs' |
||||
import mEmail from './email.vue' |
||||
import store from '@/conf/home/store' |
||||
import { warningTypeList } from './util' |
||||
import mPriority from '@/module/components/priority/priority' |
||||
|
||||
export default { |
||||
name: 'start-process', |
||||
data () { |
||||
return { |
||||
store, |
||||
processDefinitionId: 0, |
||||
failureStrategy: 'CONTINUE', |
||||
warningTypeList: warningTypeList, |
||||
warningType: {}, |
||||
notifyGroupList: [], |
||||
warningGroupId: {}, |
||||
scheduleTime: '', |
||||
spinnerLoading: false, |
||||
execType: false, |
||||
taskDependType: 'TASK_POST', |
||||
receivers: [], |
||||
receiversCc: [], |
||||
runMode: 'RUN_MODE_SERIAL', |
||||
processInstancePriority: 'MEDIUM' |
||||
} |
||||
}, |
||||
props: { |
||||
item: Object, |
||||
startNodeList: { |
||||
type: String, |
||||
default: '' |
||||
}, |
||||
sourceType: String |
||||
}, |
||||
methods: { |
||||
_datepicker (val) { |
||||
this.scheduleTime = val |
||||
}, |
||||
_start () { |
||||
this.spinnerLoading = true |
||||
let param = { |
||||
processDefinitionId: this.item.id, |
||||
scheduleTime: this.scheduleTime.length && this.scheduleTime.join(',') || '', |
||||
failureStrategy: this.failureStrategy, |
||||
warningType: this.warningType.id, |
||||
warningGroupId: _.isEmpty(this.warningGroupId) ? 0 : this.warningGroupId.id, |
||||
execType: this.execType ? 'COMPLEMENT_DATA' : null, |
||||
startNodeList: this.startNodeList, |
||||
taskDependType: this.taskDependType, |
||||
runMode: this.runMode, |
||||
processInstancePriority: this.processInstancePriority, |
||||
receivers: this.receivers.join(',') || '', |
||||
receiversCc: this.receiversCc.join(',') || '' |
||||
} |
||||
// Executed from the specified node |
||||
if (this.sourceType === 'contextmenu') { |
||||
param.taskDependType = this.taskDependType |
||||
} |
||||
this.store.dispatch('dag/processStart', param).then(res => { |
||||
this.$message.success(res.msg) |
||||
this.$emit('onUpdate') |
||||
setTimeout(() => { |
||||
this.spinnerLoading = false |
||||
this.close() |
||||
}, 500) |
||||
}).catch(e => { |
||||
this.$message.error(e.msg || '') |
||||
this.spinnerLoading = false |
||||
}) |
||||
}, |
||||
_getNotifyGroupList () { |
||||
return new Promise((resolve, reject) => { |
||||
let notifyGroupListS = _.cloneDeep(this.store.state.dag.notifyGroupListS) || [] |
||||
if (!notifyGroupListS.length) { |
||||
this.store.dispatch('dag/getNotifyGroupList').then(res => { |
||||
this.notifyGroupList = res |
||||
resolve() |
||||
}) |
||||
} else { |
||||
this.notifyGroupList = notifyGroupListS |
||||
resolve() |
||||
} |
||||
}) |
||||
}, |
||||
_getReceiver () { |
||||
this.store.dispatch('dag/getReceiver', { processDefinitionId: this.item.id }).then(res => { |
||||
this.receivers = res.receivers && res.receivers.split(',') || [] |
||||
this.receiversCc = res.receiversCc && res.receiversCc.split(',') || [] |
||||
}) |
||||
}, |
||||
ok () { |
||||
this._start() |
||||
}, |
||||
close () { |
||||
this.$emit('close') |
||||
} |
||||
}, |
||||
watch: { |
||||
execType (a) { |
||||
this.scheduleTime = a ? [dayjs().format('YYYY-MM-DD 00:00:00'), dayjs().format('YYYY-MM-DD 00:00:00')] : '' |
||||
} |
||||
}, |
||||
created () { |
||||
this.warningType = this.warningTypeList[0] |
||||
|
||||
this._getReceiver() |
||||
}, |
||||
mounted () { |
||||
this._getNotifyGroupList().then(() => { |
||||
this.$nextTick(() => { |
||||
this.warningGroupId = { id: 0 } |
||||
}) |
||||
}) |
||||
}, |
||||
computed: {}, |
||||
components: { mEmail, mPriority } |
||||
} |
||||
</script> |
||||
|
||||
<style lang="scss" rel="stylesheet/scss"> |
||||
.start-process-model { |
||||
width: 860px; |
||||
min-height: 300px; |
||||
background: #fff; |
||||
border-radius: 3px; |
||||
.title-box { |
||||
margin-bottom: 18px; |
||||
span { |
||||
padding-left: 30px; |
||||
font-size: 16px; |
||||
padding-top: 29px; |
||||
display: block; |
||||
} |
||||
} |
||||
.list { |
||||
margin-bottom: 14px; |
||||
.text { |
||||
width: 140px; |
||||
float: left; |
||||
text-align: right; |
||||
line-height: 32px; |
||||
padding-right: 8px; |
||||
} |
||||
.cont { |
||||
width: 350px; |
||||
float: left; |
||||
.add-email-model { |
||||
padding: 20px; |
||||
} |
||||
|
||||
} |
||||
} |
||||
.submit { |
||||
text-align: right; |
||||
padding-right: 30px; |
||||
padding-top: 10px; |
||||
padding-bottom: 30px; |
||||
} |
||||
} |
||||
</style> |
@ -0,0 +1,323 @@
|
||||
<template> |
||||
<div class="timing-process-model"> |
||||
<div class="title-box"> |
||||
<span>{{$t('定时前请先设置参数')}}</span> |
||||
</div> |
||||
<div class="clearfix list"> |
||||
<div class="text"> |
||||
{{$t('起止时间')}} |
||||
</div> |
||||
<div class="cont"> |
||||
<x-datepicker |
||||
style="width: 300px;" |
||||
:panel-num="2" |
||||
placement="bottom-start" |
||||
@on-change="_datepicker" |
||||
:value="scheduleTime" |
||||
type="daterange" |
||||
:placeholder="$t('选择日期区间')" |
||||
format="YYYY-MM-DD HH:mm:ss"> |
||||
</x-datepicker> |
||||
</div> |
||||
</div> |
||||
<div class="clearfix list"> |
||||
<div class="text"> |
||||
{{$t('定时')}} |
||||
</div> |
||||
<div class="cont"> |
||||
<template> |
||||
<x-poptip :ref="'poptip'" placement="bottom-start"> |
||||
<div class="crontab-box"> |
||||
<v-crontab v-model="crontab" :locale="i18n"></v-crontab> |
||||
</div> |
||||
<template slot="reference"> |
||||
<x-input |
||||
style="width: 300px;" |
||||
type="text" |
||||
readonly |
||||
:value="crontab" |
||||
autocomplete="off"> |
||||
</x-input> |
||||
</template> |
||||
</x-poptip> |
||||
</template> |
||||
</div> |
||||
</div> |
||||
<div class="clearfix list"> |
||||
<div class="text"> |
||||
{{$t('失败策略')}} |
||||
</div> |
||||
<div class="cont"> |
||||
<x-radio-group v-model="failureStrategy" style="margin-top: 7px;"> |
||||
<x-radio :label="'CONTINUE'">{{$t('继续')}}</x-radio> |
||||
<x-radio :label="'END'">{{$t('结束')}}</x-radio> |
||||
</x-radio-group> |
||||
</div> |
||||
</div> |
||||
<div class="clearfix list"> |
||||
<div class="text"> |
||||
{{$t('通知策略')}} |
||||
</div> |
||||
<div class="cont"> |
||||
<x-select |
||||
style="width: 200px;" |
||||
v-model="warningType"> |
||||
<x-option |
||||
v-for="city in warningTypeList" |
||||
:key="city.id" |
||||
:value="city.id" |
||||
:label="city.code"> |
||||
</x-option> |
||||
</x-select> |
||||
</div> |
||||
</div> |
||||
<div class="clearfix list"> |
||||
<div class="text"> |
||||
{{$t('流程优先级')}} |
||||
</div> |
||||
<div class="cont"> |
||||
<m-priority v-model="processInstancePriority"></m-priority> |
||||
</div> |
||||
</div> |
||||
<div class="clearfix list"> |
||||
<div class="text"> |
||||
{{$t('通知组')}} |
||||
</div> |
||||
<div class="cont"> |
||||
<x-select |
||||
style="width: 200px;" |
||||
:disabled="!notifyGroupList.length" |
||||
v-model="warningGroupId"> |
||||
<x-input slot="trigger" readonly slot-scope="{ selectedModel }" :placeholder="$t('请选择通知组')" :value="selectedModel ? selectedModel.label : ''" style="width: 200px;" @on-click-icon.stop="warningGroupId = {}"> |
||||
<i slot="suffix" class="fa fa-times-circle" style="font-size: 15px;cursor: pointer;" v-show="warningGroupId.id"></i> |
||||
<i slot="suffix" class="ans-icon-arrow-down" style="font-size: 12px;" v-show="!warningGroupId.id"></i> |
||||
</x-input> |
||||
<x-option |
||||
v-for="city in notifyGroupList" |
||||
:key="city.id" |
||||
:value="city" |
||||
:label="city.code"> |
||||
</x-option> |
||||
</x-select> |
||||
</div> |
||||
</div> |
||||
<div class="clearfix list"> |
||||
<div class="text"> |
||||
{{$t('收件人')}} |
||||
</div> |
||||
<div class="cont" style="width: 680px;"> |
||||
<m-email v-model="receivers" :repeat-data="receiversCc"></m-email> |
||||
</div> |
||||
</div> |
||||
<div class="clearfix list"> |
||||
<div class="text"> |
||||
{{$t('抄送人')}} |
||||
</div> |
||||
<div class="cont" style="width: 680px;"> |
||||
<m-email v-model="receiversCc" :repeat-data="receivers"></m-email> |
||||
</div> |
||||
</div> |
||||
<div class="submit"> |
||||
<x-button type="text" @click="close()"> {{$t('取消')}} </x-button> |
||||
<x-button type="primary" shape="circle" :loading="spinnerLoading" @click="ok()" v-ps="['GENERAL_USER']">{{spinnerLoading ? 'Loading...' : (item.crontab ? $t('编辑') : $t('创建'))}} </x-button> |
||||
</div> |
||||
</div> |
||||
</template> |
||||
<script> |
||||
import _ from 'lodash' |
||||
import i18n from '@/module/i18n' |
||||
import mEmail from './email.vue' |
||||
import '~/@vue/crontab/dist/index.css' |
||||
import store from '@/conf/home/store' |
||||
import { warningTypeList } from './util' |
||||
import { vCrontab } from '~/@vue/crontab/dist' |
||||
import { formatDate } from '@/module/filter/filter' |
||||
import mPriority from '@/module/components/priority/priority' |
||||
|
||||
export default { |
||||
name: 'timing-process', |
||||
data () { |
||||
return { |
||||
store, |
||||
processDefinitionId: 0, |
||||
failureStrategy: 'CONTINUE', |
||||
warningTypeList: warningTypeList, |
||||
warningType: 'NONE', |
||||
notifyGroupList: [], |
||||
warningGroupId: {}, |
||||
spinnerLoading: false, |
||||
scheduleTime: '', |
||||
crontab: '* * * * * ? *', |
||||
cronPopover: false, |
||||
receivers: [], |
||||
receiversCc: [], |
||||
i18n: i18n.globalScope.LOCALE, |
||||
processInstancePriority: 'MEDIUM' |
||||
} |
||||
}, |
||||
props: { |
||||
item: Object, |
||||
receiversD: Array, |
||||
receiversCcD: Array |
||||
}, |
||||
methods: { |
||||
_datepicker (val) { |
||||
this.scheduleTime = val |
||||
}, |
||||
_verification () { |
||||
if (!this.scheduleTime) { |
||||
this.$message.warning(`${i18n.$t('请选择时间')}`) |
||||
return false |
||||
} |
||||
|
||||
if (!this.crontab) { |
||||
this.$message.warning(`${i18n.$t('请填写 crontab')}`) |
||||
return false |
||||
} |
||||
return true |
||||
}, |
||||
_timing () { |
||||
if (this._verification()) { |
||||
let api = '' |
||||
let searchParams = { |
||||
schedule: JSON.stringify({ |
||||
startTime: this.scheduleTime[0], |
||||
endTime: this.scheduleTime[1], |
||||
crontab: this.crontab |
||||
}), |
||||
failureStrategy: this.failureStrategy, |
||||
warningType: this.warningType, |
||||
processInstancePriority: this.processInstancePriority, |
||||
warningGroupId: _.isEmpty(this.warningGroupId) ? 0 : this.warningGroupId.id, |
||||
receivers: this.receivers.join(',') || '', |
||||
receiversCc: this.receiversCc.join(',') || '' |
||||
} |
||||
|
||||
// edit |
||||
if (this.item.crontab) { |
||||
api = 'dag/updateSchedule' |
||||
searchParams.id = this.item.id |
||||
} else { |
||||
api = 'dag/createSchedule' |
||||
searchParams.processDefinitionId = this.item.id |
||||
} |
||||
|
||||
this.store.dispatch(api, searchParams).then(res => { |
||||
this.$message.success(res.msg) |
||||
this.$emit('onUpdate') |
||||
}).catch(e => { |
||||
this.$message.error(e.msg || '') |
||||
}) |
||||
} |
||||
}, |
||||
|
||||
_getNotifyGroupList () { |
||||
return new Promise((resolve, reject) => { |
||||
let notifyGroupListS = _.cloneDeep(this.store.state.dag.notifyGroupListS) || [] |
||||
if (!notifyGroupListS.length) { |
||||
this.store.dispatch('dag/getNotifyGroupList').then(res => { |
||||
this.notifyGroupList = res |
||||
if (this.notifyGroupList.length) { |
||||
resolve() |
||||
} else { |
||||
reject(new Error(0)) |
||||
} |
||||
}) |
||||
} else { |
||||
this.notifyGroupList = notifyGroupListS |
||||
resolve() |
||||
} |
||||
}) |
||||
}, |
||||
ok () { |
||||
this._timing() |
||||
}, |
||||
close () { |
||||
this.$emit('close') |
||||
} |
||||
}, |
||||
watch: { |
||||
}, |
||||
created () { |
||||
this.receivers = _.cloneDeep(this.receiversD) |
||||
this.receiversCc = _.cloneDeep(this.receiversCcD) |
||||
}, |
||||
mounted () { |
||||
let item = this.item |
||||
|
||||
// Determine whether to echo |
||||
if (this.item.crontab) { |
||||
this.crontab = item.crontab |
||||
this.scheduleTime = [formatDate(item.startTime), formatDate(item.endTime)] |
||||
this.failureStrategy = item.failureStrategy |
||||
this.warningType = item.warningType |
||||
this.processInstancePriority = item.processInstancePriority |
||||
this._getNotifyGroupList().then(() => { |
||||
this.$nextTick(() => { |
||||
let list = _.filter(this.notifyGroupList, v => v.id === item.warningGroupId) |
||||
this.warningGroupId = list.length && list[0] || { id: 0 } |
||||
}) |
||||
}).catch(() => this.warningGroupId = { id: 0 }) |
||||
} else { |
||||
this._getNotifyGroupList().then(() => { |
||||
this.$nextTick(() => { |
||||
this.warningGroupId = { id: 0 } |
||||
}) |
||||
}).catch(() => this.warningGroupId = { id: 0 }) |
||||
} |
||||
}, |
||||
components: { vCrontab, mEmail, mPriority } |
||||
} |
||||
</script> |
||||
|
||||
<style lang="scss" rel="stylesheet/scss"> |
||||
.timing-process-model { |
||||
width: 860px; |
||||
min-height: 300px; |
||||
background: #fff; |
||||
border-radius: 3px; |
||||
margin-top: -24%; |
||||
.crontab-box { |
||||
margin: -6px; |
||||
.v-crontab { |
||||
} |
||||
} |
||||
.from-model { |
||||
padding-top: 0; |
||||
} |
||||
.title-box { |
||||
margin-bottom: 18px; |
||||
span { |
||||
padding-left: 30px; |
||||
font-size: 16px; |
||||
padding-top: 29px; |
||||
display: block; |
||||
} |
||||
} |
||||
.list { |
||||
margin-bottom: 14px; |
||||
>.text { |
||||
width: 140px; |
||||
float: left; |
||||
text-align: right; |
||||
line-height: 32px; |
||||
padding-right: 8px; |
||||
} |
||||
.cont { |
||||
width: 350px; |
||||
float: left; |
||||
} |
||||
} |
||||
.submit { |
||||
text-align: right; |
||||
padding-right: 30px; |
||||
padding-top: 10px; |
||||
padding-bottom: 30px; |
||||
} |
||||
} |
||||
.v-crontab-from-model { |
||||
.list-box { |
||||
padding: 0; |
||||
} |
||||
} |
||||
</style> |
@ -0,0 +1,60 @@
|
||||
/* |
||||
* Licensed to the Apache Software Foundation (ASF) under one or more |
||||
* contributor license agreements. See the NOTICE file distributed with |
||||
* this work for additional information regarding copyright ownership. |
||||
* The ASF licenses this file to You under the Apache License, Version 2.0 |
||||
* (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
import i18n from '@/module/i18n' |
||||
|
||||
let warningTypeList = [ |
||||
{ |
||||
id: 'NONE', |
||||
code: `${i18n.$t('都不发')}` |
||||
}, |
||||
{ |
||||
id: 'SUCCESS', |
||||
code: `${i18n.$t('成功发')}` |
||||
}, |
||||
{ |
||||
id: 'FAILURE', |
||||
code: `${i18n.$t('失败发')}` |
||||
}, |
||||
{ |
||||
id: 'ALL', |
||||
code: `${i18n.$t('成功或失败都发')}` |
||||
} |
||||
] |
||||
|
||||
const isEmial = (val) => { |
||||
let regEmail = /^([a-zA-Z0-9]+[_|\_|\.]?)*[a-zA-Z0-9]+@([a-zA-Z0-9]+[_|\_|\.]?)*[a-zA-Z0-9]+\.[a-zA-Z]{2,3}$/ // eslint-disable-line
|
||||
return regEmail.test(val) |
||||
} |
||||
|
||||
const fuzzyQuery = (list, keyWord) => { |
||||
let len = list.length |
||||
let arr = [] |
||||
let reg = new RegExp(keyWord) |
||||
for (let i = 0; i < len; i++) { |
||||
if (list[i].match(reg)) { |
||||
arr.push(list[i]) |
||||
} |
||||
} |
||||
return arr |
||||
} |
||||
|
||||
export { |
||||
warningTypeList, |
||||
isEmial, |
||||
fuzzyQuery |
||||
} |
@ -0,0 +1,147 @@
|
||||
<template> |
||||
<div class="main-layout-box"> |
||||
<m-secondary-menu :type="'projects'"></m-secondary-menu> |
||||
<m-list-construction :title="$t('工作流定义')"> |
||||
<template slot="conditions"> |
||||
<m-conditions @on-conditions="_onConditions"> |
||||
<template slot="button-group"> |
||||
<x-button type="ghost" size="small" v-ps="['GENERAL_USER']" @click="() => this.$router.push({name: 'definition-create'})">{{$t('创建工作流')}}</x-button> |
||||
</template> |
||||
</m-conditions> |
||||
</template> |
||||
<template slot="content"> |
||||
<template v-if="processListP.length"> |
||||
<m-list :process-list="processListP" @on-update="_onUpdate" :page-no="searchParams.pageNo" :page-size="searchParams.pageSize"></m-list> |
||||
<div class="page-box"> |
||||
<x-page :current="parseInt(searchParams.pageNo)" :total="total" show-elevator @on-change="_page"></x-page> |
||||
</div> |
||||
</template> |
||||
<template v-if="!processListP.length"> |
||||
<m-no-data></m-no-data> |
||||
</template> |
||||
<m-spin :is-spin="isLoading"></m-spin> |
||||
</template> |
||||
</m-list-construction> |
||||
</div> |
||||
</template> |
||||
<script> |
||||
import _ from 'lodash' |
||||
import { mapActions } from 'vuex' |
||||
import mList from './_source/list' |
||||
import mSpin from '@/module/components/spin/spin' |
||||
import localStore from '@/module/util/localStorage' |
||||
import { setUrlParams } from '@/module/util/routerUtil' |
||||
import mNoData from '@/module/components/noData/noData' |
||||
import mConditions from '@/module/components/conditions/conditions' |
||||
import mSecondaryMenu from '@/module/components/secondaryMenu/secondaryMenu' |
||||
import mListConstruction from '@/module/components/listConstruction/listConstruction' |
||||
|
||||
export default { |
||||
name: 'definition-list-index', |
||||
data () { |
||||
return { |
||||
total: null, |
||||
processListP: [], |
||||
isLoading: true, |
||||
searchParams: { |
||||
// 分页条数 |
||||
pageSize: 10, |
||||
// 分页 |
||||
pageNo: 1, |
||||
// 查询名称 |
||||
searchVal: '' |
||||
} |
||||
} |
||||
}, |
||||
props: { |
||||
}, |
||||
methods: { |
||||
...mapActions('dag', ['getProcessListP']), |
||||
/** |
||||
* page |
||||
*/ |
||||
_page (val) { |
||||
this.searchParams.pageNo = val |
||||
setUrlParams({ |
||||
pageNo: this.searchParams.pageNo |
||||
}) |
||||
this._debounceGET() |
||||
}, |
||||
/** |
||||
* conditions |
||||
*/ |
||||
_onConditions (o) { |
||||
this.searchParams.searchVal = o.searchVal |
||||
this.searchParams.pageNo = 1 |
||||
setUrlParams({ |
||||
pageNo: this.searchParams.pageNo |
||||
}) |
||||
this._debounceGET() |
||||
}, |
||||
/** |
||||
* get data list |
||||
*/ |
||||
_getProcessListP (flag) { |
||||
this.isLoading = !flag |
||||
this.getProcessListP({ |
||||
pageSize: this.searchParams.pageSize, |
||||
pageNo: this.searchParams.pageNo, |
||||
searchVal: this.searchParams.searchVal, |
||||
userId: this.$route.query.userId || '' |
||||
}).then(res => { |
||||
setUrlParams({ pageNo: this.pageNo }) |
||||
this.processListP = [] |
||||
this.processListP = res.totalList |
||||
this.total = res.total |
||||
this.isLoading = false |
||||
}).catch(e => { |
||||
this.isLoading = false |
||||
}) |
||||
}, |
||||
_onUpdate () { |
||||
this._debounceGET('false') |
||||
}, |
||||
/** |
||||
* Anti-shake request interface |
||||
* @desc Prevent function from being called multiple times |
||||
*/ |
||||
_debounceGET: _.debounce(function (flag) { |
||||
this._getProcessListP(flag) |
||||
}, 100, { |
||||
'leading': false, |
||||
'trailing': true |
||||
}) |
||||
}, |
||||
watch: { |
||||
'$route' (a) { |
||||
// url no params get instance list |
||||
if (_.isEmpty(a.query)) { |
||||
this.searchParams.pageNo = 1 |
||||
} else { |
||||
this.searchParams.pageNo = a.query.pageNo || 1 |
||||
} |
||||
}, |
||||
'searchParams': { |
||||
deep: true, |
||||
handler () { |
||||
this._debounceGET() |
||||
} |
||||
} |
||||
}, |
||||
created () { |
||||
localStore.removeItem('subProcessId') |
||||
|
||||
// Routing parameter merging |
||||
if (!_.isEmpty(this.$route.query)) { |
||||
this.searchParams = _.assign(this.searchParams, this.$route.query) |
||||
} |
||||
}, |
||||
mounted () { |
||||
this._debounceGET() |
||||
}, |
||||
components: { mList, mConditions, mSpin, mListConstruction, mSecondaryMenu, mNoData } |
||||
} |
||||
</script> |
||||
|
||||
<style lang="scss" rel="stylesheet/scss"> |
||||
</style> |
@ -0,0 +1,279 @@
|
||||
|
||||
import $ from 'jquery' |
||||
import * as d3 from 'd3' |
||||
import { rtInstancesTooltip, rtCountMethod } from './util' |
||||
import { tasksType, tasksState } from '@/conf/home/pages/dag/_source/config' |
||||
|
||||
let self = this |
||||
|
||||
let Tree = function () { |
||||
self = this |
||||
this.selfTree = {} |
||||
this.tree = function () {} |
||||
// basic configuration
|
||||
this.config = { |
||||
barHeight: 26, |
||||
axisHeight: 40, |
||||
squareSize: 10, |
||||
squarePading: 4, |
||||
taskNum: 25, |
||||
nodesMax: 0 |
||||
} |
||||
// Margin configuration
|
||||
this.config.margin = { |
||||
top: this.config.barHeight / 2 + this.config.axisHeight, |
||||
right: 0, |
||||
bottom: 0, |
||||
left: this.config.barHeight / 2 |
||||
} |
||||
// width
|
||||
this.config.margin.width = 960 - this.config.margin.left - this.config.margin.right |
||||
// bar width
|
||||
this.config.barWidth = parseInt(this.config.margin.width * 0.9) |
||||
} |
||||
|
||||
/** |
||||
* init |
||||
*/ |
||||
Tree.prototype.init = function ({ data, limit, selfTree }) { |
||||
return new Promise((resolve, reject) => { |
||||
this.selfTree = selfTree |
||||
this.config.taskNum = limit |
||||
this.duration = 400 |
||||
this.i = 0 |
||||
this.tree = d3.layout.tree().nodeSize([0, 46]) |
||||
let tasks = this.tree.nodes(data) |
||||
|
||||
this.diagonal = d3.svg |
||||
.diagonal() |
||||
.projection(d => [d.y, d.x]) |
||||
|
||||
this.svg = d3.select('svg') |
||||
.append('g') |
||||
.attr('class', 'level') |
||||
.attr('transform', 'translate(' + this.config.margin.left + ',' + this.config.margin.top + ')') |
||||
|
||||
data.x0 = 0 |
||||
data.y0 = 0 |
||||
|
||||
this.squareNum = tasks[tasks.length === 1 ? 0 : 1].instances.length |
||||
|
||||
// Calculate the maximum node length
|
||||
this.config.nodesMax = rtCountMethod(data.children) |
||||
|
||||
this.treeUpdate(this.root = data).then(() => { |
||||
this.treeTooltip() |
||||
selfTree.isLoading = false |
||||
resolve() |
||||
}) |
||||
}) |
||||
} |
||||
|
||||
/** |
||||
* tasks |
||||
*/ |
||||
Tree.prototype.nodesClass = function (d) { |
||||
let sclass = 'node' |
||||
if (d.children === undefined && d._children === undefined) { |
||||
sclass += ' leaf' |
||||
} else { |
||||
sclass += ' parent' |
||||
if (d.children === undefined) { sclass += ' collapsed' } else { sclass += ' expanded' } |
||||
} |
||||
return sclass |
||||
} |
||||
|
||||
/** |
||||
* toottip handle |
||||
*/ |
||||
Tree.prototype.treeTooltip = function () { |
||||
$('rect.state').tooltip({ |
||||
html: true, |
||||
container: 'body' |
||||
}) |
||||
$('circle.task').tooltip({ |
||||
html: true, |
||||
container: 'body' |
||||
}) |
||||
} |
||||
|
||||
/** |
||||
* tree Expand hidden |
||||
*/ |
||||
Tree.prototype.treeToggles = function (clicked_d) { // eslint-disable-line
|
||||
|
||||
self.removeTooltip() |
||||
|
||||
d3.selectAll("[task_id='" + clicked_d.uuid + "']").each((d) => { |
||||
if (clicked_d !== d && d.children) { // eslint-disable-line
|
||||
d._children = d.children |
||||
d.children = null |
||||
self.treeUpdate(d) |
||||
} |
||||
}) |
||||
if (clicked_d._children) { |
||||
clicked_d.children = clicked_d._children |
||||
clicked_d._children = null |
||||
} else { |
||||
clicked_d._children = clicked_d.children |
||||
clicked_d.children = null |
||||
} |
||||
self.treeUpdate(clicked_d) |
||||
self.treeTooltip() |
||||
} |
||||
|
||||
/** |
||||
* update tree |
||||
*/ |
||||
Tree.prototype.treeUpdate = function (source) { |
||||
return new Promise((resolve, reject) => { |
||||
let tasks = this.tree.nodes(this.root) |
||||
let height = Math.max(500, tasks.length * this.config.barHeight + this.config.margin.top + this.config.margin.bottom) |
||||
let width = (this.config.nodesMax * 70) + (this.squareNum * (this.config.squareSize + this.config.squarePading)) + this.config.margin.left + this.config.margin.right + 50 |
||||
|
||||
d3.select('svg') |
||||
.transition() |
||||
.duration(this.duration) |
||||
.attr('height', height) |
||||
.attr('width', width) |
||||
|
||||
tasks.forEach((n, i) => { |
||||
n.x = i * this.config.barHeight |
||||
}) |
||||
|
||||
let task = this.svg.selectAll('g.node') |
||||
.data(tasks, (d) => { |
||||
return d.id || (d.id = ++this.i) |
||||
}) |
||||
|
||||
let nodeEnter = task.enter() |
||||
.append('g') |
||||
.attr('class', this.nodesClass) |
||||
.attr('transform', () => 'translate(' + source.y0 + ',' + source.x0 + ')') |
||||
.style('opacity', 1e-6) |
||||
|
||||
// Node circle
|
||||
nodeEnter.append('circle') |
||||
.attr('r', (this.config.barHeight / 3)) |
||||
.attr('class', 'task') |
||||
.attr('data-toggle', 'tooltip') |
||||
.attr('title', d => d.type ? d.type : '') |
||||
.attr('height', this.config.barHeight) |
||||
.attr('width', d => this.config.barWidth - d.y) |
||||
.style('fill', d => d.type ? tasksType[d.type].color : '#fff') |
||||
.attr('task_id', d => d.name) |
||||
.on('click', this.treeToggles) |
||||
|
||||
// Node text
|
||||
nodeEnter.append('text') |
||||
.attr('dy', 3.5) |
||||
.attr('dx', this.config.barHeight / 2) |
||||
.text((d) => { |
||||
return d.name |
||||
}) |
||||
|
||||
// Right node information
|
||||
nodeEnter.append('g') |
||||
.attr('class', 'stateboxes') |
||||
.attr('transform', (d, i) => 'translate(' + ((this.config.nodesMax * 60) - d.y) + ',0)') |
||||
.selectAll('rect').data((d) => d.instances) |
||||
.enter() |
||||
.append('rect') |
||||
.on('click', d => { |
||||
this.removeTooltip() |
||||
if (d.type === 'SUB_PROCESS') { |
||||
this.selfTree._subProcessHandle(d.subflowId) |
||||
} |
||||
}) |
||||
.attr('class', 'state') |
||||
.style('fill', d => d.state && tasksState[d.state].color || '#ffffff') |
||||
.attr('data-toggle', 'tooltip') |
||||
.attr('rx', d => d.type ? 0 : 12) |
||||
.attr('ry', d => d.type ? 0 : 12) |
||||
.style('shape-rendering', d => d.type ? 'crispEdges' : 'auto') |
||||
.attr('title', data => rtInstancesTooltip(data)) |
||||
.attr('x', (d, i) => (i * (this.config.squareSize + this.config.squarePading))) |
||||
.attr('y', -(this.config.squareSize / 2)) |
||||
.attr('width', 10) |
||||
.attr('height', 10) |
||||
.on('mouseover', () => { |
||||
d3.select(this).transition() |
||||
}) |
||||
.on('mouseout', () => { |
||||
d3.select(this).transition() |
||||
}) |
||||
|
||||
// Convert nodes to their new location。
|
||||
nodeEnter.transition() |
||||
.duration(this.duration) |
||||
.attr('transform', d => 'translate(' + d.y + ',' + d.x + ')') |
||||
.style('opacity', 1) |
||||
|
||||
// Node line
|
||||
task.transition() |
||||
.duration(this.duration) |
||||
.attr('class', this.nodesClass) |
||||
.attr('transform', d => 'translate(' + d.y + ',' + d.x + ')') |
||||
.style('opacity', 1) |
||||
|
||||
|
||||
// Convert the exit node to the new location of the parent node。
|
||||
task.exit().transition() |
||||
.duration(this.duration) |
||||
.attr('transform', d => 'translate(' + source.y + ',' + source.x + ')') |
||||
.style('opacity', 1e-6) |
||||
.remove() |
||||
|
||||
// Update link
|
||||
let link = this.svg.selectAll('path.link') |
||||
.data(this.tree.links(tasks), d => d.target.id) |
||||
|
||||
// Enter any new links in the previous location of the parent node。
|
||||
link.enter().insert('path', 'g') |
||||
.attr('class', 'link') |
||||
.attr('d', (d) => { |
||||
let o = { x: source.x0, y: source.y0 } |
||||
return this.diagonal({ source: o, target: o }) |
||||
}) |
||||
.transition() |
||||
.duration(this.duration) |
||||
.attr('d', this.diagonal) |
||||
|
||||
// Transition link
|
||||
link.transition() |
||||
.duration(this.duration) |
||||
.attr('d', this.diagonal) |
||||
|
||||
// Convert the exit node to the new location of the parent node
|
||||
link.exit().transition() |
||||
.duration(this.duration) |
||||
.attr('d', (d) => { |
||||
let o = { x: source.x, y: source.y } |
||||
return this.diagonal({ source: o, target: o }) |
||||
}) |
||||
.remove() |
||||
|
||||
// Hide the old position for a transition.
|
||||
tasks.forEach((d) => { |
||||
d.x0 = d.x |
||||
d.y0 = d.y |
||||
}) |
||||
resolve() |
||||
}) |
||||
} |
||||
|
||||
/** |
||||
* reset |
||||
*/ |
||||
Tree.prototype.reset = function () { |
||||
$('.d3-tree .tree').html('') |
||||
} |
||||
|
||||
/** |
||||
* Manually clear tooltip |
||||
*/ |
||||
Tree.prototype.removeTooltip = function () { |
||||
$('body').find('.tooltip.fade.top.in').remove() |
||||
} |
||||
|
||||
export default new Tree() |
@ -0,0 +1,72 @@
|
||||
/* |
||||
* Licensed to the Apache Software Foundation (ASF) under one or more |
||||
* contributor license agreements. See the NOTICE file distributed with |
||||
* this work for additional information regarding copyright ownership. |
||||
* The ASF licenses this file to You under the Apache License, Version 2.0 |
||||
* (the "License"); you may not use this file except in compliance with |
||||
* the License. You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
|
||||
import { formatDate } from '@/module/filter/filter' |
||||
import { tasksState } from '@/conf/home/pages/dag/_source/config' |
||||
|
||||
/** |
||||
* Node prompt dom |
||||
*/ |
||||
const rtInstancesTooltip = (data) => { |
||||
let str = `<div style="text-align: left;">` |
||||
str += `id : ${data.id}</br>` |
||||
str += `host : ${data.host}</br>` |
||||
str += `name : ${data.name}</br>` |
||||
str += `state : ${data.state ? tasksState[data.state].desc : '-'}(${data.state})</br>` |
||||
if (data.type) { |
||||
str += `type : ${data.type}</br>` |
||||
} |
||||
str += `startTime : ${data.startTime ? formatDate(data.startTime) : 'null'}</br>` |
||||
str += `endTime : ${data.endTime ? formatDate(data.endTime) : 'null'}</br>` |
||||
str += `duration : ${data.duration}</br>` |
||||
str += `</div>` |
||||
return str |
||||
} |
||||
|
||||
/** |
||||
* Calculate the maximum node length |
||||
* Easy to calculate the width dynamically |
||||
*/ |
||||
const rtCountMethod = list => { |
||||
let arr = [] |
||||
function count (list, t) { |
||||
let toggle = false |
||||
list.forEach(v => { |
||||
if (v.children && v.children.length > 0) { |
||||
if (!toggle) { |
||||
toggle = true |
||||
t += '*' |
||||
arr.push(t) |
||||
} |
||||
count(v.children, t) |
||||
} |
||||
}) |
||||
} |
||||
count(list, '*') |
||||
let num = 6 |
||||
arr.forEach(v => { |
||||
if (v.length > num) { |
||||
num = v.length |
||||
} |
||||
}) |
||||
return num |
||||
} |
||||
|
||||
export { |
||||
rtInstancesTooltip, |
||||
rtCountMethod |
||||
} |
After Width: | Height: | Size: 5.0 KiB |
@ -0,0 +1,248 @@
|
||||
<template> |
||||
<div class="main-layout-box"> |
||||
<m-secondary-menu :type="'projects'"></m-secondary-menu> |
||||
<m-list-construction :title="$t('树形图')"> |
||||
<template slot="conditions"></template> |
||||
<template slot="content"> |
||||
<div class="tree-view-index-model"> |
||||
<div class="tree-limit-select"> |
||||
<x-select v-model="limit" style="width: 70px;" @on-change="_onChangeSelect"> |
||||
<x-option |
||||
v-for="city in [{value:25},{value:50},{value:75},{value:100}]" |
||||
:key="city.value" |
||||
:value="city.value" |
||||
:label="city.value"> |
||||
</x-option> |
||||
</x-select> |
||||
<x-button |
||||
@click="_rtTasksDag" |
||||
v-if="$route.query.subProcessIds" |
||||
type="primary" |
||||
size="default" |
||||
icon="fa fa-reply"> |
||||
返回上一节点 |
||||
</x-button> |
||||
</div> |
||||
<div class="tasks-color"> |
||||
<div class="toolbar-color-sp"> |
||||
<a href="javascript:"> |
||||
<span>节点类型</span> |
||||
</a> |
||||
<a href="javascript:" v-for="(k,v) in tasksType"> |
||||
<i class="fa fa-circle" :style="{color:k.color}"></i> |
||||
<span>{{v}}</span> |
||||
</a> |
||||
</div> |
||||
<div class="state-tasks-color-sp"> |
||||
<a href="javascript:"> |
||||
<span>任务状态</span> |
||||
</a> |
||||
<a href="javascript:" v-for="(item) in tasksState"> |
||||
<i class="fa fa-square" :style="{color:item.color}"></i> |
||||
<span>{{item.desc}}</span> |
||||
</a> |
||||
</div> |
||||
</div> |
||||
<div class="tree-model" v-show="!isNodata"> |
||||
<div class="d3-tree"> |
||||
<svg class='tree' width="100%"></svg> |
||||
</div> |
||||
</div> |
||||
<m-no-data v-if="isNodata"></m-no-data> |
||||
</div> |
||||
<m-spin :is-spin="isLoading"></m-spin> |
||||
</template> |
||||
</m-list-construction> |
||||
</div> |
||||
|
||||
</template> |
||||
<script> |
||||
import _ from 'lodash' |
||||
import { mapActions } from 'vuex' |
||||
import Tree from './_source/tree' |
||||
import { uuid } from '@/module/util' |
||||
import mSpin from '@/module/components/spin/spin' |
||||
import mNoData from '@/module/components/noData/noData' |
||||
import { tasksType, tasksState } from '@/conf/home/pages/dag/_source/config' |
||||
import mSecondaryMenu from '@/module/components/secondaryMenu/secondaryMenu' |
||||
import mListConstruction from '@/module/components/listConstruction/listConstruction' |
||||
|
||||
export default { |
||||
name: 'tree-view-index-index', |
||||
data () { |
||||
return { |
||||
// limit |
||||
limit: 25, |
||||
// loading |
||||
isLoading: true, |
||||
// node type |
||||
tasksType: tasksType, |
||||
// node state |
||||
tasksState: tasksState, |
||||
// tree data |
||||
treeData: {}, |
||||
// is data |
||||
isNodata: false |
||||
} |
||||
}, |
||||
props: {}, |
||||
methods: { |
||||
...mapActions('dag', ['getViewTree']), |
||||
/** |
||||
* get tree data |
||||
*/ |
||||
_getViewTree () { |
||||
this.isLoading = true |
||||
|
||||
Tree.reset() |
||||
|
||||
this.getViewTree({ |
||||
processId: this.$route.params.id, |
||||
limit: this.limit |
||||
}).then(res => { |
||||
let data = _.cloneDeep(res) |
||||
this.treeData = data |
||||
if (!this.treeData.children) { |
||||
this.isLoading = false |
||||
this.isNodata = true |
||||
return |
||||
} |
||||
let recursiveChildren = (children) => { |
||||
if (children.length) { |
||||
_.map(children, v => { |
||||
v.uuid = `${uuid('uuid_')}${uuid() + uuid()}` |
||||
if (v.children.length) { |
||||
recursiveChildren(v.children) |
||||
} |
||||
}) |
||||
} |
||||
} |
||||
recursiveChildren(data.children) |
||||
// init tree |
||||
Tree.init({ |
||||
data: _.cloneDeep(data), |
||||
limit: this.limit, |
||||
selfTree: this |
||||
}).then(() => { |
||||
setTimeout(() => { |
||||
// this.isLoading = false |
||||
}, 100) |
||||
}) |
||||
}).catch(e => { |
||||
this.isLoading = false |
||||
if (!e.data) { |
||||
this.isNodata = true |
||||
} |
||||
}) |
||||
}, |
||||
|
||||
/** |
||||
* Return to the previous child node |
||||
*/ |
||||
_rtTasksDag () { |
||||
let getIds = this.$route.query.subProcessIds |
||||
let idsArr = getIds.split(',') |
||||
let ids = idsArr.slice(0, idsArr.length - 1) |
||||
let id = idsArr[idsArr.length - 1] |
||||
let query = {} |
||||
|
||||
if (id !== idsArr[0]) { |
||||
query = { subProcessIds: ids.join(',') } |
||||
} |
||||
this.$router.push({ path: `/projects/definition/tree/${id}`, query: query }) |
||||
}, |
||||
/** |
||||
* Subprocess processing |
||||
* @param subProcessId 子流程Id |
||||
*/ |
||||
_subProcessHandle (subProcessId) { |
||||
let subProcessIds = [] |
||||
let getIds = this.$route.query.subProcessIds |
||||
if (getIds) { |
||||
let newId = getIds.split(',') |
||||
newId.push(this.$route.params.id) |
||||
subProcessIds = newId |
||||
} else { |
||||
subProcessIds.push(this.$route.params.id) |
||||
} |
||||
this.$router.push({ path: `/projects/definition/tree/${subProcessId}`, query: { subProcessIds: subProcessIds.join(',') } }) |
||||
}, |
||||
_onChangeSelect (o) { |
||||
this.limit = o.value |
||||
this._getViewTree() |
||||
} |
||||
}, |
||||
watch: { |
||||
'$route.params.id' () { |
||||
this._getViewTree() |
||||
} |
||||
}, |
||||
created () { |
||||
this._getViewTree() |
||||
}, |
||||
mounted () { |
||||
}, |
||||
components: { mSpin, mSecondaryMenu, mListConstruction, mNoData } |
||||
} |
||||
</script> |
||||
|
||||
<style lang="scss" rel="stylesheet/scss"> |
||||
|
||||
.tree-view-index-model { |
||||
background: url('img/dag_bg.png'); |
||||
position: relative; |
||||
.tree-limit-select { |
||||
position: absolute; |
||||
right: 20px; |
||||
top: 22px; |
||||
z-index: 1; |
||||
} |
||||
.tasks-color { |
||||
min-height: 76px; |
||||
background: #fff; |
||||
padding-left: 20px; |
||||
position: relative; |
||||
padding-bottom: 10px; |
||||
.toolbar-color-sp { |
||||
padding: 12px 0; |
||||
} |
||||
|
||||
} |
||||
.tree-model { |
||||
width: calc(100%); |
||||
height: calc(100vh - 224px); |
||||
overflow-x: scroll; |
||||
} |
||||
.d3-tree { |
||||
padding-left: 30px; |
||||
.node { |
||||
text { |
||||
font: 11px sans-serif; |
||||
pointer-events: none; |
||||
} |
||||
} |
||||
rect { |
||||
cursor: pointer; |
||||
&.state { |
||||
stroke: #666; |
||||
shape-rendering: crispEdges; |
||||
} |
||||
} |
||||
path { |
||||
&.link{ |
||||
fill: none; |
||||
stroke: #666; |
||||
stroke-width: 2px; |
||||
} |
||||
} |
||||
circle { |
||||
stroke: #666; |
||||
fill: #0097e0; |
||||
stroke-width: 1.5px; |
||||
cursor: pointer; |
||||
} |
||||
} |
||||
} |
||||
|
||||
|
||||
</style> |
@ -0,0 +1,265 @@
|
||||
<template> |
||||
<div> |
||||
<div class="conditions-box"> |
||||
<!--<m-conditions @on-conditions="_onConditions"></m-conditions>--> |
||||
</div> |
||||
<div class="list-model" v-if="!isLoading"> |
||||
<template v-if="list.length"> |
||||
<div class="table-box"> |
||||
<table> |
||||
<tr> |
||||
<th> |
||||
<span>{{$t('编号')}}</span> |
||||
</th> |
||||
<th> |
||||
<span>{{$t('流程名称')}}</span> |
||||
</th> |
||||
<th> |
||||
<span>{{$t('开始时间')}}</span> |
||||
</th> |
||||
<th> |
||||
<span>{{$t('结束时间')}}</span> |
||||
</th> |
||||
<th> |
||||
<span>{{$t('crontab')}}</span> |
||||
</th> |
||||
<th> |
||||
<span>{{$t('失败策略')}}</span> |
||||
</th> |
||||
<th> |
||||
<span>{{$t('状态')}}</span> |
||||
</th> |
||||
<th> |
||||
<span>{{$t('创建时间')}}</span> |
||||
</th> |
||||
<th> |
||||
<span>{{$t('更新时间')}}</span> |
||||
</th> |
||||
<th width="80"> |
||||
<span>{{$t('操作')}}</span> |
||||
</th> |
||||
</tr> |
||||
<tr v-for="(item, $index) in list" :key="item.id"> |
||||
<td> |
||||
<span>{{parseInt(pageNo === 1 ? ($index + 1) : (($index + 1) + (pageSize * (pageNo - 1))))}}</span> |
||||
</td> |
||||
<td> |
||||
<span><a href="javascript:">{{item.processDefinitionName}}</a></span> |
||||
</td> |
||||
<td> |
||||
<span>{{item.startTime | formatDate}}</span> |
||||
</td> |
||||
<td> |
||||
<span>{{item.endTime | formatDate}}</span> |
||||
</td> |
||||
<td> |
||||
<span>{{item.crontab}}</span> |
||||
</td> |
||||
<td> |
||||
<span>{{item.failureStrategy}}</span> |
||||
</td> |
||||
<td> |
||||
<span>{{_rtReleaseState(item.releaseState)}}</span> |
||||
</td> |
||||
<td> |
||||
<span>{{item.createTime | formatDate}}</span> |
||||
</td> |
||||
<td> |
||||
<span>{{item.updateTime | formatDate}}</span> |
||||
</td> |
||||
<td> |
||||
<x-button |
||||
type="info" |
||||
shape="circle" |
||||
size="xsmall" |
||||
data-toggle="tooltip" |
||||
:title="$t('编辑')" |
||||
@click="_editTiming(item)" |
||||
icon="iconfont icon-bianji" |
||||
:disabled="item.releaseState === 'ONLINE'" > |
||||
</x-button> |
||||
<x-button |
||||
type="success" |
||||
shape="circle" |
||||
size="xsmall" |
||||
data-toggle="tooltip" |
||||
:title="$t('上线')" |
||||
@click="_online(item)" |
||||
icon="iconfont icon-erji-xiaxianjilu-copy" |
||||
v-if="item.releaseState === 'OFFLINE'"> |
||||
</x-button> |
||||
<x-button |
||||
type="warning" |
||||
shape="circle" |
||||
size="xsmall" |
||||
data-toggle="tooltip" |
||||
:title="$t('下线')" |
||||
icon="iconfont icon-erji-xiaxianjilu" |
||||
@click="_offline(item)" |
||||
v-if="item.releaseState === 'ONLINE'"> |
||||
</x-button> |
||||
</td> |
||||
</tr> |
||||
</table> |
||||
</div> |
||||
<div class="page-box"> |
||||
<x-page :current="pageNo" :total="total" show-elevator @on-change="_page"></x-page> |
||||
</div> |
||||
</template> |
||||
<template v-if="!list.length"> |
||||
<m-no-data></m-no-data> |
||||
</template> |
||||
</div> |
||||
<m-spin :is-spin="isLoading"></m-spin> |
||||
</div> |
||||
</template> |
||||
<script> |
||||
import _ from 'lodash' |
||||
import { mapActions } from 'vuex' |
||||
import '@/module/filter/formatDate' |
||||
import mSpin from '@/module/components/spin/spin' |
||||
import mTiming from '../../pages/list/_source/timing' |
||||
import mNoData from '@/module/components/noData/noData' |
||||
import { publishStatus } from '@/conf/home/pages/dag/_source/config' |
||||
|
||||
export default { |
||||
name: 'list', |
||||
data () { |
||||
return { |
||||
isLoading: false, |
||||
total: null, |
||||
pageNo: 1, |
||||
pageSize: 10, |
||||
list: [] |
||||
} |
||||
}, |
||||
props: { |
||||
}, |
||||
methods: { |
||||
...mapActions('dag', ['getScheduleList', 'scheduleOffline', 'scheduleOnline', 'getReceiver']), |
||||
/** |
||||
* return state |
||||
*/ |
||||
_rtReleaseState (code) { |
||||
return _.filter(publishStatus, v => v.code === code)[0].desc |
||||
}, |
||||
/** |
||||
* page |
||||
*/ |
||||
_page (val) { |
||||
this.pageNo = val |
||||
this._getScheduleList() |
||||
}, |
||||
/** |
||||
* Inquire list |
||||
*/ |
||||
_getScheduleList (flag) { |
||||
this.isLoading = !flag |
||||
this.getScheduleList({ |
||||
processDefinitionId: this.$route.params.id, |
||||
searchVal: '', |
||||
pageNo: this.pageNo, |
||||
pageSize: this.pageSize |
||||
}).then(res => { |
||||
this.list = res.data.totalList |
||||
this.total = res.data.total |
||||
this.isLoading = false |
||||
}).catch(e => { |
||||
this.isLoading = false |
||||
}) |
||||
}, |
||||
/** |
||||
* search |
||||
*/ |
||||
_onConditions (o) { |
||||
this.searchVal = o.searchVal |
||||
this.pageNo = 1 |
||||
this._getScheduleList('false') |
||||
}, |
||||
/** |
||||
* online |
||||
*/ |
||||
_online (item) { |
||||
this.pageNo = 1 |
||||
this.scheduleOnline({ |
||||
id: item.id |
||||
}).then(res => { |
||||
this.$message.success(res.msg) |
||||
this._getScheduleList('false') |
||||
}).catch(e => { |
||||
this.$message.error(e.msg || '') |
||||
}) |
||||
}, |
||||
/** |
||||
* offline |
||||
*/ |
||||
_offline (item) { |
||||
this.pageNo = 1 |
||||
this.scheduleOffline({ |
||||
id: item.id |
||||
}).then(res => { |
||||
this.$message.success(res.msg) |
||||
this._getScheduleList('false') |
||||
}).catch(e => { |
||||
this.$message.error(e.msg || '') |
||||
}) |
||||
}, |
||||
/** |
||||
* get email |
||||
*/ |
||||
_getReceiver (id) { |
||||
return new Promise((resolve, reject) => { |
||||
this.getReceiver({ processDefinitionId: id }).then(res => { |
||||
resolve({ |
||||
receivers: res.receivers && res.receivers.split(',') || [], |
||||
receiversCc: res.receiversCc && res.receiversCc.split(',') || [] |
||||
}) |
||||
}) |
||||
}) |
||||
}, |
||||
/** |
||||
* timing |
||||
*/ |
||||
_editTiming (item) { |
||||
let self = this |
||||
this._getReceiver(item.processDefinitionId).then(res => { |
||||
let modal = this.$modal.dialog({ |
||||
closable: false, |
||||
showMask: true, |
||||
escClose: true, |
||||
className: 'v-modal-custom', |
||||
transitionName: 'opacityp', |
||||
render (h) { |
||||
return h(mTiming, { |
||||
on: { |
||||
onUpdate () { |
||||
self.pageNo = 1 |
||||
self._getScheduleList('false') |
||||
modal.remove() |
||||
}, |
||||
close () { |
||||
modal.remove() |
||||
} |
||||
}, |
||||
props: { |
||||
item: item, |
||||
receiversD: res.receivers, |
||||
receiversCcD: res.receiversCc |
||||
} |
||||
}) |
||||
} |
||||
}) |
||||
}) |
||||
} |
||||
}, |
||||
watch: {}, |
||||
created () { |
||||
this._getScheduleList() |
||||
}, |
||||
mounted () {}, |
||||
components: { mSpin, mNoData } |
||||
} |
||||
</script> |
||||
|
||||
<style lang="scss" rel="stylesheet/scss"> |
||||
</style> |