<!-- 时序预测 --> <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: "#fa541c", //更改坐标轴文字颜色 fontSize: 12 //更改坐标轴文字大小 } }, boundaryGap: false, //从x轴起点开始 data: xAxis }, legend: { data: devisors, itemGap: 5, icon: "rect", itemWidth: 15, // 设置宽度 itemHeight: 15, textStyle: { color: "rgb(0, 119, 254)" //图例文字颜色 }, 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: "#fa541c", //更改坐标轴文字颜色 fontSize: 12 //更改坐标轴文字大小 } }, name: "", splitLine: { show: true, lineStyle: { type: "dashed", color: "rgb(0, 119, 254)" } } }, dataZoom: [ { show: true, backgroundColor:"rgb(0, 119, 254)", start: 0, end: 30, height: 18, textStyle: { color: "rgb(0, 119, 254)" } }, { 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 #1890fd80; } .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; } /* // 图形展示 */ #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>