Explorar o código

Merge remote-tracking branch 'origin/master'

wukai hai 8 meses
pai
achega
dc5006b20e

+ 33 - 0
src/api/risk/r2.js

@@ -0,0 +1,33 @@
+import request from '@/utils/request'
+
+export const listModel = query => request({
+    url: '/risk/model/list',
+    method: 'get',
+    params: query
+})
+
+export const listModelMs = () => request({
+    url: '/risk/model/ms/list',
+    method: 'get',
+})
+
+export const listModelObj = metricsId => request({
+    url: '/risk/model/obj/list/' + metricsId,
+    method: 'get',
+})
+
+export const listAnalysis = query => request({
+    url: '/risk/analysis/list',
+    method: 'get',
+    params: query
+})
+
+export const analysisAtOnce = riskId => request({
+    url: '/risk/analysis/atOnce/' + riskId,
+    method: 'get',
+})
+
+export const analysisTypeTrend = id => request({
+    url: '/risk/analysis/type3/trend/' + id,
+    method: 'get',
+})

+ 13 - 0
src/api/risk/r3.js

@@ -0,0 +1,13 @@
+import request from '@/utils/request'
+
+export const listAnalysis = query => request({
+    url: '/risk/analysis/list',
+    method: 'get',
+    params: query
+})
+
+export const listModel = query => request({
+    url: '/risk/model/list',
+    method: 'get',
+    params: query
+})

+ 132 - 0
src/views/risk/r2/components/addModel.vue

@@ -0,0 +1,132 @@
+<template>
+  <div>
+    <el-form ref="objRef" :model="form" :rules="rules" label-width="120px" label-suffix=":">
+      <el-form-item label="指标" prop="metricsId">
+        <el-select v-model="form.metricsId" filterable placeholder="请输入..." style="width: 240px"
+                   @change="changeMetricsId">
+          <el-option v-for="item in metricsData" :key="item.metricsId" :label="item.metricsName"
+                     :value="item.metricsId"/>
+        </el-select>
+      </el-form-item>
+      <el-form-item label="分析类型">
+        异类分析
+      </el-form-item>
+      <el-form-item label="定时分析" prop="timeSwitch" required>
+        <el-radio-group v-model="form.timeSwitch">
+          <el-radio label="Y">开启</el-radio>
+          <el-radio label="N">关闭</el-radio>
+        </el-radio-group>
+      </el-form-item>
+      <el-form-item label="指标对象" prop="objType" required>
+        <el-select v-model="form.objType" filterable placeholder="请选择..." style="width: 240px"
+                   :disabled="!form.metricsId" @change="changeObjType">
+          <el-option v-for="item in [{key:'1',label:'全部'},{key:'2',label:'自定义'}]" :key="item.key"
+                     :label="item.label" :value="item.key"/>
+        </el-select>
+        <div class="tree-row" v-if="form.objType === '2'">
+          <el-tree style="max-width: 600px" :props="props" :data="modelTreeData" show-checkbox ref="treeRef"
+                   node-key="objId"
+                   @check-change="handleCheckChange"/>
+        </div>
+      </el-form-item>
+    </el-form>
+    <div class="btn-row">
+      <el-button type="primary" @click="submitForm">提交</el-button>
+      <el-button @click="emit('cancel')">取消</el-button>
+    </div>
+  </div>
+</template>
+<script setup lang="ts">
+import {onMounted, reactive} from "vue";
+import {listModelMs, listModelObj} from "@/api/risk/r2";
+import {addModel, updateModel} from "@/api/risk/model"
+
+const objRef = ref(null)
+const {proxy} = getCurrentInstance()
+const emit = defineEmits(["cancel", "success"])
+const treeRef = ref(null)
+const riskIds = ref(null)
+const treeIndex = ref([])
+const data = reactive({
+  form: {
+    metricsName: "",
+    riskType: "3",
+    timeSwitch: "Y",
+    metricsId: null,
+    objType: "1",
+    objIds: []
+  },
+  rules: {
+    metricsId: {required: true, message: "请选择指标"}
+  }
+})
+const metricsData = ref([])
+const modelTreeData = ref([])
+const {form, rules} = toRefs(data)
+
+const props = {
+  label: 'objName',
+}
+
+function handleCheckChange(data, checked) {
+  form.value.objIds = checked ? [...form.value.objIds, data.objId] : form.value.objIds.filter(item => item !== data.objId)
+}
+
+function changeMetricsId(id) {
+  form.value.metricsName = metricsData.value.find(item => item.metricsId === id).metricsName
+  changeObjType(form.value.objType)
+}
+
+async function changeObjType(val) {
+  if (val === "2") {
+    form.value.objIds = []
+    const res = await listModelObj(form.value.metricsId)
+    modelTreeData.value = res.data
+  } else {
+    form.value.objIds = []
+  }
+}
+
+onMounted(async () => {
+  const res = await listModelMs()
+  metricsData.value = res.data
+})
+
+async function submitForm() {
+  objRef.value.validate(async valid => {
+    if (valid) {
+      const res = riskIds.value ? await updateModel({...form.value, riskId: riskIds.value}) : await addModel(form.value)
+      proxy.$message.success(res.msg)
+      emit('success')
+    }
+  })
+}
+
+async function editInfo(row) {
+  treeIndex.value = []
+  const {metricsName, riskType, timeSwitch, metricsId, objType, riskId} = row
+  form.value = {metricsName, riskType, timeSwitch, metricsId, objType, objIds:[]}
+  riskIds.value = riskId
+  await changeObjType(objType)
+  treeRef.value!.setCheckedNodes(row.riskObjList)
+}
+
+defineExpose({editInfo})
+
+</script>
+<style scoped lang="scss">
+.tree-row {
+  width: 100%;
+  border: 1px solid #f1f1f1;
+  padding: 5px;
+  margin-top: 10px;
+}
+
+.btn-row {
+  display: flex;
+  justify-content: center;
+  margin: 20px -20px 0 -20px;
+  padding-top: 20px;
+  border-top: 1px solid #f1f1f1;
+}
+</style>

