新增:新增前端项目模块(基于 Vue3、TypeScript、Arco Design Pro Vue 技术栈),已对接现有 API

This commit is contained in:
2022-12-28 22:44:57 +08:00
parent 6a7ad96fa3
commit 9064d06ff5
239 changed files with 22549 additions and 34 deletions

View File

@@ -0,0 +1,94 @@
<template>
<div :class="['chat-item', itemData.isCollect ? 'chat-item-collected' : '']">
<a-space :size="4" direction="vertical" fill>
<a-typography-text type="warning">
{{ itemData.username }}
</a-typography-text>
<a-typography-text>{{ itemData.content }}</a-typography-text>
<div class="chat-item-footer">
<div class="chat-item-time">
<a-typography-text type="secondary">
{{ itemData.time }}
</a-typography-text>
</div>
<div class="chat-item-actions">
<div class="chat-item-actions-item">
<icon-command />
</div>
<div class="chat-item-actions-item chat-item-actions-collect">
<icon-star />
</div>
</div>
</div>
</a-space>
</div>
</template>
<script lang="ts" setup>
import { PropType } from 'vue';
import { ChatRecord } from '@/api/message';
defineProps({
itemData: {
type: Object as PropType<ChatRecord>,
default() {
return {};
},
},
});
</script>
<style scoped lang="less">
.chat-item {
padding: 8px;
font-size: 12px;
line-height: 20px;
border-radius: 2px;
&-footer {
display: flex;
align-items: center;
justify-content: space-between;
}
&-actions {
display: flex;
opacity: 0;
&-item {
display: flex;
align-items: center;
justify-content: center;
width: 20px;
height: 20px;
margin-right: 4px;
color: var(--color-text-3);
font-size: 14px;
border-radius: 50%;
cursor: pointer;
&:hover {
background-color: rgb(var(--gray-3));
}
&:last-child {
margin-right: 0;
}
}
}
&-collected {
.chat-item-actions-collect {
color: rgb(var(--gold-6));
}
}
&:hover {
background-color: rgb(var(--gray-2));
.chat-item-actions {
opacity: 1;
}
}
}
</style>

View File

@@ -0,0 +1,76 @@
<template>
<div class="chat-list">
<ChatItem v-for="item in renderList" :key="item.id" :item-data="item" />
<a-result v-if="!renderList.length" status="404" />
</div>
</template>
<script lang="ts" setup>
import { PropType } from 'vue';
import { ChatRecord } from '@/api/message';
import ChatItem from './chat-item.vue';
defineProps({
renderList: {
type: Array as PropType<ChatRecord[]>,
default() {
return [];
},
},
});
</script>
<style scoped lang="less">
.chat-item {
padding: 8px;
font-size: 12px;
line-height: 20px;
border-radius: 2px;
&-footer {
display: flex;
align-items: center;
justify-content: space-between;
}
&-actions {
display: flex;
opacity: 0;
&-item {
display: flex;
align-items: center;
justify-content: center;
width: 20px;
height: 20px;
margin-right: 4px;
color: var(--color-text-3);
font-size: 14px;
border-radius: 50%;
cursor: pointer;
&:hover {
background-color: rgb(var(--gray-3));
}
&:last-child {
margin-right: 0;
}
}
}
&-collected {
.message-item-actions-collect {
color: rgb(var(--gold-6));
}
}
&:hover {
background-color: rgb(var(--gray-2));
.message-item-actions {
opacity: 1;
}
}
}
</style>

View File

@@ -0,0 +1,79 @@
<template>
<a-card
class="general-card chat-panel"
:title="$t('monitor.title.chatPanel')"
:bordered="false"
:header-style="{ paddingBottom: '0' }"
:body-style="{
height: '100%',
paddingTop: '16px',
display: 'flex',
flexFlow: 'column',
}"
>
<a-space :size="8">
<a-select style="width: 86px" default-value="all">
<a-option value="all">
{{ $t('monitor.chat.options.all') }}
</a-option>
</a-select>
<a-input-search
:placeholder="$t('monitor.chat.placeholder.searchCategory')"
/>
<a-button type="text">
<icon-download />
</a-button>
</a-space>
<div class="chat-panel-content">
<a-spin :loading="loading" style="width: 100%">
<ChatList :render-list="chatData" />
</a-spin>
</div>
<div class="chat-panel-footer">
<a-space :size="8">
<a-Input>
<template #suffix>
<icon-face-smile-fill />
</template>
</a-Input>
<a-button type="primary">{{ $t('monitor.chat.update') }}</a-button>
</a-space>
</div>
</a-card>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { queryChatList, ChatRecord } from '@/api/message';
import useLoading from '@/hooks/loading';
import ChatList from './chat-list.vue';
const { loading, setLoading } = useLoading(true);
const chatData = ref<ChatRecord[]>([]);
const fetchData = async () => {
try {
const { data } = await queryChatList();
chatData.value = data;
} catch (err) {
// you can report use errorHandler or other
} finally {
setLoading(false);
}
};
fetchData();
</script>
<style scoped lang="less">
.chat-panel {
display: flex;
flex-direction: column;
height: 100%;
// padding: 20px;
background-color: var(--color-bg-2);
&-content {
flex: 1;
margin: 20px 0;
}
}
</style>

View File

