feat: 分题统计前端开发 (#276)
This commit is contained in:
parent
1f1dd86f89
commit
cce508d17a
2
web/components.d.ts
vendored
2
web/components.d.ts
vendored
@ -17,6 +17,7 @@ declare module 'vue' {
|
|||||||
ElDialog: typeof import('element-plus/es')['ElDialog']
|
ElDialog: typeof import('element-plus/es')['ElDialog']
|
||||||
ElForm: typeof import('element-plus/es')['ElForm']
|
ElForm: typeof import('element-plus/es')['ElForm']
|
||||||
ElFormItem: typeof import('element-plus/es')['ElFormItem']
|
ElFormItem: typeof import('element-plus/es')['ElFormItem']
|
||||||
|
ElIcon: typeof import('element-plus/es')['ElIcon']
|
||||||
ElInput: typeof import('element-plus/es')['ElInput']
|
ElInput: typeof import('element-plus/es')['ElInput']
|
||||||
ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
|
ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
|
||||||
ElMenu: typeof import('element-plus/es')['ElMenu']
|
ElMenu: typeof import('element-plus/es')['ElMenu']
|
||||||
@ -29,6 +30,7 @@ declare module 'vue' {
|
|||||||
ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
|
ElRadioButton: typeof import('element-plus/es')['ElRadioButton']
|
||||||
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
|
ElRadioGroup: typeof import('element-plus/es')['ElRadioGroup']
|
||||||
ElRow: typeof import('element-plus/es')['ElRow']
|
ElRow: typeof import('element-plus/es')['ElRow']
|
||||||
|
ElSegmented: typeof import('element-plus/es')['ElSegmented']
|
||||||
ElSelect: typeof import('element-plus/es')['ElSelect']
|
ElSelect: typeof import('element-plus/es')['ElSelect']
|
||||||
ElSelectV2: typeof import('element-plus/es')['ElSelectV2']
|
ElSelectV2: typeof import('element-plus/es')['ElSelectV2']
|
||||||
ElSlider: typeof import('element-plus/es')['ElSlider']
|
ElSlider: typeof import('element-plus/es')['ElSlider']
|
||||||
|
@ -21,6 +21,7 @@
|
|||||||
"axios": "^1.4.0",
|
"axios": "^1.4.0",
|
||||||
"clipboard": "^2.0.11",
|
"clipboard": "^2.0.11",
|
||||||
"crypto-js": "^4.2.0",
|
"crypto-js": "^4.2.0",
|
||||||
|
"echarts": "^5.5.0",
|
||||||
"element-plus": "^2.7.0",
|
"element-plus": "^2.7.0",
|
||||||
"lodash-es": "^4.17.21",
|
"lodash-es": "^4.17.21",
|
||||||
"moment": "^2.29.4",
|
"moment": "^2.29.4",
|
||||||
|
@ -8,3 +8,11 @@ export const getRecycleList = (data) => {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getStatisticList = (data) => {
|
||||||
|
return axios.get('/survey/dataStatistic/aggregationStatis', {
|
||||||
|
params: {
|
||||||
|
...data
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
77
web/src/management/config/analysisConfig.js
Normal file
77
web/src/management/config/analysisConfig.js
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
import { menuItems } from './questionMenuConfig'
|
||||||
|
|
||||||
|
export const noDataConfig = {
|
||||||
|
title: '暂无数据',
|
||||||
|
desc: '您的问卷当前还没有数据,快去回收问卷吧!',
|
||||||
|
img: '/imgs/icons/analysis-empty.webp'
|
||||||
|
}
|
||||||
|
|
||||||
|
export const separateItemListHead = [
|
||||||
|
{
|
||||||
|
title: '选项',
|
||||||
|
field: 'text'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '数量',
|
||||||
|
field: 'count'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: '占比',
|
||||||
|
field: 'percent'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
// 图表名称需要和./chartConfig.js中保持一致
|
||||||
|
export const questionChartsConfig = {
|
||||||
|
[menuItems['checkbox']['type']]: ['bar'],
|
||||||
|
[menuItems['radio-nps']['type']]: ['gauge', 'pie', 'bar'],
|
||||||
|
default: ['pie', 'bar']
|
||||||
|
}
|
||||||
|
|
||||||
|
export const analysisTypeMap = {
|
||||||
|
dataTable: 'dataTable',
|
||||||
|
separateStatistics: 'separateStatistics'
|
||||||
|
}
|
||||||
|
|
||||||
|
export const analysisType = [
|
||||||
|
{
|
||||||
|
value: analysisTypeMap.dataTable,
|
||||||
|
label: '数据列表',
|
||||||
|
icon: 'icon-shujuliebiao'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: analysisTypeMap.separateStatistics,
|
||||||
|
label: '分题统计',
|
||||||
|
icon: 'icon-fentitongji'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
export const summaryType = {
|
||||||
|
between: 'between'
|
||||||
|
}
|
||||||
|
|
||||||
|
export const summaryItemConfig = {
|
||||||
|
'radio-nps': [
|
||||||
|
{
|
||||||
|
text: '推荐者',
|
||||||
|
field: 'id',
|
||||||
|
type: summaryType.between,
|
||||||
|
max: 10,
|
||||||
|
min: 9
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: '中立者',
|
||||||
|
field: 'id',
|
||||||
|
type: summaryType.between,
|
||||||
|
max: 8,
|
||||||
|
min: 7
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: '贬损者',
|
||||||
|
field: 'id',
|
||||||
|
type: summaryType.between,
|
||||||
|
max: 6,
|
||||||
|
min: 0
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
57
web/src/management/config/chartConfig/bar.js
Normal file
57
web/src/management/config/chartConfig/bar.js
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
/**
|
||||||
|
* @Description: 柱状图配置
|
||||||
|
* @CreateDate: 2024-04-30
|
||||||
|
*/
|
||||||
|
export default (data) => {
|
||||||
|
const xAxisData = data.map((item) => item.name)
|
||||||
|
return {
|
||||||
|
color: ['#55A8FD'],
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
axisPointer: {
|
||||||
|
type: 'shadow'
|
||||||
|
},
|
||||||
|
formatter: '{a} <br/>{b}: {c}'
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
left: '3%',
|
||||||
|
right: '4%',
|
||||||
|
bottom: '3%',
|
||||||
|
containLabel: true
|
||||||
|
},
|
||||||
|
xAxis: [
|
||||||
|
{
|
||||||
|
type: 'category',
|
||||||
|
data: xAxisData,
|
||||||
|
axisTick: {
|
||||||
|
alignWithLabel: true
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
interval: 0,
|
||||||
|
formatter(value) {
|
||||||
|
return value
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
yAxis: [
|
||||||
|
{
|
||||||
|
type: 'value',
|
||||||
|
splitLine: {
|
||||||
|
lineStyle: {
|
||||||
|
type: 'dashed'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
],
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
showAllSymbol: true,
|
||||||
|
name: '提交人数',
|
||||||
|
type: 'bar',
|
||||||
|
barMaxWidth: 50,
|
||||||
|
data
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
146
web/src/management/config/chartConfig/gauge.js
Normal file
146
web/src/management/config/chartConfig/gauge.js
Normal file
@ -0,0 +1,146 @@
|
|||||||
|
/**
|
||||||
|
* @Description: gauge(仪表盘)
|
||||||
|
* @CreateDate: 2024-04-30
|
||||||
|
*/
|
||||||
|
export default (data) => {
|
||||||
|
return {
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
type: 'gauge',
|
||||||
|
startAngle: 180,
|
||||||
|
endAngle: 0,
|
||||||
|
min: -100,
|
||||||
|
max: 100,
|
||||||
|
radius: '130%',
|
||||||
|
center: ['50%', '80%'],
|
||||||
|
splitNumber: 4,
|
||||||
|
z: 2,
|
||||||
|
axisLabel: {
|
||||||
|
show: false,
|
||||||
|
distance: 0,
|
||||||
|
color: '#AAB1C0',
|
||||||
|
fontSize: 12,
|
||||||
|
fontFamily: 'DaQi-Font'
|
||||||
|
},
|
||||||
|
splitLine: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
axisTick: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
axisLine: {
|
||||||
|
lineStyle: {
|
||||||
|
width: 40,
|
||||||
|
color: [[1, '#e3efff']]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'gauge',
|
||||||
|
startAngle: 174,
|
||||||
|
endAngle: 5,
|
||||||
|
min: -100,
|
||||||
|
max: 100,
|
||||||
|
radius: '130%',
|
||||||
|
splitNumber: 4,
|
||||||
|
center: ['50%', '80%'],
|
||||||
|
z: 3,
|
||||||
|
axisLabel: {
|
||||||
|
distance: -5,
|
||||||
|
color: '#666',
|
||||||
|
rotate: 'tangential',
|
||||||
|
fontSize: 12,
|
||||||
|
fontFamily: 'DaQi-Font'
|
||||||
|
},
|
||||||
|
splitLine: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
axisTick: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
axisLine: {
|
||||||
|
lineStyle: {
|
||||||
|
width: 40,
|
||||||
|
color: [[1, '#e3efff']]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'gauge',
|
||||||
|
startAngle: 178,
|
||||||
|
endAngle: 0,
|
||||||
|
min: -100,
|
||||||
|
max: 100,
|
||||||
|
radius: '109%',
|
||||||
|
z: 4,
|
||||||
|
center: ['50%', '80%'],
|
||||||
|
splitNumber: 4,
|
||||||
|
itemStyle: {
|
||||||
|
color: '#58D9F9'
|
||||||
|
},
|
||||||
|
progress: {
|
||||||
|
show: true,
|
||||||
|
roundCap: true,
|
||||||
|
width: 15,
|
||||||
|
itemStyle: {
|
||||||
|
color: '#55A8FD',
|
||||||
|
shadowBlur: 10,
|
||||||
|
shadowColor: '#55A8FD'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
pointer: {
|
||||||
|
icon: 'triangle',
|
||||||
|
length: '10%',
|
||||||
|
width: 8,
|
||||||
|
offsetCenter: [0, '-80%'],
|
||||||
|
itemStyle: {
|
||||||
|
color: '#55A8FD'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
axisLine: {
|
||||||
|
lineStyle: {
|
||||||
|
width: 15,
|
||||||
|
color: [[1, '#d3e5fe']]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
axisTick: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
splitLine: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
offsetCenter: [0, '-15%'],
|
||||||
|
fontSize: 18,
|
||||||
|
color: '#666'
|
||||||
|
},
|
||||||
|
detail: {
|
||||||
|
fontSize: 46,
|
||||||
|
lineHeight: 40,
|
||||||
|
height: 40,
|
||||||
|
offsetCenter: [0, '-45%'],
|
||||||
|
valueAnimation: true,
|
||||||
|
color: '#55A8FD',
|
||||||
|
formatter: function (value) {
|
||||||
|
if (value) {
|
||||||
|
return value + '%'
|
||||||
|
} else if (value === 0) {
|
||||||
|
return value
|
||||||
|
} else {
|
||||||
|
return '--'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
value: data,
|
||||||
|
name: 'NPS'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
9
web/src/management/config/chartConfig/index.js
Normal file
9
web/src/management/config/chartConfig/index.js
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import pie from './pie'
|
||||||
|
import bar from './bar'
|
||||||
|
import gauge from './gauge'
|
||||||
|
|
||||||
|
export const getOption = {
|
||||||
|
pie,
|
||||||
|
bar,
|
||||||
|
gauge
|
||||||
|
}
|
57
web/src/management/config/chartConfig/pie.js
Normal file
57
web/src/management/config/chartConfig/pie.js
Normal file
@ -0,0 +1,57 @@
|
|||||||
|
const color = [
|
||||||
|
'#55A8FD',
|
||||||
|
'#36CBCB',
|
||||||
|
'#FAD337',
|
||||||
|
'#A6D6FF',
|
||||||
|
'#A177DC',
|
||||||
|
'#F46C73',
|
||||||
|
'#FFBA62',
|
||||||
|
'#ACE474',
|
||||||
|
'#BEECD6',
|
||||||
|
'#AFD2FF'
|
||||||
|
]
|
||||||
|
/*
|
||||||
|
* @Description: 饼图配置
|
||||||
|
* @CreateDate: 2024-04-30
|
||||||
|
*/
|
||||||
|
export default (data) => {
|
||||||
|
return {
|
||||||
|
color,
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'item',
|
||||||
|
formatter: '{a} <br/>{b}: {c} ({d}%)'
|
||||||
|
},
|
||||||
|
legend: {
|
||||||
|
orient: 'vertical',
|
||||||
|
right: 12,
|
||||||
|
top: 12,
|
||||||
|
tooltip: {
|
||||||
|
show: true
|
||||||
|
},
|
||||||
|
formatter(name) {
|
||||||
|
return name.length > 17 ? name.substr(0, 17) + '...' : name
|
||||||
|
}
|
||||||
|
},
|
||||||
|
series: [
|
||||||
|
{
|
||||||
|
name: '提交人数',
|
||||||
|
type: 'pie',
|
||||||
|
radius: ['50%', '80%'],
|
||||||
|
avoidLabelOverlap: false,
|
||||||
|
itemStyle: {
|
||||||
|
borderRadius: 10,
|
||||||
|
borderColor: '#fff',
|
||||||
|
borderWidth: 2
|
||||||
|
},
|
||||||
|
label: {
|
||||||
|
show: true,
|
||||||
|
formatter({ data }) {
|
||||||
|
const name = data?.name || ''
|
||||||
|
return name.length > 17 ? name.substr(0, 17) + '...' : name
|
||||||
|
}
|
||||||
|
},
|
||||||
|
data
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
const menuItems = {
|
export const menuItems = {
|
||||||
text: {
|
text: {
|
||||||
type: 'text',
|
type: 'text',
|
||||||
snapshot: '/imgs/question-type-snapshot/iL84te6xxU1657702189333.webp',
|
snapshot: '/imgs/question-type-snapshot/iL84te6xxU1657702189333.webp',
|
||||||
|
25
web/src/management/hooks/useCharts.js
Normal file
25
web/src/management/hooks/useCharts.js
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
import * as echarts from 'echarts'
|
||||||
|
import { getOption } from '@/management/config/chartConfig'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 绘制图表
|
||||||
|
* @param {Object} el
|
||||||
|
* @param {String} type
|
||||||
|
* @param {Array} data
|
||||||
|
*/
|
||||||
|
export default (el, type, data) => {
|
||||||
|
const chart = echarts.init(el)
|
||||||
|
const option = getOption[type](data)
|
||||||
|
|
||||||
|
chart.setOption(option, true)
|
||||||
|
|
||||||
|
const resize = () => {
|
||||||
|
chart.resize()
|
||||||
|
}
|
||||||
|
|
||||||
|
const changeType = (type, data) => {
|
||||||
|
chart.setOption(getOption[type](data), true)
|
||||||
|
}
|
||||||
|
|
||||||
|
return { chart, resize, changeType }
|
||||||
|
}
|
20
web/src/management/hooks/useResizeObserver.js
Normal file
20
web/src/management/hooks/useResizeObserver.js
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
// 引入防抖函数
|
||||||
|
import _debounce from 'lodash/debounce'
|
||||||
|
/**
|
||||||
|
* @description: 监听元素尺寸变化
|
||||||
|
* @param {*} el 元素dom
|
||||||
|
* @param {*} cb resize变化时执行的方法
|
||||||
|
* @param {*} wait 防抖间隔
|
||||||
|
* @return {*}
|
||||||
|
*/
|
||||||
|
export default (el, cb, wait = 200) => {
|
||||||
|
const resizeObserver = new ResizeObserver(_debounce(cb, wait))
|
||||||
|
|
||||||
|
resizeObserver.observe(el)
|
||||||
|
|
||||||
|
const destroy = () => {
|
||||||
|
resizeObserver.disconnect(el)
|
||||||
|
}
|
||||||
|
|
||||||
|
return { destroy, resizeObserver }
|
||||||
|
}
|
77
web/src/management/hooks/useStatisticsItemChart.js
Normal file
77
web/src/management/hooks/useStatisticsItemChart.js
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
import { ref, watchEffect } from 'vue'
|
||||||
|
import { cleanRichText } from '@/common/xss'
|
||||||
|
import { questionChartsConfig } from '../config/analysisConfig'
|
||||||
|
|
||||||
|
// 饼图数据处理
|
||||||
|
const pie = (data) => {
|
||||||
|
const aggregation = data?.aggregation
|
||||||
|
return (
|
||||||
|
aggregation?.map?.((item) => {
|
||||||
|
const { id, count, text } = item
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
value: count,
|
||||||
|
name: cleanRichText(text)
|
||||||
|
}
|
||||||
|
}) || []
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// 柱状图数据处理
|
||||||
|
const bar = (data) => {
|
||||||
|
const aggregation = data?.aggregation
|
||||||
|
return (
|
||||||
|
aggregation?.map?.((item) => {
|
||||||
|
const { id, count, text } = item
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
value: count,
|
||||||
|
name: cleanRichText(text)
|
||||||
|
}
|
||||||
|
}) || []
|
||||||
|
)
|
||||||
|
}
|
||||||
|
// 仪表盘数据处理
|
||||||
|
const gauge = (data) => {
|
||||||
|
return parseFloat(data?.summary?.nps) || 0
|
||||||
|
}
|
||||||
|
|
||||||
|
const dataFormateConfig = {
|
||||||
|
pie,
|
||||||
|
bar,
|
||||||
|
gauge
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @description: 分题统计图表hook
|
||||||
|
* @param {*} chartType
|
||||||
|
* @param {*} data
|
||||||
|
* @return {*} chartRef 图表实例 chartTypeList 图表类型列表 chartType 图表类型 chartData 图表数据
|
||||||
|
*/
|
||||||
|
export default ({ questionType, data }) => {
|
||||||
|
const chartRef = ref(null)
|
||||||
|
const chartTypeList = ref([])
|
||||||
|
const chartType = ref('')
|
||||||
|
const chartData = ref({})
|
||||||
|
|
||||||
|
watchEffect(() => {
|
||||||
|
if (questionType.value) {
|
||||||
|
// 根据题型获取图表类型列表
|
||||||
|
chartTypeList.value = questionChartsConfig[questionType.value] || questionChartsConfig.default
|
||||||
|
if (!chartType.value) {
|
||||||
|
// 默认选中第一项
|
||||||
|
chartType.value = chartTypeList.value?.[0]
|
||||||
|
}
|
||||||
|
if (chartType.value) {
|
||||||
|
// 根据图表类型获取图表数据
|
||||||
|
chartData.value = dataFormateConfig[chartType.value](data)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
chartRef,
|
||||||
|
chartTypeList,
|
||||||
|
chartType,
|
||||||
|
chartData
|
||||||
|
}
|
||||||
|
}
|
@ -1,139 +1,28 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="analysis-page">
|
<div class="analysis-page">
|
||||||
<leftMenu class="left"></leftMenu>
|
<leftMenu class="left"></leftMenu>
|
||||||
<div class="content-wrapper right">
|
<div class="right">
|
||||||
<template v-if="tableData.total">
|
<div class="analysis-tabs">
|
||||||
<h2 class="data-list">数据列表</h2>
|
<router-link
|
||||||
<div class="menus">
|
v-for="item in analysisType"
|
||||||
<el-switch
|
class="analysis-tabs__item"
|
||||||
:model-value="isShowOriginData"
|
:key="item.value"
|
||||||
active-text="是否展示原数据"
|
:to="{ name: item.value }"
|
||||||
@input="onIsShowOriginChange"
|
|
||||||
>
|
|
||||||
</el-switch>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<template v-if="tableData.total">
|
|
||||||
<DataTable :main-table-loading="mainTableLoading" :table-data="tableData" />
|
|
||||||
<el-pagination
|
|
||||||
background
|
|
||||||
layout="prev, pager, next"
|
|
||||||
popper-class="analysis-pagination"
|
|
||||||
:total="tableData.total"
|
|
||||||
@current-change="handleCurrentChange"
|
|
||||||
>
|
>
|
||||||
</el-pagination>
|
<i class="iconfont" :class="item.icon"></i>
|
||||||
</template>
|
<span>{{ item.label }}</span>
|
||||||
<div v-else>
|
</router-link>
|
||||||
<EmptyIndex :data="noDataConfig" />
|
</div>
|
||||||
|
<div class="content-wrapper">
|
||||||
|
<router-view />
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script setup>
|
||||||
import { ElMessage } from 'element-plus'
|
|
||||||
import 'element-plus/theme-chalk/src/message.scss'
|
|
||||||
|
|
||||||
import EmptyIndex from '@/management/components/EmptyIndex.vue'
|
|
||||||
import LeftMenu from '@/management/components/LeftMenu.vue'
|
import LeftMenu from '@/management/components/LeftMenu.vue'
|
||||||
import { getRecycleList } from '@/management/api/analysis'
|
import { analysisType } from '@/management/config/analysisConfig'
|
||||||
|
|
||||||
import DataTable from './components/DataTable.vue'
|
|
||||||
|
|
||||||
export default {
|
|
||||||
name: 'AnalysisPage',
|
|
||||||
data() {
|
|
||||||
return {
|
|
||||||
mainTableLoading: false,
|
|
||||||
tableData: {
|
|
||||||
total: 0,
|
|
||||||
listHead: [],
|
|
||||||
listBody: []
|
|
||||||
},
|
|
||||||
noDataConfig: {
|
|
||||||
title: '暂无数据',
|
|
||||||
desc: '您的问卷当前还没有数据,快去回收问卷吧!',
|
|
||||||
img: '/imgs/icons/analysis-empty.webp'
|
|
||||||
},
|
|
||||||
currentPage: 1,
|
|
||||||
isShowOriginData: false,
|
|
||||||
tmpIsShowOriginData: false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
computed: {},
|
|
||||||
created() {
|
|
||||||
this.init()
|
|
||||||
},
|
|
||||||
methods: {
|
|
||||||
async init() {
|
|
||||||
if (!this.$route.params.id) {
|
|
||||||
ElMessage.error('没有传入问卷参数~')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.mainTableLoading = true
|
|
||||||
try {
|
|
||||||
const res = await getRecycleList({
|
|
||||||
page: this.currentPage,
|
|
||||||
surveyId: this.$route.params.id,
|
|
||||||
isDesensitive: !this.tmpIsShowOriginData // 发起请求的时候,isShowOriginData还没改变,暂存了一个字段
|
|
||||||
})
|
|
||||||
|
|
||||||
if (res.code === 200) {
|
|
||||||
const listHead = this.formatHead(res.data.listHead)
|
|
||||||
this.tableData = { ...res.data, listHead }
|
|
||||||
this.mainTableLoading = false
|
|
||||||
}
|
|
||||||
} catch (error) {
|
|
||||||
ElMessage.error('查询回收数据失败,请重试')
|
|
||||||
}
|
|
||||||
},
|
|
||||||
handleCurrentChange(current) {
|
|
||||||
if (this.mainTableLoading) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
this.currentPage = current
|
|
||||||
this.init()
|
|
||||||
},
|
|
||||||
formatHead(listHead = []) {
|
|
||||||
const head = []
|
|
||||||
|
|
||||||
listHead.forEach((headItem) => {
|
|
||||||
head.push({
|
|
||||||
field: headItem.field,
|
|
||||||
title: headItem.title
|
|
||||||
})
|
|
||||||
|
|
||||||
if (headItem.othersCode?.length) {
|
|
||||||
headItem.othersCode.forEach((item) => {
|
|
||||||
head.push({
|
|
||||||
field: item.code,
|
|
||||||
title: `${headItem.title}-${item.option}`
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
return head
|
|
||||||
},
|
|
||||||
async onIsShowOriginChange(data) {
|
|
||||||
if (this.mainTableLoading) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
// console.log(data)
|
|
||||||
this.tmpIsShowOriginData = data
|
|
||||||
await this.init()
|
|
||||||
this.isShowOriginData = data
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
components: {
|
|
||||||
DataTable,
|
|
||||||
EmptyIndex,
|
|
||||||
LeftMenu
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@ -152,29 +41,60 @@ export default {
|
|||||||
.right {
|
.right {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
padding-left: 120px;
|
min-width: 1160px;
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.menus {
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.content-wrapper {
|
|
||||||
padding: 30px 40px 50px 40px;
|
|
||||||
border-radius: 2px;
|
|
||||||
background-color: #f6f7f9;
|
|
||||||
box-sizing: border-box;
|
|
||||||
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.04);
|
|
||||||
|
|
||||||
:deep(.el-pagination) {
|
|
||||||
margin-top: 20px;
|
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: flex-end;
|
flex-direction: column;
|
||||||
|
overflow: hidden;
|
||||||
|
background-color: #f6f7f9;
|
||||||
|
|
||||||
|
.analysis-tabs {
|
||||||
|
flex: none;
|
||||||
|
gap: 40px;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: normal;
|
||||||
|
width: 100%;
|
||||||
|
height: 56px;
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
background-color: #fff;
|
||||||
|
border-bottom: 1px solid #e7e9eb;
|
||||||
|
|
||||||
|
&__item {
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 8px 0;
|
||||||
|
color: #92949d;
|
||||||
|
|
||||||
|
.iconfont {
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.router-link-active {
|
||||||
|
color: $font-color-title;
|
||||||
|
position: relative;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
width: calc(100% + 5px);
|
||||||
|
height: 3px;
|
||||||
|
background-color: $primary-color;
|
||||||
|
bottom: 0;
|
||||||
|
left: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.data-list {
|
.content-wrapper {
|
||||||
margin-bottom: 20px;
|
flex: auto;
|
||||||
|
overflow: hidden;
|
||||||
|
padding: 24px 24px 24px 104px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
@ -17,22 +17,23 @@
|
|||||||
minWidth="200"
|
minWidth="200"
|
||||||
>
|
>
|
||||||
<template #header="scope">
|
<template #header="scope">
|
||||||
<div class="table-row-cell">
|
<div
|
||||||
<span
|
class="table-row-cell"
|
||||||
@mouseover="onPopoverRefOver(scope, 'head')"
|
@mouseover="onPopoverRefOver(scope, 'head')"
|
||||||
:ref="(el) => (popoverRefMap[scope.column.id] = el)"
|
:ref="(el) => (popoverRefMap[scope.column.id] = el)"
|
||||||
>
|
>
|
||||||
|
<span>
|
||||||
{{ scope.column.label.replace(/ /g, '') }}
|
{{ scope.column.label.replace(/ /g, '') }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<template #default="scope">
|
<template #default="scope">
|
||||||
<div>
|
<div
|
||||||
<span
|
class="table-row-cell"
|
||||||
class="table-row-cell"
|
@mouseover="onPopoverRefOver(scope, 'content')"
|
||||||
@mouseover="onPopoverRefOver(scope, 'content')"
|
:ref="(el) => (popoverRefMap[scope.$index + scope.column.property] = el)"
|
||||||
:ref="(el) => (popoverRefMap[scope.$index + scope.column.property] = el)"
|
>
|
||||||
>
|
<span>
|
||||||
{{ getContent(scope.row[scope.column.property]) }}
|
{{ getContent(scope.row[scope.column.property]) }}
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
@ -44,6 +45,7 @@
|
|||||||
popper-style="text-align: center;"
|
popper-style="text-align: center;"
|
||||||
:virtual-ref="popoverVirtualRef"
|
:virtual-ref="popoverVirtualRef"
|
||||||
placement="top"
|
placement="top"
|
||||||
|
width="400"
|
||||||
trigger="hover"
|
trigger="hover"
|
||||||
virtual-triggering
|
virtual-triggering
|
||||||
:content="popoverContent"
|
:content="popoverContent"
|
||||||
@ -62,6 +64,10 @@ const props = defineProps({
|
|||||||
},
|
},
|
||||||
mainTableLoading: {
|
mainTableLoading: {
|
||||||
type: Boolean
|
type: Boolean
|
||||||
|
},
|
||||||
|
tableMinHeight: {
|
||||||
|
type: String,
|
||||||
|
default: '620px'
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
const popoverRefMap = ref({})
|
const popoverRefMap = ref({})
|
||||||
@ -94,15 +100,18 @@ const onPopoverRefOver = (scope, type) => {
|
|||||||
position: relative;
|
position: relative;
|
||||||
width: 100%;
|
width: 100%;
|
||||||
padding-bottom: 20px;
|
padding-bottom: 20px;
|
||||||
min-height: 620px;
|
min-height: v-bind('tableMinHeight');
|
||||||
background: #fff;
|
background: #fff;
|
||||||
padding: 10px 20px;
|
padding: 10px 20px;
|
||||||
|
|
||||||
.table-border {
|
.table-border {
|
||||||
box-sizing: border-box;
|
box-sizing: border-box;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.el-table__header) {
|
:deep(.el-table__header) {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
|
|
||||||
.thead-cell .el-table__cell {
|
.thead-cell .el-table__cell {
|
||||||
.cell {
|
.cell {
|
||||||
height: 24px;
|
height: 24px;
|
||||||
@ -111,10 +120,16 @@ const onPopoverRefOver = (scope, type) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.table-row-cell {
|
.table-row-cell {
|
||||||
white-space: nowrap; /* 禁止自动换行 */
|
max-width: 100%;
|
||||||
overflow: hidden; /* 超出部分隐藏 */
|
display: inline-block;
|
||||||
text-overflow: ellipsis; /* 显示省略号 */
|
white-space: nowrap;
|
||||||
|
/* 禁止自动换行 */
|
||||||
|
overflow: hidden;
|
||||||
|
/* 超出部分隐藏 */
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
/* 显示省略号 */
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
242
web/src/management/pages/analysis/components/StatisticsItem.vue
Normal file
242
web/src/management/pages/analysis/components/StatisticsItem.vue
Normal file
@ -0,0 +1,242 @@
|
|||||||
|
<template>
|
||||||
|
<div class="separate-item">
|
||||||
|
<div class="separate-item-title">
|
||||||
|
<el-popover
|
||||||
|
placement="top"
|
||||||
|
width="400"
|
||||||
|
trigger="hover"
|
||||||
|
:disabled="!titlePoppverShow"
|
||||||
|
:content="cleanRichText(StatisticsData.title)"
|
||||||
|
>
|
||||||
|
<template #reference>
|
||||||
|
<p ref="titleRef" class="text" v-html="cleanRichText(StatisticsData.title)"></p>
|
||||||
|
</template>
|
||||||
|
</el-popover>
|
||||||
|
<p v-if="questionTypeDesc" class="type">{{ questionTypeDesc }}</p>
|
||||||
|
</div>
|
||||||
|
<div class="separate-item-content">
|
||||||
|
<div class="chart-wrapper">
|
||||||
|
<div ref="chartRef" class="chart"></div>
|
||||||
|
<div v-if="chartTypeList.length > 1" class="chart-type-list">
|
||||||
|
<el-segmented v-model="chartType" :options="chartTypeList" size="small">
|
||||||
|
<template #default="{ item }">
|
||||||
|
<i class="iconfont" :class="`icon-${item}`"></i>
|
||||||
|
</template>
|
||||||
|
</el-segmented>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="table-wrapper">
|
||||||
|
<data-table :table-data :table-min-height />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { reactive, toRefs, defineProps, computed, watch, onMounted, onUnmounted, ref } from 'vue'
|
||||||
|
import _cloneDeep from 'lodash/cloneDeep'
|
||||||
|
import {
|
||||||
|
separateItemListHead,
|
||||||
|
summaryType,
|
||||||
|
summaryItemConfig
|
||||||
|
} from '@/management/config/analysisConfig'
|
||||||
|
import useCharts from '@/management/hooks/useCharts'
|
||||||
|
import useStatisticsItemChart from '@/management/hooks/useStatisticsItemChart'
|
||||||
|
import { cleanRichText } from '@/common/xss'
|
||||||
|
import { menuItems } from '@/management/config/questionMenuConfig'
|
||||||
|
import DataTable from './DataTable.vue'
|
||||||
|
import useResizeObserver from '@/management/hooks/useResizeObserver'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
StatisticsData: {
|
||||||
|
type: Object,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const questionType = computed(() => {
|
||||||
|
return props?.StatisticsData?.type
|
||||||
|
})
|
||||||
|
|
||||||
|
const questionTypeDesc = computed(() => {
|
||||||
|
return menuItems?.[questionType.value]?.title || ''
|
||||||
|
})
|
||||||
|
|
||||||
|
// 表格数据
|
||||||
|
const separateItemListBody = computed(() => {
|
||||||
|
try {
|
||||||
|
const aggregation = _cloneDeep(props?.StatisticsData?.data?.aggregation)
|
||||||
|
const submitionCount = props?.StatisticsData?.data?.submitionCount
|
||||||
|
const summaryList = summaryItemConfig[questionType.value]
|
||||||
|
// 增加聚合信息
|
||||||
|
if (summaryList?.length) {
|
||||||
|
summaryList.forEach((item, index) => {
|
||||||
|
const { type, text, field, max, min } = item
|
||||||
|
if (text && field && type === summaryType.between) {
|
||||||
|
aggregation.push({
|
||||||
|
id: `summary_${index}`,
|
||||||
|
text,
|
||||||
|
count: aggregation.reduce((n, item) => {
|
||||||
|
if (item[field] >= min && item[field] <= max) {
|
||||||
|
return n + item.count
|
||||||
|
}
|
||||||
|
return n
|
||||||
|
}, 0)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
aggregation?.map((item) => {
|
||||||
|
const { id, count, text } = item
|
||||||
|
const percent = submitionCount ? `${((count / submitionCount) * 100).toFixed(1)}%` : '0%'
|
||||||
|
return {
|
||||||
|
id,
|
||||||
|
count,
|
||||||
|
text,
|
||||||
|
percent
|
||||||
|
}
|
||||||
|
}) || []
|
||||||
|
)
|
||||||
|
} catch (e) {
|
||||||
|
console.log(e)
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const separateItemState = reactive({
|
||||||
|
tableData: {
|
||||||
|
total: 0,
|
||||||
|
listHead: separateItemListHead,
|
||||||
|
listBody: separateItemListBody
|
||||||
|
},
|
||||||
|
tableMinHeight: '0px'
|
||||||
|
})
|
||||||
|
|
||||||
|
const { tableData, tableMinHeight } = toRefs(separateItemState)
|
||||||
|
|
||||||
|
const titlePoppverShow = ref(false)
|
||||||
|
const titleRef = ref(null)
|
||||||
|
|
||||||
|
const titleResize = () => {
|
||||||
|
if (titleRef.value?.scrollWidth > titleRef.value?.offsetWidth) {
|
||||||
|
titlePoppverShow.value = true
|
||||||
|
} else {
|
||||||
|
titlePoppverShow.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const { chartRef, chartTypeList, chartType, chartData } = useStatisticsItemChart({
|
||||||
|
questionType,
|
||||||
|
data: props?.StatisticsData?.data
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
// 需要获取图表dom,所以得在mounted中执行
|
||||||
|
const { changeType, resize: chartResize } = useCharts(
|
||||||
|
chartRef.value,
|
||||||
|
chartType.value,
|
||||||
|
chartData.value
|
||||||
|
)
|
||||||
|
|
||||||
|
const { destroy } = useResizeObserver(chartRef.value, () => {
|
||||||
|
chartResize()
|
||||||
|
titleResize()
|
||||||
|
})
|
||||||
|
|
||||||
|
// 图型切换
|
||||||
|
watch(chartType, () => {
|
||||||
|
changeType(chartType.value, chartData.value)
|
||||||
|
})
|
||||||
|
|
||||||
|
// 销毁resizeObserver
|
||||||
|
onUnmounted(destroy)
|
||||||
|
})
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.separate-item {
|
||||||
|
padding: 32px 12px;
|
||||||
|
border-bottom: 1px solid #efefef;
|
||||||
|
|
||||||
|
&:nth-last-of-type(1) {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-title {
|
||||||
|
font-size: 16px;
|
||||||
|
color: #333;
|
||||||
|
font-weight: 600;
|
||||||
|
margin-bottom: 24px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
|
||||||
|
.text {
|
||||||
|
max-width: 50%;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
white-space: nowrap;
|
||||||
|
}
|
||||||
|
|
||||||
|
.type {
|
||||||
|
font-size: 12px;
|
||||||
|
margin-left: 8px;
|
||||||
|
color: white;
|
||||||
|
background-color: var(--primary-color);
|
||||||
|
border-radius: 7px 3px;
|
||||||
|
padding: 2px 6px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&-content {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 50px;
|
||||||
|
|
||||||
|
.chart-wrapper {
|
||||||
|
position: relative;
|
||||||
|
flex: auto;
|
||||||
|
width: 50%;
|
||||||
|
min-width: 300px;
|
||||||
|
height: 320px;
|
||||||
|
max-width: 1000px;
|
||||||
|
box-shadow: 0 2px 8px -2px rgba(136, 136, 157, 0.2);
|
||||||
|
border-radius: 2px;
|
||||||
|
padding: 24px;
|
||||||
|
|
||||||
|
.chart-type-list {
|
||||||
|
position: absolute;
|
||||||
|
left: 0;
|
||||||
|
top: 0;
|
||||||
|
|
||||||
|
.iconfont {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.chart {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.table-wrapper {
|
||||||
|
flex: auto;
|
||||||
|
width: 50%;
|
||||||
|
min-width: 300px;
|
||||||
|
max-width: 1000px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@media screen and (min-width: 1660px) {
|
||||||
|
&-content {
|
||||||
|
gap: 80px;
|
||||||
|
|
||||||
|
.chart-wrapper {
|
||||||
|
height: 400px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
142
web/src/management/pages/analysis/pages/DataTablePage.vue
Normal file
142
web/src/management/pages/analysis/pages/DataTablePage.vue
Normal file
@ -0,0 +1,142 @@
|
|||||||
|
<template>
|
||||||
|
<div class="data-table-page">
|
||||||
|
<template v-if="tableData.total">
|
||||||
|
<div class="menus">
|
||||||
|
<el-switch
|
||||||
|
:model-value="isShowOriginData"
|
||||||
|
active-text="是否展示原数据"
|
||||||
|
@input="onIsShowOriginChange"
|
||||||
|
>
|
||||||
|
</el-switch>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template v-if="tableData.total">
|
||||||
|
<DataTable :main-table-loading :table-data />
|
||||||
|
<el-pagination
|
||||||
|
background
|
||||||
|
layout="prev, pager, next"
|
||||||
|
popper-class="analysis-pagination"
|
||||||
|
:total="tableData.total"
|
||||||
|
@current-change="handleCurrentChange"
|
||||||
|
>
|
||||||
|
</el-pagination>
|
||||||
|
</template>
|
||||||
|
<div v-else>
|
||||||
|
<EmptyIndex :data="noDataConfig" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { reactive, toRefs } from 'vue'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import 'element-plus/theme-chalk/src/message.scss'
|
||||||
|
import EmptyIndex from '@/management/components/EmptyIndex.vue'
|
||||||
|
import { getRecycleList } from '@/management/api/analysis'
|
||||||
|
import { noDataConfig } from '@/management/config/analysisConfig'
|
||||||
|
import DataTable from '../components/DataTable.vue'
|
||||||
|
|
||||||
|
const dataTableState = reactive({
|
||||||
|
mainTableLoading: false,
|
||||||
|
tableData: {
|
||||||
|
total: 0,
|
||||||
|
listHead: [],
|
||||||
|
listBody: []
|
||||||
|
},
|
||||||
|
currentPage: 1,
|
||||||
|
isShowOriginData: false,
|
||||||
|
tmpIsShowOriginData: false
|
||||||
|
})
|
||||||
|
|
||||||
|
const { mainTableLoading, tableData, isShowOriginData } = toRefs(dataTableState)
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
|
||||||
|
const formatHead = (listHead) => {
|
||||||
|
const head = []
|
||||||
|
|
||||||
|
listHead.forEach((headItem) => {
|
||||||
|
head.push({
|
||||||
|
field: headItem.field,
|
||||||
|
title: headItem.title
|
||||||
|
})
|
||||||
|
|
||||||
|
if (headItem.othersCode?.length) {
|
||||||
|
headItem.othersCode.forEach((item) => {
|
||||||
|
head.push({
|
||||||
|
field: item.code,
|
||||||
|
title: `${headItem.title}-${item.option}`
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
return head
|
||||||
|
}
|
||||||
|
|
||||||
|
const onIsShowOriginChange = async (data) => {
|
||||||
|
if (dataTableState.mainTableLoading) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dataTableState.tmpIsShowOriginData = data
|
||||||
|
await init()
|
||||||
|
dataTableState.isShowOriginData = data
|
||||||
|
}
|
||||||
|
|
||||||
|
const handleCurrentChange = async (page) => {
|
||||||
|
if (dataTableState.mainTableLoading) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dataTableState.currentPage = page
|
||||||
|
await init()
|
||||||
|
}
|
||||||
|
|
||||||
|
const init = async () => {
|
||||||
|
if (!route.params.id) {
|
||||||
|
ElMessage.error('没有传入问卷参数~')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
dataTableState.mainTableLoading = true
|
||||||
|
try {
|
||||||
|
const res = await getRecycleList({
|
||||||
|
page: dataTableState.currentPage,
|
||||||
|
surveyId: route.params.id,
|
||||||
|
isDesensitive: !dataTableState.tmpIsShowOriginData // 发起请求的时候,isShowOriginData还没改变,暂存了一个字段
|
||||||
|
})
|
||||||
|
|
||||||
|
if (res.code === 200) {
|
||||||
|
const listHead = formatHead(res.data.listHead)
|
||||||
|
dataTableState.tableData = { ...res.data, listHead }
|
||||||
|
dataTableState.mainTableLoading = false
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
ElMessage.error('查询回收数据失败,请重试')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
init()
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.data-table-page {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.menus {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.el-pagination) {
|
||||||
|
margin-top: 20px;
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.data-list {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
</style>
|
@ -0,0 +1,49 @@
|
|||||||
|
<template>
|
||||||
|
<div v-if="data.length" class="separate-statis-page">
|
||||||
|
<StatisticsItem v-for="StatisticsData in data" :key="StatisticsData.field" :StatisticsData />
|
||||||
|
</div>
|
||||||
|
<div v-else>
|
||||||
|
<EmptyIndex :data="noDataConfig" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup>
|
||||||
|
import { ref, onMounted } from 'vue'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import 'element-plus/theme-chalk/src/message.scss'
|
||||||
|
import { noDataConfig } from '@/management/config/analysisConfig'
|
||||||
|
import EmptyIndex from '@/management/components/EmptyIndex.vue'
|
||||||
|
import { getStatisticList } from '@/management/api/analysis'
|
||||||
|
import StatisticsItem from '../components/StatisticsItem.vue'
|
||||||
|
|
||||||
|
const route = useRoute()
|
||||||
|
|
||||||
|
const data = ref([])
|
||||||
|
|
||||||
|
const initData = async () => {
|
||||||
|
try {
|
||||||
|
const res = await getStatisticList({
|
||||||
|
surveyId: route.params.id
|
||||||
|
})
|
||||||
|
if (res.code === 200) {
|
||||||
|
data.value = res?.data || []
|
||||||
|
} else {
|
||||||
|
ElMessage.error(res?.errmsg)
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
ElMessage.error(error?.message || '查询回收数据失败,请重试')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
onMounted(initData)
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="scss" scoped>
|
||||||
|
.separate-statis-page {
|
||||||
|
height: 100%;
|
||||||
|
background: #fff;
|
||||||
|
padding: 0 24px;
|
||||||
|
overflow-y: auto;
|
||||||
|
}
|
||||||
|
</style>
|
@ -139,7 +139,7 @@ import {
|
|||||||
noSearchDataConfig,
|
noSearchDataConfig,
|
||||||
selectOptionsDict,
|
selectOptionsDict,
|
||||||
buttonOptionsDict
|
buttonOptionsDict
|
||||||
} from '../config'
|
} from '@/management/config/listConfig'
|
||||||
|
|
||||||
const store = useStore()
|
const store = useStore()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
@ -75,7 +75,7 @@ import { useStore } from 'vuex'
|
|||||||
import { ElMessageBox } from 'element-plus'
|
import { ElMessageBox } from 'element-plus'
|
||||||
import 'element-plus/theme-chalk/src/message-box.scss'
|
import 'element-plus/theme-chalk/src/message-box.scss'
|
||||||
import { get, map } from 'lodash-es'
|
import { get, map } from 'lodash-es'
|
||||||
import { spaceListConfig } from '../config'
|
import { spaceListConfig } from '@/management/config/listConfig'
|
||||||
import SpaceModify from './SpaceModify.vue'
|
import SpaceModify from './SpaceModify.vue'
|
||||||
import { UserRole } from '@/management/utils/types/workSpace'
|
import { UserRole } from '@/management/utils/types/workSpace'
|
||||||
|
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { statusMaps } from '../config'
|
import { statusMaps } from '@/management/config/listConfig'
|
||||||
export default {
|
export default {
|
||||||
name: 'StateModule',
|
name: 'StateModule',
|
||||||
props: {
|
props: {
|
||||||
|
@ -6,7 +6,7 @@
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { type as surveyType } from '../config'
|
import { type as surveyType } from '@/management/config/listConfig'
|
||||||
export default {
|
export default {
|
||||||
name: 'TagModule',
|
name: 'TagModule',
|
||||||
props: {
|
props: {
|
||||||
|
@ -2,6 +2,7 @@ import { createRouter, createWebHistory, type RouteLocationNormalized, type Navi
|
|||||||
import type { RouteRecordRaw } from 'vue-router'
|
import type { RouteRecordRaw } from 'vue-router'
|
||||||
import { useStore, type Store } from 'vuex'
|
import { useStore, type Store } from 'vuex'
|
||||||
import { SurveyPermissions } from '@/management/utils/types/workSpace'
|
import { SurveyPermissions } from '@/management/utils/types/workSpace'
|
||||||
|
import { analysisTypeMap } from '@/management/config/analysisConfig'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import 'element-plus/theme-chalk/src/message.scss'
|
import 'element-plus/theme-chalk/src/message.scss'
|
||||||
|
|
||||||
@ -92,11 +93,34 @@ const routes: RouteRecordRaw[] = [
|
|||||||
{
|
{
|
||||||
path: '/survey/:id/analysis',
|
path: '/survey/:id/analysis',
|
||||||
name: 'analysisPage',
|
name: 'analysisPage',
|
||||||
|
redirect: {
|
||||||
|
name: analysisTypeMap.dataTable
|
||||||
|
},
|
||||||
meta: {
|
meta: {
|
||||||
needLogin: true,
|
needLogin: true,
|
||||||
permissions: [SurveyPermissions.DataManage]
|
permissions: [SurveyPermissions.DataManage]
|
||||||
},
|
},
|
||||||
component: () => import('../pages/analysis/AnalysisPage.vue')
|
component: () => import('../pages/analysis/AnalysisPage.vue'),
|
||||||
|
children: [
|
||||||
|
{
|
||||||
|
path: analysisTypeMap.dataTable,
|
||||||
|
name: analysisTypeMap.dataTable,
|
||||||
|
meta: {
|
||||||
|
needLogin: true,
|
||||||
|
premissions: [SurveyPermissions.DataManage]
|
||||||
|
},
|
||||||
|
component: () => import('../pages/analysis/pages/DataTablePage.vue')
|
||||||
|
},
|
||||||
|
{
|
||||||
|
path: analysisTypeMap.separateStatistics,
|
||||||
|
name: analysisTypeMap.separateStatistics,
|
||||||
|
meta: {
|
||||||
|
needLogin: true,
|
||||||
|
premissions: [SurveyPermissions.DataManage]
|
||||||
|
},
|
||||||
|
component: () => import('../pages/analysis/pages/SeparateStatisticsPage.vue')
|
||||||
|
}
|
||||||
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: '/survey/:id/publish',
|
path: '/survey/:id/publish',
|
||||||
|
@ -1,9 +1,10 @@
|
|||||||
@font-face {
|
@font-face {
|
||||||
font-family: 'iconfont'; /* Project id 4263849 */
|
font-family: 'iconfont';
|
||||||
|
/* Project id 4263849 */
|
||||||
src:
|
src:
|
||||||
url('//at.alicdn.com/t/c/font_4263849_xfsn9z31epc.woff2?t=1716556097756') format('woff2'),
|
url('//at.alicdn.com/t/c/font_4263849_tj4thybmhq.woff2?t=1717580126029') format('woff2'),
|
||||||
url('//at.alicdn.com/t/c/font_4263849_xfsn9z31epc.woff?t=1716556097756') format('woff'),
|
url('//at.alicdn.com/t/c/font_4263849_tj4thybmhq.woff?t=1717580126029') format('woff'),
|
||||||
url('//at.alicdn.com/t/c/font_4263849_xfsn9z31epc.ttf?t=1716556097756') format('truetype');
|
url('//at.alicdn.com/t/c/font_4263849_tj4thybmhq.ttf?t=1717580126029') format('truetype');
|
||||||
}
|
}
|
||||||
|
|
||||||
.iconfont {
|
.iconfont {
|
||||||
@ -125,15 +126,39 @@
|
|||||||
.icon-erweima:before {
|
.icon-erweima:before {
|
||||||
content: '\e6c0';
|
content: '\e6c0';
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-yangshishezhi:before {
|
.icon-yangshishezhi:before {
|
||||||
content: '\e6e6';
|
content: '\e6e6';
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-NPSpingfen::before {
|
.icon-NPSpingfen::before {
|
||||||
content: '\e6e7';
|
content: '\e6e7';
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-wodekongjian::before {
|
.icon-wodekongjian::before {
|
||||||
content: '\e6ee';
|
content: '\e6ee';
|
||||||
}
|
}
|
||||||
|
|
||||||
.icon-tuanduikongjian::before {
|
.icon-tuanduikongjian::before {
|
||||||
content: '\e6ec';
|
content: '\e6ec';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.icon-shujuliebiao:before {
|
||||||
|
content: '\e6f2';
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-fentitongji:before {
|
||||||
|
content: '\e6f3';
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-bar:before {
|
||||||
|
content: '\e600';
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-pie:before {
|
||||||
|
content: '\e606';
|
||||||
|
}
|
||||||
|
|
||||||
|
.icon-gauge:before {
|
||||||
|
content: '\e6db';
|
||||||
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user