前端和后端源码,合并到一个git仓库中,方便用户下载,避免前后端不匹配的问题

This commit is contained in:
JEECG
2024-06-23 10:39:52 +08:00
parent bb918b742e
commit 0325e34dcb
1439 changed files with 171106 additions and 0 deletions

View File

@ -0,0 +1,16 @@
import { defHttp } from '/@/utils/http/axios';
enum Api {
loginfo = '/sys/loginfo',
visitInfo = '/sys/visitInfo',
}
/**
* 日志统计信息
* @param params
*/
export const getLoginfo = (params) => defHttp.get({ url: Api.loginfo, params }, { isTransformResponse: false });
/**
* 访问量信息
* @param params
*/
export const getVisitInfo = (params) => defHttp.get({ url: Api.visitInfo, params }, { isTransformResponse: false });

View File

@ -0,0 +1,128 @@
<template>
<a-card :loading="loading" :bordered="false" :body-style="{ padding: '0' }">
<div class="salesCard">
<a-tabs default-active-key="1" size="large" :tab-bar-style="{ marginBottom: '24px', paddingLeft: '16px' }">
<template #rightExtra>
<div class="extra-wrapper">
<div class="extra-item">
<a>今日</a>
<a>本周</a>
<a>本月</a>
<a>本年</a>
</div>
<a-range-picker :style="{ width: '256px' }" />
</div>
</template>
<a-tab-pane loading="true" tab="受理监管" key="1">
<a-row>
<a-col :xl="16" :lg="12" :md="12" :sm="24" :xs="24">
<Bar :chartData="barData" :option="{ title: { text: '', textStyle: { fontWeight: 'lighter' } } }" height="40vh" :seriesColor="seriesColor" />
</a-col>
<a-col :xl="8" :lg="12" :md="12" :sm="24" :xs="24">
<QuickNav :loading="loading" class="enter-y" :bordered="false" :body-style="{ padding: 0 }" />
</a-col>
</a-row>
</a-tab-pane>
<a-tab-pane tab="交互监管" key="2">
<a-row>
<a-col :xl="16" :lg="12" :md="12" :sm="24" :xs="24">
<BarMulti
:seriesColor="interactiveColor"
:chartData="barMultiData"
:option="{ title: { text: '', textStyle: { fontWeight: 'lighter' } } }"
height="40vh"
/>
</a-col>
<a-col :xl="8" :lg="12" :md="12" :sm="24" :xs="24">
<QuickNav :loading="loading" class="enter-y" :bordered="false" :body-style="{ padding: 0 }" />
</a-col>
</a-row>
</a-tab-pane>
<a-tab-pane tab="存储监管" key="3">
<a-row>
<a-col :xl="16" :lg="12" :md="12" :sm="24" :xs="24" style="display: flex">
<Gauge :seriesColor="seriesColor" :chartData="{ name: 'C盘', value: 70 }" height="30vh"></Gauge>
<Gauge :seriesColor="seriesColor" :chartData="{ name: 'D盘', value: 50 }" height="30vh"></Gauge>
</a-col>
<a-col :xl="8" :lg="12" :md="12" :sm="24" :xs="24">
<QuickNav :loading="loading" class="enter-y" :bordered="false" :body-style="{ padding: 0 }" />
</a-col>
</a-row>
</a-tab-pane>
</a-tabs>
</div>
</a-card>
</template>
<script lang="ts" setup>
import { ref, computed } from 'vue';
import Bar from '/@/components/chart/Bar.vue';
import BarMulti from '/@/components/chart/BarMulti.vue';
import Gauge from '/@/components/chart/Gauge.vue';
import QuickNav from './QuickNav.vue';
import { useRootSetting } from '/@/hooks/setting/useRootSetting';
defineProps({
loading: {
type: Boolean,
},
});
const { getThemeColor } = useRootSetting();
const interactiveColor = ref();
const rankList = [];
for (let i = 0; i < 7; i++) {
rankList.push({
name: '白鹭岛 ' + (i + 1) + ' 号店',
total: 1234.56 - i * 100,
});
}
const barData = [];
for (let i = 0; i < 12; i += 1) {
barData.push({
name: `${i + 1}月`,
value: Math.floor(Math.random() * 1000) + 200,
});
}
const barMultiData = [];
for (let j = 0; j < 2; j++) {
for (let i = 0; i < 12; i += 1) {
barMultiData.push({
type: j == 0 ? 'jeecg' : 'jeebt',
name: `${i + 1}月`,
value: Math.floor(Math.random() * 1000) + 200,
});
}
}
const seriesColor = computed(() => {
interactiveColor.value = [
{ type: 'jeecg', color: getThemeColor.value },
{ type: 'jeebt', color: getRandomColor() },
];
return getThemeColor.value;
});
function getRandomColor() {
var letters = '0123456789ABCDEF';
var color = '#';
for (var i = 0; i < 6; i++) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
}
</script>
<style lang="less" scoped>
.extra-wrapper {
line-height: 55px;
padding-right: 24px;
.extra-item {
display: inline-block;
margin-right: 24px;
a {
margin-left: 24px;
}
}
}
</style>

View File

@ -0,0 +1,109 @@
<template>
<div class="md:flex">
<template v-for="(item, index) in dataList" :key="item.title">
<ChartCard
:loading="loading"
:title="item.title"
:total="getTotal(item.total, index)"
class="md:w-1/4 w-full !md:mt-0 !mt-4"
:class="[index + 1 < 4 && '!md:mr-4']"
>
<template #action>
<a-tooltip title="指标说明">
<Icon :icon="item.icon" :size="20" />
</a-tooltip>
</template>
<div v-if="type === 'chart'">
<Trend term="周同比" :percentage="12" v-if="index === 0" />
<Trend term="日同比" :percentage="11" v-if="index === 0" :type="false" />
<SingleLine v-if="index === 1" :option="option" :chartData="chartData" :seriesColor="seriesColor" height="50px"></SingleLine>
<Bar v-if="index === 2" :option="option" :chartData="chartData" :seriesColor="seriesColor" height="50px"></Bar>
<Progress v-if="index === 3" :percent="78" :show-info="false"></Progress>
</div>
<div v-else>
<SingleLine :seriesColor="seriesColor" v-if="index === 0" :option="option" :chartData="chartData" height="50px"></SingleLine>
<SingleLine :seriesColor="seriesColor" v-if="index === 1" :option="option" :chartData="chartData" height="50px"></SingleLine>
<Bar :seriesColor="seriesColor" v-if="index === 2" :option="option" :chartData="chartData" height="50px"></Bar>
<Progress v-if="index === 3" :percent="78" :show-info="false"></Progress>
</div>
<template #footer v-if="type === 'chart'">
<span v-if="index !== 3"
>{{ item.footer }}<span>{{ item.value }}</span></span
>
<Trend term="周同比" :percentage="12" v-if="index === 3" />
<Trend term="日同比" :percentage="11" v-if="index === 3" :type="false" />
</template>
<template #footer v-else>
<span
>{{ item.footer }}<span>{{ item.value }}</span></span
>
</template>
</ChartCard>
</template>
</div>
</template>
<script lang="ts" setup>
import { ref, computed } from 'vue';
import { Icon } from '/@/components/Icon';
import { Progress } from 'ant-design-vue';
import ChartCard from '/@/components/chart/ChartCard.vue';
import Trend from '/@/components/chart/Trend.vue';
import Bar from '/@/components/chart/Bar.vue';
import SingleLine from '/@/components/chart/SingleLine.vue';
import { chartCardList, bdcCardList } from '../data';
import { useRootSetting } from '/@/hooks/setting/useRootSetting';
const { getThemeColor } = useRootSetting();
const props = defineProps({
loading: {
type: Boolean,
},
type: {
type: String,
default: 'chart',
},
});
const option = ref({ xAxis: { show: false, boundaryGap: false }, yAxis: { show: false, boundaryGap: false, max: 220 } });
const chartData = ref([
{
name: '1月',
value: 50,
},
{
name: '2月',
value: 100,
},
{
name: '3月',
value: 150,
},
{
name: '4月',
value: 40,
},
{
name: '5月',
value: 110,
},
{
name: '6月',
value: 120,
},
]);
const seriesColor = computed(() => {
return getThemeColor.value;
})
const dataList = computed(() => (props.type === 'dbc' ? bdcCardList : chartCardList));
function getTotal(total, index) {
return index === 0 ? `¥${total}` : index === 3 ? `${total}%` : total;
}
</script>