+ 59 - 0
src/views/risk/r2/components/historyRisk.vue

@@ -0,0 +1,59 @@
+<template>
+  <el-table v-loading="loading" :data="analysisData" border width="100%">
+    <el-table-column label="#" type="index" align="center"/>
+    <el-table-column label="分析时间">
+      <template #default="scope">
+        {{scope.row.analyseTime}}
+      </template>
+    </el-table-column>
+    <el-table-column label="操作" width="150" align="center">
+      <template #default="scope">
+        <el-button type="primary" link icon="edit" @click="handleShowDetail(scope.row)">查看</el-button>
+      </template>
+    </el-table-column>
+  </el-table>
+  <pagination
+      v-show="total>0"
+      :total="total"
+      v-model:page="page.pageNum"
+      v-model:limit="page.pageSize"
+      @pagination="getHistoryList"
+  />
+
+  <el-dialog :title="'分析详情'" v-model="visible" width="800" append-to-body>
+    <risk-detail :rows="detailRow" v-if="visible" />
+  </el-dialog>
+</template>
+<script setup lang="ts">
+import {onMounted} from "vue";
+import {listAnalysis} from "@/api/risk/r2"
+const props = defineProps(['rows'])
+import riskDetail from "./riskDetail.vue"
+const analysisData = ref([])
+const visible = ref(false)
+const loading = ref(false)
+const total = ref(0)
+const detailRow = ref({})
+const page = ref({
+  pageNum: 1,
+  pageSize: 10
+})
+onMounted(()=>{
+  getHistoryList()
+})
+
+function handleShowDetail(row){
+  visible.value = true
+  detailRow.value = row
+}
+
+async function getHistoryList(){
+  loading.value = true
+  const res = await listAnalysis({riskId: props.rows.riskId,...page.value})
+  analysisData.value = res.rows
+  loading.value = false
+  total.value = res.total
+}
+</script>
+<style scoped lang="scss">
+</style>

