第一次提交
This commit is contained in:
72
src/views/dashboard/analysis/components/hot-search.vue
Normal file
72
src/views/dashboard/analysis/components/hot-search.vue
Normal file
@@ -0,0 +1,72 @@
|
||||
<template>
|
||||
<a-card :bordered="false" title="热门搜索">
|
||||
<v-chart
|
||||
ref="hotSearchChartRef"
|
||||
:option="hotSearchChartOption"
|
||||
style="height: 330px"
|
||||
/>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive } from 'vue';
|
||||
import { message } from 'ant-design-vue/es';
|
||||
import { use } from 'echarts/core';
|
||||
import type { EChartsCoreOption } from 'echarts/core';
|
||||
import { CanvasRenderer } from 'echarts/renderers';
|
||||
import { LineChart, BarChart } from 'echarts/charts';
|
||||
import { GridComponent, TooltipComponent } from 'echarts/components';
|
||||
import VChart from 'vue-echarts';
|
||||
import 'echarts-wordcloud';
|
||||
import { wordCloudColor } from 'ele-admin-pro/es';
|
||||
import { getWordCloudList } from '@/api/dashboard/analysis';
|
||||
import useEcharts from '@/utils/use-echarts';
|
||||
|
||||
use([CanvasRenderer, LineChart, BarChart, GridComponent, TooltipComponent]);
|
||||
|
||||
//
|
||||
const hotSearchChartRef = ref<InstanceType<typeof VChart> | null>(null);
|
||||
|
||||
useEcharts([hotSearchChartRef]);
|
||||
|
||||
// 词云图表配置
|
||||
const hotSearchChartOption: EChartsCoreOption = reactive({});
|
||||
|
||||
/* 获取词云数据 */
|
||||
const getWordCloudData = () => {
|
||||
getWordCloudList()
|
||||
.then((data) => {
|
||||
Object.assign(hotSearchChartOption, {
|
||||
tooltip: {
|
||||
show: true,
|
||||
confine: true,
|
||||
borderWidth: 1
|
||||
},
|
||||
series: [
|
||||
{
|
||||
type: 'wordCloud',
|
||||
width: '100%',
|
||||
height: '100%',
|
||||
sizeRange: [12, 24],
|
||||
gridSize: 6,
|
||||
textStyle: {
|
||||
color: wordCloudColor
|
||||
},
|
||||
emphasis: {
|
||||
textStyle: {
|
||||
shadowBlur: 8,
|
||||
shadowColor: 'rgba(0, 0, 0, .15)'
|
||||
}
|
||||
},
|
||||
data: data
|
||||
}
|
||||
]
|
||||
});
|
||||
})
|
||||
.catch((e) => {
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
getWordCloudData();
|
||||
</script>
|
||||
248
src/views/dashboard/analysis/components/sale-card.vue
Normal file
248
src/views/dashboard/analysis/components/sale-card.vue
Normal file
@@ -0,0 +1,248 @@
|
||||
<template>
|
||||
<a-card :bordered="false" :body-style="{ padding: 0 }">
|
||||
<a-tabs
|
||||
size="large"
|
||||
v-model:activeKey="saleSearch.type"
|
||||
class="monitor-card-tabs"
|
||||
@change="onSaleTypeChange"
|
||||
>
|
||||
<a-tab-pane tab="销售额" key="saleroom" />
|
||||
<a-tab-pane tab="访问量" key="visits" />
|
||||
<template #rightExtra>
|
||||
<a-space
|
||||
size="middle"
|
||||
:class="[
|
||||
'analysis-tabs-extra',
|
||||
{ 'hidden-lg-and-down': styleResponsive }
|
||||
]"
|
||||
>
|
||||
<a-radio-group v-model:value="saleSearch.dateType">
|
||||
<a-radio-button value="1">今天</a-radio-button>
|
||||
<a-radio-button value="2">本周</a-radio-button>
|
||||
<a-radio-button value="3">本月</a-radio-button>
|
||||
<a-radio-button value="4">本年</a-radio-button>
|
||||
</a-radio-group>
|
||||
<div style="width: 300px">
|
||||
<a-range-picker
|
||||
value-format="YYYY-MM-DD"
|
||||
v-model:value="saleSearch.datetime"
|
||||
/>
|
||||
</div>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-tabs>
|
||||
<div style="padding-bottom: 10px">
|
||||
<a-row :gutter="16">
|
||||
<a-col
|
||||
v-bind="
|
||||
styleResponsive ? { lg: 17, md: 15, sm: 24, xs: 24 } : { span: 17 }
|
||||
"
|
||||
>
|
||||
<div v-if="saleSearch.type === 'saleroom'" class="demo-monitor-title">
|
||||
销售量趋势
|
||||
</div>
|
||||
<div v-else class="demo-monitor-title">访问量趋势</div>
|
||||
<v-chart
|
||||
ref="saleChartRef"
|
||||
:option="saleChartOption"
|
||||
style="height: 320px"
|
||||
/>
|
||||
</a-col>
|
||||
<a-col
|
||||
v-bind="
|
||||
styleResponsive ? { lg: 7, md: 9, sm: 24, xs: 24 } : { span: 7 }
|
||||
"
|
||||
>
|
||||
<div v-if="saleSearch.type === 'saleroom'">
|
||||
<div class="demo-monitor-title">门店销售额排名</div>
|
||||
<div
|
||||
v-for="(item, index) in saleroomRankData"
|
||||
:key="index"
|
||||
class="demo-monitor-rank-item ele-cell"
|
||||
>
|
||||
<ele-tag
|
||||
shape="circle"
|
||||
:color="index < 3 ? '#314659' : ''"
|
||||
style="border: none"
|
||||
>
|
||||
{{ index + 1 }}
|
||||
</ele-tag>
|
||||
<div class="ele-cell-content ele-elip">{{ item.name }}</div>
|
||||
<div class="ele-text-secondary">{{ item.value }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-else>
|
||||
<div class="demo-monitor-title">门店访问量排名</div>
|
||||
<div
|
||||
v-for="(item, index) in visitsRankData"
|
||||
:key="index"
|
||||
class="demo-monitor-rank-item ele-cell"
|
||||
>
|
||||
<ele-tag
|
||||
shape="circle"
|
||||
:color="index < 3 ? '#314659' : ''"
|
||||
style="border: none"
|
||||
>
|
||||
{{ index + 1 }}
|
||||
</ele-tag>
|
||||
<div class="ele-cell-content ele-elip">{{ item.name }}</div>
|
||||
<div class="ele-text-secondary">{{ item.value }}</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive } from 'vue';
|
||||
import { message } from 'ant-design-vue/es';
|
||||
import { use } from 'echarts/core';
|
||||
import type { EChartsCoreOption } from 'echarts/core';
|
||||
import { CanvasRenderer } from 'echarts/renderers';
|
||||
import { BarChart } from 'echarts/charts';
|
||||
import { GridComponent, TooltipComponent } from 'echarts/components';
|
||||
import VChart from 'vue-echarts';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { getSaleroomList } from '@/api/dashboard/analysis';
|
||||
import type { SaleroomData } from '@/api/dashboard/analysis/model';
|
||||
import useEcharts from '@/utils/use-echarts';
|
||||
|
||||
use([CanvasRenderer, BarChart, GridComponent, TooltipComponent]);
|
||||
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
|
||||
//
|
||||
const saleChartRef = ref<InstanceType<typeof VChart> | null>(null);
|
||||
|
||||
useEcharts([saleChartRef]);
|
||||
|
||||
// 销售额柱状图配置
|
||||
const saleChartOption: EChartsCoreOption = reactive({});
|
||||
|
||||
// 门店销售排名数据
|
||||
const saleroomRankData = ref([
|
||||
{ name: '工专路 1 号店', value: '323,234' },
|
||||
{ name: '工专路 2 号店', value: '323,234' },
|
||||
{ name: '工专路 3 号店', value: '323,234' },
|
||||
{ name: '工专路 4 号店', value: '323,234' },
|
||||
{ name: '工专路 5 号店', value: '323,234' },
|
||||
{ name: '工专路 6 号店', value: '323,234' },
|
||||
{ name: '工专路 7 号店', value: '323,234' }
|
||||
]);
|
||||
|
||||
// 门店访问排名数据
|
||||
const visitsRankData = ref([
|
||||
{ name: '工专路 1 号店', value: '323,234' },
|
||||
{ name: '工专路 2 号店', value: '323,234' },
|
||||
{ name: '工专路 3 号店', value: '323,234' },
|
||||
{ name: '工专路 4 号店', value: '323,234' },
|
||||
{ name: '工专路 5 号店', value: '323,234' },
|
||||
{ name: '工专路 6 号店', value: '323,234' },
|
||||
{ name: '工专路 7 号店', value: '323,234' }
|
||||
]);
|
||||
|
||||
// 销售量趋势数据
|
||||
const saleroomData1 = ref<SaleroomData[]>([]);
|
||||
|
||||
// 访问量趋势数据
|
||||
const saleroomData2 = ref<SaleroomData[]>([]);
|
||||
|
||||
interface SaleSearchType {
|
||||
type: string;
|
||||
dateType: string;
|
||||
datetime: [string, string];
|
||||
}
|
||||
|
||||
// 销售量搜索参数
|
||||
const saleSearch = reactive<SaleSearchType>({
|
||||
type: 'saleroom',
|
||||
dateType: '1',
|
||||
datetime: ['2022-01-08', '2022-02-12']
|
||||
});
|
||||
|
||||
/* 获取销售量数据 */
|
||||
const getSaleroomData = () => {
|
||||
getSaleroomList()
|
||||
.then((data) => {
|
||||
saleroomData1.value = data.list1;
|
||||
saleroomData2.value = data.list2;
|
||||
onSaleTypeChange();
|
||||
})
|
||||
.catch((e) => {
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
/* 销售量tab选择改变事件 */
|
||||
const onSaleTypeChange = () => {
|
||||
if (saleSearch.type === 'saleroom') {
|
||||
Object.assign(saleChartOption, {
|
||||
tooltip: {
|
||||
trigger: 'axis'
|
||||
},
|
||||
xAxis: [
|
||||
{
|
||||
type: 'category',
|
||||
data: saleroomData1.value.map((d) => d.month)
|
||||
}
|
||||
],
|
||||
yAxis: [
|
||||
{
|
||||
type: 'value'
|
||||
}
|
||||
],
|
||||
series: [
|
||||
{
|
||||
type: 'bar',
|
||||
data: saleroomData1.value.map((d) => d.value)
|
||||
}
|
||||
]
|
||||
});
|
||||
} else {
|
||||
Object.assign(saleChartOption, {
|
||||
tooltip: {
|
||||
trigger: 'axis'
|
||||
},
|
||||
xAxis: [
|
||||
{
|
||||
type: 'category',
|
||||
data: saleroomData2.value.map((d) => d.month)
|
||||
}
|
||||
],
|
||||
yAxis: [
|
||||
{
|
||||
type: 'value'
|
||||
}
|
||||
],
|
||||
series: [
|
||||
{
|
||||
type: 'bar',
|
||||
data: saleroomData2.value.map((d) => d.value)
|
||||
}
|
||||
]
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
getSaleroomData();
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.monitor-card-tabs :deep(.ant-tabs-nav) {
|
||||
padding: 0 16px;
|
||||
}
|
||||
|
||||
.demo-monitor-title {
|
||||
padding: 6px 20px;
|
||||
}
|
||||
|
||||
.demo-monitor-rank-item {
|
||||
padding: 0 20px;
|
||||
margin-top: 18px;
|
||||
}
|
||||
</style>
|
||||
246
src/views/dashboard/analysis/components/statistics-card.vue
Normal file
246
src/views/dashboard/analysis/components/statistics-card.vue
Normal file
@@ -0,0 +1,246 @@
|
||||
<!-- 统计卡片 -->
|
||||
<template>
|
||||
<a-row :gutter="16">
|
||||
<a-col
|
||||
v-bind="styleResponsive ? { lg: 6, md: 12, sm: 24, xs: 24 } : { span: 6 }"
|
||||
>
|
||||
<a-card class="analysis-chart-card" :bordered="false">
|
||||
<div class="ele-text-secondary ele-cell">
|
||||
<div class="ele-cell-content">总销售额</div>
|
||||
<a-tooltip title="指标说明">
|
||||
<question-circle-outlined />
|
||||
</a-tooltip>
|
||||
</div>
|
||||
<h1 class="analysis-chart-card-num">¥ 126,560</h1>
|
||||
<div class="analysis-chart-card-content" style="padding-top: 16px">
|
||||
<a-space size="middle">
|
||||
<span class="analysis-trend-text">
|
||||
周同比12% <caret-up-outlined class="ele-text-danger" />
|
||||
</span>
|
||||
<span class="analysis-trend-text">
|
||||
日同比11% <caret-down-outlined class="ele-text-success" />
|
||||
</span>
|
||||
</a-space>
|
||||
</div>
|
||||
<a-divider />
|
||||
<div>日销售额 ¥12,423</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col
|
||||
v-bind="styleResponsive ? { lg: 6, md: 12, sm: 24, xs: 24 } : { span: 6 }"
|
||||
>
|
||||
<a-card class="analysis-chart-card" :bordered="false">
|
||||
<div class="ele-text-secondary ele-cell">
|
||||
<div class="ele-cell-content">访问量</div>
|
||||
<ele-tag color="red">日</ele-tag>
|
||||
</div>
|
||||
<h1 class="analysis-chart-card-num">8,846</h1>
|
||||
<v-chart
|
||||
ref="visitChartRef"
|
||||
:option="visitChartOption"
|
||||
style="height: 40px"
|
||||
/>
|
||||
<a-divider />
|
||||
<div>日访问量 1,234</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col
|
||||
v-bind="styleResponsive ? { lg: 6, md: 12, sm: 24, xs: 24 } : { span: 6 }"
|
||||
>
|
||||
<a-card class="analysis-chart-card" :bordered="false">
|
||||
<div class="ele-text-secondary ele-cell">
|
||||
<div class="ele-cell-content">支付笔数</div>
|
||||
<ele-tag color="blue">月</ele-tag>
|
||||
</div>
|
||||
<h1 class="analysis-chart-card-num">6,560</h1>
|
||||
<v-chart
|
||||
ref="payNumChartRef"
|
||||
:option="payNumChartOption"
|
||||
style="height: 40px"
|
||||
/>
|
||||
<a-divider />
|
||||
<div>转化率 60%</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col
|
||||
v-bind="styleResponsive ? { lg: 6, md: 12, sm: 24, xs: 24 } : { span: 6 }"
|
||||
>
|
||||
<a-card class="analysis-chart-card" :bordered="false">
|
||||
<div class="ele-text-secondary ele-cell">
|
||||
<div class="ele-cell-content">活动运营效果</div>
|
||||
<ele-tag color="green">周</ele-tag>
|
||||
</div>
|
||||
<h1 class="analysis-chart-card-num">78%</h1>
|
||||
<div class="analysis-chart-card-content" style="padding-top: 16px">
|
||||
<a-progress
|
||||
:percent="78"
|
||||
:show-info="false"
|
||||
stroke-color="#13c2c2"
|
||||
status="active"
|
||||
/>
|
||||
</div>
|
||||
<a-divider />
|
||||
<a-space size="middle">
|
||||
<span class="analysis-trend-text">
|
||||
周同比12% <caret-up-outlined class="ele-text-danger" />
|
||||
</span>
|
||||
<span class="analysis-trend-text">
|
||||
日同比11% <caret-down-outlined class="ele-text-success" />
|
||||
</span>
|
||||
</a-space>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive } from 'vue';
|
||||
import { message } from 'ant-design-vue/es';
|
||||
import {
|
||||
QuestionCircleOutlined,
|
||||
CaretUpOutlined,
|
||||
CaretDownOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
import { use } from 'echarts/core';
|
||||
import type { EChartsCoreOption } from 'echarts/core';
|
||||
import { CanvasRenderer } from 'echarts/renderers';
|
||||
import { LineChart, BarChart } from 'echarts/charts';
|
||||
import { GridComponent, TooltipComponent } from 'echarts/components';
|
||||
import VChart from 'vue-echarts';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { getPayNumList } from '@/api/dashboard/analysis';
|
||||
import useEcharts from '@/utils/use-echarts';
|
||||
|
||||
use([CanvasRenderer, LineChart, BarChart, GridComponent, TooltipComponent]);
|
||||
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
|
||||
//
|
||||
const visitChartRef = ref<InstanceType<typeof VChart> | null>(null);
|
||||
const payNumChartRef = ref<InstanceType<typeof VChart> | null>(null);
|
||||
|
||||
useEcharts([visitChartRef, payNumChartRef]);
|
||||
|
||||
// 访问量折线图配置
|
||||
const visitChartOption: EChartsCoreOption = reactive({});
|
||||
|
||||
// 支付笔数柱状图配置
|
||||
const payNumChartOption: EChartsCoreOption = reactive({});
|
||||
|
||||
/* 获取支付笔数数据 */
|
||||
const getPayNumData = () => {
|
||||
getPayNumList()
|
||||
.then((data) => {
|
||||
Object.assign(visitChartOption, {
|
||||
color: '#975fe5',
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
formatter:
|
||||
'<i class="ele-chart-dot" style="background: #975fe5;"></i>{b0}: {c0}'
|
||||
},
|
||||
grid: {
|
||||
top: 10,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0
|
||||
},
|
||||
xAxis: [
|
||||
{
|
||||
show: false,
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: data.map((d) => d.date)
|
||||
}
|
||||
],
|
||||
yAxis: [
|
||||
{
|
||||
show: false,
|
||||
type: 'value',
|
||||
splitLine: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
],
|
||||
series: [
|
||||
{
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
symbol: 'none',
|
||||
areaStyle: {
|
||||
opacity: 0.5
|
||||
},
|
||||
data: data.map((d) => d.value)
|
||||
}
|
||||
]
|
||||
});
|
||||
|
||||
Object.assign(payNumChartOption, {
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
formatter:
|
||||
'<i class="ele-chart-dot" style="background: #5b8ff9;"></i>{b0}: {c0}'
|
||||
},
|
||||
grid: {
|
||||
top: 10,
|
||||
bottom: 0,
|
||||
left: 0,
|
||||
right: 0
|
||||
},
|
||||
xAxis: [
|
||||
{
|
||||
show: false,
|
||||
type: 'category',
|
||||
data: data.map((d) => d.date)
|
||||
}
|
||||
],
|
||||
yAxis: [
|
||||
{
|
||||
show: false,
|
||||
type: 'value',
|
||||
splitLine: {
|
||||
show: false
|
||||
}
|
||||
}
|
||||
],
|
||||
series: [
|
||||
{
|
||||
type: 'bar',
|
||||
data: data.map((d) => d.value)
|
||||
}
|
||||
]
|
||||
});
|
||||
})
|
||||
.catch((e) => {
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
getPayNumData();
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.analysis-chart-card {
|
||||
:deep(.ant-card-body) {
|
||||
padding: 16px 22px 12px 22px;
|
||||
}
|
||||
|
||||
:deep(.ant-divider) {
|
||||
margin: 12px 0;
|
||||
}
|
||||
}
|
||||
|
||||
.analysis-chart-card-num {
|
||||
font-size: 30px;
|
||||
}
|
||||
|
||||
.analysis-chart-card-content {
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
.analysis-trend-text {
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
||||
101
src/views/dashboard/analysis/components/visit-hour.vue
Normal file
101
src/views/dashboard/analysis/components/visit-hour.vue
Normal file
@@ -0,0 +1,101 @@
|
||||
<template>
|
||||
<a-card
|
||||
:bordered="false"
|
||||
title="最近1小时访问情况"
|
||||
:body-style="{ padding: '16px 6px 0 0' }"
|
||||
>
|
||||
<v-chart
|
||||
ref="visitHourChartRef"
|
||||
:option="visitHourChartOption"
|
||||
style="height: 362px"
|
||||
/>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive } from 'vue';
|
||||
import { message } from 'ant-design-vue/es';
|
||||
import { use } from 'echarts/core';
|
||||
import type { EChartsCoreOption } from 'echarts/core';
|
||||
import { CanvasRenderer } from 'echarts/renderers';
|
||||
import { LineChart } from 'echarts/charts';
|
||||
import {
|
||||
GridComponent,
|
||||
TooltipComponent,
|
||||
LegendComponent
|
||||
} from 'echarts/components';
|
||||
import VChart from 'vue-echarts';
|
||||
import { getVisitHourList } from '@/api/dashboard/analysis';
|
||||
import useEcharts from '@/utils/use-echarts';
|
||||
|
||||
use([
|
||||
CanvasRenderer,
|
||||
LineChart,
|
||||
GridComponent,
|
||||
TooltipComponent,
|
||||
LegendComponent
|
||||
]);
|
||||
|
||||
//
|
||||
const visitHourChartRef = ref<InstanceType<typeof VChart> | null>(null);
|
||||
|
||||
useEcharts([visitHourChartRef]);
|
||||
|
||||
// 最近 1 小时访问情况折线图配置
|
||||
const visitHourChartOption: EChartsCoreOption = reactive({});
|
||||
|
||||
/* 获取最近 1 小时访问情况数据 */
|
||||
const getVisitHourData = () => {
|
||||
getVisitHourList()
|
||||
.then((data) => {
|
||||
Object.assign(visitHourChartOption, {
|
||||
tooltip: {
|
||||
trigger: 'axis'
|
||||
},
|
||||
legend: {
|
||||
data: ['浏览量', '访问量'],
|
||||
right: 20
|
||||
},
|
||||
xAxis: [
|
||||
{
|
||||
type: 'category',
|
||||
boundaryGap: false,
|
||||
data: data.map((d) => d.time)
|
||||
}
|
||||
],
|
||||
yAxis: [
|
||||
{
|
||||
type: 'value'
|
||||
}
|
||||
],
|
||||
series: [
|
||||
{
|
||||
name: '浏览量',
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
symbol: 'none',
|
||||
areaStyle: {
|
||||
opacity: 0.5
|
||||
},
|
||||
data: data.map((d) => d.views)
|
||||
},
|
||||
{
|
||||
name: '访问量',
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
symbol: 'none',
|
||||
areaStyle: {
|
||||
opacity: 0.5
|
||||
},
|
||||
data: data.map((d) => d.visits)
|
||||
}
|
||||
]
|
||||
});
|
||||
})
|
||||
.catch((e) => {
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
getVisitHourData();
|
||||
</script>
|
||||
41
src/views/dashboard/analysis/index.vue
Normal file
41
src/views/dashboard/analysis/index.vue
Normal file
@@ -0,0 +1,41 @@
|
||||
<template>
|
||||
<div class="ele-body ele-body-card">
|
||||
<statistics-card />
|
||||
<sale-card />
|
||||
<a-row :gutter="16">
|
||||
<a-col
|
||||
v-bind="
|
||||
styleResponsive ? { lg: 16, md: 14, sm: 24, xs: 24 } : { span: 16 }
|
||||
"
|
||||
>
|
||||
<visit-hour />
|
||||
</a-col>
|
||||
<a-col
|
||||
v-bind="
|
||||
styleResponsive ? { lg: 8, md: 10, sm: 24, xs: 24 } : { span: 8 }
|
||||
"
|
||||
>
|
||||
<hot-search />
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import StatisticsCard from './components/statistics-card.vue';
|
||||
import SaleCard from './components/sale-card.vue';
|
||||
import VisitHour from './components/visit-hour.vue';
|
||||
import HotSearch from './components/hot-search.vue';
|
||||
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'DashboardAnalysis'
|
||||
};
|
||||
</script>
|
||||
69
src/views/dashboard/monitor/components/browser-card.vue
Normal file
69
src/views/dashboard/monitor/components/browser-card.vue
Normal file
@@ -0,0 +1,69 @@
|
||||
<template>
|
||||
<a-card :bordered="false" title="浏览器分布" :body-style="{ padding: '0px' }">
|
||||
<v-chart
|
||||
ref="browserChartRef"
|
||||
:option="browserChartOption"
|
||||
style="height: 222px"
|
||||
/>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive } from 'vue';
|
||||
import { message } from 'ant-design-vue/es';
|
||||
import { use } from 'echarts/core';
|
||||
import type { EChartsCoreOption } from 'echarts/core';
|
||||
import { CanvasRenderer } from 'echarts/renderers';
|
||||
import { PieChart } from 'echarts/charts';
|
||||
import { TooltipComponent, LegendComponent } from 'echarts/components';
|
||||
import VChart from 'vue-echarts';
|
||||
import { getBrowserCountList } from '@/api/dashboard/monitor';
|
||||
import useEcharts from '@/utils/use-echarts';
|
||||
|
||||
use([CanvasRenderer, PieChart, TooltipComponent, LegendComponent]);
|
||||
|
||||
//
|
||||
const browserChartRef = ref<InstanceType<typeof VChart> | null>(null);
|
||||
|
||||
useEcharts([browserChartRef]);
|
||||
|
||||
// 浏览器分布饼图配置
|
||||
const browserChartOption: EChartsCoreOption = reactive({});
|
||||
|
||||
/* 获取用户浏览器分布数据 */
|
||||
const getBrowserCountData = () => {
|
||||
getBrowserCountList()
|
||||
.then((data) => {
|
||||
Object.assign(browserChartOption, {
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
confine: true,
|
||||
borderWidth: 1
|
||||
},
|
||||
legend: {
|
||||
bottom: 5,
|
||||
itemWidth: 10,
|
||||
itemHeight: 10,
|
||||
icon: 'circle',
|
||||
data: data.map((d) => d.name)
|
||||
},
|
||||
series: [
|
||||
{
|
||||
type: 'pie',
|
||||
radius: ['45%', '70%'],
|
||||
center: ['50%', '43%'],
|
||||
label: {
|
||||
show: false
|
||||
},
|
||||
data: data
|
||||
}
|
||||
]
|
||||
});
|
||||
})
|
||||
.catch((e) => {
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
getBrowserCountData();
|
||||
</script>
|
||||
147
src/views/dashboard/monitor/components/map-card.vue
Normal file
147
src/views/dashboard/monitor/components/map-card.vue
Normal file
@@ -0,0 +1,147 @@
|
||||
<template>
|
||||
<a-card :bordered="false" title="用户分布">
|
||||
<a-row>
|
||||
<a-col v-bind="styleResponsive ? { sm: 18, xs: 24 } : { span: 18 }">
|
||||
<v-chart
|
||||
ref="userCountMapChartRef"
|
||||
:option="userCountMapOption"
|
||||
style="height: 469px"
|
||||
/>
|
||||
</a-col>
|
||||
<a-col v-bind="styleResponsive ? { sm: 6, xs: 24 } : { span: 6 }">
|
||||
<div
|
||||
v-for="item in userCountDataRank"
|
||||
:key="item.name"
|
||||
class="monitor-user-count-item ele-cell"
|
||||
>
|
||||
<div>{{ item.name }}</div>
|
||||
<div class="ele-cell-content">
|
||||
<a-progress
|
||||
status="normal"
|
||||
:show-info="false"
|
||||
:percent="item.percent"
|
||||
/>
|
||||
</div>
|
||||
<div>{{ item.value }}</div>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive } from 'vue';
|
||||
import { message } from 'ant-design-vue/es';
|
||||
import { use, registerMap } from 'echarts/core';
|
||||
import type { EChartsCoreOption } from 'echarts/core';
|
||||
import { CanvasRenderer } from 'echarts/renderers';
|
||||
import { MapChart } from 'echarts/charts';
|
||||
import {
|
||||
VisualMapComponent,
|
||||
GeoComponent,
|
||||
TooltipComponent
|
||||
} from 'echarts/components';
|
||||
import VChart from 'vue-echarts';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import { getChinaMapData, getUserCountList } from '@/api/dashboard/monitor';
|
||||
import type { UserCount } from '@/api/dashboard/monitor/model';
|
||||
import useEcharts from '@/utils/use-echarts';
|
||||
|
||||
use([
|
||||
CanvasRenderer,
|
||||
MapChart,
|
||||
VisualMapComponent,
|
||||
GeoComponent,
|
||||
TooltipComponent
|
||||
]);
|
||||
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
|
||||
//
|
||||
const userCountMapChartRef = ref<InstanceType<typeof VChart> | null>(null);
|
||||
|
||||
useEcharts([userCountMapChartRef]);
|
||||
|
||||
// 用户分布前 10 名
|
||||
const userCountDataRank = ref<UserCount[]>([]);
|
||||
|
||||
// 用户分布地图配置
|
||||
const userCountMapOption: EChartsCoreOption = reactive({});
|
||||
|
||||
/* 获取中国地图数据并注册地图 */
|
||||
const registerChinaMap = () => {
|
||||
getChinaMapData()
|
||||
.then((data) => {
|
||||
registerMap('china', data);
|
||||
getUserCountData();
|
||||
})
|
||||
.catch((e) => {
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
/* 获取用户分布数据 */
|
||||
const getUserCountData = () => {
|
||||
getUserCountList()
|
||||
.then((data) => {
|
||||
const temp = data.sort((a, b) => b.value - a.value);
|
||||
const min = temp[temp.length - 1].value || 0;
|
||||
const max = temp[0].value || 1;
|
||||
//
|
||||
const list = temp.length > 10 ? temp.slice(0, 15) : temp;
|
||||
userCountDataRank.value = list.map((d) => {
|
||||
return {
|
||||
name: d.name,
|
||||
value: d.value,
|
||||
percent: (d.value / max) * 100
|
||||
};
|
||||
});
|
||||
//
|
||||
Object.assign(userCountMapOption, {
|
||||
tooltip: {
|
||||
trigger: 'item',
|
||||
borderWidth: 1
|
||||
},
|
||||
visualMap: {
|
||||
min: min,
|
||||
max: max,
|
||||
text: ['高', '低'],
|
||||
calculable: true
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '用户数',
|
||||
label: {
|
||||
show: true
|
||||
},
|
||||
type: 'map',
|
||||
map: 'china',
|
||||
data: data
|
||||
}
|
||||
]
|
||||
});
|
||||
})
|
||||
.catch((e) => {
|
||||
message.error(e.message);
|
||||
});
|
||||
};
|
||||
|
||||
registerChinaMap();
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.monitor-user-count-item {
|
||||
margin-bottom: 8px;
|
||||
|
||||
:deep(.ant-progress-inner) {
|
||||
background: none;
|
||||
}
|
||||
|
||||
.ele-cell-content {
|
||||
padding-right: 10px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
70
src/views/dashboard/monitor/components/online-num.vue
Normal file
70
src/views/dashboard/monitor/components/online-num.vue
Normal file
@@ -0,0 +1,70 @@
|
||||
<template>
|
||||
<a-card :bordered="false" title="在线人数">
|
||||
<div class="monitor-online-num-card">
|
||||
<div>{{ currentTime }}</div>
|
||||
<div class="monitor-online-num-title">
|
||||
<ele-count-up :end-val="onlineNum" />
|
||||
</div>
|
||||
<div class="monitor-online-num-text">在线总人数</div>
|
||||
<a-badge status="processing" :text="updateTimeText" />
|
||||
</div>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, onBeforeUnmount } from 'vue';
|
||||
import { toDateString } from 'ele-admin-pro/es';
|
||||
// 在线人数更新定时器
|
||||
let onlineNumTimer: number | null = null;
|
||||
|
||||
// 在线总人数倒计时
|
||||
const updateTime = ref(10);
|
||||
|
||||
// 当前时间
|
||||
const currentTime = ref(toDateString(new Date(), 'HH:mm:ss'));
|
||||
|
||||
// 在线人数
|
||||
const onlineNum = ref(228);
|
||||
|
||||
// 在线人数倒计时文字
|
||||
const updateTimeText = computed(() => updateTime.value + ' 秒后更新');
|
||||
|
||||
/* 在线人数更新倒计时 */
|
||||
const startUpdateOnlineNum = () => {
|
||||
onlineNumTimer = window.setInterval(() => {
|
||||
currentTime.value = toDateString(new Date(), 'HH:mm:ss');
|
||||
if (updateTime.value === 1) {
|
||||
updateTime.value = 10;
|
||||
onlineNum.value = 100 + Math.round(Math.random() * 900);
|
||||
} else {
|
||||
updateTime.value--;
|
||||
}
|
||||
}, 1000);
|
||||
};
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
// 销毁定时器
|
||||
if (onlineNumTimer) {
|
||||
clearInterval(onlineNumTimer);
|
||||
onlineNumTimer = null;
|
||||
}
|
||||
});
|
||||
|
||||
startUpdateOnlineNum();
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.monitor-online-num-card {
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.monitor-online-num-title {
|
||||
line-height: 1;
|
||||
font-size: 50px;
|
||||
margin: 22px 0 14px;
|
||||
}
|
||||
|
||||
.monitor-online-num-text {
|
||||
margin-bottom: 22px;
|
||||
}
|
||||
</style>
|
||||
166
src/views/dashboard/monitor/components/statistics-card.vue
Normal file
166
src/views/dashboard/monitor/components/statistics-card.vue
Normal file
@@ -0,0 +1,166 @@
|
||||
<!-- 统计卡片 -->
|
||||
<template>
|
||||
<a-row :gutter="16">
|
||||
<a-col
|
||||
v-bind="styleResponsive ? { lg: 6, md: 12, sm: 12, xs: 24 } : { span: 6 }"
|
||||
>
|
||||
<a-card :bordered="false" class="monitor-count-card">
|
||||
<ele-tag color="blue" shape="circle" size="large">
|
||||
<eye-filled />
|
||||
</ele-tag>
|
||||
<h1 class="monitor-count-card-num">21.2 k</h1>
|
||||
<div class="monitor-count-card-text">总访问人数</div>
|
||||
<ele-avatar-list :data="visitUsers" size="small" :max="4" />
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col
|
||||
v-bind="styleResponsive ? { lg: 6, md: 12, sm: 12, xs: 24 } : { span: 6 }"
|
||||
>
|
||||
<a-card :bordered="false" class="monitor-count-card">
|
||||
<ele-tag color="orange" shape="circle" size="large">
|
||||
<fire-filled />
|
||||
</ele-tag>
|
||||
<h1 class="monitor-count-card-num">1.6 k</h1>
|
||||
<div class="monitor-count-card-text">点击量(近30天)</div>
|
||||
<div class="monitor-count-card-trend ele-text-success">
|
||||
<up-outlined />
|
||||
<span>110.5%</span>
|
||||
</div>
|
||||
<a-tooltip title="指标说明">
|
||||
<question-circle-outlined class="monitor-count-card-tips" />
|
||||
</a-tooltip>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col
|
||||
v-bind="styleResponsive ? { lg: 6, md: 12, sm: 12, xs: 24 } : { span: 6 }"
|
||||
>
|
||||
<a-card :bordered="false" class="monitor-count-card">
|
||||
<ele-tag color="red" shape="circle" size="large">
|
||||
<flag-filled />
|
||||
</ele-tag>
|
||||
<h1 class="monitor-count-card-num">826.0</h1>
|
||||
<div class="monitor-count-card-text">到达量(近30天)</div>
|
||||
<div class="monitor-count-card-trend ele-text-danger">
|
||||
<down-outlined />
|
||||
<span>15.5%</span>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-col>
|
||||
<a-col
|
||||
v-bind="styleResponsive ? { lg: 6, md: 12, sm: 12, xs: 24 } : { span: 6 }"
|
||||
>
|
||||
<a-card :bordered="false" class="monitor-count-card">
|
||||
<ele-tag color="green" shape="circle" size="large">
|
||||
<thunderbolt-filled />
|
||||
</ele-tag>
|
||||
<h1 class="monitor-count-card-num">28.8 %</h1>
|
||||
<div class="monitor-count-card-text">转化率(近30天)</div>
|
||||
<div class="monitor-count-card-trend ele-text-success">
|
||||
<up-outlined />
|
||||
<span>65.8%</span>
|
||||
</div>
|
||||
<a-tooltip title="指标说明">
|
||||
<question-circle-outlined class="monitor-count-card-tips" />
|
||||
</a-tooltip>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import {
|
||||
QuestionCircleOutlined,
|
||||
EyeFilled,
|
||||
FireFilled,
|
||||
FlagFilled,
|
||||
ThunderboltFilled,
|
||||
UpOutlined,
|
||||
DownOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
|
||||
interface VisitUserType {
|
||||
key: string | number;
|
||||
name: string;
|
||||
avatar: string;
|
||||
}
|
||||
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
|
||||
// 访问人数
|
||||
const visitUsers = ref<VisitUserType[]>([
|
||||
{
|
||||
key: 1,
|
||||
name: 'SunSmile',
|
||||
avatar:
|
||||
'https://cdn.eleadmin.com/20200609/c184eef391ae48dba87e3057e70238fb.jpg'
|
||||
},
|
||||
{
|
||||
key: 2,
|
||||
name: '你的名字很好听',
|
||||
avatar:
|
||||
'https://cdn.eleadmin.com/20200609/b6a811873e704db49db994053a5019b2.jpg'
|
||||
},
|
||||
{
|
||||
key: 3,
|
||||
name: '全村人的希望',
|
||||
avatar:
|
||||
'https://cdn.eleadmin.com/20200609/948344a2a77c47a7a7b332fe12ff749a.jpg'
|
||||
},
|
||||
{
|
||||
key: 4,
|
||||
name: 'Jasmine',
|
||||
avatar:
|
||||
'https://cdn.eleadmin.com/20200609/f6bc05af944a4f738b54128717952107.jpg'
|
||||
},
|
||||
{
|
||||
key: 5,
|
||||
name: '酷酷的大叔',
|
||||
avatar:
|
||||
'https://cdn.eleadmin.com/20200609/2d98970a51b34b6b859339c96b240dcd.jpg'
|
||||
},
|
||||
{
|
||||
key: 6,
|
||||
name: '管理员',
|
||||
avatar: 'https://cdn.eleadmin.com/20200610/avatar.jpg'
|
||||
}
|
||||
]);
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.monitor-count-card {
|
||||
text-align: center;
|
||||
|
||||
.monitor-count-card-num {
|
||||
margin-top: 6px;
|
||||
font-size: 32px;
|
||||
}
|
||||
|
||||
.monitor-count-card-text {
|
||||
font-size: 12px;
|
||||
margin: 8px 0;
|
||||
opacity: 0.8;
|
||||
}
|
||||
|
||||
.monitor-count-card-trend {
|
||||
font-weight: bold;
|
||||
line-height: 26px;
|
||||
|
||||
& > .anticon {
|
||||
margin-right: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
.monitor-count-card-tips {
|
||||
position: absolute;
|
||||
top: 16px;
|
||||
right: 16px;
|
||||
cursor: pointer;
|
||||
opacity: 0.6;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
75
src/views/dashboard/monitor/components/user-liveness.vue
Normal file
75
src/views/dashboard/monitor/components/user-liveness.vue
Normal file
@@ -0,0 +1,75 @@
|
||||
<template>
|
||||
<a-card
|
||||
:bordered="false"
|
||||
title="用户活跃度"
|
||||
:body-style="{ padding: '56px 0' }"
|
||||
>
|
||||
<div class="ele-cell">
|
||||
<div class="ele-cell-content ele-text-center">
|
||||
<div class="monitor-progress-group">
|
||||
<a-progress
|
||||
type="circle"
|
||||
:percent="70"
|
||||
stroke-color="#52c41a"
|
||||
:show-info="false"
|
||||
:width="161"
|
||||
/>
|
||||
<a-progress
|
||||
type="circle"
|
||||
:percent="60"
|
||||
stroke-color="#1890ff"
|
||||
:show-info="false"
|
||||
:width="121"
|
||||
:stroke-width="5"
|
||||
/>
|
||||
<a-progress
|
||||
type="circle"
|
||||
:percent="35"
|
||||
stroke-color="#f5222d"
|
||||
:show-info="false"
|
||||
:width="91"
|
||||
:stroke-width="4"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="monitor-progress-legends">
|
||||
<div class="ele-text-small ele-elip">
|
||||
<a-badge color="green" text="活跃率: 70%" />
|
||||
</div>
|
||||
<div class="ele-text-small ele-elip">
|
||||
<a-badge color="blue" text="留存率: 60%" />
|
||||
</div>
|
||||
<div class="ele-text-small ele-elip">
|
||||
<a-badge color="red" text="跳出率: 35%" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.monitor-progress-group {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
|
||||
.ant-progress:not(:first-child) {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
margin-top: -1px;
|
||||
}
|
||||
}
|
||||
|
||||
.monitor-progress-legends {
|
||||
padding-right: 24px;
|
||||
|
||||
:deep(.ant-badge-status-text) {
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
& > div + div {
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
86
src/views/dashboard/monitor/components/user-rate.vue
Normal file
86
src/views/dashboard/monitor/components/user-rate.vue
Normal file
@@ -0,0 +1,86 @@
|
||||
<template>
|
||||
<a-card :bordered="false" title="用户评价">
|
||||
<div class="ele-cell ele-cell-align-bottom">
|
||||
<div style="font-size: 51px; line-height: 1">4.5</div>
|
||||
<div class="ele-cell-content">
|
||||
<a-rate :value="userRate" disabled />
|
||||
<span style="color: #fadb14; margin-left: 8px">很棒</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ele-cell" style="margin: 18px 0">
|
||||
<div style="font-size: 28px; line-height: 1" class="ele-text-placeholder">
|
||||
-0%
|
||||
</div>
|
||||
<div class="ele-cell-content ele-text-small ele-text-secondary">
|
||||
当前没有评价波动
|
||||
</div>
|
||||
</div>
|
||||
<div class="ele-cell">
|
||||
<div class="ele-cell-content">
|
||||
<a-progress :percent="60" stroke-color="#52c41a" :show-info="false" />
|
||||
</div>
|
||||
<div class="monitor-evaluate-text">
|
||||
<star-filled />
|
||||
<span>5 : 368 人</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ele-cell">
|
||||
<div class="ele-cell-content">
|
||||
<a-progress :percent="40" stroke-color="#1890ff" :show-info="false" />
|
||||
</div>
|
||||
<div class="monitor-evaluate-text">
|
||||
<star-filled />
|
||||
<span>4 : 256 人</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ele-cell">
|
||||
<div class="ele-cell-content">
|
||||
<a-progress :percent="20" stroke-color="#faad14" :show-info="false" />
|
||||
</div>
|
||||
<div class="monitor-evaluate-text">
|
||||
<star-filled />
|
||||
<span>3 : 49 人</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ele-cell">
|
||||
<div class="ele-cell-content">
|
||||
<a-progress :percent="10" stroke-color="#f5222d" :show-info="false" />
|
||||
</div>
|
||||
<div class="monitor-evaluate-text">
|
||||
<star-filled />
|
||||
<span>2 : 14 人</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ele-cell">
|
||||
<div class="ele-cell-content">
|
||||
<a-progress :percent="0" :show-info="false" />
|
||||
</div>
|
||||
<div class="monitor-evaluate-text">
|
||||
<star-filled />
|
||||
<span>1 : 0 人</span>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { StarFilled } from '@ant-design/icons-vue';
|
||||
|
||||
// 用户评分
|
||||
const userRate = ref(4.5);
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.monitor-evaluate-text {
|
||||
width: 90px;
|
||||
flex-shrink: 0;
|
||||
white-space: nowrap;
|
||||
opacity: 0.8;
|
||||
|
||||
& > .anticon {
|
||||
font-size: 12px;
|
||||
margin: 0 6px 0 8px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
79
src/views/dashboard/monitor/components/user-satisfaction.vue
Normal file
79
src/views/dashboard/monitor/components/user-satisfaction.vue
Normal file
@@ -0,0 +1,79 @@
|
||||
<template>
|
||||
<a-card :bordered="false" title="用户满意度">
|
||||
<div class="ele-cell ele-text-center">
|
||||
<div class="ele-cell-content" style="font-size: 24px">856</div>
|
||||
<div class="ele-cell-content">
|
||||
<div class="monitor-face-smile"><i></i></div>
|
||||
<div class="ele-text-secondary ele-elip" style="margin-top: 8px">
|
||||
正面评论
|
||||
</div>
|
||||
</div>
|
||||
<h2 class="ele-cell-content ele-text-success">82%</h2>
|
||||
</div>
|
||||
<a-divider style="margin: 26px 0" />
|
||||
<div class="ele-cell ele-text-center">
|
||||
<div class="ele-cell-content" style="font-size: 24px">60</div>
|
||||
<div class="ele-cell-content">
|
||||
<div class="monitor-face-cry"><i></i></div>
|
||||
<div class="ele-text-secondary ele-elip" style="margin-top: 8px">
|
||||
负面评论
|
||||
</div>
|
||||
</div>
|
||||
<h2 class="ele-cell-content ele-text-danger">9%</h2>
|
||||
</div>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.monitor-face-smile,
|
||||
.monitor-face-cry {
|
||||
width: 50px;
|
||||
height: 50px;
|
||||
display: inline-block;
|
||||
background: #fbd971;
|
||||
border-radius: 50%;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.monitor-face-smile > i,
|
||||
.monitor-face-smile:before,
|
||||
.monitor-face-smile:after,
|
||||
.monitor-face-cry > i,
|
||||
.monitor-face-cry:before,
|
||||
.monitor-face-cry:after {
|
||||
width: 28px;
|
||||
height: 28px;
|
||||
border-radius: 50%;
|
||||
transform: rotate(225deg);
|
||||
border: 3px solid #f0c419;
|
||||
border-right-color: transparent !important;
|
||||
border-bottom-color: transparent !important;
|
||||
position: absolute;
|
||||
bottom: 8px;
|
||||
left: 11px;
|
||||
}
|
||||
|
||||
.monitor-face-smile:before,
|
||||
.monitor-face-smile:after,
|
||||
.monitor-face-cry:before,
|
||||
.monitor-face-cry:after {
|
||||
content: '';
|
||||
width: 12px;
|
||||
height: 12px;
|
||||
left: 8px;
|
||||
top: 14px;
|
||||
border-color: #f29c1f;
|
||||
transform: rotate(45deg);
|
||||
}
|
||||
|
||||
.monitor-face-smile:after,
|
||||
.monitor-face-cry:after {
|
||||
left: auto;
|
||||
right: 8px;
|
||||
}
|
||||
|
||||
.monitor-face-cry > i {
|
||||
transform: rotate(45deg);
|
||||
bottom: -6px;
|
||||
}
|
||||
</style>
|
||||
91
src/views/dashboard/monitor/index.vue
Normal file
91
src/views/dashboard/monitor/index.vue
Normal file
@@ -0,0 +1,91 @@
|
||||
<template>
|
||||
<div class="ele-body ele-body-card">
|
||||
<statistics-card />
|
||||
<a-row :gutter="16">
|
||||
<a-col
|
||||
v-bind="
|
||||
styleResponsive ? { lg: 18, md: 24, sm: 24, xs: 24 } : { span: 18 }
|
||||
"
|
||||
>
|
||||
<map-card />
|
||||
</a-col>
|
||||
<a-col
|
||||
v-bind="
|
||||
styleResponsive ? { lg: 6, md: 24, sm: 24, xs: 24 } : { span: 6 }
|
||||
"
|
||||
>
|
||||
<a-row :gutter="16">
|
||||
<a-col
|
||||
v-bind="
|
||||
styleResponsive
|
||||
? { lg: 24, md: 12, sm: 12, xs: 24 }
|
||||
: { span: 24 }
|
||||
"
|
||||
>
|
||||
<online-num />
|
||||
</a-col>
|
||||
<a-col
|
||||
v-bind="
|
||||
styleResponsive
|
||||
? { lg: 24, md: 12, sm: 12, xs: 24 }
|
||||
: { span: 24 }
|
||||
"
|
||||
>
|
||||
<browser-card />
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="16">
|
||||
<a-col
|
||||
v-bind="
|
||||
styleResponsive
|
||||
? { xl: 12, lg: 24, md: 24, sm: 24, xs: 24 }
|
||||
: { span: 12 }
|
||||
"
|
||||
>
|
||||
<user-rate />
|
||||
</a-col>
|
||||
<a-col
|
||||
v-bind="
|
||||
styleResponsive
|
||||
? { xl: 6, lg: 12, md: 12, sm: 12, xs: 24 }
|
||||
: { span: 6 }
|
||||
"
|
||||
>
|
||||
<user-satisfaction />
|
||||
</a-col>
|
||||
<a-col
|
||||
v-bind="
|
||||
styleResponsive
|
||||
? { xl: 6, lg: 12, md: 12, sm: 12, xs: 24 }
|
||||
: { span: 6 }
|
||||
"
|
||||
>
|
||||
<user-liveness />
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { storeToRefs } from 'pinia';
|
||||
import { useThemeStore } from '@/store/modules/theme';
|
||||
import StatisticsCard from './components/statistics-card.vue';
|
||||
import MapCard from './components/map-card.vue';
|
||||
import OnlineNum from './components/online-num.vue';
|
||||
import BrowserCard from './components/browser-card.vue';
|
||||
import UserRate from './components/user-rate.vue';
|
||||
import UserSatisfaction from './components/user-satisfaction.vue';
|
||||
import UserLiveness from './components/user-liveness.vue';
|
||||
|
||||
// 是否开启响应式布局
|
||||
const themeStore = useThemeStore();
|
||||
const { styleResponsive } = storeToRefs(themeStore);
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'DashboardMonitor'
|
||||
};
|
||||
</script>
|
||||
138
src/views/dashboard/workplace/components/activities-card.vue
Normal file
138
src/views/dashboard/workplace/components/activities-card.vue
Normal file
@@ -0,0 +1,138 @@
|
||||
<!-- 最新动态 -->
|
||||
<template>
|
||||
<a-card :title="title" :bordered="false" :body-style="{ padding: '6px 0' }">
|
||||
<template #extra>
|
||||
<more-icon @remove="onRemove" @edit="onEdit" />
|
||||
</template>
|
||||
<div
|
||||
style="height: 346px; padding: 22px 20px 0 20px"
|
||||
class="ele-scrollbar-hover"
|
||||
>
|
||||
<a-timeline>
|
||||
<a-timeline-item
|
||||
v-for="item in activities"
|
||||
:key="item.id"
|
||||
:color="item.color"
|
||||
>
|
||||
<em>{{ item.time }}</em>
|
||||
<em>{{ item.title }}</em>
|
||||
</a-timeline-item>
|
||||
</a-timeline>
|
||||
</div>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import MoreIcon from './more-icon.vue';
|
||||
|
||||
defineProps<{
|
||||
title?: string;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'remove'): void;
|
||||
(e: 'edit'): void;
|
||||
}>();
|
||||
|
||||
interface Activitie {
|
||||
id: number;
|
||||
title: string;
|
||||
time: string;
|
||||
color?: string;
|
||||
}
|
||||
|
||||
// 最新动态数据
|
||||
const activities = ref<Activitie[]>([]);
|
||||
|
||||
/* 查询最新动态 */
|
||||
const queryActivities = () => {
|
||||
activities.value = [
|
||||
{
|
||||
id: 1,
|
||||
title: 'SunSmile 解决了bug 登录提示操作失败',
|
||||
time: '20:30',
|
||||
color: 'gray'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
title: 'Jasmine 解决了bug 按钮颜色与设计不符',
|
||||
time: '19:30',
|
||||
color: 'gray'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
title: '项目经理 指派了任务 解决项目一的bug',
|
||||
time: '18:30'
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
title: '项目经理 指派了任务 解决项目二的bug',
|
||||
time: '17:30'
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
title: '项目经理 指派了任务 解决项目三的bug',
|
||||
time: '16:30'
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
title: '项目经理 指派了任务 解决项目四的bug',
|
||||
time: '15:30',
|
||||
color: 'gray'
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
title: '项目经理 指派了任务 解决项目五的bug',
|
||||
time: '14:30',
|
||||
color: 'gray'
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
title: '项目经理 指派了任务 解决项目六的bug',
|
||||
time: '12:30',
|
||||
color: 'gray'
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
title: '项目经理 指派了任务 解决项目七的bug',
|
||||
time: '11:30'
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
title: '项目经理 指派了任务 解决项目八的bug',
|
||||
time: '10:30',
|
||||
color: 'gray'
|
||||
},
|
||||
{
|
||||
id: 11,
|
||||
title: '项目经理 指派了任务 解决项目九的bug',
|
||||
time: '09:30',
|
||||
color: 'green'
|
||||
},
|
||||
{
|
||||
id: 12,
|
||||
title: '项目经理 指派了任务 解决项目十的bug',
|
||||
time: '08:30',
|
||||
color: 'red'
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
const onRemove = () => {
|
||||
emit('remove');
|
||||
};
|
||||
|
||||
const onEdit = () => {
|
||||
emit('edit');
|
||||
};
|
||||
|
||||
queryActivities();
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.ele-scrollbar-hover
|
||||
:deep(.ant-timeline-item-last > .ant-timeline-item-content) {
|
||||
min-height: auto;
|
||||
}
|
||||
</style>
|
||||
70
src/views/dashboard/workplace/components/docs.vue
Normal file
70
src/views/dashboard/workplace/components/docs.vue
Normal file
@@ -0,0 +1,70 @@
|
||||
<!-- 本月目标 -->
|
||||
<template>
|
||||
<a-card :title="title" :bordered="false">
|
||||
<template #extra>
|
||||
<more-icon @remove="onRemove" @edit="onEdit" />
|
||||
</template>
|
||||
<div class="workplace-goal-group">
|
||||
<a-progress
|
||||
:width="180"
|
||||
:percent="80"
|
||||
type="dashboard"
|
||||
:stroke-width="4"
|
||||
:show-info="false"
|
||||
/>
|
||||
<div class="workplace-goal-content">
|
||||
<ele-tag color="blue" size="large" shape="circle">
|
||||
<trophy-outlined />
|
||||
</ele-tag>
|
||||
<div class="workplace-goal-num">285</div>
|
||||
</div>
|
||||
<div class="workplace-goal-text">恭喜, 本月目标已达标!</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { TrophyOutlined } from '@ant-design/icons-vue';
|
||||
import MoreIcon from './more-icon.vue';
|
||||
|
||||
defineProps<{
|
||||
title?: string;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'remove'): void;
|
||||
(e: 'edit'): void;
|
||||
}>();
|
||||
|
||||
const onRemove = () => {
|
||||
emit('remove');
|
||||
};
|
||||
|
||||
const onEdit = () => {
|
||||
emit('edit');
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.workplace-goal-group {
|
||||
height: 310px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
|
||||
.workplace-goal-content {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 180px;
|
||||
margin: -50px 0 0 -90px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.workplace-goal-num {
|
||||
font-size: 40px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
70
src/views/dashboard/workplace/components/goal-card.vue
Normal file
70
src/views/dashboard/workplace/components/goal-card.vue
Normal file
@@ -0,0 +1,70 @@
|
||||
<!-- 本月目标 -->
|
||||
<template>
|
||||
<a-card :title="title" :bordered="false">
|
||||
<template #extra>
|
||||
<more-icon @remove="onRemove" @edit="onEdit" />
|
||||
</template>
|
||||
<div class="workplace-goal-group">
|
||||
<a-progress
|
||||
:width="180"
|
||||
:percent="80"
|
||||
type="dashboard"
|
||||
:stroke-width="4"
|
||||
:show-info="false"
|
||||
/>
|
||||
<div class="workplace-goal-content">
|
||||
<ele-tag color="blue" size="large" shape="circle">
|
||||
<trophy-outlined />
|
||||
</ele-tag>
|
||||
<div class="workplace-goal-num">285</div>
|
||||
</div>
|
||||
<div class="workplace-goal-text">恭喜, 本月目标已达标!</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { TrophyOutlined } from '@ant-design/icons-vue';
|
||||
import MoreIcon from './more-icon.vue';
|
||||
|
||||
defineProps<{
|
||||
title?: string;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'remove'): void;
|
||||
(e: 'edit'): void;
|
||||
}>();
|
||||
|
||||
const onRemove = () => {
|
||||
emit('remove');
|
||||
};
|
||||
|
||||
const onEdit = () => {
|
||||
emit('edit');
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.workplace-goal-group {
|
||||
height: 310px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
position: relative;
|
||||
|
||||
.workplace-goal-content {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
width: 180px;
|
||||
margin: -50px 0 0 -90px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.workplace-goal-num {
|
||||
font-size: 40px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
152
src/views/dashboard/workplace/components/link-card.vue
Normal file
152
src/views/dashboard/workplace/components/link-card.vue
Normal file
@@ -0,0 +1,152 @@
|
||||
<!-- 快捷方式 -->
|
||||
<template>
|
||||
<a-row :gutter="16" ref="wrapRef">
|
||||
<a-col v-for="item in data" :key="item.url" :lg="3" :md="6" :sm="9" :xs="8">
|
||||
<a-card :bordered="false" hoverable :body-style="{ padding: 0 }">
|
||||
<router-link :to="item.url" class="app-link-block">
|
||||
<component
|
||||
:is="item.icon"
|
||||
class="app-link-icon"
|
||||
:style="{ color: item.color }"
|
||||
/>
|
||||
<div class="app-link-title">{{ item.title }}</div>
|
||||
</router-link>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted, onBeforeUnmount } from 'vue';
|
||||
import SortableJs from 'sortablejs';
|
||||
import type { Row as ARow } from 'ant-design-vue';
|
||||
const CACHE_KEY = 'workplace-links';
|
||||
|
||||
interface LinkItem {
|
||||
icon: string;
|
||||
title: string;
|
||||
url: string;
|
||||
color?: string;
|
||||
}
|
||||
|
||||
// 默认顺序
|
||||
const DEFAULT: LinkItem[] = [
|
||||
{
|
||||
icon: 'ChromeOutlined',
|
||||
title: '网址导航',
|
||||
url: '/apps/link'
|
||||
},
|
||||
{
|
||||
icon: 'LaptopOutlined',
|
||||
title: '项目管理',
|
||||
url: '/oa/project'
|
||||
},
|
||||
{
|
||||
icon: 'ShoppingOutlined',
|
||||
title: '商城系统',
|
||||
url: '/shop/goods'
|
||||
},
|
||||
{
|
||||
icon: 'FileSearchOutlined',
|
||||
title: 'CMS内容管理系统',
|
||||
url: '/cms/article'
|
||||
},
|
||||
{
|
||||
icon: 'settingOutlined',
|
||||
title: '系统设置',
|
||||
url: '/system/user'
|
||||
},
|
||||
{
|
||||
icon: 'AppstoreAddOutlined',
|
||||
title: '扩展插件',
|
||||
url: '/system/appstore'
|
||||
},
|
||||
{
|
||||
icon: 'TeamOutlined',
|
||||
title: '用户管理',
|
||||
url: '/system/user'
|
||||
},
|
||||
{
|
||||
icon: 'DesktopOutlined',
|
||||
title: '网站首页',
|
||||
url: '/apps/home/index'
|
||||
}
|
||||
];
|
||||
|
||||
// 获取缓存的顺序
|
||||
const cache = (() => {
|
||||
const str = localStorage.getItem(CACHE_KEY);
|
||||
try {
|
||||
return str ? JSON.parse(str) : null;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
})();
|
||||
|
||||
const data = ref<LinkItem[]>([...(cache ?? DEFAULT)]);
|
||||
|
||||
const wrapRef = ref<InstanceType<typeof ARow> | null>(null);
|
||||
|
||||
let sortableIns: SortableJs | null = null;
|
||||
|
||||
/* 重置布局 */
|
||||
const reset = () => {
|
||||
data.value = [...DEFAULT];
|
||||
cacheData();
|
||||
};
|
||||
|
||||
/* 缓存布局 */
|
||||
const cacheData = () => {
|
||||
localStorage.setItem(CACHE_KEY, JSON.stringify(data.value));
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
const isTouchDevice = 'ontouchstart' in document.documentElement;
|
||||
if (isTouchDevice) {
|
||||
return;
|
||||
}
|
||||
sortableIns = new SortableJs(wrapRef.value?.$el, {
|
||||
animation: 300,
|
||||
onUpdate: ({ oldIndex, newIndex }) => {
|
||||
if (typeof oldIndex === 'number' && typeof newIndex === 'number') {
|
||||
const temp = [...data.value];
|
||||
temp.splice(newIndex, 0, temp.splice(oldIndex, 1)[0]);
|
||||
data.value = temp;
|
||||
cacheData();
|
||||
}
|
||||
},
|
||||
setData: () => {}
|
||||
});
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (sortableIns) {
|
||||
sortableIns.destroy();
|
||||
}
|
||||
});
|
||||
|
||||
defineExpose({ reset });
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import * as icons from './link-icons';
|
||||
|
||||
export default {
|
||||
components: icons
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.app-link-block {
|
||||
padding: 12px;
|
||||
text-align: center;
|
||||
display: block;
|
||||
color: inherit;
|
||||
|
||||
.app-link-icon {
|
||||
color: #666666;
|
||||
font-size: 30px;
|
||||
margin: 6px 0 10px 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
11
src/views/dashboard/workplace/components/link-icons.ts
Normal file
11
src/views/dashboard/workplace/components/link-icons.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
export {
|
||||
UserOutlined,
|
||||
TeamOutlined,
|
||||
FileSearchOutlined,
|
||||
ChromeOutlined,
|
||||
ShoppingOutlined,
|
||||
LaptopOutlined,
|
||||
AppstoreAddOutlined,
|
||||
DesktopOutlined,
|
||||
SettingOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
38
src/views/dashboard/workplace/components/more-icon.vue
Normal file
38
src/views/dashboard/workplace/components/more-icon.vue
Normal file
@@ -0,0 +1,38 @@
|
||||
<template>
|
||||
<a-dropdown placement="bottomRight">
|
||||
<more-outlined class="ele-text-secondary" style="font-size: 18px" />
|
||||
<template #overlay>
|
||||
<a-menu :selectable="false" @click="onClick">
|
||||
<a-menu-item key="edit">
|
||||
<div class="ele-cell">
|
||||
<edit-outlined />
|
||||
<div class="ele-cell-content">编辑</div>
|
||||
</div>
|
||||
</a-menu-item>
|
||||
<a-menu-item key="remove">
|
||||
<div class="ele-cell ele-text-danger">
|
||||
<delete-outlined />
|
||||
<div class="ele-cell-content">删除</div>
|
||||
</div>
|
||||
</a-menu-item>
|
||||
</a-menu>
|
||||
</template>
|
||||
</a-dropdown>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {
|
||||
MoreOutlined,
|
||||
EditOutlined,
|
||||
DeleteOutlined
|
||||
} from '@ant-design/icons-vue';
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'edit'): void;
|
||||
(e: 'remove'): void;
|
||||
}>();
|
||||
|
||||
const onClick = ({ key }) => {
|
||||
emit(key);
|
||||
};
|
||||
</script>
|
||||
112
src/views/dashboard/workplace/components/profile-card.vue
Normal file
112
src/views/dashboard/workplace/components/profile-card.vue
Normal file
@@ -0,0 +1,112 @@
|
||||
<!-- 用户信息 -->
|
||||
<template>
|
||||
<a-card :bordered="false" :body-style="{ padding: '20px' }">
|
||||
<div class="ele-cell workplace-user-card">
|
||||
<div class="ele-cell-content ele-cell">
|
||||
<a-avatar :size="68" :src="loginUser.avatar">
|
||||
<template v-if="!loginUser.avatar" #icon>
|
||||
<user-outlined />
|
||||
</template>
|
||||
</a-avatar>
|
||||
<div class="ele-cell-content">
|
||||
<h4 class="ele-elip">
|
||||
早安, {{ loginUser.nickname }}, 开始您一天的工作吧!
|
||||
</h4>
|
||||
<div class="ele-elip ele-text-secondary">
|
||||
<cloud-outlined />
|
||||
<em>今日多云转阴,18℃ - 22℃,出门记得穿外套哦~</em>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="workplace-count-group">
|
||||
<!-- <div class="workplace-count-item">-->
|
||||
<!-- <div class="workplace-count-header">-->
|
||||
<!-- <ele-tag color="blue" shape="circle" size="small">-->
|
||||
<!-- <appstore-filled />-->
|
||||
<!-- </ele-tag>-->
|
||||
<!-- <span class="workplace-count-name">项目数</span>-->
|
||||
<!-- </div>-->
|
||||
<!-- <h2>0</h2>-->
|
||||
<!-- </div>-->
|
||||
<!-- <div class="workplace-count-item">-->
|
||||
<!-- <div class="workplace-count-header">-->
|
||||
<!-- <ele-tag color="orange" shape="circle" size="small">-->
|
||||
<!-- <check-square-outlined />-->
|
||||
<!-- </ele-tag>-->
|
||||
<!-- <span class="workplace-count-name">待办项</span>-->
|
||||
<!-- </div>-->
|
||||
<!-- <h2>6 / 24</h2>-->
|
||||
<!-- </div>-->
|
||||
<!-- <div class="workplace-count-item">-->
|
||||
<!-- <div class="workplace-count-header">-->
|
||||
<!-- <ele-tag color="green" shape="circle" size="small">-->
|
||||
<!-- <bell-filled />-->
|
||||
<!-- </ele-tag>-->
|
||||
<!-- <span class="workplace-count-name">消息</span>-->
|
||||
<!-- </div>-->
|
||||
<!-- <h2>0</h2>-->
|
||||
<!-- </div>-->
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
import {
|
||||
UserOutlined,
|
||||
CloudOutlined,
|
||||
AppstoreFilled,
|
||||
CheckSquareOutlined,
|
||||
BellFilled
|
||||
} from '@ant-design/icons-vue';
|
||||
import { useUserStore } from '@/store/modules/user';
|
||||
|
||||
const userStore = useUserStore();
|
||||
|
||||
// 当前登录用户信息
|
||||
const loginUser = computed(() => userStore.info ?? {});
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.workplace-user-card {
|
||||
.ele-cell-content {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
h4 {
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
}
|
||||
|
||||
.workplace-count-group {
|
||||
white-space: nowrap;
|
||||
text-align: right;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
|
||||
.workplace-count-item {
|
||||
display: inline-block;
|
||||
margin: 0 4px 0 24px;
|
||||
}
|
||||
|
||||
.workplace-count-name {
|
||||
margin-left: 8px;
|
||||
}
|
||||
|
||||
@media screen and (max-width: 992px) {
|
||||
.workplace-count-item {
|
||||
margin: 0 2px 0 12px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 768px) {
|
||||
.workplace-user-card {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.workplace-count-group {
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
120
src/views/dashboard/workplace/components/project-card.vue
Normal file
120
src/views/dashboard/workplace/components/project-card.vue
Normal file
@@ -0,0 +1,120 @@
|
||||
<!-- 项目进度 -->
|
||||
<template>
|
||||
<a-card
|
||||
:title="title"
|
||||
:bordered="false"
|
||||
:body-style="{ padding: '14px', height: '358px' }"
|
||||
>
|
||||
<template #extra>
|
||||
<more-icon @remove="onRemove" @edit="onEdit" />
|
||||
</template>
|
||||
<a-table
|
||||
row-key="id"
|
||||
size="middle"
|
||||
:pagination="false"
|
||||
:data-source="projectList"
|
||||
:columns="projectColumns"
|
||||
:scroll="{ x: 600 }"
|
||||
>
|
||||
<template #bodyCell="{ column, record }">
|
||||
<template v-if="column.key === 'projectName'">
|
||||
<a @click="openUrl('/project?id=' + record.projectId)">{{ record.projectName }}</a>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'status'">
|
||||
<div v-for="(dict, index) in projectStatus" :key="index">
|
||||
<a-tag :color="dict.comments" v-if="dict.value === record.status">{{
|
||||
dict.label
|
||||
}}</a-tag>
|
||||
</div>
|
||||
</template>
|
||||
<template v-else-if="column.key === 'progress'">
|
||||
<a-progress :percent="record.progress * 2" :steps="5" />
|
||||
</template>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import MoreIcon from './more-icon.vue';
|
||||
import type { ColumnsType } from 'ant-design-vue/es/table';
|
||||
import { pageProject } from '@/api/oa/project';
|
||||
import { message, SelectProps } from 'ant-design-vue';
|
||||
import { listDictionaryData } from '@/api/system/dictionary-data';
|
||||
import {getDictionaryOptions, openUrl} from '@/utils/common';
|
||||
import type { Project } from '@/api/oa/project/model';
|
||||
|
||||
defineProps<{
|
||||
title?: string;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'remove'): void;
|
||||
(e: 'edit'): void;
|
||||
}>();
|
||||
|
||||
const projectColumns = ref<ColumnsType>([
|
||||
{
|
||||
key: 'index',
|
||||
align: 'center',
|
||||
width: 38,
|
||||
customRender: ({ index }) => index + 1,
|
||||
fixed: 'left'
|
||||
},
|
||||
{
|
||||
title: '项目名称',
|
||||
key: 'projectName',
|
||||
ellipsis: true,
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
title: '开始时间',
|
||||
dataIndex: 'createTime',
|
||||
align: 'center',
|
||||
minWidth: 100,
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '结束时间',
|
||||
dataIndex: 'updateTime',
|
||||
align: 'center',
|
||||
minWidth: 100,
|
||||
ellipsis: true
|
||||
},
|
||||
{
|
||||
title: '状态',
|
||||
key: 'status',
|
||||
align: 'center',
|
||||
width: 90
|
||||
},
|
||||
{
|
||||
title: '进度',
|
||||
key: 'progress',
|
||||
align: 'center',
|
||||
width: 180
|
||||
}
|
||||
]);
|
||||
|
||||
// 项目进度数据
|
||||
const projectList = ref<Project[]>([]);
|
||||
/* 获取字典数据 */
|
||||
const projectStatus = getDictionaryOptions('projectStatus');
|
||||
|
||||
/* 查询项目进度 */
|
||||
const queryProjectList = () => {
|
||||
pageProject({ limit: 5, status: '1' }).then((data) => {
|
||||
projectList.value = data.list;
|
||||
});
|
||||
};
|
||||
|
||||
const onRemove = () => {
|
||||
emit('remove');
|
||||
};
|
||||
|
||||
const onEdit = () => {
|
||||
emit('edit');
|
||||
};
|
||||
|
||||
queryProjectList();
|
||||
</script>
|
||||
157
src/views/dashboard/workplace/components/task-card.vue
Normal file
157
src/views/dashboard/workplace/components/task-card.vue
Normal file
@@ -0,0 +1,157 @@
|
||||
<!-- 我的任务 -->
|
||||
<template>
|
||||
<a-card
|
||||
:title="title"
|
||||
:bordered="false"
|
||||
:body-style="{ padding: '10px', height: '358px' }"
|
||||
class="workplace-table-card"
|
||||
>
|
||||
<template #extra>
|
||||
<more-icon @remove="onRemove" @edit="onEdit" />
|
||||
</template>
|
||||
<div style="overflow: auto; position: relative">
|
||||
<table class="ele-table" style="table-layout: fixed; min-width: 300px">
|
||||
<colgroup>
|
||||
<col width="38" />
|
||||
<col width="65" />
|
||||
<col />
|
||||
<col width="70" />
|
||||
</colgroup>
|
||||
<thead>
|
||||
<tr>
|
||||
<th style="position: sticky; left: 0"></th>
|
||||
<th style="text-align: center">优先级</th>
|
||||
<th>任务名称</th>
|
||||
<th style="text-align: center">状态</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<vue-draggable
|
||||
tag="tbody"
|
||||
item-key="id"
|
||||
v-model="taskList"
|
||||
handle=".sort-handle"
|
||||
:animation="300"
|
||||
:set-data="() => void 0"
|
||||
>
|
||||
<template #item="{ element }">
|
||||
<tr>
|
||||
<td style="text-align: center; position: sticky; left: 0">
|
||||
<menu-outlined class="sort-handle ele-text-secondary" />
|
||||
</td>
|
||||
<td style="text-align: center">
|
||||
<ele-tag
|
||||
:color="['red', 'orange', 'blue'][element.priority - 1]"
|
||||
shape="circle"
|
||||
>
|
||||
{{ element.priority }}
|
||||
</ele-tag>
|
||||
</td>
|
||||
<td class="ele-elip" :title="element.taskName">
|
||||
<a>{{ element.taskName }}</a>
|
||||
</td>
|
||||
<td style="text-align: center">
|
||||
<span v-if="element.status === 0" class="ele-text-warning">
|
||||
未开始
|
||||
</span>
|
||||
<span v-else-if="element.status === 1" class="ele-text-success">
|
||||
进行中
|
||||
</span>
|
||||
<span v-else-if="element.status === 2" class="ele-text-info">
|
||||
已完成
|
||||
</span>
|
||||
</td>
|
||||
</tr>
|
||||
</template>
|
||||
</vue-draggable>
|
||||
</table>
|
||||
</div>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import VueDraggable from 'vuedraggable';
|
||||
import { MenuOutlined } from '@ant-design/icons-vue';
|
||||
import MoreIcon from './more-icon.vue';
|
||||
|
||||
defineProps<{
|
||||
title?: string;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'remove'): void;
|
||||
(e: 'edit'): void;
|
||||
}>();
|
||||
|
||||
interface Task {
|
||||
id: number;
|
||||
priority: number;
|
||||
taskName: string;
|
||||
status: number;
|
||||
}
|
||||
|
||||
// 我的任务数据
|
||||
const taskList = ref<Task[]>([]);
|
||||
|
||||
/* 查询我的任务 */
|
||||
const queryTaskList = () => {
|
||||
taskList.value = [
|
||||
{
|
||||
id: 1,
|
||||
priority: 1,
|
||||
taskName: '解决项目一的bug',
|
||||
status: 0
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
priority: 2,
|
||||
taskName: '解决项目二的bug',
|
||||
status: 0
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
priority: 2,
|
||||
taskName: '解决项目三的bug',
|
||||
status: 1
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
priority: 3,
|
||||
taskName: '解决项目四的bug',
|
||||
status: 1
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
priority: 3,
|
||||
taskName: '解决项目五的bug',
|
||||
status: 2
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
priority: 3,
|
||||
taskName: '解决项目六的bug',
|
||||
status: 2
|
||||
}
|
||||
];
|
||||
};
|
||||
|
||||
const onRemove = () => {
|
||||
emit('remove');
|
||||
};
|
||||
|
||||
const onEdit = () => {
|
||||
emit('edit');
|
||||
};
|
||||
|
||||
queryTaskList();
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.ele-table tr.sortable-chosen {
|
||||
background: hsla(0, 0%, 60%, 0.1);
|
||||
}
|
||||
|
||||
.workplace-table-card .sort-handle {
|
||||
cursor: move;
|
||||
}
|
||||
</style>
|
||||
84
src/views/dashboard/workplace/components/user-list.vue
Normal file
84
src/views/dashboard/workplace/components/user-list.vue
Normal file
@@ -0,0 +1,84 @@
|
||||
<!-- 小组成员 -->
|
||||
<template>
|
||||
<a-card :title="title" :bordered="false" :body-style="{ padding: '2px 0px' }">
|
||||
<template #extra>
|
||||
<more-icon @remove="onRemove" @edit="onEdit" />
|
||||
</template>
|
||||
<div
|
||||
v-for="(item, index) in userList"
|
||||
:key="index"
|
||||
class="ele-cell user-list-item"
|
||||
>
|
||||
<div style="flex-shrink: 0">
|
||||
<a-avatar :size="46" :src="item.avatar" />
|
||||
</div>
|
||||
<div class="ele-cell-content">
|
||||
<span class="ele-cell-title ele-elip">{{ item.nickname }}</span>
|
||||
<div class="ele-cell-desc ele-elip">{{ item.phone }}</div>
|
||||
</div>
|
||||
<div style="flex-shrink: 0">
|
||||
<a-tag :color="['green', 'red'][item.status]">
|
||||
{{ ['在线', '离线'][item.status] }}
|
||||
</a-tag>
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import MoreIcon from './more-icon.vue';
|
||||
import { pageUsers } from '@/api/system/user';
|
||||
import type { User } from '@/api/system/user/model';
|
||||
|
||||
defineProps<{
|
||||
title?: string;
|
||||
}>();
|
||||
|
||||
const emit = defineEmits<{
|
||||
(e: 'remove'): void;
|
||||
(e: 'edit'): void;
|
||||
}>();
|
||||
|
||||
// 小组成员数据
|
||||
const userList = ref<User[]>([]);
|
||||
|
||||
/* 查询小组成员 */
|
||||
const queryUserList = () => {
|
||||
pageUsers({ parentId: 11, limit: 5 }).then((data: any) => {
|
||||
userList.value = data.list;
|
||||
});
|
||||
};
|
||||
|
||||
const onRemove = () => {
|
||||
emit('remove');
|
||||
};
|
||||
|
||||
const onEdit = () => {
|
||||
emit('edit');
|
||||
};
|
||||
|
||||
queryUserList();
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.user-list-item {
|
||||
padding: 12px 18px;
|
||||
|
||||
& + .user-list-item {
|
||||
border-top: 1px solid hsla(0, 0%, 60%, 0.15);
|
||||
}
|
||||
|
||||
.ele-cell-content {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.ele-cell-desc {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
.ant-tag {
|
||||
margin: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
302
src/views/dashboard/workplace/index.vue
Normal file
302
src/views/dashboard/workplace/index.vue
Normal file
@@ -0,0 +1,302 @@
|
||||
<template>
|
||||
<div class="ele-body ele-body-card">
|
||||
<profile-card />
|
||||
<link-card ref="linkCardRef" />
|
||||
<a-row :gutter="16" ref="wrapRef">
|
||||
<a-col
|
||||
v-for="(item, index) in data"
|
||||
:key="item.name"
|
||||
:lg="item.lg"
|
||||
:md="item.md"
|
||||
:sm="item.sm"
|
||||
:xs="item.xs"
|
||||
>
|
||||
<component
|
||||
:is="item.name"
|
||||
:title="item.title"
|
||||
@remove="onRemove(index)"
|
||||
@edit="onEdit(index)"
|
||||
/>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-card :bordered="false" :body-style="{ padding: 0 }">
|
||||
<div class="ele-cell" style="line-height: 42px">
|
||||
<div
|
||||
class="ele-cell-content ele-text-primary workplace-bottom-btn"
|
||||
@click="add"
|
||||
>
|
||||
<PlusCircleOutlined /> 添加视图
|
||||
</div>
|
||||
<a-divider type="vertical" />
|
||||
<div
|
||||
class="ele-cell-content ele-text-primary workplace-bottom-btn"
|
||||
@click="reset"
|
||||
>
|
||||
<UndoOutlined /> 重置布局
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
<ele-modal
|
||||
:width="680"
|
||||
v-model:visible="visible"
|
||||
title="未添加的视图"
|
||||
:footer="null"
|
||||
>
|
||||
<a-row :gutter="16">
|
||||
<a-col
|
||||
v-for="item in notAddedData"
|
||||
:key="item.name"
|
||||
:md="8"
|
||||
:sm="12"
|
||||
:xs="24"
|
||||
>
|
||||
<div
|
||||
class="workplace-card-item ele-border-split"
|
||||
@click="addView(item)"
|
||||
>
|
||||
<div class="workplace-card-header ele-border-split">
|
||||
{{ item.title }}
|
||||
</div>
|
||||
<div class="workplace-card-body ele-text-placeholder">
|
||||
<plus-circle-outlined />
|
||||
</div>
|
||||
</div>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-empty v-if="!notAddedData.length" description="已添加所有视图" />
|
||||
</ele-modal>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, computed, onMounted, onBeforeUnmount } from 'vue';
|
||||
import SortableJs from 'sortablejs';
|
||||
import type { Row as ARow } from 'ant-design-vue';
|
||||
import { message } from 'ant-design-vue';
|
||||
import { PlusCircleOutlined, UndoOutlined } from '@ant-design/icons-vue';
|
||||
import ProfileCard from './components/profile-card.vue';
|
||||
import LinkCard from './components/link-card.vue';
|
||||
const CACHE_KEY = 'workplace-layout';
|
||||
|
||||
interface ViewItem {
|
||||
name: string;
|
||||
title: string;
|
||||
lg: number;
|
||||
md: number;
|
||||
sm: number;
|
||||
xs: number;
|
||||
}
|
||||
|
||||
// 默认布局
|
||||
const DEFAULT: ViewItem[] = [
|
||||
// {
|
||||
// name: 'project-card',
|
||||
// title: '项目管理',
|
||||
// lg: 16,
|
||||
// md: 24,
|
||||
// sm: 24,
|
||||
// xs: 24
|
||||
// },
|
||||
// {
|
||||
// name: 'user-list',
|
||||
// title: '小组成员',
|
||||
// lg: 8,
|
||||
// md: 24,
|
||||
// sm: 24,
|
||||
// xs: 24
|
||||
// },
|
||||
// {
|
||||
// name: 'activities-card',
|
||||
// title: '最新动态',
|
||||
// lg: 8,
|
||||
// md: 24,
|
||||
// sm: 24,
|
||||
// xs: 24
|
||||
// },
|
||||
// {
|
||||
// name: 'task-card',
|
||||
// title: '我的任务',
|
||||
// lg: 8,
|
||||
// md: 24,
|
||||
// sm: 24,
|
||||
// xs: 24
|
||||
// },
|
||||
// {
|
||||
// name: 'goal-card',
|
||||
// title: '本月目标',
|
||||
// lg: 8,
|
||||
// md: 24,
|
||||
// sm: 24,
|
||||
// xs: 24
|
||||
// },
|
||||
// {
|
||||
// name: 'docs',
|
||||
// title: '知识库',
|
||||
// lg: 8,
|
||||
// md: 24,
|
||||
// sm: 24,
|
||||
// xs: 24
|
||||
// }
|
||||
];
|
||||
|
||||
// 获取缓存的顺序
|
||||
const cache = (() => {
|
||||
const str = localStorage.getItem(CACHE_KEY);
|
||||
try {
|
||||
return str ? JSON.parse(str) : null;
|
||||
} catch (e) {
|
||||
return null;
|
||||
}
|
||||
})();
|
||||
|
||||
const data = ref<ViewItem[]>([...(cache ?? DEFAULT)]);
|
||||
|
||||
const visible = ref(false);
|
||||
|
||||
const linkCardRef = ref<InstanceType<typeof LinkCard> | null>(null);
|
||||
|
||||
const wrapRef = ref<InstanceType<typeof ARow> | null>(null);
|
||||
|
||||
let sortableIns: SortableJs | null = null;
|
||||
|
||||
// 未添加的数据
|
||||
const notAddedData = computed(() => {
|
||||
return DEFAULT.filter((d) => !data.value.some((t) => t.name === d.name));
|
||||
});
|
||||
|
||||
/* 添加 */
|
||||
const add = () => {
|
||||
visible.value = true;
|
||||
};
|
||||
|
||||
/* 重置布局 */
|
||||
const reset = () => {
|
||||
data.value = [...DEFAULT];
|
||||
cacheData();
|
||||
linkCardRef.value?.reset();
|
||||
message.success('已重置');
|
||||
};
|
||||
|
||||
/* 缓存布局 */
|
||||
const cacheData = () => {
|
||||
localStorage.setItem(CACHE_KEY, JSON.stringify(data.value));
|
||||
};
|
||||
|
||||
/* 删除视图 */
|
||||
const onRemove = (index: number) => {
|
||||
data.value = data.value.filter((_d, i) => i !== index);
|
||||
cacheData();
|
||||
};
|
||||
|
||||
/* 编辑视图 */
|
||||
const onEdit = (index: number) => {
|
||||
data.value.map((d) => {
|
||||
if (d.name == 'user-list') {
|
||||
}
|
||||
});
|
||||
// message.info('点击了编辑');
|
||||
};
|
||||
|
||||
/* 添加视图 */
|
||||
const addView = (item) => {
|
||||
data.value.push(item);
|
||||
cacheData();
|
||||
message.success('已添加');
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
const isTouchDevice = 'ontouchstart' in document.documentElement;
|
||||
if (isTouchDevice) {
|
||||
return;
|
||||
}
|
||||
sortableIns = new SortableJs(wrapRef.value?.$el, {
|
||||
handle: '.ant-card-head',
|
||||
animation: 300,
|
||||
onUpdate: ({ oldIndex, newIndex }) => {
|
||||
if (typeof oldIndex === 'number' && typeof newIndex === 'number') {
|
||||
const temp = [...data.value];
|
||||
temp.splice(newIndex, 0, temp.splice(oldIndex, 1)[0]);
|
||||
data.value = temp;
|
||||
cacheData();
|
||||
}
|
||||
},
|
||||
setData: () => {}
|
||||
});
|
||||
});
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
if (sortableIns) {
|
||||
sortableIns.destroy();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
import ActivitiesCard from './components/activities-card.vue';
|
||||
import TaskCard from './components/task-card.vue';
|
||||
import GoalCard from './components/goal-card.vue';
|
||||
import ProjectCard from './components/project-card.vue';
|
||||
import UserList from './components/user-list.vue';
|
||||
import Docs from './components/docs.vue';
|
||||
|
||||
export default {
|
||||
name: 'DashboardWorkplace',
|
||||
components: {
|
||||
ProjectCard,
|
||||
UserList,
|
||||
ActivitiesCard,
|
||||
TaskCard,
|
||||
GoalCard,
|
||||
Docs
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.ele-body :deep(.ant-card-head) {
|
||||
cursor: move;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.ele-body :deep(.ant-row > .ant-col.sortable-chosen > .ant-card) {
|
||||
box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.2);
|
||||
}
|
||||
|
||||
.workplace-bottom-btn {
|
||||
text-align: center;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s;
|
||||
}
|
||||
|
||||
.workplace-bottom-btn:hover {
|
||||
background: hsla(0, 0%, 60%, 0.05);
|
||||
}
|
||||
|
||||
/* 添加弹窗 */
|
||||
.workplace-card-item {
|
||||
margin-bottom: 15px;
|
||||
border-width: 1px;
|
||||
border-style: solid;
|
||||
border-radius: 4px;
|
||||
position: relative;
|
||||
cursor: pointer;
|
||||
transition: box-shadow 0.2s, background-color 0.2s;
|
||||
}
|
||||
|
||||
.workplace-card-item:hover {
|
||||
box-shadow: 0 0 4px 0 rgba(0, 0, 0, 0.1);
|
||||
background: hsla(0, 0%, 60%, 0.05);
|
||||
}
|
||||
|
||||
.workplace-card-item .workplace-card-header {
|
||||
border-bottom-width: 1px;
|
||||
border-bottom-style: solid;
|
||||
padding: 8px;
|
||||
}
|
||||
|
||||
.workplace-card-body {
|
||||
font-size: 26px;
|
||||
padding: 24px 10px;
|
||||
text-align: center;
|
||||
}
|
||||
</style>
|
||||
Reference in New Issue
Block a user