View File

@ -0,0 +1,40 @@
<template>
<div class="md:flex">
<template v-for="(item, index) in growCardList" :key="item.title">
<Card
size="small"
:loading="loading"
:title="item.title"
class="md:w-1/4 w-full !md:mt-0 !mt-4"
:class="[index + 1 < 4 && '!md:mr-4']"
:canExpan="false"
>
<template #extra>
<Tag :color="item.color">{{ item.action }}</Tag>
</template>
<div class="py-4 px-4 flex justify-between">
<CountTo prefix="$" :startVal="1" :endVal="item.value" class="text-2xl" />
<Icon :icon="item.icon" :size="40" />
</div>
<div class="p-2 px-4 flex justify-between">
<span>总{{ item.title }}</span>
<CountTo prefix="$" :startVal="1" :endVal="item.total" />
</div>
</Card>
</template>
</div>
</template>
<script lang="ts" setup>
import { CountTo } from '/@/components/CountTo/index';
import { Icon } from '/@/components/Icon';
import { Tag, Card } from 'ant-design-vue';
import { growCardList } from '../data';
defineProps({
loading: {
type: Boolean,
},
});
</script>

View File

@ -0,0 +1,56 @@
<template>
<Card title="快捷导航" v-bind="$attrs">
<template v-for="item in navItems" :key="item">
<CardGrid @click="goPage">
<span class="flex flex-col items-center">
<Icon :icon="item.icon" :color="item.color" size="20" />
<span class="text-md mt-2">{{ item.title }}</span>
</span>
</CardGrid>
</template>
</Card>
</template>
<script lang="ts" setup>
import { Card } from 'ant-design-vue';
import { useMessage } from '/@/hooks/web/useMessage';
import { Icon } from '/@/components/Icon';
const CardGrid = Card.Grid;
const $message = useMessage();
const navItems = [
{
title: '业务受理',
icon: 'ion:home-outline',
color: '#1fdaca',
},
{
title: '业务管理',
icon: 'ion:grid-outline',
color: '#bf0c2c',
},
{
title: '文件管理',
icon: 'ion:layers-outline',
color: '#e18525',
},
{
title: '信息查询',
icon: 'ion:settings-outline',
color: '#3fb27f',
},
{
title: '通知公告',
icon: 'ion:notifications',
color: '#13b0ff',
},
{
title: '我的任务',
icon: 'ion:person-stalker',
color: '#b27315',
}
];
function goPage() {
$message.createMessage.success('根据业务自行处理跳转页面!');
}
</script>

View File

@ -0,0 +1,87 @@
<template>
<a-card :loading="loading" :bordered="false" :body-style="{ padding: '0' }">
<div class="salesCard">
<a-tabs default-active-key="1" size="large" :tab-bar-style="{ marginBottom: '24px', paddingLeft: '16px' }">
<template #rightExtra>
<div class="extra-wrapper">
<div class="extra-item">
<a>今日</a>
<a>本周</a>
<a>本月</a>
<a>本年</a>
</div>
<a-range-picker :style="{ width: '256px' }" />
</div>
</template>
<a-tab-pane loading="true" tab="销售额" key="1">
<a-row>
<a-col :xl="16" :lg="12" :md="12" :sm="24" :xs="24">
<Bar :chartData="barData" :option="{ title: { text: '', textStyle: { fontWeight: 'lighter' } } }" height="40vh" :seriesColor="seriesColor" />
</a-col>
<a-col :xl="8" :lg="12" :md="12" :sm="24" :xs="24">
<RankList title="门店销售排行榜" :list="rankList" />
</a-col>
</a-row>
</a-tab-pane>
<a-tab-pane tab="销售趋势" key="2">
<a-row>
<a-col :xl="16" :lg="12" :md="12" :sm="24" :xs="24">
<Bar :chartData="barData.reverse()" :option="{ title: { text: '', textStyle: { fontWeight: 'lighter' } } }" height="40vh" :seriesColor="seriesColor" />
</a-col>
<a-col :xl="8" :lg="12" :md="12" :sm="24" :xs="24">
<RankList title="门店销售排行榜" :list="rankList" />
</a-col>
</a-row>
</a-tab-pane>
</a-tabs>
</div>
</a-card>
</template>
<script lang="ts" setup>
import { ref, computed } from 'vue';
import Bar from '/@/components/chart/Bar.vue';
import RankList from '/@/components/chart/RankList.vue';
import { useRootSetting } from '/@/hooks/setting/useRootSetting';
defineProps({
loading: {
type: Boolean,
},
});
const { getThemeColor } = useRootSetting();
const rankList = [];
for (let i = 0; i < 7; i++) {
rankList.push({
name: '白鹭岛 ' + (i + 1) + ' 号店',
total: 1234.56 - i * 100,
});
}
const barData = [];
for (let i = 0; i < 12; i += 1) {
barData.push({
name: `${i + 1}月`,
value: Math.floor(Math.random() * 1000) + 200,
});
}
const seriesColor = computed(() => {
return getThemeColor.value
})
</script>
<style lang="less" scoped>
.extra-wrapper {
line-height: 55px;
padding-right: 24px;
.extra-item {
display: inline-block;
margin-right: 24px;
a {
margin-left: 24px;
}
}
}
</style>

View File

@ -0,0 +1,63 @@
<template>
<Card title="成交占比" :loading="loading">
<div ref="chartRef" :style="{ width, height }"></div>
</Card>
</template>
<script lang="ts" setup>
import { Ref, ref, watch } from 'vue';
import { Card } from 'ant-design-vue';
import { useECharts } from '/@/hooks/web/useECharts';
const props = defineProps({
loading: Boolean,
width: {
type: String as PropType<string>,
default: '100%',
},
height: {
type: String as PropType<string>,
default: '300px',
},
});
const chartRef = ref<HTMLDivElement | null>(null);
const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>);
watch(
() => props.loading,
() => {
if (props.loading) {
return;
}
setOptions({
tooltip: {
trigger: 'item',
},
series: [
{
name: '访问来源',
type: 'pie',
radius: '80%',
center: ['50%', '50%'],
color: ['#5ab1ef', '#b6a2de', '#67e0e3', '#2ec7c9'],
data: [
{ value: 500, name: '电子产品' },
{ value: 310, name: '服装' },
{ value: 274, name: '化妆品' },
{ value: 400, name: '家居' },
].sort(function (a, b) {
return a.value - b.value;
}),
roseType: 'radius',
animationType: 'scale',
animationEasing: 'exponentialInOut',
animationDelay: function () {
return Math.random() * 400;
},
},
],
});
},
{ immediate: true }
);
</script>

View File

@ -0,0 +1,33 @@
<template>
<Card :tab-list="tabListTitle" v-bind="$attrs" :active-tab-key="activeKey" @tabChange="onTabChange">
<p v-if="activeKey === 'tab1'">
<VisitAnalysis />
</p>
<p v-if="activeKey === 'tab2'">
<VisitAnalysisBar />
</p>
</Card>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { Card } from 'ant-design-vue';
import VisitAnalysis from './VisitAnalysis.vue';
import VisitAnalysisBar from './VisitAnalysisBar.vue';
const activeKey = ref('tab1');
const tabListTitle = [
{
key: 'tab1',
tab: '流量趋势',
},
{
key: 'tab2',
tab: '访问量',
},
];
function onTabChange(key) {
activeKey.value = key;
}
</script>

View File