+ 110 - 0
src/views/risk/r2/components/riskDetail.vue

@@ -0,0 +1,110 @@
+<template>
+  <div class="risk-detail">
+    <div class="risk-row">
+      <div>
+        <div class="top-title">对象TOP</div>
+        <div v-for="(item,index) in analysisData.top" :key="item.riskObjId" class="top-style" @click="initChart(item)">
+          <span class="index" :style="`background:${colors[index]||'#A1B3D2'}`">
+            {{ index + 1 }}
+          </span>
+          <div class="progress-row">
+            {{ item.name }}
+            {{ maxNum }}
+            <div class="progress-bg">
+              <div class="progress-bar"
+                   :style="`width:${item.num/maxNum*100}%;background:${colors[index]||'#A1B3D2'}`"/>
+              {{ item.num }}
+            </div>
+          </div>
+        </div>
+      </div>
+      <div>
+        <div class="top-title">离群分析</div>
+        <el-image style="width: 400px" :src="analysisData.imgPath" fit="scale-down"/>
+      </div>
+    </div>
+  </div>
+  <div class="chart-row"  v-if="activeTop">
+    <div ref="chartTop" style="height: 300px;" />
+  </div>
+</template>
+<script setup lang="ts">
+import {parseTime} from "@/utils/ruoyi"
+import {analysisAtOnce,analysisTypeTrend} from "@/api/risk/r2"
+import {watchEffect} from "vue";
+import * as echarts from "echarts";
+
+const chartTop = ref(null)
+const maxNum = ref(0)
+const props = defineProps(['rows'])
+const analysisData = ref({})
+const colors = ['#f56c6c', '#e6a23c', '#409EFF']
+const activeTop = ref(null)
+watchEffect(() => {
+  getAnalysisData()
+})
+
+async function getAnalysisData() {
+  const res = await analysisAtOnce(props.rows.riskId)
+  analysisData.value = res.data
+  maxNum.value = Math.max(...analysisData.value.top.map(item => item.num)) + 20
+}
+
+async function initChart(item){
+  activeTop.value = item
+  const {data} = await analysisTypeTrend(item.id)
+  const myChart = echarts.init(chartTop.value);
+  const option = {
+    tooltip: {
+      trigger: 'axis'
+    },
+    grid: {
+      left: '0%',
+      right: '0%',
+      bottom: '3%',
+      top: '15%',
+      containLabel: true
+    },
+    xAxis: {
+      type: 'category',
+      boundaryGap: false,
+      data: data.times.map(item => parseTime(item, '{y}-{m}-{d} {h}:{i}:{s}')),
+      axisLine: {
+        show: true,
+        lineStyle: {
+          color: "#9FB9E7",
+          width: 0,
+        }
+      },
+      axisTick: {
+        show: false,
+      },
+    },
+    yAxis: {
+      type: 'value',
+      axisLine: {
+        show: true,
+        lineStyle: {
+          color: "#9FB9E7",
+          width: 0,
+        }
+      },
+      splitLine: {
+        lineStyle: {
+          color: "#6B8AC080",
+          type: 'dashed',
+        }
+      },
+    },
+    series:{
+        type: 'line',
+        stack: 'Total',
+        smooth: true,
+        symbolSize: 0,
+        data: data.values
+      }
+  };
+  myChart.setOption(option)
+}
+
+</script>

+ 55 - 0
src/views/risk/r2/css/style.scss

