first commit
This commit is contained in:
206
src/views/dashboard/analysis/components/AccessTimeslot.vue
Normal file
206
src/views/dashboard/analysis/components/AccessTimeslot.vue
Normal file
@@ -0,0 +1,206 @@
|
||||
<template>
|
||||
<a-spin :loading="loading" style="width: 100%">
|
||||
<a-card class="general-card" title="访问时段分析">
|
||||
<Chart :option="chartOption" style="width: 100%; height: 370px" />
|
||||
</a-card>
|
||||
</a-spin>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { type EChartsOption, graphic } from 'echarts'
|
||||
import { useChart } from '@/hooks'
|
||||
import { type DashboardChartCommonResp, getAnalysisTimeslot as getData } from '@/apis/common'
|
||||
import handleIcon from '@/assets/icons/slider.svg'
|
||||
|
||||
// 提示框
|
||||
const tooltipItemsHtmlString = (items) => {
|
||||
return items
|
||||
.map(
|
||||
(el) => `<div class="content-panel">
|
||||
<p>
|
||||
<span style="background-color: ${el.color}" class="tooltip-item-icon"></span>
|
||||
<span>${el.seriesName}</span>
|
||||
</p>
|
||||
<span class="tooltip-value">
|
||||
${el.value}
|
||||
</span>
|
||||
</div>`,
|
||||
)
|
||||
.join('')
|
||||
}
|
||||
|
||||
const xAxis = ref<string[]>([])
|
||||
const chartData = ref<number[]>([])
|
||||
const { chartOption } = useChart((isDark: EChartsOption) => {
|
||||
return {
|
||||
grid: {
|
||||
left: '40',
|
||||
right: 0,
|
||||
top: '20',
|
||||
bottom: '100',
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
offset: 2,
|
||||
data: xAxis.value,
|
||||
boundaryGap: false,
|
||||
axisLabel: {
|
||||
color: '#4E5969',
|
||||
formatter(value: number, idx: number) {
|
||||
if (idx === 0) return ''
|
||||
if (idx === xAxis.value.length - 1) return ''
|
||||
return `${value}`
|
||||
},
|
||||
},
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: isDark ? '#3f3f3f' : '#A9AEB8',
|
||||
},
|
||||
},
|
||||
axisTick: {
|
||||
show: true,
|
||||
alignWithLabel: true,
|
||||
lineStyle: {
|
||||
color: '#86909C',
|
||||
},
|
||||
interval(idx: number) {
|
||||
if (idx === 0) return false
|
||||
if (idx === xAxis.value.length - 1) return false
|
||||
return true
|
||||
},
|
||||
},
|
||||
splitLine: {
|
||||
show: true,
|
||||
interval: (idx: number) => {
|
||||
if (idx === 0) return false
|
||||
return idx !== xAxis.value.length - 1
|
||||
},
|
||||
lineStyle: {
|
||||
color: isDark ? '#3F3F3F' : '#E5E8EF',
|
||||
},
|
||||
},
|
||||
axisPointer: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
color: '#23ADFF',
|
||||
width: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
axisLabel: {
|
||||
formatter(value: any, idx: number) {
|
||||
if (idx === 0) return value
|
||||
if (value >= 1000) {
|
||||
return `${value / 1000}k`
|
||||
}
|
||||
return `${value}`
|
||||
},
|
||||
},
|
||||
axisLine: {
|
||||
show: false,
|
||||
},
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
type: 'dashed',
|
||||
color: isDark ? '#3F3F3F' : '#E5E8EF',
|
||||
},
|
||||
},
|
||||
},
|
||||
tooltip: {
|
||||
show: true,
|
||||
trigger: 'axis',
|
||||
formatter(params) {
|
||||
const [firstElement] = params
|
||||
return `<div>
|
||||
<p class="tooltip-title">${firstElement.axisValueLabel}</p>
|
||||
${tooltipItemsHtmlString(params)}
|
||||
</div>`
|
||||
},
|
||||
className: 'echarts-tooltip-diy',
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '访问次数',
|
||||
data: chartData.value,
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
showSymbol: false,
|
||||
color: isDark ? '#3D72F6' : '#246EFF',
|
||||
symbol: 'circle',
|
||||
symbolSize: 10,
|
||||
emphasis: {
|
||||
focus: 'series',
|
||||
itemStyle: {
|
||||
borderWidth: 2,
|
||||
borderColor: '#E0E3FF',
|
||||
},
|
||||
},
|
||||
areaStyle: {
|
||||
opacity: 0.8,
|
||||
color: new graphic.LinearGradient(0, 0, 0, 1, [
|
||||
{
|
||||
offset: 0,
|
||||
color: 'rgba(17, 126, 255, 0.16)',
|
||||
},
|
||||
{
|
||||
offset: 1,
|
||||
color: 'rgba(17, 128, 255, 0)',
|
||||
},
|
||||
]),
|
||||
},
|
||||
},
|
||||
],
|
||||
dataZoom: [
|
||||
{
|
||||
bottom: 40,
|
||||
type: 'slider',
|
||||
left: 40,
|
||||
right: 14,
|
||||
height: 14,
|
||||
borderColor: 'transparent',
|
||||
handleIcon: `image://${handleIcon}`,
|
||||
handleSize: '20',
|
||||
handleStyle: {
|
||||
shadowColor: 'rgba(0, 0, 0, 0.2)',
|
||||
shadowBlur: 4,
|
||||
},
|
||||
brushSelect: false,
|
||||
backgroundColor: isDark ? '#313132' : '#F2F3F5',
|
||||
},
|
||||
{
|
||||
type: 'inside',
|
||||
start: 0,
|
||||
end: 100,
|
||||
zoomOnMouseWheel: false,
|
||||
},
|
||||
],
|
||||
}
|
||||
})
|
||||
|
||||
const loading = ref(false)
|
||||
// 查询图表数据
|
||||
const getChartData = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
const { data } = await getData()
|
||||
data.forEach((item: DashboardChartCommonResp) => {
|
||||
xAxis.value.push(item.name)
|
||||
chartData.value.push(item.value)
|
||||
})
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getChartData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
:deep(.arco-card-body) {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
82
src/views/dashboard/analysis/components/Browser.vue
Normal file
82
src/views/dashboard/analysis/components/Browser.vue
Normal file
@@ -0,0 +1,82 @@
|
||||
<template>
|
||||
<a-spin :loading="loading" style="width: 100%">
|
||||
<a-card class="general-card" title="浏览器">
|
||||
<div class="chart">
|
||||
<Chart v-if="!loading" :option="chartOption" style="height: 190px" />
|
||||
</div>
|
||||
</a-card>
|
||||
</a-spin>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { EChartsOption } from 'echarts'
|
||||
import { useChart } from '@/hooks'
|
||||
import { type DashboardChartCommonResp, getAnalysisBrowser as getData } from '@/apis/common'
|
||||
|
||||
const xAxis = ref<string[]>([])
|
||||
const chartData = ref([])
|
||||
const { chartOption } = useChart((isDark: EChartsOption) => {
|
||||
return {
|
||||
legend: {
|
||||
data: xAxis.value,
|
||||
bottom: 0,
|
||||
icon: 'circle',
|
||||
itemWidth: 8,
|
||||
textStyle: {
|
||||
color: isDark ? 'rgba(255,255,255,0.7)' : '#4E5969',
|
||||
},
|
||||
itemStyle: {
|
||||
borderWidth: 0,
|
||||
},
|
||||
},
|
||||
tooltip: {
|
||||
show: true,
|
||||
trigger: 'item',
|
||||
},
|
||||
series: [
|
||||
{
|
||||
type: 'pie',
|
||||
radius: ['35%', '60%'],
|
||||
center: ['50%', '42%'],
|
||||
label: {
|
||||
formatter: '{d}% ',
|
||||
color: isDark ? 'rgba(255, 255, 255, 0.7)' : '#4E5969',
|
||||
},
|
||||
itemStyle: {
|
||||
borderColor: isDark ? '#000' : '#fff',
|
||||
borderWidth: 1,
|
||||
},
|
||||
data: chartData.value,
|
||||
},
|
||||
],
|
||||
}
|
||||
})
|
||||
|
||||
const loading = ref(false)
|
||||
const colors = ['#246EFF', '#00B2FF', '#81E2FF', '#846BCE', '#86DF6C']
|
||||
// 查询图表数据
|
||||
const getChartData = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
const { data } = await getData()
|
||||
data.forEach((item: DashboardChartCommonResp, index: number) => {
|
||||
xAxis.value.push(item.name)
|
||||
chartData.value.push({
|
||||
...item,
|
||||
itemStyle: {
|
||||
color: data.length > 1 && index === data.length - 1 ? colors[colors.length - 1] : colors[index],
|
||||
},
|
||||
})
|
||||
})
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getChartData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
</style>
|
||||
148
src/views/dashboard/analysis/components/DataOverview/Demo1.vue
Normal file
148
src/views/dashboard/analysis/components/DataOverview/Demo1.vue
Normal file
@@ -0,0 +1,148 @@
|
||||
<template>
|
||||
<a-spin :loading="loading" style="width: 100%">
|
||||
<a-card
|
||||
class="general-card"
|
||||
:style="{
|
||||
background: isDark
|
||||
? 'linear-gradient(180deg, #284991 0%, #122B62 100%)'
|
||||
: 'linear-gradient(180deg, #f2f9fe 0%, #e6f4fe 100%)',
|
||||
}"
|
||||
>
|
||||
<div class="content-wrap">
|
||||
<div class="content">
|
||||
<a-statistic
|
||||
title="统计示例"
|
||||
:value="count"
|
||||
:value-from="0"
|
||||
animation
|
||||
show-group-separator
|
||||
/>
|
||||
<div class="desc">
|
||||
<a-typography-text type="secondary" class="label">较昨日</a-typography-text>
|
||||
<a-typography-text v-if="growth > 0" type="success" :title="`${growth}%`">
|
||||
{{ growth }}
|
||||
<icon-arrow-rise />
|
||||
</a-typography-text>
|
||||
<a-typography-text v-else type="danger" :title="`${growth}%`">
|
||||
{{ growth }}
|
||||
<icon-arrow-fall />
|
||||
</a-typography-text>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chart">
|
||||
<Chart v-if="!loading" :option="chartOption" />
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-spin>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useChart } from '@/hooks'
|
||||
import { useAppStore } from '@/stores'
|
||||
|
||||
const appStore = useAppStore()
|
||||
const isDark = computed(() => appStore.theme === 'dark')
|
||||
|
||||
const count = ref(0)
|
||||
const growth = ref(0)
|
||||
const xAxis = ref<string[]>([])
|
||||
const chartData = ref<number[]>([])
|
||||
const { chartOption } = useChart(() => {
|
||||
return {
|
||||
grid: {
|
||||
left: 0,
|
||||
right: 30,
|
||||
top: 10,
|
||||
bottom: 0,
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: xAxis.value,
|
||||
},
|
||||
yAxis: {
|
||||
show: false,
|
||||
},
|
||||
tooltip: {
|
||||
show: true,
|
||||
trigger: 'axis',
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '示例',
|
||||
data: chartData.value,
|
||||
type: 'line',
|
||||
showSymbol: false,
|
||||
smooth: true,
|
||||
lineStyle: {
|
||||
color: '#246EFF',
|
||||
width: 2,
|
||||
type: 'dashed',
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
})
|
||||
|
||||
const loading = ref(false)
|
||||
// 查询图表数据
|
||||
const getChartData = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
xAxis.value = ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月']
|
||||
count.value = 88888
|
||||
growth.value = 88.8
|
||||
chartData.value = [4, 5, 6, 2, 3, 4, 25, 21, 6, 7, 8, 1]
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getChartData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
:deep(.arco-card) {
|
||||
border-radius: 4px;
|
||||
}
|
||||
:deep(.arco-card-body) {
|
||||
width: 100%;
|
||||
height: 134px;
|
||||
padding: 0;
|
||||
}
|
||||
.content-wrap {
|
||||
width: 100%;
|
||||
padding: 16px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
:deep(.content) {
|
||||
float: left;
|
||||
width: 108px;
|
||||
height: 102px;
|
||||
}
|
||||
:deep(.arco-statistic) {
|
||||
.arco-statistic-title {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.arco-statistic-content {
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.chart {
|
||||
float: right;
|
||||
width: calc(100% - 108px);
|
||||
height: 90px;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
.label {
|
||||
padding-right: 8px;
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
||||
158
src/views/dashboard/analysis/components/DataOverview/Demo2.vue
Normal file
158
src/views/dashboard/analysis/components/DataOverview/Demo2.vue
Normal file
@@ -0,0 +1,158 @@
|
||||
<template>
|
||||
<a-spin :loading="loading" style="width: 100%">
|
||||
<a-card
|
||||
class="general-card"
|
||||
:style="{
|
||||
background: isDark
|
||||
? 'linear-gradient(180deg, #312565 0%, #201936 100%)'
|
||||
: 'linear-gradient(180deg, #F7F7FF 0%, #ECECFF 100%)',
|
||||
}"
|
||||
>
|
||||
<div class="content-wrap">
|
||||
<div class="content">
|
||||
<a-statistic
|
||||
title="统计示例"
|
||||
:value="count"
|
||||
:value-from="0"
|
||||
animation
|
||||
show-group-separator
|
||||
/>
|
||||
<div class="desc">
|
||||
<a-typography-text type="secondary" class="label">较昨日</a-typography-text>
|
||||
<a-typography-text v-if="growth > 0" type="success" :title="`${growth}%`">
|
||||
{{ growth }}
|
||||
<icon-arrow-rise />
|
||||
</a-typography-text>
|
||||
<a-typography-text v-else type="danger" :title="`${growth}%`">
|
||||
{{ growth }}
|
||||
<icon-arrow-fall />
|
||||
</a-typography-text>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chart">
|
||||
<Chart v-if="!loading" :option="chartOption" />
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-spin>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useChart } from '@/hooks'
|
||||
import { useAppStore } from '@/stores'
|
||||
|
||||
const appStore = useAppStore()
|
||||
const isDark = computed(() => appStore.theme === 'dark')
|
||||
|
||||
const count = ref(0)
|
||||
const growth = ref(0)
|
||||
const chartData = ref<number[]>([])
|
||||
const { chartOption } = useChart(() => {
|
||||
return {
|
||||
grid: {
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
},
|
||||
legend: {
|
||||
show: true,
|
||||
top: 'center',
|
||||
right: '20%',
|
||||
orient: 'vertical',
|
||||
icon: 'circle',
|
||||
itemWidth: 6,
|
||||
itemHeight: 6,
|
||||
textStyle: {
|
||||
color: '#4E5969',
|
||||
},
|
||||
},
|
||||
tooltip: {
|
||||
show: true,
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '总计',
|
||||
type: 'pie',
|
||||
radius: ['50%', '70%'],
|
||||
center: ['30%', '50%'],
|
||||
label: {
|
||||
show: false,
|
||||
},
|
||||
data: chartData.value,
|
||||
},
|
||||
],
|
||||
}
|
||||
})
|
||||
|
||||
const loading = ref(false)
|
||||
const colors = ['#8D4EDA', '#00B2FF', '#86DF6C']
|
||||
// 查询图表数据
|
||||
const getChartData = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
count.value = 88888
|
||||
growth.value = 88.8
|
||||
const data = [30, 20, 10]
|
||||
data.forEach((item, index) => {
|
||||
chartData.value.push({
|
||||
name: `示例${index + 1}`,
|
||||
value: item,
|
||||
itemStyle: {
|
||||
color: data.length > 1 && index === data.length - 1 ? colors[colors.length - 1] : colors[index],
|
||||
},
|
||||
})
|
||||
})
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getChartData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
:deep(.arco-card) {
|
||||
border-radius: 4px;
|
||||
}
|
||||
:deep(.arco-card-body) {
|
||||
width: 100%;
|
||||
height: 134px;
|
||||
padding: 0;
|
||||
}
|
||||
.content-wrap {
|
||||
width: 100%;
|
||||
padding: 16px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
:deep(.content) {
|
||||
float: left;
|
||||
width: 108px;
|
||||
height: 102px;
|
||||
}
|
||||
:deep(.arco-statistic) {
|
||||
.arco-statistic-title {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.arco-statistic-content {
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.chart {
|
||||
float: right;
|
||||
width: calc(100% - 108px);
|
||||
height: 90px;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
.label {
|
||||
padding-right: 8px;
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
||||
152
src/views/dashboard/analysis/components/DataOverview/Ip.vue
Normal file
152
src/views/dashboard/analysis/components/DataOverview/Ip.vue
Normal file
@@ -0,0 +1,152 @@
|
||||
<template>
|
||||
<a-spin :loading="loading" style="width: 100%">
|
||||
<a-card
|
||||
class="general-card"
|
||||
:style="{
|
||||
background: isDark
|
||||
? ' linear-gradient(180deg, #3D492E 0%, #263827 100%)'
|
||||
: 'linear-gradient(180deg, #F5FEF2 0%, #E6FEEE 100%)',
|
||||
}"
|
||||
>
|
||||
<div class="content-wrap">
|
||||
<div class="content">
|
||||
<a-statistic
|
||||
title="独立IP"
|
||||
:value="count"
|
||||
:value-from="0"
|
||||
animation
|
||||
show-group-separator
|
||||
/>
|
||||
<div class="desc">
|
||||
<a-typography-text type="secondary" class="label">今日</a-typography-text>
|
||||
<a-typography-text v-if="growth > 0" type="success" :title="`${growth}%`">
|
||||
{{ today }}
|
||||
<icon-arrow-rise />
|
||||
</a-typography-text>
|
||||
<a-typography-text v-else type="danger" :title="`${growth}%`">
|
||||
{{ today }}
|
||||
<icon-arrow-fall />
|
||||
</a-typography-text>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chart">
|
||||
<Chart v-if="!loading" :option="chartOption" />
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-spin>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useChart } from '@/hooks'
|
||||
import { useAppStore } from '@/stores'
|
||||
import { type DashboardChartCommonResp, getDashboardOverviewIp as getData } from '@/apis'
|
||||
|
||||
const appStore = useAppStore()
|
||||
const isDark = computed(() => appStore.theme === 'dark')
|
||||
|
||||
const count = ref(0)
|
||||
const today = ref(0)
|
||||
const growth = ref(0)
|
||||
const xAxis = ref<string[]>([])
|
||||
const chartData = ref<number[]>([])
|
||||
const { chartOption } = useChart(() => {
|
||||
return {
|
||||
grid: {
|
||||
left: 0,
|
||||
right: 30,
|
||||
top: 10,
|
||||
bottom: 0,
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: xAxis.value,
|
||||
},
|
||||
yAxis: {
|
||||
show: false,
|
||||
},
|
||||
tooltip: {
|
||||
show: true,
|
||||
trigger: 'axis',
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '独立IP',
|
||||
data: chartData.value,
|
||||
type: 'line',
|
||||
showSymbol: false,
|
||||
lineStyle: {
|
||||
color: '#2CAB40',
|
||||
width: 2,
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
})
|
||||
|
||||
const loading = ref(false)
|
||||
// 查询图表数据
|
||||
const getChartData = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
const { data } = await getData()
|
||||
count.value = data.total
|
||||
today.value = data.today
|
||||
growth.value = data.growth
|
||||
data.dataList.forEach((item: DashboardChartCommonResp) => {
|
||||
xAxis.value.push(item.name)
|
||||
chartData.value.push(item.value)
|
||||
})
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getChartData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
:deep(.arco-card) {
|
||||
border-radius: 4px;
|
||||
}
|
||||
:deep(.arco-card-body) {
|
||||
width: 100%;
|
||||
height: 134px;
|
||||
padding: 0;
|
||||
}
|
||||
.content-wrap {
|
||||
width: 100%;
|
||||
padding: 16px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
:deep(.content) {
|
||||
float: left;
|
||||
width: 108px;
|
||||
height: 102px;
|
||||
}
|
||||
:deep(.arco-statistic) {
|
||||
.arco-statistic-title {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.arco-statistic-content {
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.chart {
|
||||
float: right;
|
||||
width: calc(100% - 108px);
|
||||
height: 90px;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
.label {
|
||||
padding-right: 8px;
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
||||
151
src/views/dashboard/analysis/components/DataOverview/Pv.vue
Normal file
151
src/views/dashboard/analysis/components/DataOverview/Pv.vue
Normal file
@@ -0,0 +1,151 @@
|
||||
<template>
|
||||
<a-spin :loading="loading" style="width: 100%">
|
||||
<a-card
|
||||
class="general-card"
|
||||
:style="{
|
||||
background: isDark
|
||||
? 'linear-gradient(180deg, #284991 0%, #122B62 100%)'
|
||||
: 'linear-gradient(180deg, #f2f9fe 0%, #e6f4fe 100%)',
|
||||
}"
|
||||
>
|
||||
<div class="content-wrap">
|
||||
<div class="content">
|
||||
<a-statistic
|
||||
title="访问次数"
|
||||
:value="count"
|
||||
:value-from="0"
|
||||
animation
|
||||
show-group-separator
|
||||
/>
|
||||
<div class="desc">
|
||||
<a-typography-text type="secondary" class="label">今日</a-typography-text>
|
||||
<a-typography-text v-if="growth > 0" type="success" :title="`${growth}%`">
|
||||
{{ today }}
|
||||
<icon-arrow-rise />
|
||||
</a-typography-text>
|
||||
<a-typography-text v-else type="danger" :title="`${growth}%`">
|
||||
{{ today }}
|
||||
<icon-arrow-fall />
|
||||
</a-typography-text>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chart">
|
||||
<Chart v-if="!loading" :option="chartOption" />
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-spin>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useChart } from '@/hooks'
|
||||
import { useAppStore } from '@/stores'
|
||||
import { type DashboardChartCommonResp, getDashboardOverviewPv as getData } from '@/apis'
|
||||
|
||||
const appStore = useAppStore()
|
||||
const isDark = computed(() => appStore.theme === 'dark')
|
||||
const count = ref(0)
|
||||
const today = ref(0)
|
||||
const growth = ref(0)
|
||||
const xAxis = ref<string[]>([])
|
||||
const chartData = ref<number[]>([])
|
||||
const { chartOption } = useChart(() => {
|
||||
return {
|
||||
grid: {
|
||||
left: 0,
|
||||
right: 30,
|
||||
top: 10,
|
||||
bottom: 0,
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: xAxis.value,
|
||||
},
|
||||
yAxis: {
|
||||
show: false,
|
||||
},
|
||||
tooltip: {
|
||||
show: true,
|
||||
trigger: 'axis',
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '访问次数',
|
||||
data: chartData.value,
|
||||
type: 'line',
|
||||
showSymbol: false,
|
||||
lineStyle: {
|
||||
color: '#246EFF',
|
||||
width: 2,
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
})
|
||||
|
||||
const loading = ref(false)
|
||||
// 查询图表数据
|
||||
const getChartData = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
const { data } = await getData()
|
||||
count.value = data.total
|
||||
today.value = data.today
|
||||
growth.value = data.growth
|
||||
data.dataList.forEach((item: DashboardChartCommonResp) => {
|
||||
xAxis.value.push(item.name)
|
||||
chartData.value.push(item.value)
|
||||
})
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getChartData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
:deep(.arco-card) {
|
||||
border-radius: 4px;
|
||||
}
|
||||
:deep(.arco-card-body) {
|
||||
width: 100%;
|
||||
height: 134px;
|
||||
padding: 0;
|
||||
}
|
||||
.content-wrap {
|
||||
width: 100%;
|
||||
padding: 16px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
:deep(.content) {
|
||||
float: left;
|
||||
width: 108px;
|
||||
height: 102px;
|
||||
}
|
||||
:deep(.arco-statistic) {
|
||||
.arco-statistic-title {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.arco-statistic-content {
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.chart {
|
||||
float: right;
|
||||
width: calc(100% - 108px);
|
||||
height: 90px;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
.label {
|
||||
padding-right: 8px;
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,30 @@
|
||||
<template>
|
||||
<a-card class="general-card" title="数据总览">
|
||||
<a-grid :cols="24" :col-gap="12" :row-gap="12">
|
||||
<a-grid-item :span="{ xs: 24, sm: 24, md: 24, lg: 24, xl: 6, xxl: 6 }">
|
||||
<Pv />
|
||||
</a-grid-item>
|
||||
<a-grid-item :span="{ xs: 24, sm: 24, md: 24, lg: 24, xl: 6, xxl: 6 }">
|
||||
<Ip />
|
||||
</a-grid-item>
|
||||
<a-grid-item :span="{ xs: 24, sm: 24, md: 24, lg: 24, xl: 6, xxl: 6 }">
|
||||
<Demo1 />
|
||||
</a-grid-item>
|
||||
<a-grid-item :span="{ xs: 24, sm: 24, md: 24, lg: 24, xl: 6, xxl: 6 }">
|
||||
<Demo2 />
|
||||
</a-grid-item>
|
||||
</a-grid>
|
||||
<template #extra>
|
||||
<slot name="extra"></slot>
|
||||
</template>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Pv from './Pv.vue'
|
||||
import Ip from './Ip.vue'
|
||||
import Demo1 from './Demo1.vue'
|
||||
import Demo2 from './Demo2.vue'
|
||||
</script>
|
||||
|
||||
<style scoped lang="less"></style>
|
||||
138
src/views/dashboard/analysis/components/Geo.vue
Normal file
138
src/views/dashboard/analysis/components/Geo.vue
Normal file
@@ -0,0 +1,138 @@
|
||||
<template>
|
||||
<a-spin :loading="loading" style="width: 100%">
|
||||
<a-card class="general-card" title="地理位置">
|
||||
<template #extra>
|
||||
<a-radio-group type="button" size="small">
|
||||
<a-radio value="china">中国</a-radio>
|
||||
<a-radio value="world" disabled title="暂未开放">世界</a-radio>
|
||||
</a-radio-group>
|
||||
</template>
|
||||
<div class="content">
|
||||
<div class="mapChart">
|
||||
<Chart ref="chartRef" :option="chartOption" style="height: 468px" />
|
||||
</div>
|
||||
<div class="dataShow">
|
||||
<div v-for="item in topData" :key="item.name" class="dataItem">
|
||||
<div class="title">
|
||||
<span>{{ item.name }}</span>
|
||||
<span>{{ item.value }}</span>
|
||||
</div>
|
||||
<a-progress color="#165dff" size="medium" :percent="item.value / totalValue" :show-text="false" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-spin>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { EChartsOption } from 'echarts'
|
||||
import { getAnalysisGeo as getData } from '@/apis/common/dashboard'
|
||||
import { useChart } from '@/hooks'
|
||||
|
||||
const chartRef = useTemplateRef('chartRef')
|
||||
const chartData = ref([])
|
||||
const totalValue = ref(0)
|
||||
const topData = ref([])
|
||||
|
||||
const { chartOption } = useChart((isDark: EChartsOption) => {
|
||||
return {
|
||||
visualMap: {
|
||||
left: 'left',
|
||||
min: 0,
|
||||
max: 20000,
|
||||
inRange: {
|
||||
color: ['#EAF4FF', '#3C7EFF'],
|
||||
},
|
||||
orient: 'horizontal',
|
||||
calculable: false,
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
formatter(data: any) {
|
||||
return `${data.name}<br/>访问次数: ${data.value || 0}`
|
||||
},
|
||||
},
|
||||
series: [
|
||||
{
|
||||
type: 'map',
|
||||
map: 'china',
|
||||
zoom: 1.1,
|
||||
label: {
|
||||
show: false,
|
||||
},
|
||||
itemStyle: {
|
||||
normal: {
|
||||
areaColor: isDark ? '#313132' : '#F6F6F6',
|
||||
},
|
||||
},
|
||||
data: chartData.value,
|
||||
},
|
||||
],
|
||||
}
|
||||
})
|
||||
|
||||
const loading = ref(false)
|
||||
// 查询图表数据
|
||||
const getChartData = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
const { data } = await getData()
|
||||
chartData.value = data.filter((item) => item.value !== 0)
|
||||
totalValue.value = data.reduce((sum, item) => sum + item.value, 0)
|
||||
topData.value = data.sort((a, b) => b.value - a.value).slice(0, 7)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getChartData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.mapChart {
|
||||
flex: 3;
|
||||
}
|
||||
|
||||
.dataShow {
|
||||
height: 100%;
|
||||
max-height: 500px;
|
||||
flex: 1;
|
||||
padding: 16px 24px;
|
||||
border-radius: 4px;
|
||||
overflow: auto;
|
||||
background-color: var(--color-bg-4);
|
||||
|
||||
.dataItem {
|
||||
padding-top: 16px;
|
||||
|
||||
.title {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
|
||||
.dataItem:first-child {
|
||||
padding-top: 0;
|
||||
}
|
||||
|
||||
&::-webkit-scrollbar {
|
||||
display: none;
|
||||
/* 隐藏滚动条 */
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.mobile {
|
||||
.dataShow {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
101
src/views/dashboard/analysis/components/Module.vue
Normal file
101
src/views/dashboard/analysis/components/Module.vue
Normal file
@@ -0,0 +1,101 @@
|
||||
<template>
|
||||
<a-spin :loading="loading" style="width: 100%">
|
||||
<a-card class="general-card" title="热门模块 (Top10)">
|
||||
<div class="chart">
|
||||
<Chart v-if="!loading" :option="chartOption" style="width: 100%; height: 355px" />
|
||||
</div>
|
||||
</a-card>
|
||||
</a-spin>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { EChartsOption } from 'echarts'
|
||||
import { useChart } from '@/hooks'
|
||||
import { type DashboardChartCommonResp, getAnalysisModule as getData } from '@/apis/common'
|
||||
|
||||
const yAxis = ref<string[]>([])
|
||||
const chartData = ref([])
|
||||
const { chartOption } = useChart((isDark: EChartsOption) => {
|
||||
return {
|
||||
grid: {
|
||||
left: 55,
|
||||
right: 20,
|
||||
top: 0,
|
||||
bottom: 20,
|
||||
},
|
||||
xAxis: {
|
||||
type: 'value',
|
||||
axisLabel: {
|
||||
show: true,
|
||||
formatter(value: number, idx: number) {
|
||||
if (idx === 0) return String(value)
|
||||
return `${Number(value) / 1000}k`
|
||||
},
|
||||
},
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
color: isDark ? '#484849' : '#E5E8EF',
|
||||
},
|
||||
},
|
||||
},
|
||||
yAxis: {
|
||||
type: 'category',
|
||||
data: yAxis.value,
|
||||
axisLabel: {
|
||||
show: true,
|
||||
color: '#4E5969',
|
||||
},
|
||||
axisTick: {
|
||||
show: true,
|
||||
length: 2,
|
||||
lineStyle: {
|
||||
color: '#A9AEB8',
|
||||
},
|
||||
alignWithLabel: true,
|
||||
},
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: isDark ? '#484849' : '#A9AEB8',
|
||||
},
|
||||
},
|
||||
},
|
||||
tooltip: {
|
||||
show: true,
|
||||
trigger: 'axis',
|
||||
},
|
||||
series: [
|
||||
{
|
||||
data: chartData.value,
|
||||
type: 'bar',
|
||||
barWidth: 7,
|
||||
itemStyle: {
|
||||
color: '#4086FF',
|
||||
borderRadius: 4,
|
||||
},
|
||||
},
|
||||
],
|
||||
}
|
||||
})
|
||||
|
||||
const loading = ref(false)
|
||||
// 查询图表数据
|
||||
const getChartData = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
const { data } = await getData()
|
||||
data.forEach((item: DashboardChartCommonResp) => {
|
||||
yAxis.value.unshift(item.name)
|
||||
chartData.value.unshift(item.value)
|
||||
})
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getChartData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
</style>
|
||||
82
src/views/dashboard/analysis/components/Os.vue
Normal file
82
src/views/dashboard/analysis/components/Os.vue
Normal file
@@ -0,0 +1,82 @@
|
||||
<template>
|
||||
<a-spin :loading="loading" style="width: 100%">
|
||||
<a-card class="general-card" title="终端">
|
||||
<div class="chart">
|
||||
<Chart v-if="!loading" :option="chartOption" style="height: 190px" />
|
||||
</div>
|
||||
</a-card>
|
||||
</a-spin>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import type { EChartsOption } from 'echarts'
|
||||
import { useChart } from '@/hooks'
|
||||
import { type DashboardChartCommonResp, getAnalysisOs as getData } from '@/apis/common'
|
||||
|
||||
const xAxis = ref<string[]>([])
|
||||
const chartData = ref([])
|
||||
const { chartOption } = useChart((isDark: EChartsOption) => {
|
||||
return {
|
||||
legend: {
|
||||
data: xAxis.value,
|
||||
bottom: 0,
|
||||
icon: 'circle',
|
||||
itemWidth: 8,
|
||||
textStyle: {
|
||||
color: isDark ? 'rgba(255,255,255,0.7)' : '#4E5969',
|
||||
},
|
||||
itemStyle: {
|
||||
borderWidth: 0,
|
||||
},
|
||||
},
|
||||
tooltip: {
|
||||
show: true,
|
||||
trigger: 'item',
|
||||
},
|
||||
series: [
|
||||
{
|
||||
type: 'pie',
|
||||
radius: ['35%', '60%'],
|
||||
center: ['50%', '42%'],
|
||||
label: {
|
||||
formatter: '{d}% ',
|
||||
color: isDark ? 'rgba(255, 255, 255, 0.7)' : '#4E5969',
|
||||
},
|
||||
itemStyle: {
|
||||
borderColor: isDark ? '#000' : '#fff',
|
||||
borderWidth: 1,
|
||||
},
|
||||
data: chartData.value,
|
||||
},
|
||||
],
|
||||
}
|
||||
})
|
||||
|
||||
const loading = ref(false)
|
||||
const colors = ['#246EFF', '#00B2FF', '#81E2FF', '#846BCE', '#86DF6C']
|
||||
// 查询图表数据
|
||||
const getChartData = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
const { data } = await getData()
|
||||
data.forEach((item: DashboardChartCommonResp, index: number) => {
|
||||
xAxis.value.push(item.name)
|
||||
chartData.value.push({
|
||||
...item,
|
||||
itemStyle: {
|
||||
color: data.length > 1 && index === data.length - 1 ? colors[colors.length - 1] : colors[index],
|
||||
},
|
||||
})
|
||||
})
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getChartData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
</style>
|
||||
43
src/views/dashboard/analysis/index.vue
Normal file
43
src/views/dashboard/analysis/index.vue
Normal file
@@ -0,0 +1,43 @@
|
||||
<template>
|
||||
<div class="gi_page container">
|
||||
<a-space direction="vertical" :size="14" fill>
|
||||
<div>
|
||||
<DataOverview />
|
||||
</div>
|
||||
<div>
|
||||
<a-grid :cols="24" :col-gap="14" :row-gap="14">
|
||||
<a-grid-item :span="{ xs: 24, sm: 24, md: 24, lg: 24, xl: 18, xxl: 18 }">
|
||||
<Geo />
|
||||
</a-grid-item>
|
||||
<a-grid-item :span="{ xs: 24, sm: 24, md: 24, lg: 24, xl: 6, xxl: 6 }">
|
||||
<Os style="margin-bottom: 16px" />
|
||||
<Browser />
|
||||
</a-grid-item>
|
||||
</a-grid>
|
||||
</div>
|
||||
<div>
|
||||
<a-grid :cols="24" :col-gap="16" :row-gap="16">
|
||||
<a-grid-item :span="{ xs: 24, sm: 24, md: 24, lg: 24, xl: 18, xxl: 18 }">
|
||||
<AccessTimeslot />
|
||||
</a-grid-item>
|
||||
<a-grid-item :span="{ xs: 24, sm: 24, md: 24, lg: 24, xl: 6, xxl: 6 }">
|
||||
<Module />
|
||||
</a-grid-item>
|
||||
</a-grid>
|
||||
</div>
|
||||
</a-space>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import DataOverview from './components/DataOverview/index.vue'
|
||||
import Geo from './components/Geo.vue'
|
||||
import Os from './components/Os.vue'
|
||||
import Browser from './components/Browser.vue'
|
||||
import Module from './components/Module.vue'
|
||||
import AccessTimeslot from './components/AccessTimeslot.vue'
|
||||
|
||||
defineOptions({ name: 'Analysis' })
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss"></style>
|
||||
92
src/views/dashboard/workplace/components/Carousel.vue
Normal file
92
src/views/dashboard/workplace/components/Carousel.vue
Normal file
@@ -0,0 +1,92 @@
|
||||
<template>
|
||||
<a-carousel
|
||||
indicator-type="slider"
|
||||
show-arrow="hover"
|
||||
auto-play
|
||||
style="width: 100%; height: 150px; border-radius: 4px; overflow: hidden"
|
||||
>
|
||||
<a-carousel-item v-for="(item, idx) in dataList" :key="idx">
|
||||
<div>
|
||||
<a-link
|
||||
:href="item.url"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
<img :src="item.img" style="width: 100%; height: 150px;" :alt="item.name" />
|
||||
</a-link>
|
||||
</div>
|
||||
</a-carousel-item>
|
||||
</a-carousel>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import axios, { type AxiosRequestConfig, type AxiosResponse } from 'axios'
|
||||
import qs from 'query-string'
|
||||
import { isHttp } from '@/utils/validate'
|
||||
|
||||
export interface DataItem {
|
||||
name: string
|
||||
img: string
|
||||
url: string
|
||||
}
|
||||
|
||||
const images = ref<DataItem[]>([
|
||||
{
|
||||
name: '公众号',
|
||||
img: `https://continew.top/qrcode-text.jpg?${new Date().getTime()}`,
|
||||
url: 'https://continew.top/about/intro.html',
|
||||
},
|
||||
{
|
||||
name: '赞助',
|
||||
img: `https://continew.top/sponsor.jpg?${new Date().getTime()}`,
|
||||
url: 'https://continew.top/sponsor.html',
|
||||
},
|
||||
])
|
||||
|
||||
const get = <T = unknown>(url: string, params?: object, config?: AxiosRequestConfig): Promise<ApiRes<T>> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
axios
|
||||
.request<T>({
|
||||
method: 'get',
|
||||
url,
|
||||
params,
|
||||
paramsSerializer: (obj) => {
|
||||
return qs.stringify(obj)
|
||||
},
|
||||
...config,
|
||||
})
|
||||
.then((res: AxiosResponse) => resolve(res.data))
|
||||
.catch((err: { msg: string }) => reject(err))
|
||||
})
|
||||
}
|
||||
|
||||
const dataList = ref<DataItem[]>([])
|
||||
const loading = ref(false)
|
||||
// 查询列表数据
|
||||
const getDataList = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
const { data } = await get('https://api.charles7c.top/sponsor/platinum')
|
||||
if (data) {
|
||||
data.forEach((item) => {
|
||||
dataList.value.push({
|
||||
name: item.name,
|
||||
img: isHttp(item.img) ? item.img : `https://continew.top${item.img}`,
|
||||
url: item.url,
|
||||
})
|
||||
})
|
||||
dataList.value = [...dataList.value, ...images.value]
|
||||
} else {
|
||||
dataList.value = images.value
|
||||
}
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
await getDataList()
|
||||
})
|
||||
</script>
|
||||
42
src/views/dashboard/workplace/components/Docs.vue
Normal file
42
src/views/dashboard/workplace/components/Docs.vue
Normal file
@@ -0,0 +1,42 @@
|
||||
<template>
|
||||
<a-card
|
||||
class="general-card"
|
||||
title="帮助文档"
|
||||
:header-style="{ paddingBottom: 0 }"
|
||||
:body-style="{ paddingTop: '5px' }"
|
||||
style="height: 166px"
|
||||
>
|
||||
<template #extra>
|
||||
<a-link href="https://continew.top" target="_blank" rel="noopener">更多</a-link>
|
||||
</template>
|
||||
<a-row>
|
||||
<a-col v-for="link in links" :key="link.text" :span="12">
|
||||
<a-link
|
||||
:href="link.url"
|
||||
target="_blank"
|
||||
rel="noopener"
|
||||
>
|
||||
{{ link.text }}
|
||||
</a-link>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const links = [
|
||||
{ text: '项目简介', url: 'https://continew.top/admin/intro/what-is.html' },
|
||||
{ text: '快速开始', url: 'https://continew.top/admin/intro/quick-start.html' },
|
||||
{ text: '常见问题', url: 'https://continew.top/faq.html' },
|
||||
{ text: '更新日志', url: 'https://continew.top/admin/other/changelog.html' },
|
||||
{ text: '贡献指南', url: 'https://continew.top/admin/other/contributing.html' },
|
||||
{ text: '赞助支持 💖', url: 'https://continew.top/sponsor.html' },
|
||||
]
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.arco-card-body .arco-link {
|
||||
margin: 5px 0;
|
||||
color: rgb(var(--gray-8));
|
||||
}
|
||||
</style>
|
||||
198
src/views/dashboard/workplace/components/LatestActivity.vue
Normal file
198
src/views/dashboard/workplace/components/LatestActivity.vue
Normal file
@@ -0,0 +1,198 @@
|
||||
<template>
|
||||
<a-card class="general-card" title="最新动态" style="margin-bottom: 14px">
|
||||
<template #extra>
|
||||
<a-dropdown>
|
||||
<a-link>更多</a-link>
|
||||
<template #content>
|
||||
<!-- <a-doption>
|
||||
<a-link href="https://gitee.com/organizations/continew/events" target="_blank" rel="noopener">Gitee</a-link>
|
||||
</a-doption>
|
||||
<a-doption>
|
||||
<a-link href="https://gitcode.com/org/continew/discussion" target="_blank" rel="noopener">GitCode</a-link>
|
||||
</a-doption>
|
||||
<a-doption>
|
||||
<a-link href="https://github.com/orgs/continew-org/discussions" target="_blank" rel="noopener">GitHub</a-link>
|
||||
</a-doption>-->
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
<a-skeleton v-if="loading" :loading="loading" :animation="true">
|
||||
<a-skeleton-line :rows="10" />
|
||||
</a-skeleton>
|
||||
<div v-else>
|
||||
<a-empty v-if="dataList.length === 0">暂无动态</a-empty>
|
||||
<!-- <a-comment
|
||||
v-for="(item, index) in dataList"
|
||||
v-else
|
||||
:key="index"
|
||||
:author="item.actor.nickname"
|
||||
:class="`animated-fade-up-${index}`"
|
||||
>
|
||||
<template #avatar>
|
||||
<a-badge>
|
||||
<template #content>
|
||||
<GiSvgIcon v-if="item.platform === 'GitHub'" name="github" :size="15" />
|
||||
<GiSvgIcon v-else-if="item.platform === 'Gitee'" name="gitee" :size="15" />
|
||||
</template>
|
||||
<a :href="item.actor.url" target="_blank" rel="noopener">
|
||||
<a-avatar :size="30">
|
||||
<img :src="item.actor.avatar" alt="avatar" />
|
||||
</a-avatar>
|
||||
</a>
|
||||
</a-badge>
|
||||
</template>
|
||||
<template #datetime>
|
||||
<span :title="item.createTime">{{ item.createTimeString }}</span>
|
||||
</template>
|
||||
<template #content>
|
||||
<div class="content">
|
||||
<p v-if="item.type === 'PUSH'">
|
||||
推送到了 <a-link :href="item.repo.url" target="_blank" rel="noopener">{{ item.repo.alias }}</a-link>
|
||||
{{ `的 ${item.payload.branch} 分支 ${item.payload.commits.length} 个提交` }}
|
||||
<a-comment
|
||||
v-for="(commit, idx) in item.payload.commits"
|
||||
:key="idx"
|
||||
class="commit"
|
||||
>
|
||||
<template #content>
|
||||
<a-link :href="commit.url" target="_blank" rel="noopener" style="font-size: 12px" :title="commit.message">{{ commit.sha.substring(0, 7) }}</a-link>
|
||||
<a :href="commit.url" target="_blank" rel="noopener" :title="commit.message">{{ commit.message }}</a>
|
||||
</template>
|
||||
</a-comment>
|
||||
</p>
|
||||
<p v-else-if="item.type === 'ISSUE_OPEN'">
|
||||
在 <a-link :href="item.repo.url" target="_blank" rel="noopener">{{ item.repo.alias }}</a-link>
|
||||
创建了 Issue <a-link :href="item.payload.url" target="_blank" rel="noopener">{{ item.payload.title }}</a-link>
|
||||
</p>
|
||||
<p v-else-if="item.type === 'ISSUE_CLOSE' || item.type === 'ISSUE_REOPEN'">
|
||||
更改了 <a-link :href="item.repo.url" target="_blank" rel="noopener">{{ item.repo.alias }}</a-link>
|
||||
的 Issue <a-link :href="item.payload.url" target="_blank" rel="noopener">{{ item.payload.title }}</a-link>
|
||||
状态为 {{ item.payload.stateString }}
|
||||
</p>
|
||||
<p v-else-if="item.type === 'ISSUE_COMMENT'">
|
||||
评论了 <a-link :href="item.repo.url" target="_blank" rel="noopener">{{ item.repo.alias }}</a-link>
|
||||
的 Issue <a-link :href="item.payload.url" target="_blank" rel="noopener">{{ item.payload.title }}</a-link>
|
||||
</p>
|
||||
<p v-else-if="item.type === 'PULL_REQUEST_OPEN'">
|
||||
在 <a-link :href="item.repo.url" target="_blank" rel="noopener">{{ item.repo.alias }}</a-link>
|
||||
创建了 Pull Request <a-link :href="item.payload.url" target="_blank" rel="noopener">{{ item.payload.title }}</a-link>
|
||||
</p>
|
||||
<p v-else-if="item.type === 'PULL_REQUEST_MERGE'">
|
||||
接受了 <a-link :href="item.repo.url" target="_blank" rel="noopener">{{ item.repo.alias }}</a-link>
|
||||
的 Pull Request <a-link :href="item.payload.url" target="_blank" rel="noopener">{{ item.payload.title }}</a-link>
|
||||
</p>
|
||||
<p v-else-if="item.type === 'PULL_REQUEST_CLOSE' || item.type === 'PULL_REQUEST_REOPEN'">
|
||||
更改了 <a-link :href="item.repo.url" target="_blank" rel="noopener">{{ item.repo.alias }}</a-link>
|
||||
的 Pull Request <a-link :href="item.payload.url" target="_blank" rel="noopener">{{ item.payload.title }}</a-link>
|
||||
状态为 {{ item.payload.stateString }}
|
||||
</p>
|
||||
<p v-else-if="item.type === 'CREATE'">
|
||||
推送了新的 {{ item.payload.refType }}
|
||||
<a-link :href="item.payload.url" target="_blank" rel="noopener">{{ item.payload.ref }}</a-link>
|
||||
到 <a-link :href="item.repo.url" target="_blank" rel="noopener">{{ item.repo.alias }}</a-link>
|
||||
</p>
|
||||
<p v-else-if="item.type === 'DELETE'">
|
||||
删除了 <a-link :href="item.repo.url" target="_blank" rel="noopener">{{ item.repo.alias }}</a-link>
|
||||
的 {{ item.payload.ref }} {{ item.payload.refType }}
|
||||
</p>
|
||||
<p v-else>暂无</p>
|
||||
</div>
|
||||
</template>
|
||||
</a-comment>-->
|
||||
</div>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import dayjs from 'dayjs'
|
||||
import relativeTime from 'dayjs/plugin/relativeTime'
|
||||
import axios, { type AxiosRequestConfig, type AxiosResponse } from 'axios'
|
||||
import qs from 'query-string'
|
||||
|
||||
dayjs.extend(relativeTime)
|
||||
dayjs.locale('zh-cn')
|
||||
export interface DataItem {
|
||||
type: string
|
||||
actor: {
|
||||
username: string
|
||||
nickname: string
|
||||
avatar: string
|
||||
url: string
|
||||
}
|
||||
repo: {
|
||||
name: string
|
||||
alias: string
|
||||
url: string
|
||||
}
|
||||
payload: object
|
||||
createTime: string
|
||||
createTimeString: string
|
||||
}
|
||||
|
||||
const get = <T = unknown>(url: string, params?: object, config?: AxiosRequestConfig): Promise<ApiRes<T>> => {
|
||||
return new Promise((resolve, reject) => {
|
||||
axios
|
||||
.request<T>({
|
||||
method: 'get',
|
||||
url,
|
||||
params,
|
||||
paramsSerializer: (obj) => {
|
||||
return qs.stringify(obj)
|
||||
},
|
||||
...config,
|
||||
})
|
||||
.then((res: AxiosResponse) => resolve(res.data))
|
||||
.catch((err: { msg: string }) => reject(err))
|
||||
})
|
||||
}
|
||||
|
||||
const dataList = ref<DataItem[]>([])
|
||||
const loading = ref(false)
|
||||
// 查询列表数据
|
||||
const getDataList = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
// const { data } = await get('https://api.charles7c.top/git/orgs/events/continew')
|
||||
// data.forEach((item) => {
|
||||
// dataList.value.push({
|
||||
// ...item,
|
||||
// createTimeString: dayjs(new Date(item.createTime)).fromNow(),
|
||||
// })
|
||||
// })
|
||||
} catch (err) {
|
||||
// console.log(err)
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getDataList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
:deep(.arco-comment:not(:first-of-type), .arco-comment-inner-comment) {
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
:deep(.arco-comment:not(:last-of-type)) {
|
||||
border-bottom: 1px solid var(--color-border-1);
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
:deep(.arco-comment-content) {
|
||||
color: var(--color-text-2);
|
||||
}
|
||||
|
||||
:deep(.arco-comment-datetime) {
|
||||
color: var(--color-text-4);
|
||||
}
|
||||
|
||||
.commit.arco-comment {
|
||||
margin-top: 10px;
|
||||
font-size: 12px;
|
||||
border-bottom: none;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
</style>
|
||||
94
src/views/dashboard/workplace/components/Notice.vue
Normal file
94
src/views/dashboard/workplace/components/Notice.vue
Normal file
@@ -0,0 +1,94 @@
|
||||
<template>
|
||||
<a-card
|
||||
class="general-card"
|
||||
title="公告"
|
||||
:header-style="{ paddingBottom: '0' }"
|
||||
:body-style="{ padding: '15px 20px 13px 20px' }"
|
||||
>
|
||||
<template #extra>
|
||||
<a-link @click="router.replace({ path: '/system/notice' })">更多</a-link>
|
||||
</template>
|
||||
<a-skeleton v-if="loading" :loading="loading" :animation="true">
|
||||
<a-skeleton-line :rows="5" />
|
||||
</a-skeleton>
|
||||
<div v-else>
|
||||
<a-empty v-if="dataList.length === 0">暂无公告</a-empty>
|
||||
<div v-else>
|
||||
<div v-for="(item, idx) in dataList" :key="idx" class="item">
|
||||
<GiCellTag :value="item.type" :dict="notice_type" />
|
||||
<a-link class="item-content" @click="onDetail(item.id)">
|
||||
<a-typography-paragraph
|
||||
:ellipsis="{
|
||||
rows: 1,
|
||||
showTooltip: true,
|
||||
css: true,
|
||||
}"
|
||||
>
|
||||
{{ item.title }}
|
||||
</a-typography-paragraph>
|
||||
</a-link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
|
||||
<NoticeDetailModal ref="NoticeDetailModalRef" />
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { type DashboardNoticeResp, listDashboardNotice } from '@/apis'
|
||||
import { useDict } from '@/hooks/app'
|
||||
import NoticeDetailModal from '@/views/system/notice/NoticeDetailModal.vue'
|
||||
|
||||
const router = useRouter()
|
||||
const { notice_type } = useDict('notice_type')
|
||||
|
||||
const dataList = ref<DashboardNoticeResp[]>([])
|
||||
const loading = ref(false)
|
||||
// 查询列表数据
|
||||
const getDataList = async () => {
|
||||
try {
|
||||
loading.value = true
|
||||
const res = await listDashboardNotice()
|
||||
dataList.value = res.data
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const NoticeDetailModalRef = ref<InstanceType<typeof NoticeDetailModal>>()
|
||||
// 详情
|
||||
const onDetail = (id: string) => {
|
||||
NoticeDetailModalRef.value?.onDetail(id)
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
getDataList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
:deep(.arco-typography) {
|
||||
color: var(--color-text-2);
|
||||
}
|
||||
|
||||
.item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
height: 24px;
|
||||
margin-bottom: 4px;
|
||||
.item-content {
|
||||
flex: 1;
|
||||
justify-content: flex-start;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
margin-left: 4px;
|
||||
color: var(--color-text-2);
|
||||
text-decoration: none;
|
||||
font-size: 13px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
192
src/views/dashboard/workplace/components/Project.vue
Normal file
192
src/views/dashboard/workplace/components/Project.vue
Normal file
@@ -0,0 +1,192 @@
|
||||
<template>
|
||||
<a-card class="general-card" title="我的项目">
|
||||
<template #extra>
|
||||
<a-dropdown>
|
||||
<a-link>更多</a-link>
|
||||
<template #content>
|
||||
<a-doption>
|
||||
<a-link href="https://gitee.com/charles7c" target="_blank" rel="noopener">Gitee</a-link>
|
||||
</a-doption>
|
||||
<a-doption>
|
||||
<a-link href="https://gitcode.com/charles_7c" target="_blank" rel="noopener">GitCode</a-link>
|
||||
</a-doption>
|
||||
<a-doption>
|
||||
<a-link href="https://github.com/charles7c" target="_blank" rel="noopener">GitHub</a-link>
|
||||
</a-doption>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
<a-row :gutter="[14, 14]">
|
||||
<a-col
|
||||
v-for="(item, index) in list"
|
||||
:key="index"
|
||||
:xs="24"
|
||||
:sm="24"
|
||||
:md="12"
|
||||
:lg="12"
|
||||
:xl="8"
|
||||
:xxl="8"
|
||||
>
|
||||
<a-card :bordered="true" hoverable>
|
||||
<div class="badge badge-right" :style="`background-color: ${item.statusColor}`">{{ item.status }}</div>
|
||||
<a-card-meta>
|
||||
<template #title>
|
||||
<a-space>
|
||||
<img :src="item.logo" width="35px" height="25px" alt="logo" />
|
||||
<a-typography-paragraph
|
||||
:ellipsis="{
|
||||
rows: 1,
|
||||
showTooltip: true,
|
||||
css: true,
|
||||
}"
|
||||
>
|
||||
{{ item.alias }}
|
||||
</a-typography-paragraph>
|
||||
</a-space>
|
||||
</template>
|
||||
<template #description>
|
||||
<a-typography-paragraph
|
||||
:ellipsis="{
|
||||
rows: 2,
|
||||
showTooltip: true,
|
||||
css: true,
|
||||
}"
|
||||
>
|
||||
<a-typography-text type="secondary">
|
||||
{{ item.desc }}
|
||||
</a-typography-text>
|
||||
</a-typography-paragraph>
|
||||
</template>
|
||||
</a-card-meta>
|
||||
<template #actions>
|
||||
<a-tooltip content="点个 Star 吧">
|
||||
<span class="icon-hover">
|
||||
<a :href="item.url" target="_blank" rel="noopener"><IconThumbUp :size="20" /></a>
|
||||
</span>
|
||||
</a-tooltip>
|
||||
</template>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
const list = [
|
||||
{
|
||||
alias: 'ContiNew Admin',
|
||||
name: 'continew-admin',
|
||||
owner: 'continew-org',
|
||||
desc: '🔥Almost最佳后端规范🔥持续迭代优化的前后端分离中后台管理系统框架,开箱即用,持续提供舒适的开发体验。',
|
||||
logo: 'https://continew.top/logo.svg',
|
||||
url: 'https://gitee.com/continew/continew-admin/stargazers',
|
||||
status: '迭代',
|
||||
statusColor: 'rgb(var(--primary-6))',
|
||||
},
|
||||
{
|
||||
alias: 'ContiNew Starter',
|
||||
name: 'continew-starter',
|
||||
owner: 'continew-org',
|
||||
desc: '🔥高质量Starter🔥包含了一系列经过企业实践优化的依赖包(如 MyBatis-Plus、SaToken),可轻松集成到应用中,为开发人员减少手动引入依赖及配置的麻烦,为 Spring Boot Web 项目的灵活快速构建提供支持。',
|
||||
logo: 'https://continew.top/logo.svg',
|
||||
url: 'https://gitee.com/continew/continew-starter/stargazers',
|
||||
status: '迭代',
|
||||
statusColor: 'rgb(var(--primary-6))',
|
||||
},
|
||||
{
|
||||
alias: 'ContiNew Admin UI',
|
||||
name: 'continew-admin-ui',
|
||||
owner: 'continew-org',
|
||||
desc: '全新 3.x 版本,基于 Gi Demo 前端模板开发的 ContiNew Admin 前端适配项目。',
|
||||
logo: 'https://continew.top/logo.svg',
|
||||
url: 'https://gitee.com/continew/continew-admin-ui/stargazers',
|
||||
status: '迭代',
|
||||
statusColor: 'rgb(var(--primary-6))',
|
||||
},
|
||||
{
|
||||
alias: 'ContiNew Admin UI Arco',
|
||||
name: 'continew-admin-ui-arco',
|
||||
owner: 'continew-org',
|
||||
desc: '2.5 版本,基于 Arco Design Pro 前端模板开发的 ContiNew Admin 前端适配项目。',
|
||||
logo: 'https://continew.top/logo.svg',
|
||||
url: 'https://gitee.com/continew/continew-admin-ui-arco/stargazers',
|
||||
status: '归档',
|
||||
statusColor: 'rgb(var(--warning-6))',
|
||||
},
|
||||
{
|
||||
alias: 'ContiNew Cloud',
|
||||
name: 'continew-admin',
|
||||
owner: 'continew',
|
||||
desc: 'ContiNew Admin 微服务版本。基于 SpringBoot 3.x、Spring Cloud 2023 & Alibaba。',
|
||||
logo: 'https://continew.top/logo.svg',
|
||||
url: '#',
|
||||
status: '孵化',
|
||||
statusColor: 'rgb(var(--danger-6))',
|
||||
},
|
||||
{
|
||||
alias: 'charles7c.github.io',
|
||||
name: 'charles7c.github.io',
|
||||
owner: 'charles7c',
|
||||
desc: '基于 VitePress 构建的个人知识库/博客。扩展 VitePress 默认主题:增加ICP备案号、公安备案号显示,增加文章元数据信息(原创标识、作者、发布时间、分类、标签)显示,增加文末版权声明,增加 Gitalk 评论功能,主页美化、自动生成侧边栏、文章内支持 Mermaid 流程图、MD公式、MD脚注、增加我的标签、我的归档等独立页面,以及浏览器滚条等细节优化。',
|
||||
logo: 'https://blog.charles7c.top/logo.png',
|
||||
url: 'https://github.com/Charles7c/charles7c.github.io/stargazers',
|
||||
status: '归档',
|
||||
statusColor: 'rgb(var(--warning-6))',
|
||||
},
|
||||
]
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
:deep(.arco-card-body) {
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
.badge {
|
||||
position: absolute;
|
||||
font-size: 11px;
|
||||
height: 18px;
|
||||
line-height: 18px;
|
||||
text-align: center;
|
||||
width: 74px;
|
||||
color: #fff;
|
||||
}
|
||||
.badge-left {
|
||||
-moz-transform: rotate(-45deg);
|
||||
-ms-transform: rotate(-45deg);
|
||||
-o-transform: rotate(-45deg);
|
||||
-webkit-transform: rotate(-45deg);
|
||||
transform: rotate(-45deg);
|
||||
left: -20px;
|
||||
top: 6px;
|
||||
}
|
||||
.badge-right {
|
||||
-moz-transform: rotate(45deg);
|
||||
-ms-transform: rotate(45deg);
|
||||
-o-transform: rotate(45deg);
|
||||
-webkit-transform: rotate(45deg);
|
||||
transform: rotate(45deg);
|
||||
right: -20px;
|
||||
top: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
.icon-hover {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
color: rgba(var(--primary-6));
|
||||
border-radius: 50%;
|
||||
transition: all 0.1s;
|
||||
animation: icon-hover-animated 1.2s ease-in-out infinite;
|
||||
}
|
||||
.icon-hover:hover {
|
||||
background-color: rgb(var(--gray-2));
|
||||
}
|
||||
|
||||
@keyframes icon-hover-animated {
|
||||
50% {
|
||||
transform: scale(0.8);
|
||||
}
|
||||
}
|
||||
</style>
|
||||
45
src/views/dashboard/workplace/components/QuickOperation.vue
Normal file
45
src/views/dashboard/workplace/components/QuickOperation.vue
Normal file
@@ -0,0 +1,45 @@
|
||||
<template>
|
||||
<a-card
|
||||
class="general-card"
|
||||
title="快捷操作"
|
||||
:header-style="{ paddingBottom: '0' }"
|
||||
:body-style="{ padding: '24px 20px 16px 20px' }"
|
||||
>
|
||||
<a-row :gutter="8">
|
||||
<a-empty v-if="!has.hasPermOr(links.map((item) => item.permission))" />
|
||||
<a-col
|
||||
v-for="link in links"
|
||||
v-else
|
||||
v-show="has.hasPerm(link.permission)"
|
||||
:key="link.text"
|
||||
:span="8"
|
||||
class="wrapper"
|
||||
@click="router.replace({ path: link.path })"
|
||||
>
|
||||
<div class="icon">
|
||||
<GiSvgIcon :name="link.icon" />
|
||||
</div>
|
||||
<a-typography-paragraph class="text">
|
||||
{{ link.text }}
|
||||
</a-typography-paragraph>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import has from '@/utils/has'
|
||||
|
||||
const router = useRouter()
|
||||
|
||||
const links = [
|
||||
{ text: '用户管理', icon: 'user', path: '/system/user', permission: 'system:user:list' },
|
||||
{ text: '角色管理', icon: 'user-group', path: '/system/role', permission: 'system:role:list' },
|
||||
{ text: '菜单管理', icon: 'menu', path: '/system/menu', permission: 'system:menu:list' },
|
||||
{ text: '文件管理', icon: 'file', path: '/system/file', permission: 'system:file:list' },
|
||||
{ text: '代码生成', icon: 'code', path: '/code/generator', permission: 'code:generator:list' },
|
||||
{ text: '系统日志', icon: 'history', path: '/monitor/log', permission: 'monitor:log:list' },
|
||||
]
|
||||
</script>
|
||||
|
||||
<style scoped lang="less"></style>
|
||||
41
src/views/dashboard/workplace/components/Welcome.vue
Normal file
41
src/views/dashboard/workplace/components/Welcome.vue
Normal file
@@ -0,0 +1,41 @@
|
||||
<template>
|
||||
<a-card class="card" :bordered="false">
|
||||
<a-row align="center" wrap :gutter="[{ xs: 0, sm: 14, md: 14, lg: 14, xl: 14, xxl: 14 }, 16]" class="content">
|
||||
<a-space size="medium">
|
||||
<Avatar :src="userStore.avatar" :name="userStore.nickname" :size="68" />
|
||||
<div class="welcome">
|
||||
<p class="hello">{{ goodTimeText() }}!{{ userStore.nickname }}</p>
|
||||
<p>北海虽赊,扶摇可接;东隅已逝,桑榆非晚。</p>
|
||||
</div>
|
||||
</a-space>
|
||||
</a-row>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useUserStore } from '@/stores'
|
||||
import { goodTimeText } from '@/utils'
|
||||
|
||||
const userStore = useUserStore()
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
:deep(.arco-statistic-title) {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
.card {
|
||||
.content {
|
||||
padding: 8px 20px;
|
||||
.welcome {
|
||||
margin: 8px 0;
|
||||
color: $color-text-3;
|
||||
.hello {
|
||||
font-size: 1.25rem;
|
||||
color: $color-text-1;
|
||||
margin-bottom: 10px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
132
src/views/dashboard/workplace/index.vue
Normal file
132
src/views/dashboard/workplace/index.vue
Normal file
@@ -0,0 +1,132 @@
|
||||
<template>
|
||||
<div class="gi_page container">
|
||||
<div class="left-side">
|
||||
<div class="panel">
|
||||
<Welcome />
|
||||
</div>
|
||||
<div style="margin-top: 14px">
|
||||
<a-grid :cols="24" :col-gap="14" :row-gap="14">
|
||||
<a-grid-item :span="24">
|
||||
<Project />
|
||||
</a-grid-item>
|
||||
<a-grid-item :span="24">
|
||||
<LatestActivity />
|
||||
</a-grid-item>
|
||||
</a-grid>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right-side">
|
||||
<a-grid :cols="24" :row-gap="14">
|
||||
<a-grid-item :span="24">
|
||||
<div class="panel moduler-wrap">
|
||||
<QuickOperation />
|
||||
</div>
|
||||
</a-grid-item>
|
||||
<a-grid-item class="panel" :span="24">
|
||||
<Carousel />
|
||||
</a-grid-item>
|
||||
<a-grid-item class="panel" :span="24">
|
||||
<Notice />
|
||||
</a-grid-item>
|
||||
<a-grid-item class="panel" :span="24">
|
||||
<Docs />
|
||||
</a-grid-item>
|
||||
</a-grid>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import Welcome from './components/Welcome.vue'
|
||||
import Project from './components/Project.vue'
|
||||
import LatestActivity from './components/LatestActivity.vue'
|
||||
import QuickOperation from './components/QuickOperation.vue'
|
||||
import Carousel from './components/Carousel.vue'
|
||||
import Notice from './components/Notice.vue'
|
||||
import Docs from './components/Docs.vue'
|
||||
|
||||
defineOptions({ name: 'Workplace' })
|
||||
</script>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.container {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.left-side {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
.right-side {
|
||||
width: 280px;
|
||||
margin-left: 14px;
|
||||
}
|
||||
|
||||
.panel {
|
||||
background-color: var(--color-bg-2);
|
||||
border-radius: 4px;
|
||||
overflow: auto;
|
||||
}
|
||||
:deep(.panel-border) {
|
||||
margin-bottom: 0;
|
||||
border-bottom: 1px solid rgb(var(--gray-2));
|
||||
}
|
||||
.moduler-wrap {
|
||||
border-radius: 4px;
|
||||
background-color: var(--color-bg-2);
|
||||
:deep(.text) {
|
||||
font-size: 12px;
|
||||
text-align: center;
|
||||
color: rgb(var(--gray-8));
|
||||
}
|
||||
|
||||
:deep(.wrapper) {
|
||||
margin-bottom: 8px;
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
|
||||
&:last-child {
|
||||
.text {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
&:hover {
|
||||
.icon {
|
||||
color: rgb(var(--arcoblue-6));
|
||||
background-color: #e8f3ff;
|
||||
}
|
||||
.text {
|
||||
color: rgb(var(--arcoblue-6));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.icon) {
|
||||
display: inline-block;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
margin-bottom: 4px;
|
||||
color: rgb(var(--dark-gray-1));
|
||||
line-height: 32px;
|
||||
font-size: 16px;
|
||||
text-align: center;
|
||||
background-color: rgb(var(--gray-1));
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="less" scoped>
|
||||
// responsive
|
||||
.mobile {
|
||||
.container {
|
||||
display: block;
|
||||
}
|
||||
.right-side {
|
||||
// display: none;
|
||||
width: 100%;
|
||||
margin-left: 0;
|
||||
margin-top: 16px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user