@ -0,0 +1,104 @@
<template>
<div ref="chartRef" :style="{ height, width }"></div>
</template>
<script lang="ts" setup>
import { onMounted, ref, Ref, watchEffect } from 'vue';
import { useECharts } from '/@/hooks/web/useECharts';
import { basicProps } from './props';
import { useRootSetting } from '/@/hooks/setting/useRootSetting';
defineProps({
...basicProps,
});
const chartRef = ref<HTMLDivElement | null>(null);
const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>);
const { getThemeColor } = useRootSetting();
const init = () => {
setOptions({
tooltip: {
trigger: 'axis',
axisPointer: {
lineStyle: {
width: 1,
color: getThemeColor.value,
},
},
},
xAxis: {
type: 'category',
boundaryGap: false,
data: [
'6:00',
'7:00',
'8:00',
'9:00',
'10:00',
'11:00',
'12:00',
'13:00',
'14:00',
'15:00',
'16:00',
'17:00',
'18:00',
'19:00',
'20:00',
'21:00',
'22:00',
'23:00',
],
splitLine: {
show: true,
lineStyle: {
width: 1,
type: 'solid',
color: 'rgba(226,226,226,0.5)',
},
},
axisTick: {
show: false,
},
},
yAxis: [
{
type: 'value',
max: 80000,
splitNumber: 4,
axisTick: {
show: false,
},
splitArea: {
show: true,
areaStyle: {
color: ['rgba(255,255,255,0.2)', 'rgba(226,226,226,0.2)'],
},
},
},
],
grid: { left: '1%', right: '1%', top: '2 %', bottom: 0, containLabel: true },
series: [
{
smooth: true,
data: [111, 222, 4000, 18000, 33333, 55555, 66666, 33333, 14000, 36000, 66666, 44444, 22222, 11111, 4000, 2000, 500, 333, 222, 111],
type: 'line',
areaStyle: {},
itemStyle: {
color: '#5ab1ef',
},
},
{
smooth: true,
data: [33, 66, 88, 333, 3333, 5000, 18000, 3000, 1200, 13000, 22000, 11000, 2221, 1201, 390, 198, 60, 30, 22, 11],
type: 'line',
areaStyle: {},
itemStyle: {
color: getThemeColor.value,
},
},
],
});
};
watchEffect(() => {
init();
});
</script>

View File

@ -0,0 +1,51 @@
<template>
<div ref="chartRef" :style="{ height, width }"></div>
</template>
<script lang="ts" setup>
import { onMounted, ref, Ref, watchEffect } from 'vue';
import { useECharts } from '/@/hooks/web/useECharts';
import { basicProps } from './props';
import { useRootSetting } from '/@/hooks/setting/useRootSetting';
defineProps({
...basicProps,
});
const chartRef = ref<HTMLDivElement | null>(null);
const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>);
const { getThemeColor } = useRootSetting();
const init = () => {
setOptions({
tooltip: {
trigger: 'axis',
axisPointer: {
lineStyle: {
width: 1,
color: getThemeColor.value,
},
},
},
grid: { left: '1%', right: '1%', top: '2 %', bottom: 0, containLabel: true },
xAxis: {
type: 'category',
data: ['1月', '2月', '3月', '4月', '5月', '6月', '7月', '8月', '9月', '10月', '11月', '12月'],
},
yAxis: {
type: 'value',
max: 8000,
splitNumber: 4,
},
series: [
{
data: [3000, 2000, 3333, 5000, 3200, 4200, 3200, 2100, 3000, 5100, 6000, 3200, 4800],
type: 'bar',
barMaxWidth: 80,
color: getThemeColor.value,
},
],
});
};
watchEffect(() => {
init();
});
</script>

View File

@ -0,0 +1,94 @@
<template>
<Card title="转化率" :loading="loading">
<div ref="chartRef" :style="{ width, height }"></div>
</Card>
</template>
<script lang="ts" setup>
import { Ref, ref, watch } from 'vue';
import { Card } from 'ant-design-vue';
import { useECharts } from '/@/hooks/web/useECharts';
const props = defineProps({
loading: Boolean,
width: {
type: String as PropType<string>,
default: '100%',
},
height: {
type: String as PropType<string>,
default: '300px',
},
});
const chartRef = ref<HTMLDivElement | null>(null);
const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>);
watch(
() => props.loading,
() => {
if (props.loading) {
return;
}
setOptions({
legend: {
bottom: 0,
data: ['访问', '购买'],
},
tooltip: {},
radar: {
radius: '60%',
splitNumber: 8,
indicator: [
{
name: '电脑',
},
{
name: '充电器',
},
{
name: '耳机',
},
{
name: '手机',
},
{
name: 'Ipad',
},
{
name: '耳机',
},
],
},
series: [
{
type: 'radar' as 'custom',
symbolSize: 0,
areaStyle: {
shadowBlur: 0,
shadowColor: 'rgba(0,0,0,.2)',
shadowOffsetX: 0,
shadowOffsetY: 10,
opacity: 1,
},
data: [
{
value: [90, 50, 86, 40, 50, 20],
name: '访问',
itemStyle: {
color: '#b6a2de',
},
},
{
value: [70, 75, 70, 76, 20, 85],
name: '购买',
itemStyle: {
color: '#5ab1ef',
},
},
],
},
],
});
},
{ immediate: true }
);
</script>

View File

@ -0,0 +1,80 @@
<template>
<Card title="访问来源" :loading="loading">
<div ref="chartRef" :style="{ width, height }"></div>
</Card>
</template>
<script lang="ts" setup>
import { Ref, ref, watch } from 'vue';
import { Card } from 'ant-design-vue';
import { useECharts } from '/@/hooks/web/useECharts';
const props = defineProps({
loading: Boolean,
width: {
type: String as PropType<string>,
default: '100%',
},
height: {
type: String as PropType<string>,
default: '300px',
},
});
const chartRef = ref<HTMLDivElement | null>(null);
const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>);
watch(
() => props.loading,
() => {
if (props.loading) {
return;
}
setOptions({
tooltip: {
trigger: 'item',
},
legend: {
bottom: '1%',
left: 'center',
},
series: [
{
color: ['#5ab1ef', '#b6a2de', '#67e0e3', '#2ec7c9'],
name: '访问来源',
type: 'pie',
radius: ['40%', '70%'],
avoidLabelOverlap: false,
itemStyle: {
borderRadius: 10,
borderColor: '#fff',
borderWidth: 2,
},
label: {
show: false,
position: 'center',
},
emphasis: {
label: {
show: true,
fontSize: '12',
fontWeight: 'bold',
},
},
labelLine: {
show: false,
},
data: [
{ value: 1048, name: '搜索引擎' },
{ value: 735, name: '直接访问' },
{ value: 580, name: '邮件营销' },
{ value: 484, name: '联盟广告' },
],
animationType: 'scale',
animationEasing: 'exponentialInOut',
animationDelay: function () {
return Math.random() * 100;
},
},
],
});
},
{ immediate: true }
);
</script>

View File

@ -0,0 +1,16 @@
import { PropType } from 'vue';
export interface BasicProps {
width: string;
height: string;
}
export const basicProps = {
width: {
type: String as PropType<string>,
default: '100%',
},
height: {
type: String as PropType<string>,
default: '280px',
},
};

View File

