提交 9dd9c254 authored 作者: kxjia's avatar kxjia

指标名称

上级 dc235789
...@@ -8,7 +8,7 @@ VITE_GLOB_APP_TITLE = 科技风险管理平台 ...@@ -8,7 +8,7 @@ VITE_GLOB_APP_TITLE = 科技风险管理平台
VITE_GLOB_APP_SHORT_NAME = 科技风险管理平台 VITE_GLOB_APP_SHORT_NAME = 科技风险管理平台
# 单点登录服务端地址 # 单点登录服务端地址
VITE_GLOB_APP_CAS_BASE_URL=http://cas.test.com:8443/cas # VITE_GLOB_APP_CAS_BASE_URL=http://cas.test.com:8443/cas
# 是否开启单点登录 # 是否开启单点登录
...@@ -18,10 +18,10 @@ VITE_GLOB_APP_OPEN_SSO = false ...@@ -18,10 +18,10 @@ VITE_GLOB_APP_OPEN_SSO = false
VITE_GLOB_APP_OPEN_QIANKUN=true VITE_GLOB_APP_OPEN_QIANKUN=true
#后台接口全路径地址(必填) #后台接口全路径地址(必填)
VITE_GLOB_DOMAIN_URL=http://47.98.203.68:8080/stm/ #VITE_GLOB_DOMAIN_URL=http://47.98.203.68:8080/stm/
# 文件预览地址 # 文件预览地址
# VITE_GLOB_ONLINE_VIEW_URL=http://fileview.jeecg.com/onlinePreview # VITE_GLOB_ONLINE_VIEW_URL=http://fileview.jeecg.com/onlinePreview
VITE_GLOB_ONLINE_VIEW_URL=http://47.98.203.68:8080/stm/sys/common/static/ # VITE_GLOB_ONLINE_VIEW_URL=http://47.98.203.68:8080/stm/sys/common/static/
...@@ -9,7 +9,7 @@ VITE_PUBLIC_PATH = / ...@@ -9,7 +9,7 @@ VITE_PUBLIC_PATH = /
VITE_PROXY = [["/stm","http://localhost:8080/stm"],["/upload","http://localhost:3300/upload"]] VITE_PROXY = [["/stm","http://localhost:8080/stm"],["/upload","http://localhost:3300/upload"]]
#后台接口全路径地址(必填) #后台接口全路径地址(必填)
VITE_GLOB_DOMAIN_URL=http://localhost:8080/stm/ VITE_GLOB_DOMAIN_URL=http://localhost:8080/stm
#后台接口父地址(必填) #后台接口父地址(必填)
VITE_GLOB_API_URL=/stm VITE_GLOB_API_URL=/stm
...@@ -33,5 +33,5 @@ VITE_APP_SUB_jeecg-app-1 = '//localhost:8092' ...@@ -33,5 +33,5 @@ VITE_APP_SUB_jeecg-app-1 = '//localhost:8092'
VITE_GLOB_ONLINE_DOCUMENT_VERSION=wps VITE_GLOB_ONLINE_DOCUMENT_VERSION=wps
# 文件预览地址 # 文件预览地址
VITE_GLOB_ONLINE_VIEW_URL=http://localhost:8080/stm/sys/common/static/ VITE_GLOB_ONLINE_VIEW_URL=http://localhost:3100/stm/sys/common/static/
...@@ -16,13 +16,12 @@ VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE = false ...@@ -16,13 +16,12 @@ VITE_BUILD_COMPRESS_DELETE_ORIGIN_FILE = false
VITE_GLOB_API_URL=/stm VITE_GLOB_API_URL=/stm
#后台接口全路径地址(必填) #后台接口全路径地址(必填)
# VITE_GLOB_DOMAIN_URL=https://itrm.westmining.com:8080/stm VITE_GLOB_DOMAIN_URL=https://itrm.westmining.com/stm
# VITE_GLOB_ONLINE_VIEW_URL=https://itrm.westmining.com:8080/stm/sys/common/static/ VITE_GLOB_ONLINE_VIEW_URL=https://itrm.westmining.com/stm/sys/common/static/
#后台接口全路径地址(必填) #后台接口全路径地址(必填)
VITE_GLOB_DOMAIN_URL=http://47.98.203.68:8080/stm/ # VITE_GLOB_DOMAIN_URL=http://47.98.203.68:8080/stm/
# VITE_GLOB_ONLINE_VIEW_URL=http://47.98.203.68:8080/stm/sys/common/static/
VITE_GLOB_ONLINE_VIEW_URL=http://47.98.203.68:8080/stm/sys/common/static/
# 接口父路径前缀 # 接口父路径前缀
VITE_GLOB_API_URL_PREFIX= VITE_GLOB_API_URL_PREFIX=
......
...@@ -157,7 +157,7 @@ ...@@ -157,7 +157,7 @@
</style> </style>
<div class="app-loading"> <div class="app-loading">
<div class="app-loading-wrap"> <div class="app-loading-wrap">
<img src="<%= basePublicPath %>/resource/img/logo.png" class="app-loading-logo" alt="Logo" style="width: 80px;height:32px;" /> <img src="<%= basePublicPath %>/resource/img/logo.png" class="app-loading-logo" alt="Logo" />
<div class="app-loading-dots"> <div class="app-loading-dots">
<span class="dot dot-spin"><i></i><i></i><i></i><i></i></span> <span class="dot dot-spin"><i></i><i></i><i></i><i></i></span>
</div> </div>
......
...@@ -22,8 +22,13 @@ enum Api { ...@@ -22,8 +22,13 @@ enum Api {
queryDepartPostUserPageList = '/sys/user/queryDepartPostUserPageList', queryDepartPostUserPageList = '/sys/user/queryDepartPostUserPageList',
//查询所选部门的所有父节点ID //查询所选部门的所有父节点ID
queryAllParentId = '/sys/sysDepart/queryAllParentId', queryAllParentId = '/sys/sysDepart/queryAllParentId',
getMetricList = '/metric/metricMonitorSet/list',
queryAllMetric = '/metric/metricMonitorSet/queryAllMetric',
rateRuleList = '/rate/rateRule/list',
} }
/** /**
* 上传父路径 * 上传父路径
*/ */
...@@ -188,3 +193,16 @@ export const refreshDragCache = () => defHttp.get({ url: Api.refreshDragCache }, ...@@ -188,3 +193,16 @@ export const refreshDragCache = () => defHttp.get({ url: Api.refreshDragCache },
* @param params * @param params
*/ */
export const refreshHomeCache = () => defHttp.get({ url: Api.refreshDefaultIndexCache }, { isTransformResponse: false }); export const refreshHomeCache = () => defHttp.get({ url: Api.refreshDefaultIndexCache }, { isTransformResponse: false });
export const getMetricList = (params) => {
return defHttp.get({ url: Api.getMetricList, params })
};
export const queryAllMetric = (params) =>{
return defHttp.get({url: Api.queryAllMetric, params})
}
export const getRateRuleList = (params) => {
return defHttp.get({ url: Api.rateRuleList, params });
};
...@@ -76,7 +76,7 @@ import JCaSelect from './jeecg/components/JCaSelect.vue'; ...@@ -76,7 +76,7 @@ import JCaSelect from './jeecg/components/JCaSelect.vue';
import StSelectLable from './jeecg/components/StSelectLable.vue'; import StSelectLable from './jeecg/components/StSelectLable.vue';
import JSearchSelectDuty from './jeecg/components/JSearchSelectDuty.vue'; import JSearchSelectDuty from './jeecg/components/JSearchSelectDuty.vue';
import JSelectMetric from './jeecg/components/JSelectMetric.vue';
const componentMap = new Map<ComponentType, Component>(); const componentMap = new Map<ComponentType, Component>();
...@@ -182,6 +182,8 @@ componentMap.set('JCaSelect', JCaSelect); ...@@ -182,6 +182,8 @@ componentMap.set('JCaSelect', JCaSelect);
componentMap.set('StSelectLable', StSelectLable); componentMap.set('StSelectLable', StSelectLable);
componentMap.set('JSearchSelectDuty', JSearchSelectDuty); componentMap.set('JSearchSelectDuty', JSearchSelectDuty);
componentMap.set('JSelectMetric', JSelectMetric);
export function add(compName: ComponentType, component: Component) { export function add(compName: ComponentType, component: Component) {
componentMap.set(compName, component); componentMap.set(compName, component);
} }
......
...@@ -29,7 +29,7 @@ ...@@ -29,7 +29,7 @@
enum Api { enum Api {
url = '/sys/dict/loadTreeData', url = '/sys/dict/loadTreeData',
view = '/sys/dict/loadDictItem/', view = '/base.domain/stDomain/queryByCode',
} }
const props = defineProps({ const props = defineProps({
...@@ -110,29 +110,30 @@ ...@@ -110,29 +110,30 @@
treeValue.value = props.multiple ? [] : null; treeValue.value = props.multiple ? [] : null;
return; return;
} }
console.log(props.value, 'props.value');
const params = { key: props.value }; const params = { code: props.value };
const result = await defHttp.get({ url: `${Api.view}${props.dict}`, params }, { isTransformResponse: false }); const result = await defHttp.get({ url: `${Api.view}`, params }, { isTransformResponse: false });
console.log(result, 'result');
if (result.success && result.result?.length > 0) { if (result.success && result.result) {
console.log(result, 'result');
console.log(result.result, 'result.result'); // console.log(result.result, 'result.result');
console.log(props, 'props'); // console.log(props, 'props');
const values = props.value; // const values = props.value;
console.log(values, 'values'); // console.log(values, 'values');
const length = values.length; // const length = values.length;
console.log(length, 'length'); // console.log(length, 'length');
if (length === 2) { // if (length === 2) {
treeValue.value = [values]; // treeValue.value = [values];
console.log(treeValue.value, 'treeValue.value2'); // console.log(treeValue.value, 'treeValue.value2');
} else if (length === 4) { // } else if (length === 4) {
treeValue.value = [values.substring(0, 2), values]; // treeValue.value = [values.substring(0, 2), values];
console.log(treeValue.value, 'treeValue.value4'); // console.log(treeValue.value, 'treeValue.value4');
} else if (length === 6) { // } else if (length === 6) {
treeValue.value = [values.substring(0, 2), values.substring(0, 4), values]; // treeValue.value = [values.substring(0, 2), values.substring(0, 4), values];
console.log(treeValue.value, 'treeValue.value6'); // console.log(treeValue.value, 'treeValue.value6');
} // }
treeValue.value = [result.result];
onLoadTriggleChange(result.result[0]); onLoadTriggleChange(result.result[0]);
} }
} }
......
<template> <template>
<div> <div>
<JMetricBiz @handleOpen="handleOpen" :loading="loadingEcho" v-bind="attrs"></JMetricBiz> <JMetricBiz @handleOpen="handleOpen" :loading="loadingEcho" v-bind="attrs"></JMetricBiz>
......
...@@ -168,4 +168,5 @@ export type ComponentType = ...@@ -168,4 +168,5 @@ export type ComponentType =
| 'StSelectLable' | 'StSelectLable'
| 'JTreeSelectDomain' | 'JTreeSelectDomain'
| 'JCaSelect' | 'JCaSelect'
| 'JInputSelect'; | 'JInputSelect'
\ No newline at end of file | 'JSelectMetric';
\ No newline at end of file
...@@ -275,7 +275,7 @@ ...@@ -275,7 +275,7 @@
</vxe-column> </vxe-column>
<!-- 备注列 --> <!-- 备注列 -->
<vxe-column field="remarks" title="备注" width="200"> <vxe-column field="remarks" title="备注" width="100">
<template #default="{ row }"> <template #default="{ row }">
<vxe-textarea <vxe-textarea
:rows="row.remarks.rows" :rows="row.remarks.rows"
......
<template>
<a-modal
:visible="visible"
:title="title"
:width="'60%'"
:bodyStyle="{ height: '80vh', padding: '20px' }"
:footer="null"
@ok="handleOk"
@cancel="handleCancel"
@update:visible="(value) => emit('update:visible', value)"
:centered="true"
:destroyOnClose="true"
:maskClosable="false"
>
<div style="height: 100%; display: flex; flex-direction: column;">
<!-- 加载状态 -->
<div v-if="chartLoading" style="text-align: center; padding: 50px;">
<a-spin size="large" />
</div>
<template v-else>
<!-- 指标信息 -->
<div style="margin-bottom: 16px; padding: 12px; background: #f5f7fa; border-radius: 4px;">
<a-row :gutter="16">
<a-col :span="4"><strong>指标编码:</strong>{{ row?.mtrcNo }}</a-col>
<a-col :span="12" style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis;"><strong>指标名称:</strong>{{ row?.mtrcName }}</a-col>
<a-col :span="4"><strong>采集频率:</strong>{{ getFreqText(row?.collFreq) }}</a-col>
<a-col :span="4"><strong>数据条数:</strong>{{ chartDataList.length }}</a-col>
</a-row>
</div>
<!-- 时间选择表单 -->
<div style="margin-bottom: 16px; padding: 12px; background: #f9f9f9; border-radius: 4px;">
<a-row :gutter="16">
<a-col :span="16">
<BasicForm @register="registerForm" />
</a-col>
<a-col :span="8">
<a-space :size="5" style="float: right;">
<a-button type="primary" @click="searchQuery">查询</a-button>
<a-button @click="searchReset">重置</a-button>
</a-space>
</a-col>
</a-row>
</div>
<a-tabs v-model:activeKey="activeTabKey" @change="handleTabChange" style="flex: 1; display: flex; flex-direction: column;">
<a-tab-pane key="list" tab="指标值" style="height: 100%;">
<a-table
:dataSource="chartDataList"
:columns="dataColumns"
:pagination="{ pageSize: 10 }"
size="small"
:scroll="{ y: 'calc(70vh - 200px)' }"
bordered
>
<template #bodyCell="{ column, text, record }">
<template v-if="column.key === 'riskLevel'">
<a-badge
:status="getRiskLevelStatus(record.riskLevel)"
:text="getRiskLevelText(record.riskLevel)"
/>
</template>
</template>
</a-table>
</a-tab-pane>
<a-tab-pane key="line" tab="走势图" style="height: 100%;">
<div ref="lineChartRef" style="width: 100%; height: calc(70vh - 150px);"></div>
</a-tab-pane>
<a-tab-pane key="bar" tab="柱状图" style="height: 100%;">
<div ref="barChartRef" style="width: 100%; height: calc(70vh - 150px);"></div>
</a-tab-pane>
<a-tab-pane key="pie" tab="风险分布" style="height: 100%;">
<div ref="pieChartRef" style="width: 100%; height: calc(70vh - 150px);"></div>
</a-tab-pane>
</a-tabs>
</template>
</div>
</a-modal>
</template>
<script lang="ts" setup>
import { ref, watch, nextTick } from 'vue'
import * as echarts from 'echarts'
import { queryAllList,queryByMtrcNo } from '../../MetricReport.api'
import { BasicForm, useForm } from '/@/components/Form/index'
import { FormSchema } from '/@/components/Table'
interface Props {
visible: boolean
title: string
row?: any
}
const props = defineProps<Props>()
const emit = defineEmits(['update:visible', 'ok', 'cancel'])
// 响应式数据
const activeTabKey = ref('list')
const chartLoading = ref(false)
const chartDataList = ref<any[]>([])
const curMtrcSet = ref<any>({})
// 图表实例
const lineChartRef = ref<HTMLDivElement | null>(null)
const barChartRef = ref<HTMLDivElement | null>(null)
const pieChartRef = ref<HTMLDivElement | null>(null)
let lineChart: echarts.ECharts | null = null
let barChart: echarts.ECharts | null = null
let pieChart: echarts.ECharts | null = null
// 表单配置
const searchFormSchema: FormSchema[] = [
{
label: '开始日期',
field: 'stFillTime',
component: 'DatePicker',
colProps: { xs: 12, sm: 12, md: 12, lg: 8, xl: 8 },
defaultValue: (new Date().getFullYear() - 3) + "-" + (new Date().getMonth() + 1) + "-" + (new Date().getDate()),
},
{
label: '结束日期',
field: 'endFillTime',
defaultValue: new Date(),
component: 'DatePicker',
colProps: { xs: 12, sm: 12, md: 12, lg: 8, xl: 8 },
},
]
// 初始化表单
const [registerForm, { setFieldsValue, getFieldsValue }] = useForm({
labelWidth: 100,
schemas: searchFormSchema,
showActionButtonGroup: false,
baseColProps: { span: 24 }
})
// 表格列定义
const dataColumns = [
{ title: '填报时间', dataIndex: 'fillTime', key: 'fillTime', width: 150, render: (text) => text || '无数据' },
{ title: '填报值', dataIndex: 'fillVals', key: 'fillVals', width: 100 },
{
title: '风险等级',
dataIndex: 'riskLevel',
key: 'riskLevel',
width: 100
},
]
// 处理确定
const handleOk = () => {
emit('ok')
emit('update:visible', false)
}
// 处理取消
const handleCancel = () => {
emit('cancel')
emit('update:visible', false)
// 销毁图表实例
cleanupCharts()
}
// 清理图表实例
const cleanupCharts = () => {
if (lineChart) {
lineChart.dispose()
lineChart = null
}
if (barChart) {
barChart.dispose()
barChart = null
}
if (pieChart) {
pieChart.dispose()
pieChart = null
}
}
// 获取采集频率文本
const getFreqText = (freq) => {
const map = {
1: '每日',
2: '每周',
3: '每月',
4: '每季度',
5: '每半年',
6: '每年'
}
return map[freq] || freq
}
// 获取风险等级状态
const getRiskLevelStatus = (level) => {
if (level === 1) return 'success'
if (level === 2) return 'processing'
if (level === 3) return 'error'
return 'default'
}
// 获取风险等级文本
const getRiskLevelText = (level) => {
if (level === 1) return '低风险'
if (level === 2) return '中风险'
if (level === 3) return '高风险'
return '其他'
}
// 格式化时间
const formatTime = (time, collFreq) => {
if (!time) return ''
// 确保 time 是字符串
const timeStr = String(time)
// 确保 collFreq 是数字
const freq = Number(collFreq) || 0
try {
if (freq == 1) {
return timeStr.length >= 10 ? timeStr.substr(5, 5) : timeStr // MM-DD
} else if (freq == 2) {
return timeStr.length >= 10 ? timeStr.substr(5, 5) : timeStr // MM-DD
} else if (freq == 3 || freq === 4 || freq === 5) {
return timeStr.length >= 7 ? timeStr.substr(0, 7) : timeStr // YYYY-MM
} else if (freq == 6) {
return timeStr.length >= 4 ? timeStr.substr(0, 4) : timeStr // YYYY
} else {
return timeStr.length >= 17 ? timeStr.substr(11, 16) : timeStr // HH:mm
}
} catch (error) {
return timeStr
}
}
// 加载数据
const loadData = async () => {
if (!props.row) return
chartLoading.value = true
try {
// 获取表单数据
const formData = getFieldsValue()
queryByMtrcNo({
mtrcNo: props.row.mtrcNo,
}).then(res => {
curMtrcSet.value = res || {}
})
// 从接口获取数据
const res = await queryAllList({
mtrcNos: props.row.mtrcNo,
stFillTime: formData.stFillTime,
endFillTime: formData.endFillTime
})
// 处理数据
if (res && res.dataList) {
chartDataList.value = res.dataList.map(item => {
const formattedTime = formatTime(item.fillTime, curMtrcSet.value.collFreq)
return {
...item,
fillTime: formattedTime
}
})
} else {
chartDataList.value = []
}
await nextTick()
// 根据当前激活的标签页初始化对应的图表
setTimeout(() => {
if (activeTabKey.value === 'line') {
initLineChart()
} else if (activeTabKey.value === 'bar') {
initBarChart()
} else if (activeTabKey.value === 'pie') {
initPieChart()
}
}, 200)
} catch (error) {
chartDataList.value = []
} finally {
chartLoading.value = false
}
}
// 查询
const searchQuery = () => {
loadData()
}
// 重置
const searchReset = () => {
setFieldsValue({
stFillTime: (new Date().getFullYear() - 3) + "-" + (new Date().getMonth() + 1) + "-" + (new Date().getDate()),
endFillTime: new Date()
})
loadData()
}
// 初始化所有图表
const initAllCharts = () => {
// 注意:由于标签页可能未激活,容器可能不存在,所以这里不直接初始化
// 图表会在标签切换时初始化
}
// 获取图表数据
const getChartData = () => {
const categories = chartDataList.value.map(item => item.fillTime)
const values = chartDataList.value.map(item => {
const val = parseFloat(item.fillVals) || 0
return val
})
// 按风险等级统计饼图数据
const riskCount = {
1: 0, // 低风险
2: 0, // 中风险
3: 0 // 高风险
}
chartDataList.value.forEach(item => {
if (item.riskLevel && riskCount[item.riskLevel] !== undefined) {
riskCount[item.riskLevel]++
}
})
const pieData = []
// 按固定顺序添加数据项,并指定颜色
if (riskCount[1] > 0) {
pieData.push({ value: riskCount[1], name: '低风险', itemStyle: { color: '#73D13D' } })
}
if (riskCount[2] > 0) {
pieData.push({ value: riskCount[2], name: '中风险', itemStyle: { color: '#FA8C16' } })
}
if (riskCount[3] > 0) {
pieData.push({ value: riskCount[3], name: '高风险', itemStyle: { color: '#CF1322' } })
}
return { categories, values, pieData }
}
// 初始化折线图
const initLineChart = () => {
if (!lineChartRef.value) {
return
}
if (lineChart) {
lineChart.dispose()
}
lineChart = echarts.init(lineChartRef.value)
const { categories, values } = getChartData()
const option = {
title: {
text: `${props.row?.mtrcName} - 趋势分析`,
left: 'center',
top: 10,
textStyle: {
fontSize: 16,
fontWeight: 'normal'
}
},
tooltip: {
trigger: 'axis',
formatter: function(params) {
return `${params[0].name}<br/>${params[0].marker}${params[0].seriesName}: ${params[0].value}`
}
},
grid: {
left: '8%',
right: '5%',
top: '15%',
bottom: '8%',
containLabel: true
},
xAxis: {
type: 'category',
data: categories.length > 0 ? categories : ['无数据'],
axisLabel: {
rotate: categories.length > 10 ? 30 : 0,
interval: categories.length > 20 ? 2 : 0
},
axisLine: {
lineStyle: { color: '#999' }
}
},
yAxis: {
type: 'value',
name: `单位[${props.row?.unit || ''}]`,
nameLocation: 'middle',
nameGap: 45,
splitLine: {
lineStyle: { type: 'dashed', color: '#eee' }
}
},
series: [{
name: props.row?.mtrcName || '指标数值',
data: values.length > 0 ? values : [0],
type: 'line',
smooth: true,
symbol: 'circle',
symbolSize: 6,
lineStyle: {
width: 3,
color: '#409eff'
},
areaStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: 'rgba(64, 158, 255, 0.3)' },
{ offset: 1, color: 'rgba(64, 158, 255, 0.05)' }
])
},
markPoint: {
data: [
{ type: 'max', name: '最大值' },
{ type: 'min', name: '最小值' }
]
},
markLine: {
data: [
{ type: 'average', name: '平均值' }
]
}
}]
}
lineChart.setOption(option)
lineChart.resize()
}
// 初始化柱状图
const initBarChart = () => {
if (!barChartRef.value) {
return
}
if (barChart) {
barChart.dispose()
}
barChart = echarts.init(barChartRef.value)
const { categories, values } = getChartData()
const option = {
title: {
text: `${props.row?.mtrcName} - 对比分析`,
left: 'center',
top: 10,
textStyle: {
fontSize: 16,
fontWeight: 'normal'
}
},
tooltip: {
trigger: 'axis',
axisPointer: { type: 'shadow' },
formatter: function(params) {
return `${params[0].name}<br/>${params[0].marker}${params[0].seriesName}: ${params[0].value}`
}
},
grid: {
left: '8%',
right: '5%',
top: '15%',
bottom: '8%',
containLabel: true
},
xAxis: {
type: 'category',
data: categories.length > 0 ? categories : ['无数据'],
axisLabel: {
rotate: categories.length > 10 ? 30 : 0,
interval: categories.length > 20 ? 2 : 0
},
axisLine: {
lineStyle: { color: '#999' }
}
},
yAxis: {
type: 'value',
name: `单位[${props.row?.unit || ''}]`,
nameLocation: 'middle',
nameGap: 45,
splitLine: {
lineStyle: { type: 'dashed', color: '#eee' }
}
},
series: [{
name: props.row?.mtrcName || '指标数值',
data: values.length > 0 ? values : [0],
type: 'bar',
barWidth: '50%',
itemStyle: {
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
{ offset: 0, color: '#83bff6' },
{ offset: 0.5, color: '#188df0' },
{ offset: 1, color: '#188df0' }
]),
borderRadius: [6, 6, 0, 0]
},
label: {
show: values.length <= 20,
position: 'top',
color: '#333',
fontSize: 11,
formatter: '{c}'
},
markPoint: {
data: [
{ type: 'max', name: '最大值' },
{ type: 'min', name: '最小值' }
]
}
}]
}
barChart.setOption(option)
barChart.resize()
}
// 初始化饼图
const initPieChart = () => {
if (!pieChartRef.value) {
return
}
if (pieChart) {
pieChart.dispose()
}
pieChart = echarts.init(pieChartRef.value)
const { pieData } = getChartData()
if (pieData.length === 0) {
pieData.push({ value: 1, name: '暂无数据' })
}
const option = {
title: {
text: `${props.row?.mtrcName} - 风险分布`,
left: 'center',
top: 10,
textStyle: {
fontSize: 16,
fontWeight: 'normal'
}
},
tooltip: {
trigger: 'item',
formatter: '{b}<br/>{c} ({d}%)'
},
legend: {
orient: 'horizontal',
bottom: 20,
left: 'center',
itemWidth: 25,
itemHeight: 14
},
series: [{
name: '风险分布',
type: 'pie',
radius: ['45%', '70%'],
center: ['50%', '45%'],
avoidLabelOverlap: true,
itemStyle: {
borderRadius: 8,
borderColor: '#fff',
borderWidth: 2
},
label: {
show: true,
position: 'outside',
formatter: '{b}: {d}%',
fontSize: 11
},
emphasis: {
scale: true,
label: { show: true, fontWeight: 'bold' }
},
data: pieData
}]
}
pieChart.setOption(option)
pieChart.resize()
}
// 标签切换处理
const handleTabChange = (key) => {
setTimeout(() => {
if (key === 'line') {
initLineChart()
} else if (key === 'bar') {
initBarChart()
} else if (key === 'pie') {
initPieChart()
}
}, 100)
}
// 监听可见性变化
watch(() => props.visible, async (newVal) => {
if (newVal) {
activeTabKey.value = 'list' // 默认选中列表
await loadData()
} else {
cleanupCharts()
}
})
</script>
<style scoped>
</style>
\ No newline at end of file
<template> <template>
<div style="height: 100%;padding: 10px;"> <div style="padding: 10px;">
<a-row style="height: 100%;" :gutter="[8, 8]"> <a-row style="height: 100%;" :gutter="[8, 8]">
<a-col :span="5"> <a-col :span="5">
<a-card title="" :bordered="true" style="width:100%;height:100%;"> <a-card :bordered="true" style="width:100%;height:100%;">
<Left @searchData="setDataList"></Left> <Left
@searchData="handleLeftSearch"
@resetData="handleLeftReset"
/>
</a-card> </a-card>
</a-col> </a-col>
<a-col :span="19"> <a-col :span="19">
<!-- 时间搜索表单 --> <!-- 时间搜索表单 -->
<div style="margin-bottom: 16px; padding: 12px; background: #f9f9f9; border-radius: 4px;"> <div style="margin-bottom: 16px; padding: 12px; background: #f9f9f9; border-radius: 4px;">
<a-row :gutter="16" align="middle"> <a-row :gutter="16" align="middle">
<a-col :span="14"> <a-col :span="16">
<div style="display: flex; align-items: center; gap: 16px;"> <div style="display: flex; align-items: center; gap: 16px;">
<div style="display: flex; align-items: center;"> <div style="display: flex; align-items: center;">
<span style="margin-right: 8px;">开始日期:</span> <span style="margin-right: 8px;">开始日期:</span>
<a-date-picker v-model:value="searchForm.stFillTime" style="width: 150px;" /> <a-date-picker v-model:value="searchForm.startTime" style="width: 150px;" />
</div> </div>
<div style="display: flex; align-items: center;"> <div style="display: flex; align-items: center;">
<span style="margin-right: 8px;">结束日期:</span> <span style="margin-right: 8px;">结束日期:</span>
<a-date-picker v-model:value="searchForm.endFillTime" style="width: 150px;" /> <a-date-picker v-model:value="searchForm.endTime" style="width: 150px;" />
</div>
<div style="display: flex; align-items: center;">
<span style="margin-right: 8px;">指标名称:</span>
<a-input v-model:value="searchForm.mtrcName" placeholder="请输入指标名称" style="width: 150px;" />
</div> </div>
</div> </div>
</a-col> </a-col>
<a-col :span="10"> <a-col :span="8">
<a-space :size="5" style="margin-left:0px;"> <a-space :size="5" style="margin-left:0px;">
<a-button type="primary" @click="handleSearch">查询</a-button> <a-button type="primary" @click="handleSearch" :loading="gridOptions.loading">查询</a-button>
<a-button @click="resetSearch">重置</a-button> <a-button @click="resetSearch">重置</a-button>
</a-space> </a-space>
</a-col> </a-col>
</a-row> </a-row>
</div> </div>
<vxe-grid v-bind="gridOptions" ref="tableRef"> <vxe-grid
v-bind="gridOptions"
ref="tableRef"
@page-change="handlePageChange"
>
<template #toolbarButtons> <template #toolbarButtons>
<div style="margin:2px 20px;size:15px">指标填报列表:</div> <div style="margin:2px 20px;size:15px">指标填报列表:</div>
<vxe-button size="small" @click="handleApproval(1)">审批通过</vxe-button> <vxe-button size="small" @click="handleApproval(1)" :loading="batchLoading">审批通过</vxe-button>
<vxe-button size="small" @click="handleApproval(2)">审批未通过</vxe-button> <vxe-button size="small" @click="handleApproval(2)" :loading="batchLoading">审批未通过</vxe-button>
<div style="margin:auto"> <div style="margin:auto">
共搜索到 <span class="count-number">{{ totalRecords }}</span> 条记录 共搜索到 <span class="count-number">{{ totalRecords }}</span> 条记录
</div> </div>
</template> </template>
<template #active="{ row }"> <template #active="{ row }">
<vxe-button mode="text" status="primary" @click="saveApproval(row.reportId,1)">通过</vxe-button> <vxe-button mode="text" status="primary" @click="saveApproval(row.reportId, 1)" :loading="row._loading">通过</vxe-button>
<vxe-button mode="text" status="error" @click="saveApproval(row.reportId,2)">退回</vxe-button> <vxe-button mode="text" status="error" @click="saveApproval(row.reportId, 2)" :loading="row._loading">退回</vxe-button>
</template> </template>
<template #mtrcName="{ row }"> <template #mtrcName="{ row }">
<a @click="showChartModal(row)" style="color: #409eff; cursor: pointer;"> <a @click="showChartModal(row)" style="color: #409eff; cursor: pointer;">
...@@ -53,24 +63,26 @@ ...@@ -53,24 +63,26 @@
</template> </template>
</vxe-grid> </vxe-grid>
</a-col> </a-col>
</a-row> </a-row>
<!-- 图表弹窗组件 --> <!-- 图表弹窗组件 -->
<MetricChartModal <MetricChartModal
v-model:visible="chartModalVisible" v-model:visible="chartModalVisible"
:title="chartModalTitle" :title="chartModalTitle"
:row="currentRow" :row="currentRow"
@cancel="handleModalClose" @cancel="handleModalClose"
/> />
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { reactive, onMounted, ref } from 'vue' import { reactive, onMounted, ref, computed } from 'vue'
import type { VxeGridProps } from 'vxe-table' import type { VxeGridProps, VxeGridInstance } from 'vxe-table'
import { dataApprovalList,batchSaveApproval } from '../MetricReport.api'; import { debounce } from 'lodash-es'
import { dataApprovalList, batchSaveApproval } from '../MetricReport.api'
import Left from './left.vue' import Left from './left.vue'
import MetricChartModal from './components/MetricChartModal.vue' import MetricChartModal from './components/MetricChartModal.vue'
import { VXETable } from 'vxe-table'
interface RowVO { interface RowVO {
id: number id: number
...@@ -81,8 +93,9 @@ interface RowVO { ...@@ -81,8 +93,9 @@ interface RowVO {
address: string address: string
} }
const tableRef = ref() const tableRef = ref<VxeGridInstance>()
const totalRecords = ref() const totalRecords = ref(0)
const batchLoading = ref(false)
// 图表弹窗 // 图表弹窗
const chartModalVisible = ref(false) const chartModalVisible = ref(false)
...@@ -91,203 +104,274 @@ const currentRow = ref<any>(null) ...@@ -91,203 +104,274 @@ const currentRow = ref<any>(null)
// 搜索表单 // 搜索表单
const searchForm = reactive({ const searchForm = reactive({
stFillTime: null, startTime: null,
endFillTime: null endTime: null,
mtrcName: ''
}) })
// 左侧筛选条件 // 左侧筛选条件
const formData = reactive({}) const leftFormData = ref({})
// 存储所有原始数据
const allData = ref<any[]>([])
// 防抖搜索函数
const debouncedSearch = debounce(() => {
handleSearch()
}, 500)
// 表格配置
const gridOptions = reactive<VxeGridProps<RowVO>>({ const gridOptions = reactive<VxeGridProps<RowVO>>({
border: true, border: true,
showOverflow: true, showOverflow: true,
keepSource: true, keepSource: true,
height: "auto", maxHeight: "750px",
loading: false,
exportConfig: {}, exportConfig: {},
columnConfig: { columnConfig: {
resizable: true resizable: true
}, },
// 分页配置 - 前端分页模式
editConfig: { pagerConfig: {
trigger: 'click', enabled: true,
mode: 'row', pageSize: 10,
showStatus: true currentPage: 1,
total: 0,
pageSizes: [10, 20, 50, 100],
layouts: ['PrevJump', 'PrevPage', 'Number', 'NextPage', 'NextJump', 'Sizes', 'Total'],
perfect: true
}, },
cellStyle ({ row, column }) { // 工具栏配置
if (column.field === 'appSta') { toolbarConfig: {
if (row.appSta == '通过') { refresh: true,
return { export: true,
color: 'blue' zoom: true,
} custom: true,
} else if (row.appSta == '未通过') { slots: {
return { buttons: 'toolbarButtons'
color: 'red'
}
} else if (row.appSta == '未审批') {
return {
backgroundColor: ''
}
}
} else if (column.field === 'riskLevel') {
if (row.riskLevel == '低风险') {
return {
color: '#73D13D'
}
} else if (row.riskLevel == '中风险') {
return {
color: '#FA8C16'
}
} else if (row.riskLevel == '高风险') {
return {
color: '#CF1322'
}
}
} }
}, },
// 列配置
columns: [ columns: [
{ type: 'seq', width: 50 }, { type: 'seq', width: 50 },
{ type: 'checkbox', width: 50 }, { type: 'checkbox', width: 50 },
{ field: 'reportId', title: 'id', visible: false },
{ field: 'reportId', title: 'id', visible:false },
{ field: 'mtrcNo', title: '指标编码', sortable: true }, { field: 'mtrcNo', title: '指标编码', sortable: true },
{ field: 'mtrcName', title: '指标名称', showOverflow: true, slots: { default: 'mtrcName' } }, { field: 'mtrcName', title: '指标名称', showOverflow: true, slots: { default: 'mtrcName' } },
{ field: 'collFreq', title: '采集频率', }, { field: 'collFreq', title: '采集频率' },
{ field: 'timeName', title: '填报时间' }, { field: 'timeName', title: '填报时间' },
{ field: 'riskLevel', title: '风险等级' }, { field: 'riskLevel', title: '风险等级' },
{ field: 'domainName', title: '所属领域', showOverflow: true }, { field: 'domainName', title: '所属领域', showOverflow: true },
{ field: 'mtrcCtp', title: '指标类型' }, { field: 'mtrcCtp', title: '指标类型' },
{ field: 'fillVals', title: '填报值' }, { field: 'fillVals', title: '填报值' },
{ field: 'appSta', title: '状态', }, { field: 'appSta', title: '状态' },
{ field: 'active', title: '审批', width: 150, fixed: 'right', slots: { default: 'active' } } { field: 'active', title: '审批', width: 150, fixed: 'right', slots: { default: 'active' } }
], ],
toolbarConfig: { // 单元格样式
refresh: true, cellStyle({ row, column }) {
export: true, if (column.field === 'appSta') {
zoom: true, if (row.appSta == '通过') {
custom: true, return { color: 'blue' }
slots: { } else if (row.appSta == '未通过') {
buttons: 'toolbarButtons', return { color: 'red' }
}, }
} else if (column.field === 'riskLevel') {
if (row.riskLevel == '低风险') {
return { color: '#73D13D' }
} else if (row.riskLevel == '中风险') {
return { color: '#FA8C16' }
} else if (row.riskLevel == '高风险') {
return { color: '#CF1322' }
}
}
}, },
data: [] data: []
}) })
async function setDataList(params = {}) { // 更新当前页数据
// 存储左侧筛选条件 const updatePageData = () => {
Object.assign(formData, params) const { currentPage, pageSize } = gridOptions.pagerConfig!
const start = (currentPage - 1) * pageSize
const end = start + pageSize
gridOptions.data = allData.value.slice(start, end)
// 合并时间搜索条件 // 更新分页总记录数
gridOptions.pagerConfig!.total = allData.value.length
totalRecords.value = allData.value.length
}
// 加载数据
async function loadData() {
if (gridOptions.loading) return
// 合并所有搜索条件
const searchParams = { const searchParams = {
...params, ...leftFormData.value,
stFillTime: searchForm.stFillTime, startTime: searchForm.startTime,
endFillTime: searchForm.endFillTime endTime: searchForm.endTime,
mtrcName: searchForm.mtrcName
} }
console.log('加载数据参数:', searchParams)
gridOptions.loading = true gridOptions.loading = true
try { try {
const res = await dataApprovalList(searchParams); const res = await dataApprovalList(searchParams)
gridOptions.data = res || [] console.log('返回数据:', res)
totalRecords.value = res.length
// 处理返回数据
if (Array.isArray(res)) {
allData.value = res
} else if (res && res.records) {
allData.value = res.records
} else if (res && res.data) {
allData.value = res.data
} else {
allData.value = []
}
// 重置到第一页
gridOptions.pagerConfig!.currentPage = 1
// 更新表格数据
updatePageData()
} catch (error) { } catch (error) {
console.error('加载数据失败:', error) console.error('加载数据失败:', error)
allData.value = []
gridOptions.data = [] gridOptions.data = []
totalRecords.value = 0
gridOptions.pagerConfig!.total = 0
} finally { } finally {
gridOptions.loading = false gridOptions.loading = false
} }
} }
const handleApproval = async (appSta)=> { // 处理分页变化
const ids = await getSelectIds(); const handlePageChange = ({ currentPage, pageSize }: { currentPage: number, pageSize: number }) => {
if(ids.length == 0){ console.log('分页变化:', currentPage, pageSize)
return;
} // 更新分页配置
batchSaveApproval({ gridOptions.pagerConfig!.currentPage = currentPage
ids:ids, gridOptions.pagerConfig!.pageSize = pageSize
appSta:appSta
},setDataList) // 前端分页:直接从 allData 中切片
updatePageData()
} }
const saveApproval = async (id,sta)=> { // 处理左侧搜索
batchSaveApproval({ function handleLeftSearch(params) {
ids:[id], console.log('左侧搜索:', params)
appSta:sta leftFormData.value = { ...params }
},function(){ loadData() // 重新从后端获取数据
})
} }
async function getSelectIds() { // 处理左侧重置
let ids = []; function handleLeftReset() {
const $table = tableRef.value; console.log('左侧重置')
if ($table) { leftFormData.value = {}
const selRecords = $table.getCheckboxRecords(); searchForm.startTime = null
selRecords.forEach(function (ele) { searchForm.endTime = null
ids.push(ele.reportId); loadData() // 重新从后端获取数据
});
}
return ids;
} }
// 处理搜索 // 处理搜索
const handleSearch = async () => { async function handleSearch() {
await setDataList(formData) console.log('执行搜索')
await loadData() // 重新从后端获取数据
} }
// 重置搜索 // 重置搜索
const resetSearch = () => { function resetSearch() {
searchForm.stFillTime = null searchForm.startTime = null
searchForm.endFillTime = null searchForm.endTime = null
setDataList({}) searchForm.mtrcName = ''
handleSearch()
} }
onMounted(async () => { // 批量审批
await setDataList({}); async function handleApproval(appSta: number) {
}) const $table = tableRef.value
if (!$table) return
const selectRecords = $table.getCheckboxRecords()
if (selectRecords.length === 0) {
VXETable.modal.message({ content: '请至少选择一条记录', status: 'warning' })
return
}
const ids = selectRecords.map(item => item.reportId)
batchLoading.value = true
try {
await batchSaveApproval({
ids: ids,
appSta: appSta
})
VXETable.modal.message({ content: '操作成功', status: 'success' })
// 重新加载数据
await loadData()
// 清空选中
$table.clearCheckboxRow()
} catch (error) {
console.error('批量审批失败:', error)
VXETable.modal.message({ content: '操作失败', status: 'error' })
} finally {
batchLoading.value = false
}
}
// 单个审批
async function saveApproval(id: number, sta: number) {
const row = gridOptions.data.find(item => item.reportId === id)
if (row) {
row._loading = true
}
try {
await batchSaveApproval({
ids: [id],
appSta: sta
})
VXETable.modal.message({ content: '操作成功', status: 'success' })
// 重新加载数据
await loadData()
} catch (error) {
console.error('审批失败:', error)
VXETable.modal.message({ content: '操作失败', status: 'error' })
} finally {
if (row) {
row._loading = false
}
}
}
// 显示图表弹窗 // 显示图表弹窗
const showChartModal = (row) => { function showChartModal(row: any) {
currentRow.value = row currentRow.value = row
chartModalTitle.value = `指标详情 - ${row.mtrcName}` chartModalTitle.value = `指标详情 - ${row.mtrcName}`
chartModalVisible.value = true chartModalVisible.value = true
} }
// 处理弹窗关闭 // 处理弹窗关闭
const handleModalClose = () => { function handleModalClose() {
chartModalVisible.value = false chartModalVisible.value = false
} }
onMounted(async () => {
await loadData()
})
defineExpose({ defineExpose({
setDataList, loadData,
refreshData: loadData
}) })
</script> </script>
<style scoped> <style scoped>
.custom-bottom {
height: 60px;
display: flex;
align-items: center;
justify-content: center;
background: linear-gradient(135deg, #f8f9fa 0%, #e9ecef 100%);
border-top: 1px solid #e1e4e8;
}
.record-count {
display: flex;
align-items: center;
padding: 8px 16px;
background: white;
border-radius: 6px;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
font-size: 14px;
color: #606266;
}
.record-count i {
margin-right: 8px;
color: #409eff;
}
.count-number { .count-number {
font-weight: bold; font-weight: bold;
color: #409eff; color: #409eff;
......
...@@ -5,15 +5,14 @@ ...@@ -5,15 +5,14 @@
ref="formRef" ref="formRef"
:data="formData" :data="formData"
:loading="loading" :loading="loading"
@submit="submitEvent" >
@reset="resetEvent">
<vxe-form-item title="年份" field="qyear" span="24"> <vxe-form-item title="年份" field="qyear" span="24">
<template #default="params"> <template #default="params">
<div class="button-group"> <div class="button-group">
<vxe-button <vxe-button
:mode="params.data.qyear === undefined ? 'button' : 'text'" :mode="params.data.qyear === undefined ? 'button' : 'text'"
:status="params.data.qyear === undefined ? 'primary' : ''" :status="params.data.qyear === undefined ? 'primary' : ''"
@click="handleButtonClick('qyear', undefined, params)" @click="handleButtonClick('qyear', undefined)"
> >
全部 全部
</vxe-button> </vxe-button>
...@@ -22,20 +21,21 @@ ...@@ -22,20 +21,21 @@
:key="year" :key="year"
:mode="params.data.qyear === year ? 'button' : 'text'" :mode="params.data.qyear === year ? 'button' : 'text'"
:status="params.data.qyear === year ? 'primary' : ''" :status="params.data.qyear === year ? 'primary' : ''"
@click="handleButtonClick('qyear', year, params)" @click="handleButtonClick('qyear', year)"
> >
{{ year }} {{ year }}
</vxe-button> </vxe-button>
</div> </div>
</template> </template>
</vxe-form-item> </vxe-form-item>
<vxe-form-item title="风险等级" field="riskLevel" span="24" :item-render="{}" title-overflow>
<vxe-form-item title="风险等级" field="riskLevel" span="24">
<template #default="params"> <template #default="params">
<div class="button-group"> <div class="button-group">
<vxe-button <vxe-button
:mode="params.data.riskLevel === undefined ? 'button' : 'text'" :mode="params.data.riskLevel === undefined ? 'button' : 'text'"
:status="params.data.riskLevel === undefined ? 'primary' : ''" :status="params.data.riskLevel === undefined ? 'primary' : ''"
@click="handleButtonClick('riskLevel', undefined, params)" @click="handleButtonClick('riskLevel', undefined)"
> >
全部 全部
</vxe-button> </vxe-button>
...@@ -44,20 +44,21 @@ ...@@ -44,20 +44,21 @@
:key="item.value" :key="item.value"
:mode="params.data.riskLevel === item.value ? 'button' : 'text'" :mode="params.data.riskLevel === item.value ? 'button' : 'text'"
:status="params.data.riskLevel === item.value ? 'primary' : ''" :status="params.data.riskLevel === item.value ? 'primary' : ''"
@click="handleButtonClick('riskLevel', item.value, params)" @click="handleButtonClick('riskLevel', item.value)"
> >
{{ item.label }} {{ item.label }}
</vxe-button> </vxe-button>
</div> </div>
</template> </template>
</vxe-form-item> </vxe-form-item>
<vxe-form-item title="采集频率" field="collFreq" span="24" :item-render="{}" title-overflow>
<vxe-form-item title="采集频率" field="collFreq" span="24">
<template #default="params"> <template #default="params">
<div class="button-group"> <div class="button-group">
<vxe-button <vxe-button
:mode="params.data.collFreq === undefined ? 'button' : 'text'" :mode="params.data.collFreq === undefined ? 'button' : 'text'"
:status="params.data.collFreq === undefined ? 'primary' : ''" :status="params.data.collFreq === undefined ? 'primary' : ''"
@click="handleButtonClick('collFreq', undefined, params)" @click="handleButtonClick('collFreq', undefined)"
> >
全部 全部
</vxe-button> </vxe-button>
...@@ -66,20 +67,21 @@ ...@@ -66,20 +67,21 @@
:key="item.value" :key="item.value"
:mode="params.data.collFreq === item.value ? 'button' : 'text'" :mode="params.data.collFreq === item.value ? 'button' : 'text'"
:status="params.data.collFreq === item.value ? 'primary' : ''" :status="params.data.collFreq === item.value ? 'primary' : ''"
@click="handleButtonClick('collFreq', item.value, params)" @click="handleButtonClick('collFreq', item.value)"
> >
{{ item.label }} {{ item.label }}
</vxe-button> </vxe-button>
</div> </div>
</template> </template>
</vxe-form-item> </vxe-form-item>
<vxe-form-item title="采集方法" field="collMethod" span="24" :item-render="{}" title-overflow>
<vxe-form-item title="采集方法" field="collMethod" span="24">
<template #default="params"> <template #default="params">
<div class="button-group"> <div class="button-group">
<vxe-button <vxe-button
:mode="params.data.collMethod === undefined ? 'button' : 'text'" :mode="params.data.collMethod === undefined ? 'button' : 'text'"
:status="params.data.collMethod === undefined ? 'primary' : ''" :status="params.data.collMethod === undefined ? 'primary' : ''"
@click="handleButtonClick('collMethod', undefined, params)" @click="handleButtonClick('collMethod', undefined)"
> >
全部 全部
</vxe-button> </vxe-button>
...@@ -88,34 +90,35 @@ ...@@ -88,34 +90,35 @@
:key="item.value" :key="item.value"
:mode="params.data.collMethod === item.value ? 'button' : 'text'" :mode="params.data.collMethod === item.value ? 'button' : 'text'"
:status="params.data.collMethod === item.value ? 'primary' : ''" :status="params.data.collMethod === item.value ? 'primary' : ''"
@click="handleButtonClick('collMethod', item.value, params)" @click="handleButtonClick('collMethod', item.value)"
> >
{{ item.label }} {{ item.label }}
</vxe-button> </vxe-button>
</div> </div>
</template> </template>
</vxe-form-item> </vxe-form-item>
<vxe-form-item title="指标类型" field="mtrcCtp" span="24" :item-render="{}" title-overflow>
<vxe-form-item title="指标类型" field="mtrcCtp" span="24">
<template #default="params"> <template #default="params">
<div class="button-group"> <div class="button-group">
<vxe-button <vxe-button
:mode="params.data.mtrcCtp === undefined ? 'button' : 'text'" :mode="params.data.mtrcCtp === undefined ? 'button' : 'text'"
:status="params.data.mtrcCtp === undefined ? 'primary' : ''" :status="params.data.mtrcCtp === undefined ? 'primary' : ''"
@click="handleButtonClick('mtrcCtp', undefined, params)" @click="handleButtonClick('mtrcCtp', undefined)"
> >
全部 全部
</vxe-button> </vxe-button>
<vxe-button <vxe-button
:mode="params.data.mtrcCtp === '1' ? 'button' : 'text'" :mode="params.data.mtrcCtp === '1' ? 'button' : 'text'"
:status="params.data.mtrcCtp === '1' ? 'primary' : ''" :status="params.data.mtrcCtp === '1' ? 'primary' : ''"
@click="handleButtonClick('mtrcCtp', '1', params)" @click="handleButtonClick('mtrcCtp', '1')"
> >
基础指标 基础指标
</vxe-button> </vxe-button>
<vxe-button <vxe-button
:mode="params.data.mtrcCtp === '2' ? 'button' : 'text'" :mode="params.data.mtrcCtp === '2' ? 'button' : 'text'"
:status="params.data.mtrcCtp === '2' ? 'primary' : ''" :status="params.data.mtrcCtp === '2' ? 'primary' : ''"
@click="handleButtonClick('mtrcCtp', '2', params)" @click="handleButtonClick('mtrcCtp', '2')"
> >
计算指标 计算指标
</vxe-button> </vxe-button>
...@@ -123,210 +126,178 @@ ...@@ -123,210 +126,178 @@
</template> </template>
</vxe-form-item> </vxe-form-item>
<vxe-form-item title="是否统计" field="mtrcTp" span="24">
<vxe-form-item title="是否统计" field="metrictype" span="24" :item-render="{}" title-overflow>
<template #default="params"> <template #default="params">
<div class="button-group"> <div class="button-group">
<vxe-button <vxe-button
:mode="params.data.metrictype === undefined ? 'button' : 'text'" :mode="params.data.mtrcTp === undefined ? 'button' : 'text'"
:status="params.data.metrictype === undefined ? 'primary' : ''" :status="params.data.mtrcTp === undefined ? 'primary' : ''"
@click="handleButtonClick('mtrcTp', undefined, params)" @click="handleButtonClick('mtrcTp', undefined)"
> >
全部 全部
</vxe-button> </vxe-button>
<vxe-button <vxe-button
:mode="params.data.mtrcTp === '1' ? 'button' : 'text'" :mode="params.data.mtrcTp === '1' ? 'button' : 'text'"
:status="params.data.mtrcTp === '1' ? 'primary' : ''" :status="params.data.mtrcTp === '1' ? 'primary' : ''"
@click="handleButtonClick('mtrcTp', '1', params)" @click="handleButtonClick('mtrcTp', '1')"
> >
统计 统计
</vxe-button> </vxe-button>
<vxe-button <vxe-button
:mode="params.data.metrictype === '2' ? 'button' : 'text'" :mode="params.data.mtrcTp === '2' ? 'button' : 'text'"
:status="params.data.mtrcTp === '2' ? 'primary' : ''" :status="params.data.mtrcTp === '2' ? 'primary' : ''"
@click="handleButtonClick('mtrcTp', '2', params)" @click="handleButtonClick('mtrcTp', '2')"
> >
不统计 不统计
</vxe-button> </vxe-button>
</div> </div>
</template> </template>
</vxe-form-item> </vxe-form-item>
<vxe-form-item title="审批状态" field="appSta" span="24" :item-render="{}" title-overflow>
<vxe-form-item title="审批状态" field="appSta" span="24">
<template #default="params"> <template #default="params">
<div class="button-group"> <div class="button-group">
<vxe-button <vxe-button
:mode="params.data.appSta === undefined ? 'button' : 'text'" :mode="params.data.appSta === undefined ? 'button' : 'text'"
:status="params.data.appSta === undefined ? 'primary' : ''" :status="params.data.appSta === undefined ? 'primary' : ''"
@click="handleButtonClick('appSta', undefined, params)" @click="handleButtonClick('appSta', undefined)"
> >
全部 全部
</vxe-button> </vxe-button>
<vxe-button <vxe-button
:mode="params.data.appSta === '1' ? 'button' : 'text'" :mode="params.data.appSta === '1' ? 'button' : 'text'"
:status="params.data.appSta === '1' ? 'primary' : ''" :status="params.data.appSta === '1' ? 'primary' : ''"
@click="handleButtonClick('appSta', '1', params)" @click="handleButtonClick('appSta', '1')"
> >
通过 通过
</vxe-button> </vxe-button>
<vxe-button <vxe-button
:mode="params.data.appSta === '2' ? 'button' : 'text'" :mode="params.data.appSta === '2' ? 'button' : 'text'"
:status="params.data.appSta === '2' ? 'primary' : ''" :status="params.data.appSta === '2' ? 'primary' : ''"
@click="handleButtonClick('appSta', '2', params)" @click="handleButtonClick('appSta', '2')"
> >
未通过 未通过
</vxe-button> </vxe-button>
</div> </div>
</template> </template>
</vxe-form-item> </vxe-form-item>
<vxe-form-item title="指标名称" field="mtrcName" span="24" :item-render="{}" title-overflow>
<template #default="params">
<vxe-input v-model="params.data.mtrcName" placeholder="请输入名称" clearable @change="changeEvent"></vxe-input>
<!-- 添加搜索和重置按钮 -->
<!-- <vxe-form-item span="24">
<template #default>
<div style="display: flex; gap: 8px; justify-content: center;">
<vxe-button status="primary" @click="handleSubmit">查询</vxe-button>
<vxe-button @click="handleReset">重置</vxe-button>
</div>
</template> </template>
</vxe-form-item> </vxe-form-item> -->
</vxe-form> </vxe-form>
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref,onMounted } from 'vue' import { ref, onMounted } from 'vue'
import { Dayjs } from 'dayjs';
import { VXETable, VxeFormInstance, VxeFormPropTypes, VxeFormEvents } from 'vxe-table'
import { labelCol } from '../../newlib/components/data/AuditCommon.data';
interface FormDataVO { interface FormDataVO {
qyear:number|undefined qyear: number | undefined
qmonth: number|undefined qmonth: number | undefined
quarter: number|undefined quarter: number | undefined
riskLevel:number|undefined riskLevel: number | undefined
collMethod:number|undefined collMethod: number | undefined
collFreq:number|undefined collFreq: number | undefined
mtrcCtp:string|undefined mtrcCtp: string | undefined
appSta:string|undefined, appSta: string | undefined
mtrcTp:string|undefined, mtrcTp: string | undefined
} }
const emit = defineEmits(['searchData']); const emit = defineEmits(['searchData', 'resetData'])
const formRef = ref<VxeFormInstance>()
const yearOptions = ref([]);
const formRef = ref()
const yearOptions = ref<number[]>([])
const loading = ref(false) const loading = ref(false)
const formData = ref<FormDataVO>({ const formData = ref<FormDataVO>({
qyear: 2025, qyear: 2025,
qmonth: undefined, qmonth: undefined,
quarter: undefined, quarter: undefined,
riskLevel:undefined, riskLevel: undefined,
collMethod:undefined, collMethod: undefined,
collFreq:undefined, collFreq: undefined,
mtrcCtp:undefined, mtrcCtp: undefined,
appSta:undefined, appSta: undefined,
mtrcTp:undefined, mtrcTp: undefined
}) })
// 处理按钮点击事件 // 选项数据
const handleButtonClick = (field: keyof FormDataVO, value: any, params: any) => { const riskLevelOptions = ref([
{ label: '低风险', value: 1 },
// 如果点击已选中的按钮,则取消选择 { label: '中风险', value: 2 },
if (formData.value[field] === value) { { label: '高风险', value: 3 }
formData.value[field] = undefined; ])
} else {
formData.value[field] = value; const collMethodOptions = ref([
} { label: '手动', value: 1 },
{ label: '自动', value: 2 },
const $form = formRef.value { label: '人工或自动', value: 3 }
if ($form) { ])
$form.updateStatus(params)
searchData() const collFreqOptions = ref([
{ label: '月', value: 3 },
{ label: '季', value: 4 },
{ label: '半年', value: 5 },
{ label: '年', value: 6 }
])
// 初始化年份选项
const initYearOptions = () => {
const currentYear = new Date().getFullYear()
yearOptions.value = []
for (let i = currentYear - 3; i <= currentYear; i++) {
yearOptions.value.push(i)
} }
} }
const changeEvent = (params: any) => { // 处理按钮点击
const $form = formRef.value const handleButtonClick = (field: keyof FormDataVO, value: any) => {
if ($form) { if (formData.value[field] === value) {
$form.updateStatus(params) formData.value[field] = undefined
searchData() } else {
formData.value[field] = value
} }
}
const submitEvent: VxeFormEvents.Submit = () => { handleSubmit();
loading.value = true
setTimeout(() => {
loading.value = false
VXETable.modal.message({ content: '保存成功', status: 'success' })
}, 1000)
} }
const resetEvent: VxeFormEvents.Reset = () => { // 处理输入变化
VXETable.modal.message({ content: '重置事件', status: 'info' }) const handleInputChange = () => {
// 不立即搜索,等待用户点击查询按钮
} }
const getMonths = (value: Dayjs) => { // 处理提交查询
const localeData = value.localeData(); const handleSubmit = () => {
const months = []; emit('searchData', { ...formData.value })
for (let i = 0; i < 12; i++) { }
months.push(localeData.monthsShort(value.month(i)));
}
return months;
};
const initYearOptions = () => { // 处理重置
yearOptions.value = []; const handleReset = () => {
const currentYear = new Date().getFullYear(); formData.value = {
for(let i = currentYear - 3; i <= currentYear; i++) { qyear: 2025,
yearOptions.value.push(i); qmonth: undefined,
quarter: undefined,
riskLevel: undefined,
collMethod: undefined,
collFreq: undefined,
mtrcCtp: undefined,
appSta: undefined,
mtrcTp: undefined
} }
}; emit('resetData')
const monthOptions = ref([
{ label: '1月', value: 1 },
{ label: '2月', value: 2 },
{ label: '3月', value: 3 },
{ label: '4月', value: 4 },
{ label: '5月', value: 5 },
{ label: '6月', value: 6 },
{ label: '7月', value: 7 },
{ label: '8月', value: 8 },
{ label: '9月', value: 9 },
{ label: '10月', value: 10 },
{ label: '11月', value: 11 },
{ label: '12月', value: 12 }
]);
const quarterOptions = ref([
{ label: '第一季度', value: 1 },
{ label: '第二季度', value: 2 },
{ label: '第三季度', value: 3 },
{ label: '第四季度', value: 4 }
]);
const collMethodOptions = ref([
{ label: '手动', value: 1 },
{ label: '自动', value: 2},
{ label: '人工或自动', value: 3},
]);
const riskLevelOptions = ref([
{ label: '低风险', value: 1 },
{ label: '中风险', value: 2},
{ label: '高风险', value: 3},
]);
const collFreqOptions = ref([
{ label: '月', value: 3 },
{ label: '季', value: 4},
{ label: '半年', value: 5},
{ label: '年', value: 6},
]);
const searchData = ()=> {
emit('searchData',formData.value);
} }
onMounted(async () => { onMounted(() => {
await initYearOptions(); initYearOptions()
}) })
</script> </script>
......
...@@ -23,6 +23,7 @@ export const columns: BasicColumn[] = [ ...@@ -23,6 +23,7 @@ export const columns: BasicColumn[] = [
ellipsis: true, ellipsis: true,
dataIndex: 'domainDesc', dataIndex: 'domainDesc',
width: 300, width: 300,
ifShow: false,
}, },
{ {
title: '更新时间', title: '更新时间',
......
...@@ -7,6 +7,8 @@ ...@@ -7,6 +7,8 @@
<a-button type="primary" @click="handleCreate" preIcon="ant-design:plus-outlined">新建</a-button> <a-button type="primary" @click="handleCreate" preIcon="ant-design:plus-outlined">新建</a-button>
<a-button v-show="showBtn" type="primary" preIcon="ant-design:export-outlined" @click="onExportXls"> 导出</a-button> <a-button v-show="showBtn" type="primary" preIcon="ant-design:export-outlined" @click="onExportXls"> 导出</a-button>
<j-upload-button v-show="showBtn" type="primary" preIcon="ant-design:import-outlined" @click="onImportXls">导入</j-upload-button> <j-upload-button v-show="showBtn" type="primary" preIcon="ant-design:import-outlined" @click="onImportXls">导入</j-upload-button>
<a-button type="primary" @click="handleExpandAll" preIcon="ant-design:down-outlined">展开所有</a-button>
<a-button type="primary" @click="handleCollapseAll" preIcon="ant-design:up-outlined">折叠所有</a-button>
<a-dropdown v-show="showBtn" v-if="selectedRowKeys.length > 0"> <a-dropdown v-show="showBtn" v-if="selectedRowKeys.length > 0">
<template #overlay> <template #overlay>
...@@ -217,7 +219,6 @@ import { downloadFile } from '/@/utils/common/renderUtils'; ...@@ -217,7 +219,6 @@ import { downloadFile } from '/@/utils/common/renderUtils';
getDataByResult(result.items); getDataByResult(result.items);
setTimeout(() => { setTimeout(() => {
loadDataByExpandedRows() loadDataByExpandedRows()
nextTick(expandAll)
}, 800); }, 800);
} }
...@@ -281,10 +282,7 @@ import { downloadFile } from '/@/utils/common/renderUtils'; ...@@ -281,10 +282,7 @@ import { downloadFile } from '/@/utils/common/renderUtils';
//判断是否标记了带有子节点 //判断是否标记了带有子节点
if(item["hasChild"]=='1'){ if(item["hasChild"]=='1'){
let loadChild = { id: item.id+'_loadChild', name: 'loading...', isLoading: true } let loadChild = { id: item.id+'_loadChild', name: 'loading...', isLoading: true }
console.log(loadChild)
item.children = [loadChild] item.children = [loadChild]
// // 设置展开的key
handleExpand(true,item)
} }
return item return item
}) })
...@@ -394,6 +392,51 @@ import { downloadFile } from '/@/utils/common/renderUtils'; ...@@ -394,6 +392,51 @@ import { downloadFile } from '/@/utils/common/renderUtils';
] ]
} }
/**
* 展开所有节点
*/
async function handleExpandAll() {
// 展开所有节点
expandAll();
// 加载所有子节点数据
const dataSource = getDataSource();
await loadAllChildren(dataSource);
}
/**
* 折叠所有节点
*/
function handleCollapseAll() {
// 清空展开的节点
expandedRowKeys.value = [];
// 重新加载表格数据,确保所有节点都折叠
reload();
}
/**
* 递归加载所有子节点数据
*/
async function loadAllChildren(nodes) {
if (!nodes || nodes.length === 0) return;
for (const node of nodes) {
if (node.children && node.children.length > 0 && node.children[0].isLoading) {
let data = getFormVlaue();
if (data.level !== "1") {
let result = await getChildList({upperDomainCode: node.domainCode, level: data.level, domainName: data.domainName});
result = result.records ? result.records : result;
if (result && result.length > 0) {
node.children = getDataByResult(result);
await loadAllChildren(node.children);
}
}
} else if (node.children && node.children.length > 0) {
await loadAllChildren(node.children);
}
}
}
</script> </script>
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论