@@ -0,0 +1,133 @@
<template>
<div>
<a-table
:columns="columns"
:data="data"
row-key="id"
:row-selection="{
type: 'checkbox',
showCheckedAll: true,
}"
:border="false"
:pagination="false"
/>
<a-typography-text type="secondary" class="data-statistic-list-tip">
{{ $t('monitor.list.tip.rotations') }} {{ data.length }}
{{ $t('monitor.list.tip.rest') }}
</a-typography-text>
</div>
</template>
<script lang="ts" setup>
import { computed, h, compile } from 'vue';
import { useI18n } from 'vue-i18n';
import type {
TableColumnData,
TableData,
} from '@arco-design/web-vue/es/table/interface.d';
interface PreviewRecord {
cover: string;
name: string;
duration: string;
id: string;
status: number;
}
const { t } = useI18n();
const data: PreviewRecord[] = [
{
cover:
'http://p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/c788fc704d32cf3b1136c7d45afc2669.png~tplv-uwbnlip3yd-webp.webp',
name: '视频直播',
duration: '00:05:19',
id: '54e23ade',
status: -1,
},
];
const renderTag = (status: number) => {
if (status === -1) {
return `<a-tag color="red" class='data-statistic-list-cover-tag'>
${t('monitor.list.tag.auditFailed')}
</a-tag>`;
}
return '';
};
// Using the Render function is more flexible than using templates.
// But, cannot bind context and local scopes are also lost
const columns = computed(() => {
return [
{
title: t('monitor.list.title.order'),
render({
rowIndex,
}: {
record: TableData;
column: TableColumnData;
rowIndex: number;
}) {
const tmp = `<span>${rowIndex + 1}</span>`;
return h(compile(tmp));
},
},
{
title: t('monitor.list.title.cover'),
render({
record,
}: {
record: TableData;
column: TableColumnData;
rowIndex: number;
}) {
const tmp = `<div class='data-statistic-list-cover-wrapper'>
<img src=${record.cover} />
${renderTag(record.status)}
</div>`;
return h(compile(tmp));
},
},
{
title: t('monitor.list.title.name'),
dataIndex: 'name',
},
{
dataIndex: 'duration',
title: t('monitor.list.title.duration'),
},
{
dataIndex: 'id',
title: t('monitor.list.title.id'),
},
];
});
</script>
<style lang="less">
// Warning: Here is the global style
.data-statistic {
&-list {
&-cover {
&-wrapper {
position: relative;
height: 68px;
img {
height: 100%;
}
}
&-tag {
position: absolute;
top: 6px;
left: 6px;
}
}
&-tip {
display: block;
margin-top: 16px;
text-align: center;
}
}
}
</style>

View File

@@ -0,0 +1,56 @@
<template>
<a-card :bordered="false" :body-style="{ padding: '20px' }">
<a-tabs default-active-tab="liveMethod">
<a-tab-pane
key="liveMethod"
:title="$t('monitor.tab.title.liveMethod')"
/>
<a-tab-pane
key="onlinePopulation"
:title="$t('monitor.tab.title.onlinePopulation')"
/>
</a-tabs>
<div class="data-statistic-content">
<a-radio-group :default-value="3" type="button">
<a-radio :value="1">{{ $t('monitor.liveMethod.normal') }}</a-radio>
<a-radio :value="2">{{ $t('monitor.liveMethod.flowControl') }}</a-radio>
<a-radio :value="3">{{ $t('monitor.liveMethod.video') }}</a-radio>
<a-radio :value="4">{{ $t('monitor.liveMethod.web') }}</a-radio>
</a-radio-group>
<div class="data-statistic-list-wrapper">
<div class="data-statistic-list-header">
<a-button type="text">{{ $t('monitor.editCarousel') }}</a-button>
<a-button disabled>{{ $t('monitor.startCarousel') }}</a-button>
</div>
<div class="data-statistic-list-content">
<DataStatisticList />
</div>
</div>
</div>
</a-card>
</template>
<script lang="ts" setup>
import DataStatisticList from './data-statistic-list.vue';
</script>
<style scoped lang="less">
.data-statistic {
&-content {
padding: 20px 0;
}
&-list {
&-header {
display: flex;
justify-content: space-between;
margin-top: 16px;
}
&-content {
margin-top: 16px;
}
}
}
</style>

View File

@@ -0,0 +1,32 @@
<template>
<a-card class="general-card" :title="$t('monitor.title.quickOperation')">
<a-space direction="vertical" fill :size="10">
<a-button long>
{{ $t('monitor.quickOperation.changeClarity') }}
<template #icon>
<IconTags />
</template>
</a-button>
<a-button long>
{{ $t('monitor.quickOperation.switchStream') }}
<template #icon>
<IconSwap />
</template>
</a-button>
<a-button long>
{{ $t('monitor.quickOperation.removeClarity') }}
<template #icon>
<IconStop />
</template>
</a-button>
<a-button long>
{{ $t('monitor.quickOperation.pushFlowGasket') }}
<template #icon>
<IconArrowRight />
</template>
</a-button>
</a-space>
</a-card>
</template>
<script lang="ts" setup></script>

View File