@ -0,0 +1,219 @@
export interface GrowCardItem {
icon: string;
title: string;
value?: number;
total: number;
color?: string;
action?: string;
footer?: string;
}
export const growCardList: GrowCardItem[] = [
{
title: '访问数',
icon: 'visit-count|svg',
value: 2000,
total: 120000,
color: 'green',
action: '月',
},
{
title: '成交额',
icon: 'total-sales|svg',
value: 20000,
total: 500000,
color: 'blue',
action: '月',
},
{
title: '下载数',
icon: 'download-count|svg',
value: 8000,
total: 120000,
color: 'orange',
action: '周',
},
{
title: '成交数',
icon: 'transaction|svg',
value: 5000,
total: 50000,
color: 'purple',
action: '年',
},
];
export const chartCardList: GrowCardItem[] = [
{
title: '总销售额',
icon: 'visit-count|svg',
total: 126560,
value: 234.56,
footer: '日均销售额',
},
{
title: '订单量',
icon: 'total-sales|svg',
value: 1234,
total: 8846,
color: 'blue',
footer: '日订单量',
},
{
title: '支付笔数',
icon: 'download-count|svg',
value: 60,
total: 6560,
color: 'orange',
footer: '转化率',
},
{
title: '运营活动效果',
icon: 'transaction|svg',
total: 78,
},
];
export const bdcCardList: GrowCardItem[] = [
{
title: '受理量',
icon: 'ant-design:info-circle-outlined',
total: 100,
value: 60,
footer: '今日受理量',
},
{
title: '办结量',
icon: 'ant-design:info-circle-outlined',
value: 54,
total: 87,
color: 'blue',
footer: '今日办结量',
},
{
title: '用户受理量',
icon: 'ant-design:info-circle-outlined',
value: 13,
total: 15,
color: 'orange',
footer: '用户今日受理量',
},
{
title: '用户办结量',
icon: 'ant-design:info-circle-outlined',
total: 9,
value: 7,
footer: '用户今日办结量',
},
];
export const table = {
dataSource: [
{ reBizCode: '1', type: '转移登记', acceptBy: '张三', acceptDate: '2019-01-22', curNode: '任务分派', flowRate: 60 },
{ reBizCode: '2', type: '抵押登记', acceptBy: '李四', acceptDate: '2019-01-23', curNode: '领导审核', flowRate: 30 },
{ reBizCode: '3', type: '转移登记', acceptBy: '王武', acceptDate: '2019-01-25', curNode: '任务处理', flowRate: 20 },
{ reBizCode: '4', type: '转移登记', acceptBy: '赵楼', acceptDate: '2019-11-22', curNode: '部门审核', flowRate: 80 },
{ reBizCode: '5', type: '转移登记', acceptBy: '钱就', acceptDate: '2019-12-12', curNode: '任务分派', flowRate: 90 },
{ reBizCode: '6', type: '转移登记', acceptBy: '孙吧', acceptDate: '2019-03-06', curNode: '任务处理', flowRate: 10 },
{ reBizCode: '7', type: '抵押登记', acceptBy: '周大', acceptDate: '2019-04-13', curNode: '任务分派', flowRate: 100 },
{ reBizCode: '8', type: '抵押登记', acceptBy: '吴二', acceptDate: '2019-05-09', curNode: '任务上报', flowRate: 50 },
{ reBizCode: '9', type: '抵押登记', acceptBy: '郑爽', acceptDate: '2019-07-12', curNode: '任务处理', flowRate: 63 },
{ reBizCode: '20', type: '抵押登记', acceptBy: '林有', acceptDate: '2019-12-12', curNode: '任务打回', flowRate: 59 },
{ reBizCode: '11', type: '转移登记', acceptBy: '码云', acceptDate: '2019-09-10', curNode: '任务签收', flowRate: 87 },
],
columns: [
{
title: '业务号',
align: 'center',
dataIndex: 'reBizCode',
},
{
title: '业务类型',
align: 'center',
dataIndex: 'type',
},
{
title: '受理人',
align: 'center',
dataIndex: 'acceptBy',
},
{
title: '受理时间',
align: 'center',
dataIndex: 'acceptDate',
},
{
title: '当前节点',
align: 'center',
dataIndex: 'curNode',
},
{
title: '办理时长',
align: 'center',
dataIndex: 'flowRate',
},
],
ipagination: {
current: 1,
pageSize: 5,
pageSizeOptions: ['10', '20', '30'],
showTotal: (total, range) => {
return range[0] + '-' + range[1] + ' 共' + total + '条';
},
showQuickJumper: true,
showSizeChanger: true,
total: 0,
},
};
export const table1 = {
dataSource: [
{ reBizCode: 'A001', type: '转移登记', acceptBy: '张四', acceptDate: '2019-01-22', curNode: '任务分派', flowRate: 12 },
{ reBizCode: 'A002', type: '抵押登记', acceptBy: '李吧', acceptDate: '2019-01-23', curNode: '任务签收', flowRate: 3 },
{ reBizCode: 'A003', type: '转移登记', acceptBy: '王三', acceptDate: '2019-01-25', curNode: '任务处理', flowRate: 24 },
{ reBizCode: 'A004', type: '转移登记', acceptBy: '赵二', acceptDate: '2019-11-22', curNode: '部门审核', flowRate: 10 },
{ reBizCode: 'A005', type: '转移登记', acceptBy: '钱大', acceptDate: '2019-12-12', curNode: '任务签收', flowRate: 8 },
{ reBizCode: 'A006', type: '转移登记', acceptBy: '孙就', acceptDate: '2019-03-06', curNode: '任务处理', flowRate: 10 },
{ reBizCode: 'A007', type: '抵押登记', acceptBy: '周晕', acceptDate: '2019-04-13', curNode: '部门审核', flowRate: 24 },
{ reBizCode: 'A008', type: '抵押登记', acceptBy: '吴有', acceptDate: '2019-05-09', curNode: '部门审核', flowRate: 30 },
{ reBizCode: 'A009', type: '抵押登记', acceptBy: '郑武', acceptDate: '2019-07-12', curNode: '任务分派', flowRate: 1 },
{ reBizCode: 'A0010', type: '抵押登记', acceptBy: '林爽', acceptDate: '2019-12-12', curNode: '部门审核', flowRate: 16 },
{ reBizCode: 'A0011', type: '转移登记', acceptBy: '码楼', acceptDate: '2019-09-10', curNode: '部门审核', flowRate: 7 },
],
columns: [
{
title: '业务号',
align: 'center',
dataIndex: 'reBizCode',
},
{
title: '受理人',
align: 'center',
dataIndex: 'acceptBy',
},
{
title: '发起时间',
align: 'center',
dataIndex: 'acceptDate',
},
{
title: '当前节点',
align: 'center',
dataIndex: 'curNode',
},
{
title: '超时时间',
align: 'center',
dataIndex: 'flowRate',
},
],
ipagination: {
current: 1,
pageSize: 5,
pageSizeOptions: ['10', '20', '30'],
showTotal: (total, range) => {
return range[0] + '-' + range[1] + ' 共' + total + '条';
},
showQuickJumper: true,
showSizeChanger: true,
total: 0,
},
};

View File

