|
@@ -1,15 +1,16 @@
|
|
|
-import { Ref, computed, defineComponent, onMounted, reactive, ref } from 'vue';
|
|
|
+import { Ref, computed, defineComponent, reactive, ref } from 'vue';
|
|
|
import styles from '../index2.module.less';
|
|
|
-import { NButton, NDataTable, NNumberAnimation } from 'naive-ui';
|
|
|
-import numeral from 'numeral';
|
|
|
+import { NButton, NDataTable, NNumberAnimation, NTooltip, useMessage } from 'naive-ui';
|
|
|
import { useECharts } from '@/hooks/web/useECharts';
|
|
|
-import Pagination from '/src/components/pagination';
|
|
|
-import { getTestStat } from '@/views/data-module/api';
|
|
|
-import { getMinutes, getSecend, getTimes } from '/src/utils/dateFormat';
|
|
|
-import { useRoute, useRouter } from 'vue-router';
|
|
|
-import { getTrainingStatList } from '../../classList/api';
|
|
|
-import dayjs from 'dayjs';
|
|
|
+// import Pagination from '/src/components/pagination';
|
|
|
+import { getPracticePageStat, getTestStat } from '@/views/data-module/api';
|
|
|
+import { formateSeconds, getHours, getMinutes, getSecend, getTimes } from '/src/utils/dateFormat';
|
|
|
+import { api_practiceStatPage } from '../../classList/api';
|
|
|
import TheEmpty from '/src/components/TheEmpty';
|
|
|
+import iconSortDefault from '@/common/images/icon-sort-default.png';
|
|
|
+import iconSortDesc from '@/common/images/icon-sort-desc.png';
|
|
|
+import iconSortAsc from '@/common/images/icon-sort-asc.png';
|
|
|
+import { convertToChineseNumeral } from '/src/utils';
|
|
|
export default defineComponent({
|
|
|
name: 'home-practiceData',
|
|
|
props: {
|
|
@@ -19,12 +20,18 @@ export default defineComponent({
|
|
|
}
|
|
|
},
|
|
|
setup(props, { expose }) {
|
|
|
+ const message = useMessage()
|
|
|
const chartRef = ref<HTMLDivElement | null>(null);
|
|
|
const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>);
|
|
|
const practiceFlag = ref(true);
|
|
|
const payForm = reactive({
|
|
|
height: '360px',
|
|
|
width: '100%',
|
|
|
+ practiceDuration: 0,
|
|
|
+ evaluateUserCount: 0,
|
|
|
+ evaluateFrequency: 0,
|
|
|
+ publishUserCount: 0,
|
|
|
+ publishCount: 0,
|
|
|
practiceUserCount: 0,
|
|
|
paymentAmount: 0,
|
|
|
practiceDurationAvg: 0,
|
|
@@ -41,54 +48,274 @@ export default defineComponent({
|
|
|
rows: 10,
|
|
|
pageTotal: 4
|
|
|
},
|
|
|
+ searchForm: {
|
|
|
+ orderBy: null as any,
|
|
|
+ sort: null as any,
|
|
|
+ },
|
|
|
tableList: [] as any,
|
|
|
goCourseVisiable: false
|
|
|
});
|
|
|
const currentTimer = computed(() => {
|
|
|
return props.timer;
|
|
|
});
|
|
|
+
|
|
|
+ const toolTitleTips = (title: string, item: any) => {
|
|
|
+ return <NTooltip showArrow={false} placement="top-start">
|
|
|
+ {{
|
|
|
+ trigger: () => (
|
|
|
+ <div class={styles.cell}>
|
|
|
+ {title}
|
|
|
+ <img
|
|
|
+ class={styles.sortIcon}
|
|
|
+ src={
|
|
|
+ item.sortOrder === 'descend'
|
|
|
+ ? iconSortDesc
|
|
|
+ : item.sortOrder === 'ascend'
|
|
|
+ ? iconSortAsc
|
|
|
+ : iconSortDefault
|
|
|
+ }
|
|
|
+ />
|
|
|
+ </div>
|
|
|
+ ),
|
|
|
+ default:
|
|
|
+ item.sortOrder === 'descend'
|
|
|
+ ? '点击升序'
|
|
|
+ : item.sortOrder === 'ascend'
|
|
|
+ ? '取消排序'
|
|
|
+ : '点击降序'
|
|
|
+ }}
|
|
|
+ </NTooltip>
|
|
|
+ }
|
|
|
+
|
|
|
+ const practiceDurationRef = reactive({
|
|
|
+ title() {
|
|
|
+ return (
|
|
|
+ toolTitleTips('练习总时长', practiceDurationRef)
|
|
|
+ );
|
|
|
+ },
|
|
|
+ key: 'practiceDuration',
|
|
|
+ sorter: true,
|
|
|
+ sortOrder: false as any,
|
|
|
+ render(row: any) {
|
|
|
+ return <>{formateSeconds((row.practiceDuration as any) || 0)}</>
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ const practiceDaysRef = reactive({
|
|
|
+ title() {
|
|
|
+ return (
|
|
|
+ toolTitleTips('练习天数', practiceDaysRef)
|
|
|
+ );
|
|
|
+ },
|
|
|
+ key: 'practiceDays',
|
|
|
+ sorter: true,
|
|
|
+ sortOrder: false as any
|
|
|
+ });
|
|
|
+
|
|
|
+ const practiceDurationAvgRef = reactive({
|
|
|
+ title() {
|
|
|
+ return (
|
|
|
+ toolTitleTips('平均练习时长', practiceDurationAvgRef)
|
|
|
+ );
|
|
|
+ },
|
|
|
+ key: 'practiceDurationAvg',
|
|
|
+ sorter: true,
|
|
|
+ sortOrder: false as any,
|
|
|
+ render(row: any) {
|
|
|
+ return <>{formateSeconds((row.practiceDurationAvg as any) || 0)}</>
|
|
|
+ }
|
|
|
+ });
|
|
|
+
|
|
|
+ const evaluateFrequencyRef = reactive({
|
|
|
+ title() {
|
|
|
+ return (
|
|
|
+ toolTitleTips('评测次数', evaluateFrequencyRef)
|
|
|
+ );
|
|
|
+ },
|
|
|
+ key: 'evaluateFrequency',
|
|
|
+ sorter: true,
|
|
|
+ sortOrder: false as any
|
|
|
+ });
|
|
|
+
|
|
|
+ const publishCountRef = reactive({
|
|
|
+ title() {
|
|
|
+ return (
|
|
|
+ toolTitleTips('作品数量', publishCountRef)
|
|
|
+ );
|
|
|
+ },
|
|
|
+ key: 'publishCount',
|
|
|
+ sorter: true,
|
|
|
+ sortOrder: false as any
|
|
|
+ });
|
|
|
+
|
|
|
+ const publishScoreRef = reactive({
|
|
|
+ title() {
|
|
|
+ return (
|
|
|
+ toolTitleTips('最新作品分数', publishScoreRef)
|
|
|
+ );
|
|
|
+ },
|
|
|
+ key: 'publishScore',
|
|
|
+ sorter: true,
|
|
|
+ sortOrder: false as any
|
|
|
+ });
|
|
|
+
|
|
|
+ const publishTimeRef = reactive({
|
|
|
+ title() {
|
|
|
+ return (
|
|
|
+ toolTitleTips('最新作品时间', publishTimeRef)
|
|
|
+ );
|
|
|
+ },
|
|
|
+ key: 'publishTime',
|
|
|
+ sorter: true,
|
|
|
+ sortOrder: false as any
|
|
|
+ });
|
|
|
+
|
|
|
+ const copyTo = (text: string) => {
|
|
|
+ const input = document.createElement('input');
|
|
|
+ input.value = text;
|
|
|
+ document.body.appendChild(input);
|
|
|
+ input.select();
|
|
|
+ input.setSelectionRange(0, input.value.length);
|
|
|
+ document.execCommand('Copy');
|
|
|
+ document.body.removeChild(input);
|
|
|
+ message.success('复制成功');
|
|
|
+ };
|
|
|
const columns = () => {
|
|
|
return [
|
|
|
{
|
|
|
- title: '日期',
|
|
|
- key: 'date'
|
|
|
- },
|
|
|
- {
|
|
|
- title: '练习人数',
|
|
|
- key: 'practiceUserCount',
|
|
|
- render(row: any) {
|
|
|
- return <>{row.practiceUserCount}人</>;
|
|
|
+ title: '学生姓名',
|
|
|
+ key: 'studentName',
|
|
|
+ render: (row: any) => {
|
|
|
+ return (
|
|
|
+ <NTooltip showArrow={false} placement="top-start">
|
|
|
+ {{
|
|
|
+ trigger: () => (
|
|
|
+ <div
|
|
|
+ style={{ userSelect: 'all', cursor: 'pointer' }}
|
|
|
+ onClick={() => copyTo(row.studentName)}>
|
|
|
+ {row.studentName}
|
|
|
+ </div>
|
|
|
+ ),
|
|
|
+ default: '点击复制'
|
|
|
+ }}
|
|
|
+ </NTooltip>
|
|
|
+ );
|
|
|
}
|
|
|
},
|
|
|
{
|
|
|
- title: '平均每天练习时长',
|
|
|
- key: 'practiceDuration',
|
|
|
+ title: '年级班级',
|
|
|
+ key: 'date',
|
|
|
render(row: any) {
|
|
|
return (
|
|
|
<>
|
|
|
- {' '}
|
|
|
- <>
|
|
|
- {row.practiceDuration
|
|
|
- ? getMinutes(row.practiceDuration) > 0
|
|
|
- ? getMinutes(row.practiceDuration) +
|
|
|
- '分' +
|
|
|
- getSecend(row.practiceDuration) +
|
|
|
- '秒'
|
|
|
- : getSecend(row.practiceDuration) + '秒'
|
|
|
- : 0 + '分钟'}
|
|
|
- </>
|
|
|
+ {row.currentGradeNum && row.currentClass
|
|
|
+ ? convertToChineseNumeral(row.currentGradeNum) + '年级' + row.currentClass + '班'
|
|
|
+ : ''}
|
|
|
</>
|
|
|
- );
|
|
|
+ )
|
|
|
}
|
|
|
+ },
|
|
|
+ {
|
|
|
+ title: '乐器',
|
|
|
+ key: 'instrumentName'
|
|
|
+ },
|
|
|
+ practiceDurationRef,
|
|
|
+ practiceDaysRef,
|
|
|
+ practiceDurationAvgRef,
|
|
|
+ evaluateFrequencyRef,
|
|
|
+ {
|
|
|
+ title: '发布作品',
|
|
|
+ key: 'publishFlag',
|
|
|
+ render: (row: any) => row.publishFlag ? '是' : '否'
|
|
|
+ },
|
|
|
+ publishCountRef,
|
|
|
+ publishScoreRef,
|
|
|
+ publishTimeRef,
|
|
|
+ {
|
|
|
+ title: '操作',
|
|
|
+ key: 'titleImg',
|
|
|
+ render: (row: any) => (
|
|
|
+ <NButton
|
|
|
+ type="primary"
|
|
|
+ text
|
|
|
+ onClick={() => {
|
|
|
+ // router.push({
|
|
|
+ // path: '/studentManageDetail',
|
|
|
+ // query: {
|
|
|
+ // id: row.studentId,
|
|
|
+ // name: row.studentName,
|
|
|
+ // timer: (state.searchForm.times)
|
|
|
+ // }
|
|
|
+ // })
|
|
|
+ }}
|
|
|
+ >
|
|
|
+ 详情
|
|
|
+ </NButton>
|
|
|
+ )
|
|
|
}
|
|
|
];
|
|
|
};
|
|
|
- const getList = async () => {
|
|
|
+
|
|
|
+ // 统计排序
|
|
|
+ const handleSorterChange = (sorter: any) => {
|
|
|
+ if (!sorter.order) {
|
|
|
+ state.searchForm.orderBy = '' as string
|
|
|
+ state.searchForm.sort = '' as string
|
|
|
+ practiceDurationRef.sortOrder = false
|
|
|
+ practiceDaysRef.sortOrder = false
|
|
|
+ practiceDurationAvgRef.sortOrder = false
|
|
|
+ evaluateFrequencyRef.sortOrder = false
|
|
|
+ publishCountRef.sortOrder = false
|
|
|
+ publishScoreRef.sortOrder = false
|
|
|
+ publishTimeRef.sortOrder = false
|
|
|
+ } else {
|
|
|
+ state.searchForm.orderBy = sorter.columnKey
|
|
|
+ practiceDurationRef.sortOrder = false
|
|
|
+ practiceDaysRef.sortOrder = false
|
|
|
+ practiceDurationAvgRef.sortOrder = false
|
|
|
+ evaluateFrequencyRef.sortOrder = false
|
|
|
+ publishCountRef.sortOrder = false
|
|
|
+ publishScoreRef.sortOrder = false
|
|
|
+ publishTimeRef.sortOrder = false
|
|
|
+ if (sorter.columnKey == 'practiceDuration') {
|
|
|
+ practiceDurationRef.sortOrder = sorter.order
|
|
|
+ }
|
|
|
+ if (sorter.columnKey == 'practiceDays') {
|
|
|
+ practiceDaysRef.sortOrder = sorter.order
|
|
|
+ }
|
|
|
+
|
|
|
+ if (sorter.columnKey == 'practiceDurationAvg') {
|
|
|
+ practiceDurationAvgRef.sortOrder = sorter.order
|
|
|
+ }
|
|
|
+
|
|
|
+ if (sorter.columnKey == 'evaluateFrequency') {
|
|
|
+ evaluateFrequencyRef.sortOrder = sorter.order
|
|
|
+ }
|
|
|
+
|
|
|
+ if (sorter.columnKey == 'publishCount') {
|
|
|
+ publishCountRef.sortOrder = sorter.order
|
|
|
+ }
|
|
|
+
|
|
|
+ if (sorter.columnKey == 'publishScore') {
|
|
|
+ publishScoreRef.sortOrder = sorter.order
|
|
|
+ }
|
|
|
+
|
|
|
+ if (sorter.columnKey == 'publishTime') {
|
|
|
+ publishTimeRef.sortOrder = sorter.order
|
|
|
+ }
|
|
|
+
|
|
|
+ state.searchForm.sort = sorter.order == 'ascend' ? 'asc' : 'desc'
|
|
|
+ }
|
|
|
+ getList2()
|
|
|
+ }
|
|
|
+
|
|
|
+ const getList2 = async () => {
|
|
|
state.loading = true
|
|
|
try {
|
|
|
- const res = await getTrainingStatList({
|
|
|
+ const res = await api_practiceStatPage({
|
|
|
page: 1,
|
|
|
rows: 999,
|
|
|
+ ...state.searchForm,
|
|
|
...getTimes(
|
|
|
currentTimer.value,
|
|
|
['startTime', 'endTime'],
|
|
@@ -96,6 +323,16 @@ export default defineComponent({
|
|
|
)
|
|
|
});
|
|
|
|
|
|
+ state.tableList = res.data.rows;
|
|
|
+ } catch (e) {
|
|
|
+ console.log(e);
|
|
|
+ }
|
|
|
+ state.loading = false
|
|
|
+ };
|
|
|
+
|
|
|
+ const getTestStatList = async () => {
|
|
|
+ state.loading = true
|
|
|
+ try {
|
|
|
const res2 = await getTestStat({
|
|
|
page: 1,
|
|
|
rows: 999,
|
|
@@ -105,10 +342,7 @@ export default defineComponent({
|
|
|
'YYYY-MM-DD'
|
|
|
)
|
|
|
});
|
|
|
- state.tableList = res.data.rows;
|
|
|
|
|
|
- payForm.practiceDurationAvg = res2.data.practiceDurationAvg;
|
|
|
- payForm.practiceUserCount = res2.data.practiceUserCount;
|
|
|
payForm.dateList = res2.data.trainingStatDetailList.map((item: any) => {
|
|
|
return item.date;
|
|
|
});
|
|
@@ -118,11 +352,43 @@ export default defineComponent({
|
|
|
});
|
|
|
|
|
|
setChart();
|
|
|
- } catch (e) {
|
|
|
- console.log(e);
|
|
|
+ } catch {
|
|
|
+ //
|
|
|
}
|
|
|
state.loading = false
|
|
|
- };
|
|
|
+ }
|
|
|
+
|
|
|
+ const getPracticePageStatList = async () => {
|
|
|
+ state.loading = true
|
|
|
+ try {
|
|
|
+ const {data} = await getPracticePageStat({
|
|
|
+ page: 1,
|
|
|
+ rows: 999,
|
|
|
+ ...getTimes(
|
|
|
+ currentTimer.value,
|
|
|
+ ['startTime', 'endTime'],
|
|
|
+ 'YYYY-MM-DD'
|
|
|
+ )
|
|
|
+ });
|
|
|
+ payForm.practiceDuration = data.practiceDuration;
|
|
|
+ payForm.practiceDurationAvg = data.practiceDurationAvg;
|
|
|
+ payForm.practiceUserCount = data.practiceUserCount;
|
|
|
+ payForm.evaluateUserCount = data.evaluateUserCount
|
|
|
+ payForm.evaluateFrequency = data.evaluateFrequency
|
|
|
+ payForm.publishUserCount = data.publishUserCount
|
|
|
+ payForm.publishCount = data.publishCount
|
|
|
+ } catch {
|
|
|
+ //
|
|
|
+ }
|
|
|
+ state.loading = false
|
|
|
+ }
|
|
|
+
|
|
|
+ const getList = async () => {
|
|
|
+ await getPracticePageStatList()
|
|
|
+ await getTestStatList()
|
|
|
+ await getList2()
|
|
|
+ }
|
|
|
+
|
|
|
expose({ getList });
|
|
|
const setChart = () => {
|
|
|
setOptions({
|
|
@@ -176,33 +442,19 @@ export default defineComponent({
|
|
|
},
|
|
|
series: [
|
|
|
{
|
|
|
- // smooth: true,
|
|
|
data: payForm.timeList,
|
|
|
- symbolSize: 10,
|
|
|
- type: 'line',
|
|
|
- symbol: 'circle',
|
|
|
- smooth: true,
|
|
|
- // barWidth: '48px',
|
|
|
- // label: {
|
|
|
- // // 柱图头部显示值
|
|
|
- // show: true,
|
|
|
- // position: 'top',
|
|
|
- // color: '#333',
|
|
|
- // fontSize: '12px',
|
|
|
- // fontWeight: 600
|
|
|
- // },
|
|
|
+ type: 'bar',
|
|
|
+ barWidth: '48px',
|
|
|
|
|
|
itemStyle: {
|
|
|
normal: {
|
|
|
//这里设置柱形图圆角 [左上角,右上角,右下角,左下角]
|
|
|
barBorderRadius: [8, 8, 0, 0],
|
|
|
- color: '#3583FA'
|
|
|
+ color: '#CDE5FF'
|
|
|
},
|
|
|
emphasis: {
|
|
|
+ focus: 'series',
|
|
|
color: '#3583FA' //hover时改变柱子颜色
|
|
|
- // borderWidth: 4,
|
|
|
- // borderColor: 'rgba(213, 233, 255,.4)',
|
|
|
- // borderType: 'solid'
|
|
|
}
|
|
|
} as any
|
|
|
}
|
|
@@ -223,14 +475,6 @@ export default defineComponent({
|
|
|
return item;
|
|
|
}
|
|
|
}
|
|
|
- // dataZoom: [
|
|
|
- // {
|
|
|
- // type: 'slider',
|
|
|
- // start: 5,
|
|
|
- // end: 100,
|
|
|
- // filterMode: 'empty'
|
|
|
- // }
|
|
|
- // ]
|
|
|
});
|
|
|
};
|
|
|
|
|
@@ -256,6 +500,18 @@ export default defineComponent({
|
|
|
</div>
|
|
|
<div class={styles.TrainDataItem}>
|
|
|
<p class={styles.TrainDataItemTitle}>
|
|
|
+ {getHours(payForm.practiceDurationAvg) > 0 ? (
|
|
|
+ <div>
|
|
|
+ <span>
|
|
|
+ <NNumberAnimation
|
|
|
+ from={0}
|
|
|
+ to={getHours(
|
|
|
+ payForm.practiceDurationAvg
|
|
|
+ )}></NNumberAnimation>
|
|
|
+ </span>
|
|
|
+ 时
|
|
|
+ </div>
|
|
|
+ ) : null}
|
|
|
{getMinutes(payForm.practiceDurationAvg) > 0 ? (
|
|
|
<div>
|
|
|
<span>
|
|
@@ -281,6 +537,78 @@ export default defineComponent({
|
|
|
</p>
|
|
|
<p class={styles.TrainDataItemsubTitle}>平均每天练习时长</p>
|
|
|
</div>
|
|
|
+
|
|
|
+ <div class={styles.TrainDataItem}>
|
|
|
+ <p class={styles.TrainDataItemTitle}>
|
|
|
+ {getHours(payForm.practiceDuration) > 0 ? (
|
|
|
+ <div>
|
|
|
+ <span>
|
|
|
+ <NNumberAnimation
|
|
|
+ from={0}
|
|
|
+ to={getHours(
|
|
|
+ payForm.practiceDuration
|
|
|
+ )}></NNumberAnimation>
|
|
|
+ </span>
|
|
|
+ 时
|
|
|
+ </div>
|
|
|
+ ) : null}
|
|
|
+ {getMinutes(payForm.practiceDuration) > 0 ? (
|
|
|
+ <div>
|
|
|
+ <span>
|
|
|
+ <NNumberAnimation
|
|
|
+ from={0}
|
|
|
+ to={getMinutes(
|
|
|
+ payForm.practiceDuration
|
|
|
+ )}></NNumberAnimation>
|
|
|
+ </span>
|
|
|
+ 分
|
|
|
+ </div>
|
|
|
+ ) : null}
|
|
|
+ <div>
|
|
|
+ <span>
|
|
|
+ <NNumberAnimation
|
|
|
+ from={0}
|
|
|
+ to={getSecend(
|
|
|
+ payForm.practiceDuration
|
|
|
+ )}></NNumberAnimation>
|
|
|
+ </span>
|
|
|
+ 秒
|
|
|
+ </div>
|
|
|
+ </p>
|
|
|
+ <p class={styles.TrainDataItemsubTitle}>练习总时长</p>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class={styles.TrainDataItem}>
|
|
|
+ <p class={styles.TrainDataItemTitle}>
|
|
|
+ <div>
|
|
|
+ <span>
|
|
|
+ <NNumberAnimation
|
|
|
+ from={0}
|
|
|
+ to={payForm.evaluateUserCount}></NNumberAnimation>/
|
|
|
+ <NNumberAnimation
|
|
|
+ from={0}
|
|
|
+ to={payForm.evaluateFrequency}></NNumberAnimation>
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ </p>
|
|
|
+ <p class={styles.TrainDataItemsubTitle}>评测人数/次数</p>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class={styles.TrainDataItem}>
|
|
|
+ <p class={styles.TrainDataItemTitle}>
|
|
|
+ <div>
|
|
|
+ <span>
|
|
|
+ <NNumberAnimation
|
|
|
+ from={0}
|
|
|
+ to={payForm.publishUserCount}></NNumberAnimation>/
|
|
|
+ <NNumberAnimation
|
|
|
+ from={0}
|
|
|
+ to={payForm.publishCount}></NNumberAnimation>
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ </p>
|
|
|
+ <p class={styles.TrainDataItemsubTitle}>作品人数/数量</p>
|
|
|
+ </div>
|
|
|
</div>
|
|
|
<div class={styles.TrainDataTopRight}>
|
|
|
{/* <div
|
|
@@ -306,7 +634,7 @@ export default defineComponent({
|
|
|
ref={chartRef}
|
|
|
style={{ height: payForm.height, width: payForm.width }}></div>
|
|
|
</div>
|
|
|
- <div class={styles.tableWrap}>
|
|
|
+ <div class={[styles.tableWrap, styles.noSort]}>
|
|
|
<NDataTable
|
|
|
v-slots={{
|
|
|
empty: () => <TheEmpty></TheEmpty>
|
|
@@ -314,6 +642,7 @@ export default defineComponent({
|
|
|
class={styles.classTable}
|
|
|
loading={state.loading}
|
|
|
columns={columns()}
|
|
|
+ onUpdate:sorter={handleSorterChange}
|
|
|
data={state.tableList}></NDataTable>
|
|
|
{/* <Pagination
|
|
|
v-model:page={state.pagination.page}
|