@@ -0,0 +1,34 @@
<template>
<a-card class="general-card" :title="$t('monitor.title.studioInfo')">
<a-form :model="{}" layout="vertical">
<a-form-item :label="$t('monitor.studioInfo.label.studioTitle')" required>
<a-input
:placeholder="`王立群${$t(
'monitor.studioInfo.placeholder.studioTitle'
)}`"
/>
</a-form-item>
<a-form-item
:label="$t('monitor.studioInfo.label.onlineNotification')"
required
>
<a-textarea />
</a-form-item>
<a-form-item
:label="$t('monitor.studioInfo.label.studioCategory')"
required
>
<a-input-search />
</a-form-item>
<a-form-item
:label="$t('monitor.studioInfo.label.studioCategory')"
required
>
<a-input-search />
</a-form-item>
</a-form>
<a-button type="primary">{{ $t('monitor.studioInfo.btn.fresh') }}</a-button>
</a-card>
</template>
<script lang="ts" setup></script>

View File

@@ -0,0 +1,84 @@
<template>
<a-card
class="general-card"
:title="$t('monitor.studioStatus.title.studioStatus')"
>
<template #extra>
<a-tag color="green">{{ $t('monitor.studioStatus.smooth') }}</a-tag>
</template>
<a-descriptions layout="horizontal" :data="dataStatus" :column="2">
<template #label="{ label }">
<span
v-if="['mainstream', 'hotStandby', 'coldStandby'].includes(label)"
>
<a-typography-text style="padding-right: 8px">
{{ $t(`monitor.studioStatus.${label}`) }}
</a-typography-text>
{{ $t('monitor.studioStatus.bitRate') }}
</span>
<span v-else>{{ label }}</span>
</template>
</a-descriptions>
<a-typography-title style="margin-bottom: 16px" :heading="6">
{{ $t('monitor.studioStatus.title.pictureInfo') }}
</a-typography-title>
<a-descriptions layout="horizontal" :data="dataPicture" :column="2" />
</a-card>
</template>
<script lang="ts" setup>
import { computed } from 'vue';
import { useI18n } from 'vue-i18n';
const { t } = useI18n();
const dataStatus = computed(() => [
{
label: 'mainstream',
value: '6 Mbps',
},
{
label: t('monitor.studioStatus.frameRate'),
value: '60',
},
{
label: 'hotStandby',
value: '6 Mbps',
},
{
label: t('monitor.studioStatus.frameRate'),
value: '60',
},
{
label: 'coldStandby',
value: '6 Mbps',
},
{
label: t('monitor.studioStatus.frameRate'),
value: '60',
},
]);
const dataPicture = computed(() => [
{
label: t('monitor.studioStatus.line'),
value: '热备',
},
{
label: 'CDN',
value: 'KS',
},
{
label: t('monitor.studioStatus.play'),
value: 'FLV',
},
{
label: t('monitor.studioStatus.pictureQuality'),
value: '原画',
},
]);
</script>
<style scoped lang="less">
:deep(.arco-descriptions-item-label) {
padding-right: 6px;
}
</style>

View File

@@ -0,0 +1,51 @@
<template>
<a-card class="general-card" :title="$t('monitor.title.studioPreview')">
<template #extra>
<icon-more />
</template>
<div class="studio-wrapper">
<img
src="http://p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/c788fc704d32cf3b1136c7d45afc2669.png~tplv-uwbnlip3yd-webp.webp"
class="studio-preview"
/>
<div class="studio-bar">
<div v-if="userInfo">
<a-space :size="12">
<a-avatar :size="24">
<img :src="userInfo.avatar" />
</a-avatar>
<a-typography-text>
{{ userInfo.nickname }} {{ $t('monitor.studioPreview.studio') }}
</a-typography-text>
</a-space>
</div>
<a-typography-text type="secondary">
36,000 {{ $t('monitor.studioPreview.watching') }}
</a-typography-text>
</div>
</div>
</a-card>
</template>
<script lang="ts" setup>
import { useLoginStore } from '@/store';
const userInfo = useLoginStore();
</script>
<style scoped lang="less">
.studio {
&-preview {
display: block;
max-width: 600px;
margin: 0 auto;
width: 100%;
}
&-bar {
display: flex;
justify-content: space-between;
margin-top: 16px;
}
}
</style>

View File

@@ -0,0 +1,87 @@
<template>
<div class="container">
<Breadcrumb :items="['menu.dashboard', 'menu.dashboard.monitor']" />
<div class="layout">
<div class="layout-left-side">
<ChatPanel />
</div>
<div class="layout-content">
<a-space :size="16" direction="vertical" fill>
<Studio />
<DataStatistic />
</a-space>
</div>
<div class="layout-right-side">
<a-space :size="16" direction="vertical" fill>
<StudioStatus />
<QuickOperation />
<StudioInformation />
</a-space>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import ChatPanel from './components/chat-panel.vue';
import Studio from './components/studio.vue';
import DataStatistic from './components/data-statistic.vue';
import StudioStatus from './components/studio-status.vue';
import QuickOperation from './components/quick-operation.vue';
import StudioInformation from './components/studio-information.vue';
</script>
<script lang="ts">
export default {
name: 'Monitor',
};
</script>
<style scoped lang="less">
.container {
padding: 0 20px 20px 20px;
}
.layout {
display: flex;
&-left-side {
flex-basis: 300px;
}
&-content {
flex: 1;
padding: 0 16px;
}
&-right-side {
flex-basis: 280px;
}
}
</style>
<style lang="less" scoped>
// responsive
@media (max-width: @screen-lg) {
.layout {
flex-wrap: wrap;
&-left-side {
flex: 1;
flex-basis: 100%;
margin-bottom: 16px;
}
&-content {
flex: none;
flex-basis: 100%;
padding: 0;
order: -1;
margin-bottom: 16px;
}
&-right-side {
flex-basis: 100%;
}
}
}
</style>

View File

@@ -0,0 +1,48 @@
export default {
'menu.dashboard.monitor': 'Real-time Monitor',
'monitor.title.chatPanel': 'Chat Window',
'monitor.title.quickOperation': 'Quick Operation',
'monitor.title.studioInfo': 'Studio Information',
'monitor.title.studioPreview': 'Studio Preview',
'monitor.chat.options.all': 'All',
'monitor.chat.placeholder.searchCategory': 'Search Category',
'monitor.chat.update': 'Update',
'monitor.list.title.order': 'Order',
'monitor.list.title.cover': 'Cover',
'monitor.list.title.name': 'Name',
'monitor.list.title.duration': 'Duration',
'monitor.list.title.id': 'ID',
'monitor.list.tip.rotations': 'Rotations ',
'monitor.list.tip.rest': ', The program list is not visible to viewers',
'monitor.list.tag.auditFailed': 'Audit Failed',
'monitor.tab.title.liveMethod': 'Live Method',
'monitor.tab.title.onlinePopulation': 'Online Population',
'monitor.liveMethod.normal': 'Normal Live',
'monitor.liveMethod.flowControl': 'Flow Control Live',
'monitor.liveMethod.video': 'Video Live',
'monitor.liveMethod.web': 'Web Live',
'monitor.editCarousel': 'Edit',
'monitor.startCarousel': 'Start',
'monitor.quickOperation.changeClarity': 'Change the Clarity',
'monitor.quickOperation.switchStream': 'Switch Stream',
'monitor.quickOperation.removeClarity': 'Remove the Clarity',
'monitor.quickOperation.pushFlowGasket': 'Push Flow Gasket',
'monitor.studioInfo.label.studioTitle': 'Studio Title',
'monitor.studioInfo.label.onlineNotification': 'Online Notification',
'monitor.studioInfo.label.studioCategory': 'Studio Category',
'monitor.studioInfo.placeholder.studioTitle': "'s Studio",
'monitor.studioInfo.btn.fresh': 'Fresh',
'monitor.studioStatus.title.studioStatus': 'Studio Status',
'monitor.studioStatus.title.pictureInfo': 'Picture Information',
'monitor.studioStatus.smooth': 'Smooth',
'monitor.studioStatus.frameRate': 'Frame',
'monitor.studioStatus.bitRate': 'Bit',
'monitor.studioStatus.mainstream': 'Main',
'monitor.studioStatus.hotStandby': 'Hot',
'monitor.studioStatus.coldStandby': 'Cold',
'monitor.studioStatus.line': 'Line',
'monitor.studioStatus.play': 'Format',
'monitor.studioStatus.pictureQuality': 'Quality',
'monitor.studioPreview.studio': 'Studio',
'monitor.studioPreview.watching': 'watching',
};

View File

@@ -0,0 +1,48 @@
export default {
'menu.dashboard.monitor': '实时监控',
'monitor.title.chatPanel': '聊天窗口',
'monitor.title.quickOperation': '快捷操作',
'monitor.title.studioInfo': '直播信息',
'monitor.title.studioPreview': '直播预览',
'monitor.chat.options.all': '全部',
'monitor.chat.placeholder.searchCategory': '搜索类目',
'monitor.chat.update': '更新',
'monitor.list.title.order': '序号',
'monitor.list.title.cover': '封面',
'monitor.list.title.name': '名称',
'monitor.list.title.duration': '视频时长',
'monitor.list.title.id': '视频Id',
'monitor.list.tip.rotations': '轮播次数',
'monitor.list.tip.rest': ',节目单观众不可见',
'monitor.list.tag.auditFailed': '审核未通过',
'monitor.tab.title.liveMethod': '直播方式',
'monitor.tab.title.onlinePopulation': '在线人数',
'monitor.liveMethod.normal': '普通直播',
'monitor.liveMethod.flowControl': '控流直播',
'monitor.liveMethod.video': '视频直播',
'monitor.liveMethod.web': '网页开播',
'monitor.editCarousel': '编辑轮播',
'monitor.startCarousel': '开始轮播',
'monitor.quickOperation.changeClarity': '切换清晰度',
'monitor.quickOperation.switchStream': '主备流切换',
'monitor.quickOperation.removeClarity': '摘除清晰度',
'monitor.quickOperation.pushFlowGasket': '推流垫片',
'monitor.studioInfo.label.studioTitle': '直播标题',
'monitor.studioInfo.label.onlineNotification': '上线通知',
'monitor.studioInfo.label.studioCategory': '直播类目',
'monitor.studioInfo.placeholder.studioTitle': '的直播间',
'monitor.studioInfo.btn.fresh': '更新',
'monitor.studioStatus.title.studioStatus': '直播状态',
'monitor.studioStatus.title.pictureInfo': '画面信息',
'monitor.studioStatus.smooth': '流畅',
'monitor.studioStatus.frameRate': '帧率',
'monitor.studioStatus.bitRate': '码率',
'monitor.studioStatus.mainstream': '主流',
'monitor.studioStatus.hotStandby': '热备',
'monitor.studioStatus.coldStandby': '冷备',
'monitor.studioStatus.line': '线路',
'monitor.studioStatus.play': '播放格式',
'monitor.studioStatus.pictureQuality': '画质',
'monitor.studioPreview.studio': '直播间',
'monitor.studioPreview.watching': '在看',
};

View File

@@ -0,0 +1,26 @@
import Mock from 'mockjs';
import setupMock, {
successResponseWrap,
// failResponseWrap,
} from '@/utils/setup-mock';
setupMock({
setup() {
Mock.mock(new RegExp('/api/chat/list'), () => {
// return failResponseWrap(null, '重新登陆', 50008);
const data = Mock.mock({
'data|4-6': [
{
'id|+1': 1,
'username': '用户7352772',
'content': '马上就开始了,好激动!',
'time': '13:09:12',
'isCollect|2': true,
},
],
});
return successResponseWrap(data.data);
});
},
});

View File

@@ -0,0 +1,71 @@
<template>
<a-card
class="general-card"
:title="$t('workplace.announcement')"
:header-style="{ paddingBottom: '0' }"
:body-style="{ padding: '15px 20px 13px 20px' }"
>
<template #extra>
<a-link>{{ $t('workplace.viewMore') }}</a-link>
</template>
<div>
<div v-for="(item, idx) in list" :key="idx" class="item">
<a-tag :color="item.type" size="small">{{ item.label }}</a-tag>
<span class="item-content">
{{ item.content }}
</span>
</div>
</div>
</a-card>
</template>
<script lang="ts" setup>
const list = [
{
type: 'orangered',
label: '活动',
content: '内容最新优惠活动',
},
{
type: 'cyan',
label: '消息',
content: '新增内容尚未通过审核,详情请点击查看。',
},
{
type: 'blue',
label: '通知',
content: '当前产品试用期即将结束,如需续费请点击查看。',
},
{
type: 'blue',
label: '通知',
content: '1月新系统升级计划通知',
},
{
type: 'cyan',
label: '消息',
content: '新增内容已经通过审核,详情请点击查看。',
},
];
</script>
<style scoped lang="less">
.item {
display: flex;
align-items: center;
width: 100%;
height: 24px;
margin-bottom: 4px;
.item-content {
flex: 1;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
margin-left: 4px;
color: var(--color-text-2);
text-decoration: none;
font-size: 13px;
cursor: pointer;
}
}
</style>

View File

@@ -0,0 +1,35 @@
<template>
<a-col class="banner">
<a-col :span="8">
<a-typography-title :heading="5" style="margin-top: 0">
{{ $t('workplace.welcome') }} {{ userInfo.nickname }}
</a-typography-title>
</a-col>
<a-divider class="panel-border" />
</a-col>
</template>
<script lang="ts" setup>
import { computed } from 'vue';
import { useLoginStore } from '@/store';
const loginStore = useLoginStore();
const userInfo = computed(() => {
return {
nickname: loginStore.nickname,
};
});
</script>
<style scoped lang="less">
.banner {
width: 100%;
padding: 20px 20px 0 20px;
background-color: var(--color-bg-2);
border-radius: 4px 4px 0 0;
}
:deep(.arco-icon-home) {
margin-right: 6px;
}
</style>

View File

@@ -0,0 +1,24 @@
<template>
<a-carousel
indicator-type="slider"
show-arrow="hover"
auto-play
style="width: 100%; height: 170px; border-radius: 4px; overflow: hidden"
>
<a-carousel-item v-for="(src, idx) in imageSrc" :key="idx">
<div>
<img :src="src" style="width: 100%" />
</div>
</a-carousel-item>
</a-carousel>
</template>
<script lang="ts" setup>
const imageSrc = [
'//p3-armor.byteimg.com/tos-cn-i-49unhts6dw/5cc3cd1d994b7ef9db6a1f619a22addd.jpg~tplv-49unhts6dw-image.image',
'//p3-armor.byteimg.com/tos-cn-i-49unhts6dw/f256cbcc287139e191fecea9d255a1f0.jpg~tplv-49unhts6dw-image.image',
'//p3-armor.byteimg.com/tos-cn-i-49unhts6dw/b557ff0cd44146a2e471b477af2f30d0.jpg~tplv-49unhts6dw-image.image',
'//p3-armor.byteimg.com/tos-cn-i-49unhts6dw/665106f4bbd2a2df96eaf7aec52f7bc3.jpg~tplv-49unhts6dw-image.image',
'//p3-armor.byteimg.com/tos-cn-i-49unhts6dw/ea095a2c9c72b5d8f2f2818040db736d.jpg~tplv-49unhts6dw-image.image',
];
</script>

View File

@@ -0,0 +1,114 @@
<template>
<a-spin :loading="loading" style="width: 100%">
<a-card
class="general-card"
:header-style="{ paddingBottom: '0' }"
:body-style="{
padding: '20px',
}"
>
<template #title>
{{ $t('workplace.categoriesPercent') }}
</template>
<Chart height="310px" :option="chartOption" />
</a-card>
</a-spin>
</template>
<script lang="ts" setup>
import useLoading from '@/hooks/loading';
import useChartOption from '@/hooks/chart-option';
const { loading } = useLoading();
const { chartOption } = useChartOption((isDark) => {
// echarts support https://echarts.apache.org/zh/theme-builder.html
// It's not used here
return {
legend: {
left: 'center',
data: ['纯文本', '图文类', '视频类'],
bottom: 0,
icon: 'circle',
itemWidth: 8,
textStyle: {
color: isDark ? 'rgba(255, 255, 255, 0.7)' : '#4E5969',
},
itemStyle: {
borderWidth: 0,
},
},
tooltip: {
show: true,
trigger: 'item',
},
graphic: {
elements: [
{
type: 'text',
left: 'center',
top: '40%',
style: {
text: '内容量',
textAlign: 'center',
fill: isDark ? '#ffffffb3' : '#4E5969',
fontSize: 14,
},
},
{
type: 'text',
left: 'center',
top: '50%',
style: {
text: '928,531',
textAlign: 'center',
fill: isDark ? '#ffffffb3' : '#1D2129',
fontSize: 16,
fontWeight: 500,
},
},
],
},
series: [
{
type: 'pie',
radius: ['50%', '70%'],
center: ['50%', '50%'],
label: {
formatter: '{d}%',
fontSize: 14,
color: isDark ? 'rgba(255, 255, 255, 0.7)' : '#4E5969',
},
itemStyle: {
borderColor: isDark ? '#232324' : '#fff',
borderWidth: 1,
},
data: [
{
value: [148564],
name: '纯文本',
itemStyle: {
color: isDark ? '#3D72F6' : '#249EFF',
},
},
{
value: [334271],
name: '图文类',
itemStyle: {
color: isDark ? '#A079DC' : '#313CA9',
},
},
{
value: [445694],
name: '视频类',
itemStyle: {
color: isDark ? '#6CAAF5' : '#21CCFF',
},
},
],
},
],
};
});
</script>
<style scoped lang="less"></style>

View File

@@ -0,0 +1,200 @@
<template>
<a-spin :loading="loading" style="width: 100%">
<a-card
class="general-card"
:header-style="{ paddingBottom: 0 }"
:body-style="{
paddingTop: '20px',
}"
:title="$t('workplace.contentData')"
>
<template #extra>
<a-link>{{ $t('workplace.viewMore') }}</a-link>
</template>
<Chart height="289px" :option="chartOption" />
</a-card>
</a-spin>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import { graphic } from 'echarts';
import useLoading from '@/hooks/loading';
import { queryContentData, ContentDataRecord } from '@/api/dashboard';
import useChartOption from '@/hooks/chart-option';
import { ToolTipFormatterParams } from '@/types/echarts';
import { AnyObject } from '@/types/global';
function graphicFactory(side: AnyObject) {
return {
type: 'text',
bottom: '8',
...side,
style: {
text: '',
textAlign: 'center',
fill: '#4E5969',
fontSize: 12,
},
};
}
const { loading, setLoading } = useLoading(true);
const xAxis = ref<string[]>([]);
const chartsData = ref<number[]>([]);
const graphicElements = ref([
graphicFactory({ left: '2.6%' }),
graphicFactory({ right: 0 }),
]);
const { chartOption } = useChartOption(() => {
return {
grid: {
left: '2.6%',
right: '0',
top: '10',
bottom: '30',
},
xAxis: {
type: 'category',
offset: 2,
data: xAxis.value,
boundaryGap: false,
axisLabel: {
color: '#4E5969',
formatter(value: number, idx: number) {
if (idx === 0) return '';
if (idx === xAxis.value.length - 1) return '';
return `${value}`;
},
},
axisLine: {
show: false,
},
axisTick: {
show: false,
},
splitLine: {
show: true,
interval: (idx: number) => {
if (idx === 0) return false;
if (idx === xAxis.value.length - 1) return false;
return true;
},
lineStyle: {
color: '#E5E8EF',
},
},
axisPointer: {
show: true,
lineStyle: {
color: '#23ADFF',
width: 2,
},
},
},
yAxis: {
type: 'value',
axisLine: {
show: false,
},
axisLabel: {
formatter(value: any, idx: number) {
if (idx === 0) return value;
return `${value}k`;
},
},
splitLine: {
show: true,
lineStyle: {
type: 'dashed',
color: '#E5E8EF',
},
},
},
tooltip: {
trigger: 'axis',
formatter(params) {
const [firstElement] = params as ToolTipFormatterParams[];
return `<div>
<p class="tooltip-title">${firstElement.axisValueLabel}</p>
<div class="content-panel"><span>总内容量</span><span class="tooltip-value">${(
Number(firstElement.value) * 10000
).toLocaleString()}</span></div>
</div>`;
},
className: 'echarts-tooltip-diy',
},
graphic: {
elements: graphicElements.value,
},
series: [
{
data: chartsData.value,
type: 'line',
smooth: true,
// symbol: 'circle',
symbolSize: 12,
emphasis: {
focus: 'series',
itemStyle: {
borderWidth: 2,
},
},
lineStyle: {
width: 3,
color: new graphic.LinearGradient(0, 0, 1, 0, [
{
offset: 0,
color: 'rgba(30, 231, 255, 1)',
},
{
offset: 0.5,
color: 'rgba(36, 154, 255, 1)',
},
{
offset: 1,
color: 'rgba(111, 66, 251, 1)',
},
]),
},
showSymbol: false,
areaStyle: {
opacity: 0.8,
color: new graphic.LinearGradient(0, 0, 0, 1, [
{
offset: 0,
color: 'rgba(17, 126, 255, 0.16)',
},
{
offset: 1,
color: 'rgba(17, 128, 255, 0)',
},
]),
},
},
],
};
});
const fetchData = async () => {
setLoading(true);
try {
const { data: chartData } = await queryContentData();
chartData.forEach((el: ContentDataRecord, idx: number) => {
xAxis.value.push(el.x);
chartsData.value.push(el.y);
if (idx === 0) {
graphicElements.value[0].style.text = el.x;
}
if (idx === chartData.length - 1) {
graphicElements.value[1].style.text = el.x;
}
});
} catch (err) {
// you can report use errorHandler or other
} finally {
setLoading(false);
}
};
fetchData();
</script>
<style scoped lang="less"></style>

View File

@@ -0,0 +1,131 @@
<template>
<a-grid :cols="24" :row-gap="16" class="panel">
<a-grid-item
class="panel-col"
:span="{ xs: 12, sm: 12, md: 12, lg: 12, xl: 12, xxl: 6 }"
>
<a-space>
<a-avatar :size="54" class="col-avatar">
<img
alt="avatar"
src="//p3-armor.byteimg.com/tos-cn-i-49unhts6dw/288b89194e657603ff40db39e8072640.svg~tplv-49unhts6dw-image.image"
/>
</a-avatar>
<a-statistic
:title="$t('workplace.onlineContent')"
:value="373.5"
:precision="1"
:value-from="0"
animation
show-group-separator
>
<template #suffix>
W+ <span class="unit">{{ $t('workplace.pecs') }}</span>
</template>
</a-statistic>
</a-space>
</a-grid-item>
<a-grid-item
class="panel-col"
:span="{ xs: 12, sm: 12, md: 12, lg: 12, xl: 12, xxl: 6 }"
>
<a-space>
<a-avatar :size="54" class="col-avatar">
<img
alt="avatar"
src="//p3-armor.byteimg.com/tos-cn-i-49unhts6dw/fdc66b07224cdf18843c6076c2587eb5.svg~tplv-49unhts6dw-image.image"
/>
</a-avatar>
<a-statistic
:title="$t('workplace.putIn')"
:value="368"
:value-from="0"
animation
show-group-separator
>
<template #suffix>
<span class="unit">{{ $t('workplace.pecs') }}</span>
</template>
</a-statistic>
</a-space>
</a-grid-item>
<a-grid-item
class="panel-col"
:span="{ xs: 12, sm: 12, md: 12, lg: 12, xl: 12, xxl: 6 }"
>
<a-space>
<a-avatar :size="54" class="col-avatar">
<img
alt="avatar"
src="//p3-armor.byteimg.com/tos-cn-i-49unhts6dw/77d74c9a245adeae1ec7fb5d4539738d.svg~tplv-49unhts6dw-image.image"
/>
</a-avatar>
<a-statistic
:title="$t('workplace.newDay')"
:value="8874"
:value-from="0"
animation
show-group-separator
>
<template #suffix>
<span class="unit">{{ $t('workplace.pecs') }}</span>
</template>
</a-statistic>
</a-space>
</a-grid-item>
<a-grid-item
class="panel-col"
:span="{ xs: 12, sm: 12, md: 12, lg: 12, xl: 12, xxl: 6 }"
style="border-right: none"
>
<a-space>
<a-avatar :size="54" class="col-avatar">
<img
alt="avatar"
src="//p3-armor.byteimg.com/tos-cn-i-49unhts6dw/c8b36e26d2b9bb5dbf9b74dd6d7345af.svg~tplv-49unhts6dw-image.image"
/>
</a-avatar>
<a-statistic
:title="$t('workplace.newFromYesterday')"
:value="2.8"
:precision="1"
:value-from="0"
animation
>
<template #suffix> % <icon-caret-up class="up-icon" /> </template>
</a-statistic>
</a-space>
</a-grid-item>
<a-grid-item :span="24">
<a-divider class="panel-border" />
</a-grid-item>
</a-grid>
</template>
<script lang="ts" setup></script>
<style lang="less" scoped>
.arco-grid.panel {
margin-bottom: 0;
padding: 16px 20px 0 20px;
}
.panel-col {
padding-left: 43px;
border-right: 1px solid rgb(var(--gray-2));
}
.col-avatar {
margin-right: 12px;
background-color: var(--color-fill-2);
}
.up-icon {
color: rgb(var(--red-6));
}
.unit {
margin-left: 8px;
color: rgb(var(--gray-8));
font-size: 12px;
}
:deep(.panel-border) {
margin: 4px 0 0 0;
}
</style>

View File

@@ -0,0 +1,42 @@
<template>
<a-card
class="general-card"
:title="$t('workplace.docs')"
:header-style="{ paddingBottom: 0 }"
:body-style="{ paddingTop: 0 }"
style="height: 166px"
>
<template #extra>
<a-link>{{ $t('workplace.viewMore') }}</a-link>
</template>
<a-row>
<a-col :span="12">
<a-link>
{{ $t('workplace.docs.productOverview') }}
</a-link>
</a-col>
<a-col :span="12">
<a-link>
{{ $t('workplace.docs.userGuide') }}
</a-link>
</a-col>
<a-col :span="12">
<a-link>
{{ $t('workplace.docs.workflow') }}
</a-link>
</a-col>
<a-col :span="12">
<a-link>
{{ $t('workplace.docs.interfaceDocs') }}
</a-link>
</a-col>
</a-row>
</a-card>
</template>
<style lang="less" scoped>
.arco-card-body .arco-link {
margin: 10px 0;
color: rgb(var(--gray-8));
}
</style>

View File

@@ -0,0 +1,118 @@
<template>
<a-spin :loading="loading" style="width: 100%">
<a-card
class="general-card"
:header-style="{ paddingBottom: '0' }"
:body-style="{ padding: '17px 20px 21px 20px' }"
>
<template #title>
{{ $t('workplace.popularContent') }}
</template>
<template #extra>
<a-link>{{ $t('workplace.viewMore') }}</a-link>
</template>
<a-space direction="vertical" :size="10" fill>
<a-radio-group
v-model:model-value="type"
type="button"
@change="typeChange as any"
>
<a-radio value="text">
{{ $t('workplace.popularContent.text') }}
</a-radio>
<a-radio value="image">
{{ $t('workplace.popularContent.image') }}
</a-radio>
<a-radio value="video">
{{ $t('workplace.popularContent.video') }}
</a-radio>
</a-radio-group>
<a-table
:data="renderList"
:pagination="false"
:bordered="false"
:scroll="{ x: '100%', y: '264px' }"
>
<template #columns>
<a-table-column title="排名" data-index="key"></a-table-column>
<a-table-column title="内容标题" data-index="title">
<template #cell="{ record }">
<a-typography-paragraph
:ellipsis="{
rows: 1,
}"
>
{{ record.title }}
</a-typography-paragraph>
</template>
</a-table-column>
<a-table-column title="点击量" data-index="clickNumber">
</a-table-column>
<a-table-column
title="日涨幅"
data-index="increases"
:sortable="{
sortDirections: ['ascend', 'descend'],
}"
>
<template #cell="{ record }">
<div class="increases-cell">
<span>{{ record.increases }}%</span>
<icon-caret-up
v-if="record.increases !== 0"
style="color: #f53f3f; font-size: 8px"
/>
</div>
</template>
</a-table-column>
</template>
</a-table>
</a-space>
</a-card>
</a-spin>
</template>
<script lang="ts" setup>
import { ref } from 'vue';
import useLoading from '@/hooks/loading';
import { queryPopularList } from '@/api/dashboard';
import type { TableData } from '@arco-design/web-vue/es/table/interface';
const type = ref('text');
const { loading, setLoading } = useLoading();
const renderList = ref<TableData[]>();
const fetchData = async (contentType: string) => {
try {
setLoading(true);
const { data } = await queryPopularList({ type: contentType });
renderList.value = data;
} catch (err) {
// you can report use errorHandler or other
} finally {
setLoading(false);
}
};
const typeChange = (contentType: string) => {
fetchData(contentType);
};
fetchData('text');
</script>
<style scoped lang="less">
.general-card {
min-height: 395px;
}
:deep(.arco-table-tr) {
height: 44px;
.arco-typography {
margin-bottom: 0;
}
}
.increases-cell {
display: flex;
align-items: center;
span {
margin-right: 4px;
}
}
</style>

View File

@@ -0,0 +1,35 @@
<template>
<a-card
class="general-card"
:title="$t('workplace.quick.operation')"
:header-style="{ paddingBottom: '0' }"
:body-style="{ padding: '24px 20px 0 20px' }"
>
<template #extra>
<a-link>{{ $t('workplace.quickOperation.setup') }}</a-link>
</template>
<a-row :gutter="8">
<a-col v-for="link in links" :key="link.text" :span="8" class="wrapper">
<div class="icon">
<component :is="link.icon" />
</div>
<a-typography-paragraph class="text">
{{ $t(link.text) }}
</a-typography-paragraph>
</a-col>
</a-row>
<a-divider class="split-line" style="margin: 0" />
</a-card>
</template>
<script lang="ts" setup>
const links = [
{ text: 'workplace.contentManagement', icon: 'icon-file' },
{ text: 'workplace.contentStatistical', icon: 'icon-storage' },
{ text: 'workplace.advanced', icon: 'icon-settings' },
{ text: 'workplace.onlinePromotion', icon: 'icon-mobile' },
{ text: 'workplace.contentPutIn', icon: 'icon-fire' },
];
</script>
<style scoped lang="less"></style>

View File

@@ -0,0 +1,44 @@
<template>
<a-card
class="general-card"
:title="$t('workplace.recently.visited')"
:header-style="{ paddingBottom: '0' }"
:body-style="{ paddingTop: '26px' }"
>
<div style="margin-bottom: -1rem">
<a-row :gutter="8">
<a-col v-for="link in links" :key="link.text" :span="8" class="wrapper">
<div class="icon">
<component :is="link.icon" />
</div>
<a-typography-paragraph class="text">
{{ $t(link.text) }}
</a-typography-paragraph>
</a-col>
</a-row>
</div>
</a-card>
</template>
<script lang="ts" setup>
const links = [
{
text: 'workplace.contentManagement',
icon: 'icon-storage',
},
{
text: 'workplace.contentStatistical',
icon: 'icon-file',
},
{
text: 'workplace.advanced',
icon: 'icon-settings',
},
];
</script>
<style lang="less" scoped>
:deep(.arco-card-header-title) {
line-height: inherit;
}
</style>

View File

@@ -0,0 +1,148 @@
<template>
<div class="container">
<div class="left-side">
<div class="panel">
<Banner />
<DataPanel />
<ContentChart />
</div>
<a-grid :cols="24" :col-gap="16" :row-gap="16" style="margin-top: 16px">
<a-grid-item
:span="{ xs: 24, sm: 24, md: 24, lg: 12, xl: 12, xxl: 12 }"
>
<PopularContent />
</a-grid-item>
<a-grid-item
:span="{ xs: 24, sm: 24, md: 24, lg: 12, xl: 12, xxl: 12 }"
>
<CategoriesPercent />
</a-grid-item>
</a-grid>
</div>
<div class="right-side">
<a-grid :cols="24" :row-gap="16">
<a-grid-item :span="24">
<div class="panel moduler-wrap">
<QuickOperation />
<RecentlyVisited />
</div>
</a-grid-item>
<a-grid-item class="panel" :span="24">
<Carousel />
</a-grid-item>
<a-grid-item class="panel" :span="24">
<Announcement />
</a-grid-item>
<a-grid-item class="panel" :span="24">
<Docs />
</a-grid-item>
</a-grid>
</div>
</div>
</template>
<script lang="ts" setup>
import Banner from './components/banner.vue';
import DataPanel from './components/data-panel.vue';
import ContentChart from './components/content-chart.vue';
import PopularContent from './components/popular-content.vue';
import CategoriesPercent from './components/categories-percent.vue';
import RecentlyVisited from './components/recently-visited.vue';
import QuickOperation from './components/quick-operation.vue';
import Announcement from './components/announcement.vue';
import Carousel from './components/carousel.vue';
import Docs from './components/docs.vue';
</script>
<script lang="ts">
export default {
name: 'Dashboard', // If you want the include property of keep-alive to take effect, you must name the component
};
</script>
<style lang="less" scoped>
.container {
background-color: var(--color-fill-2);
padding: 16px 20px;
padding-bottom: 0;
display: flex;
}
.left-side {
flex: 1;
overflow: auto;
}
.right-side {
width: 280px;
margin-left: 16px;
}
.panel {
background-color: var(--color-bg-2);
border-radius: 4px;
overflow: auto;
}
:deep(.panel-border) {
margin-bottom: 0;
border-bottom: 1px solid rgb(var(--gray-2));
}
.moduler-wrap {
border-radius: 4px;
background-color: var(--color-bg-2);
:deep(.text) {
font-size: 12px;
text-align: center;
color: rgb(var(--gray-8));
}
:deep(.wrapper) {
margin-bottom: 8px;
text-align: center;
cursor: pointer;
&:last-child {
.text {
margin-bottom: 0;
}
}
&:hover {
.icon {
color: rgb(var(--arcoblue-6));
background-color: #e8f3ff;
}
.text {
color: rgb(var(--arcoblue-6));
}
}
}
:deep(.icon) {
display: inline-block;
width: 32px;
height: 32px;
margin-bottom: 4px;
color: rgb(var(--dark-gray-1));
line-height: 32px;
font-size: 16px;
text-align: center;
background-color: rgb(var(--gray-1));
border-radius: 4px;
}
}
</style>
<style lang="less" scoped>
// responsive
.mobile {
.container {
display: block;
}
.right-side {
// display: none;
width: 100%;
margin-left: 0;
margin-top: 16px;
}
}
</style>

View File

@@ -0,0 +1,38 @@
export default {
'menu.dashboard.workplace': 'Workplace',
'workplace.welcome': 'Welcome!',
'workplace.balance': 'Balance (CNY)',
'workplace.order.pending': 'Pending',
'workplace.order.pendingRenewal': 'Renewal Order',
'workplace.onlineContent': 'Online Content',
'workplace.putIn': 'Put In',
'workplace.newDay': 'Daily Additional Comments',
'workplace.newFromYesterday': 'New From Yesterday',
'workplace.minute': 'Min',
'workplace.docs': 'Documents',
'workplace.docs.productOverview': 'Product Overview',
'workplace.docs.userGuide': 'User Guide',
'workplace.docs.workflow': 'Workflow',
'workplace.docs.interfaceDocs': 'Interface Docs',
//
'workplace.contentManagement': 'Content Management',
'workplace.contentStatistical': 'Content Statistical',
'workplace.advanced': 'Advanced',
'workplace.onlinePromotion': 'Online Promotion',
'workplace.contentPutIn': 'Put In',
'workplace.announcement': 'Announcement',
'workplace.recently.visited': 'Recently Visited',
'workplace.record.nodata': 'No data',
'workplace.quick.operation': 'Quick Operation',
'workplace.quickOperation.setup': 'Setup',
'workplace.allProject': 'All',
'workplace.loadMore': 'More',
'workplace.viewMore': 'More',
'workplace.contentData': 'Content Data',
'workplace.popularContent': 'Popular Content',
'workplace.popularContent.text': 'text',
'workplace.popularContent.image': 'image',
'workplace.popularContent.video': 'video',
'workplace.categoriesPercent': 'Categories Percent',
'workplace.pecs': 'pecs',
};

View File

@@ -0,0 +1,37 @@
export default {
'menu.dashboard.workplace': '工作台',
'workplace.welcome': '欢迎回来!',
'workplace.balance': '余额(元)',
'workplace.order.pending': '待支付',
'workplace.order.pendingRenewal': '待续费订单',
'workplace.onlineContent': '线上总内容',
'workplace.putIn': '投放中内容',
'workplace.newDay': '日新增评论',
'workplace.newFromYesterday': '较昨日新增',
'workplace.minute': '分钟',
'workplace.docs': '帮助文档',
'workplace.docs.productOverview': '产品概要',
'workplace.docs.userGuide': '使用指南',
'workplace.docs.workflow': '接入流程',
'workplace.docs.interfaceDocs': '接口文档',
'workplace.contentManagement': '内容管理',
'workplace.contentStatistical': '内容分析',
'workplace.advanced': '高级管理',
'workplace.onlinePromotion': '线上推广',
'workplace.contentPutIn': '内容投放',
'workplace.announcement': '公告',
'workplace.recently.visited': '最近访问',
'workplace.record.nodata': '暂无数据',
'workplace.quick.operation': '快捷操作',
'workplace.quickOperation.setup': '管理',
'workplace.allProject': '所有项目',
'workplace.loadMore': '加载更多',
'workplace.viewMore': '查看更多',
'workplace.contentData': '内容数据',
'workplace.popularContent': '线上热门内容',
'workplace.popularContent.text': '文本',
'workplace.popularContent.image': '图片',
'workplace.popularContent.video': '视频',
'workplace.categoriesPercent': '内容类型占比',
'workplace.pecs': '个',
};

View File

@@ -0,0 +1,129 @@
import Mock from 'mockjs';
import qs from 'query-string';
import dayjs from 'dayjs';
import { GetParams } from '@/types/global';
import setupMock, { successResponseWrap } from '@/utils/setup-mock';
const textList = [
{
key: 1,
clickNumber: '346.3w+',
title: '经济日报:财政政策要精准提升…',
increases: 35,
},
{
key: 2,
clickNumber: '324.2w+',
title: '双12遇冷消费者厌倦了电商平…',
increases: 22,
},
{
key: 3,
clickNumber: '318.9w+',
title: '致敬坚守战“疫”一线的社区工作…',
increases: 9,
},
{
key: 4,
clickNumber: '257.9w+',
title: '普高还是职高?家长们陷入选择…',
increases: 17,
},
{
key: 5,
clickNumber: '124.2w+',
title: '人民快评:没想到“浓眉大眼”的…',
increases: 37,
},
];
const imageList = [
{
key: 1,
clickNumber: '15.3w+',
title: '杨涛接替陆慷出任外交部美大司…',
increases: 15,
},
{
key: 2,
clickNumber: '12.2w+',
title: '图集:龙卷风袭击美国多州房屋…',
increases: 26,
},
{
key: 3,
clickNumber: '18.9w+',
title: '52岁大姐贴钱照顾自闭症儿童八…',
increases: 9,
},
{
key: 4,
clickNumber: '7.9w+',
title: '杭州一家三口公园宿营取暖中毒',
increases: 0,
},
{
key: 5,
clickNumber: '5.2w+',
title: '派出所副所长威胁市民?警方调…',
increases: 4,
},
];
const videoList = [
{
key: 1,
clickNumber: '367.6w+',
title: '这是今日10点的南京',
increases: 5,
},
{
key: 2,
clickNumber: '352.2w+',
title: '立陶宛不断挑衅致经济受损民众…',
increases: 17,
},
{
key: 3,
clickNumber: '348.9w+',
title: '韩国艺人刘在石确诊新冠',
increases: 30,
},
{
key: 4,
clickNumber: '346.3w+',
title: '关于北京冬奥会,文在寅表态',
increases: 12,
},
{
key: 5,
clickNumber: '271.2w+',
title: '95后现役军人荣立一等功',
increases: 2,
},
];
setupMock({
setup() {
Mock.mock(new RegExp('/api/content-data'), () => {
const presetData = [58, 81, 53, 90, 64, 88, 49, 79];
const getLineData = () => {
const count = 8;
return new Array(count).fill(0).map((el, idx) => ({
x: dayjs()
.day(idx - 2)
.format('YYYY-MM-DD'),
y: presetData[idx],
}));
};
return successResponseWrap([...getLineData()]);
});
Mock.mock(new RegExp('/api/popular/list'), (params: GetParams) => {
const { type = 'text' } = qs.parseUrl(params.url).query;
if (type === 'image') {
return successResponseWrap([...videoList]);
}
if (type === 'video') {
return successResponseWrap([...imageList]);
}
return successResponseWrap([...textList]);
});
},
});