@ -0,0 +1,244 @@
<template>
<div class="p-4">
<ChartGroupCard class="enter-y" :loading="loading" type="bdc" />
<BdcTabCard class="!my-4 enter-y" :loading="loading" />
<a-row>
<a-col :span="24">
<a-card :loading="loading" :class="{ 'anty-list-cust': true }" :bordered="false">
<a-tabs v-model:activeKey="indexBottomTab" size="large" :tab-bar-style="{ marginBottom: '24px', paddingLeft: '16px' }">
<template #rightExtra>
<div class="extra-wrapper">
<a-radio-group v-model:value="indexRegisterType" @change="changeRegisterType">
<a-radio-button value="转移登记">转移登记</a-radio-button>
<a-radio-button value="抵押登记">抵押登记</a-radio-button>
<a-radio-button value="">所有</a-radio-button>
</a-radio-group>
</div>
</template>
<a-tab-pane tab="业务流程限时监管" key="1">
<a-table
:dataSource="dataSource"
size="default"
rowKey="reBizCode"
:columns="table.columns"
:pagination="ipagination"
@change="tableChange"
>
<template #bodyCell="{ column, record }">
<template v-if="column.dataIndex === 'flowRate'">
<Progress
:strokeColor="getPercentColor(record.flowRate)"
:format="getPercentFormat"
:percent="getFlowRateNumber(record.flowRate)"
style="width: 80px"
/>
</template>
</template>
</a-table>
</a-tab-pane>
<a-tab-pane loading="true" tab="业务节点限时监管" key="2">
<a-table
:dataSource="dataSource1"
size="default"
rowKey="reBizCode"
:columns="table1.columns"
:pagination="ipagination1"
@change="tableChange1"
>
<template #bodyCell="{ column, record }">
<template v-if="column.dataIndex === 'flowRate'">
<span style="color: red">{{ record.flowRate }}小时</span>
</template>
</template>
</a-table>
</a-tab-pane>
</a-tabs>
</a-card>
</a-col>
</a-row>
</div>
</template>
<script lang="ts" setup>
import { ref, unref } from 'vue';
import { Progress } from 'ant-design-vue';
import ChartGroupCard from '../components/ChartGroupCard.vue';
import BdcTabCard from '../components/BdcTabCard.vue';
import LineMulti from '/@/components/chart/LineMulti.vue';
import HeadInfo from '/@/components/chart/HeadInfo.vue';
import { table, table1 } from '../data';
const loading = ref(true);
setTimeout(() => {
loading.value = false;
}, 500);
const indexBottomTab = ref('1');
const indexRegisterType = ref('转移登记');
const dataSource = ref([]);
const dataSource1 = ref([]);
const ipagination = ref(table.ipagination);
const ipagination1 = ref(table1.ipagination);
function changeRegisterType(e) {
indexRegisterType.value = e.target.value;
if (unref(indexBottomTab) == '1') {
loadDataSource();
} else {
loadDataSource1();
}
}
function tableChange(pagination) {
ipagination.value.current = pagination.current;
ipagination.value.pageSize = pagination.pageSize;
loadDataSource();
}
function tableChange1(pagination) {
ipagination1.value.current = pagination.current;
ipagination1.value.pageSize = pagination.pageSize;
loadDataSource1();
}
function getFlowRateNumber(value) {
return +value;
}
function getPercentFormat(value) {
if (value == 100) {
return '超时';
} else {
return value + '%';
}
}
function getPercentColor(value) {
let p = +value;
if (p >= 90 && p < 100) {
return 'rgb(244, 240, 89)';
} else if (p >= 100) {
return 'red';
} else {
return 'rgb(16, 142, 233)';
}
}
function loadDataSource() {
dataSource.value = table.dataSource.filter((item) => {
if (!unref(indexRegisterType)) {
return true;
}
return item.type == unref(indexRegisterType);
});
}
function loadDataSource1() {
dataSource1.value = table1.dataSource.filter((item) => {
if (!unref(indexRegisterType)) {
return true;
}
return item.type == unref(indexRegisterType);
});
}
loadDataSource();
loadDataSource1();
</script>
<style lang="less" scoped>
.ant-table-wrapper {
:deep(.ant-table){
td,th {
padding: 10px;
}
}
}
.extra-wrapper {
line-height: 55px;
padding-right: 24px;
.extra-item {
display: inline-block;
margin-right: 24px;
a {
margin-left: 24px;
}
}
}
.item-group {
padding: 20px 0 8px 24px;
font-size: 0;
a {
color: rgba(0, 0, 0, 0.65);
display: inline-block;
font-size: 14px;
margin-bottom: 13px;
width: 25%;
}
}
.item-group {
.more-btn {
margin-bottom: 13px;
text-align: center;
}
}
.list-content-item {
color: rgba(0, 0, 0, 0.45);
display: inline-block;
vertical-align: middle;
font-size: 14px;
margin-left: 40px;
}
@media only screen and (min-width: 1600px) {
.list-content-item {
margin-left: 60px;
}
}
@media only screen and (max-width: 1300px) {
.list-content-item {
margin-left: 20px;
}
.width-hidden4 {
display: none;
}
}
.list-content-item {
span {
line-height: 20px;
}
}
.list-content-item {
p {
margin-top: 4px;
margin-bottom: 0;
line-height: 22px;
}
}
.anty-list-cust {
.ant-list-item-meta {
flex: 0.3 !important;
}
}
.anty-list-cust {
.ant-list-item-content {
flex: 1 !important;
justify-content: flex-start !important;
margin-left: 20px;
}
}
</style>

View File

@ -0,0 +1,149 @@
<template>
<div class="p-4">
<ChartGroupCard class="enter-y" :loading="loading" type="chart" />
<SaleTabCard class="!my-4 enter-y" :loading="loading" />
<a-row>
<a-col :span="24">
<a-card :loading="loading" :bordered="false" title="最近一周访问量统计">
<div class="infoArea">
<HeadInfo title="今日IP" :iconColor="ipColor" :content="loginfo.todayIp" icon="environment"></HeadInfo>
<HeadInfo title="今日访问" :iconColor="visitColor" :content="loginfo.todayVisitCount" icon="team"></HeadInfo>
<HeadInfo title="总访问量" :iconColor="seriesColor" :content="loginfo.totalVisitCount" icon="rise"></HeadInfo>
</div>
<LineMulti :chartData="lineMultiData" height="33vh" type="line" :option="{ legend: { top: 'bottom' } }"></LineMulti>
</a-card>
</a-col>
</a-row>
</div>
</template>
<script lang="ts" setup>
import { ref, watch } from 'vue';
import ChartGroupCard from '../components/ChartGroupCard.vue';
import SaleTabCard from '../components/SaleTabCard.vue';
import LineMulti from '/@/components/chart/LineMulti.vue';
import HeadInfo from '/@/components/chart/HeadInfo.vue';
import { getLoginfo, getVisitInfo } from '../api.ts';
import { useRootSetting } from '/@/hooks/setting/useRootSetting';
const loading = ref(true);
const { getThemeColor } = useRootSetting();
setTimeout(() => {
loading.value = false;
}, 500);
const loginfo = ref({});
const lineMultiData = ref([]);
function initLogInfo() {
getLoginfo(null).then((res) => {
if (res.success) {
Object.keys(res.result).forEach((key) => {
res.result[key] = res.result[key] + '';
});
loginfo.value = res.result;
}
});
getVisitInfo(null).then((res) => {
if (res.success) {
lineMultiData.value = [];
res.result.forEach((item) => {
lineMultiData.value.push({ name: item.type, type: 'ip', value: item.ip, color: ipColor.value });
lineMultiData.value.push({ name: item.type, type: 'visit', value: item.visit, color: visitColor.value });
});
}
});
}
const ipColor = ref();
const visitColor = ref();
const seriesColor = ref();
watch(
() => getThemeColor.value,
() => {
seriesColor.value = getThemeColor.value;
visitColor.value = '#67B962';
ipColor.value = getThemeColor.value;
initLogInfo();
},
{ immediate: true }
);
function getRandomColor() {
var letters = '0123456789ABCDEF';
var color = '#';
for (var i = 0; i < 6; i++) {
color += letters[Math.floor(Math.random() * 16)];
}
return color;
}
</script>
<style lang="less" scoped>
.infoArea {
display: flex;
justify-content: space-between;
padding: 0 10%;
.head-info.center {
padding: 0;
}
.head-info {
min-width: 0;
}
}
.circle-cust {
position: relative;
top: 28px;
left: -100%;
}
.extra-wrapper {
line-height: 55px;
padding-right: 24px;
.extra-item {
display: inline-block;
margin-right: 24px;
a {
margin-left: 24px;
}
}
}
/* 首页访问量统计 */
.head-info {
position: relative;
text-align: left;
padding: 0 32px 0 0;
min-width: 125px;
&.center {
text-align: center;
padding: 0 32px;
}
span {
color: rgba(0, 0, 0, 0.45);
display: inline-block;
font-size: 0.95rem;
line-height: 42px;
margin-bottom: 4px;
}
p {
line-height: 42px;
margin: 0;
a {
font-weight: 600;
font-size: 1rem;
}
}
}
.ant-card {
::v-deep(.ant-card-head-title) {
font-weight: normal;
}
}
</style>

View File

@ -0,0 +1,25 @@
<template>
<div class="p-4">
<GrowCard :loading="loading" class="enter-y" />
<SiteAnalysis class="!my-4 enter-y" :loading="loading" />
<div class="md:flex enter-y">
<VisitRadar class="md:w-1/3 w-full" :loading="loading" />
<VisitSource class="md:w-1/3 !md:mx-4 !md:my-0 !my-4 w-full" :loading="loading" />
<SalesProductPie class="md:w-1/3 w-full" :loading="loading" />
</div>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import GrowCard from '../components/GrowCard.vue';
import SiteAnalysis from '../components/SiteAnalysis.vue';
import VisitSource from '../components/VisitSource.vue';
import VisitRadar from '../components/VisitRadar.vue';
import SalesProductPie from '../components/SalesProductPie.vue';
const loading = ref(true);
setTimeout(() => {
loading.value = false;
}, 500);
</script>

View File