@@ -0,0 +1,55 @@
+@import "@/assets/styles/common.scss";
+.link-text {
+  font-size: 12px;
+  margin-right: 10px;
+}
+.risk-detail {
+  .risk-row {
+    @include flexBetween;
+    align-items: flex-start;
+    .top-style{
+      @include flexStart;
+      margin-bottom:10px;
+      cursor: pointer;
+      &:last-child{
+        margin-bottom:0;
+      }
+      .index{
+        width: 20px;
+        font-size: 12px;
+        text-align: center;
+        line-height: 20px;
+        display: inline-block;
+        background: #A1B3D2;
+        border-radius: 100%;
+        color:white;
+        margin-right:10px;
+      }
+      .progress-row{
+        width: 300px;
+        .progress-bg{
+          background: #A1B3D280;
+          height:14px;
+          width: 100%;
+          border-radius: 6px;
+          margin-top:5px;
+          @include flexStart;
+          font-size: 12px;
+          .progress-bar{
+            height:14px;
+            border-radius: 6px;
+            background: #A1B3D2;
+            margin-right:5px;
+          }
+        }
+      }
+
+    }
+  }
+  .top-title{
+    font-weight: bolder;
+    margin-bottom: 10px;
+    border-bottom: 1px solid #EEE;
+    padding-bottom: 10px;
+  }
+}

+ 136 - 0
src/views/risk/r2/index.vue

@@ -0,0 +1,136 @@
+<template>
+  <div class="app-container">
+    <el-row :gutter="10" class="mb10">
+      <el-col :span="18">
+        <el-input v-model="params.name" placeholder="请输入分类名称" clearable @keyup.enter="handleQuery"
+                  style="width: 200px;margin-right:10px;"/>
+        <el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
+      </el-col>
+      <el-col :span="6" style="text-align: right">
+        <el-button type="primary" plain icon="Plus" @click="handleAdd">新增</el-button>
+      </el-col>
+    </el-row>
+    <el-table v-loading="loading" :data="modelData" border width="100%">
+      <el-table-column prop="riskId" label="风险ID" align="center" width="80"/>
+      <el-table-column prop="metricsName" label="指标名称" align="left"/>
+      <el-table-column label="运行业对象" align="left" width="250">
+        <template #default="scope">
+          <span v-for="(item,index) in scope.row.riskObjList" :key="item.objId">
+            {{ item.objName }} <span v-if="index != scope.row.riskObjList.length-1">,</span>
+          </span>
+        </template>
+      </el-table-column>
+      <el-table-column label="分析类型" align="left" width="120">
+        <template #default="scope">
+          {{ scope.row.riskType === "3" ? "异类分析" : "-" }}
+        </template>
+      </el-table-column>
+      <el-table-column label="定时分析" align="center" width="100">
+        <template #default="scope">
+          <dict-tag :options="sys_yes_no" :value="scope.row.timeSwitch"/>
+        </template>
+      </el-table-column>
+      <el-table-column label="操作" align="left" width="220">
+        <template #default="scope">
+          <el-link type="primary" :underline="false" class="link-text" @click="handleNowAnalyse(scope.row)">立即分析</el-link>
+          <el-link type="primary" :underline="false" @click="handleHistory(scope.row)" class="link-text">历史分析</el-link>
+          <el-link type="primary" :underline="false" @click="handleEdit(scope.row)" class="link-text">编辑</el-link>
+          <el-popconfirm title="确认要删除该条信息吗?" @confirm="handleDelete(scope.row)" width="200">
+            <template #reference>
+              <el-link type="danger" :underline="false" class="link-text">删除</el-link>
+            </template>
+          </el-popconfirm>
+        </template>
+      </el-table-column>
+    </el-table>
+    <pagination
+        v-show="total>0"
+        :total="total"
+        v-model:page="params.pageNum"
+        v-model:limit="params.pageSize"
+        @pagination="handleQuery"
+    />
+  </div>
+  <el-dialog :title="isComponents[active].title" v-model="visible" width="800" append-to-body>
+    <component :is="isComponents[active].component" @cancel="visible=false" v-if="visible" ref="amRef" @success="handleQuery" :rows="rows"/>
+  </el-dialog>
+</template>
+<script setup lang="ts">
+import {onMounted, reactive} from "vue";
+import {listModel} from "@/api/risk/r2"
+import {delModel} from "@/api/risk/model"
+import historyRisk from "./components/historyRisk.vue"
+import addModel from "./components/addModel.vue"
+import riskDetail from "./components/riskDetail.vue"
+
+const {proxy} = getCurrentInstance()
+const { sys_yes_no} = proxy.useDict('sys_yes_no');
+
+const visible = ref(false)
+const modelData = ref([])
+const loading = ref(false)
+const amRef = ref(null)
+const rows= ref({})
+const isComponents= [
+  {title:"新增指标",component:addModel},
+  {title:"修改指标",component:addModel},
+  {title:"历史分析",component:historyRisk},
+  {title:"立即分析",component:riskDetail},
+]
+const active = ref(0)
+
+const params = reactive({
+  name: "",
+  pageNum: 1,
+  pageSize: 10,
+  riskType: 3
+})
+const total = ref(0)
+onMounted(() => {
+  handleQuery()
+})
+
+async function handleNowAnalyse(row){
+  active.value = 3
+  visible.value = true;
+  rows.value = row
+}
+
+function handleHistory(row){
+  active.value = 2
+  visible.value = true;
+  rows.value = row
+}
+
+function handleEdit(row) {
+  active.value = 1
+  visible.value = true;
+  proxy.$nextTick(() => {
+    amRef.value.editInfo(row)
+  })
+}
+
+async function handleDelete(row) {
+  const res = await delModel(row.riskId)
+  proxy.$message.success(res.msg)
+  await handleQuery()
+}
+
+
+function handleAdd() {
+  active.value = 0
+  visible.value = true
+}
+
+async function handleQuery() {
+  visible.value = false
+  loading.value = true
+  const res = await listModel(params)
+  modelData.value = res.rows
+  total.value = res.total
+  loading.value = false
+}
+</script>
+<style lang="scss">
+@import "./css/style.scss";
+</style>

