优化:优化菜单配置

1. 调整菜单排序
2. 优化部分菜单图标
3. 新增菜单栏手风琴配置,默认生效
This commit is contained in:
2023-02-14 23:37:56 +08:00
parent 148a98371f
commit 302f0ea573
101 changed files with 126 additions and 107 deletions

View File

@@ -1,94 +0,0 @@
<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

@@ -1,76 +0,0 @@
<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

@@ -1,79 +0,0 @@
<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

@@ -1,133 +0,0 @@
<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

@@ -1,56 +0,0 @@
<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

@@ -1,32 +0,0 @@
<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

@@ -1,34 +0,0 @@
<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

@@ -1,84 +0,0 @@
<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

@@ -1,52 +0,0 @@
<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="getAvatar(userInfo)" />
</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';
import getAvatar from '@/utils/avatar';
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

@@ -1,87 +0,0 @@
<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

@@ -1,48 +0,0 @@
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

@@ -1,48 +0,0 @@
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

@@ -1,26 +0,0 @@
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);
});
},
});