@ -0,0 +1,422 @@
<template>
<div class="index-container-ty">
<a-row type="flex" justify="start" :gutter="3">
<a-col :sm="24" :lg="12">
<a-card>
<template #title>
<div class="index-md-title">
<img src="../../../../assets/images/daiban.png" />
我的待办【{{ dataSource1.length }}】
</div>
</template>
<template v-if="dataSource1 && dataSource1.length > 0" #extra>
<a @click="goPage"
>更多
<Icon icon="ant-design:double-right-outlined" />
</a>
</template>
<a-table
:class="'my-index-table tytable1'"
ref="table1"
size="small"
rowKey="id"
:columns="columns"
:dataSource="dataSource1"
:pagination="false"
>
<template #ellipsisText="{ text }">
<JEllipsis :value="text" :length="textMaxLength"></JEllipsis>
</template>
<template #dayWarnning="{ text, record }">
<BellTwoTone style="font-size: 22px" :twoToneColor="getTipColor(record)" />
</template>
<template #action="{ text, record }">
<a @click="handleData">办理</a>
</template>
</a-table>
</a-card>
</a-col>
<a-col :sm="24" :lg="12">
<a-card>
<template #title>
<div class="index-md-title">
<img src="../../../../assets/images/zaiban.png" />
我的在办【{{ dataSource2.length }}】
</div>
</template>
<template v-if="dataSource2 && dataSource2.length > 0" #extra>
<a @click="goPage"
>更多
<Icon icon="ant-design:double-right-outlined" />
</a>
</template>
<a-table
:class="'my-index-table tytable2'"
ref="table1"
size="small"
rowKey="id"
:columns="columns"
:dataSource="dataSource2"
:pagination="false"
>
<template #ellipsisText="{ text }">
<JEllipsis :value="text" :length="textMaxLength"></JEllipsis>
</template>
<template #dayWarnning="{ text, record }">
<BellTwoTone style="font-size: 22px" :twoToneColor="getTipColor(record)" />
</template>
<template #action="{ text, record }">
<a @click="handleData">办理</a>
</template>
</a-table>
</a-card>
</a-col>
<a-col :span="24">
<div style="height: 5px"></div>
</a-col>
<a-col :sm="24" :lg="12">
<a-card>
<template #title>
<div class="index-md-title">
<img src="../../../../assets/images/guaz.png" />
我的挂账【{{ dataSource4.length }}】
</div>
</template>
<a-table
:class="'my-index-table tytable4'"
ref="table1"
size="small"
rowKey="id"
:columns="columns"
:dataSource="dataSource4"
:pagination="false"
>
<template #ellipsisText="{ text }">
<JEllipsis :value="text" :length="textMaxLength"></JEllipsis>
</template>
<template #dayWarnning="{ text, record }">
<BellTwoTone style="font-size: 22px" :twoToneColor="getTipColor(record)" />
</template>
<template #action="{ text, record }">
<a @click="handleData">办理</a>
</template>
</a-table>
</a-card>
</a-col>
<a-col :sm="24" :lg="12">
<a-card>
<template #title>
<div class="index-md-title">
<img src="../../../../assets/images/duban.png" />
我的督办【{{ dataSource3.length }}】
</div>
</template>
<a-table
:class="'my-index-table tytable3'"
ref="table1"
size="small"
rowKey="id"
:columns="columns"
:dataSource="dataSource3"
:pagination="false"
>
<template #ellipsisText="{ text }">
<JEllipsis :value="text" :length="textMaxLength"></JEllipsis>
</template>
<template #dayWarnning="{ text, record }">
<BellTwoTone style="font-size: 22px" :twoToneColor="getTipColor(record)" />
</template>
<template #action="{ text, record }">
<a @click="handleData">办理</a>
</template>
</a-table>
</a-card>
</a-col>
</a-row>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import noDataPng from '/@/assets/images/nodata.png';
import { useMessage } from '/@/hooks/web/useMessage';
import JEllipsis from '/@/components/Form/src/jeecg/components/JEllipsis.vue';
import { BulbTwoTone, BellTwoTone } from '@ant-design/icons-vue';
const tempSs1 = [
{
id: '001',
orderNo: '电[1]1267102',
orderTitle: '药品出问题了',
restDay: 1,
},
{
id: '002',
orderNo: '电[4]5967102',
orderTitle: '吃了xxx医院的药病情越来越严重',
restDay: 0,
},
{
id: '003',
orderNo: '电[3]5988987',
orderTitle: '今天去超市买鸡蛋,鸡蛋都是坏的',
restDay: 7,
},
{
id: '004',
orderNo: '电[2]5213491',
orderTitle: 'xx宝实体店高价售卖xx',
restDay: 5,
},
{
id: '005',
orderNo: '电[1]1603491',
orderTitle: '以红利相诱,答应退保后扣一年费用',
restDay: 0,
},
];
const tempSs2 = [
{
id: '001',
orderTitle: '我要投诉这个大超市',
orderNo: '电[1]10299456',
restDay: 6,
},
{
id: '002',
orderTitle: 'xxx医院乱开药方,售卖假药',
orderNo: '电[2]20235691',
restDay: 0,
},
{
id: '003',
orderTitle: '我想问问这家店是干啥的',
orderNo: '电[3]495867322',
restDay: 7,
},
{
id: '004',
orderTitle: '我要举报朝阳区奥森公园酒店',
orderNo: '电[2]1193849',
restDay: 3,
},
{
id: '005',
orderTitle: '我今天吃饭吃到一个石头子',
orderNo: '电[4]56782344',
restDay: 9,
},
];
//4-7天
const tip_green = 'rgba(0, 255, 0, 1)';
//1-3天
const tip_yellow = 'rgba(255, 255, 0, 1)';
//超期
const tip_red = 'rgba(255, 0, 0, 1)';
const textMaxLength = 8;
const $message = useMessage();
const dataSource1 = ref([]);
const dataSource2 = ref([]);
const dataSource3 = ref([]);
const dataSource4 = ref([]);
const columns = [
{
title: '',
dataIndex: '',
key: 'rowIndex',
width: 50,
fixed: 'left',
align: 'center',
slots: { customRender: 'dayWarnning' },
},
{
title: '剩余天数',
align: 'center',
dataIndex: 'restDay',
width: 80,
},
{
title: '工单标题',
align: 'center',
dataIndex: 'orderTitle',
slots: { customRender: 'ellipsisText' },
},
{
title: '工单编号',
align: 'center',
dataIndex: 'orderNo',
},
{
title: '操作',
dataIndex: 'action',
align: 'center',
slots: { customRender: 'action' },
},
];
function getTipColor(rd) {
let num = rd.restDay;
if (num <= 0) {
return tip_red;
} else if (num >= 1 && num < 4) {
return tip_yellow;
} else if (num >= 4) {
return tip_green;
}
}
function mock() {
dataSource1.value = tempSs1;
dataSource2.value = tempSs2;
dataSource3.value = tempSs1;
dataSource4.value = tempSs2;
ifNullDataSource(dataSource4, '.tytable4');
}
function ifNullDataSource(ds, tb) {
if (!ds || ds.length == 0) {
var tmp = document.createElement('img');
tmp.src = noDataPng;
tmp.width = 300;
let tbclass = `${tb} .ant-table-placeholder`;
document.querySelector(tbclass).innerHTML = '';
document.querySelector(tbclass).appendChild(tmp);
}
}
function handleData() {
$message.createMessage.success('办理完成');
}
function goPage() {
$message.createMessage.success('请根据具体业务跳转页面');
}
mock();
</script>
<style scoped lang="less">
.my-index-table {
height: 270px;
table {
font-size: 14px !important;
}
}
.index-container-ty {
margin: 12px 12px 0;
:deep(.ant-card-body) {
padding: 10px 12px 0 12px;
}
:deep(.ant-card-head) {
line-height: 24px;
min-height: 24px;
background: #7196fb !important;
.ant-card-head-title {
padding-top: 6px;
padding-bottom: 6px;
}
.ant-card-extra {
padding: 0;
a {
color: #fff;
}
a:hover {
color: #152ede;
}
}
}
:deep(.ant-table-footer) {
text-align: right;
padding: 6px 12px 6px 6px;
background: #fff;
border-top: 2px solid #f7f1f1;
}
.index-md-title {
position: relative;
width: 100%;
color: #fff;
font-size: 21px;
font-family: cursive;
padding-left: 25px;
img {
position: absolute;
height: 25px;
left: 0;
}
}
:deep(.ant-table-thead > tr > th),
:deep(.ant-table-tbody > tr > td) {
border-bottom: 1px solid #90aeff;
}
:deep(
.ant-table-small
> .ant-table-content
> .ant-table-fixed-left
> .ant-table-body-outer
> .ant-table-body-inner
> table
> .ant-table-thead
> tr
> th),
:deep(
.ant-table-small
> .ant-table-content
> .ant-table-fixed-right
> .ant-table-body-outer
> .ant-table-body-inner
> table
> .ant-table-thead
> tr
> th) {
border-bottom: 1px solid #90aeff;
}
:deep(.ant-table-small > .ant-table-content > .ant-table-scroll > .ant-table-body > table > .ant-table-thead > tr > th) {
border-bottom: 1px solid #90aeff;
}
:deep(.ant-table-small) {
border: 1px solid #90aeff;
}
:deep(.ant-table-placeholder) {
padding: 0;
height: 215px;
}
}
</style>

