addPredicEdb.vue 42 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252
  1. <template>
  2. <div class="add-predicedb-page">
  3. <div class="aside-warp">
  4. <div class="btn-box">
  5. <el-button type="primary" @click="saveHandle">保存</el-button>
  6. <el-button type="primary" plain @click="$router.back()">取消</el-button>
  7. </div>
  8. <div class="con">
  9. <el-form
  10. ref="formRef"
  11. hide-required-asterisk
  12. :model="formData"
  13. :rules="formRules"
  14. >
  15. <el-form-item prop="classify">
  16. <div class="item">
  17. <span class="label">添加到分类</span>
  18. <!-- <el-select
  19. v-model="formData.classify"
  20. placeholder="请选择分类"
  21. style="width: 100%"
  22. clearable
  23. >
  24. <el-option
  25. v-for="item in classifyArr"
  26. :key="item.ClassifyId"
  27. :label="item.ClassifyName"
  28. :value="item.ClassifyId"
  29. >
  30. </el-option>
  31. </el-select> -->
  32. <el-cascader
  33. v-model="formData.classify"
  34. :options="classifyArr"
  35. :props="{
  36. label: 'ClassifyName',
  37. value: 'ClassifyId',
  38. children: 'Children',
  39. }"
  40. style="width: 90%"
  41. placeholder="请选择所属分类"
  42. />
  43. </div>
  44. </el-form-item>
  45. <el-form-item prop="oldEdb" v-if="!formData.edb_id">
  46. <div class="item">
  47. <span class="label">选择指标</span>
  48. <el-select
  49. v-model="formData.oldEdb"
  50. v-loadMore="searchLoad"
  51. style="width: 100%"
  52. :filterable="!formData.oldEdb"
  53. remote
  54. clearable
  55. placeholder="指标ID/指标名称"
  56. :remote-method="searchHandle"
  57. @click.native="inputFocusHandle"
  58. @change="selectEdbHandle"
  59. >
  60. <i slot="prefix" class="el-input__icon el-icon-search"></i>
  61. <el-option
  62. v-for="item in searchOptions"
  63. :key="item.EdbInfoId"
  64. :label="item.EdbName"
  65. :value="item.EdbInfoId"
  66. >
  67. <edbDetailPopover :info="item">
  68. <div slot="reference">{{item.EdbName}}</div>
  69. </edbDetailPopover>
  70. </el-option>
  71. </el-select>
  72. </div>
  73. </el-form-item>
  74. <el-form-item prop="edbName">
  75. <div class="item">
  76. <span class="label">预测指标名称</span>
  77. <el-input
  78. v-model="formData.edbName"
  79. style="width: 100%"
  80. placeholder="指标名称"
  81. />
  82. </div>
  83. </el-form-item>
  84. <el-form-item prop="dateType">
  85. <div class="item">
  86. <span class="label">预测日期类型</span>
  87. <el-select
  88. v-model="formData.dateType"
  89. placeholder="请选择日期类型"
  90. style="width: 100%"
  91. @change="getChartInfo"
  92. >
  93. <el-option label="交易日" value="交易日"></el-option>
  94. <el-option label="自然日" value="自然日"></el-option>
  95. </el-select>
  96. </div>
  97. </el-form-item>
  98. <el-form-item v-if="formData.oldEdbName">
  99. <div class="item" style="margin-top: 30px">
  100. <span class="label">原指标名称:{{ formData.oldEdbName }}</span>
  101. </div>
  102. </el-form-item>
  103. <el-form-item prop="frequency">
  104. <div class="item">
  105. <span class="label">频度:{{ formData.frequency }}</span>
  106. </div>
  107. </el-form-item>
  108. <el-form-item prop="newdata">
  109. <div class="item">
  110. <span class="label">最新值:{{ formData.newdata }}</span>
  111. </div>
  112. </el-form-item>
  113. <el-form-item prop="lasetDate">
  114. <div class="item" style="margin-bottom: 30px">
  115. <span class="label">最新日期:{{ formData.lasetDate }}</span>
  116. </div>
  117. </el-form-item>
  118. </el-form>
  119. <!-- 规则 -->
  120. <el-collapse v-model="activeNames" class="rules-ul">
  121. <el-collapse-item
  122. v-for="(item, index) in rulesArr"
  123. :key="index"
  124. :name="index"
  125. >
  126. <template slot="title">
  127. <span class="text_oneLine">规则{{ index + 1 }}</span>
  128. <i
  129. class="el-icon-delete del-icon"
  130. @click.stop="removeRuleHandle(item, index)"
  131. />
  132. </template>
  133. <div class="wrap">
  134. <div class="item">
  135. <span class="label">预测截止日期</span>
  136. <el-date-picker
  137. v-model="item.endDate"
  138. type="date"
  139. style="width: 100%"
  140. value-format="yyyy-MM-dd"
  141. placeholder="请选择日期"
  142. :picker-options="timePickerOptions"
  143. @change="changeDateHandle($event,index)"
  144. >
  145. <!-- :disabled="index!==0 && !rulesArr[index-1].endDate" -->
  146. </el-date-picker>
  147. </div>
  148. <div class="item">
  149. <span class="label">
  150. 预测规则
  151. <el-tooltip effect="dark" placement="right">
  152. <div
  153. slot="content"
  154. v-html="rulesTip"
  155. style="line-height: 20px;max-width:600px"
  156. ></div>
  157. <i class="el-icon-question" style="color: #666" />
  158. </el-tooltip>
  159. </span>
  160. <el-select
  161. v-model="item.predict_type"
  162. placeholder="请选择分类"
  163. style="width: 100%"
  164. @change="changePredictType($event,item,index)"
  165. >
  166. <el-option
  167. v-for="item in predictTypeOptions"
  168. :key="item.key"
  169. :label="item.label"
  170. :value="item.key"
  171. :disabled="formData.frequency==='年度'&&[5,6,11,12].includes(item.key)"
  172. >
  173. </el-option>
  174. </el-select>
  175. </div>
  176. <div class="item" v-if="![1,9,11,12,14,15].includes(item.predict_type)">
  177. <span class="label">{{labelMap[item.predict_type]}}</span>
  178. <el-input
  179. v-model="item.fixedValue"
  180. style="width: 100%"
  181. placeholder="请输入值"
  182. type="number"
  183. />
  184. </div>
  185. <!-- 季节性规则 -->
  186. <div v-if="[11,15].includes(item.predict_type)">
  187. <div class="item">
  188. <span class="label">选择方式</span>
  189. <el-select
  190. v-model="item.season_way"
  191. placeholder="请选择方式"
  192. style="width: 100%"
  193. @change="item.n_value = '';item.season_years=[]"
  194. >
  195. <el-option label="连续N年" :value="1"/>
  196. <el-option label="指定N年" :value="2"/>
  197. </el-select>
  198. </div>
  199. <div class="item" v-if="item.season_way===1">
  200. <span class="label">期数</span>
  201. <el-input
  202. v-model="item.n_value"
  203. style="width: 100%"
  204. placeholder="请输入值"
  205. type="number"
  206. />
  207. </div>
  208. <div class="item" v-else>
  209. <el-date-picker
  210. v-model="select_year"
  211. type="year"
  212. value-format="yyyy"
  213. placeholder="选择年份"
  214. @change="pushYear($event,item)"
  215. />
  216. <div class="season-year-cont" v-if="item.season_years.length">
  217. <el-tag
  218. v-for="year in item.season_years"
  219. :key="year"
  220. style="margin: 5px;"
  221. @close="removeYear(year,item)"
  222. closable
  223. >
  224. {{year}}
  225. </el-tag>
  226. </div>
  227. </div>
  228. <div class="item" v-if="item.predict_type===11">
  229. <el-radio-group v-model="item.season_type">
  230. <el-radio label="公历">公历</el-radio>
  231. <el-radio label="农历">农历</el-radio>
  232. </el-radio-group>
  233. </div>
  234. </div>
  235. <!-- 移动平均同比 -->
  236. <div v-else-if="item.predict_type===12">
  237. <div class="item">
  238. <span class="label">期数</span>
  239. <el-input
  240. v-model="item.n_value"
  241. style="width: 100%"
  242. placeholder="请输入值"
  243. type="number"
  244. />
  245. </div>
  246. <div class="item">
  247. <span class="label">同比年份</span>
  248. <el-date-picker
  249. v-model="item.move_average_year"
  250. type="year"
  251. value-format="yyyy"
  252. placeholder="选择年份"
  253. @change="pushYear($event,item)"
  254. />
  255. </div>
  256. </div>
  257. <!-- 一元线性拟合 -->
  258. <div v-else-if="item.predict_type===14">
  259. <div class="item">
  260. <span class="label">
  261. 自变量
  262. <el-radio-group v-model="edbFromType">
  263. <el-radio :label="0" style="margin-right: 15px">ETA指标</el-radio>
  264. <el-radio :label="1">ETA预测指标</el-radio>
  265. </el-radio-group>
  266. </span>
  267. <el-select
  268. v-model="item.self_target"
  269. v-loadMore="searchLoad"
  270. ref="searchRef"
  271. :filterable="!item.self_target"
  272. remote
  273. clearable
  274. placeholder="指标ID/指标名称"
  275. style="width:88%;margin-top: 10px;display:inline-block"
  276. :remote-method="searchHandle"
  277. @click.native="inputFocusHandle($event,'self_target')"
  278. >
  279. <i slot="prefix" class="el-input__icon el-icon-search"></i>
  280. <el-option
  281. v-for="item in searchOptions"
  282. :key="item.EdbInfoId"
  283. :label="item.EdbName"
  284. :value="item.EdbInfoId"
  285. >
  286. </el-option>
  287. </el-select>
  288. <i class="el-icon-tickets" style="color:#409EFF;font-size:18px" @click="isLookHistory=true;lookEdbId=item.self_target;" v-if="item.self_target"/>
  289. </div>
  290. <div class="item">
  291. <span class="label">
  292. 领先天数
  293. <el-input
  294. v-model="item.fixedValue"
  295. style="width: 80px;margin:0 5px;"
  296. type="number"
  297. :step="1"
  298. @keyup.native="filterCode(item)"
  299. />天
  300. </span>
  301. </div>
  302. <div class="item">
  303. <span class="label">拟合时间段</span>
  304. <el-date-picker
  305. v-model="item.fit_date[0]"
  306. range-separator="至"
  307. placeholder="开始日期"
  308. value-format="yyyy-MM-dd"
  309. style="width: 90%;margin-bottom: 10px;"
  310. @change="changeFitDate(item)"
  311. />
  312. <el-date-picker
  313. v-model="item.fit_date[1]"
  314. placeholder="结束日期"
  315. value-format="yyyy-MM-dd"
  316. style="width: 90%"
  317. @change="changeFitDate(item)"
  318. :picker-options="{
  319. shortcuts: [{
  320. text: '至今',
  321. onClick(picker) {
  322. const date = new Date();
  323. picker.$et('pick',date);
  324. }
  325. }]
  326. }"
  327. />
  328. </div>
  329. </div>
  330. <!-- 年度值倒退 -->
  331. <div v-else-if="item.predict_type===16">
  332. <div class="item">
  333. <span class="label">余额分配方式</span>
  334. <el-select
  335. v-model="item.distribute_type"
  336. placeholder="请选择方式"
  337. style="width: 100%"
  338. @change="item.on_year=''"
  339. >
  340. <el-option label="均值法" :value="1"/>
  341. <el-option label="同比法" :value="2"/>
  342. </el-select>
  343. </div>
  344. <div class="item" v-if="item.distribute_type===2">
  345. <span class="label">同比年份</span>
  346. <el-date-picker
  347. v-model="item.on_year"
  348. type="year"
  349. value-format="yyyy"
  350. placeholder="选择年份"
  351. />
  352. </div>
  353. </div>
  354. <el-button type="text" v-if="item.predict_type===9" @click="setRingAddHandle(item,index)">设置环比增加值</el-button>
  355. </div>
  356. </el-collapse-item>
  357. </el-collapse>
  358. <div class="rules-add" @click="addRuleHandle">
  359. <img
  360. src="~@/assets/img/set_m/add_ico.png"
  361. alt=""
  362. style="width: 16px; height: 16px; margin-right: 10px"
  363. />
  364. <span>添加更多</span>
  365. </div>
  366. </div>
  367. </div>
  368. <div class="main-wrap" id="right">
  369. <div class="con-box" v-show="edbData.DataList">
  370. <div class="top-title">{{ formData.edbName }}</div>
  371. <chartInfo
  372. :edbData="edbData"
  373. @refreshData="refreshData"
  374. ref="chartInfo"
  375. />
  376. </div>
  377. <div class="empty-box" v-show="!edbData.DataList">
  378. <tableNoData text="暂无信息"/>
  379. </div>
  380. </div>
  381. <!-- 设置动态环差弹窗 -->
  382. <dynamic-differ
  383. :isOpenDialog.sync="isOpenDialog"
  384. :edbList="dynamicDifferList"
  385. :dialog_formula="dialog_formula"
  386. @ensureBack="saveDynamicDifferRule"
  387. @lookHistory="id => {isLookHistory=true;lookEdbId=id;}"
  388. />
  389. <!-- 指标历史记录 -->
  390. <edbHistoryDialog
  391. :isOpenDialog.sync="isLookHistory"
  392. :edbId="lookEdbId"
  393. />
  394. </div>
  395. </template>
  396. <script>
  397. import * as preDictEdbInterface from "@/api/modules/predictEdbApi.js";
  398. import { dataBaseInterface } from "@/api/api.js";
  399. import chartInfo from "./components/chartInfo.vue";
  400. import dynamicDiffer from './components/dynamicRingdiffer.vue';
  401. export default {
  402. components: { chartInfo,dynamicDiffer },
  403. computed: {
  404. // 图需要更新的依赖项
  405. showChart() {
  406. return {
  407. oldEdb: this.formData.oldEdb,
  408. };
  409. },
  410. timePickerOptions() {
  411. let that=this
  412. let obj={
  413. disabledDate(time) {
  414. if(that.formData.lasetDate){
  415. return time.getTime() < new Date(that.formData.lasetDate).getTime()
  416. }else{
  417. return time.getTime() < Date.now()
  418. }
  419. }
  420. }
  421. return obj
  422. }
  423. },
  424. watch: {
  425. showChart(n, o) {
  426. if (n.oldEdb) {
  427. console.log("更新");
  428. if (n.oldEdb != o.oldEdb) {
  429. this.$refs.chartInfo.chartInfo.ChartType = 1;
  430. }
  431. //this.getChartInfo();
  432. }
  433. },
  434. rulesArr: {
  435. handler(newval) {
  436. this.isNeedWatch && this.formData.oldEdb && this.getChartInfo();
  437. },
  438. deep: true
  439. }
  440. },
  441. data() {
  442. return {
  443. formData: {
  444. edb_id: "",
  445. classify: "",
  446. oldEdb: "",
  447. edbName: "",
  448. frequency: "",
  449. newdata: "",
  450. lasetDate: "",
  451. dateType: '交易日',
  452. },
  453. formRules: {
  454. classify: [
  455. { required: true, message: "分类不能为空", trigger: "blur" },
  456. ],
  457. oldEdb: [{ required: true, message: "指标不能为空", trigger: "blur" }],
  458. edbName: [
  459. { required: true, message: "指标名称不能为空", trigger: "blur" },
  460. ],
  461. },
  462. classifyArr: [],
  463. predictTypeSetting: [
  464. { key: 1, label: "最新" },
  465. { key: 2, label: "固定值" },
  466. { key: 3, label: "同比" },
  467. { key: 4, label: "同差" },
  468. { key: 5, label: "环比" },
  469. { key: 6, label: "环差" },
  470. { key: 7, label: "N期移动均值" },
  471. { key: 8, label: "N期段线性外推值" },
  472. { key: 9, label: '动态环差' },
  473. { key: 10, label: "给定终值后插值" },
  474. { key: 11, label: "季节性" },
  475. { key: 12, label: "移动平均同比" },
  476. { key: 13, label: "同比增速插值" },
  477. { key: 14, label: "一元线性拟合" },
  478. { key: 15, label: "N年均值" },
  479. { key: 16, label: "年度值倒推" },
  480. ], //预测规则
  481. predictTypeOptions:[],
  482. rulesTip: `预测规则说明:<br>
  483. 1、最新:预测值全部等于最新值;<br>
  484. 2、固定值:预测值默认全部等于常数值;<br>
  485. 3、同比:去年同期值乘同比增速得到预测值;<br>
  486. 4、同差:去年同期值加同比增加得到预测值;<br>
  487. 5、环比:上期值乘环比增速得到预测值;<br>
  488. 6、环差:上期值加同比增加得到预测值;<br>
  489. 7、N期移动均值:过去N期值的平均值;<br>
  490. 8、N期段线性外推值:过去N期值生成线性回归方程:Y=aX+b,将未来的期数代入得到预测值;<br>
  491. 9、动态环差:选择几个指标进行指标计算得到结果值作为动态环差值,预测值=上期值+动态环差值;<br>
  492. 10、给定终值后插值:计算最新数据和预测终值的期数差T和数据差S,环差值=S/T,预测数值=前一期数值+环差值(本期-上期);<br>
  493. 11、季节性:计算过去N年同期的环差(本期-上期)均值,预测值=上期值+环差均值;<br>
  494. 12、移动平均同比:计算过去N期平均值的同比值(本期/上期),选择同比年份,预测值=同比年份同期值*同比值;<br>
  495. 13、同比增速插值:计算最新数据的同比增速:(本期数值-去年同期数值)/去年同期数值*100%,输入同比增速终值,通过期数差值,计算每一期同比增速,预测值=去年同期值*(1+该期同比增速)。<br>
  496. 14、一元线性拟合:由指标A(自变量)和指标B(基础指标)在拟合时间段生成线性回归方程:Y=aX+b,将指标A代入该方程得到拟合指标B',拼接指标B的实际值和B'的预测值,生成预测指标;,<br>
  497. 15、N年均值:过去N年同期均值。过去N年可以连续或者不连续,指标数据均用线性插值补全为日度数据后计算;<br>
  498. 16、年度值倒推:设定年度值,余额=年度值-年初至今累计值(算法参考累计值),进行余额分配,均值法分配时保证每期数值相等(日度/周度:剩余期数=剩余自然日历天数/今年指标最新日期自然日历天数*今年至今指标数据期数;旬度/月度/季度/半年度:剩余期数=全年期数(36/12/4/2)-今年至今自然日历期数),同比法保证每期同比相等(同比增速=余额/同比年份相应日期的余额,预测值等于同比年份同期值*同比增速)`,
  499. labelMap: {
  500. 2: '固定值',
  501. 3: '同比增速',
  502. 4: '同比增加值',
  503. 5: '环比增速',
  504. 6: '环比增加值',
  505. 7: '期数',
  506. 8: '期数',
  507. 10: '预测终值',
  508. 11: '期数',
  509. 12: '期数',
  510. 13: '同比增速终值',
  511. 16: '年度值'
  512. },
  513. rulesArr: [{
  514. endDate: "",
  515. predict_type: 1,
  516. fixedValue: "",
  517. edbarr:[],
  518. season_way: 1,
  519. season_years: [],
  520. season_type: '公历',
  521. n_value:'',
  522. move_average_year: '',
  523. self_target: '',
  524. fit_date:['',this.$moment().format('YYYY-MM-DD')],
  525. distribute_type: 1,
  526. on_year: ''
  527. }],
  528. activeNames: [0],
  529. searchOptions: [],
  530. search_page: 1,
  531. search_have_more: false,
  532. current_search: "",
  533. edbData: {
  534. DataList: null,
  535. EdbInfo: {},
  536. },
  537. DateType: 10, //年份选择项 默认全部
  538. isNeedWatch: true,
  539. /* 动态环差弹窗 */
  540. isOpenDialog: false,
  541. dynamicDifferList: [],//依赖指标
  542. dialog_formula: '',
  543. click_rule_index: -1,
  544. select_year: '',
  545. edbFromType: 0,//标准指标
  546. /* 查看历史弹窗 */
  547. isLookHistory: false,
  548. lookEdbId: 0,
  549. };
  550. },
  551. created() {
  552. if (this.$route.path == "/editpredictEdb") {
  553. this.formData.edb_id = Number(this.$route.query.id);
  554. this.getEditInitData();
  555. }
  556. this.getClassifyOne();
  557. this.setRulesArr()
  558. },
  559. methods: {
  560. // 获取详情
  561. getEditInitData() {
  562. this.isNeedWatch = false; //控制编辑进来调用多次watch
  563. preDictEdbInterface
  564. .edbDetail({
  565. EdbInfoId: this.formData.edb_id,
  566. })
  567. .then((res) => {
  568. if (res.Ret !== 200) return;
  569. const {
  570. ClassifyId,
  571. EdbName,
  572. Frequency,
  573. LatestValue,
  574. RuleList,
  575. EdbInfoId,
  576. LatestDate,
  577. DataDateType
  578. } = res.Data;
  579. this.formData = {
  580. edb_id: EdbInfoId,
  581. classify: ClassifyId,
  582. oldEdb: res.Data.CalculateList[0].FromEdbInfoId,
  583. oldEdbName: res.Data.CalculateList[0].FromEdbName,
  584. edbName: EdbName,
  585. frequency: Frequency,
  586. newdata: LatestValue,
  587. lasetDate: LatestDate,
  588. dateType: DataDateType
  589. };
  590. this.rulesArr = RuleList.map(_ => ({
  591. endDate: this.$moment(_.EndDate).format('YYYY-MM-DD'),
  592. predict_type: _.RuleType,
  593. fixedValue: _.RuleType === 14
  594. ? JSON.parse(_.Value).MoveDay
  595. : _.RuleType === 16
  596. ? JSON.parse(_.Value).Value
  597. : _.Value,
  598. edbarr: _.RuleType === 9
  599. ? _.CalculateList.map(item => ({ target:item.FromEdbInfoId,tag:item.FromTag,name:item.FromEdbName,start_date: item.StartDate,end_date:item.EndDate }))
  600. : [],
  601. season_way:[11,15].includes(_.RuleType) ? JSON.parse(_.Value).YearType : '',
  602. season_years: [11,15].includes(_.RuleType) ? JSON.parse(_.Value).YearList.map(_ =>_.toString()) : [],
  603. season_type: _.RuleType === 11 ? JSON.parse(_.Value).Calendar : '',
  604. n_value: [11,12,15].includes(_.RuleType) ? JSON.parse(_.Value).NValue : '',
  605. move_average_year: _.RuleType === 12 ? JSON.parse(_.Value).Year.toString() : '',
  606. self_target: _.RuleType === 14 ? JSON.parse(_.Value).EdbInfoId : '',
  607. fit_date: _.RuleType === 14 ? [JSON.parse(_.Value).StartDate,JSON.parse(_.Value).EndDate] : [],
  608. distribute_type: _.RuleType === 16?JSON.parse(_.Value).Type:1,
  609. on_year: _.RuleType === 16?JSON.parse(_.Value).Year.toString():'',
  610. }))
  611. this.searchOptions = RuleList.map(item => ({
  612. EdbInfoId: item.CalculateList[0] ? item.CalculateList[0].FromEdbInfoId : '',
  613. EdbName: item.CalculateList[0] ? item.CalculateList[0].FromEdbName : ''
  614. }));
  615. this.edbData.EdbInfo = {
  616. ...res.Data,
  617. ChartColor: "",
  618. ChartStyle: "",
  619. ChartType: 0,
  620. ChartWidth: 0,
  621. MaxData: res.Data.MaxValue,
  622. MinData: res.Data.MinValue,
  623. EdbInfoCategoryType: 1,
  624. EdbInfoType: 1,
  625. EdbName: this.formData.edbName,
  626. IsAxis: 1,
  627. };
  628. });
  629. },
  630. getClassifyOne() {
  631. preDictEdbInterface.classifyListV2().then((res) => {
  632. if (res.Ret !== 200) return;
  633. this.classifyArr = res.Data.AllNodes || [];
  634. if (this.$route.path == "/editpredictEdb") {
  635. //将formData.classify转为数组的格式
  636. this.formData.classify = this.findParentNodeHandle(this.classifyArr,this.formData.classify).reverse()
  637. }
  638. });
  639. },
  640. findParentNodeHandle(arr, id) {
  641. // 遍历取父级code push数组
  642. for (let i of arr) {
  643. if (i.ClassifyId === id) {
  644. return [i.ClassifyId];
  645. }
  646. if (i.Children) {
  647. let node = this.findParentNodeHandle(i.Children, id);
  648. if (node) {
  649. return node.concat(i.ClassifyId);
  650. }
  651. }
  652. }
  653. },
  654. searchLoad() {
  655. if (!this.search_have_more) return;
  656. this.searchApi(this.current_search, ++this.search_page);
  657. },
  658. async searchApi(query, page = 1) {
  659. let params = {
  660. KeyWord: query,
  661. CurrentIndex: page,
  662. }
  663. const res = (this.edbFromType === 1 && this.current_type==='self_target')
  664. ? await preDictEdbInterface.edbSearch(params)
  665. : await dataBaseInterface.targetSearchByPage(params)
  666. if (res.Ret !== 200) return;
  667. const { List, Paging } = res.Data;
  668. this.search_have_more = page < Paging.Pages;
  669. this.searchOptions = page === 1 ? List : this.searchOptions.concat(List);
  670. },
  671. /* 聚焦获取当前检索 */
  672. inputFocusHandle(e,type='') {
  673. this.search_page = 1;
  674. this.current_search = e.target.value;
  675. this.current_type = type;
  676. this.searchApi(this.current_search);
  677. },
  678. /* 选择指标 同步名称频度最新值*/
  679. async selectEdbHandle(val) {
  680. if (val) {
  681. const { Data } = await dataBaseInterface.targetDetail({
  682. EdbInfoId: val,
  683. });
  684. this.formData.edbName = `${Data.EdbName}(预测)`;
  685. this.formData.frequency = Data.Frequency;
  686. this.formData.newdata = Data.LatestValue;
  687. this.formData.lasetDate = Data.LatestDate;
  688. this.edbData.EdbInfo = {
  689. ...Data,
  690. ChartColor: "",
  691. ChartStyle: "",
  692. ChartType: 0,
  693. ChartWidth: 0,
  694. // MaxData: Data.MaxValue,
  695. // MinData: Data.MinValue,
  696. EdbInfoCategoryType: 1,
  697. EdbInfoType: 1,
  698. EdbName: this.formData.edbName,
  699. IsAxis: 1
  700. };
  701. this.setRulesArr()
  702. this.getChartInfo()
  703. } else {
  704. this.formData.edbName = "";
  705. this.formData.frequency = "";
  706. this.formData.newdata = "";
  707. this.formData.lasetDate = "";
  708. }
  709. },
  710. /* 搜索 */
  711. searchHandle(query) {
  712. this.search_page = 1;
  713. this.current_search = query;
  714. this.searchApi(this.current_search);
  715. },
  716. // 获取图表数据
  717. async getChartInfo() {
  718. console.log(this.rulesArr)
  719. let rules_params = this.rulesArr.filter(_ => (
  720. (_.predict_type === 1 && _.endDate)
  721. || [11,12,15].includes(_.predict_type) && _.endDate && (_.n_value||_.season_years.length)
  722. || (_.predict_type===14 &&_.endDate && _.fit_date[0] && _.fit_date[1] && _.self_target)
  723. || ![1,11,12,14,15].includes(_.predict_type) && _.endDate && _.fixedValue
  724. )).map(_ =>{
  725. let dynamic_params = {};
  726. switch(_.predict_type) {
  727. case 11:
  728. case 15:
  729. dynamic_params = {
  730. Calendar: _.season_type,
  731. YearType: _.season_way,
  732. NValue: Number(_.n_value),
  733. YearList: _.season_years.map(_ =>Number(_)),
  734. }
  735. break
  736. case 12:
  737. dynamic_params = {
  738. Year: Number(_.move_average_year),
  739. NValue: Number(_.n_value)
  740. }
  741. break
  742. case 14:
  743. dynamic_params = {
  744. StartDate: _.fit_date[0],
  745. EndDate: _.fit_date[1],
  746. MoveDay: Number(_.fixedValue),
  747. EdbInfoId: _.self_target
  748. }
  749. break
  750. case 16:
  751. dynamic_params = {
  752. Type: _.distribute_type,
  753. Value: Number(_.fixedValue),
  754. Year: Number(_.on_year)
  755. }
  756. break
  757. }
  758. return {
  759. RuleType: _.predict_type,
  760. EndDate: _.endDate,
  761. Value: [11,12,14,15,16].includes(_.predict_type) ? JSON.stringify(dynamic_params) : _.fixedValue,
  762. EdbInfoIdArr: _.predict_type === 9
  763. ? _.edbarr.map(item => ({ EdbInfoId:item.target,FromTag:item.tag }))
  764. : _.predict_type === 14
  765. ? [{EdbInfoId: _.self_target,FromTag: ''}]
  766. :[]
  767. }
  768. })
  769. let params = {
  770. SourceEdbInfoId: this.formData.oldEdb,
  771. DateType: this.$refs.chartInfo.year_select,
  772. StartDate: this.$refs.chartInfo.select_date[0] || "",
  773. EndDate: this.$refs.chartInfo.select_date[1] || "",
  774. Calendar: this.$refs.chartInfo.calendar_type,
  775. ChartType: this.$refs.chartInfo.chartInfo.ChartType,
  776. SeasonStartDate: this.$refs.chartInfo.season_year[0] || "",
  777. SeasonEndDate: this.$refs.chartInfo.season_year[1] || "",
  778. DataDateType: this.formData.dateType,
  779. }
  780. const res = await preDictEdbInterface.edbChartDataForAdd(rules_params.length ? {...params,RuleList: rules_params} : params);
  781. if (res.Ret != 200) return;
  782. this.edbData.DataList = res.Data.DataList;
  783. this.edbData.EdbInfo.MaxData = this.edbData.EdbInfo.MaxData || res.Data.MaxValue;
  784. this.edbData.EdbInfo.MinData = this.edbData.EdbInfo.MinData || res.Data.MinValue;
  785. this.isNeedWatch = true; //控制编辑进来调用多次watch
  786. },
  787. /* 保存 */
  788. async saveHandle() {
  789. await this.$refs.formRef.validate();
  790. // 规则填写是否完整
  791. let isRulesComplete = this.rulesArr.every(item => {
  792. return item.predict_type === 1 && item.endDate
  793. || [11,12,15].includes(item.predict_type) && item.endDate && (item.n_value||item.season_years.length)
  794. || (item.predict_type===14 && item.endDate && item.fit_date[0] && item.fit_date[1] && item.self_target)
  795. || ![1,11,12,14,15].includes(item.predict_type) && item.endDate && item.fixedValue
  796. })
  797. if(!isRulesComplete) return this.$message.warning('请填写完整的规则')
  798. const { edb_id,classify,oldEdb,edbName,dateType } = this.formData;
  799. //规则参数
  800. let RuleList = this.rulesArr.map(_ => {
  801. let dynamic_params = {};
  802. switch(_.predict_type) {
  803. case 11:
  804. case 15:
  805. dynamic_params = {
  806. Calendar: _.season_type,
  807. YearType: _.season_way,
  808. NValue: Number(_.n_value),
  809. YearList: _.season_years.map(_ =>Number(_)),
  810. }
  811. break
  812. case 12:
  813. dynamic_params = {
  814. Year: Number(_.move_average_year),
  815. NValue: Number(_.n_value)
  816. }
  817. break
  818. case 14:
  819. dynamic_params = {
  820. StartDate: _.fit_date[0],
  821. EndDate: _.fit_date[1],
  822. MoveDay: Number(_.fixedValue),
  823. EdbInfoId: _.self_target
  824. }
  825. break
  826. case 16:
  827. dynamic_params = {
  828. Type: _.distribute_type,
  829. Value: Number(_.fixedValue),
  830. Year: Number(_.on_year)
  831. }
  832. break
  833. }
  834. return {
  835. RuleType: _.predict_type,
  836. EndDate: _.endDate,
  837. Value: [11,12,14,15,16].includes(_.predict_type) ? JSON.stringify(dynamic_params) : _.fixedValue,
  838. EdbInfoIdArr: _.predict_type === 9
  839. ? _.edbarr.map(item => ({ EdbInfoId:item.target,FromTag:item.tag }))
  840. : _.predict_type === 14
  841. ? [{EdbInfoId: _.self_target,FromTag: ''}]
  842. :[]
  843. }
  844. })
  845. let params = {
  846. ClassifyId: classify[classify.length-1],
  847. EdbName: edbName,
  848. MaxValue: Number(this.$refs.chartInfo.tableData[0].MaxData),
  849. MinValue: Number(this.$refs.chartInfo.tableData[0].MinData),
  850. DataDateType: dateType,
  851. RuleList
  852. };
  853. const { Ret, Data } = edb_id
  854. ? await preDictEdbInterface.edbEdit({ ...params, EdbInfoId: edb_id })
  855. : await preDictEdbInterface.edbAdd({
  856. ...params,
  857. SourceEdbInfoId: oldEdb,
  858. });
  859. if (Ret !== 200) return;
  860. this.$message.success("保存成功");
  861. //编辑的话更新图
  862. edb_id && this.$nextTick(() => {
  863. this.setChartImage();
  864. });
  865. if (!edb_id) {
  866. const obj = {
  867. code: Data.UniqueCode,
  868. id: Data.EdbInfoId,
  869. };
  870. sessionStorage.setItem("predictEdbTreeData", JSON.stringify(obj));
  871. }
  872. setTimeout(() => {
  873. this.$router.back();
  874. }, 1500);
  875. },
  876. /* 关联图片 */
  877. setChartImage() {
  878. let svg = this.$refs.chartInfo.$refs.chartRef.chart.getSVG({
  879. chart: {
  880. width: 340,
  881. height: 230,
  882. },
  883. });
  884. let form = new FormData();
  885. form.append("Img", svg);
  886. this.setImageHandle(form);
  887. },
  888. async setImageHandle(form) {
  889. let { Data } = await dataBaseInterface.uploadImgSvg(form);
  890. await preDictEdbInterface.setImg({
  891. EdbInfoId: this.formData.edb_id,
  892. ImageUrl: Data.ResourceUrl,
  893. });
  894. },
  895. refreshData() {
  896. this.getChartInfo();
  897. },
  898. /* 添加新规则 */
  899. addRuleHandle() {
  900. const newItem = {
  901. endDate: "",
  902. predict_type: 1,
  903. fixedValue: "",
  904. edbarr: [],
  905. season_way: 1,
  906. season_years: [],
  907. season_type: '公历',
  908. n_value: '',
  909. move_average_year: '',
  910. self_target: '',
  911. fit_date:['',this.$moment().format('YYYY-MM-DD')],
  912. distribute_type: 1
  913. };
  914. this.rulesArr.push(newItem);
  915. },
  916. /* 切换预测规则 重置value */
  917. initRule(e,item) {
  918. item.fixedValue = '';
  919. item.edbarr=[];
  920. item.season_way=1;
  921. item.season_years=[];
  922. item.season_type='公历';
  923. item.n_value='';
  924. item.move_average_year='';
  925. item.self_target = '';
  926. item.fit_date = ['',this.$moment().format('YYYY-MM-DD')];
  927. item.distribute_type=1;
  928. },
  929. /* 设置预测规则数组 */
  930. setRulesArr(){
  931. const {frequency} = this.formData
  932. let filterKey=[]
  933. //如果是年度,则不显示环比、环差、动态环差、季节性、移动平均同比的选项
  934. if(frequency==='年度'){
  935. filterKey = [5,6,11,12]
  936. }
  937. //this.predictTypeOptions = this.predictTypeSetting.filter(item=>{return !filterKey.includes(item.key)})
  938. this.predictTypeOptions = this.predictTypeSetting
  939. //年度指标时,将不支持的规则类型重置
  940. this.isNeedWatch = false
  941. this.rulesArr.forEach(item=>{
  942. if(filterKey.length&&filterKey.includes(item.predict_type)){
  943. this.initRule(_,item)
  944. item.predict_type = 1
  945. }
  946. })
  947. this.$nextTick(()=>{
  948. this.isNeedWatch=true
  949. })
  950. },
  951. /* 删除规则 */
  952. removeRuleHandle(item, index) {
  953. if(this.rulesArr.length===1) return this.$message.warning('请至少保留一条预测规则');
  954. this.rulesArr.splice(index, 1);
  955. this.$message.success("删除成功");
  956. },
  957. /* 选择日期时提示不能小于上个日期 */
  958. changeDateHandle(value,index) {
  959. console.log(value,index)
  960. let isDateRepeat = this.rulesArr.some((_,_index) => _index!==index&&this.$moment(_.endDate).valueOf()===this.$moment(value).valueOf())
  961. if(isDateRepeat) {
  962. this.rulesArr[index].endDate = '';
  963. return this.$message.warning('所选日期不能和其他规则重复')
  964. }
  965. // if(index > 0) {
  966. // let prev_date = this.rulesArr[index-1].endDate;
  967. // if(this.$moment(prev_date).valueOf() >= this.$moment(value).valueOf()){
  968. // this.$message.warning('所选日期不能小于上个规则的日期')
  969. // this.rulesArr[index].endDate = '';
  970. // }
  971. // }
  972. this.rulesArr[index].predict_type===16 && this.rulesArr[index].endDate && this.checkReverseYearDate(index)
  973. },
  974. // n年倒推 校验日期时同一年份
  975. checkReverseYearDate(index) {
  976. let selectDateYear = new Date(this.rulesArr[index].endDate).getFullYear();
  977. let prevRuleDateYear = '';
  978. if(this.rulesArr.length === 1) {
  979. prevRuleDateYear = new Date(this.formData.lasetDate).getFullYear();
  980. } else {
  981. let sortRuleArr = [...this.rulesArr].sort((x,y) => new Date(x.endDate)-new Date(y.endDate));
  982. let newIndex = sortRuleArr.findIndex(_ => _.endDate===this.rulesArr[index].endDate);
  983. prevRuleDateYear = newIndex === 0 ? new Date(this.formData.lasetDate).getFullYear() : new Date(sortRuleArr[newIndex-1].endDate).getFullYear()
  984. }
  985. if(selectDateYear !== prevRuleDateYear) {
  986. this.rulesArr[index].endDate = ''
  987. return this.$message.warning('年度值倒推不支持跨年预测')
  988. }
  989. },
  990. changePredictType(e,item,index) {
  991. e===16&&item.endDate&&this.checkReverseYearDate(index);
  992. this.initRule(e,item)
  993. },
  994. /* 拟合选择日期 */
  995. changeFitDate(item) {
  996. if(item.fit_date[0] && item.fit_date[1]) {
  997. let differ = this.$moment(item.fit_date[1]).diff(
  998. this.$moment(item.fit_date[0]),
  999. 'days',
  1000. true
  1001. )
  1002. if(differ < 2) {
  1003. item.fit_date = [];
  1004. this.$message.warning(`${differ<0?'开始日期不能晚于结束日期':'日期间隔不得少于两天'}`)
  1005. }
  1006. }
  1007. },
  1008. /* 设置环比增加值 */
  1009. setRingAddHandle({edbarr,fixedValue},index) {
  1010. this.click_rule_index = index;
  1011. this.dynamicDifferList = edbarr;
  1012. this.dialog_formula = fixedValue;
  1013. this.isOpenDialog = true;
  1014. },
  1015. /* 规则动态环差信息 */
  1016. saveDynamicDifferRule({arr,formula}) {
  1017. this.rulesArr[this.click_rule_index].edbarr = arr;
  1018. this.rulesArr[this.click_rule_index].fixedValue = formula;
  1019. console.log(this.rulesArr)
  1020. },
  1021. /* 追加年份 */
  1022. pushYear(e,item) {
  1023. this.select_year = '';
  1024. if(item.season_years.includes(e)) return this.$message.warning('年份已存在')
  1025. item.season_years.push(e)
  1026. },
  1027. /* 删除年份 */
  1028. removeYear(year,item) {
  1029. item.season_years.splice(item.season_years.findIndex(_ => _===year),1)
  1030. },
  1031. /*小数点*/
  1032. filterCode(item) {
  1033. item.fixedValue=item.fixedValue.replace('.','');
  1034. },
  1035. },
  1036. };
  1037. </script>
  1038. <style lang="scss">
  1039. .add-predicedb-page {
  1040. .aside-warp {
  1041. /* .el-form-item {
  1042. margin-bottom: 0;
  1043. } */
  1044. .el-form-item__content {
  1045. line-height: 1;
  1046. }
  1047. }
  1048. .el-collapse-item__header {
  1049. background-color: #F0F2F5;
  1050. margin-bottom: 0;
  1051. border-bottom: 1px solid #dcdfe6;
  1052. padding: 0 30px;
  1053. .el-collapse-item__arrow {
  1054. position: absolute;
  1055. left: 8px;
  1056. }
  1057. }
  1058. .el-collapse-item__content {
  1059. padding-bottom: 10px;
  1060. }
  1061. .el-collapse-item.is-disabled .el-collapse-item__header {
  1062. color: #333;
  1063. }
  1064. }
  1065. </style>
  1066. <style lang="scss" scoped>
  1067. div {
  1068. box-sizing: border-box;
  1069. }
  1070. .add-predicedb-page {
  1071. display: flex;
  1072. min-height: calc(100vh - 150px);
  1073. .aside-warp {
  1074. flex-shrink: 0;
  1075. width: 300px;
  1076. background: #ffffff;
  1077. border-radius: 4px;
  1078. border: 1px solid #ececec;
  1079. margin-right: 20px;
  1080. .btn-box {
  1081. height: 80px;
  1082. background: #ffffff;
  1083. box-shadow: 0px 2px 6px 0px rgba(0, 0, 0, 0.06);
  1084. border-radius: 4px 4px 0px 0px;
  1085. opacity: 1;
  1086. border-bottom: 1px solid #ececec;
  1087. padding-top: 20px;
  1088. padding-left: 15px;
  1089. }
  1090. .con {
  1091. padding: 20px;
  1092. max-height: calc(100vh - 200px);
  1093. overflow-y: auto;
  1094. .item {
  1095. margin-bottom: 20px;
  1096. font-size: 14px;
  1097. .label {
  1098. margin-bottom: 10px;
  1099. display: block;
  1100. }
  1101. .season-year-cont {
  1102. padding: 10px;
  1103. margin-top: 15px;
  1104. border: 1px dashed #AAB4CC;
  1105. }
  1106. }
  1107. .tip-wrap {
  1108. color: #999;
  1109. margin-top: 30px;
  1110. }
  1111. }
  1112. }
  1113. .main-wrap {
  1114. flex: 1;
  1115. width: 80%;
  1116. background: #ffffff;
  1117. border-radius: 4px;
  1118. border: 1px solid #ececec;
  1119. }
  1120. .rules-ul {
  1121. border: 1px solid #dcdfe6;
  1122. .del-icon {
  1123. position: absolute;
  1124. right: 10px;
  1125. font-size: 16px;
  1126. color: #f00;
  1127. cursor: pointer;
  1128. }
  1129. .wrap {
  1130. padding: 20px 20px 0;
  1131. li {
  1132. padding-bottom: 20px;
  1133. margin-bottom: 20px;
  1134. border-bottom: 1px solid #dcdfe6;
  1135. &:last-child {
  1136. padding-bottom: 0;
  1137. margin-bottom: 0;
  1138. border-bottom: none;
  1139. }
  1140. }
  1141. }
  1142. }
  1143. .rules-add {
  1144. margin-top: 10px;
  1145. display: flex;
  1146. align-items: center;
  1147. color: #409eff;
  1148. color: pointer;
  1149. }
  1150. }
  1151. .main-wrap {
  1152. .empty-box {
  1153. padding-top: 200px;
  1154. color: #aab4cc;
  1155. text-align: center;
  1156. img {
  1157. width: 150px;
  1158. }
  1159. }
  1160. .con-box {
  1161. .top-title {
  1162. height: 80px;
  1163. background: #ffffff;
  1164. box-shadow: 0px 2px 6px 0px rgba(0, 0, 0, 0.06);
  1165. border-radius: 4px 4px 0px 0px;
  1166. border-bottom: 1px solid #ececec;
  1167. font-size: 18px;
  1168. display: flex;
  1169. padding-left: 30px;
  1170. align-items: center;
  1171. }
  1172. }
  1173. }
  1174. </style>