first commit

This commit is contained in:
zc
2026-02-26 17:31:18 +08:00
commit f1b87df6ca
803 changed files with 297148 additions and 0 deletions

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View File

@@ -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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>

View 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>