View File

@ -0,0 +1,24 @@
<template>
<IndexChart v-if="indexStyle === 0"></IndexChart>
<IndexDef v-if="indexStyle === 1"></IndexDef>
<IndexBdc v-if="indexStyle == 2"></IndexBdc>
<IndexTask v-if="indexStyle == 3"></IndexTask>
<div style="width: 100%; text-align: right; margin-top: 20px">
首页主题
<a-radio-group v-model:value="indexStyle">
<a-radio :value="0">默认</a-radio>
<a-radio :value="1">销量统计</a-radio>
<a-radio :value="2">业务统计</a-radio>
<a-radio :value="3">我的任务</a-radio>
</a-radio-group>
</div>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import IndexDef from './homePage/IndexDef.vue';
import IndexChart from './homePage/IndexChart.vue';
import IndexBdc from './homePage/IndexBdc.vue';
import IndexTask from './homePage/IndexTask.vue';
const indexStyle = ref(0);
</script>

View File

@ -0,0 +1,85 @@
<template>
<div v-if="visible" ref="aideWrapRef" class="aide-wrap" @click="handleGo">
<div class="icon">
<svg t="1706259688149" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="2056" width="17" height="17">
<path
fill="currentColor"
d="M427.904 492.608a16.896 16.896 0 0 0 0 24.96l438.528 426.368a54.272 54.272 0 0 0 77.056 0 50.752 50.752 0 0 0 0-74.88L504.96 442.688a18.112 18.112 0 0 0-25.664 0l-51.392 49.92z m-12.16-137.728l-70.272-58.624a36.48 36.48 0 0 0-46.208 0l-46.144 38.464c-13.248 11.008-13.248 27.52 0 38.464l70.336 58.624a24.32 24.32 0 0 0 30.784 0l61.568-51.264c8.768-7.36 8.768-18.304 0-25.664z m-160.64 448c23.68-78.72 81.152-140.8 158.4-165.696a13.12 13.12 0 0 0 0-24.832C338.24 587.52 278.848 527.424 255.104 446.656c-3.968-12.416-19.84-12.416-23.808 0-23.68 78.72-81.152 140.8-158.4 165.696a13.12 13.12 0 0 0 0 24.832c75.264 24.896 134.656 84.928 158.4 165.76 3.968 10.304 19.84 10.304 23.808 0zM621.184 71.04a203.584 203.584 0 0 1-132.096 132.096 11.008 11.008 0 0 0 0 20.48 203.584 203.584 0 0 1 132.16 132.16 11.008 11.008 0 0 0 20.48 0 203.584 203.584 0 0 1 132.096-132.16 11.008 11.008 0 0 0 0-20.48 203.584 203.584 0 0 1-132.096-132.096c-3.776-9.28-18.624-9.28-20.48 0zM191.488 282.368c15.936-48.512 53.76-83.968 105.536-98.88 7.936-1.92 7.936-13.056 0-14.976-51.776-14.912-89.6-50.368-105.536-98.88-1.984-7.488-13.952-7.488-15.936 0-15.936 48.512-53.76 83.968-105.536 98.88-7.936 1.92-7.936 13.056 0 14.976 51.84 14.912 89.6 50.368 105.6 98.88 1.92 7.488 13.888 7.488 15.872 0z"
p-id="2057"
></path>
</svg>
</div>
<a-popconfirm
:open="popconfirmVisible"
title="确定AI助手退出吗"
ok-text="确定"
cancel-text="取消"
@cancel="handleCancel"
@confirm="handleConfirm"
>
<span class="text">AI助手</span>
</a-popconfirm>
</div>
</template>
<script setup>
import { onMounted, ref } from 'vue';
import { router } from '/@/router';
import { AIDE_FLAG } from '/@/enums/cacheEnum';
import { getToken } from '/@/utils/auth';
import { getAuthCache, setAuthCache, removeAuthCache } from '/@/utils/auth';
const visible = ref(1);
const aideWrapRef = ref(null);
const popconfirmVisible = ref(false);
onMounted(() => {
const aideFlag = getAuthCache(AIDE_FLAG);
if (aideFlag && aideFlag == getToken()) {
visible.value = false;
} else {
visible.value = true;
}
if (visible.value) {
aideWrapRef.value.addEventListener('contextmenu', (e) => {
popconfirmVisible.value = true;
e.preventDefault();
});
}
});
const handleCancel = () => {
popconfirmVisible.value = false;
};
const handleConfirm = () => {
popconfirmVisible.value = false;
visible.value = false;
setAuthCache(AIDE_FLAG, getToken());
};
const handleGo = (params) => {
router.push({ path: '/ai' });
};
</script>
<style lang="less" scoped>
.aide-wrap {
position: fixed;
top: 50%;
right: 0;
transform: translate(0, -50%);
background-color: @primary-color;
height: 46px;
width: 46px;
border-radius: 50%;
text-align: center;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
line-height: 1;
color: #fff;
cursor: pointer;
.text {
font-size: 12px;
transform: scale(0.88);
}
}
</style>

View File

@ -0,0 +1,26 @@
<template>
<div class="wrap">
<div class="content">
<AiChat></AiChat>
</div>
</div>
</template>
<script setup>
import AiChat from '/@/components/jeecg/AiChat/index.vue';
</script>
<style lang="less" scoped>
.wrap {
height: 100%;
width: 100%;
padding: 20px;
.content {
background: #fff;
width: 100%;
height: 100%;
padding: 20px;
}
}
</style>

View File

@ -0,0 +1,31 @@
<template>
<Card title="最新动态" v-bind="$attrs">
<template #extra>
<a-button type="link" size="small">更多</a-button>
</template>
<List item-layout="horizontal" :data-source="dynamicInfoItems">
<template #renderItem="{ item }">
<ListItem>
<ListItemMeta>
<template #description>
{{ item.date }}
</template>
<!-- eslint-disable-next-line -->
<template #title> {{ item.name }} <span v-html="item.desc"> </span> </template>
<template #avatar>
<Icon :icon="item.avatar" :size="30" />
</template>
</ListItemMeta>
</ListItem>
</template>
</List>
</Card>
</template>
<script lang="ts" setup>
import { Card, List } from 'ant-design-vue';
import { dynamicInfoItems } from './data';
import { Icon } from '/@/components/Icon';
const ListItem = List.Item;
const ListItemMeta = List.Item.Meta;
</script>

View File

@ -0,0 +1,34 @@
<template>
<Card title="项目" v-bind="$attrs">
<template #extra>
<a-button type="link" size="small">更多</a-button>
</template>
<template v-for="item in items" :key="item">
<CardGrid class="!md:w-1/3 !w-full">
<span class="flex">
<Icon :icon="item.icon" :color="item.color" size="30" />
<span class="text-lg ml-4">{{ item.title }}</span>
</span>
<div class="flex mt-2 h-10 text-secondary"> {{ item.desc }} </div>
<div class="flex justify-between text-secondary">
<span>{{ item.group }}</span>
<span>{{ item.date }}</span>
</div>
</CardGrid>
</template>
</Card>
</template>
<script lang="ts">
import { defineComponent } from 'vue';
import { Card } from 'ant-design-vue';
import { Icon } from '/@/components/Icon';
import { groupItems } from './data';
export default defineComponent({
components: { Card, CardGrid: Card.Grid, Icon },
setup() {
return { items: groupItems };
},
});
</script>

View File