+ 64 - 0
src/views/risk/r3/components/historyAnalyse.vue

@@ -0,0 +1,64 @@
+<template>
+  <el-row :gutter="20">
+    <el-col :span="24">
+      <el-input v-model="params.metricsName" placeholder="请输入指标名称" style="width: 200px;margin-right: 10px" size="small"/>
+      <el-input v-model="params.objName" placeholder="请输入对象名称" style="width: 200px;margin-right: 10px" size="small" />
+      <el-button type="primary" icon="Search" @click="getAnalysis" size="small">搜索</el-button>
+    </el-col>
+  </el-row>
+  <el-table v-loading="loading" :data="analysisList" border width="100%" size="small" style="margin-top:10px">
+    <el-table-column label="#" type="index" width="80" align="center"/>
+    <el-table-column label="指标名称" prop="metricsName"/>
+    <el-table-column label="运行业务对象" prop="objName"/>
+    <el-table-column label="分析时间" prop="analyseTime"/>
+    <el-table-column label="分析结果">
+      <template #default="scope">
+        <el-tag v-if="scope.row.result=='趋势恶化'" type="danger">{{scope.row.result}}</el-tag>
+        <el-tag v-else >{{scope.row.result}}</el-tag>
+      </template>
+    </el-table-column>
+    <el-table-column label="操作" width="120" align="center">
+      <template #default="scope">
+        <el-button type="primary" link icon="edit">查看</el-button>
+      </template>
+    </el-table-column>
+  </el-table>
+  <pagination
+      style="margin-top:10px"
+      v-show="total>0"
+      layout="prev, pager, next"
+      :total="total"
+      :background="false"
+      v-model:page="params.pageNum"
+      v-model:limit="params.pageSize"
+      @pagination="getAnalysis"
+  />
+
+</template>
+<script setup lang="ts">
+import {listAnalysis} from "@/api/risk/r3"
+import {onMounted} from "vue";
+const params = ref({
+  riskType:1,
+  metricsName:"",
+  objName:"",
+  pageNum:1,
+  pageSize:10
+})
+const analysisList = ref([])
+const total = ref(0)
+const loading = ref(true)
+
+onMounted(()=>{
+  getAnalysis()
+})
+
+async function getAnalysis(){
+  loading.value = true
+  const res = await listAnalysis(params.value)
+  analysisList.value = res.rows
+  total.value = res.total
+  loading.value = false
+}
+
+</script>

