FittingResidualsCalculate.vue 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575
  1. <script setup>
  2. import apiDataEDB from '@/api/dataEDB'
  3. import {calculateTypeTipsMap} from '../../util/config'
  4. import {ref,reactive,watch} from 'vue'
  5. import SelectEDB from './SelectEDB.vue'
  6. import SelectEDBClassify from '../../components/SelectEDBClassify.vue'
  7. import SelectEDBUnit from '../../components/SelectEDBUnit.vue'
  8. import SelectEDBFrequency from '../../components/SelectEDBFrequency.vue'
  9. import SelectDateRange from '@/components/SelectDateRange.vue'
  10. import EDBHistory from '@/views/dataEDB/components/EDBHistory.vue'
  11. import { showToast } from 'vant'
  12. import { useRoute, useRouter } from 'vue-router'
  13. import moment from 'moment'
  14. import {useToHistoryPage} from '@/hooks/edb/useToHistoryPage'
  15. const {toHistoryPage} = useToHistoryPage()
  16. const route=useRoute()
  17. const router=useRouter()
  18. const props=defineProps({
  19. edbInfo:{
  20. type:Object,
  21. default:null
  22. }
  23. })
  24. watch(
  25. ()=>props.edbInfo,
  26. ()=>{
  27. if(['edit','preview'].includes(route.query.type)){
  28. props.edbInfo.CalculateList.forEach(item=>{
  29. if(item.FromTag==='A'){
  30. independentEDBInfo.value={
  31. EdbInfoId:item.FromEdbInfoId,
  32. EdbName:item.FromEdbName,
  33. StartDate:item.StartDate,
  34. EndDate:item.EndDate
  35. }
  36. selfMoveType.value=item.MoveValue===0?0:1
  37. selfMoveVal.value=item.MoveValue
  38. }
  39. if(item.FromTag==='B'){
  40. dependentEDBInfo.value={
  41. EdbInfoId:item.FromEdbInfoId,
  42. EdbName:item.FromEdbName,
  43. StartDate:item.StartDate,
  44. EndDate:item.EndDate
  45. }
  46. }
  47. })
  48. fittingDate.value=props.edbInfo.EdbInfoDetail.CalculateFormula.split(',')
  49. baseInfo.name=props.edbInfo.EdbInfoDetail.EdbName
  50. baseInfo.unit=props.edbInfo.EdbInfoDetail.Unit
  51. baseInfo.classify=props.edbInfo.EdbInfoDetail.ClassifyId
  52. baseInfo.frequency=props.edbInfo.EdbInfoDetail.Frequency
  53. setTimeout(() => {
  54. selectEDBClassifyINS.value?.getSelectClassifyOpt(props.edbInfo.EdbInfoDetail.ClassifyId)//获取选择的分类目录
  55. }, 1000);
  56. getEDBCorrelationIndex()
  57. }
  58. }
  59. )
  60. // 预览页面
  61. const isPreview=ref(route.query.type==='preview'||false)
  62. const source=ref(Number(route.query.source)||0)//计算类型
  63. //公式说明
  64. const showTips=ref(false)
  65. const tipsContent=ref(calculateTypeTipsMap.get(['toMonthSeason','accumulate'].includes(route.query.source)? route.query.source: Number(route.query.source))||'')
  66. // 获取两个指标的相关系数
  67. const correlationIndex=ref('')
  68. async function getEDBCorrelationIndex(){
  69. if(!independentEDBInfo.value||!dependentEDBInfo.value||fittingDate.value.length===0) return correlationIndex.value=''
  70. if(selfMoveType.value===1&&!selfMoveVal.value) return correlationIndex.value=''
  71. const params={
  72. Formula:fittingDate.value.join(','),
  73. EdbInfoIdArr:[
  74. {
  75. EdbInfoId:independentEDBInfo.value.EdbInfoId,
  76. FromTag: 'A',
  77. MoveValue:selfMoveType.value===0?0:Number(selfMoveVal.value)
  78. },
  79. {
  80. EdbInfoId:dependentEDBInfo.value.EdbInfoId,
  81. FromTag: 'B',
  82. MoveValue:0
  83. }
  84. ]
  85. }
  86. const res=await apiDataEDB.edbCorrelationIndex(params)
  87. if(res.Ret===200){
  88. correlationIndex.value=res.Data
  89. }
  90. }
  91. // 选择指标
  92. const showSelectEDB=ref(false)
  93. const independentEDBInfo=ref(null)//自变量指标
  94. const dependentEDBInfo=ref(null)//因变量指标
  95. let currentSelectEDBType=''
  96. function handleShowSelectEDB(type){
  97. if(isPreview.value) return
  98. currentSelectEDBType=type
  99. showSelectEDB.value=true
  100. }
  101. function handleConfirmSelectEDB(e){
  102. if(currentSelectEDBType==='independent'){
  103. independentEDBInfo.value=e
  104. baseInfo.unit=e.Unit
  105. baseInfo.frequency=e.Frequency
  106. selectEDBClassifyINS.value?.getSelectClassifyOpt(e.ClassifyId)
  107. }else{
  108. dependentEDBInfo.value=e
  109. }
  110. if(independentEDBInfo.value&&dependentEDBInfo.value){
  111. baseInfo.name=`${dependentEDBInfo.value.EdbName}拟合残差/${independentEDBInfo.value.EdbName}`
  112. }
  113. getEDBCorrelationIndex()
  114. }
  115. // 类型
  116. const selfMoveType=ref(0)
  117. const selfMoveVal=ref('')
  118. function handleSelfMoveTypeChange(){
  119. selfMoveVal.value=''
  120. getEDBCorrelationIndex()
  121. }
  122. //拟合时间段
  123. const showFittingDate=ref(false)
  124. const fittingDate=ref([])
  125. function handleConfirmFittingDate(e){
  126. // 日期间隔不得少于两天
  127. const diff=moment(e[1]).diff(e[0],'days')
  128. if(diff<2){
  129. showToast('日期间隔不得少于两天')
  130. return
  131. }
  132. fittingDate.value=e
  133. getEDBCorrelationIndex()
  134. }
  135. // 基础信息
  136. const edbNameInputFocus=ref(false)
  137. const baseInfo=reactive({
  138. name:'',
  139. unit:'',
  140. classify:'',
  141. frequency:''
  142. })
  143. // 选择单位
  144. const showSelectUnit=ref(false)
  145. function onConfirmSelectUnit(value){
  146. baseInfo.unit=value
  147. }
  148. //选择分类
  149. const showSelectClassify=ref(false)
  150. const classifyStr=ref('')
  151. const selectEDBClassifyINS=ref(null)
  152. function handleConfirmClassify({value,selectedOptions}){
  153. if(selectedOptions.length===0){
  154. baseInfo.classify=''
  155. classifyStr.value=''
  156. return
  157. }
  158. baseInfo.classify=value
  159. const textArr=selectedOptions.map(item=>item.ClassifyName)
  160. classifyStr.value=`${textArr.join('/')}`
  161. }
  162. //选择频度
  163. const showSelectFrequency=ref(false)
  164. function handleConfirmFrequency(value){
  165. baseInfo.frequency=value
  166. }
  167. // 提交计算
  168. const saveBtnLoading=ref(false)
  169. async function handleSave(){
  170. if(!independentEDBInfo.value){
  171. showToast('自变量不能为空')
  172. return
  173. }
  174. if(!dependentEDBInfo.value){
  175. showToast('因变量不能为空')
  176. return
  177. }
  178. if(fittingDate.value.length===0){
  179. showToast('拟合时间段不能为空')
  180. return
  181. }
  182. if(!baseInfo.name){
  183. showToast('指标名称不能为空')
  184. return
  185. }
  186. if(!baseInfo.unit){
  187. showToast('指标单位不能为空')
  188. return
  189. }
  190. if(!baseInfo.classify){
  191. showToast('指标目录不能为空')
  192. return
  193. }
  194. if(!baseInfo.frequency){
  195. showToast('指标频度不能为空')
  196. return
  197. }
  198. const params={
  199. Source: source.value,
  200. EdbName: baseInfo.name,
  201. Unit: baseInfo.unit,
  202. ClassifyId: baseInfo.classify,
  203. Frequency: baseInfo.frequency,
  204. Formula:fittingDate.value.join(','),
  205. EdbInfoIdArr:[
  206. {
  207. EdbInfoId:independentEDBInfo.value.EdbInfoId,
  208. FromTag: 'A',
  209. MoveValue:selfMoveType.value===0?0:Number(selfMoveVal.value)
  210. },
  211. {
  212. EdbInfoId:dependentEDBInfo.value.EdbInfoId,
  213. FromTag: 'B',
  214. MoveValue:0
  215. }
  216. ]
  217. }
  218. saveBtnLoading.value=true
  219. const res=route.query.type==='edit'?await apiDataEDB.editCalculateEDB({...params,EdbInfoId:Number(route.query.edbInfoId)}) : await apiDataEDB.addCalculateEDB(params)
  220. saveBtnLoading.value=false
  221. if(res.Ret===200){
  222. showToast(res.Msg)
  223. setTimeout(() => {
  224. if(route.query.type==='edit'){
  225. router.back()
  226. }else{
  227. router.replace({
  228. path:'/dataEDB/detail',
  229. query:{
  230. edbInfoId:res.Data.EdbInfoId
  231. }
  232. })
  233. }
  234. }, 1500);
  235. }
  236. }
  237. //点击选择的指标左侧图标查看指标详情
  238. const showEDBHistory=ref(false)// 显示指标溯源
  239. const edbHistoryId=ref(0)
  240. function handleShowEDBHistory(data){
  241. //计算指标打开弹窗,基础指标打开新页面
  242. if(data.EdbType===2){
  243. /* edbHistoryId.value=data.EdbInfoId
  244. showEDBHistory.value=true */
  245. toHistoryPage(data.EdbInfoId)
  246. }else{
  247. const routerEl=router.resolve({
  248. path:'/dataEDB/detail',
  249. query:{
  250. edbInfoId:data.EdbInfoId
  251. }
  252. })
  253. window.open(routerEl.href,'_blank')
  254. }
  255. }
  256. </script>
  257. <template>
  258. <div class="fitting-residuals-wrap">
  259. <section class="section select-edb-box">
  260. <van-field
  261. label="自变量"
  262. required
  263. :right-icon="!isPreview?'arrow':''"
  264. @click-input="handleShowSelectEDB('independent')"
  265. :disabled="isPreview"
  266. >
  267. <template #left-icon>
  268. <div class="left-icon" v-if="independentEDBInfo" @click="handleShowEDBHistory(independentEDBInfo)">
  269. <svg-icon name="edb-history-tag" size="24px"/>
  270. </div>
  271. </template>
  272. <template #input>
  273. <div class="edb-info-box">
  274. <div class="edb-info" v-if="independentEDBInfo">
  275. <span class="name">{{independentEDBInfo.EdbName}}</span>
  276. <span class="time">{{independentEDBInfo.StartDate}}至{{independentEDBInfo.EndDate}}</span>
  277. </div>
  278. <span class="placeholder" v-else>请选择指标</span>
  279. </div>
  280. </template>
  281. </van-field>
  282. <van-cell>
  283. <div class="self-move-type-box">
  284. <van-radio-group :disabled="isPreview" v-model="selfMoveType" shape="dot" direction="horizontal" @change="handleSelfMoveTypeChange">
  285. <van-radio :name="0">标准指标</van-radio>
  286. <van-radio :name="1">领先天数</van-radio>
  287. </van-radio-group>
  288. <div class="day-box" v-show="selfMoveType===1">
  289. <input class="input" :disabled="isPreview" type="number" :min="0" v-model="selfMoveVal" @change="getEDBCorrelationIndex()">
  290. <span>天</span>
  291. </div>
  292. </div>
  293. </van-cell>
  294. <van-field
  295. label="因变量"
  296. required
  297. :right-icon="!isPreview?'arrow':''"
  298. @click-input="handleShowSelectEDB('dependent')"
  299. :disabled="isPreview"
  300. >
  301. <template #left-icon>
  302. <div class="left-icon" v-if="dependentEDBInfo" @click="handleShowEDBHistory(dependentEDBInfo)">
  303. <svg-icon name="edb-history-tag" size="24px"/>
  304. </div>
  305. </template>
  306. <template #input>
  307. <div class="edb-info-box">
  308. <div class="edb-info" v-if="dependentEDBInfo">
  309. <span class="name">{{dependentEDBInfo.EdbName}}</span>
  310. <span class="time">{{dependentEDBInfo.StartDate}}至{{dependentEDBInfo.EndDate}}</span>
  311. </div>
  312. <span class="placeholder" v-else>请选择指标</span>
  313. </div>
  314. </template>
  315. </van-field>
  316. <van-field
  317. label="拟合时间段"
  318. :right-icon="!isPreview?'arrow':''"
  319. required
  320. @click-input="()=>{if(isPreview) return false ;showFittingDate=true}"
  321. :disabled="isPreview"
  322. >
  323. <template #input>
  324. <div class="edb-info-box">
  325. <div class="edb-info" v-if="fittingDate.length>0">
  326. <span class="name">{{fittingDate[0]}}~{{fittingDate[1]}}</span>
  327. <span class="time" v-if="correlationIndex">相关系数:{{correlationIndex}}</span>
  328. </div>
  329. <span class="placeholder" v-else>请选择时间段</span>
  330. </div>
  331. </template>
  332. </van-field>
  333. </section>
  334. <section class="section baseinfo-box">
  335. <van-field
  336. v-model="baseInfo.name"
  337. label="指标名称"
  338. placeholder="指标名称"
  339. input-align="right"
  340. required
  341. @focus="edbNameInputFocus=true"
  342. @blur="edbNameInputFocus=false"
  343. :disabled="isPreview"
  344. >
  345. <template #right-icon>
  346. <svg-icon v-if="!isPreview" class="edit-icon" name="edit" :color="edbNameInputFocus?'#0052D9':'#333333'"/>
  347. </template>
  348. </van-field>
  349. <van-field
  350. :modelValue="baseInfo.unit"
  351. readonly
  352. label="单位"
  353. placeholder="请选择单位"
  354. input-align="right"
  355. :right-icon="!isPreview?'arrow':''"
  356. required
  357. @click-input="showSelectUnit=true"
  358. :disabled="isPreview"
  359. />
  360. <van-field
  361. :modelValue="classifyStr"
  362. readonly
  363. label="指标目录"
  364. placeholder="请选择指标目录"
  365. input-align="right"
  366. :right-icon="!isPreview?'arrow':''"
  367. required
  368. @click-input="showSelectClassify=true"
  369. :disabled="isPreview"
  370. />
  371. <van-field
  372. :modelValue="baseInfo.frequency"
  373. readonly
  374. label="频度"
  375. placeholder="请选择指标频度"
  376. input-align="right"
  377. :right-icon="!isPreview?'arrow':''"
  378. required
  379. @click-input="showSelectFrequency=true"
  380. :disabled="isPreview"
  381. />
  382. </section>
  383. <div class="formula-intro-btn" @click="showTips=true">
  384. <svg-icon class="icon" name="warning"></svg-icon>
  385. <span>公式说明</span>
  386. </div>
  387. <div class="opt-btns" v-if="!isPreview">
  388. <van-button class="primary2" @click="$router.back()">取消</van-button>
  389. <van-button
  390. type="primary"
  391. :loading="saveBtnLoading"
  392. loading-text="计算中..."
  393. @click="handleSave"
  394. >拟合残差计算</van-button>
  395. </div>
  396. </div>
  397. <!-- 选择指标 -->
  398. <SelectEDB v-model:show="showSelectEDB" :params="{FilterSource:1}" @select="handleConfirmSelectEDB"/>
  399. <!-- 选择时间段 -->
  400. <SelectDateRange v-model:show="showFittingDate" @select="handleConfirmFittingDate"/>
  401. <!-- 选择单位 -->
  402. <SelectEDBUnit v-model:show="showSelectUnit" @select="onConfirmSelectUnit"/>
  403. <!-- 选择分类 -->
  404. <SelectEDBClassify ref="selectEDBClassifyINS" :defaultId="baseInfo.classify" v-model:show="showSelectClassify" @select="handleConfirmClassify" />
  405. <!-- 选择频度 -->
  406. <SelectEDBFrequency v-model:show="showSelectFrequency" @select="handleConfirmFrequency"/>
  407. <!-- 指标溯源 -->
  408. <EDBHistory v-model:show="showEDBHistory" :edbInfoId="edbHistoryId"/>
  409. <!-- 公式说明 -->
  410. <van-dialog
  411. v-model:show="showTips"
  412. :title="$route.query.name"
  413. confirmButtonText='知道啦'
  414. >
  415. <div class="edb-formula-tips-html-box" v-html="tipsContent"></div>
  416. </van-dialog>
  417. </template>
  418. <style lang="scss" scoped>
  419. .fitting-residuals-wrap{
  420. min-height: 90vh;
  421. background-color: $page-bg-grey;
  422. padding-bottom: 210px ;
  423. }
  424. .section{
  425. background-color: #fff;
  426. margin-bottom: 32px;
  427. }
  428. .self-move-type-box{
  429. padding: 20px 0;
  430. display: flex;
  431. justify-content: space-between;
  432. .day-box{
  433. display: flex;
  434. align-items: center;
  435. gap: 5px;
  436. .input{
  437. width: 80px;
  438. height: 48px;
  439. border: 1px solid #DCDCDC;
  440. border-radius: 6px;
  441. text-align: center;
  442. padding: 0 10px;
  443. color: #333;
  444. }
  445. }
  446. }
  447. .select-edb-box{
  448. :deep(.van-cell__right-icon){
  449. align-self: center;
  450. color: #333;
  451. }
  452. .edb-info-box{
  453. width: 100%;
  454. text-align: right;
  455. .placeholder{
  456. color: var(--van-text-color-3);
  457. }
  458. .edb-info{
  459. display: flex;
  460. flex-direction: column;
  461. }
  462. .time{
  463. color: $font-grey_999;
  464. font-size: 24px;
  465. }
  466. }
  467. }
  468. .formula-intro-btn{
  469. width: 180px;
  470. height: 60px;
  471. display: flex;
  472. align-items: center;
  473. justify-content: center;
  474. gap: 5px;
  475. color: $theme-color;
  476. line-height: 1;
  477. background-color: #fff;
  478. border-radius: 32px;
  479. margin-left: auto;
  480. margin-right: var(--van-cell-horizontal-padding);
  481. .icon{
  482. width: 24px;
  483. height: 24px;
  484. }
  485. }
  486. .opt-btns{
  487. position: fixed;
  488. left: 0;
  489. right: 0;
  490. bottom: 0;
  491. background-color: #fff;
  492. z-index: 99;
  493. padding: 48px;
  494. display: flex;
  495. justify-content: space-between;
  496. .van-button{
  497. width: 48%;
  498. max-width: 300PX;
  499. }
  500. }
  501. @media screen and (min-width:$media-width){
  502. .fitting-residuals-wrap{
  503. padding-bottom: 105px;
  504. }
  505. .section{
  506. margin-bottom: 16px;
  507. }
  508. .self-move-type-box{
  509. padding: 10px 0;
  510. .day-box{
  511. gap: 5px;
  512. .input{
  513. width: 40px;
  514. height: 24px;
  515. border-radius: 3px;
  516. padding: 0 5px;
  517. }
  518. }
  519. }
  520. .select-edb-box{
  521. .edb-info-box{
  522. .time{
  523. font-size: 12px;
  524. }
  525. }
  526. }
  527. .formula-intro-btn{
  528. width: 90px;
  529. height: 30px;
  530. gap: 2px;
  531. border-radius: 16px;
  532. .icon{
  533. width: 12px;
  534. height: 12px;
  535. }
  536. }
  537. .opt-btns{
  538. padding: 24px;
  539. justify-content: center;
  540. gap: 10px;
  541. }
  542. }
  543. </style>