@ -0,0 +1,19 @@
<template>
<Card title="快捷导航" v-bind="$attrs">
<template v-for="item in navItems" :key="item">
<CardGrid>
<span class="flex flex-col items-center">
<Icon :icon="item.icon" :color="item.color" size="20" />
<span class="text-md mt-2">{{ item.title }}</span>
</span>
</CardGrid>
</template>
</Card>
</template>
<script lang="ts" setup>
import { Card } from 'ant-design-vue';
import { navItems } from './data';
import { Icon } from '/@/components/Icon';
const CardGrid = Card.Grid;
</script>

View File

@ -0,0 +1,94 @@
<template>
<Card title="销售统计" :loading="loading">
<div ref="chartRef" :style="{ width, height }"></div>
</Card>
</template>
<script lang="ts" setup>
import { Ref, ref, watch } from 'vue';
import { Card } from 'ant-design-vue';
import { useECharts } from '/@/hooks/web/useECharts';
const props = defineProps({
loading: Boolean,
width: {
type: String as PropType<string>,
default: '100%',
},
height: {
type: String as PropType<string>,
default: '400px',
},
});
const chartRef = ref<HTMLDivElement | null>(null);
const { setOptions } = useECharts(chartRef as Ref<HTMLDivElement>);
watch(
() => props.loading,
() => {
if (props.loading) {
return;
}
setOptions({
legend: {
bottom: 0,
data: ['Visits', 'Sales'],
},
tooltip: {},
radar: {
radius: '60%',
splitNumber: 8,
indicator: [
{
name: '2017',
},
{
name: '2017',
},
{
name: '2018',
},
{
name: '2019',
},
{
name: '2020',
},
{
name: '2021',
},
],
},
series: [
{
type: 'radar' as 'custom',
symbolSize: 0,
areaStyle: {
shadowBlur: 0,
shadowColor: 'rgba(0,0,0,.2)',
shadowOffsetX: 0,
shadowOffsetY: 10,
opacity: 1,
},
data: [
{
value: [90, 50, 86, 40, 50, 20],
name: 'Visits',
itemStyle: {
color: '#b6a2de',
},
},
{
value: [70, 75, 70, 76, 20, 85],
name: 'Sales',
itemStyle: {
color: '#67e0e3',
},
},
],
},
],
});
},
{ immediate: true }
);
</script>

View File

@ -0,0 +1,33 @@
<template>
<div class="lg:flex">
<Avatar :src="userinfo.avatar || headerImg" :size="72" class="!mx-auto !block" />
<div class="md:ml-6 flex flex-col justify-center md:mt-0 mt-2">
<h1 class="md:text-lg text-md">早安, {{ userinfo.realname }}, 开始您一天的工作吧!</h1>
<span class="text-secondary"> 今日晴20℃ - 32℃ </span>
</div>
<div class="flex flex-1 justify-end md:mt-0 mt-4">
<div class="flex flex-col justify-center text-right">
<span class="text-secondary"> 待办 </span>
<span class="text-2xl">2/10</span>
</div>
<div class="flex flex-col justify-center text-right md:mx-16 mx-12">
<span class="text-secondary"> 项目 </span>
<span class="text-2xl">8</span>
</div>
<div class="flex flex-col justify-center text-right md:mr-10 mr-4">
<span class="text-secondary"> 团队 </span>
<span class="text-2xl">300</span>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { computed } from 'vue';
import { Avatar } from 'ant-design-vue';
import { useUserStore } from '/@/store/modules/user';
import headerImg from '/@/assets/images/header.jpg';
const userStore = useUserStore();
const userinfo = computed(() => userStore.getUserInfo);
</script>

View File

@ -0,0 +1,156 @@
interface GroupItem {
title: string;
icon: string;
color: string;
desc: string;
date: string;
group: string;
}
interface NavItem {
title: string;
icon: string;
color: string;
}
interface DynamicInfoItem {
avatar: string;
name: string;
date: string;
desc: string;
}
export const navItems: NavItem[] = [
{
title: '首页',
icon: 'ion:home-outline',
color: '#1fdaca',
},
{
title: '仪表盘',
icon: 'ion:grid-outline',
color: '#bf0c2c',
},
{
title: '组件',
icon: 'ion:layers-outline',
color: '#e18525',
},
{
title: '系统管理',
icon: 'ion:settings-outline',
color: '#3fb27f',
},
{
title: '权限管理',
icon: 'ion:key-outline',
color: '#4daf1bc9',
},
{
title: '图表',
icon: 'ion:bar-chart-outline',
color: '#00d8ff',
},
];
export const dynamicInfoItems: DynamicInfoItem[] = [
{
avatar: 'dynamic-avatar-1|svg',
name: '威廉',
date: '刚刚',
desc: `在 <a>开源组</a> 创建了项目 <a>Vue</a>`,
},
{
avatar: 'dynamic-avatar-2|svg',
name: '艾文',
date: '1个小时前',
desc: `关注了 <a>威廉</a> `,
},
{
avatar: 'dynamic-avatar-3|svg',
name: '克里斯',
date: '1天前',
desc: `发布了 <a>个人动态</a> `,
},
{
avatar: 'dynamic-avatar-4|svg',
name: 'Jeecg',
date: '2天前',
desc: `发表文章 <a>如何编写一个Vite插件</a> `,
},
{
avatar: 'dynamic-avatar-5|svg',
name: '皮特',
date: '3天前',
desc: `回复了 <a>杰克</a> 的问题 <a>如何进行项目优化?</a>`,
},
{
avatar: 'dynamic-avatar-6|svg',
name: '杰克',
date: '1周前',
desc: `关闭了问题 <a>如何运行项目</a> `,
},
{
avatar: 'dynamic-avatar-1|svg',
name: '威廉',
date: '1周前',
desc: `发布了 <a>个人动态</a> `,
},
{
avatar: 'dynamic-avatar-1|svg',
name: '威廉',
date: '2021-04-01 20:00',
desc: `推送了代码到 <a>Github</a>`,
},
];
export const groupItems: GroupItem[] = [
{
title: 'Github',
icon: 'carbon:logo-github',
color: '',
desc: '不要等待机会,而要创造机会。',
group: '开源组',
date: '2021-04-01',
},
{
title: 'Vue',
icon: 'ion:logo-vue',
color: '#3fb27f',
desc: '现在的你决定将来的你。',
group: '算法组',
date: '2021-04-01',
},
{
title: 'Html5',
icon: 'ion:logo-html5',
color: '#e18525',
desc: '没有什么才能比努力更重要。',
group: '上班摸鱼',
date: '2021-04-01',
},
{
title: 'Angular',
icon: 'ion:logo-angular',
color: '#bf0c2c',
desc: '热情和欲望可以突破一切难关。',
group: 'UI',
date: '2021-04-01',
},
{
title: 'React',
icon: 'bx:bxl-react',
color: '#00d8ff',
desc: '健康的身体是实目标的基石。',
group: '技术牛',
date: '2021-04-01',
},
{
title: 'Js',
icon: 'ion:logo-javascript',
color: '#4daf1bc9',
desc: '路是走出来的,而不是空想出来的。',
group: '架构组',
date: '2021-04-01',
},
];

View File

@ -0,0 +1,36 @@
<template>
<PageWrapper>
<template #headerContent> <WorkbenchHeader /> </template>
<div class="lg:flex">
<div class="lg:w-7/10 w-full !mr-4 enter-y">
<ProjectCard :loading="loading" class="enter-y" />
<DynamicInfo :loading="loading" class="!my-4 enter-y" />
</div>
<div class="lg:w-3/10 w-full enter-y">
<QuickNav :loading="loading" class="enter-y" />
<Card class="!my-4 enter-y" :loading="loading">
<img class="xl:h-50 h-30 mx-auto" src="../../../assets/svg/illustration.svg" />
</Card>
<SaleRadar :loading="loading" class="enter-y" />
</div>
</div>
</PageWrapper>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { Card } from 'ant-design-vue';
import { PageWrapper } from '/@/components/Page';
import WorkbenchHeader from './components/WorkbenchHeader.vue';
import ProjectCard from './components/ProjectCard.vue';
import QuickNav from './components/QuickNav.vue';
import DynamicInfo from './components/DynamicInfo.vue';
import SaleRadar from './components/SaleRadar.vue';
const loading = ref(true);
setTimeout(() => {
loading.value = false;
}, 500);
</script>