dynamicRingdiffer.vue 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584
  1. <template>
  2. <el-dialog
  3. :visible.sync="isOpenDialog"
  4. :close-on-click-modal="false"
  5. :modal-append-to-body="false"
  6. @close="cancelHandle"
  7. custom-class="dynamic-differ-dialog"
  8. center
  9. width="1090px"
  10. top="8vh"
  11. v-dialogDrag
  12. >
  13. <div slot="title" style="display: flex; align-items: center">
  14. <span style="font-size: 16px">{{ title }}</span>
  15. </div>
  16. <div class="dialog-main">
  17. <ul class="add-cont">
  18. <li class="add-li" v-for="(list, index) in addList" :key="index">
  19. <span class="li-tag">{{ list.tag }}</span>
  20. <el-select
  21. v-model="list.target"
  22. v-loadMore="searchLoad"
  23. :filterable="!list.target"
  24. clearable
  25. placeholder="请输入指标名称"
  26. style="width: 400px"
  27. @change="chooseTarget"
  28. @clear="clearHandle(index)"
  29. remote
  30. :remote-method="searchTarget"
  31. @click.native="inputFocusHandle"
  32. >
  33. <i slot="prefix" class="el-input__icon el-icon-search"></i>
  34. <el-option
  35. v-for="item in searchOptions"
  36. :key="item.EdbInfoId"
  37. :label="item.EdbName"
  38. :value="item.EdbInfoId"
  39. >
  40. </el-option>
  41. </el-select>
  42. <i class="el-icon-tickets" style="color:#409EFF;font-size:18px" @click="$emit('lookHistory',list.target)" v-if="list.target"/>
  43. <i
  44. class="el-icon-error del-tag"
  45. v-if="index > 3"
  46. @click="delTarget(index)"
  47. />
  48. <span class="target-date" v-if="list.start_date">{{
  49. `${list.start_date}至${list.end_date}`
  50. }}</span>
  51. </li>
  52. </ul>
  53. <span class="add-icon" @click="addTargetHandle">
  54. <i
  55. class="el-icon-circle-plus-outline"
  56. style="color: #5882ef; font-size: 16px"
  57. />
  58. 添加更多参数
  59. </span>
  60. <div class="computed-min">
  61. <div class="computed-section">
  62. <div>
  63. <label class="label">空值处理
  64. <el-tooltip placement="top">
  65. <div slot="content" v-html="formTips['null-val']" style="width:300px;line-height:20px;"/>
  66. <i class="el-icon-question"/>
  67. </el-tooltip>
  68. </label>
  69. <el-select
  70. v-model="otherForm.nullValueWay"
  71. placeholder="请选择"
  72. >
  73. <el-option
  74. v-for="item in nullWayOptions"
  75. :key="item.value"
  76. :label="item.label"
  77. :value="item.value"
  78. >
  79. </el-option>
  80. </el-select>
  81. </div>
  82. <div style="margin-left: 120px" v-if="showMaxNullDeal">
  83. <label class="label">MAX、MIN空值处理
  84. <el-tooltip placement="top">
  85. <div slot="content" v-html="formTips['max-null-val']" style="width:300px;line-height:20px;"/>
  86. <i class="el-icon-question"/>
  87. </el-tooltip>
  88. </label>
  89. <el-select
  90. v-model="otherForm.maxNullWay"
  91. placeholder="请选择"
  92. >
  93. <el-option label="等于0" value="等于0" />
  94. <el-option label="跳过空值" value="跳过空值" />
  95. </el-select>
  96. </div>
  97. <el-button type="primary" @click="getResult" style="margin-left: 10px">一键计算</el-button>
  98. </div>
  99. <div class="computed-section">
  100. <label class="label">计算公式
  101. <el-tooltip placement="top">
  102. <div slot="content" v-html="formTips['formula']" style="width:300px;line-height:20px;"/>
  103. <i class="el-icon-question"/>
  104. </el-tooltip>
  105. </label>
  106. <ul class="formula-list">
  107. <li style="margin-bottom: 15px;">
  108. <el-input placeholder="请输入公式" v-model="formulaList[0].formula" clearable style="width: 220px"/>
  109. <span v-if="formulaDateArr.length" class="date-section-text">{{formulaDateArr[formulaDateArr.length-1]}}(含)之后</span>
  110. <span class="example-txt">公式示例:A*0.5+B*C*1.2+120-MAX(A,B,C) &nbsp;函数支持:MAX(),MIN(),ln(A),log(a,A)</span>
  111. </li>
  112. <li class="formula-item" v-for="(item,index) in formulaList.slice(1)" :key="index+1">
  113. <el-input
  114. placeholder="请输入公式"
  115. v-model="item.formula"
  116. clearable
  117. style="width: 220px"
  118. />
  119. <el-date-picker
  120. v-model="item.date"
  121. type="date"
  122. value-format="yyyy-MM-dd"
  123. style="margin: 0 10px;width: 220px"
  124. placeholder="选择日期"
  125. @change="selectFormulaDate($event,item)"
  126. />
  127. <i class="el-icon-circle-close" style="font-size:20px;" @click="removeFormulaItem(index+1)"/>
  128. <template v-if="formulaDateArr.length&&item.date">
  129. <span v-if="item.date===formulaDateArr[0]" class="date-section-text">{{formulaDateArr[0]}}之前</span>
  130. <span v-else class="date-section-text">{{formulaDateArr[formulaDateArr.findIndex(_ =>_ ===item.date)-1]}}(含)——{{item.date}}</span>
  131. </template>
  132. </li>
  133. </ul>
  134. </div>
  135. <el-button icon="el-icon-plus" style="margin-left:70px;" @click="addFormulaHandle">新增分段</el-button>
  136. </div>
  137. <el-table
  138. border
  139. :data="resultData"
  140. v-if="resultData.length"
  141. max-height="320"
  142. >
  143. <el-table-column
  144. v-for="item in tableColums"
  145. :key="item.label"
  146. :label="item.label"
  147. :width="item.widthsty"
  148. :min-width="item.minwidthsty"
  149. align="center"
  150. >
  151. <template slot-scope="scope">
  152. <span :style="scope.row.isPredict&&'color: orange'">{{ scope.row[item.key] }}</span>
  153. </template>
  154. </el-table-column>
  155. <div slot="empty" style="padding:20px 0 30px;">
  156. <tableNoData text="暂无指标" size="mini"/>
  157. </div>
  158. </el-table>
  159. </div>
  160. <div class="dia-bot">
  161. <el-button
  162. type="primary"
  163. style="margin-right: 20px"
  164. @click="saveHandle"
  165. >保存</el-button
  166. >
  167. <el-button type="primary" plain @click="cancelHandle">取消</el-button>
  168. </div>
  169. </el-dialog>
  170. </template>
  171. <script>
  172. import * as preDictEdbInterface from '@/api/modules/predictEdbApi.js';
  173. const tag_arr = [];
  174. for(var i=0;i<26;i++) tag_arr.push(String.fromCharCode(65+i));
  175. export default {
  176. name: '',
  177. props: {
  178. isOpenDialog: {
  179. type: Boolean,
  180. },
  181. title: {
  182. type: String,
  183. default: '设置环比增加值',
  184. },
  185. dialog_formula: {
  186. type: String,
  187. },
  188. edbList: {
  189. type: Array,
  190. },
  191. },
  192. computed: {
  193. /* max空值处理显示 当输入的公式包含MAX、MIN且空值处理为0时,输入公式失焦后出现右侧选项; */
  194. showMaxNullDeal() {
  195. let haveMaxOrMin = this.formulaList.some(_ => _.formula.toUpperCase().includes('MAX') || _.formula.toUpperCase().includes('MIN'))
  196. return haveMaxOrMin && this.otherForm.nullValueWay===5
  197. },
  198. formulaDateArr() {
  199. return this.formulaList.map(_ => _.date).filter(_ => _).sort((a,b) => new Date(a)-new Date(b))
  200. }
  201. },
  202. watch: {
  203. isOpenDialog(newval) {
  204. /* 回显 */
  205. if (this.edbList.length && newval) {
  206. this.addList = _.cloneDeep(this.edbList);
  207. this.formula = this.dialog_formula;
  208. this.searchOptions = this.edbList.map(item => ({
  209. EdbInfoId: item.target,
  210. EdbName: item.name,
  211. }))
  212. }
  213. },
  214. },
  215. data() {
  216. return {
  217. addList: [
  218. {
  219. tag: tag_arr[0],
  220. name: '',
  221. target: '',
  222. start_date: '',
  223. end_date: '',
  224. },
  225. {
  226. tag: tag_arr[1],
  227. name: '',
  228. target: '',
  229. start_date: '',
  230. end_date: '',
  231. },
  232. {
  233. tag: tag_arr[2],
  234. name: '',
  235. target: '',
  236. start_date: '',
  237. end_date: '',
  238. },
  239. {
  240. tag: tag_arr[3],
  241. name: '',
  242. target: '',
  243. start_date: '',
  244. end_date: '',
  245. },
  246. ],
  247. searchOptions: [],
  248. formula: '', //计算公式
  249. dataloading: false,
  250. search_have_more: false,
  251. search_page: 1,
  252. current_search:'',
  253. tableColums: [
  254. {
  255. label: '日期',
  256. key: 'DataTime',
  257. },
  258. {
  259. label: '环比增加值',
  260. key: 'Value',
  261. },
  262. ],
  263. formulaList: [
  264. { formula: '', },
  265. { formula: '',date: '' }
  266. ],
  267. otherForm: {
  268. nullValueWay: 1,
  269. maxNullWay: '等于0'
  270. },//空值处理
  271. nullWayOptions: [
  272. { label: '查找前后35天最近值',value: 1 },
  273. { label: '不计算',value: 2 },
  274. { label: '前值填充',value: 3 },
  275. { label: '后值填充',value: 4 },
  276. { label: '等于0',value: 5 },
  277. ],
  278. formTips: {
  279. 'null-val': `1、查找前后35天最近值:在参与计算的日期序列上某指标无值时,该指标往前/往后找距离最近的值作为当天的值进行计算,遍历允许跨年,往前最多35天,往后最多35天,<br>
  280. 2、不计算:只要有一个指标在某个日期没有值(即空值),则计算指标在该日期没有值 <br>
  281. 3、前值填充:空值优先以最近的前值填充,没有前值时,用后值填充 <br>
  282. 4、后值填充:空值优先以最近的后值填充,没有前值时,用后值填充 <br>
  283. 5、等于0:空值以0值参与计算 注意:此处缺失值的处理,作用于数据全部时间段`,
  284. 'max-null-val': `MAX、MIN公式中指标存在空值时按如下规则处理:<br>
  285. 1、等于0,空值用0参与计算;<br>
  286. 2、跳过空值,去除空值指标,剩余指标进行计算,若该日期所有指标均为空值,则该日期无值;`,
  287. 'formula':`1、支持新增分段,实现不同分段使用不同的计算公式,若未新增分段,则所有日期序列用统一公式计算<br>
  288. 2、新增分段需配置新公式和时间节点,在时间节点之前(不含)使用新公式,在时间节点之后(含)使用已配置公式,每个分段公式支持修改<br>
  289. 3、分段时间节点不允许重复,不允许超出第一个指标的日期区间`
  290. },
  291. resultData: [],//
  292. };
  293. },
  294. methods: {
  295. /* 添加额外的指标列 */
  296. addTargetHandle() {
  297. let tag = this.addList[this.addList.length-1].tag;
  298. let index = tag_arr.findIndex(item => item === tag);
  299. const item = {
  300. tag: tag_arr[index+1],
  301. target: '',
  302. name: '',
  303. start_date: '',
  304. end_date: ''
  305. };
  306. this.addList.push(item);
  307. },
  308. /* 搜索指标 */
  309. searchTarget(query) {
  310. this.search_page = 1;
  311. this.current_search = query;
  312. this.searchApi(this.current_search);
  313. },
  314. /* 聚焦获取当前检索 */
  315. inputFocusHandle(e) {
  316. this.search_page = 1;
  317. this.current_search = e.target.value;
  318. this.searchApi(this.current_search);
  319. },
  320. searchApi(query,page=1) {
  321. preDictEdbInterface
  322. .edbSearch({
  323. Keyword: query,
  324. CurrentIndex: page
  325. })
  326. .then((res) => {
  327. if (res.Ret !== 200) return
  328. const { List,Paging } = res.Data;
  329. this.search_have_more = page < Paging.Pages;
  330. this.searchOptions = page === 1 ? List : this.searchOptions.concat(List);
  331. });
  332. },
  333. searchLoad() {
  334. if(!this.search_have_more) return;
  335. this.searchApi(this.current_search,++this.search_page)
  336. },
  337. /* 选中指标 显示开始日期结束日期 */
  338. chooseTarget(val) {
  339. if (val) {
  340. const choose_obj = this.searchOptions.find(
  341. (item) => item.EdbInfoId === val
  342. );
  343. this.addList.forEach((list) => {
  344. if (list.target === val) {
  345. list.name = choose_obj.EdbName;
  346. list.start_date = choose_obj.StartDate;
  347. list.end_date = choose_obj.EndDate;
  348. }
  349. });
  350. }
  351. },
  352. /* 清空指标和关联日期 */
  353. clearHandle(index) {
  354. this.addList[index].start_date = '';
  355. this.addList[index].end_date = '';
  356. this.addList[index].name = '';
  357. },
  358. // 删除指标
  359. delTarget(index) {
  360. this.addList.splice(index, 1);
  361. },
  362. saveHandle() {
  363. if (!this.formula) return this.$message.warning('计算公式不能为空');
  364. // 指标id数组
  365. let target_arr = this.addList.filter(item => item.target);
  366. this.$emit('ensureBack',{arr:target_arr,formula: this.formula})
  367. this.cancelHandle();
  368. },
  369. /* 获取数据 */
  370. async getResult() {
  371. if (!this.formula) return this.$message.warning('计算公式不能为空');
  372. let EdbInfoIdArr = this.addList.filter(_ => _.target).map(_ => ({
  373. EdbInfoId: _.target,
  374. FromTag: _.tag
  375. }))
  376. const params = {
  377. RuleType: 9,
  378. EndDate: '',
  379. Value: this.formula,
  380. EdbInfoIdArr
  381. }
  382. const { Ret,Data } = await preDictEdbInterface.getRuleNineData(params);
  383. if(Ret !== 200) return
  384. this.resultData = Data.DataList.map(_ => ({
  385. ..._,
  386. isPredict: _.DataTimestamp > this.$moment(Data.LatestDate+' 8:00').valueOf()
  387. })) || [];
  388. },
  389. /* 新增公式分段 */
  390. addFormulaHandle() {
  391. let addItem = {
  392. formula: this.formulaList[0].formula,
  393. date: ''
  394. }
  395. this.formulaList.push(addItem)
  396. },
  397. /* 移除公式 */
  398. removeFormulaItem(index) {
  399. this.formulaList.splice(index,1)
  400. },
  401. /* 选择日期 检验 */
  402. selectFormulaDate(val,item) {
  403. const { start_date,end_date } = this.addList[0];
  404. if(!start_date) return
  405. let dateStamp = new Date(val).getTime(),
  406. startStamp = new Date(start_date).getTime(),
  407. endStamp = new Date(end_date).getTime();
  408. if (dateStamp > endStamp || dateStamp < startStamp) {
  409. item.date = '';
  410. return this.$message.warning('分段日期必须在第一个指标日期区间')
  411. }
  412. else if(this.formulaList.filter(_ => _.date===val).length>1) {
  413. item.date = '';
  414. return this.$message.warning('分段日期不可重复')
  415. }
  416. },
  417. init() {
  418. this.addList = [
  419. {
  420. tag: tag_arr[0],
  421. name: '',
  422. target: '',
  423. start_date: '',
  424. end_date: '',
  425. },
  426. {
  427. tag: tag_arr[1],
  428. name: '',
  429. target: '',
  430. start_date: '',
  431. end_date: '',
  432. },
  433. {
  434. tag: tag_arr[2],
  435. name: '',
  436. target: '',
  437. start_date: '',
  438. end_date: '',
  439. },
  440. {
  441. tag: tag_arr[3],
  442. name: '',
  443. target: '',
  444. start_date: '',
  445. end_date: '',
  446. },
  447. ];
  448. this.searchOptions = [];
  449. this.formula = '';
  450. this.resultData = [];
  451. this.formulaList = [
  452. { formula: '' }
  453. ]
  454. this.otherForm = {
  455. nullValueWay: 1,
  456. maxNullWay: '等于0'
  457. }
  458. },
  459. cancelHandle() {
  460. this.init();
  461. this.$emit('update:isOpenDialog',false);
  462. },
  463. },
  464. mounted() {},
  465. };
  466. </script>
  467. <style lang="scss">
  468. .dynamic-differ-dialog {
  469. overflow: hidden;
  470. div::-webkit-scrollbar {
  471. width: 6px !important;
  472. }
  473. .el-dialog__body {
  474. max-height: 700px;
  475. overflow: auto;
  476. }
  477. .dialog-main {
  478. padding: 25px 42px 25px 25px;
  479. .el-cascader .el-input {
  480. width: 340px;
  481. }
  482. .add-cont {
  483. display: flex;
  484. flex-wrap: wrap;
  485. justify-content: space-between;
  486. .add-li {
  487. position: relative;
  488. margin-bottom: 48px;
  489. .li-tag {
  490. font-size: 16px;
  491. margin-right: 8px;
  492. }
  493. .del-tag {
  494. position: absolute;
  495. right: -30px;
  496. top: 12px;
  497. font-size: 16px;
  498. cursor: pointer;
  499. }
  500. .target-date {
  501. color: #5882ef;
  502. position: absolute;
  503. bottom: -25px;
  504. left: 24px;
  505. }
  506. }
  507. }
  508. .add-icon {
  509. font-size: 16px;
  510. color: #5882ef;
  511. cursor: pointer;
  512. }
  513. .computed-min {
  514. margin: 50px 0;
  515. .example-txt {
  516. display: block;
  517. margin-top: 10px;
  518. }
  519. .computed-section {
  520. display: flex;
  521. margin-top: 20px;
  522. }
  523. .label {
  524. padding:10px 10px 10px 0;
  525. }
  526. .formula-item {
  527. display: flex;
  528. align-items: center;
  529. margin-bottom: 15px;
  530. }
  531. .date-section-text {
  532. margin-left: 15px;
  533. }
  534. }
  535. }
  536. .dia-bot {
  537. padding-bottom: 40px;
  538. display: flex;
  539. justify-content: center;
  540. }
  541. }
  542. </style>