+ 69 - 0
src/views/risk/r3/components/hzAnalyse.vue

@@ -0,0 +1,69 @@
+<template>
+  <el-row :gutter="20">
+    <el-col :span="18">
+      <el-input v-model="params.metricsName" placeholder="请输入指标名称" style="width: 200px;margin-right: 10px" size="small"/>
+      <el-button type="primary" icon="Search" @click="getAnalysis" size="small">搜索</el-button>
+    </el-col>
+    <el-col :span="6" style="text-align: right">
+      <el-button type="primary" icon="Plus" @click="addModel" size="small">新增</el-button>
+    </el-col>
+  </el-row>
+  <el-table v-loading="loading" :data="modelList" border width="100%" size="small" style="margin-top:10px">
+    <el-table-column label="#" type="index" width="80" align="center"/>
+    <el-table-column label="指标名称" prop="metricsName" />
+    <el-table-column label="运行业务对象"  width="250">
+      <template #default="scope">
+        <span v-for="(item,index) in scope.row.riskObjList" :key="item.objId">{{item.objName}}
+          <span v-if="index != scope.row.riskObjList.length-1">,</span>
+        </span>
+      </template>
+    </el-table-column>
+    <el-table-column label="分析类型" width="150">
+      <template #default="scope">
+        <dict-tag :options="analy_result" :value="scope.row.riskType"/>
+      </template>
+    </el-table-column>
+    <el-table-column label="定时分析" width="100">
+      <template #default="scope">
+        <dict-tag :options="sys_yes_no" :value="scope.row.timeSwitch"/>
+      </template>
+    </el-table-column>
+    <el-table-column label="趋势恶化方向" prop="metricsName" width="100">
+      <template #default="scope">
+        <dict-tag :options="risk_up" :value="scope.row.upDown"/>
+      </template>
+    </el-table-column>
+  </el-table>
+</template>
+<script setup lang="ts">
+import {listModel} from "@/api/risk/r3"
+import {onMounted} from "vue";
+const {proxy} = getCurrentInstance()
+const { analy_result ,risk_up,sys_yes_no} = proxy.useDict('analy_result','risk_up','sys_yes_no');
+const params = ref({
+  riskType:1,
+  metricsName:"",
+  pageNum:1,
+  pageSize:10
+})
+const total = ref(0)
+const modelList = ref([])
+const loading = ref(true)
+
+onMounted(()=>{
+  getModelList()
+})
+
+function addModel(){
+
+}
+
+async function getModelList(){
+  loading.value = true
+  const res = await listModel(params.value)
+  modelList.value = res.rows
+  total.value = res.total
+  loading.value = false
+}
+
+</script>

+ 18 - 0
src/views/risk/r3/css/style.scss

@@ -0,0 +1,18 @@
+@import "@/assets/styles/common.scss";
+.state-err{
+  height:calc(100vh - 72px);
+  margin:-10px;
+  .state-row{
+    height:calc(100% / 2);
+    overflow: auto;
+    padding:10px;
+    @include borderBox();
+    margin-bottom:10px;
+    &:last-child{
+      margin-bottom:0;
+    }
+  }
+  .el-card__header{
+    padding:5px 5px 5px !important;
+  }
+}

+ 20 - 0
src/views/risk/r3/index.vue

@@ -0,0 +1,20 @@
+<template>
+  <div class="app-container state-err">
+    <el-card class="state-row">
+      <template #header>成史分析</template>
+      <history-analyse/>
+    </el-card>
+
+    <el-card class="state-row">
+      <template #header>频次分析指标</template>
+      <hz-analyse/>
+    </el-card>
+  </div>
+</template>
+<script setup lang="ts">
+import historyAnalyse from "./components/historyAnalyse.vue"
+import hzAnalyse from "./components/hzAnalyse.vue"
+</script>
+<style lang="scss">
+@import "./css/style.scss";
+</style>