|
@@ -0,0 +1,396 @@
|
|
|
+import { computed, defineComponent, onBeforeUnmount, onMounted, reactive, ref } from 'vue'
|
|
|
+import styles from './index.module.less'
|
|
|
+import CircleProgress from './component/CircleProgress'
|
|
|
+import iconBackup from './image/icon-backup.png'
|
|
|
+import iconEnsemble from './image/icon-ensemble.png'
|
|
|
+
|
|
|
+import * as echarts from 'echarts'
|
|
|
+import { listenerMessage, postMessage, removeListenerMessage } from '@/helpers/native-message'
|
|
|
+
|
|
|
+type EChartsOption = echarts.EChartsOption
|
|
|
+interface ISubjectItem {
|
|
|
+ /** 声部名称 */
|
|
|
+ subjectName: string
|
|
|
+ /**达标率 */
|
|
|
+ practiceRate: number
|
|
|
+ /** 达标人数 */
|
|
|
+ passNum: number
|
|
|
+ /** 未达标人数 */
|
|
|
+ noPassNum: number
|
|
|
+ /** 非会员 */
|
|
|
+ noMemberNum: number
|
|
|
+}
|
|
|
+const symbolData: any = {
|
|
|
+ type: 'line',
|
|
|
+ symbol: 'circle',
|
|
|
+ symbolSize: 6,
|
|
|
+ triggerLineEvent: true
|
|
|
+}
|
|
|
+export default defineComponent({
|
|
|
+ name: 'subject-echarts',
|
|
|
+ setup() {
|
|
|
+ const activeData = reactive({
|
|
|
+ index: -1,
|
|
|
+ sum: {
|
|
|
+ /**达标率 */
|
|
|
+ practiceRate: 0,
|
|
|
+ /**达标人数 */
|
|
|
+ passNum: 0,
|
|
|
+ /**未达标人数 */
|
|
|
+ noPassNum: 0,
|
|
|
+ /**未会员 */
|
|
|
+ noMemberNum: 0
|
|
|
+ },
|
|
|
+ practiceThisWeeks: [] as ISubjectItem[],
|
|
|
+ colors: [
|
|
|
+ {
|
|
|
+ color: '#FF8057',
|
|
|
+ borderColor: 'rgba(255,128,87,0.5)',
|
|
|
+ text: '达标率',
|
|
|
+ select: true,
|
|
|
+ key: 'practiceRate'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ color: '#2FC58D',
|
|
|
+ borderColor: 'rgba(47,197,141,0.5)',
|
|
|
+ text: '达标',
|
|
|
+ select: true,
|
|
|
+ key: 'passNum'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ color: '#4A99FF',
|
|
|
+ borderColor: 'rgba(74,153,255,0.5)',
|
|
|
+ text: '未达标',
|
|
|
+ select: true,
|
|
|
+ key: 'noPassNum'
|
|
|
+ },
|
|
|
+ {
|
|
|
+ color: '#9884BA',
|
|
|
+ borderColor: 'rgba(152,132,186,0.5)',
|
|
|
+ text: '非会员',
|
|
|
+ select: true,
|
|
|
+ key: 'noMemberNum'
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ })
|
|
|
+ const subjects = computed(() => {
|
|
|
+ return activeData.practiceThisWeeks.map((n) => n.subjectName)
|
|
|
+ })
|
|
|
+ let myChart: echarts.ECharts
|
|
|
+ const handleInit = (data: any) => {
|
|
|
+ const { content } = data
|
|
|
+ if (!content) return
|
|
|
+ if (myChart) {
|
|
|
+ myChart.dispose()
|
|
|
+ }
|
|
|
+ activeData.sum = content.sum || {}
|
|
|
+ activeData.practiceThisWeeks = content.practiceThisWeeks || []
|
|
|
+ const chartDom = document.getElementById('subjectEcharts')!
|
|
|
+ myChart = echarts.init(chartDom)
|
|
|
+ const option: EChartsOption = {
|
|
|
+ tooltip: {
|
|
|
+ trigger: 'axis',
|
|
|
+ showContent: false,
|
|
|
+ axisPointer: {
|
|
|
+ type: 'line',
|
|
|
+ lineStyle: {
|
|
|
+ width: 30,
|
|
|
+ type: 'solid',
|
|
|
+ opacity: 0.2,
|
|
|
+ },
|
|
|
+ }
|
|
|
+ },
|
|
|
+ grid: {
|
|
|
+ left: 8,
|
|
|
+ top: 5,
|
|
|
+ right: 5,
|
|
|
+ bottom: 5,
|
|
|
+ containLabel: true
|
|
|
+ },
|
|
|
+ legend: {
|
|
|
+ type: 'plain',
|
|
|
+ align: 'auto',
|
|
|
+ itemGap: 10,
|
|
|
+ itemWidth: 6,
|
|
|
+ itemStyle: {
|
|
|
+ opacity: 0
|
|
|
+ },
|
|
|
+ lineStyle: {
|
|
|
+ width: 0,
|
|
|
+ opacity: 0
|
|
|
+ },
|
|
|
+ textStyle: {
|
|
|
+ opacity: 0
|
|
|
+ }
|
|
|
+ },
|
|
|
+ xAxis: {
|
|
|
+ type: 'category',
|
|
|
+ axisLine: {
|
|
|
+ show: false
|
|
|
+ },
|
|
|
+ axisLabel: {
|
|
|
+ interval: 0,
|
|
|
+ color: '#333',
|
|
|
+ fontSize: 9
|
|
|
+ },
|
|
|
+ axisTick: {
|
|
|
+ alignWithLabel: true,
|
|
|
+ show: false
|
|
|
+ },
|
|
|
+ // boundaryGap: false,
|
|
|
+ data: subjects.value
|
|
|
+ },
|
|
|
+ yAxis: [
|
|
|
+ {
|
|
|
+ type: 'value',
|
|
|
+ position: 'left',
|
|
|
+ axisLine: {
|
|
|
+ show: false,
|
|
|
+ lineStyle: {
|
|
|
+ color: '#999'
|
|
|
+ }
|
|
|
+ },
|
|
|
+ splitLine: {
|
|
|
+ show: true, // 是否显示y轴分割线
|
|
|
+ lineStyle: {
|
|
|
+ color: ['rgba(249, 234, 220, 1)'] // 分隔线颜色。
|
|
|
+ }
|
|
|
+ },
|
|
|
+ min: 0,
|
|
|
+ splitNumber: 5
|
|
|
+ },
|
|
|
+ {
|
|
|
+ type: 'value',
|
|
|
+ position: 'right',
|
|
|
+ splitLine: {
|
|
|
+ show: true, // 是否显示y轴分割线
|
|
|
+ lineStyle: {
|
|
|
+ color: ['rgba(249, 234, 220, 1)'] // 分隔线颜色。
|
|
|
+ }
|
|
|
+ },
|
|
|
+ axisLine: {
|
|
|
+ show: false,
|
|
|
+ lineStyle: {
|
|
|
+ color: '#999'
|
|
|
+ }
|
|
|
+ },
|
|
|
+ interval: 20,
|
|
|
+ min: 0,
|
|
|
+ max: 100,
|
|
|
+ splitNumber: 5,
|
|
|
+ axisLabel: {
|
|
|
+ formatter: function (value: number, index: number) {
|
|
|
+ return value + '%'
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+ ],
|
|
|
+ series: ['practiceRate', 'passNum', 'noPassNum', 'noMemberNum'].map(
|
|
|
+ (key: string, index: number) => {
|
|
|
+ return {
|
|
|
+ name: activeData.colors[index].text,
|
|
|
+ color: activeData.colors[index].color,
|
|
|
+ ...symbolData,
|
|
|
+ yAxisIndex: index === 0 ? 1 : 0,
|
|
|
+ data: activeData.practiceThisWeeks.map((n) => n[key])
|
|
|
+ }
|
|
|
+ }
|
|
|
+ )
|
|
|
+ }
|
|
|
+ let maxNum = Math.max(...(option.series as any[]).map((n) => n.data).flat())
|
|
|
+ let minNum = Math.min(...(option.series as any[]).map((n) => n.data).flat())
|
|
|
+ maxNum = Math.ceil(maxNum / 9.5) * 10
|
|
|
+ minNum = Math.floor(minNum / 12) * 10
|
|
|
+ ;(option.yAxis as any[])[0].interval = Math.ceil(Math.ceil(maxNum) / 5)
|
|
|
+ ;(option.yAxis as any[])[0].max = Math.ceil(Math.ceil(maxNum) / 5) * 5
|
|
|
+ option && myChart.setOption(option)
|
|
|
+ myChart.on('highlight', function (params: any) {
|
|
|
+ activeData.index = params.batch[0].dataIndex
|
|
|
+ })
|
|
|
+ }
|
|
|
+ const handleAction = (index: number) => {
|
|
|
+ activeData.index = index
|
|
|
+ myChart?.dispatchAction({
|
|
|
+ type: 'showTip',
|
|
|
+ seriesIndex: 0,
|
|
|
+ dataIndex: index
|
|
|
+ })
|
|
|
+ }
|
|
|
+ const changeLegend = (item: any) => {
|
|
|
+ myChart?.dispatchAction({
|
|
|
+ type: item.select ? 'legendUnSelect' : 'legendSelect',
|
|
|
+ // 图例名称
|
|
|
+ name: item.text
|
|
|
+ })
|
|
|
+ item.select = !item.select
|
|
|
+ }
|
|
|
+ const handleMock = () => {
|
|
|
+ // console.log('handleMock1232')
|
|
|
+ const resulst = {
|
|
|
+ sum: {
|
|
|
+ noMemberNum: Math.floor(Math.random() * 1000),
|
|
|
+ noPassNum: Math.floor(Math.random() * 1000),
|
|
|
+ passNum: Math.floor(Math.random() * 1000),
|
|
|
+ practiceRate: Math.ceil(Math.random() * 100)
|
|
|
+ },
|
|
|
+ practiceThisWeeks: new Array(Math.ceil(Math.random() * 9)).fill(1).map((n, i) => {
|
|
|
+ return {
|
|
|
+ /** 声部名称 */
|
|
|
+ subjectName: '声部' + (i + 1),
|
|
|
+ /**达标率 */
|
|
|
+ practiceRate: Math.ceil(Math.random() * 100),
|
|
|
+ /** 达标人数 */
|
|
|
+ passNum: Math.floor(Math.random() * 1000),
|
|
|
+ /** 未达标人数 */
|
|
|
+ noPassNum: Math.floor(Math.random() * 1000),
|
|
|
+ /** 非会员 */
|
|
|
+ noMemberNum: Math.floor(Math.random() * 1000)
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ console.log(resulst)
|
|
|
+ handleInit({ content: resulst })
|
|
|
+ }
|
|
|
+ onMounted(() => {
|
|
|
+ handleMock()
|
|
|
+ listenerMessage('setAccomanyEcharts', handleInit)
|
|
|
+ postMessage({
|
|
|
+ api: 'setAccomanyEcharts'
|
|
|
+ })
|
|
|
+ })
|
|
|
+ onBeforeUnmount(() => {
|
|
|
+ removeListenerMessage('setAccomanyEcharts', handleInit)
|
|
|
+ })
|
|
|
+ const goto = () => {
|
|
|
+ const url = location.origin + location.pathname + `#/exercise-record`
|
|
|
+ console.log('🚀 ~ url:', url)
|
|
|
+ postMessage({
|
|
|
+ api: 'openWebView',
|
|
|
+ content: {
|
|
|
+ url: url,
|
|
|
+ orientation: 1
|
|
|
+ }
|
|
|
+ })
|
|
|
+ }
|
|
|
+ return () => (
|
|
|
+ <div class={styles.subjectEcharts}>
|
|
|
+ <div class={[styles.container, styles.ensemble]}>
|
|
|
+ <div class={styles.head}>
|
|
|
+ <div
|
|
|
+ class={styles.headLeft}
|
|
|
+ >
|
|
|
+ <img class={styles.icon} src={iconEnsemble} />
|
|
|
+ <div>总体情况</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class={styles.content} onClick={() => goto()}>
|
|
|
+ <CircleProgress
|
|
|
+ value={Number(activeData.sum.practiceRate)}
|
|
|
+ size={80}
|
|
|
+ color="#FF8057"
|
|
|
+ strokeWidth={6}
|
|
|
+ />
|
|
|
+ <div class={styles.items}>
|
|
|
+ <div class={styles.item}>
|
|
|
+ <div class={styles.itemNum}>
|
|
|
+ <span class={styles.rect}></span>
|
|
|
+ <span style={{ color: '#FF8057' }}>{activeData.sum.passNum}</span>
|
|
|
+ </div>
|
|
|
+ <div class={styles.itemTitle}>达标人数</div>
|
|
|
+ </div>
|
|
|
+ <div class={[styles.item, styles.line]}>
|
|
|
+ <div class={styles.itemNum}>
|
|
|
+ <span class={styles.rect} style={{ background: '#FFE7DF' }}></span>
|
|
|
+ <span>{activeData.sum.noPassNum}</span>
|
|
|
+ </div>
|
|
|
+ <div class={styles.itemTitle}>未达标人数</div>
|
|
|
+ </div>
|
|
|
+ <div class={styles.item}>
|
|
|
+ <div class={styles.itemNum}>
|
|
|
+ <span>{activeData.sum.noMemberNum}</span>
|
|
|
+ </div>
|
|
|
+ <div class={styles.itemTitle}>非会员人数</div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class={[styles.container, styles.ensemble]}>
|
|
|
+ <div class={styles.head}>
|
|
|
+ <div class={styles.headLeft}>
|
|
|
+ <img class={styles.icon} src={iconBackup} />
|
|
|
+ <div>声部情况</div>
|
|
|
+ </div>
|
|
|
+ <div class={styles.headRight}>
|
|
|
+ {activeData.colors.map((c) => (
|
|
|
+ <div
|
|
|
+ style={
|
|
|
+ c.select
|
|
|
+ ? { color: '#fff', borderColor: c.color, backgroundColor: c.color }
|
|
|
+ : { color: c.color, borderColor: c.borderColor }
|
|
|
+ }
|
|
|
+ onClick={() => changeLegend(c)}
|
|
|
+ >
|
|
|
+ {c.text}
|
|
|
+ </div>
|
|
|
+ ))}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ <div class={styles.headLabelWrap}>
|
|
|
+ {activeData.colors.map((c, cIndex) => {
|
|
|
+ return (
|
|
|
+ <div class={styles.headLabel} style={{ display: c.select ? '' : 'none' }}>
|
|
|
+ <div
|
|
|
+ class={styles.headLabelDot}
|
|
|
+ style={{ background: c.color, color: c.color }}
|
|
|
+ ></div>
|
|
|
+ <div style={{ color: c.color }}>
|
|
|
+ {activeData.sum[c.key] || 0}
|
|
|
+ {cIndex === 0 ? '%' : '人'}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ )
|
|
|
+ })}
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div id="subjectEcharts" class={styles.echartsMain}></div>
|
|
|
+ {!activeData.practiceThisWeeks.length && <div class={styles.emtry}>暂无练习记录</div>}
|
|
|
+ </div>
|
|
|
+
|
|
|
+ <div class={[styles.container, styles.subjectWrap]}>
|
|
|
+ {activeData.practiceThisWeeks.map((item, index: number) => (
|
|
|
+ <div
|
|
|
+ class={[styles.listItem, index === activeData.index ? styles.listItemActive : '']}
|
|
|
+ onClick={() => handleAction(index)}
|
|
|
+ >
|
|
|
+ <div class={styles.dot}></div>
|
|
|
+ <div class={styles.itemLeft}>
|
|
|
+ <div class={styles.subjectName}>{item.subjectName}</div>
|
|
|
+ <div class={styles.subjectType}>声部</div>
|
|
|
+ </div>
|
|
|
+ <div class={styles.listitems}>
|
|
|
+ {activeData.colors.map((c, index: number) => {
|
|
|
+ return (
|
|
|
+ <div class={styles.item}>
|
|
|
+ <div class={styles.itemNum}>
|
|
|
+ <span style={{ color: c.color }}>
|
|
|
+ {item[c.key]}
|
|
|
+ {index === 0 ? '%' : ''}
|
|
|
+ </span>
|
|
|
+ </div>
|
|
|
+ <div class={styles.itemTitle}>
|
|
|
+ {c.text}
|
|
|
+ {index === 0 ? '' : '人数'}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ )
|
|
|
+ })}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ ))}
|
|
|
+ </div>
|
|
|
+ </div>
|
|
|
+ )
|
|
|
+ }
|
|
|
+})
|