<!-- 时序预测 --> <template> <div id='timeSeiesPrediction' class="tab"> <!-- 搜索条件 --> <div class="searchCondition"> <el-form status-icon :model="ruleForm" :rules="rules" ref="ruleForm" label-width='auto' class="demo-ruleForm"> <div class='condition' :style='{height:conditionHeight}' v-for='(parentItem,parentIndex) in ruleForm.dataPreviewLists' :key='parentIndex'> <el-row> <el-col :span='5' class="p-r-10"> <el-form-item label="连接名" :prop="'dataPreviewLists.' + parentIndex + '.dataSourceName'"> <el-select @change="dataSourceChange" v-model="parentItem.dataSourceName" placeholder="请选择连接"> <el-option v-for='(item,index) in datasSources' :key='index+99' :label="item.value" :value="item.value"> </el-option> </el-select> </el-form-item> </el-col> <el-col :span='5' class="p-r-10"> <el-form-item label="数据表名" :prop="'dataPreviewLists.' + parentIndex + '.tableName'"> <el-autocomplete @select='dataSourceTable' v-model="parentItem.tableName" :fetch-suggestions="querySearch" placeholder="请选择数据表"> </el-autocomplete> </el-form-item> </el-col> <el-col :span='5' class="p-r-10"> <el-form-item label="时序字段" :prop="'dataPreviewLists.' + parentIndex + '.queryColumnName'"> <el-select v-model="parentItem.queryColumnName" placeholder="请选择时序字段"> <el-option v-for='(item,index) in parentItem.datas.numberColumn' :key='item.value' :label="item.label" :value="item.value"> </el-option> </el-select> </el-form-item> </el-col> </el-row> <el-row> <el-col :span='5' class="p-r-10"> <el-form-item label="时间字段"> <el-select v-model="parentItem.timeColumnName" placeholder="请选择时间字段"> <el-option v-for='(item,index) in parentItem.datas.dateColumn' :key='item.value' :label="item.label" :value="item.value"> </el-option> </el-select> </el-form-item> </el-col> <el-col :span='10' class="p-r-10"> <el-form-item label="时间范围"> <el-date-picker v-model="dates" type="datetimerange" :picker-options="pickerOptions" :editable="false" range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期" value-format="yyyy-MM-dd HH:MM:ss" align="right"> </el-date-picker> </el-form-item> </el-col> </el-row> <!-- 字段名称 - 过滤条件 --> <el-row v-for='(items,indexs) in parentItem.queryConditions' :key='indexs+999'> <el-col :span='4' class="p-r-10"> <el-form-item label="过滤条件"> <el-select v-model="items.name" placeholder="请选择字段名称" @change="fieldNameChange" @visible-change='fieldNameFocus(indexs)'> <el-option v-for='(item,index) in parentItem.datas.sourcesField' :key='item.value' :label="item.value" :value="item.isStringL"> </el-option> </el-select> </el-form-item> </el-col> <el-col :span='3' class="p-r-10"> <el-select v-model="items.symbol" style='line-height:40px' placeholder="类型"> <el-option v-for='(item,index) in parentItem.datas.symbolNum' :key='index' :label="item.label" :value="item.value"> </el-option> </el-select> </el-col> <el-col :span='3' style='margin-top:5px;'> <el-input v-if='items.select' v-model.trim="items.value" placeholder="输入字段值"></el-input> <el-date-picker v-if='!items.select' v-model="items.value" type="datetime" placeholder="选择字段值" align="right" value-format="yyyy-MM-dd HH:mm:ss"> </el-date-picker> </el-col> <el-col :span='1' class="addendumNames text-right"> <i @click='deleteAutoIcons("queryConditions",indexs)' class="iconfont icon-qiyong deleteIcon"></i> </el-col> </el-row> <el-row> <el-col :span='10' class="addFilter"> <span @click='filterCondition(parentIndex)'><i class="iconfont icon-jia"></i> 添加</span> </el-col> <el-col :offset='1' :span='10'> <el-button size='mini' type='primary' @click='getCharts("ruleForm")'>搜索</el-button> <el-button size='mini'>保存</el-button> </el-col> </el-row> <!-- 伸缩按钮 --> <div class="telescopic" title='条件窗开关' @click='conditional'> <i :class="conditionIcon"></i> </div> </div> </el-form> </div> <!-- 预测条件 --> <div class="searchCondition ycSearch"> <el-form status-icon :model="ruleForms" :rules="rules" ref="ruleForms" label-width='auto' class="demo-ruleForm"> <div class='condition'> <el-row> <el-col :span='5' class="p-r-10"> <el-form-item label="预测模型" prop='modelType'> <el-select v-model="ruleForms.modelType" placeholder="请选择预测模型"> <el-option v-for='(item,index) in models' :key='index+99' :label="item.value" :value="item.value"> </el-option> </el-select> </el-form-item> </el-col> <el-col :span='5' class="p-r-10"> <el-form-item label="验证比例" prop='scale'> <el-input v-model.native.number="ruleForms.scale" placeholder="输入验证比例"></el-input> </el-form-item> </el-col> <el-col :span='5' class="p-r-10"> <el-form-item label="周期长度" prop='trainCycle'> <el-input type='number' v-model.native.number="ruleForms.trainCycle" placeholder="输入周期长度"> </el-input> </el-form-item> </el-col> <el-col :span='5'> <el-button size='mini' type='primary' style='margin-top:5px' @click='predictionModel("ruleForms")'>预测 </el-button> </el-col> </el-row> </div> </el-form> </div> <!-- 图形展示 --> <div id='charts' ref='Echarts'> </div> </div> </template> <script> import echarts from 'echarts' import project from '../../mixins/project' import devisor from '../../util/devisor' import { randomColor, deepClone } from '../../util' import qs from 'qs'; import { notify } from '../../util/item' let moment = require('moment'); export default { data() { return { addText: '添加数据', dates: '', datasSources: [], fieldIndex: 0, chart: '', $echarts: '', datasSourceTypes: [], conditionHeight: 'auto', //条件窗口高度 conditionIcon: 'el-icon-arrow-up', //条件窗口高度 modelsDatas: null, pickerOptions: { shortcuts: [{ text: '最近一周', onClick(picker) { let end = moment().subtract('days', 0).format('YYYY-MM-DD HH:mm:ss'); let start = moment().subtract('days', 7).format('YYYY-MM-DD HH:mm:ss'); // end = moment(end, 'YYYY-MM-DD HH:mm:ss').valueOf(); // start = moment(start, 'YYYY-MM-DD HH:mm:ss').valueOf(); picker.$emit('pick', [start, end]); } }, { text: '最近一个月', onClick(picker) { let end = moment().subtract('days', 0).format('YYYY-MM-DD HH:mm:ss'); let start = moment().subtract('days', 30).format('YYYY-MM-DD HH:mm:ss'); // end = moment(end, 'YYYY-MM-DD HH:mm:ss').valueOf(); // start = moment(start, 'YYYY-MM-DD HH:mm:ss').valueOf(); picker.$emit('pick', [start, end]); } }, { text: '最近三个月', onClick(picker) { let end = moment().subtract('days', 0).format('YYYY-MM-DD HH:mm:ss'); let start = moment().subtract('days', 90).format('YYYY-MM-DD HH:mm:ss'); // end = moment(end, 'YYYY-MM-DD HH:mm:ss').valueOf(); // start = moment(start, 'YYYY-MM-DD HH:mm:ss').valueOf(); picker.$emit('pick', [start, end]); } }], }, models: [{ label: 'ARIMA', value: 'ARIMA' }, { label: 'LSTM', value: 'LSTM' }], ruleForm: { dataPreviewLists: [{ dataSourceName: '', //连接名称 tableName: '', //数据表名称 timeColumnName: '', //时间字段名称 queryColumnName: '', //待查询字段名称 datas: { //存储改变字段,进行数据区分,比较改变数值,修改不必要字段 dateColumn: [], //时间字段 numberColumn: [], //查询字段 sourcesField: [], //字段名称 symbolNum: [] //类型 }, queryConditions: [{ name: '', symbol: '', value: '', select: true }], //过滤条件 }] }, ruleForms: { trainCycle: '', scale: '', modelType: '', }, symbolNum1: [{ label: '>', value: '>' }, { label: '>=', value: '>=' }, { label: '<', value: '<' }, { label: '<=', value: '<=' }, { label: '=', value: '=' } ], symbolNum2: [{ label: '=', value: '=' }], rules: { dataPreviewLists: [{ queryConditions: [{ name: [{ required: true, message: '请选择字段名称', trigger: 'change' }], //检测字段 minVal: [{ required: true, message: '请输入小于阈值', trigger: 'blur' }], //小于阈值 maxVal: [{ required: true, message: '请输入大于阈值', trigger: 'blur' }], //大于阈值 }], queryColumnName: [{ required: true, message: '请选择查询字段', trigger: 'change' }], timeColumnName: [{ required: true, message: '请选择时间字段', trigger: 'change' }], tableName: [{ required: true, message: '请选择数据表', trigger: 'change' }], dataSourceName: [{ required: true, message: '请选择连接', trigger: 'change' }], }], modelType: [{ required: true, message: '请选择预测模型', trigger: 'change' }], scale: [{ required: true, message: '请输入验证比例', trigger: 'blur' }], trainCycle: [{ required: true, message: '请输入周期长度', trigger: 'blur' }], }, }; }, mixins: [project], components: {}, computed: {}, mounted() { /** * 页面注意事项 * 每组数值,动态进行数据的添加,方法相同,但是数据组不同 */ this.$nextTick(function () { setTimeout(() => { this.defaultDrawCanvas() }, 300); }) this.dataSourceChanges(); }, methods: { defaultDrawCanvas() { // 默认图形占位 let keys = {}; for (let i = 10; i > 0; i--) { keys[moment().subtract('days', i * 3).format('YYYY-MM-DD HH:mm:ss')] = 0 } this.drawChart([{ "PN": keys }]) }, getCharts(formName = 'ruleForm') { // 获取图形参数 if (this.dates === null || this.dates === '') { this.$notify({ type: 'warning', message: '请选择需要查询的时间范围' }) return } this.$refs[formName].validate((valid) => { if (valid) { // 成功请求数据 this.getChart() } else { console.log('error submit!!'); return false; } }); }, predictionModel(formName) { // 预测模型数据 this.$refs[formName].validate((valid) => { if (valid) { // 成功请求数据 let forms = deepClone(this.ruleForms); if (this.modelsDatas === null) { this.$notify({ type: 'warning', message: '请先进行基础条件搜索再进行预测' }) return } forms['trainDatas'] = this.modelsDatas; // 预测数据请求 this.$http.post(this.nozzle.dataModelQueryPredictData, qs.stringify({ dataJson: JSON.stringify(forms) }), { headers: { 'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8' } }).then(response => { notify(response) if (response.data.code !== 200) { this.parameters({}, 2); } else { this.parameters(response.data.data, 2); } }) } else { console.log('error submit!!'); return false; } }); }, drawChart(res) { let [xAxis, devisors, seriesFactorValue, devisorsShowOrHidd, colorPump] = [ [], [], [], [], 0 ]; //自定义色彩搭配 let colors = ['#fa541c', '#13c2c2', '#52c41a', '#2f54eb', '#fa8c16', '#1890ff', '#f5222d', '#91d5ff', '#0050b3', '#10239e', '#3366CC' ]; for (let [keys, values] of res.entries()) { for (let [ii, vv] of Object.entries(values)) { var xName = devisor[ii.toLowerCase()] || '无'; // 站点图例 devisors.push(xName); var color = colors[colorPump]; colorPump++; // 站点内容 seriesFactorValue.push({ name: xName, type: 'line', smooth: true, itemStyle: { normal: { color: color } }, areaStyle: { normal: { color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [{ offset: 0, color: color + 'E6' }, { offset: 0.6, color: color + '66' }, { offset: 1, color: color }]) } }, smooth: true, data: Object.values(vv) }) if (keys === 0 && Object.keys(values)[0] === ii) { xAxis = Object.keys(vv); } } } // 默认显示数据 if (devisors.length > 0) { for (let [index, values] of devisors.entries()) { if (index > 3) { var keys = {}; keys[devisors[index]] = false; devisorsShowOrHidd.push(keys) } else { var keys = {}; keys[devisors[index]] = true; devisorsShowOrHidd.push(keys) } } } /** * 流速(VA)处理对比分析图 */ let dom = document.getElementById('charts'); let myChart = echarts.init(dom); this.$echarts = myChart; let option = { // backgroundColor: '#F6F9FE', title: { text: '', textStyle: { color: '#333333', //标题文字颜色 }, y: 15, x: 'center' }, tooltip: { trigger: 'axis' }, grid: { x: 55, x2: 25, }, xAxis: { axisLine: { //设置地名及x轴线条颜色 show: true, lineStyle: { type: 'dashed', //设置x轴线条样式 color: '#ced0d1' } }, axisLabel: { show: true, textStyle: { color: '#f15353', //更改坐标轴文字颜色 fontSize: 12 //更改坐标轴文字大小 } }, boundaryGap: false, //从x轴起点开始 data: xAxis }, legend: { data: devisors, itemGap: 5, icon: "rect", itemWidth: 15, // 设置宽度 itemHeight: 15, textStyle: { color: '#333', //图例文字颜色 }, y: 20, selected: devisorsShowOrHidd, padding: [0, 30, 0, 0], x: 'center' }, yAxis: { "axisLine": { //y轴 "show": false, lineStyle: { color: '#ced0d1' } }, "axisTick": { //y轴刻度线 "show": false }, axisLabel: { show: true, textStyle: { color: '#f15353', //更改坐标轴文字颜色 fontSize: 12 //更改坐标轴文字大小 } }, name: '', splitLine: { show: true, lineStyle: { type: 'dashed', color: '#1890fd80' } }, }, dataZoom: [{ show: true, start: 0, end: 30, height: 18, textStyle: { color: '#f15353' }, handleStyle: { color: '#0B3C6F', shadowBlur: 3, shadowColor: 'rgba(0, 0, 0, 0.6)', shadowOffsetX: 2, shadowOffsetY: 2 }, }, { type: 'inside', realtime: true, start: 0, height: 18, end: 15 } ], series: seriesFactorValue }; myChart.clear(); myChart.setOption(option); this.$nextTick(() => { this.$echarts.resize(); }) this.init(); }, init() { // 图形自适应 let that = this; this.$nextTick(() => { // 不加延时,会出现获取不到dom的情况 setTimeout(() => { window.addEventListener('resize', function () { that.$echarts.resize(); }) }, 100) }) }, async getChart() { // 查询图形数据 let newDatas = null; newDatas = deepClone(this.ruleForm); // 输出处理 for (let [keys, items] of Object.entries(newDatas.dataPreviewLists)) { items['queryCondition'] = []; // 过滤条件处理 if (items['queryConditions'].length !== 0) { for (let [i, v] of Object.entries(items['queryConditions'])) { var pa = new RegExp(/(^[\-0-9][0-9]*(.[0-9]+)?)$/); // 处理字段值类型是否是string 或者number let paVal = pa.test(v['value']) ? (+v['value']) : ("'" + v['value'] + "'"); let da = v['name'].split(',')[0] + " " + v['symbol'] + " " + paVal; //过滤条件非必选 // if (v['name'] === '' || v['symbol'] === '' || v['value'] === '') { // this.$notify({ // type: 'warning', // message: '请将数据填充完全,再进行搜索' // }) // return // } items['queryCondition'].push(da) } } delete items['queryConditions'] delete items['datas'] } newDatas['dataPreviewLists'][0]['startTime'] = this.dates[0]; newDatas['dataPreviewLists'][0]['endTime'] = this.dates[1]; let response = await this.getHttpDatas(this.nozzle.dataModelGetDataComparisonList, newDatas[ 'dataPreviewLists'][0], 2, 2) this.parameters(response) }, parameters(response, dif = 1) { // 图形数据测试,是否有参数 /** * 图形数据测试,是否有参数 * @param {Object} response 图形参数 * */ if (JSON.stringify(response) === '{}' || JSON.stringify(response) === 'null') { this.defaultDrawCanvas() this.modelsDatas = null; return } // 有数据情况下,窗口缩小 this.conditionIcon = 'el-icon-arrow-down' this.conditionHeight = '80px'; let queryColumnName = this.ruleForm.dataPreviewLists[0]['queryColumnName'] let res = []; if (dif === 2) { // 预测数据图形渲染 let newNames = `${queryColumnName}预测` res[0] = {}; res[1] = {}; res[0][queryColumnName] = this.modelsDatas; res[1][newNames] = response; } else { // 图形渲染 res[0] = {}; res[0][queryColumnName] = response; this.$refs['ruleForms'].resetFields(); // 数据存储 if (response === {}) { this.modelsDatas = null } else { this.modelsDatas = response; } } // 图形绘制 this.drawChart(res) }, async dataSourceChanges() { // 连接列表渲染 let datas = await this.getHttpDatas(this.nozzle.queryDataSrouceName, { dbType: 'Relational' }); let dataBase = []; for (let item of datas.values()) { dataBase.push({ value: item }) } this.datasSources = dataBase; }, async dataSourceChange(val, fields = null) { // 数据表内容获取以及填充 this.ruleForm.dataPreviewLists[0]['tableName'] = ''; let datas = await this.getHttpDatas(this.nozzle.getAllTableNameByDateSourceName, { dataSourceName: val, pageSize: 999999, pageNo: 1 }); let dataBase = []; for (let item of datas.list.values()) { dataBase.push({ value: item }) } // 数据表内容 this.datasSourceTypes = dataBase; }, async dataSourceTable() { // 数据表内容变化发生改变 // 展开窗口 this.conditionHeight = 'auto' this.conditionIcon = 'el-icon-arrow-up' let val = this.ruleForm.dataPreviewLists[0].tableName; let dataBase = this.ruleForm.dataPreviewLists[0].dataSourceName; this.sourceFieldChange(dataBase, val); this.getZdname(dataBase, val); // 清空字段名 for (let item of this.ruleForm.dataPreviewLists[0].queryConditions.values()) { item['name'] = ''; } this.ruleForm.dataPreviewLists[0].datas.sourcesField = []; this.ruleForm.dataPreviewLists[0].timeColumnName = '' this.ruleForm.dataPreviewLists[0].queryColumnName = '' }, async sourceFieldChange(dataSourceName, tableName) { /** * 检测字段填充 * @param {String} dataSourceName 连接名称 * @param {String} tableName 表名称 */ const datas = await this.getHttpDatas(this.nozzle.getDataStructureByTableName, { dataSourceName: dataSourceName, tableName: tableName }) let isString = 1; //是否是string类型,是为1,否为2 if (datas['list'].length !== 0) { let newDatas = []; for (let item of datas['list'].values()) { // 判断是否为字符串类型 1 为string 2为date 3 if (item['columnType'].includes('String')) { isString = 1; } else if (item['columnType'].includes('Date')) { isString = 2; } else { isString = 3; } newDatas.push({ value: item['columnName'], isStringL: item['columnName'] + ',' + isString }) } this.ruleForm.dataPreviewLists[0].datas.sourcesField = newDatas; } else { this.ruleForm.dataPreviewLists[0].datas.sourcesField = []; } }, async getZdname(dataSourceName, tableName) { // 字段名称 let response = await this.getHttpDatas(this.nozzle.getDataStructureByTableNameAndType, { dataSourceName: dataSourceName, tableName: tableName }) // 填充时间字段和查询字段 this.ruleForm.dataPreviewLists[0].datas.dateColumn = this.enOrChin(response['dateColumn']); this.ruleForm.dataPreviewLists[0].datas.numberColumn = this.enOrChin(response['numberColumn']); }, enOrChin(datas = []) { // 英文转换成中文 if (datas.length !== 0) { let newDevisor = []; for (const item of datas.values()) { newDevisor.push({ label: devisor[item.toLowerCase()], value: item }) } return newDevisor } }, fieldNameFocus(indexs = 0) { // 获取当前字段类行数 this.fieldIndex = indexs }, fieldNameChange(val) { /** * 字段名称 * @param {Number} parentIndex 是第几个窗口 * @param {Number} index 是第几行数据 */ let isString = val.split(',')[1]; // 是字符串类型,类型判断只能展示= this.ruleForm.dataPreviewLists[0].queryConditions[this.fieldIndex].symbol = '' if (isString === '1') { // string类型 this.ruleForm.dataPreviewLists[0].datas.symbolNum = this.symbolNum2; this.ruleForm.dataPreviewLists[0].queryConditions[this.fieldIndex].select = true; } else if (isString === '2') { // 字段值转换成时间框 this.ruleForm.dataPreviewLists[0].datas.symbolNum = this.symbolNum1; this.ruleForm.dataPreviewLists[0].queryConditions[this.fieldIndex].select = false; } else { // 默认情况 this.ruleForm.dataPreviewLists[0].datas.symbolNum = this.symbolNum1; this.ruleForm.dataPreviewLists[0].queryConditions[this.fieldIndex].select = true; } }, querySearch(queryString, cb) { // 数据表内容填充 var restaurants = this.datasSourceTypes; var results = queryString ? restaurants.filter(this.createFilter(queryString)) : restaurants; // 调用 callback 返回建议列表的数据 if (results.length !== 0) { for (let item of results.values()) { item['value'] = item['value']; } } cb(results); }, createFilter(queryString) { return (restaurant) => { return (restaurant.value.indexOf(queryString) !== -1); }; }, filterCondition(index) { /** * 添加检测字段,修改时刻,渲染并进行赋值 * @param {Number} index 当前选中的删除区域 */ this.ruleForm.dataPreviewLists[index].queryConditions.push({ name: '', symbol: '', value: '', select: true }); }, conditional() { // 条件窗开关 if (this.conditionHeight === '80px') { this.conditionHeight = 'auto' this.conditionIcon = 'el-icon-arrow-up' } else { this.conditionHeight = '80px' this.conditionIcon = 'el-icon-arrow-down' } }, deleteAutoIcons(fieldName, index) { // 删除字段 this.ruleForm.dataPreviewLists[0][fieldName].splice(index, 1); }, } } </script> <style> #timeSeiesPrediction { height: 98%; background-color: #fff; overflow-x: hidden; } #timeSeiesPrediction .btns { height: 72px; line-height: 76px; overflow: hidden; padding: 20px; } .btns .dateSearch { height: 31px; } .btns .el-date-editor.el-input__inner { width: 377px !important; height: 31px; } .el-date-editor .el-range__icon, .el-date-editor .el-range-separator { line-height: 23px; } .searchButton span { line-height: 20px; } .ycSearch .el-form-item { margin-bottom: 0px; } .ycSearch .demo-ruleForm { overflow: initial; } /* // 搜索条件 */ .searchCondition { width: 96%; padding: 20px 20px 0 20px; overflow: hidden; } .searchCondition .condition { position: relative; width: 100%; padding: 20px 15px 20px 25px; border-radius: 5px; border: 1px dashed #6fa5ff; margin-bottom: 20px; overflow: hidden; } .searchCondition:hover{ border: 1px solid rgba(111, 165, 255, 1); box-shadow: 2px 2px 6px rgba(24, 144, 253, 0.5); } .condition .sign { border: 1px solid #ccc; } .el-autocomplete { width: 100%; } /* // 标记 */ .sign { position: absolute; width: 28px; height: 28px; text-align: center; line-height: 17px; top: -8px; left: -8px; font-size: 12px; border: 1px dashed #ccc; background: #d8d7d73d; border-radius: 30px; } .sign span { position: absolute; bottom: 1px; right: 5px; } /* // 打开当前按钮 */ .telescopic { position: absolute; right: 10px !important; top: 10px; color: #ccc; cursor: pointer; } .aboutType { padding-left: 15px; } .addendumNames { text-align: left; padding-left: 10px; cursor: pointer; line-height: 40px; color: #f15353; } /* // 添加按钮 */ .addFilter { padding-left: 80px; text-align: center; } .addFilter span { display: inline-block; width: 100%; border: 1px dashed #ccc; height: 31px; line-height: 31px; cursor: pointer; color: #fff; } .icon-jia { color: #fff; } /* // 图形展示 */ #charts { width: 100%; height: 400px; margin: 20px 0; text-align: center; } .el-icon-arrow-down:before { content: "\E6DF" !important; color: rgb(204, 204, 204); } .el-input__icon{ line-height: 30px; } </style>