mirror of
https://github.com/silenty4ng/k5web
synced 2025-04-01 21:25:02 +00:00
Clean up unused code
This commit is contained in:
parent
23d300b66b
commit
676b2918bb
103 changed files with 0 additions and 7823 deletions
src
locale
main.tsmock
views
dashboard
monitor
components
chat-item.vuechat-list.vuechat-panel.vuedata-statistic-list.vuedata-statistic.vuequick-operation.vuestudio-information.vuestudio-status.vuestudio.vue
index.vuelocale
mock.tsworkplace
exception
403
404
500
form
group
step
login
profile/basic
result
user
visualization
data-analysis
components
chain-item.vuecontent-period-analysis.vuecontent-publish-ratio.vuepopular-author.vuepublic-opinion.vue
index.vuelocale
mock.tsmulti-dimension-data-analysis
|
@ -1,31 +1,10 @@
|
|||
import localeMessageBox from '@/components/message-box/locale/en-US';
|
||||
import localeLogin from '@/views/login/locale/en-US';
|
||||
|
||||
import localeWorkplace from '@/views/dashboard/workplace/locale/en-US';
|
||||
|
||||
import localeMonitor from '@/views/dashboard/monitor/locale/en-US';
|
||||
|
||||
import localeSearchTable from '@/views/list/search-table/locale/en-US';
|
||||
import localeCardList from '@/views/list/card/locale/en-US';
|
||||
|
||||
import localeStepForm from '@/views/form/step/locale/en-US';
|
||||
import localeGroupForm from '@/views/form/group/locale/en-US';
|
||||
|
||||
import localeBasicProfile from '@/views/profile/basic/locale/en-US';
|
||||
|
||||
import localeDataAnalysis from '@/views/visualization/data-analysis/locale/en-US';
|
||||
import localeMultiDAnalysis from '@/views/visualization/multi-dimension-data-analysis/locale/en-US';
|
||||
|
||||
import localeSuccess from '@/views/result/success/locale/en-US';
|
||||
import localeError from '@/views/result/error/locale/en-US';
|
||||
|
||||
import locale403 from '@/views/exception/403/locale/en-US';
|
||||
import locale404 from '@/views/exception/404/locale/en-US';
|
||||
import locale500 from '@/views/exception/500/locale/en-US';
|
||||
|
||||
import localeUserInfo from '@/views/user/info/locale/en-US';
|
||||
import localeUserSetting from '@/views/user/setting/locale/en-US';
|
||||
|
||||
import localeSettings from './en-US/settings';
|
||||
|
||||
export default {
|
||||
|
@ -188,22 +167,8 @@ export default {
|
|||
'menu.satellite2': 'Satcom 2.0',
|
||||
...localeSettings,
|
||||
...localeMessageBox,
|
||||
...localeLogin,
|
||||
...localeWorkplace,
|
||||
|
||||
...localeMonitor,
|
||||
...localeSearchTable,
|
||||
...localeCardList,
|
||||
...localeStepForm,
|
||||
...localeGroupForm,
|
||||
...localeBasicProfile,
|
||||
...localeDataAnalysis,
|
||||
...localeMultiDAnalysis,
|
||||
...localeSuccess,
|
||||
...localeError,
|
||||
...locale403,
|
||||
...locale404,
|
||||
...locale500,
|
||||
...localeUserInfo,
|
||||
...localeUserSetting,
|
||||
};
|
||||
|
|
|
@ -1,31 +1,10 @@
|
|||
import localeMessageBox from '@/components/message-box/locale/zh-CN';
|
||||
import localeLogin from '@/views/login/locale/zh-CN';
|
||||
|
||||
import localeWorkplace from '@/views/dashboard/workplace/locale/zh-CN';
|
||||
|
||||
import localeMonitor from '@/views/dashboard/monitor/locale/zh-CN';
|
||||
|
||||
import localeSearchTable from '@/views/list/search-table/locale/zh-CN';
|
||||
import localeCardList from '@/views/list/card/locale/zh-CN';
|
||||
|
||||
import localeStepForm from '@/views/form/step/locale/zh-CN';
|
||||
import localeGroupForm from '@/views/form/group/locale/zh-CN';
|
||||
|
||||
import localeBasicProfile from '@/views/profile/basic/locale/zh-CN';
|
||||
|
||||
import localeDataAnalysis from '@/views/visualization/data-analysis/locale/zh-CN';
|
||||
import localeMultiDAnalysis from '@/views/visualization/multi-dimension-data-analysis/locale/zh-CN';
|
||||
|
||||
import localeSuccess from '@/views/result/success/locale/zh-CN';
|
||||
import localeError from '@/views/result/error/locale/zh-CN';
|
||||
|
||||
import locale403 from '@/views/exception/403/locale/zh-CN';
|
||||
import locale404 from '@/views/exception/404/locale/zh-CN';
|
||||
import locale500 from '@/views/exception/500/locale/zh-CN';
|
||||
|
||||
import localeUserInfo from '@/views/user/info/locale/zh-CN';
|
||||
import localeUserSetting from '@/views/user/setting/locale/zh-CN';
|
||||
|
||||
import localeSettings from './zh-CN/settings';
|
||||
|
||||
export default {
|
||||
|
@ -188,22 +167,8 @@ export default {
|
|||
'menu.satellite2': '星历写入 2.0',
|
||||
...localeSettings,
|
||||
...localeMessageBox,
|
||||
...localeLogin,
|
||||
...localeWorkplace,
|
||||
|
||||
...localeMonitor,
|
||||
...localeSearchTable,
|
||||
...localeCardList,
|
||||
...localeStepForm,
|
||||
...localeGroupForm,
|
||||
...localeBasicProfile,
|
||||
...localeDataAnalysis,
|
||||
...localeMultiDAnalysis,
|
||||
...localeSuccess,
|
||||
...localeError,
|
||||
...locale403,
|
||||
...locale404,
|
||||
...locale500,
|
||||
...localeUserInfo,
|
||||
...localeUserSetting,
|
||||
};
|
||||
|
|
|
@ -5,7 +5,6 @@ import router from './router';
|
|||
import store from './store';
|
||||
import i18n from './locale';
|
||||
import directive from './directive';
|
||||
import './mock';
|
||||
import App from './App.vue';
|
||||
// Styles are imported via arco-plugin. See config/plugin/arcoStyleImport.ts in the directory for details
|
||||
// 样式通过 arco-plugin 插件导入。详见目录文件 config/plugin/arcoStyleImport.ts
|
||||
|
|
|
@ -1,25 +0,0 @@
|
|||
import Mock from 'mockjs';
|
||||
|
||||
import './user';
|
||||
import './message-box';
|
||||
|
||||
import '@/views/dashboard/workplace/mock';
|
||||
|
||||
import '@/views/dashboard/monitor/mock';
|
||||
|
||||
import '@/views/list/card/mock';
|
||||
import '@/views/list/search-table/mock';
|
||||
|
||||
import '@/views/form/step/mock';
|
||||
|
||||
import '@/views/profile/basic/mock';
|
||||
|
||||
import '@/views/visualization/data-analysis/mock';
|
||||
import '@/views/visualization/multi-dimension-data-analysis/mock';
|
||||
|
||||
import '@/views/user/info/mock';
|
||||
import '@/views/user/setting/mock';
|
||||
|
||||
Mock.setup({
|
||||
timeout: '600-1000',
|
||||
});
|
|
@ -1,85 +0,0 @@
|
|||
import Mock from 'mockjs';
|
||||
import setupMock, { successResponseWrap } from '@/utils/setup-mock';
|
||||
|
||||
const haveReadIds: number[] = [];
|
||||
const getMessageList = () => {
|
||||
return [
|
||||
{
|
||||
id: 1,
|
||||
type: 'message',
|
||||
title: '郑曦月',
|
||||
subTitle: '的私信',
|
||||
avatar:
|
||||
'//p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/8361eeb82904210b4f55fab888fe8416.png~tplv-uwbnlip3yd-webp.webp',
|
||||
content: '审批请求已发送,请查收',
|
||||
time: '今天 12:30:01',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
type: 'message',
|
||||
title: '宁波',
|
||||
subTitle: '的回复',
|
||||
avatar:
|
||||
'//p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/3ee5f13fb09879ecb5185e440cef6eb9.png~tplv-uwbnlip3yd-webp.webp',
|
||||
content: '此处 bug 已经修复',
|
||||
time: '今天 12:30:01',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
type: 'message',
|
||||
title: '宁波',
|
||||
subTitle: '的回复',
|
||||
avatar:
|
||||
'//p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/3ee5f13fb09879ecb5185e440cef6eb9.png~tplv-uwbnlip3yd-webp.webp',
|
||||
content: '此处 bug 已经修复',
|
||||
time: '今天 12:20:01',
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
type: 'notice',
|
||||
title: '续费通知',
|
||||
subTitle: '',
|
||||
avatar: '',
|
||||
content: '您的产品使用期限即将截止,如需继续使用产品请前往购…',
|
||||
time: '今天 12:20:01',
|
||||
messageType: 3,
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
type: 'notice',
|
||||
title: '规则开通成功',
|
||||
subTitle: '',
|
||||
avatar: '',
|
||||
content: '内容屏蔽规则于 2021-12-01 开通成功并生效',
|
||||
time: '今天 12:20:01',
|
||||
messageType: 1,
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
type: 'todo',
|
||||
title: '质检队列变更',
|
||||
subTitle: '',
|
||||
avatar: '',
|
||||
content: '内容质检队列于 2021-12-01 19:50:23 进行变更,请重新…',
|
||||
time: '今天 12:20:01',
|
||||
messageType: 0,
|
||||
},
|
||||
].map((item) => ({
|
||||
...item,
|
||||
status: haveReadIds.indexOf(item.id) === -1 ? 0 : 1,
|
||||
}));
|
||||
};
|
||||
|
||||
setupMock({
|
||||
setup: () => {
|
||||
Mock.mock(new RegExp('/api/message/list'), () => {
|
||||
return successResponseWrap(getMessageList());
|
||||
});
|
||||
|
||||
Mock.mock(new RegExp('/api/message/read'), (params: { body: string }) => {
|
||||
const { ids } = JSON.parse(params.body);
|
||||
haveReadIds.push(...(ids || []));
|
||||
return successResponseWrap(true);
|
||||
});
|
||||
},
|
||||
});
|
105
src/mock/user.ts
105
src/mock/user.ts
|
@ -1,105 +0,0 @@
|
|||
import Mock from 'mockjs';
|
||||
import setupMock, {
|
||||
successResponseWrap,
|
||||
failResponseWrap,
|
||||
} from '@/utils/setup-mock';
|
||||
|
||||
import { MockParams } from '@/types/mock';
|
||||
import { isLogin } from '@/utils/auth';
|
||||
|
||||
setupMock({
|
||||
setup() {
|
||||
// Mock.XHR.prototype.withCredentials = true;
|
||||
|
||||
// 用户信息
|
||||
Mock.mock(new RegExp('/api/user/info'), () => {
|
||||
if (isLogin()) {
|
||||
const role = window.localStorage.getItem('userRole') || 'admin';
|
||||
return successResponseWrap({
|
||||
name: '王立群',
|
||||
avatar:
|
||||
'//lf1-xgcdn-tos.pstatp.com/obj/vcloud/vadmin/start.8e0e4855ee346a46ccff8ff3e24db27b.png',
|
||||
email: 'wangliqun@email.com',
|
||||
job: 'frontend',
|
||||
jobName: '前端艺术家',
|
||||
organization: 'Frontend',
|
||||
organizationName: '前端',
|
||||
location: 'beijing',
|
||||
locationName: '北京',
|
||||
introduction: '人潇洒,性温存',
|
||||
personalWebsite: 'https://www.arco.design',
|
||||
phone: '150****0000',
|
||||
registrationDate: '2013-05-10 12:10:00',
|
||||
accountId: '15012312300',
|
||||
certification: 1,
|
||||
role,
|
||||
});
|
||||
}
|
||||
return failResponseWrap(null, '未登录', 50008);
|
||||
});
|
||||
|
||||
// 登录
|
||||
Mock.mock(new RegExp('/api/user/login'), (params: MockParams) => {
|
||||
const { username, password } = JSON.parse(params.body);
|
||||
if (!username) {
|
||||
return failResponseWrap(null, '用户名不能为空', 50000);
|
||||
}
|
||||
if (!password) {
|
||||
return failResponseWrap(null, '密码不能为空', 50000);
|
||||
}
|
||||
if (username === 'admin' && password === 'admin') {
|
||||
window.localStorage.setItem('userRole', 'admin');
|
||||
return successResponseWrap({
|
||||
token: '12345',
|
||||
});
|
||||
}
|
||||
if (username === 'user' && password === 'user') {
|
||||
window.localStorage.setItem('userRole', 'user');
|
||||
return successResponseWrap({
|
||||
token: '54321',
|
||||
});
|
||||
}
|
||||
return failResponseWrap(null, '账号或者密码错误', 50000);
|
||||
});
|
||||
|
||||
// 登出
|
||||
Mock.mock(new RegExp('/api/user/logout'), () => {
|
||||
return successResponseWrap(null);
|
||||
});
|
||||
|
||||
// 用户的服务端菜单
|
||||
Mock.mock(new RegExp('/api/user/menu'), () => {
|
||||
const menuList = [
|
||||
{
|
||||
path: '/dashboard',
|
||||
name: 'dashboard',
|
||||
meta: {
|
||||
locale: 'menu.server.dashboard',
|
||||
requiresAuth: true,
|
||||
icon: 'icon-dashboard',
|
||||
order: 1,
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'workplace',
|
||||
name: 'Workplace',
|
||||
meta: {
|
||||
locale: 'menu.server.workplace',
|
||||
requiresAuth: true,
|
||||
},
|
||||
},
|
||||
{
|
||||
path: 'https://arco.design',
|
||||
name: 'arcoWebsite',
|
||||
meta: {
|
||||
locale: 'menu.arcoWebsite',
|
||||
requiresAuth: true,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
return successResponseWrap(menuList);
|
||||
});
|
||||
},
|
||||
});
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -1,51 +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="userInfo.avatar" />
|
||||
</a-avatar>
|
||||
<a-typography-text>
|
||||
{{ userInfo.name }} {{ $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 { useUserStore } from '@/store';
|
||||
|
||||
const userInfo = useUserStore();
|
||||
</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>
|
|
@ -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>
|
|
@ -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',
|
||||
};
|
|
@ -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': '在看',
|
||||
};
|
|
@ -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);
|
||||
});
|
||||
},
|
||||
});
|
|
@ -1,71 +0,0 @@
|
|||
<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>
|
|
@ -1,24 +0,0 @@
|
|||
<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>
|
|
@ -1,114 +0,0 @@
|
|||
<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>
|
|
@ -1,200 +0,0 @@
|
|||
<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>
|
|
@ -1,131 +0,0 @@
|
|||
<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>
|
|
@ -1,42 +0,0 @@
|
|||
<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>
|
|
@ -1,118 +0,0 @@
|
|||
<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>
|
|
@ -1,35 +0,0 @@
|
|||
<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>
|
|
@ -1,44 +0,0 @@
|
|||
<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>
|
|
@ -1,129 +0,0 @@
|
|||
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]);
|
||||
});
|
||||
},
|
||||
});
|
|
@ -1,41 +0,0 @@
|
|||
<template>
|
||||
<div class="container">
|
||||
<Breadcrumb :items="['menu.exception', 'menu.exception.403']" />
|
||||
<div class="content">
|
||||
<a-result
|
||||
class="result"
|
||||
status="403"
|
||||
:subtitle="$t('exception.result.403.description')"
|
||||
/>
|
||||
<a-button key="back" type="primary">
|
||||
{{ $t('exception.result.403.back') }}
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup></script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: '403',
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.container {
|
||||
padding: 0 20px 20px 20px;
|
||||
height: calc(100% - 40px);
|
||||
:deep(.content) {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
background-color: var(--color-bg-1);
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,6 +0,0 @@
|
|||
export default {
|
||||
'menu.exception.403': '403',
|
||||
'exception.result.403.description':
|
||||
'Access to this resource on the server is denied.',
|
||||
'exception.result.403.back': 'Back',
|
||||
};
|
|
@ -1,5 +0,0 @@
|
|||
export default {
|
||||
'menu.exception.403': '403',
|
||||
'exception.result.403.description': '对不起,您没有访问该资源的权限',
|
||||
'exception.result.403.back': '返回',
|
||||
};
|
|
@ -1,47 +0,0 @@
|
|||
<template>
|
||||
<div class="container">
|
||||
<Breadcrumb :items="['menu.exception', 'menu.exception.404']" />
|
||||
<div class="content">
|
||||
<a-result
|
||||
class="result"
|
||||
status="404"
|
||||
:subtitle="$t('exception.result.404.description')"
|
||||
>
|
||||
</a-result>
|
||||
<div class="operation-row">
|
||||
<a-button key="again" style="margin-right: 16px">
|
||||
{{ $t('exception.result.404.retry') }}
|
||||
</a-button>
|
||||
<a-button key="back" type="primary">
|
||||
{{ $t('exception.result.404.back') }}
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup></script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: '404',
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.container {
|
||||
padding: 0 20px 20px 20px;
|
||||
height: calc(100% - 40px);
|
||||
:deep(.content) {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
background-color: var(--color-bg-1);
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,6 +0,0 @@
|
|||
export default {
|
||||
'menu.exception.404': '404',
|
||||
'exception.result.404.description': 'Whoops, this page is gone.',
|
||||
'exception.result.404.retry': 'Retry',
|
||||
'exception.result.404.back': 'Back',
|
||||
};
|
|
@ -1,6 +0,0 @@
|
|||
export default {
|
||||
'menu.exception.404': '404',
|
||||
'exception.result.404.description': '抱歉,页面不见了~',
|
||||
'exception.result.404.retry': '重试',
|
||||
'exception.result.404.back': '返回',
|
||||
};
|
|
@ -1,41 +0,0 @@
|
|||
<template>
|
||||
<div class="container">
|
||||
<Breadcrumb :items="['menu.exception', 'menu.exception.500']" />
|
||||
<div class="content">
|
||||
<a-result
|
||||
class="result"
|
||||
status="500"
|
||||
:subtitle="$t('exception.result.500.description')"
|
||||
/>
|
||||
<a-button key="back" type="primary">
|
||||
{{ $t('exception.result.500.back') }}
|
||||
</a-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup></script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: '500',
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.container {
|
||||
padding: 0 20px 20px 20px;
|
||||
height: calc(100% - 40px);
|
||||
:deep(.content) {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
text-align: center;
|
||||
background-color: var(--color-bg-1);
|
||||
border-radius: 4px;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,5 +0,0 @@
|
|||
export default {
|
||||
'menu.exception.500': '500',
|
||||
'exception.result.500.description': 'Internal server error',
|
||||
'exception.result.500.back': 'Back',
|
||||
};
|
|
@ -1,5 +0,0 @@
|
|||
export default {
|
||||
'menu.exception.500': '500',
|
||||
'exception.result.500.description': '抱歉,服务器出了点问题~',
|
||||
'exception.result.500.back': '返回',
|
||||
};
|
|
@ -1,293 +0,0 @@
|
|||
<template>
|
||||
<div class="container">
|
||||
<Breadcrumb :items="['menu.form', 'menu.form.group']" />
|
||||
<a-form ref="formRef" layout="vertical" :model="formData">
|
||||
<a-space direction="vertical" :size="16">
|
||||
<a-card class="general-card">
|
||||
<template #title>
|
||||
{{ $t('groupForm.title.video') }}
|
||||
</template>
|
||||
<a-row :gutter="80">
|
||||
<a-col :span="8">
|
||||
<a-form-item
|
||||
:label="$t('groupForm.form.label.video.mode')"
|
||||
field="video.mode"
|
||||
>
|
||||
<a-select :placeholder="$t('groupForm.placeholder.video.mode')">
|
||||
<a-option value="custom">自定义</a-option>
|
||||
<a-option value="mode1">模式1</a-option>
|
||||
<a-option value="mode2">模式2</a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item
|
||||
:label="$t('groupForm.form.label.video.acquisition.resolution')"
|
||||
field="video.acquisition.resolution"
|
||||
>
|
||||
<a-select
|
||||
:placeholder="
|
||||
$t('groupForm.placeholder.video.acquisition.resolution')
|
||||
"
|
||||
>
|
||||
<a-option value="resolution1">分辨率1</a-option>
|
||||
<a-option value="resolution2">分辨率2</a-option>
|
||||
<a-option value="resolution3">分辨率3</a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item
|
||||
:label="$t('groupForm.form.label.video.acquisition.frameRate')"
|
||||
field="video.acquisition.frameRate"
|
||||
>
|
||||
<a-input
|
||||
:placeholder="
|
||||
$t('groupForm.placeholder.video.acquisition.frameRate')
|
||||
"
|
||||
>
|
||||
<template #append> fps </template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="80">
|
||||
<a-col :span="8">
|
||||
<a-form-item
|
||||
:label="$t('groupForm.form.label.video.encoding.resolution')"
|
||||
field="video.encoding.resolution"
|
||||
>
|
||||
<a-select
|
||||
:placeholder="
|
||||
$t('groupForm.placeholder.video.encoding.resolution')
|
||||
"
|
||||
>
|
||||
<a-option value="resolution1">分辨率1</a-option>
|
||||
<a-option value="resolution2">分辨率2</a-option>
|
||||
<a-option value="resolution3">分辨率3</a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item
|
||||
:label="$t('groupForm.form.label.video.encoding.rate.min')"
|
||||
field="video.encoding.rate.min"
|
||||
>
|
||||
<a-input
|
||||
:placeholder="
|
||||
$t('groupForm.placeholder.video.encoding.rate.min')
|
||||
"
|
||||
add-after="bps"
|
||||
>
|
||||
<template #append> bps </template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item
|
||||
:label="$t('groupForm.form.label.video.encoding.rate.max')"
|
||||
field="video.encoding.rate.max"
|
||||
>
|
||||
<a-input
|
||||
:placeholder="
|
||||
$t('groupForm.placeholder.video.encoding.rate.max')
|
||||
"
|
||||
>
|
||||
<template #append> bps </template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="80">
|
||||
<a-col :span="8">
|
||||
<a-form-item
|
||||
:label="$t('groupForm.form.label.video.encoding.rate.default')"
|
||||
field="video.encoding.rate.default"
|
||||
>
|
||||
<a-input
|
||||
:placeholder="
|
||||
$t('groupForm.placeholder.video.encoding.rate.default')
|
||||
"
|
||||
>
|
||||
<template #append> bps </template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item
|
||||
:label="$t('groupForm.form.label.video.encoding.frameRate')"
|
||||
field="video.encoding.frameRate"
|
||||
>
|
||||
<a-input
|
||||
:placeholder="
|
||||
$t('groupForm.placeholder.video.encoding.frameRate')
|
||||
"
|
||||
>
|
||||
<template #append> fps </template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item
|
||||
:label="$t('groupForm.form.label.video.encoding.profile')"
|
||||
field="video.encoding.profile"
|
||||
>
|
||||
<a-input
|
||||
:placeholder="
|
||||
$t('groupForm.placeholder.video.encoding.profile')
|
||||
"
|
||||
>
|
||||
<template #append> bps </template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
<a-card class="general-card">
|
||||
<template #title>
|
||||
{{ $t('groupForm.title.audio') }}
|
||||
</template>
|
||||
<a-row :gutter="80">
|
||||
<a-col :span="8">
|
||||
<a-form-item
|
||||
:label="$t('groupForm.form.label.audio.mode')"
|
||||
field="audio.mode"
|
||||
>
|
||||
<a-select :placeholder="$t('groupForm.placeholder.audio.mode')">
|
||||
<a-option value="custom">自定义</a-option>
|
||||
<a-option value="mode1">模式1</a-option>
|
||||
<a-option value="mode2">模式2</a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item
|
||||
:label="$t('groupForm.form.label.audio.acquisition.channels')"
|
||||
field="audio.acquisition.channels"
|
||||
>
|
||||
<a-select
|
||||
:placeholder="
|
||||
$t('groupForm.placeholder.audio.acquisition.channels')
|
||||
"
|
||||
>
|
||||
<a-option value="1">1</a-option>
|
||||
<a-option value="2">2</a-option>
|
||||
<a-option value="3">3</a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
<a-col :span="8">
|
||||
<a-form-item
|
||||
:label="$t('groupForm.form.label.audio.encoding.channels')"
|
||||
field="audio.encoding.channels"
|
||||
>
|
||||
<a-input
|
||||
:placeholder="
|
||||
$t('groupForm.placeholder.audio.encoding.channels')
|
||||
"
|
||||
>
|
||||
<template #append> bps </template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row :gutter="80">
|
||||
<a-col :span="8">
|
||||
<a-form-item
|
||||
:label="$t('groupForm.form.label.audio.encoding.rate')"
|
||||
field="audio.encoding.rate"
|
||||
>
|
||||
<a-input
|
||||
:placeholder="$t('groupForm.placeholder.audio.encoding.rate')"
|
||||
>
|
||||
<template #append> bps </template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
|
||||
<a-col :span="8">
|
||||
<a-form-item
|
||||
:label="$t('groupForm.form.label.audio.encoding.profile')"
|
||||
field="audio.encoding.profile"
|
||||
>
|
||||
<a-input
|
||||
:placeholder="
|
||||
$t('groupForm.placeholder.audio.encoding.profile')
|
||||
"
|
||||
>
|
||||
<template #append> fps </template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
<a-card class="general-card" :bordered="false">
|
||||
<template #title>
|
||||
{{ $t('groupForm.title.description') }}
|
||||
</template>
|
||||
<a-form-item
|
||||
:label="$t('groupForm.form.label.parameterDescription')"
|
||||
field="audio.approvers"
|
||||
>
|
||||
<a-textarea
|
||||
:placeholder="$t('groupForm.placeholder.description')"
|
||||
/>
|
||||
</a-form-item>
|
||||
</a-card>
|
||||
</a-space>
|
||||
<div class="actions">
|
||||
<a-space>
|
||||
<a-button>
|
||||
{{ $t('groupForm.reset') }}
|
||||
</a-button>
|
||||
<a-button type="primary" :loading="loading" @click="onSubmitClick">
|
||||
{{ $t('groupForm.submit') }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</div>
|
||||
</a-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { FormInstance } from '@arco-design/web-vue/es/form';
|
||||
import useLoading from '@/hooks/loading';
|
||||
|
||||
const formData = ref({});
|
||||
const formRef = ref<FormInstance>();
|
||||
const { loading, setLoading } = useLoading();
|
||||
const onSubmitClick = async () => {
|
||||
const res = await formRef.value?.validate();
|
||||
if (!res) {
|
||||
setLoading(true);
|
||||
}
|
||||
setTimeout(() => {
|
||||
setLoading(false);
|
||||
}, 1000);
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'Group',
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.container {
|
||||
padding: 0 20px 40px 20px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.actions {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
height: 60px;
|
||||
padding: 14px 20px 14px 0;
|
||||
background: var(--color-bg-2);
|
||||
text-align: right;
|
||||
}
|
||||
</style>
|
|
@ -1,41 +0,0 @@
|
|||
export default {
|
||||
'menu.form.group': 'Group Form',
|
||||
'groupForm.title.video': 'Video Parameters',
|
||||
'groupForm.title.audio': 'Audio Parameters',
|
||||
'groupForm.title.description': 'Enter Description',
|
||||
'groupForm.form.label.video.mode': 'Match Mode',
|
||||
'groupForm.form.label.video.acquisition.resolution': 'Acquisition Resolution',
|
||||
'groupForm.form.label.video.acquisition.frameRate': 'Acquisition Frame Rate',
|
||||
'groupForm.form.label.video.encoding.resolution': 'Encoding Resolution',
|
||||
'groupForm.form.label.video.encoding.rate.min': 'Encoding Min Rate',
|
||||
'groupForm.form.label.video.encoding.rate.max': 'Encoding Max Rate',
|
||||
'groupForm.form.label.video.encoding.rate.default': 'Encoding Default Rate',
|
||||
'groupForm.form.label.video.encoding.frameRate': 'Encoding Frame Rate',
|
||||
'groupForm.form.label.video.encoding.profile': 'Encoding Profile',
|
||||
'groupForm.placeholder.video.mode': 'Please Select',
|
||||
'groupForm.placeholder.video.acquisition.resolution': 'Please Select',
|
||||
'groupForm.placeholder.video.acquisition.frameRate': 'Enter Range [1, 30]',
|
||||
'groupForm.placeholder.video.encoding.resolution': 'Please Select',
|
||||
'groupForm.placeholder.video.encoding.rate.min': 'Enter Range [150, 1800]',
|
||||
'groupForm.placeholder.video.encoding.rate.max': 'Enter Range [150, 1800]',
|
||||
'groupForm.placeholder.video.encoding.rate.default':
|
||||
'Enter Range [150, 1800]',
|
||||
'groupForm.placeholder.video.encoding.frameRate': 'Enter Range [1, 30]',
|
||||
'groupForm.placeholder.video.encoding.profile': 'Enter Range [150, 1800]',
|
||||
'groupForm.form.label.audio.mode': 'Match Mode',
|
||||
'groupForm.form.label.audio.acquisition.channels': 'Acquisition Channels',
|
||||
'groupForm.form.label.audio.encoding.rate': 'Encoding Rate',
|
||||
'groupForm.form.label.audio.encoding.channels': 'Encoding Channels',
|
||||
'groupForm.placeholder.audio.encoding.channels': 'Enter Range [150, 1800]',
|
||||
'groupForm.form.label.audio.encoding.profile': 'Encoding Profile',
|
||||
'groupForm.placeholder.audio.mode': 'Please Select',
|
||||
'groupForm.placeholder.audio.acquisition.channels': 'Please Select',
|
||||
'groupForm.placeholder.audio.encoding.rate': 'Enter Range [150, 1800]',
|
||||
'groupForm.placeholder.audio.encoding.profile': 'Enter Range [1, 30]',
|
||||
'groupForm.form.label.parameterDescription': 'Parameter Description',
|
||||
// https://vue-i18n.intlify.dev/guide/essentials/syntax.html#list-interpolation
|
||||
'groupForm.placeholder.description': `Please fill in the parameter description with a maximum of 200 words`,
|
||||
'groupForm.submit': 'Submit',
|
||||
'groupForm.reset': 'Reset',
|
||||
'groupForm.submitSuccess': 'Submit Success',
|
||||
};
|
|
@ -1,40 +0,0 @@
|
|||
export default {
|
||||
'menu.form.group': '分组表单',
|
||||
'groupForm.title.video': '视频参数',
|
||||
'groupForm.title.audio': '音频参数',
|
||||
'groupForm.title.description': '填写说明',
|
||||
'groupForm.form.label.video.mode': '匹配模式',
|
||||
'groupForm.form.label.video.acquisition.resolution': '采集分辨率',
|
||||
'groupForm.form.label.video.acquisition.frameRate': '采集帧率',
|
||||
'groupForm.form.label.video.encoding.resolution': '编码分辨率',
|
||||
'groupForm.form.label.video.encoding.rate.min': '编码码率最小值',
|
||||
'groupForm.form.label.video.encoding.rate.max': '编码码率最大值',
|
||||
'groupForm.form.label.video.encoding.rate.default': '编码码率默认值',
|
||||
'groupForm.form.label.video.encoding.frameRate': '编码帧率',
|
||||
'groupForm.form.label.video.encoding.profile': '编码profile',
|
||||
'groupForm.placeholder.video.mode': '请选择',
|
||||
'groupForm.placeholder.video.acquisition.resolution': '请选择',
|
||||
'groupForm.placeholder.video.acquisition.frameRate': '输入范围[1, 30]',
|
||||
'groupForm.placeholder.video.encoding.resolution': '请选择',
|
||||
'groupForm.placeholder.video.encoding.rate.min': '输入范围[150, 1800]',
|
||||
'groupForm.placeholder.video.encoding.rate.max': '输入范围[150, 1800]',
|
||||
'groupForm.placeholder.video.encoding.rate.default': '输入范围[150, 1800]',
|
||||
'groupForm.placeholder.video.encoding.frameRate': '输入范围[1, 30]',
|
||||
'groupForm.placeholder.video.encoding.profile': '输入范围[150, 1800]',
|
||||
'groupForm.form.label.audio.mode': '匹配模式',
|
||||
'groupForm.form.label.audio.acquisition.channels': '采集声道数',
|
||||
'groupForm.form.label.audio.encoding.rate': '编码码率',
|
||||
'groupForm.form.label.audio.encoding.channels': '编码声道数',
|
||||
'groupForm.placeholder.audio.encoding.channels': '输入范围[150, 1800]',
|
||||
'groupForm.form.label.audio.encoding.profile': '编码profile',
|
||||
'groupForm.placeholder.audio.mode': '请选择',
|
||||
'groupForm.placeholder.audio.acquisition.channels': '请选择',
|
||||
'groupForm.placeholder.audio.encoding.rate': '输入范围[150, 1800]',
|
||||
'groupForm.placeholder.audio.encoding.profile': '输入范围[1, 30]',
|
||||
'groupForm.form.label.parameterDescription': '参数说明',
|
||||
// https://vue-i18n.intlify.dev/guide/essentials/syntax.html#list-interpolation
|
||||
'groupForm.placeholder.description': `请填写参数说明,最多不超多200字。`,
|
||||
'groupForm.submit': '提交',
|
||||
'groupForm.reset': '重置',
|
||||
'groupForm.submitSuccess': '提交成功',
|
||||
};
|
|
@ -1,137 +0,0 @@
|
|||
<template>
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
class="form"
|
||||
:label-col-props="{ span: 6 }"
|
||||
:wrapper-col-props="{ span: 18 }"
|
||||
>
|
||||
<a-form-item
|
||||
field="activityName"
|
||||
:label="$t('stepForm.form.label.activityName')"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: $t('stepForm.form.error.activityName.required'),
|
||||
},
|
||||
{
|
||||
match: /^[a-zA-Z0-9\u4e00-\u9fa5]{1,20}$/,
|
||||
message: $t('stepForm.form.error.activityName.pattern'),
|
||||
},
|
||||
]"
|
||||
>
|
||||
<a-input
|
||||
v-model="formData.activityName"
|
||||
:placeholder="$t('stepForm.placeholder.activityName')"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
field="channelType"
|
||||
:label="$t('stepForm.form.label.channelType')"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: $t('stepForm.form.error.channelType.required'),
|
||||
},
|
||||
]"
|
||||
>
|
||||
<a-select
|
||||
v-model="formData.channelType"
|
||||
:placeholder="$t('stepForm.placeholder.channelType')"
|
||||
>
|
||||
<a-option>APP通用渠道</a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
field="promotionTime"
|
||||
:label="$t('stepForm.form.label.promotionTime')"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: $t('stepForm.form.error.promotionTime.required'),
|
||||
},
|
||||
]"
|
||||
>
|
||||
<a-range-picker v-model="formData.promotionTime" />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
field="promoteLink"
|
||||
:label="$t('stepForm.form.label.promoteLink')"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: $t('stepForm.form.error.promoteLink.required'),
|
||||
},
|
||||
{
|
||||
type: 'url',
|
||||
message: $t('stepForm.form.error.promoteLink.pattern'),
|
||||
},
|
||||
]"
|
||||
row-class="keep-margin"
|
||||
>
|
||||
<a-input
|
||||
v-model="formData.promoteLink"
|
||||
:placeholder="$t('stepForm.placeholder.promoteLink')"
|
||||
/>
|
||||
<template #help>
|
||||
<span>{{ $t('stepForm.form.tip.promoteLink') }}</span>
|
||||
</template>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-button type="primary" @click="onNextClick">
|
||||
{{ $t('stepForm.button.next') }}
|
||||
</a-button>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { FormInstance } from '@arco-design/web-vue/es/form';
|
||||
import { BaseInfoModel } from '@/api/form';
|
||||
|
||||
const emits = defineEmits(['changeStep']);
|
||||
const formRef = ref<FormInstance>();
|
||||
const formData = ref<BaseInfoModel>({
|
||||
activityName: '',
|
||||
channelType: '',
|
||||
promotionTime: [],
|
||||
promoteLink: 'https://arco.design',
|
||||
});
|
||||
|
||||
const onNextClick = async () => {
|
||||
const res = await formRef.value?.validate();
|
||||
if (!res) {
|
||||
emits('changeStep', 'forward', { ...formData.value });
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.container {
|
||||
padding: 20px;
|
||||
.keep-margin {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 64px 0;
|
||||
background-color: var(--color-bg-2);
|
||||
}
|
||||
|
||||
.steps {
|
||||
margin-bottom: 36px;
|
||||
}
|
||||
|
||||
.form {
|
||||
width: 500px;
|
||||
}
|
||||
|
||||
.form-content {
|
||||
padding: 8px 50px 0 30px;
|
||||
}
|
||||
</style>
|
|
@ -1,151 +0,0 @@
|
|||
<template>
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
class="form"
|
||||
:label-col-props="{ span: 6 }"
|
||||
:wrapper-col-props="{ span: 18 }"
|
||||
>
|
||||
<a-form-item
|
||||
field="advertisingSource"
|
||||
:label="$t('stepForm.form.label.advertisingSource')"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: $t('stepForm.form.error.advertisingSource.required'),
|
||||
},
|
||||
]"
|
||||
>
|
||||
<a-input
|
||||
v-model="formData.advertisingSource"
|
||||
:placeholder="$t('stepForm.placeholder.advertisingSource')"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
field="advertisingMedia"
|
||||
:label="$t('stepForm.form.label.advertisingMedia')"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: $t('stepForm.form.error.advertisingMedia.required'),
|
||||
},
|
||||
]"
|
||||
>
|
||||
<a-input
|
||||
v-model="formData.advertisingMedia"
|
||||
:placeholder="$t('stepForm.placeholder.advertisingMedia')"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
field="keyword"
|
||||
:label="$t('stepForm.form.label.keyword')"
|
||||
:rules="[
|
||||
{ required: true, message: $t('stepForm.form.error.keyword.required') },
|
||||
]"
|
||||
>
|
||||
<a-select
|
||||
v-model="formData.keyword"
|
||||
:placeholder="$t('stepForm.placeholder.keyword')"
|
||||
multiple
|
||||
>
|
||||
<a-option>今日头条</a-option>
|
||||
<a-option>火山</a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
field="pushNotify"
|
||||
:label="$t('stepForm.form.label.pushNotify')"
|
||||
:rules="[{ required: true }]"
|
||||
>
|
||||
<a-switch v-model="formData.pushNotify" />
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
field="advertisingContent"
|
||||
:label="$t('stepForm.form.label.advertisingContent')"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: $t('stepForm.form.error.advertisingContent.required'),
|
||||
},
|
||||
{
|
||||
maxLength: 200,
|
||||
message: $t('stepForm.form.error.advertisingContent.maxLength'),
|
||||
},
|
||||
]"
|
||||
row-class="keep-margin"
|
||||
>
|
||||
<a-textarea
|
||||
v-model="formData.advertisingContent"
|
||||
:placeholder="$t('stepForm.placeholder.advertisingContent')"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<!-- <a-button type="primary" @click="onNextClick">
|
||||
{{ $t('stepForm.button.next') }}
|
||||
</a-button> -->
|
||||
<a-space>
|
||||
<a-button type="secondary" @click="goPrev">
|
||||
{{ $t('stepForm.button.prev') }}
|
||||
</a-button>
|
||||
<a-button type="primary" @click="onNextClick">
|
||||
{{ $t('stepForm.button.next') }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { FormInstance } from '@arco-design/web-vue/es/form';
|
||||
import { ChannelInfoModel } from '@/api/form';
|
||||
|
||||
const emits = defineEmits(['changeStep']);
|
||||
|
||||
const formRef = ref<FormInstance>();
|
||||
const formData = ref<ChannelInfoModel>({
|
||||
advertisingSource: '',
|
||||
advertisingMedia: '',
|
||||
keyword: [],
|
||||
pushNotify: true,
|
||||
advertisingContent: '',
|
||||
});
|
||||
|
||||
const onNextClick = async () => {
|
||||
const res = await formRef.value?.validate();
|
||||
if (!res) {
|
||||
emits('changeStep', 'submit', { ...formData.value });
|
||||
}
|
||||
};
|
||||
const goPrev = () => {
|
||||
emits('changeStep', 'backward');
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.container {
|
||||
.keep-margin {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 64px 0;
|
||||
background-color: var(--color-bg-2);
|
||||
}
|
||||
|
||||
.steps {
|
||||
margin-bottom: 36px;
|
||||
}
|
||||
|
||||
.form {
|
||||
width: 540px;
|
||||
}
|
||||
|
||||
.form-content {
|
||||
padding: 8px 50px 0 30px;
|
||||
}
|
||||
</style>
|
|
@ -1,49 +0,0 @@
|
|||
<template>
|
||||
<div class="success-wrap">
|
||||
<a-result
|
||||
status="success"
|
||||
:title="$t('stepForm.success.title')"
|
||||
:subtitle="$t('stepForm.success.subTitle')"
|
||||
/>
|
||||
<a-space :size="16">
|
||||
<a-button key="view" type="primary">
|
||||
{{ $t('stepForm.button.view') }}
|
||||
</a-button>
|
||||
<a-button key="again" type="secondary" @click="oneMore">
|
||||
{{ $t('stepForm.button.again') }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
<div class="details-wrapper">
|
||||
<a-typography-title :heading="6" style="margin-top: 0">
|
||||
{{ $t('stepForm.form.description.title') }}
|
||||
</a-typography-title>
|
||||
<a-typography-paragraph style="margin-bottom: 0">
|
||||
{{ $t('stepForm.form.description.text') }}
|
||||
<a-link href="link">{{ $t('stepForm.button.view') }}</a-link>
|
||||
</a-typography-paragraph>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
const emits = defineEmits(['changeStep']);
|
||||
const oneMore = () => {
|
||||
emits('changeStep', 1);
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.success-wrap {
|
||||
text-align: center;
|
||||
}
|
||||
:deep(.arco-result) {
|
||||
padding-top: 0;
|
||||
}
|
||||
.details-wrapper {
|
||||
width: 895px;
|
||||
margin-top: 54px;
|
||||
padding: 20px;
|
||||
text-align: left;
|
||||
background-color: var(--color-fill-2);
|
||||
}
|
||||
</style>
|
|
@ -1,118 +0,0 @@
|
|||
<template>
|
||||
<div class="container">
|
||||
<Breadcrumb :items="['menu.form', 'menu.form.step']" />
|
||||
<a-spin :loading="loading" style="width: 100%">
|
||||
<a-card class="general-card">
|
||||
<template #title>
|
||||
{{ $t('stepForm.step.title') }}
|
||||
</template>
|
||||
<div class="wrapper">
|
||||
<a-steps
|
||||
v-model:current="step"
|
||||
style="width: 580px"
|
||||
line-less
|
||||
class="steps"
|
||||
>
|
||||
<a-step :description="$t('stepForm.step.subTitle.baseInfo')">
|
||||
{{ $t('stepForm.step.title.baseInfo') }}
|
||||
</a-step>
|
||||
<a-step :description="$t('stepForm.step.subTitle.channel')">
|
||||
{{ $t('stepForm.step.title.channel') }}
|
||||
</a-step>
|
||||
<a-step :description="$t('stepForm.step.subTitle.finish')">
|
||||
{{ $t('stepForm.step.title.finish') }}
|
||||
</a-step>
|
||||
</a-steps>
|
||||
<keep-alive>
|
||||
<BaseInfo v-if="step === 1" @change-step="changeStep" />
|
||||
<ChannelInfo v-else-if="step === 2" @change-step="changeStep" />
|
||||
<Success v-else-if="step === 3" @change-step="changeStep" />
|
||||
</keep-alive>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-spin>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import useLoading from '@/hooks/loading';
|
||||
import {
|
||||
submitChannelForm,
|
||||
BaseInfoModel,
|
||||
ChannelInfoModel,
|
||||
UnitChannelModel,
|
||||
} from '@/api/form';
|
||||
import BaseInfo from './components/base-info.vue';
|
||||
import ChannelInfo from './components/channel-info.vue';
|
||||
import Success from './components/success.vue';
|
||||
|
||||
const { loading, setLoading } = useLoading(false);
|
||||
const step = ref(1);
|
||||
const submitModel = ref<UnitChannelModel>({} as UnitChannelModel);
|
||||
const submitForm = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
await submitChannelForm(submitModel.value); // The mock api default success
|
||||
step.value = 3;
|
||||
submitModel.value = {} as UnitChannelModel; // init
|
||||
} catch (err) {
|
||||
// you can report use errorHandler or other
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
const changeStep = (
|
||||
direction: string | number,
|
||||
model: BaseInfoModel | ChannelInfoModel
|
||||
) => {
|
||||
if (typeof direction === 'number') {
|
||||
step.value = direction;
|
||||
return;
|
||||
}
|
||||
|
||||
if (direction === 'forward' || direction === 'submit') {
|
||||
submitModel.value = {
|
||||
...submitModel.value,
|
||||
...model,
|
||||
};
|
||||
if (direction === 'submit') {
|
||||
submitForm();
|
||||
return;
|
||||
}
|
||||
step.value += 1;
|
||||
} else if (direction === 'backward') {
|
||||
step.value -= 1;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'Step',
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.container {
|
||||
padding: 0 20px 20px 20px;
|
||||
}
|
||||
.wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
padding: 64px 0;
|
||||
background-color: var(--color-bg-2);
|
||||
:deep(.arco-form) {
|
||||
.arco-form-item {
|
||||
&:last-child {
|
||||
margin-top: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.steps {
|
||||
margin-bottom: 76px;
|
||||
}
|
||||
</style>
|
|
@ -1,66 +0,0 @@
|
|||
export default {
|
||||
'menu.form.step': 'Step Form',
|
||||
'stepForm.step.title': 'Create Channel Forms',
|
||||
'stepForm.step.title.baseInfo': 'Select Basic Information',
|
||||
'stepForm.step.subTitle.baseInfo': 'Channel creation activities',
|
||||
'stepForm.step.title.channel': 'Channel Information',
|
||||
'stepForm.step.subTitle.channel': 'Select upstream of domain',
|
||||
'stepForm.step.title.finish': 'Finish',
|
||||
'stepForm.step.subTitle.finish': 'Submit success',
|
||||
'stepForm.success.title': 'Success',
|
||||
'stepForm.success.subTitle': 'The form is submitted successfully!',
|
||||
'stepForm.button.next': 'Next',
|
||||
'stepForm.button.prev': 'Prev',
|
||||
'stepForm.button.submit': 'Submit',
|
||||
'stepForm.button.again': 'Again',
|
||||
'stepForm.button.view': 'Detail',
|
||||
'stepForm.label.type.web': 'web domain',
|
||||
'stepForm.label.type.api': 'api domain',
|
||||
// form -1
|
||||
'stepForm.form.label.activityName': 'Activity Name',
|
||||
'stepForm.placeholder.activityName':
|
||||
'Enter a maximum of 20 Chinese characters, letters, or digits',
|
||||
'stepForm.form.error.activityName.pattern':
|
||||
'Enter a maximum of 20 Chinese characters, letters, or digits',
|
||||
'stepForm.form.error.activityName.required': 'Please enter the activity name',
|
||||
'stepForm.form.label.channelType': 'Channel Type',
|
||||
'stepForm.placeholder.channelType': 'Select a channel type',
|
||||
'stepForm.form.error.channelType.required': 'Please select a channel type',
|
||||
'stepForm.form.label.promotionTime': 'Promotion Time',
|
||||
'stepForm.form.error.promotionTime.required':
|
||||
'Please select the promotion time',
|
||||
'stepForm.form.label.promoteLink': 'Promote Link',
|
||||
'stepForm.form.error.promoteLink.required': 'Please enter the promotion link',
|
||||
'stepForm.form.error.promoteLink.pattern':
|
||||
'For example, the download address of Android or iOS or the intermediate URL must start with http:// or https://',
|
||||
'stepForm.form.tip.promoteLink':
|
||||
'For example, the download address of Android or iOS or the intermediate URL must start with http:// or https://',
|
||||
'stepForm.placeholder.promoteLink': 'Please enter the promotion page Link',
|
||||
|
||||
// form -1 end
|
||||
// form -2
|
||||
'stepForm.form.label.advertisingSource': 'Advertising Source',
|
||||
'stepForm.placeholder.advertisingSource':
|
||||
'Introduction source address: Sohu, Sina',
|
||||
'stepForm.form.error.advertisingSource.required':
|
||||
'Please enter the advertising source',
|
||||
'stepForm.form.label.advertisingMedia': 'Advertising Media',
|
||||
'stepForm.placeholder.advertisingMedia': 'Marketing media: CPC, Banner, EDM',
|
||||
'stepForm.form.error.advertisingMedia.required':
|
||||
'Please enter the advertising media',
|
||||
'stepForm.form.label.keyword': 'keyword',
|
||||
'stepForm.placeholder.keyword': 'Please select keyword',
|
||||
'stepForm.form.error.keyword.required': 'Please select keyword',
|
||||
'stepForm.form.label.pushNotify': 'Push Notify',
|
||||
'stepForm.form.label.advertisingContent': 'Advertising Content',
|
||||
'stepForm.placeholder.advertisingContent':
|
||||
'Please enter the description of advertisement content, the maximum is 200 words',
|
||||
'stepForm.form.error.advertisingContent.required':
|
||||
'Please enter the description of advertisement content',
|
||||
'stepForm.form.error.advertisingContent.maxLength':
|
||||
'the maximum is 200 words',
|
||||
// form -2 end
|
||||
'stepForm.form.description.title': 'Channel Form Description',
|
||||
'stepForm.form.description.text':
|
||||
'Advertiser channel promotion supports tracking of users who download apps by placing ads on third-party advertisers, such as toutiao channel, and tracking users who activate apps by downloading apps through channels.',
|
||||
};
|
|
@ -1,52 +0,0 @@
|
|||
export default {
|
||||
'menu.form.step': '分步表单',
|
||||
'stepForm.step.title': '创建渠道表单',
|
||||
'stepForm.step.title.baseInfo': '选择基本信息',
|
||||
'stepForm.step.subTitle.baseInfo': '创建渠道活动',
|
||||
'stepForm.step.title.channel': '输入渠道信息',
|
||||
'stepForm.step.subTitle.channel': '输入详细的渠道信息',
|
||||
'stepForm.step.title.finish': '完成创建',
|
||||
'stepForm.step.subTitle.finish': '创建成功',
|
||||
'stepForm.success.title': '提交成功',
|
||||
'stepForm.success.subTitle': '表单提交成功!',
|
||||
'stepForm.button.next': '下一步',
|
||||
'stepForm.button.prev': '上一步',
|
||||
'stepForm.button.submit': '提交',
|
||||
'stepForm.button.again': '再次创建',
|
||||
'stepForm.button.view': '查看详情',
|
||||
'stepForm.form.label.activityName': '活动名称',
|
||||
'stepForm.placeholder.activityName': '输入汉字、字母或数字,最多20字符',
|
||||
'stepForm.form.error.activityName.required': '请输入活动名称',
|
||||
'stepForm.form.error.activityName.pattern':
|
||||
'输入汉字、字母或数字,最多20字符',
|
||||
'stepForm.form.label.channelType': '渠道类型',
|
||||
'stepForm.placeholder.channelType': '请选择渠道类型',
|
||||
'stepForm.form.error.channelType.required': '请选择渠道类型',
|
||||
'stepForm.form.label.promotionTime': '推广时间',
|
||||
'stepForm.form.error.promotionTime.required': '请选择推广时间',
|
||||
'stepForm.form.label.promoteLink': '推广地址',
|
||||
'stepForm.form.error.promoteLink.required': '请输入推广地址',
|
||||
'stepForm.form.error.promoteLink.pattern':
|
||||
'如 Android 或 iOS 的下载地址、中间跳转URL,网址必须以 http:// 或 https:// 开头',
|
||||
'stepForm.form.tip.promoteLink':
|
||||
'如 Android 或 iOS 的下载地址、中间跳转URL,网址必须以 http:// 或 https:// 开头',
|
||||
'stepForm.placeholder.promoteLink': '请输入推广页面地址',
|
||||
'stepForm.form.label.advertisingSource': '广告来源',
|
||||
'stepForm.placeholder.advertisingSource': '引荐来源地址:sohu、sina',
|
||||
'stepForm.form.error.advertisingSource.required': '请输入广告来源',
|
||||
'stepForm.form.label.advertisingMedia': '广告媒介',
|
||||
'stepForm.placeholder.advertisingMedia': '营销媒介:cpc、banner、edm',
|
||||
'stepForm.form.error.advertisingMedia.required': '请输入广告媒介',
|
||||
'stepForm.form.label.keyword': '关键词',
|
||||
'stepForm.placeholder.keyword': '请选择关键词',
|
||||
'stepForm.form.error.keyword.required': '请选择关键词',
|
||||
'stepForm.form.label.pushNotify': '推送提醒',
|
||||
'stepForm.form.label.advertisingContent': '广告内容',
|
||||
'stepForm.placeholder.advertisingContent':
|
||||
'请输入广告内容介绍,最多不超过200字。',
|
||||
'stepForm.form.error.advertisingContent.required': '请输入广告内容',
|
||||
'stepForm.form.error.advertisingContent.maxLength': '最多不超过200字',
|
||||
'stepForm.form.description.title': '渠道表单说明',
|
||||
'stepForm.form.description.text':
|
||||
'广告商渠道推广支持追踪在第三方广告商投放广告下载App用户的场景,例如在今日头条渠道投放下载App广告,追踪通过在渠道下载激活App的用户。',
|
||||
};
|
|
@ -1,11 +0,0 @@
|
|||
import Mock from 'mockjs';
|
||||
import setupMock, { successResponseWrap } from '@/utils/setup-mock';
|
||||
|
||||
setupMock({
|
||||
setup() {
|
||||
// submit
|
||||
Mock.mock(new RegExp('/api/channel-form/submit'), () => {
|
||||
return successResponseWrap('ok');
|
||||
});
|
||||
},
|
||||
});
|
|
@ -1,84 +0,0 @@
|
|||
<template>
|
||||
<div class="banner">
|
||||
<div class="banner-inner">
|
||||
<a-carousel class="carousel" animation-name="fade">
|
||||
<a-carousel-item v-for="item in carouselItem" :key="item.slogan">
|
||||
<div :key="item.slogan" class="carousel-item">
|
||||
<div class="carousel-title">{{ item.slogan }}</div>
|
||||
<div class="carousel-sub-title">{{ item.subSlogan }}</div>
|
||||
<img class="carousel-image" :src="item.image" />
|
||||
</div>
|
||||
</a-carousel-item>
|
||||
</a-carousel>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import bannerImage from '@/assets/images/login-banner.png';
|
||||
|
||||
const { t } = useI18n();
|
||||
const carouselItem = computed(() => [
|
||||
{
|
||||
slogan: t('login.banner.slogan1'),
|
||||
subSlogan: t('login.banner.subSlogan1'),
|
||||
image: bannerImage,
|
||||
},
|
||||
{
|
||||
slogan: t('login.banner.slogan2'),
|
||||
subSlogan: t('login.banner.subSlogan2'),
|
||||
image: bannerImage,
|
||||
},
|
||||
{
|
||||
slogan: t('login.banner.slogan3'),
|
||||
subSlogan: t('login.banner.subSlogan3'),
|
||||
image: bannerImage,
|
||||
},
|
||||
]);
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.banner {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
&-inner {
|
||||
flex: 1;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.carousel {
|
||||
height: 100%;
|
||||
|
||||
&-item {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
&-title {
|
||||
color: var(--color-fill-1);
|
||||
font-weight: 500;
|
||||
font-size: 20px;
|
||||
line-height: 28px;
|
||||
}
|
||||
|
||||
&-sub-title {
|
||||
margin-top: 8px;
|
||||
color: var(--color-text-3);
|
||||
font-size: 14px;
|
||||
line-height: 22px;
|
||||
}
|
||||
|
||||
&-image {
|
||||
width: 320px;
|
||||
margin-top: 30px;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,165 +0,0 @@
|
|||
<template>
|
||||
<div class="login-form-wrapper">
|
||||
<div class="login-form-title">{{ $t('login.form.title') }}</div>
|
||||
<div class="login-form-sub-title">{{ $t('login.form.title') }}</div>
|
||||
<div class="login-form-error-msg">{{ errorMessage }}</div>
|
||||
<a-form
|
||||
ref="loginForm"
|
||||
:model="userInfo"
|
||||
class="login-form"
|
||||
layout="vertical"
|
||||
@submit="handleSubmit"
|
||||
>
|
||||
<a-form-item
|
||||
field="username"
|
||||
:rules="[{ required: true, message: $t('login.form.userName.errMsg') }]"
|
||||
:validate-trigger="['change', 'blur']"
|
||||
hide-label
|
||||
>
|
||||
<a-input
|
||||
v-model="userInfo.username"
|
||||
:placeholder="$t('login.form.userName.placeholder')"
|
||||
>
|
||||
<template #prefix>
|
||||
<icon-user />
|
||||
</template>
|
||||
</a-input>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
field="password"
|
||||
:rules="[{ required: true, message: $t('login.form.password.errMsg') }]"
|
||||
:validate-trigger="['change', 'blur']"
|
||||
hide-label
|
||||
>
|
||||
<a-input-password
|
||||
v-model="userInfo.password"
|
||||
:placeholder="$t('login.form.password.placeholder')"
|
||||
allow-clear
|
||||
>
|
||||
<template #prefix>
|
||||
<icon-lock />
|
||||
</template>
|
||||
</a-input-password>
|
||||
</a-form-item>
|
||||
<a-space :size="16" direction="vertical">
|
||||
<div class="login-form-password-actions">
|
||||
<a-checkbox
|
||||
checked="rememberPassword"
|
||||
:model-value="loginConfig.rememberPassword"
|
||||
@change="setRememberPassword as any"
|
||||
>
|
||||
{{ $t('login.form.rememberPassword') }}
|
||||
</a-checkbox>
|
||||
<a-link>{{ $t('login.form.forgetPassword') }}</a-link>
|
||||
</div>
|
||||
<a-button type="primary" html-type="submit" long :loading="loading">
|
||||
{{ $t('login.form.login') }}
|
||||
</a-button>
|
||||
<a-button type="text" long class="login-form-register-btn">
|
||||
{{ $t('login.form.register') }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</a-form>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive } from 'vue';
|
||||
import { useRouter } from 'vue-router';
|
||||
import { Message } from '@arco-design/web-vue';
|
||||
import { ValidatedError } from '@arco-design/web-vue/es/form/interface';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { useStorage } from '@vueuse/core';
|
||||
import { useUserStore } from '@/store';
|
||||
import useLoading from '@/hooks/loading';
|
||||
import type { LoginData } from '@/api/user';
|
||||
|
||||
const router = useRouter();
|
||||
const { t } = useI18n();
|
||||
const errorMessage = ref('');
|
||||
const { loading, setLoading } = useLoading();
|
||||
const userStore = useUserStore();
|
||||
|
||||
const loginConfig = useStorage('login-config', {
|
||||
rememberPassword: true,
|
||||
username: 'admin', // 演示默认值
|
||||
password: 'admin', // demo default value
|
||||
});
|
||||
const userInfo = reactive({
|
||||
username: loginConfig.value.username,
|
||||
password: loginConfig.value.password,
|
||||
});
|
||||
|
||||
const handleSubmit = async ({
|
||||
errors,
|
||||
values,
|
||||
}: {
|
||||
errors: Record<string, ValidatedError> | undefined;
|
||||
values: Record<string, any>;
|
||||
}) => {
|
||||
if (loading.value) return;
|
||||
if (!errors) {
|
||||
setLoading(true);
|
||||
try {
|
||||
await userStore.login(values as LoginData);
|
||||
const { redirect, ...othersQuery } = router.currentRoute.value.query;
|
||||
router.push({
|
||||
name: (redirect as string) || 'Workplace',
|
||||
query: {
|
||||
...othersQuery,
|
||||
},
|
||||
});
|
||||
Message.success(t('login.form.login.success'));
|
||||
const { rememberPassword } = loginConfig.value;
|
||||
const { username, password } = values;
|
||||
// 实际生产环境需要进行加密存储。
|
||||
// The actual production environment requires encrypted storage.
|
||||
loginConfig.value.username = rememberPassword ? username : '';
|
||||
loginConfig.value.password = rememberPassword ? password : '';
|
||||
} catch (err) {
|
||||
errorMessage.value = (err as Error).message;
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
}
|
||||
};
|
||||
const setRememberPassword = (value: boolean) => {
|
||||
loginConfig.value.rememberPassword = value;
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.login-form {
|
||||
&-wrapper {
|
||||
width: 320px;
|
||||
}
|
||||
|
||||
&-title {
|
||||
color: var(--color-text-1);
|
||||
font-weight: 500;
|
||||
font-size: 24px;
|
||||
line-height: 32px;
|
||||
}
|
||||
|
||||
&-sub-title {
|
||||
color: var(--color-text-3);
|
||||
font-size: 16px;
|
||||
line-height: 24px;
|
||||
}
|
||||
|
||||
&-error-msg {
|
||||
height: 32px;
|
||||
color: rgb(var(--red-6));
|
||||
line-height: 32px;
|
||||
}
|
||||
|
||||
&-password-actions {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
&-register-btn {
|
||||
color: var(--color-text-3) !important;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,81 +0,0 @@
|
|||
<template>
|
||||
<div class="container">
|
||||
<div class="logo">
|
||||
<img
|
||||
alt="logo"
|
||||
src="//p3-armor.byteimg.com/tos-cn-i-49unhts6dw/dfdba5317c0c20ce20e64fac803d52bc.svg~tplv-49unhts6dw-image.image"
|
||||
/>
|
||||
<div class="logo-text">Arco Design Pro</div>
|
||||
</div>
|
||||
<LoginBanner />
|
||||
<div class="content">
|
||||
<div class="content-inner">
|
||||
<LoginForm />
|
||||
</div>
|
||||
<div class="footer">
|
||||
<Footer />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import Footer from '@/components/footer/index.vue';
|
||||
import LoginBanner from './components/banner.vue';
|
||||
import LoginForm from './components/login-form.vue';
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.container {
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
|
||||
.banner {
|
||||
width: 550px;
|
||||
background: linear-gradient(163.85deg, #1d2129 0%, #00308f 100%);
|
||||
}
|
||||
|
||||
.content {
|
||||
position: relative;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding-bottom: 40px;
|
||||
}
|
||||
|
||||
.footer {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.logo {
|
||||
position: fixed;
|
||||
top: 24px;
|
||||
left: 22px;
|
||||
z-index: 1;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
|
||||
&-text {
|
||||
margin-right: 4px;
|
||||
margin-left: 4px;
|
||||
color: var(--color-fill-1);
|
||||
font-size: 20px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="less" scoped>
|
||||
// responsive
|
||||
@media (max-width: @screen-lg) {
|
||||
.container {
|
||||
.banner {
|
||||
width: 25%;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,21 +0,0 @@
|
|||
export default {
|
||||
'login.form.title': 'Login to Arco Design Pro',
|
||||
'login.form.userName.errMsg': 'Username cannot be empty',
|
||||
'login.form.password.errMsg': 'Password cannot be empty',
|
||||
'login.form.login.errMsg': 'Login error, refresh and try again',
|
||||
'login.form.login.success': 'welcome to use',
|
||||
'login.form.userName.placeholder': 'Username: admin',
|
||||
'login.form.password.placeholder': 'Password: admin',
|
||||
'login.form.rememberPassword': 'Remember password',
|
||||
'login.form.forgetPassword': 'Forgot password',
|
||||
'login.form.login': 'login',
|
||||
'login.form.register': 'register account',
|
||||
'login.banner.slogan1': 'Out-of-the-box high-quality template',
|
||||
'login.banner.subSlogan1':
|
||||
'Rich page templates, covering most typical business scenarios',
|
||||
'login.banner.slogan2': 'Built-in solutions to common problems',
|
||||
'login.banner.subSlogan2':
|
||||
'Internationalization, routing configuration, state management everything',
|
||||
'login.banner.slogan3': 'Access visualization enhancement tool AUX',
|
||||
'login.banner.subSlogan3': 'Realize flexible block development',
|
||||
};
|
|
@ -1,19 +0,0 @@
|
|||
export default {
|
||||
'login.form.title': '登录 Arco Design Pro',
|
||||
'login.form.userName.errMsg': '用户名不能为空',
|
||||
'login.form.password.errMsg': '密码不能为空',
|
||||
'login.form.login.errMsg': '登录出错,轻刷新重试',
|
||||
'login.form.login.success': '欢迎使用',
|
||||
'login.form.userName.placeholder': '用户名:admin',
|
||||
'login.form.password.placeholder': '密码:admin',
|
||||
'login.form.rememberPassword': '记住密码',
|
||||
'login.form.forgetPassword': '忘记密码',
|
||||
'login.form.login': '登录',
|
||||
'login.form.register': '注册账号',
|
||||
'login.banner.slogan1': '开箱即用的高质量模板',
|
||||
'login.banner.subSlogan1': '丰富的的页面模板,覆盖大多数典型业务场景',
|
||||
'login.banner.slogan2': '内置了常见问题的解决方案',
|
||||
'login.banner.subSlogan2': '国际化,路由配置,状态管理应有尽有',
|
||||
'login.banner.slogan3': '接入可视化增强工具AUX',
|
||||
'login.banner.subSlogan3': '实现灵活的区块式开发',
|
||||
};
|
|
@ -1,77 +0,0 @@
|
|||
<template>
|
||||
<a-card class="general-card">
|
||||
<template #title>
|
||||
{{ $t('basicProfile.title.operationLog') }}
|
||||
</template>
|
||||
<a-spin :loading="loading" style="width: 100%">
|
||||
<a-table :data="renderData">
|
||||
<template #columns>
|
||||
<a-table-column
|
||||
:title="$t('basicProfile.column.contentNumber')"
|
||||
data-index="contentNumber"
|
||||
/>
|
||||
<a-table-column
|
||||
:title="$t('basicProfile.column.updateContent')"
|
||||
data-index="updateContent"
|
||||
/>
|
||||
<a-table-column
|
||||
:title="$t('basicProfile.column.status')"
|
||||
data-index="status"
|
||||
>
|
||||
<template #cell="{ record }">
|
||||
<p v-if="record.status === 0">
|
||||
<span class="circle"></span>
|
||||
<span>{{ $t('basicProfile.cell.auditing') }}</span>
|
||||
</p>
|
||||
<p v-if="record.status === 1">
|
||||
<span class="circle pass"></span>
|
||||
<span>{{ $t('basicProfile.cell.pass') }}</span>
|
||||
</p>
|
||||
</template>
|
||||
</a-table-column>
|
||||
<a-table-column
|
||||
:title="$t('basicProfile.column.updateTime')"
|
||||
data-index="updateTime"
|
||||
/>
|
||||
<a-table-column :title="$t('basicProfile.column.operation')">
|
||||
<template #cell>
|
||||
<a-button type="text">{{
|
||||
$t('basicProfile.cell.view')
|
||||
}}</a-button>
|
||||
</template>
|
||||
</a-table-column>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-spin>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { queryOperationLog, operationLogRes } from '@/api/profile';
|
||||
import useLoading from '@/hooks/loading';
|
||||
|
||||
const { loading, setLoading } = useLoading(true);
|
||||
const renderData = ref<operationLogRes>([]);
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
const { data } = await queryOperationLog();
|
||||
renderData.value = data;
|
||||
} catch (err) {
|
||||
// you can report use errorHandler or other
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
fetchData();
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
:deep(.arco-table-th) {
|
||||
&:last-child {
|
||||
.arco-table-th-item-title {
|
||||
margin-left: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,148 +0,0 @@
|
|||
<template>
|
||||
<div class="item-container">
|
||||
<a-space :size="16" direction="vertical" fill>
|
||||
<a-descriptions
|
||||
v-for="(item, idx) in blockDataList"
|
||||
:key="idx"
|
||||
:label-style="{
|
||||
textAlign: 'right',
|
||||
width: '200px',
|
||||
paddingRight: '10px',
|
||||
color: 'rgb(var(--gray-8))',
|
||||
}"
|
||||
:value-style="{ width: '400px' }"
|
||||
:title="item.title"
|
||||
:data="item.data"
|
||||
>
|
||||
<template #value="{ value }">
|
||||
<a-skeleton v-if="loading" :animation="true">
|
||||
<a-skeleton-line :widths="['200px']" :rows="1" />
|
||||
</a-skeleton>
|
||||
<span v-else>{{ value }}</span>
|
||||
</template>
|
||||
</a-descriptions>
|
||||
</a-space>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, PropType } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { ProfileBasicRes } from '@/api/profile';
|
||||
|
||||
type BlockList = {
|
||||
title: string;
|
||||
data: {
|
||||
label: string;
|
||||
value: string;
|
||||
}[];
|
||||
}[];
|
||||
|
||||
const props = defineProps({
|
||||
type: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
renderData: {
|
||||
type: Object as PropType<ProfileBasicRes>,
|
||||
required: true,
|
||||
},
|
||||
loading: {
|
||||
type: Boolean,
|
||||
default: false,
|
||||
},
|
||||
});
|
||||
const { t } = useI18n();
|
||||
const blockDataList = computed<BlockList>(() => {
|
||||
const { renderData } = props;
|
||||
const result = [];
|
||||
result.push({
|
||||
title:
|
||||
props.type === 'pre'
|
||||
? t('basicProfile.title.preVideo')
|
||||
: t('basicProfile.title.video'),
|
||||
data: [
|
||||
{
|
||||
label: t('basicProfile.label.video.mode'),
|
||||
value: renderData?.video?.mode || '-',
|
||||
},
|
||||
{
|
||||
label: t('basicProfile.label.video.acquisition.resolution'),
|
||||
value: renderData?.video?.acquisition.resolution || '-',
|
||||
},
|
||||
{
|
||||
label: t('basicProfile.label.video.acquisition.frameRate'),
|
||||
value: `${renderData?.video?.acquisition.frameRate || '-'} fps`,
|
||||
},
|
||||
{
|
||||
label: t('basicProfile.label.video.encoding.resolution'),
|
||||
value: renderData?.video?.encoding.resolution || '-',
|
||||
},
|
||||
{
|
||||
label: t('basicProfile.label.video.encoding.rate.min'),
|
||||
value: `${renderData?.video?.encoding.rate.min || '-'} bps`,
|
||||
},
|
||||
{
|
||||
label: t('basicProfile.label.video.encoding.rate.max'),
|
||||
value: `${renderData?.video?.encoding.rate.max || '-'} bps`,
|
||||
},
|
||||
{
|
||||
label: t('basicProfile.label.video.encoding.rate.default'),
|
||||
value: `${renderData?.video?.encoding.rate.default || '-'} bps`,
|
||||
},
|
||||
{
|
||||
label: t('basicProfile.label.video.encoding.frameRate'),
|
||||
value: `${renderData?.video?.encoding.frameRate || '-'} fpx`,
|
||||
},
|
||||
{
|
||||
label: t('basicProfile.label.video.encoding.profile'),
|
||||
value: renderData?.video?.encoding.profile || '-',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
result.push({
|
||||
title:
|
||||
props.type === 'pre'
|
||||
? t('basicProfile.title.preAudio')
|
||||
: t('basicProfile.title.audio'),
|
||||
data: [
|
||||
{
|
||||
label: t('basicProfile.label.audio.mode'),
|
||||
value: renderData?.audio?.mode || '-',
|
||||
},
|
||||
{
|
||||
label: t('basicProfile.label.audio.acquisition.channels'),
|
||||
value: `${renderData?.audio?.acquisition.channels || '-'} ${t(
|
||||
'basicProfile.unit.audio.channels'
|
||||
)}`,
|
||||
},
|
||||
{
|
||||
label: t('basicProfile.label.audio.encoding.channels'),
|
||||
value: `${renderData?.audio?.encoding.channels || '-'} ${t(
|
||||
'basicProfile.unit.audio.channels'
|
||||
)}`,
|
||||
},
|
||||
{
|
||||
label: t('basicProfile.label.audio.encoding.rate'),
|
||||
value: `${renderData?.audio?.encoding.rate || '-'} kbps`,
|
||||
},
|
||||
{
|
||||
label: t('basicProfile.label.audio.encoding.profile'),
|
||||
value: renderData?.audio?.encoding.profile || '-',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
return result;
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.item-container {
|
||||
padding-top: 20px;
|
||||
:deep(.arco-descriptions-item-label) {
|
||||
font-weight: normal;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,83 +0,0 @@
|
|||
<template>
|
||||
<div class="container">
|
||||
<Breadcrumb :items="['menu.profile', 'menu.profile.basic']" />
|
||||
<a-space direction="vertical" :size="16" fill>
|
||||
<a-card class="general-card" :title="$t('basicProfile.title.form')">
|
||||
<template #extra>
|
||||
<a-space>
|
||||
<a-button>{{ $t('basicProfile.cancel') }}</a-button>
|
||||
<a-button type="primary">
|
||||
{{ $t('basicProfile.goBack') }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
<a-steps v-model:current="step" line-less class="steps">
|
||||
<a-step>{{ $t('basicProfile.steps.commit') }}</a-step>
|
||||
<a-step>{{ $t('basicProfile.steps.approval') }}</a-step>
|
||||
<a-step>{{ $t('basicProfile.steps.finish') }}</a-step>
|
||||
</a-steps>
|
||||
</a-card>
|
||||
<a-card class="general-card">
|
||||
<ProfileItem :loading="loading" :render-data="currentData" />
|
||||
</a-card>
|
||||
<a-card class="general-card">
|
||||
<ProfileItem :loading="preLoading" type="pre" :render-data="preData" />
|
||||
</a-card>
|
||||
<OperationLog />
|
||||
</a-space>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import useLoading from '@/hooks/loading';
|
||||
import { queryProfileBasic, ProfileBasicRes } from '@/api/profile';
|
||||
import ProfileItem from './components/profile-item.vue';
|
||||
import OperationLog from './components/operation-log.vue';
|
||||
|
||||
const { loading, setLoading } = useLoading(true);
|
||||
const { loading: preLoading, setLoading: preSetLoading } = useLoading(true);
|
||||
const currentData = ref<ProfileBasicRes>({} as ProfileBasicRes);
|
||||
const preData = ref<ProfileBasicRes>({} as ProfileBasicRes);
|
||||
const step = ref(1);
|
||||
const fetchCurrentData = async () => {
|
||||
try {
|
||||
const { data } = await queryProfileBasic();
|
||||
currentData.value = data;
|
||||
step.value = 2;
|
||||
} catch (err) {
|
||||
// you can report use errorHandler or other
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
const fetchPreData = async () => {
|
||||
try {
|
||||
const { data } = await queryProfileBasic();
|
||||
preData.value = data;
|
||||
} catch (err) {
|
||||
// you can report use errorHandler or other
|
||||
} finally {
|
||||
preSetLoading(false);
|
||||
}
|
||||
};
|
||||
fetchCurrentData();
|
||||
fetchPreData();
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'Basic',
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.container {
|
||||
padding: 0 20px 20px 20px;
|
||||
}
|
||||
|
||||
.steps {
|
||||
max-width: 548px;
|
||||
margin: 0 auto 10px;
|
||||
}
|
||||
</style>
|
|
@ -1,39 +0,0 @@
|
|||
export default {
|
||||
'menu.profile.basic': 'Basic Profile',
|
||||
'basicProfile.title.form': 'Parameter Approval Process Table',
|
||||
'basicProfile.steps.commit': 'Commit',
|
||||
'basicProfile.steps.approval': 'Approval',
|
||||
'basicProfile.steps.finish': 'Finish',
|
||||
'basicProfile.title.currentParams': 'Current Parameters',
|
||||
'basicProfile.title.originParams': 'Original Parameters',
|
||||
'basicProfile.title.video': 'Video Parameters',
|
||||
'basicProfile.title.audio': 'Audio Parameters',
|
||||
'basicProfile.title.preVideo': 'Original video parameters',
|
||||
'basicProfile.title.preAudio': 'Original audio parameters',
|
||||
'basicProfile.label.video.mode': 'Match Mode',
|
||||
'basicProfile.label.video.acquisition.resolution': 'Acquisition Resolution',
|
||||
'basicProfile.label.video.acquisition.frameRate': 'Acquisition Frame Rate',
|
||||
'basicProfile.label.video.encoding.resolution': 'Encoding Resolution',
|
||||
'basicProfile.label.video.encoding.rate.min': 'Encoding Min Rate',
|
||||
'basicProfile.label.video.encoding.rate.max': 'Encoding Max Rate',
|
||||
'basicProfile.label.video.encoding.rate.default': 'Encoding Default Rate',
|
||||
'basicProfile.label.video.encoding.frameRate': 'Encoding Frame Rate',
|
||||
'basicProfile.label.video.encoding.profile': 'Encoding Profile',
|
||||
'basicProfile.label.audio.mode': 'Match Mode',
|
||||
'basicProfile.label.audio.acquisition.channels': 'Acquisition Channels',
|
||||
'basicProfile.label.audio.encoding.channels': 'Encoding Channels',
|
||||
'basicProfile.label.audio.encoding.rate': 'Encoding Rate',
|
||||
'basicProfile.label.audio.encoding.profile': 'Encoding Profile',
|
||||
'basicProfile.unit.audio.channels': 'channels',
|
||||
'basicProfile.goBack': 'GoBack',
|
||||
'basicProfile.cancel': 'Cancel Process',
|
||||
'basicProfile.title.operationLog': 'Operation Log',
|
||||
'basicProfile.column.contentNumber': 'Content Number',
|
||||
'basicProfile.column.updateContent': 'Update Content',
|
||||
'basicProfile.column.status': 'Status',
|
||||
'basicProfile.column.updateTime': 'Update Time',
|
||||
'basicProfile.column.operation': 'Operation',
|
||||
'basicProfile.cell.pass': 'Pass',
|
||||
'basicProfile.cell.auditing': 'Auditing',
|
||||
'basicProfile.cell.view': 'View',
|
||||
};
|
|
@ -1,39 +0,0 @@
|
|||
export default {
|
||||
'menu.profile.basic': '基础详情页',
|
||||
'basicProfile.title.form': '参数审批流程表',
|
||||
'basicProfile.steps.commit': '提交修改',
|
||||
'basicProfile.steps.approval': '审批中',
|
||||
'basicProfile.steps.finish': '修改完成',
|
||||
'basicProfile.title.currentParams': '修改后参数',
|
||||
'basicProfile.title.originParams': '原参数',
|
||||
'basicProfile.title.video': '现视频参数',
|
||||
'basicProfile.title.preVideo': '原视频参数',
|
||||
'basicProfile.title.audio': '现音频参数',
|
||||
'basicProfile.title.preAudio': '原音频参数',
|
||||
'basicProfile.label.video.mode': '匹配模式',
|
||||
'basicProfile.label.video.acquisition.resolution': '采集分辨率',
|
||||
'basicProfile.label.video.acquisition.frameRate': '采集帧率',
|
||||
'basicProfile.label.video.encoding.resolution': '编码分辨率',
|
||||
'basicProfile.label.video.encoding.rate.min': '编码码率最小值',
|
||||
'basicProfile.label.video.encoding.rate.max': '编码码率最大值',
|
||||
'basicProfile.label.video.encoding.rate.default': '编码码率默认值',
|
||||
'basicProfile.label.video.encoding.frameRate': '编码帧率',
|
||||
'basicProfile.label.video.encoding.profile': '编码profile',
|
||||
'basicProfile.label.audio.mode': '匹配模式',
|
||||
'basicProfile.label.audio.acquisition.channels': '采集声道数',
|
||||
'basicProfile.label.audio.encoding.channels': '编码声道数',
|
||||
'basicProfile.label.audio.encoding.rate': '编码码率',
|
||||
'basicProfile.label.audio.encoding.profile': '编码 profile',
|
||||
'basicProfile.unit.audio.channels': '声道',
|
||||
'basicProfile.goBack': '返回',
|
||||
'basicProfile.cancel': '取消流程',
|
||||
'basicProfile.title.operationLog': '参数调整记录',
|
||||
'basicProfile.column.contentNumber': '内容编号',
|
||||
'basicProfile.column.updateContent': '调整内容',
|
||||
'basicProfile.column.status': '当前状态',
|
||||
'basicProfile.column.updateTime': '修改时间',
|
||||
'basicProfile.column.operation': '操作',
|
||||
'basicProfile.cell.pass': '已通过',
|
||||
'basicProfile.cell.auditing': '审核中',
|
||||
'basicProfile.cell.view': '查看',
|
||||
};
|
|
@ -1,58 +0,0 @@
|
|||
import Mock from 'mockjs';
|
||||
import setupMock, { successResponseWrap } from '@/utils/setup-mock';
|
||||
|
||||
setupMock({
|
||||
setup() {
|
||||
Mock.mock(new RegExp('/api/profile/basic'), () => {
|
||||
return successResponseWrap({
|
||||
status: 2,
|
||||
video: {
|
||||
mode: '自定义',
|
||||
acquisition: {
|
||||
resolution: '720*1280',
|
||||
frameRate: 15,
|
||||
},
|
||||
encoding: {
|
||||
resolution: '720*1280',
|
||||
rate: {
|
||||
min: 300,
|
||||
max: 800,
|
||||
default: 1500,
|
||||
},
|
||||
frameRate: 15,
|
||||
profile: 'high',
|
||||
},
|
||||
},
|
||||
audio: {
|
||||
mode: '自定义',
|
||||
acquisition: {
|
||||
channels: 8,
|
||||
},
|
||||
encoding: {
|
||||
channels: 8,
|
||||
rate: 128,
|
||||
profile: 'ACC-LC',
|
||||
},
|
||||
},
|
||||
});
|
||||
});
|
||||
Mock.mock(new RegExp('/api/operation/log'), () => {
|
||||
return successResponseWrap([
|
||||
{
|
||||
key: '1',
|
||||
contentNumber: '视频类001003',
|
||||
updateContent: '视频参数变更',
|
||||
status: 0,
|
||||
updateTime: '2021-02-28 10:30:50',
|
||||
},
|
||||
{
|
||||
key: '2',
|
||||
contentNumber: '视频类058212',
|
||||
updateContent: '视频参数变更;音频参数变更',
|
||||
status: 1,
|
||||
updateTime: '2020-05-13 08:00:00',
|
||||
},
|
||||
]);
|
||||
});
|
||||
},
|
||||
});
|
|
@ -1,77 +0,0 @@
|
|||
<template>
|
||||
<div class="container">
|
||||
<Breadcrumb :items="['menu.result', 'menu.result.error']" />
|
||||
<div class="wrapper">
|
||||
<a-result
|
||||
class="result"
|
||||
status="error"
|
||||
:title="$t('error.result.title')"
|
||||
:subtitle="$t('error.result.subTitle')"
|
||||
>
|
||||
<template #extra>
|
||||
<a-space class="operation-wrap" :size="16">
|
||||
<a-button key="again" type="secondary">
|
||||
{{ $t('error.result.goBack') }}
|
||||
</a-button>
|
||||
<a-button key="back" type="primary">
|
||||
{{ $t('error.result.retry') }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-result>
|
||||
|
||||
<div class="details-wrapper">
|
||||
<a-typography-title :heading="6" style="margin-top: 0">
|
||||
{{ $t('error.detailTitle') }}
|
||||
</a-typography-title>
|
||||
<a-typography-paragraph style="margin-bottom: 0">
|
||||
<ol>
|
||||
<li>
|
||||
{{ $t('error.detailLine.record') }}
|
||||
<a-link>
|
||||
<IconLink />
|
||||
{{ $t('error.detailLine.record.link') }}
|
||||
</a-link>
|
||||
</li>
|
||||
<li>{{ $t('error.detailLine.auth') }}</li>
|
||||
</ol>
|
||||
</a-typography-paragraph>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup></script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'Error',
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.container {
|
||||
padding: 0 20px 20px 20px;
|
||||
}
|
||||
.wrapper {
|
||||
padding: 24px 150px;
|
||||
background-color: var(--color-bg-2);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.result {
|
||||
margin: 150px 0 36px 0;
|
||||
}
|
||||
|
||||
.operation-wrap {
|
||||
margin-bottom: 40px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.details-wrapper {
|
||||
width: 100%;
|
||||
margin-bottom: 150px;
|
||||
padding: 20px;
|
||||
background-color: rgb(var(--gray-1));
|
||||
}
|
||||
</style>
|
|
@ -1,13 +0,0 @@
|
|||
export default {
|
||||
'menu.result.error': 'Error',
|
||||
'error.result.title': 'Submit Error',
|
||||
'error.result.subTitle': 'Submit form error',
|
||||
'error.result.goBack': 'Go Back',
|
||||
'error.result.retry': 'return for correction',
|
||||
'error.detailTitle': 'Details of Error',
|
||||
'error.detailLine.record':
|
||||
'The current domain name has not been registered, please check the registration process: ',
|
||||
'error.detailLine.record.link': 'Registration Process',
|
||||
'error.detailLine.auth':
|
||||
'Your user group does not have the authority to perform this operation;',
|
||||
};
|
|
@ -1,11 +0,0 @@
|
|||
export default {
|
||||
'menu.result.error': '失败页',
|
||||
'error.result.title': '提交失败',
|
||||
'error.result.subTitle': '表单提交失败,请重试。',
|
||||
'error.result.goBack': '回到首页',
|
||||
'error.result.retry': '返回修改',
|
||||
'error.detailTitle': '错误详情',
|
||||
'error.detailLine.record': '当前域名未备案,备案流程请查看:',
|
||||
'error.detailLine.record.link': '备案流程',
|
||||
'error.detailLine.auth': '你的用户组不具有进行此操作的权限;',
|
||||
};
|
|
@ -1,99 +0,0 @@
|
|||
<template>
|
||||
<div class="container">
|
||||
<Breadcrumb :items="['menu.result', 'menu.result.success']" />
|
||||
<div class="wrapper">
|
||||
<a-result
|
||||
class="result"
|
||||
status="success"
|
||||
:title="$t('success.result.title')"
|
||||
:subtitle="$t('success.result.subTitle')"
|
||||
>
|
||||
<template #extra>
|
||||
<a-space class="operation-wrap" :size="16">
|
||||
<a-button key="again" type="secondary">
|
||||
{{ $t('success.result.printResult') }}
|
||||
</a-button>
|
||||
<a-button key="back" type="primary">
|
||||
{{ $t('success.result.projectList') }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-result>
|
||||
|
||||
<div class="steps-wrapper">
|
||||
<a-typography-paragraph bold>{{
|
||||
$t('success.result.progress')
|
||||
}}</a-typography-paragraph>
|
||||
<a-steps type="dot" :current="2">
|
||||
<a-step
|
||||
:title="$t('success.submitApplication')"
|
||||
description="2020/10/10 14:00:39"
|
||||
/>
|
||||
<a-step
|
||||
:title="$t('success.leaderReview')"
|
||||
:description="$t('success.processing')"
|
||||
/>
|
||||
<a-step
|
||||
:title="$t('success.purchaseCertificate')"
|
||||
:description="$t('success.waiting')"
|
||||
/>
|
||||
<a-step
|
||||
:title="$t('success.safetyTest')"
|
||||
:description="$t('success.waiting')"
|
||||
/>
|
||||
<a-step
|
||||
:title="$t('success.launched')"
|
||||
:description="$t('success.waiting')"
|
||||
/>
|
||||
</a-steps>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup></script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'Success',
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.container {
|
||||
padding: 0 20px 20px 20px;
|
||||
}
|
||||
.wrapper {
|
||||
padding: 24px 150px;
|
||||
background-color: var(--color-bg-2);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.result {
|
||||
margin: 150px 0 0 0;
|
||||
}
|
||||
|
||||
.operation-wrap {
|
||||
margin-bottom: 40px;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.steps-wrapper {
|
||||
width: 100%;
|
||||
min-width: fit-content;
|
||||
margin-bottom: 150px;
|
||||
padding: 20px;
|
||||
background-color: rgb(var(--gray-1));
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.mobile {
|
||||
.wrapper {
|
||||
padding: 24px 10px;
|
||||
.steps-wrapper {
|
||||
transform: scale(0.8);
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,15 +0,0 @@
|
|||
export default {
|
||||
'menu.result.success': 'Success',
|
||||
'success.result.title': 'Submit Success',
|
||||
'success.result.subTitle': 'Submit form success!',
|
||||
'success.result.printResult': 'Print result',
|
||||
'success.result.projectList': 'Project List',
|
||||
'success.result.progress': 'Progress',
|
||||
'success.submitApplication': 'Submit Application',
|
||||
'success.leaderReview': 'Leader Review',
|
||||
'success.purchaseCertificate': 'Purchase Certificate',
|
||||
'success.safetyTest': 'Safety Test',
|
||||
'success.launched': 'Officially launched',
|
||||
'success.waiting': 'Waiting',
|
||||
'success.processing': 'Processing',
|
||||
};
|
|
@ -1,15 +0,0 @@
|
|||
export default {
|
||||
'menu.result.success': '成功页',
|
||||
'success.result.title': '提交成功',
|
||||
'success.result.subTitle': '表单提交成功!',
|
||||
'success.result.printResult': '打印结果',
|
||||
'success.result.projectList': '返回项目列表',
|
||||
'success.result.progress': '当前进度',
|
||||
'success.submitApplication': '提交申请',
|
||||
'success.leaderReview': '直属领导审核',
|
||||
'success.purchaseCertificate': '购买证书',
|
||||
'success.safetyTest': '安全测试',
|
||||
'success.launched': '正式上线',
|
||||
'success.waiting': '未开始',
|
||||
'success.processing': '进行中',
|
||||
};
|
|
@ -1,88 +0,0 @@
|
|||
<template>
|
||||
<a-card class="general-card" :title="$t('userInfo.title.latestActivity')">
|
||||
<template #extra>
|
||||
<a-link>{{ $t('userInfo.viewAll') }}</a-link>
|
||||
</template>
|
||||
<a-list :bordered="false">
|
||||
<a-list-item
|
||||
v-for="activity in activityList"
|
||||
:key="activity.id"
|
||||
action-layout="horizontal"
|
||||
>
|
||||
<a-skeleton
|
||||
v-if="loading"
|
||||
:loading="loading"
|
||||
:animation="true"
|
||||
class="skeleton-item"
|
||||
>
|
||||
<a-row :gutter="6">
|
||||
<a-col :span="2">
|
||||
<a-skeleton-shape shape="circle" />
|
||||
</a-col>
|
||||
<a-col :span="22">
|
||||
<a-skeleton-line :widths="['40%', '100%']" :rows="2" />
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-skeleton>
|
||||
<a-list-item-meta
|
||||
v-else
|
||||
:title="activity.title"
|
||||
:description="activity.description"
|
||||
>
|
||||
<template #avatar>
|
||||
<a-avatar>
|
||||
<img :src="activity.avatar" />
|
||||
</a-avatar>
|
||||
</template>
|
||||
</a-list-item-meta>
|
||||
</a-list-item>
|
||||
</a-list>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { queryLatestActivity, LatestActivity } from '@/api/user-center';
|
||||
import useLoading from '@/hooks/loading';
|
||||
|
||||
const { loading, setLoading } = useLoading(true);
|
||||
const activityList = ref<LatestActivity[]>(new Array(7).fill({}));
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
const { data } = await queryLatestActivity();
|
||||
activityList.value = data;
|
||||
} catch (err) {
|
||||
// you can report use errorHandler or other
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
fetchData();
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.latest-activity {
|
||||
&-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
}
|
||||
}
|
||||
.general-card :deep(.arco-list-item) {
|
||||
padding-left: 0;
|
||||
border-bottom: none;
|
||||
.arco-list-item-meta-content {
|
||||
flex: 1;
|
||||
padding-bottom: 27px;
|
||||
border-bottom: 1px solid var(--color-neutral-3);
|
||||
}
|
||||
.arco-list-item-meta-avatar {
|
||||
padding-bottom: 27px;
|
||||
}
|
||||
.skeleton-item {
|
||||
margin-top: 10px;
|
||||
padding-bottom: 20px;
|
||||
border-bottom: 1px solid var(--color-neutral-3);
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,27 +0,0 @@
|
|||
<template>
|
||||
<a-card class="general-card" :title="$t('userInfo.title.latestNotification')">
|
||||
<a-skeleton v-if="loading" :animation="true">
|
||||
<a-skeleton-line :rows="3" />
|
||||
</a-skeleton>
|
||||
<a-result v-else status="404">
|
||||
<template #subtitle>
|
||||
{{ $t('userInfo.nodata') }}
|
||||
</template>
|
||||
</a-result>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import useLoading from '@/hooks/loading';
|
||||
|
||||
const { loading, setLoading } = useLoading(true);
|
||||
setTimeout(() => {
|
||||
setLoading(false);
|
||||
}, 500);
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
:deep(.arco-result) {
|
||||
padding: 40px 32px 108px;
|
||||
}
|
||||
</style>
|
|
@ -1,91 +0,0 @@
|
|||
<template>
|
||||
<a-card class="general-card" :title="$t('userInfo.title.myProject')">
|
||||
<template #extra>
|
||||
<a-link>{{ $t('userInfo.showMore') }}</a-link>
|
||||
</template>
|
||||
<a-row :gutter="16">
|
||||
<a-col
|
||||
v-for="(project, index) in projectList"
|
||||
:key="index"
|
||||
:xs="12"
|
||||
:sm="12"
|
||||
:md="12"
|
||||
:lg="12"
|
||||
:xl="8"
|
||||
:xxl="8"
|
||||
class="my-project-item"
|
||||
>
|
||||
<a-card>
|
||||
<a-skeleton v-if="loading" :loading="loading" :animation="true">
|
||||
<a-skeleton-line :rows="3" />
|
||||
</a-skeleton>
|
||||
<a-space v-else direction="vertical">
|
||||
<a-typography-text bold>{{ project.name }}</a-typography-text>
|
||||
<a-typography-text type="secondary">
|
||||
{{ project.description }}
|
||||
</a-typography-text>
|
||||
<a-space>
|
||||
<a-avatar-group :size="24">
|
||||
{{ project.contributors }}
|
||||
<a-avatar
|
||||
v-for="(contributor, idx) in project.contributors"
|
||||
:key="idx"
|
||||
:size="32"
|
||||
>
|
||||
<img alt="avatar" :src="contributor.avatar" />
|
||||
</a-avatar>
|
||||
</a-avatar-group>
|
||||
<a-typography-text type="secondary">
|
||||
等{{ project.peopleNumber }}人
|
||||
</a-typography-text>
|
||||
</a-space>
|
||||
</a-space>
|
||||
</a-card>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { queryMyProjectList, MyProjectRecord } from '@/api/user-center';
|
||||
import useRequest from '@/hooks/request';
|
||||
|
||||
const defaultValue = Array(6).fill({} as MyProjectRecord);
|
||||
const { loading, response: projectList } = useRequest<MyProjectRecord[]>(
|
||||
queryMyProjectList,
|
||||
defaultValue
|
||||
);
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
:deep(.arco-card-body) {
|
||||
min-height: 128px;
|
||||
padding-bottom: 0;
|
||||
}
|
||||
.my-project {
|
||||
&-header {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
&-title {
|
||||
margin-top: 0 !important;
|
||||
margin-bottom: 18px !important;
|
||||
}
|
||||
|
||||
&-list {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
&-item {
|
||||
// padding-right: 16px;
|
||||
margin-bottom: 16px;
|
||||
|
||||
&:last-child {
|
||||
padding-right: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,64 +0,0 @@
|
|||
<template>
|
||||
<a-card
|
||||
class="general-card"
|
||||
:title="$t('userInfo.tab.title.team')"
|
||||
:header-style="{ paddingBottom: '18px' }"
|
||||
:body-style="{ paddingBottom: '12px' }"
|
||||
>
|
||||
<a-list :bordered="false">
|
||||
<a-list-item
|
||||
v-for="team in teamList"
|
||||
:key="team.id"
|
||||
action-layout="horizontal"
|
||||
>
|
||||
<a-skeleton v-if="loading" :loading="loading" :animation="true">
|
||||
<a-row :gutter="6">
|
||||
<a-col :span="6">
|
||||
<a-skeleton-shape shape="circle" />
|
||||
</a-col>
|
||||
<a-col :span="16">
|
||||
<a-skeleton-line :widths="['100%', '40%']" :rows="2" />
|
||||
</a-col>
|
||||
</a-row>
|
||||
</a-skeleton>
|
||||
<a-list-item-meta v-else :title="team.name">
|
||||
<template #avatar>
|
||||
<a-avatar>
|
||||
<img :src="team.avatar" />
|
||||
</a-avatar>
|
||||
</template>
|
||||
<template #description> 共{{ team.peopleNumber }}人 </template>
|
||||
</a-list-item-meta>
|
||||
</a-list-item>
|
||||
</a-list>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { queryMyTeamList, MyTeamRecord } from '@/api/user-center';
|
||||
import useRequest from '@/hooks/request';
|
||||
|
||||
const defaultValue: MyTeamRecord[] = new Array(4).fill({});
|
||||
const { loading, response: teamList } = useRequest<MyTeamRecord[]>(
|
||||
queryMyTeamList,
|
||||
defaultValue
|
||||
);
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.general-card {
|
||||
height: 356px;
|
||||
.arco-list-item {
|
||||
height: 72px;
|
||||
padding-left: 0;
|
||||
padding-bottom: 12px;
|
||||
border-bottom: 1px solid var(--color-neutral-3);
|
||||
&:last-child {
|
||||
border-bottom: none;
|
||||
}
|
||||
.arco-list-item-meta {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,69 +0,0 @@
|
|||
<template>
|
||||
<div class="header">
|
||||
<a-space :size="12" direction="vertical" align="center">
|
||||
<a-avatar :size="64">
|
||||
<template #trigger-icon>
|
||||
<icon-camera />
|
||||
</template>
|
||||
<img :src="userInfo.avatar" />
|
||||
</a-avatar>
|
||||
<a-typography-title :heading="6" style="margin: 0">
|
||||
{{ userInfo.name }}
|
||||
</a-typography-title>
|
||||
<div class="user-msg">
|
||||
<a-space :size="18">
|
||||
<div>
|
||||
<icon-user />
|
||||
<a-typography-text>{{ userInfo.jobName }}</a-typography-text>
|
||||
</div>
|
||||
<div>
|
||||
<icon-home />
|
||||
<a-typography-text>
|
||||
{{ userInfo.organizationName }}
|
||||
</a-typography-text>
|
||||
</div>
|
||||
<div>
|
||||
<icon-location />
|
||||
<a-typography-text>{{ userInfo.locationName }}</a-typography-text>
|
||||
</div>
|
||||
</a-space>
|
||||
</div>
|
||||
</a-space>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { useUserStore } from '@/store';
|
||||
|
||||
const userInfo = useUserStore();
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 204px;
|
||||
color: var(--gray-10);
|
||||
background: url(//p3-armor.byteimg.com/tos-cn-i-49unhts6dw/41c6b125cc2e27021bf7fcc9a9b1897c.svg~tplv-49unhts6dw-image.image)
|
||||
no-repeat;
|
||||
background-size: cover;
|
||||
border-radius: 4px;
|
||||
|
||||
:deep(.arco-avatar-trigger-icon-button) {
|
||||
color: rgb(var(--arcoblue-6));
|
||||
|
||||
:deep(.arco-icon) {
|
||||
vertical-align: -1px;
|
||||
}
|
||||
}
|
||||
.user-msg {
|
||||
.arco-icon {
|
||||
color: rgb(var(--gray-10));
|
||||
}
|
||||
.arco-typography {
|
||||
margin-left: 6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,87 +0,0 @@
|
|||
<template>
|
||||
<div class="container">
|
||||
<Breadcrumb :items="['menu.user', 'menu.user.info']" />
|
||||
<UserInfoHeader />
|
||||
<div class="content">
|
||||
<div class="content-left">
|
||||
<a-grid :cols="24" :col-gap="16" :row-gap="16">
|
||||
<a-grid-item :span="24">
|
||||
<MyProject />
|
||||
</a-grid-item>
|
||||
<a-grid-item :span="24">
|
||||
<LatestActivity />
|
||||
</a-grid-item>
|
||||
</a-grid>
|
||||
</div>
|
||||
<div class="content-right">
|
||||
<a-grid :cols="24" :row-gap="16">
|
||||
<a-grid-item :span="24">
|
||||
<MyTeam />
|
||||
</a-grid-item>
|
||||
<a-grid-item class="panel" :span="24">
|
||||
<LatestNotification />
|
||||
</a-grid-item>
|
||||
</a-grid>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import UserInfoHeader from './components/user-info-header.vue';
|
||||
import LatestNotification from './components/latest-notification.vue';
|
||||
import MyProject from './components/my-project.vue';
|
||||
import LatestActivity from './components/latest-activity.vue';
|
||||
import MyTeam from './components/my-team.vue';
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'Info',
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.container {
|
||||
padding: 0 20px 20px 20px;
|
||||
}
|
||||
|
||||
.content {
|
||||
display: flex;
|
||||
margin-top: 12px;
|
||||
|
||||
&-left {
|
||||
flex: 1;
|
||||
margin-right: 16px;
|
||||
overflow: hidden;
|
||||
// background-color: var(--color-bg-2);
|
||||
|
||||
:deep(.arco-tabs-nav-tab) {
|
||||
margin-left: 16px;
|
||||
}
|
||||
}
|
||||
|
||||
&-right {
|
||||
width: 332px;
|
||||
}
|
||||
|
||||
.tab-pane-wrapper {
|
||||
padding: 0 16px 16px 16px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.mobile {
|
||||
.content {
|
||||
display: block;
|
||||
&-left {
|
||||
margin-right: 0;
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
&-right {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,15 +0,0 @@
|
|||
export default {
|
||||
'menu.user.info': 'User Info',
|
||||
'userInfo.editUserInfo': 'Edit Info',
|
||||
'userInfo.tab.title.overview': 'Overview',
|
||||
'userInfo.tab.title.project': 'Project',
|
||||
'userInfo.tab.title.team': 'My Team',
|
||||
'userInfo.title.latestActivity': 'Latest Activity',
|
||||
'userInfo.title.latestNotification': 'In-site Notification',
|
||||
'userInfo.title.myProject': 'My Project',
|
||||
'userInfo.showMore': 'Show More',
|
||||
'userInfo.viewAll': 'View All',
|
||||
'userInfo.nodata': 'No Data',
|
||||
'userInfo.visits.unit': 'times',
|
||||
'userInfo.visits.lastMonth': 'Last Month',
|
||||
};
|
|
@ -1,15 +0,0 @@
|
|||
export default {
|
||||
'menu.user.info': '用户信息',
|
||||
'userInfo.editUserInfo': '编辑信息',
|
||||
'userInfo.tab.title.overview': '总览',
|
||||
'userInfo.tab.title.project': '项目',
|
||||
'userInfo.tab.title.team': '我的团队',
|
||||
'userInfo.title.latestActivity': '最新动态',
|
||||
'userInfo.title.latestNotification': '站内通知',
|
||||
'userInfo.title.myProject': '我的项目',
|
||||
'userInfo.showMore': '查看更多',
|
||||
'userInfo.viewAll': '查看全部',
|
||||
'userInfo.nodata': '暂无数据',
|
||||
'userInfo.visits.unit': '人次',
|
||||
'userInfo.visits.lastMonth': '较上月',
|
||||
};
|
|
@ -1,162 +0,0 @@
|
|||
import Mock from 'mockjs';
|
||||
import setupMock, { successResponseWrap } from '@/utils/setup-mock';
|
||||
|
||||
setupMock({
|
||||
setup() {
|
||||
// 最新项目
|
||||
Mock.mock(new RegExp('/api/user/my-project/list'), () => {
|
||||
const contributors = [
|
||||
{
|
||||
name: '秦臻宇',
|
||||
email: 'qingzhenyu@arco.design',
|
||||
avatar:
|
||||
'//p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/a8c8cdb109cb051163646151a4a5083b.png~tplv-uwbnlip3yd-webp.webp',
|
||||
},
|
||||
{
|
||||
name: '于涛',
|
||||
email: 'yuebao@arco.design',
|
||||
avatar:
|
||||
'//p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/a8c8cdb109cb051163646151a4a5083b.png~tplv-uwbnlip3yd-webp.webp',
|
||||
},
|
||||
{
|
||||
name: '宁波',
|
||||
email: 'ningbo@arco.design',
|
||||
avatar:
|
||||
'//p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/3ee5f13fb09879ecb5185e440cef6eb9.png~tplv-uwbnlip3yd-webp.webp',
|
||||
},
|
||||
{
|
||||
name: '郑曦月',
|
||||
email: 'zhengxiyue@arco.design',
|
||||
avatar:
|
||||
'//p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/8361eeb82904210b4f55fab888fe8416.png~tplv-uwbnlip3yd-webp.webp',
|
||||
},
|
||||
{
|
||||
name: '宁波',
|
||||
email: 'ningbo@arco.design',
|
||||
avatar:
|
||||
'//p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/3ee5f13fb09879ecb5185e440cef6eb9.png~tplv-uwbnlip3yd-webp.webp',
|
||||
},
|
||||
];
|
||||
const units = [
|
||||
{
|
||||
name: '企业级产品设计系统',
|
||||
description: 'Arco Design System',
|
||||
},
|
||||
{
|
||||
name: '火山引擎智能应用',
|
||||
description: 'The Volcano Engine',
|
||||
},
|
||||
{
|
||||
name: 'OCR文本识别',
|
||||
description: 'OCR text recognition',
|
||||
},
|
||||
{
|
||||
name: '内容资源管理',
|
||||
description: 'Content resource management ',
|
||||
},
|
||||
{
|
||||
name: '今日头条内容管理',
|
||||
description: 'Toutiao content management',
|
||||
},
|
||||
{
|
||||
name: '智能机器人',
|
||||
description: 'Intelligent Robot Project',
|
||||
},
|
||||
];
|
||||
return successResponseWrap(
|
||||
new Array(6).fill(null).map((_item, index) => ({
|
||||
id: index,
|
||||
name: units[index].name,
|
||||
description: units[index].description,
|
||||
peopleNumber: Mock.Random.natural(10, 1000),
|
||||
contributors,
|
||||
}))
|
||||
);
|
||||
});
|
||||
|
||||
// 最新动态
|
||||
Mock.mock(new RegExp('/api/user/latest-activity'), () => {
|
||||
return successResponseWrap(
|
||||
new Array(7).fill(null).map((_item, index) => ({
|
||||
id: index,
|
||||
title: '发布了项目 Arco Design System',
|
||||
description: '企业级产品设计系统',
|
||||
avatar:
|
||||
'//lf1-xgcdn-tos.pstatp.com/obj/vcloud/vadmin/start.8e0e4855ee346a46ccff8ff3e24db27b.png',
|
||||
}))
|
||||
);
|
||||
});
|
||||
|
||||
// 访问量
|
||||
Mock.mock(new RegExp('/api/user/visits'), () => {
|
||||
return successResponseWrap([
|
||||
{
|
||||
name: '主页访问量',
|
||||
visits: 5670,
|
||||
growth: 206.32,
|
||||
},
|
||||
{
|
||||
name: '项目访问量',
|
||||
visits: 5670,
|
||||
growth: 206.32,
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
// 项目和团队列表
|
||||
Mock.mock(new RegExp('/api/user/project-and-team/list'), () => {
|
||||
return successResponseWrap([
|
||||
{
|
||||
id: 1,
|
||||
content: '他创建的项目',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
content: '他参与的项目',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
content: '他创建的团队',
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
content: '他加入的团队',
|
||||
},
|
||||
]);
|
||||
});
|
||||
|
||||
// 团队列表
|
||||
Mock.mock(new RegExp('/api/user/my-team/list'), () => {
|
||||
return successResponseWrap([
|
||||
{
|
||||
id: 1,
|
||||
avatar:
|
||||
'//p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/a8c8cdb109cb051163646151a4a5083b.png~tplv-uwbnlip3yd-webp.webp',
|
||||
name: '火山引擎智能应用团队',
|
||||
peopleNumber: Mock.Random.natural(10, 100),
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
avatar:
|
||||
'//p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/3ee5f13fb09879ecb5185e440cef6eb9.png~tplv-uwbnlip3yd-webp.webp',
|
||||
name: '企业级产品设计团队',
|
||||
peopleNumber: Mock.Random.natural(5000, 6000),
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
avatar:
|
||||
'//p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/3ee5f13fb09879ecb5185e440cef6eb9.png~tplv-uwbnlip3yd-webp.webp',
|
||||
name: '前端/UE小分队',
|
||||
peopleNumber: Mock.Random.natural(10, 5000),
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
avatar:
|
||||
'//p1-arco.byteimg.com/tos-cn-i-uwbnlip3yd/8361eeb82904210b4f55fab888fe8416.png~tplv-uwbnlip3yd-webp.webp',
|
||||
name: '内容识别插件小分队',
|
||||
peopleNumber: Mock.Random.natural(10, 100),
|
||||
},
|
||||
]);
|
||||
});
|
||||
},
|
||||
});
|
|
@ -1,159 +0,0 @@
|
|||
<template>
|
||||
<a-form
|
||||
ref="formRef"
|
||||
:model="formData"
|
||||
class="form"
|
||||
:label-col-props="{ span: 8 }"
|
||||
:wrapper-col-props="{ span: 16 }"
|
||||
>
|
||||
<a-form-item
|
||||
field="email"
|
||||
:label="$t('userSetting.basicInfo.form.label.email')"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: $t('userSetting.form.error.email.required'),
|
||||
},
|
||||
]"
|
||||
>
|
||||
<a-input
|
||||
v-model="formData.email"
|
||||
:placeholder="$t('userSetting.basicInfo.placeholder.email')"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
field="nickname"
|
||||
:label="$t('userSetting.basicInfo.form.label.nickname')"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: $t('userSetting.form.error.nickname.required'),
|
||||
},
|
||||
]"
|
||||
>
|
||||
<a-input
|
||||
v-model="formData.nickname"
|
||||
:placeholder="$t('userSetting.basicInfo.placeholder.nickname')"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
field="countryRegion"
|
||||
:label="$t('userSetting.basicInfo.form.label.countryRegion')"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: $t('userSetting.form.error.countryRegion.required'),
|
||||
},
|
||||
]"
|
||||
>
|
||||
<a-select
|
||||
v-model="formData.countryRegion"
|
||||
:placeholder="$t('userSetting.basicInfo.placeholder.area')"
|
||||
>
|
||||
<a-option value="China">中国</a-option>
|
||||
</a-select>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
field="area"
|
||||
:label="$t('userSetting.basicInfo.form.label.area')"
|
||||
:rules="[
|
||||
{
|
||||
required: true,
|
||||
message: $t('userSetting.form.error.area.required'),
|
||||
},
|
||||
]"
|
||||
>
|
||||
<a-cascader
|
||||
v-model="formData.area"
|
||||
:placeholder="$t('userSetting.basicInfo.placeholder.area')"
|
||||
:options="[
|
||||
{
|
||||
label: '北京',
|
||||
value: 'beijing',
|
||||
children: [
|
||||
{
|
||||
label: '北京',
|
||||
value: 'beijing',
|
||||
children: [
|
||||
{
|
||||
label: '朝阳',
|
||||
value: 'chaoyang',
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
]"
|
||||
allow-clear
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
field="address"
|
||||
:label="$t('userSetting.basicInfo.form.label.address')"
|
||||
>
|
||||
<a-input
|
||||
v-model="formData.address"
|
||||
:placeholder="$t('userSetting.basicInfo.placeholder.address')"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item
|
||||
field="profile"
|
||||
:label="$t('userSetting.basicInfo.form.label.profile')"
|
||||
:rules="[
|
||||
{
|
||||
maxLength: 200,
|
||||
message: $t('userSetting.form.error.profile.maxLength'),
|
||||
},
|
||||
]"
|
||||
row-class="keep-margin"
|
||||
>
|
||||
<a-textarea
|
||||
v-model="formData.profile"
|
||||
:placeholder="$t('userSetting.basicInfo.placeholder.profile')"
|
||||
/>
|
||||
</a-form-item>
|
||||
<a-form-item>
|
||||
<a-space>
|
||||
<a-button type="primary" @click="validate">
|
||||
{{ $t('userSetting.save') }}
|
||||
</a-button>
|
||||
<a-button type="secondary" @click="reset">
|
||||
{{ $t('userSetting.reset') }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</a-form-item>
|
||||
</a-form>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { FormInstance } from '@arco-design/web-vue/es/form';
|
||||
import { BasicInfoModel } from '@/api/user-center';
|
||||
|
||||
const formRef = ref<FormInstance>();
|
||||
const formData = ref<BasicInfoModel>({
|
||||
email: '',
|
||||
nickname: '',
|
||||
countryRegion: '',
|
||||
area: '',
|
||||
address: '',
|
||||
profile: '',
|
||||
});
|
||||
const validate = async () => {
|
||||
const res = await formRef.value?.validate();
|
||||
if (!res) {
|
||||
// do some thing
|
||||
// you also can use html-type to submit
|
||||
}
|
||||
};
|
||||
const reset = async () => {
|
||||
await formRef.value?.resetFields();
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.form {
|
||||
width: 540px;
|
||||
margin: 0 auto;
|
||||
}
|
||||
</style>
|
|
@ -1,77 +0,0 @@
|
|||
<template>
|
||||
<a-card
|
||||
class="general-card"
|
||||
:title="$t('userSetting.certification.title.record')"
|
||||
:header-style="{ border: 'none' }"
|
||||
>
|
||||
<a-table v-if="renderData.length" :data="renderData">
|
||||
<template #columns>
|
||||
<a-table-column
|
||||
:title="$t('userSetting.certification.columns.certificationType')"
|
||||
>
|
||||
<template #cell>
|
||||
{{ $t('userSetting.certification.cell.certificationType') }}
|
||||
</template>
|
||||
</a-table-column>
|
||||
<a-table-column
|
||||
:title="$t('userSetting.certification.columns.certificationContent')"
|
||||
data-index="certificationContent"
|
||||
/>
|
||||
<a-table-column :title="$t('userSetting.certification.columns.status')">
|
||||
<template #cell="{ record }">
|
||||
<p v-if="record.status === 0">
|
||||
<span class="circle"></span>
|
||||
<span>{{ $t('userSetting.certification.cell.auditing') }}</span>
|
||||
</p>
|
||||
<p v-if="record.status === 1">
|
||||
<span class="circle pass"></span>
|
||||
<span>{{ $t('userSetting.certification.cell.pass') }}</span>
|
||||
</p>
|
||||
</template>
|
||||
</a-table-column>
|
||||
<a-table-column
|
||||
:title="$t('userSetting.certification.columns.time')"
|
||||
data-index="time"
|
||||
/>
|
||||
<a-table-column
|
||||
:title="$t('userSetting.certification.columns.operation')"
|
||||
>
|
||||
<template #cell="{ record }">
|
||||
<a-space>
|
||||
<a-button type="text">
|
||||
{{ $t('userSetting.certification.button.check') }}
|
||||
</a-button>
|
||||
<a-button v-if="record.status === 0" type="text">
|
||||
{{ $t('userSetting.certification.button.withdraw') }}
|
||||
</a-button>
|
||||
</a-space>
|
||||
</template>
|
||||
</a-table-column>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { PropType } from 'vue';
|
||||
import { CertificationRecord } from '@/api/user-center';
|
||||
|
||||
defineProps({
|
||||
renderData: {
|
||||
type: Array as PropType<CertificationRecord>,
|
||||
default() {
|
||||
return [];
|
||||
},
|
||||
},
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
:deep(.arco-table-th) {
|
||||
&:last-child {
|
||||
.arco-table-th-item-title {
|
||||
margin-left: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,37 +0,0 @@
|
|||
<template>
|
||||
<a-spin :loading="loading" style="width: 100%">
|
||||
<EnterpriseCertification :enterprise-info="data.enterpriseInfo" />
|
||||
<CertificationRecords :render-data="data.record" />
|
||||
</a-spin>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import {
|
||||
queryCertification,
|
||||
UnitCertification,
|
||||
EnterpriseCertificationModel,
|
||||
} from '@/api/user-center';
|
||||
import useLoading from '@/hooks/loading';
|
||||
import EnterpriseCertification from './enterprise-certification.vue';
|
||||
import CertificationRecords from './certification-records.vue';
|
||||
|
||||
const { loading, setLoading } = useLoading(true);
|
||||
const data = ref<UnitCertification>({
|
||||
enterpriseInfo: {} as EnterpriseCertificationModel,
|
||||
record: [],
|
||||
});
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
const { data: resData } = await queryCertification();
|
||||
data.value = resData;
|
||||
} catch (err) {
|
||||
// you can report use errorHandler or other
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
fetchData();
|
||||
</script>
|
||||
|
||||
<style scoped lang="less"></style>
|
|
@ -1,116 +0,0 @@
|
|||
<template>
|
||||
<a-card
|
||||
class="general-card"
|
||||
:title="$t('userSetting.certification.title.enterprise')"
|
||||
:header-style="{ padding: '0px 20px 16px 20px' }"
|
||||
>
|
||||
<template #extra>
|
||||
<a-link>{{ $t('userSetting.certification.extra.enterprise') }}</a-link>
|
||||
</template>
|
||||
<a-descriptions
|
||||
class="card-content"
|
||||
:data="renderData"
|
||||
:column="3"
|
||||
align="right"
|
||||
layout="inline-horizontal"
|
||||
:label-style="{ fontWeight: 'normal' }"
|
||||
:value-style="{
|
||||
width: '200px',
|
||||
paddingLeft: '8px',
|
||||
textAlign: 'left',
|
||||
}"
|
||||
>
|
||||
<template #label="{ label }">{{ $t(label) }} :</template>
|
||||
<template #value="{ value, data }">
|
||||
<a-tag
|
||||
v-if="data.label === 'userSetting.certification.label.status'"
|
||||
color="green"
|
||||
size="small"
|
||||
>
|
||||
已认证
|
||||
</a-tag>
|
||||
<span v-else>{{ value }}</span>
|
||||
</template>
|
||||
</a-descriptions>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { PropType, computed } from 'vue';
|
||||
import { EnterpriseCertificationModel } from '@/api/user-center';
|
||||
import type { DescData } from '@arco-design/web-vue/es/descriptions/interface';
|
||||
|
||||
const props = defineProps({
|
||||
enterpriseInfo: {
|
||||
type: Object as PropType<EnterpriseCertificationModel>,
|
||||
required: true,
|
||||
},
|
||||
});
|
||||
const renderData = computed(() => {
|
||||
const {
|
||||
accountType,
|
||||
status,
|
||||
time,
|
||||
legalPerson,
|
||||
certificateType,
|
||||
authenticationNumber,
|
||||
enterpriseName,
|
||||
enterpriseCertificateType,
|
||||
organizationCode,
|
||||
} = props.enterpriseInfo;
|
||||
return [
|
||||
{
|
||||
label: 'userSetting.certification.label.accountType',
|
||||
value: accountType,
|
||||
},
|
||||
{
|
||||
label: 'userSetting.certification.label.status',
|
||||
value: status,
|
||||
},
|
||||
{
|
||||
label: 'userSetting.certification.label.time',
|
||||
value: time,
|
||||
},
|
||||
{
|
||||
label: 'userSetting.certification.label.legalPerson',
|
||||
value: legalPerson,
|
||||
},
|
||||
{
|
||||
label: 'userSetting.certification.label.certificateType',
|
||||
value: certificateType,
|
||||
},
|
||||
{
|
||||
label: 'userSetting.certification.label.authenticationNumber',
|
||||
value: authenticationNumber,
|
||||
},
|
||||
{
|
||||
label: 'userSetting.certification.label.enterpriseName',
|
||||
value: enterpriseName,
|
||||
},
|
||||
{
|
||||
label: 'userSetting.certification.label.enterpriseCertificateType',
|
||||
value: enterpriseCertificateType,
|
||||
},
|
||||
{
|
||||
label: 'userSetting.certification.label.organizationCode',
|
||||
value: organizationCode,
|
||||
},
|
||||
] as DescData[];
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.card-content {
|
||||
width: 100%;
|
||||
padding: 20px;
|
||||
background-color: rgb(var(--gray-1));
|
||||
}
|
||||
.item-label {
|
||||
min-width: 98px;
|
||||
text-align: right;
|
||||
color: var(--color-text-8);
|
||||
&:after {
|
||||
content: ':';
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,124 +0,0 @@
|
|||
<template>
|
||||
<a-list :bordered="false">
|
||||
<a-list-item>
|
||||
<a-list-item-meta>
|
||||
<template #avatar>
|
||||
<a-typography-paragraph>
|
||||
{{ $t('userSetting.SecuritySettings.form.label.password') }}
|
||||
</a-typography-paragraph>
|
||||
</template>
|
||||
<template #description>
|
||||
<div class="content">
|
||||
<a-typography-paragraph>
|
||||
{{ $t('userSetting.SecuritySettings.placeholder.password') }}
|
||||
</a-typography-paragraph>
|
||||
</div>
|
||||
<div class="operation">
|
||||
<a-link>
|
||||
{{ $t('userSetting.SecuritySettings.button.update') }}
|
||||
</a-link>
|
||||
</div>
|
||||
</template>
|
||||
</a-list-item-meta>
|
||||
</a-list-item>
|
||||
<a-list-item>
|
||||
<a-list-item-meta>
|
||||
<template #avatar>
|
||||
<a-typography-paragraph>
|
||||
{{ $t('userSetting.SecuritySettings.form.label.securityQuestion') }}
|
||||
</a-typography-paragraph>
|
||||
</template>
|
||||
<template #description>
|
||||
<div class="content">
|
||||
<a-typography-paragraph class="tip">
|
||||
{{
|
||||
$t('userSetting.SecuritySettings.placeholder.securityQuestion')
|
||||
}}
|
||||
</a-typography-paragraph>
|
||||
</div>
|
||||
<div class="operation">
|
||||
<a-link>
|
||||
{{ $t('userSetting.SecuritySettings.button.settings') }}
|
||||
</a-link>
|
||||
</div>
|
||||
</template>
|
||||
</a-list-item-meta>
|
||||
</a-list-item>
|
||||
<a-list-item>
|
||||
<a-list-item-meta>
|
||||
<template #avatar>
|
||||
<a-typography-paragraph>
|
||||
{{ $t('userSetting.SecuritySettings.form.label.phone') }}
|
||||
</a-typography-paragraph>
|
||||
</template>
|
||||
<template #description>
|
||||
<div class="content">
|
||||
<a-typography-paragraph>
|
||||
已绑定:150******50
|
||||
</a-typography-paragraph>
|
||||
</div>
|
||||
<div class="operation">
|
||||
<a-link>
|
||||
{{ $t('userSetting.SecuritySettings.button.update') }}
|
||||
</a-link>
|
||||
</div>
|
||||
</template>
|
||||
</a-list-item-meta>
|
||||
</a-list-item>
|
||||
<a-list-item>
|
||||
<a-list-item-meta>
|
||||
<template #avatar>
|
||||
<a-typography-paragraph>
|
||||
{{ $t('userSetting.SecuritySettings.form.label.email') }}
|
||||
</a-typography-paragraph>
|
||||
</template>
|
||||
<template #description>
|
||||
<div class="content">
|
||||
<a-typography-paragraph class="tip">
|
||||
{{ $t('userSetting.SecuritySettings.placeholder.email') }}
|
||||
</a-typography-paragraph>
|
||||
</div>
|
||||
<div class="operation">
|
||||
<a-link>
|
||||
{{ $t('userSetting.SecuritySettings.button.update') }}
|
||||
</a-link>
|
||||
</div>
|
||||
</template>
|
||||
</a-list-item-meta>
|
||||
</a-list-item>
|
||||
</a-list>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup></script>
|
||||
|
||||
<style scoped lang="less">
|
||||
:deep(.arco-list-item) {
|
||||
border-bottom: none !important;
|
||||
.arco-typography {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
.arco-list-item-meta-avatar {
|
||||
margin-bottom: 1px;
|
||||
}
|
||||
.arco-list-item-meta {
|
||||
padding: 0;
|
||||
}
|
||||
}
|
||||
:deep(.arco-list-item-meta-content) {
|
||||
flex: 1;
|
||||
border-bottom: 1px solid var(--color-neutral-3);
|
||||
|
||||
.arco-list-item-meta-description {
|
||||
display: flex;
|
||||
flex-flow: row;
|
||||
justify-content: space-between;
|
||||
|
||||
.tip {
|
||||
color: rgb(var(--gray-6));
|
||||
}
|
||||
.operation {
|
||||
margin-right: 6px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,155 +0,0 @@
|
|||
<template>
|
||||
<a-card :bordered="false">
|
||||
<a-space :size="54">
|
||||
<a-upload
|
||||
:custom-request="customRequest"
|
||||
list-type="picture-card"
|
||||
:file-list="fileList"
|
||||
:show-upload-button="true"
|
||||
:show-file-list="false"
|
||||
@change="uploadChange"
|
||||
>
|
||||
<template #upload-button>
|
||||
<a-avatar :size="100" class="info-avatar">
|
||||
<template #trigger-icon>
|
||||
<icon-camera />
|
||||
</template>
|
||||
<img v-if="fileList.length" :src="fileList[0].url" />
|
||||
</a-avatar>
|
||||
</template>
|
||||
</a-upload>
|
||||
<a-descriptions
|
||||
:data="renderData"
|
||||
:column="2"
|
||||
align="right"
|
||||
layout="inline-horizontal"
|
||||
:label-style="{
|
||||
width: '140px',
|
||||
fontWeight: 'normal',
|
||||
color: 'rgb(var(--gray-8))',
|
||||
}"
|
||||
:value-style="{
|
||||
width: '200px',
|
||||
paddingLeft: '8px',
|
||||
textAlign: 'left',
|
||||
}"
|
||||
>
|
||||
<template #label="{ label }">{{ $t(label) }} :</template>
|
||||
<template #value="{ value, data }">
|
||||
<a-tag
|
||||
v-if="data.label === 'userSetting.label.certification'"
|
||||
color="green"
|
||||
size="small"
|
||||
>
|
||||
已认证
|
||||
</a-tag>
|
||||
<span v-else>{{ value }}</span>
|
||||
</template>
|
||||
</a-descriptions>
|
||||
</a-space>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import type {
|
||||
FileItem,
|
||||
RequestOption,
|
||||
} from '@arco-design/web-vue/es/upload/interfaces';
|
||||
import { useUserStore } from '@/store';
|
||||
import { userUploadApi } from '@/api/user-center';
|
||||
import type { DescData } from '@arco-design/web-vue/es/descriptions/interface';
|
||||
|
||||
const userStore = useUserStore();
|
||||
const file = {
|
||||
uid: '-2',
|
||||
name: 'avatar.png',
|
||||
url: userStore.avatar,
|
||||
};
|
||||
const renderData = [
|
||||
{
|
||||
label: 'userSetting.label.name',
|
||||
value: userStore.name,
|
||||
},
|
||||
{
|
||||
label: 'userSetting.label.certification',
|
||||
value: userStore.certification,
|
||||
},
|
||||
{
|
||||
label: 'userSetting.label.accountId',
|
||||
value: userStore.accountId,
|
||||
},
|
||||
{
|
||||
label: 'userSetting.label.phone',
|
||||
value: userStore.phone,
|
||||
},
|
||||
{
|
||||
label: 'userSetting.label.registrationDate',
|
||||
value: userStore.registrationDate,
|
||||
},
|
||||
] as DescData[];
|
||||
const fileList = ref<FileItem[]>([file]);
|
||||
const uploadChange = (fileItemList: FileItem[], fileItem: FileItem) => {
|
||||
fileList.value = [fileItem];
|
||||
};
|
||||
const customRequest = (options: RequestOption) => {
|
||||
// docs: https://axios-http.com/docs/cancellation
|
||||
const controller = new AbortController();
|
||||
|
||||
(async function requestWrap() {
|
||||
const {
|
||||
onProgress,
|
||||
onError,
|
||||
onSuccess,
|
||||
fileItem,
|
||||
name = 'file',
|
||||
} = options;
|
||||
onProgress(20);
|
||||
const formData = new FormData();
|
||||
formData.append(name as string, fileItem.file as Blob);
|
||||
const onUploadProgress = (event: ProgressEvent) => {
|
||||
let percent;
|
||||
if (event.total > 0) {
|
||||
percent = (event.loaded / event.total) * 100;
|
||||
}
|
||||
onProgress(parseInt(String(percent), 10), event);
|
||||
};
|
||||
|
||||
try {
|
||||
// https://github.com/axios/axios/issues/1630
|
||||
// https://github.com/nuysoft/Mock/issues/127
|
||||
|
||||
const res = await userUploadApi(formData, {
|
||||
controller,
|
||||
onUploadProgress,
|
||||
});
|
||||
onSuccess(res);
|
||||
} catch (error) {
|
||||
onError(error);
|
||||
}
|
||||
})();
|
||||
return {
|
||||
abort() {
|
||||
controller.abort();
|
||||
},
|
||||
};
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.arco-card {
|
||||
padding: 14px 0 4px 4px;
|
||||
border-radius: 4px;
|
||||
}
|
||||
:deep(.arco-avatar-trigger-icon-button) {
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
background-color: #e8f3ff;
|
||||
.arco-icon-camera {
|
||||
margin-top: 8px;
|
||||
color: rgb(var(--arcoblue-6));
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
</style>
|
|
@ -1,57 +0,0 @@
|
|||
<template>
|
||||
<div class="container">
|
||||
<Breadcrumb :items="['menu.user', 'menu.user.setting']" />
|
||||
<a-row style="margin-bottom: 16px">
|
||||
<a-col :span="24">
|
||||
<UserPanel />
|
||||
</a-col>
|
||||
</a-row>
|
||||
<a-row class="wrapper">
|
||||
<a-col :span="24">
|
||||
<a-tabs default-active-key="1" type="rounded">
|
||||
<a-tab-pane key="1" :title="$t('userSetting.tab.basicInformation')">
|
||||
<BasicInformation />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="2" :title="$t('userSetting.tab.securitySettings')">
|
||||
<SecuritySettings />
|
||||
</a-tab-pane>
|
||||
<a-tab-pane key="3" :title="$t('userSetting.tab.certification')">
|
||||
<Certification />
|
||||
</a-tab-pane>
|
||||
</a-tabs>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import UserPanel from './components/user-panel.vue';
|
||||
import BasicInformation from './components/basic-information.vue';
|
||||
import SecuritySettings from './components/security-settings.vue';
|
||||
import Certification from './components/certification.vue';
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'Setting',
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.container {
|
||||
padding: 0 20px 20px 20px;
|
||||
}
|
||||
|
||||
.wrapper {
|
||||
padding: 20px 0 0 20px;
|
||||
min-height: 580px;
|
||||
background-color: var(--color-bg-2);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
:deep(.section-title) {
|
||||
margin-top: 0;
|
||||
margin-bottom: 16px;
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
|
@ -1,91 +0,0 @@
|
|||
export default {
|
||||
'menu.user.setting': 'User Setting',
|
||||
'userSetting.menu.title.info': 'Personal Information',
|
||||
'userSetting.menu.title.account': 'Account Setting',
|
||||
'userSetting.menu.title.password': 'Password',
|
||||
'userSetting.menu.title.message': 'Message Notification',
|
||||
'userSetting.menu.title.result': 'Result',
|
||||
'userSetting.menu.title.data': 'Export Data',
|
||||
'userSetting.saveSuccess': 'Save Success',
|
||||
'userSetting.title.basicInfo': 'Basic Information',
|
||||
'userSetting.title.socialInfo': 'Social Information',
|
||||
'userSetting.label.avatar': 'Avatar',
|
||||
'userSetting.label.name': 'User Name',
|
||||
'userSetting.label.location': 'Office Location',
|
||||
'userSetting.label.introduction': 'Introduction',
|
||||
'userSetting.label.personalWebsite': 'Website',
|
||||
'userSetting.save': 'Save',
|
||||
'userSetting.cancel': 'Cancel',
|
||||
'userSetting.reset': 'Reset',
|
||||
// new
|
||||
'userSetting.label.certification': 'Certification',
|
||||
'userSetting.label.phone': 'Phone',
|
||||
'userSetting.label.accountId': 'Account Id',
|
||||
'userSetting.label.registrationDate': 'Registration Date',
|
||||
'userSetting.tab.basicInformation': 'Basic Information',
|
||||
'userSetting.tab.securitySettings': 'Security Settings',
|
||||
'userSetting.tab.certification': 'Certification',
|
||||
'userSetting.basicInfo.form.label.email': 'Email',
|
||||
'userSetting.basicInfo.placeholder.email': `Please enter your email address, such as xxx{'@'}bytedance.com`,
|
||||
'userSetting.form.error.email.required': 'Please enter email address',
|
||||
'userSetting.basicInfo.form.label.nickname': 'Nickname',
|
||||
'userSetting.basicInfo.placeholder.nickname': 'Please enter nickname',
|
||||
'userSetting.form.error.nickname.required': 'Please enter nickname',
|
||||
'userSetting.basicInfo.form.label.countryRegion': 'Country/region',
|
||||
'userSetting.basicInfo.placeholder.countryRegion':
|
||||
'Please select country/region',
|
||||
'userSetting.form.error.countryRegion.required':
|
||||
'Please select country/region',
|
||||
'userSetting.basicInfo.form.label.area': 'Area',
|
||||
'userSetting.basicInfo.placeholder.area': 'Please select area',
|
||||
'userSetting.form.error.area.required': 'Please Select a area',
|
||||
'userSetting.basicInfo.form.label.address': 'Address',
|
||||
'userSetting.basicInfo.placeholder.address': 'Please enter address',
|
||||
'userSetting.basicInfo.form.label.profile': 'Personal profile',
|
||||
'userSetting.basicInfo.placeholder.profile':
|
||||
'Please enter your profile, no more than 200 words',
|
||||
'userSetting.form.error.profile.maxLength': 'No more than 200 words',
|
||||
'userSetting.SecuritySettings.form.label.password': 'Login Password',
|
||||
'userSetting.SecuritySettings.placeholder.password':
|
||||
'Has been set. The password must contain at least six letters, digits, and special characters except Spaces. The password must contain both uppercase and lowercase letters.',
|
||||
'userSetting.SecuritySettings.form.label.securityQuestion':
|
||||
'Security Question',
|
||||
'userSetting.SecuritySettings.placeholder.securityQuestion':
|
||||
'You have not set the password protection question. The password protection question can effectively protect the account security.',
|
||||
'userSetting.SecuritySettings.form.label.phone': 'Phone',
|
||||
// 'userSetting.SecuritySettings.placeholder.phone': '已绑定:150******50',
|
||||
'userSetting.SecuritySettings.form.label.email': 'Email',
|
||||
'userSetting.SecuritySettings.placeholder.email':
|
||||
'You have not set a mailbox yet. The mailbox binding can be used to retrieve passwords and receive notifications.',
|
||||
'userSetting.SecuritySettings.button.settings': 'Settings',
|
||||
'userSetting.SecuritySettings.button.update': 'Update',
|
||||
'userSetting.certification.title.enterprise':
|
||||
'Enterprise Real Name Authentication',
|
||||
'userSetting.certification.extra.enterprise':
|
||||
'Modifying an Authentication Body',
|
||||
'userSetting.certification.label.accountType': 'Account Type',
|
||||
'userSetting.certification.label.status': 'status',
|
||||
'userSetting.certification.label.time': 'time',
|
||||
'userSetting.certification.label.legalPerson': 'Legal Person Name',
|
||||
'userSetting.certification.label.certificateType':
|
||||
'Types of legal person documents',
|
||||
'userSetting.certification.label.authenticationNumber':
|
||||
'Legal person certification number',
|
||||
'userSetting.certification.label.enterpriseName': 'Enterprise Name',
|
||||
'userSetting.certification.label.enterpriseCertificateType':
|
||||
'Types of corporate certificates',
|
||||
'userSetting.certification.label.organizationCode': 'Organization Code',
|
||||
'userSetting.certification.title.record': 'Certification Records',
|
||||
'userSetting.certification.columns.certificationType': 'Certification Type',
|
||||
'userSetting.certification.cell.certificationType':
|
||||
'Enterprise certificate Certification',
|
||||
'userSetting.certification.columns.certificationContent':
|
||||
'Certification Content',
|
||||
'userSetting.certification.columns.status': 'Status',
|
||||
'userSetting.certification.cell.pass': 'Pass',
|
||||
'userSetting.certification.cell.auditing': 'Auditing',
|
||||
'userSetting.certification.columns.time': 'Time',
|
||||
'userSetting.certification.columns.operation': 'Operation',
|
||||
'userSetting.certification.button.check': 'Check',
|
||||
'userSetting.certification.button.withdraw': 'Withdraw',
|
||||
};
|
|
@ -1,81 +0,0 @@
|
|||
export default {
|
||||
'menu.user.setting': '用户设置',
|
||||
'userSetting.menu.title.info': '个人信息',
|
||||
'userSetting.menu.title.account': '账号设置',
|
||||
'userSetting.menu.title.password': '密码',
|
||||
'userSetting.menu.title.message': '消息通知',
|
||||
'userSetting.menu.title.result': '结果页',
|
||||
'userSetting.menu.title.data': '导出数据',
|
||||
'userSetting.saveSuccess': '保存成功',
|
||||
'userSetting.title.basicInfo': '基本信息',
|
||||
'userSetting.title.socialInfo': '社交信息',
|
||||
'userSetting.label.avatar': '头像',
|
||||
'userSetting.label.name': '用户名',
|
||||
'userSetting.label.location': '办公地点',
|
||||
'userSetting.label.introduction': '个人简介',
|
||||
'userSetting.label.personalWebsite': '个人网站',
|
||||
'userSetting.save': '保存',
|
||||
'userSetting.cancel': '取消',
|
||||
'userSetting.reset': '重置',
|
||||
// new
|
||||
'userSetting.label.certification': '实名认证',
|
||||
'userSetting.label.phone': '手机号码',
|
||||
'userSetting.label.accountId': '账号ID',
|
||||
'userSetting.label.registrationDate': '注册时间',
|
||||
'userSetting.tab.basicInformation': '基础信息',
|
||||
'userSetting.tab.securitySettings': '安全设置',
|
||||
'userSetting.tab.certification': '实名认证',
|
||||
'userSetting.basicInfo.form.label.email': '邮箱',
|
||||
'userSetting.basicInfo.placeholder.email': `请输入邮箱地址,如xxx{'@'}bytedance.com`,
|
||||
'userSetting.form.error.email.required': '请输入邮箱',
|
||||
'userSetting.basicInfo.form.label.nickname': '昵称',
|
||||
'userSetting.basicInfo.placeholder.nickname': '请输入您的昵称',
|
||||
'userSetting.form.error.nickname.required': '请输入昵称',
|
||||
'userSetting.basicInfo.form.label.countryRegion': '国家/地区',
|
||||
'userSetting.basicInfo.placeholder.countryRegion': '请选择',
|
||||
'userSetting.form.error.countryRegion.required': '请选择国家/地区',
|
||||
'userSetting.basicInfo.form.label.area': '所在区域',
|
||||
'userSetting.basicInfo.placeholder.area': '请选择',
|
||||
'userSetting.form.error.area.required': '请选择所在区域',
|
||||
'userSetting.basicInfo.form.label.address': '具体地址',
|
||||
'userSetting.basicInfo.placeholder.address': '请输入您的地址',
|
||||
'userSetting.basicInfo.form.label.profile': '个人简介',
|
||||
'userSetting.basicInfo.placeholder.profile':
|
||||
'请输入您的个人简介,最多不超过200字。',
|
||||
'userSetting.form.error.profile.maxLength': '最多不超过200字',
|
||||
'userSetting.SecuritySettings.form.label.password': '登录密码',
|
||||
'userSetting.SecuritySettings.placeholder.password':
|
||||
'已设置。密码至少6位字符,支持数字、字母和除空格外的特殊字符,且必须同时包含数字和大小写字母。',
|
||||
'userSetting.SecuritySettings.form.label.securityQuestion': '密保问题',
|
||||
'userSetting.SecuritySettings.placeholder.securityQuestion':
|
||||
'您暂未设置密保问题,密保问题可以有效的保护账号的安全。',
|
||||
'userSetting.SecuritySettings.form.label.phone': '安全手机',
|
||||
// 'userSetting.SecuritySettings.placeholder.phone': '已绑定:150******50',
|
||||
'userSetting.SecuritySettings.form.label.email': '安全邮箱',
|
||||
'userSetting.SecuritySettings.placeholder.email':
|
||||
'您暂未设置邮箱,绑定邮箱可以用来找回密码、接收通知等。',
|
||||
'userSetting.SecuritySettings.button.settings': '设置',
|
||||
'userSetting.SecuritySettings.button.update': '修改',
|
||||
'userSetting.certification.title.enterprise': '企业实名认证',
|
||||
'userSetting.certification.extra.enterprise': '修改认证主体',
|
||||
'userSetting.certification.label.accountType': '账号类型',
|
||||
'userSetting.certification.label.status': '认证状态',
|
||||
'userSetting.certification.label.time': '认证时间',
|
||||
'userSetting.certification.label.legalPerson': '法人姓名',
|
||||
'userSetting.certification.label.certificateType': '法人证件类型',
|
||||
'userSetting.certification.label.authenticationNumber': '法人认证号码',
|
||||
'userSetting.certification.label.enterpriseName': '企业名称',
|
||||
'userSetting.certification.label.enterpriseCertificateType': '企业证件类型',
|
||||
'userSetting.certification.label.organizationCode': '组织机构代码',
|
||||
'userSetting.certification.title.record': '认证记录',
|
||||
'userSetting.certification.columns.certificationType': '认证类型',
|
||||
'userSetting.certification.cell.certificationType': '企业证件认证',
|
||||
'userSetting.certification.columns.certificationContent': '认证内容',
|
||||
'userSetting.certification.columns.status': '当前状态',
|
||||
'userSetting.certification.cell.pass': '已通过',
|
||||
'userSetting.certification.cell.auditing': '审核中',
|
||||
'userSetting.certification.columns.time': '创建时间',
|
||||
'userSetting.certification.columns.operation': '操作',
|
||||
'userSetting.certification.button.check': '查看',
|
||||
'userSetting.certification.button.withdraw': '撤回',
|
||||
};
|
|
@ -1,42 +0,0 @@
|
|||
import Mock from 'mockjs';
|
||||
import setupMock, { successResponseWrap } from '@/utils/setup-mock';
|
||||
|
||||
setupMock({
|
||||
setup() {
|
||||
Mock.mock(new RegExp('/api/user/save-info'), () => {
|
||||
return successResponseWrap('ok');
|
||||
});
|
||||
Mock.mock(new RegExp('/api/user/certification'), () => {
|
||||
return successResponseWrap({
|
||||
enterpriseInfo: {
|
||||
accountType: '企业账号',
|
||||
status: 0,
|
||||
time: '2018-10-22 14:53:12',
|
||||
legalPerson: '李**',
|
||||
certificateType: '中国身份证',
|
||||
authenticationNumber: '130************123',
|
||||
enterpriseName: '低调有实力的企业',
|
||||
enterpriseCertificateType: '企业营业执照',
|
||||
organizationCode: '7*******9',
|
||||
},
|
||||
record: [
|
||||
{
|
||||
certificationType: 1,
|
||||
certificationContent: '企业实名认证,法人姓名:李**',
|
||||
status: 0,
|
||||
time: '2021-02-28 10:30:50',
|
||||
},
|
||||
{
|
||||
certificationType: 1,
|
||||
certificationContent: '企业实名认证,法人姓名:李**',
|
||||
status: 1,
|
||||
time: '2020-05-13 08:00:00',
|
||||
},
|
||||
],
|
||||
});
|
||||
});
|
||||
Mock.mock(new RegExp('/api/user/upload'), () => {
|
||||
return successResponseWrap('ok');
|
||||
});
|
||||
},
|
||||
});
|
|
@ -1,291 +0,0 @@
|
|||
<template>
|
||||
<a-spin :loading="loading" style="width: 100%">
|
||||
<a-card :bordered="false" :style="cardStyle">
|
||||
<div class="content-wrap">
|
||||
<div class="content">
|
||||
<a-statistic
|
||||
:title="title"
|
||||
:value="renderData.count"
|
||||
:value-from="0"
|
||||
animation
|
||||
show-group-separator
|
||||
/>
|
||||
<div class="desc">
|
||||
<a-typography-text type="secondary" class="label">
|
||||
{{ $t('dataAnalysis.card.yesterday') }}
|
||||
</a-typography-text>
|
||||
<a-typography-text type="danger">
|
||||
{{ renderData.growth }}
|
||||
<icon-arrow-rise />
|
||||
</a-typography-text>
|
||||
</div>
|
||||
</div>
|
||||
<div class="chart">
|
||||
<Chart v-if="!loading" :option="chartOption" />
|
||||
</div>
|
||||
</div>
|
||||
</a-card>
|
||||
</a-spin>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, PropType, CSSProperties } from 'vue';
|
||||
import useLoading from '@/hooks/loading';
|
||||
import {
|
||||
queryPublicOpinionAnalysis,
|
||||
PublicOpinionAnalysis,
|
||||
PublicOpinionAnalysisRes,
|
||||
} from '@/api/visualization';
|
||||
import useChartOption from '@/hooks/chart-option';
|
||||
|
||||
const barChartOptionsFactory = () => {
|
||||
const data = ref<any>([]);
|
||||
const { chartOption } = useChartOption(() => {
|
||||
return {
|
||||
grid: {
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 10,
|
||||
bottom: 0,
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
show: false,
|
||||
},
|
||||
yAxis: {
|
||||
show: false,
|
||||
},
|
||||
tooltip: {
|
||||
show: true,
|
||||
trigger: 'axis',
|
||||
},
|
||||
series: {
|
||||
name: 'total',
|
||||
data,
|
||||
type: 'bar',
|
||||
barWidth: 7,
|
||||
itemStyle: {
|
||||
borderRadius: 2,
|
||||
},
|
||||
},
|
||||
};
|
||||
});
|
||||
return {
|
||||
data,
|
||||
chartOption,
|
||||
};
|
||||
};
|
||||
|
||||
const lineChartOptionsFactory = () => {
|
||||
const data = ref<number[][]>([[], []]);
|
||||
const { chartOption } = useChartOption(() => {
|
||||
return {
|
||||
grid: {
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 10,
|
||||
bottom: 0,
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
show: false,
|
||||
},
|
||||
yAxis: {
|
||||
show: false,
|
||||
},
|
||||
tooltip: {
|
||||
show: true,
|
||||
trigger: 'axis',
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '2001',
|
||||
data: data.value[0],
|
||||
type: 'line',
|
||||
showSymbol: false,
|
||||
smooth: true,
|
||||
lineStyle: {
|
||||
color: '#165DFF',
|
||||
width: 3,
|
||||
},
|
||||
},
|
||||
{
|
||||
name: '2002',
|
||||
data: data.value[1],
|
||||
type: 'line',
|
||||
showSymbol: false,
|
||||
smooth: true,
|
||||
lineStyle: {
|
||||
color: '#6AA1FF',
|
||||
width: 3,
|
||||
type: 'dashed',
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
});
|
||||
return {
|
||||
data,
|
||||
chartOption,
|
||||
};
|
||||
};
|
||||
|
||||
const pieChartOptionsFactory = () => {
|
||||
const data = ref<any>([]);
|
||||
const { chartOption } = useChartOption(() => {
|
||||
return {
|
||||
grid: {
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
},
|
||||
legend: {
|
||||
show: true,
|
||||
top: 'center',
|
||||
right: '0',
|
||||
orient: 'vertical',
|
||||
icon: 'circle',
|
||||
itemWidth: 6,
|
||||
itemHeight: 6,
|
||||
textStyle: {
|
||||
color: '#4E5969',
|
||||
},
|
||||
},
|
||||
tooltip: {
|
||||
show: true,
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '总计',
|
||||
type: 'pie',
|
||||
radius: ['50%', '70%'],
|
||||
label: {
|
||||
show: false,
|
||||
},
|
||||
data,
|
||||
},
|
||||
],
|
||||
};
|
||||
});
|
||||
return {
|
||||
data,
|
||||
chartOption,
|
||||
};
|
||||
};
|
||||
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
quota: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
chartType: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
cardStyle: {
|
||||
type: Object as PropType<CSSProperties>,
|
||||
default: () => {
|
||||
return {};
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
const { loading, setLoading } = useLoading(true);
|
||||
const { chartOption: lineChartOption, data: lineData } =
|
||||
lineChartOptionsFactory();
|
||||
const { chartOption: barChartOption, data: barData } =
|
||||
barChartOptionsFactory();
|
||||
const { chartOption: pieChartOption, data: pieData } =
|
||||
pieChartOptionsFactory();
|
||||
const renderData = ref<PublicOpinionAnalysisRes>({
|
||||
count: 0,
|
||||
growth: 0,
|
||||
chartData: [],
|
||||
});
|
||||
const chartOption = ref({});
|
||||
const fetchData = async (params: PublicOpinionAnalysis) => {
|
||||
try {
|
||||
const { data } = await queryPublicOpinionAnalysis(params);
|
||||
renderData.value = data;
|
||||
const { chartData } = data;
|
||||
if (props.chartType === 'bar') {
|
||||
chartData.forEach((el, idx) => {
|
||||
barData.value.push({
|
||||
value: el.y,
|
||||
itemStyle: {
|
||||
color: idx % 2 ? '#2CAB40' : '#86DF6C',
|
||||
},
|
||||
});
|
||||
});
|
||||
chartOption.value = barChartOption.value;
|
||||
} else if (props.chartType === 'line') {
|
||||
chartData.forEach((el) => {
|
||||
if (el.name === '2021') {
|
||||
lineData.value[0].push(el.y);
|
||||
} else {
|
||||
lineData.value[1].push(el.y);
|
||||
}
|
||||
});
|
||||
chartOption.value = lineChartOption.value;
|
||||
} else {
|
||||
chartData.forEach((el) => {
|
||||
pieData.value.push(el);
|
||||
});
|
||||
chartOption.value = pieChartOption.value;
|
||||
}
|
||||
} catch (err) {
|
||||
// you can report use errorHandler or other
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
fetchData({ quota: props.quota });
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
:deep(.arco-card) {
|
||||
border-radius: 4px;
|
||||
}
|
||||
:deep(.arco-card-body) {
|
||||
width: 100%;
|
||||
height: 134px;
|
||||
padding: 0;
|
||||
}
|
||||
.content-wrap {
|
||||
width: 100%;
|
||||
padding: 16px;
|
||||
white-space: nowrap;
|
||||
}
|
||||
:deep(.content) {
|
||||
float: left;
|
||||
width: 108px;
|
||||
height: 102px;
|
||||
}
|
||||
:deep(.arco-statistic) {
|
||||
.arco-statistic-title {
|
||||
font-size: 16px;
|
||||
font-weight: bold;
|
||||
white-space: nowrap;
|
||||
}
|
||||
.arco-statistic-content {
|
||||
margin-top: 10px;
|
||||
}
|
||||
}
|
||||
|
||||
.chart {
|
||||
float: right;
|
||||
width: calc(100% - 108px);
|
||||
height: 90px;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
.label {
|
||||
padding-right: 8px;
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
|
@ -1,216 +0,0 @@
|
|||
<template>
|
||||
<a-spin :loading="loading" style="width: 100%">
|
||||
<a-card class="general-card" :header-style="{ paddingBottom: '16px' }">
|
||||
<template #title>
|
||||
{{ $t('dataAnalysis.contentPeriodAnalysis') }}
|
||||
</template>
|
||||
<Chart style="width: 100%; height: 370px" :option="chartOption" />
|
||||
</a-card>
|
||||
</a-spin>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import useLoading from '@/hooks/loading';
|
||||
import { queryContentPeriodAnalysis } from '@/api/visualization';
|
||||
import { ToolTipFormatterParams } from '@/types/echarts';
|
||||
import useChartOption from '@/hooks/chart-option';
|
||||
|
||||
const tooltipItemsHtmlString = (items: ToolTipFormatterParams[]) => {
|
||||
return items
|
||||
.map(
|
||||
(el) => `<div class="content-panel">
|
||||
<p>
|
||||
<span style="background-color: ${el.color}" class="tooltip-item-icon"></span>
|
||||
<span>${el.seriesName}</span>
|
||||
</p>
|
||||
<span class="tooltip-value">
|
||||
${el.value}%
|
||||
</span>
|
||||
</div>`
|
||||
)
|
||||
.join('');
|
||||
};
|
||||
|
||||
const { loading, setLoading } = useLoading(true);
|
||||
const xAxis = ref<string[]>([]);
|
||||
const textChartsData = ref<number[]>([]);
|
||||
const imgChartsData = ref<number[]>([]);
|
||||
const videoChartsData = ref<number[]>([]);
|
||||
const { chartOption } = useChartOption((isDark) => {
|
||||
return {
|
||||
grid: {
|
||||
left: '40',
|
||||
right: 0,
|
||||
top: '20',
|
||||
bottom: '100',
|
||||
},
|
||||
legend: {
|
||||
bottom: 0,
|
||||
icon: 'circle',
|
||||
textStyle: {
|
||||
color: '#4E5969',
|
||||
},
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: xAxis.value,
|
||||
boundaryGap: false,
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: isDark ? '#3f3f3f' : '#A9AEB8',
|
||||
},
|
||||
},
|
||||
axisTick: {
|
||||
show: true,
|
||||
alignWithLabel: true,
|
||||
lineStyle: {
|
||||
color: '#86909C',
|
||||
},
|
||||
interval(idx: number) {
|
||||
if (idx === 0) return false;
|
||||
if (idx === xAxis.value.length - 1) return false;
|
||||
return true;
|
||||
},
|
||||
},
|
||||
axisLabel: {
|
||||
color: '#86909C',
|
||||
formatter(value: number, idx: number) {
|
||||
if (idx === 0) return '';
|
||||
if (idx === xAxis.value.length - 1) return '';
|
||||
return `${value}`;
|
||||
},
|
||||
},
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
axisLabel: {
|
||||
color: '#86909C',
|
||||
formatter: '{value}%',
|
||||
},
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
color: isDark ? '#3F3F3F' : '#E5E6EB',
|
||||
},
|
||||
},
|
||||
},
|
||||
tooltip: {
|
||||
show: true,
|
||||
trigger: 'axis',
|
||||
formatter(params) {
|
||||
const [firstElement] = params as ToolTipFormatterParams[];
|
||||
return `<div>
|
||||
<p class="tooltip-title">${firstElement.axisValueLabel}</p>
|
||||
${tooltipItemsHtmlString(params as ToolTipFormatterParams[])}
|
||||
</div>`;
|
||||
},
|
||||
className: 'echarts-tooltip-diy',
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '纯文本',
|
||||
data: textChartsData.value,
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
showSymbol: false,
|
||||
color: isDark ? '#3D72F6' : '#246EFF',
|
||||
symbol: 'circle',
|
||||
symbolSize: 10,
|
||||
emphasis: {
|
||||
focus: 'series',
|
||||
itemStyle: {
|
||||
borderWidth: 2,
|
||||
borderColor: '#E0E3FF',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: '图文类',
|
||||
data: imgChartsData.value,
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
showSymbol: false,
|
||||
color: isDark ? '#A079DC' : '#00B2FF',
|
||||
symbol: 'circle',
|
||||
symbolSize: 10,
|
||||
emphasis: {
|
||||
focus: 'series',
|
||||
itemStyle: {
|
||||
borderWidth: 2,
|
||||
borderColor: '#E2F2FF',
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
name: '视频类',
|
||||
data: videoChartsData.value,
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
showSymbol: false,
|
||||
color: isDark ? '#6CAAF5' : '#81E2FF',
|
||||
symbol: 'circle',
|
||||
symbolSize: 10,
|
||||
emphasis: {
|
||||
focus: 'series',
|
||||
itemStyle: {
|
||||
borderWidth: 2,
|
||||
borderColor: '#D9F6FF',
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
dataZoom: [
|
||||
{
|
||||
bottom: 40,
|
||||
type: 'slider',
|
||||
left: 40,
|
||||
right: 14,
|
||||
height: 14,
|
||||
borderColor: 'transparent',
|
||||
handleIcon:
|
||||
'image://http://p3-armor.byteimg.com/tos-cn-i-49unhts6dw/1ee5a8c6142b2bcf47d2a9f084096447.svg~tplv-49unhts6dw-image.image',
|
||||
handleSize: '20',
|
||||
handleStyle: {
|
||||
shadowColor: 'rgba(0, 0, 0, 0.2)',
|
||||
shadowBlur: 4,
|
||||
},
|
||||
brushSelect: false,
|
||||
backgroundColor: isDark ? '#313132' : '#F2F3F5',
|
||||
},
|
||||
{
|
||||
type: 'inside',
|
||||
start: 0,
|
||||
end: 100,
|
||||
zoomOnMouseWheel: false,
|
||||
},
|
||||
],
|
||||
};
|
||||
});
|
||||
const fetchData = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const { data: chartData } = await queryContentPeriodAnalysis();
|
||||
xAxis.value = chartData.xAxis;
|
||||
chartData.data.forEach((el) => {
|
||||
if (el.name === '纯文本') {
|
||||
textChartsData.value = el.value;
|
||||
} else if (el.name === '图文类') {
|
||||
imgChartsData.value = el.value;
|
||||
}
|
||||
videoChartsData.value = el.value;
|
||||
});
|
||||
} catch (err) {
|
||||
// you can report use errorHandler or other
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
fetchData();
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.chart-box {
|
||||
width: 100%;
|
||||
height: 230px;
|
||||
}
|
||||
</style>
|
|
@ -1,162 +0,0 @@
|
|||
<template>
|
||||
<a-spin :loading="loading" style="width: 100%">
|
||||
<a-card class="general-card" :header-style="{ paddingBottom: '14px' }">
|
||||
<template #title>
|
||||
{{ $t('dataAnalysis.contentPublishRatio') }}
|
||||
</template>
|
||||
<template #extra>
|
||||
<a-link>{{ $t('workplace.viewMore') }}</a-link>
|
||||
</template>
|
||||
<Chart style="width: 100%; height: 347px" :option="chartOption" />
|
||||
</a-card>
|
||||
</a-spin>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import { ToolTipFormatterParams } from '@/types/echarts';
|
||||
import useLoading from '@/hooks/loading';
|
||||
import {
|
||||
queryContentPublish,
|
||||
ContentPublishRecord,
|
||||
} from '@/api/visualization';
|
||||
import useChartOption from '@/hooks/chart-option';
|
||||
|
||||
const tooltipItemsHtmlString = (items: ToolTipFormatterParams[]) => {
|
||||
return items
|
||||
.map(
|
||||
(el) => `<div class="content-panel">
|
||||
<p>
|
||||
<span style="background-color: ${
|
||||
el.color
|
||||
}" class="tooltip-item-icon"></span>
|
||||
<span>
|
||||
${el.seriesName}
|
||||
</span>
|
||||
</p>
|
||||
<span class="tooltip-value">
|
||||
${Number(el.value).toLocaleString()}
|
||||
</span>
|
||||
</div>`
|
||||
)
|
||||
.join('');
|
||||
};
|
||||
|
||||
const { loading, setLoading } = useLoading(true);
|
||||
const xAxis = ref<string[]>([]);
|
||||
const textChartsData = ref<number[]>([]);
|
||||
const imgChartsData = ref<number[]>([]);
|
||||
const videoChartsData = ref<number[]>([]);
|
||||
const { chartOption } = useChartOption((isDark) => {
|
||||
return {
|
||||
grid: {
|
||||
left: '4%',
|
||||
right: 0,
|
||||
top: '20',
|
||||
bottom: '60',
|
||||
},
|
||||
legend: {
|
||||
bottom: 0,
|
||||
icon: 'circle',
|
||||
textStyle: {
|
||||
color: '#4E5969',
|
||||
},
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
data: xAxis.value,
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: isDark ? '#3f3f3f' : '#A9AEB8',
|
||||
},
|
||||
},
|
||||
axisTick: {
|
||||
show: true,
|
||||
alignWithLabel: true,
|
||||
lineStyle: {
|
||||
color: '#86909C',
|
||||
},
|
||||
},
|
||||
axisLabel: {
|
||||
color: '#86909C',
|
||||
},
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
axisLabel: {
|
||||
color: '#86909C',
|
||||
formatter(value: number, idx: number) {
|
||||
if (idx === 0) return `${value}`;
|
||||
return `${value / 1000}k`;
|
||||
},
|
||||
},
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
color: isDark ? '#3F3F3F' : '#E5E6EB',
|
||||
},
|
||||
},
|
||||
},
|
||||
tooltip: {
|
||||
show: true,
|
||||
trigger: 'axis',
|
||||
formatter(params) {
|
||||
const [firstElement] = params as ToolTipFormatterParams[];
|
||||
return `<div>
|
||||
<p class="tooltip-title">${firstElement.axisValueLabel}</p>
|
||||
${tooltipItemsHtmlString(params as ToolTipFormatterParams[])}
|
||||
</div>`;
|
||||
},
|
||||
className: 'echarts-tooltip-diy',
|
||||
},
|
||||
series: [
|
||||
{
|
||||
name: '纯文本',
|
||||
data: textChartsData.value,
|
||||
stack: 'one',
|
||||
type: 'bar',
|
||||
barWidth: 16,
|
||||
color: isDark ? '#4A7FF7' : '#246EFF',
|
||||
},
|
||||
{
|
||||
name: '图文类',
|
||||
data: imgChartsData.value,
|
||||
stack: 'one',
|
||||
type: 'bar',
|
||||
color: isDark ? '#085FEF' : '#00B2FF',
|
||||
},
|
||||
{
|
||||
name: '视频类',
|
||||
data: videoChartsData.value,
|
||||
stack: 'one',
|
||||
type: 'bar',
|
||||
color: isDark ? '#01349F' : '#81E2FF',
|
||||
itemStyle: {
|
||||
borderRadius: 2,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
});
|
||||
const fetchData = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const { data: chartData } = await queryContentPublish();
|
||||
xAxis.value = chartData[0].x;
|
||||
chartData.forEach((el: ContentPublishRecord) => {
|
||||
if (el.name === '纯文本') {
|
||||
textChartsData.value = el.y;
|
||||
} else if (el.name === '图文类') {
|
||||
imgChartsData.value = el.y;
|
||||
}
|
||||
videoChartsData.value = el.y;
|
||||
});
|
||||
} catch (err) {
|
||||
// you can report use errorHandler or other
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
fetchData();
|
||||
</script>
|
||||
|
||||
<style scoped lang="less"></style>
|
|
@ -1,75 +0,0 @@
|
|||
<template>
|
||||
<a-spin :loading="loading" style="width: 100%">
|
||||
<a-card class="general-card" :header-style="{ paddingBottom: '14px' }">
|
||||
<template #title>
|
||||
{{ $t('dataAnalysis.popularAuthor') }}
|
||||
</template>
|
||||
<template #extra>
|
||||
<a-link>{{ $t('workplace.viewMore') }}</a-link>
|
||||
</template>
|
||||
<a-table
|
||||
:data="tableData.list"
|
||||
:pagination="false"
|
||||
:bordered="false"
|
||||
style="margin-bottom: 20px"
|
||||
:scroll="{ x: '100%', y: '350px' }"
|
||||
>
|
||||
<template #columns>
|
||||
<a-table-column
|
||||
:title="$t('dataAnalysis.popularAuthor.column.ranking')"
|
||||
data-index="ranking"
|
||||
>
|
||||
</a-table-column>
|
||||
<a-table-column
|
||||
:title="$t('dataAnalysis.popularAuthor.column.author')"
|
||||
data-index="author"
|
||||
>
|
||||
</a-table-column>
|
||||
<a-table-column
|
||||
:title="$t('dataAnalysis.popularAuthor.column.content')"
|
||||
data-index="contentCount"
|
||||
:sortable="{
|
||||
sortDirections: ['ascend', 'descend'],
|
||||
}"
|
||||
>
|
||||
</a-table-column>
|
||||
<a-table-column
|
||||
:title="$t('dataAnalysis.popularAuthor.column.click')"
|
||||
data-index="clickCount"
|
||||
:sortable="{
|
||||
sortDirections: ['ascend', 'descend'],
|
||||
}"
|
||||
>
|
||||
</a-table-column>
|
||||
</template>
|
||||
</a-table>
|
||||
</a-card>
|
||||
</a-spin>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import useLoading from '@/hooks/loading';
|
||||
import { queryPopularAuthor, PopularAuthorRes } from '@/api/visualization';
|
||||
|
||||
const { loading, setLoading } = useLoading();
|
||||
const tableData = ref<PopularAuthorRes>({ list: [] });
|
||||
const fetchData = async () => {
|
||||
try {
|
||||
setLoading(true);
|
||||
const { data } = await queryPopularAuthor();
|
||||
tableData.value = data;
|
||||
} catch (err) {
|
||||
// you can report use errorHandler or other
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
fetchData();
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.general-card {
|
||||
max-height: 425px;
|
||||
}
|
||||
</style>
|
|
@ -1,65 +0,0 @@
|
|||
<template>
|
||||
<a-card
|
||||
class="general-card"
|
||||
:title="$t('dataAnalysis.title.publicOpinion')"
|
||||
:header-style="{ paddingBottom: '12px' }"
|
||||
>
|
||||
<a-grid :cols="24" :col-gap="12" :row-gap="12">
|
||||
<a-grid-item :span="{ xs: 12, sm: 12, md: 12, lg: 12, xl: 6, xxl: 6 }">
|
||||
<ChainItem
|
||||
:title="$t('dataAnalysis.card.title.allVisitors')"
|
||||
quota="visitors"
|
||||
chart-type="line"
|
||||
:card-style="{
|
||||
background: isDark
|
||||
? 'linear-gradient(180deg, #284991 0%, #122B62 100%)'
|
||||
: 'linear-gradient(180deg, #f2f9fe 0%, #e6f4fe 100%)',
|
||||
}"
|
||||
/>
|
||||
</a-grid-item>
|
||||
<a-grid-item :span="{ xs: 12, sm: 12, md: 12, lg: 12, xl: 6, xxl: 6 }">
|
||||
<ChainItem
|
||||
:title="$t('dataAnalysis.card.title.contentPublished')"
|
||||
quota="published"
|
||||
chart-type="bar"
|
||||
:card-style="{
|
||||
background: isDark
|
||||
? ' linear-gradient(180deg, #3D492E 0%, #263827 100%)'
|
||||
: 'linear-gradient(180deg, #F5FEF2 0%, #E6FEEE 100%)',
|
||||
}"
|
||||
/>
|
||||
</a-grid-item>
|
||||
<a-grid-item :span="{ xs: 12, sm: 12, md: 12, lg: 12, xl: 6, xxl: 6 }">
|
||||
<ChainItem
|
||||
:title="$t('dataAnalysis.card.title.totalComment')"
|
||||
quota="comment"
|
||||
chart-type="line"
|
||||
:card-style="{
|
||||
background: isDark
|
||||
? 'linear-gradient(180deg, #294B94 0%, #0F275C 100%)'
|
||||
: 'linear-gradient(180deg, #f2f9fe 0%, #e6f4fe 100%)',
|
||||
}"
|
||||
/>
|
||||
</a-grid-item>
|
||||
<a-grid-item :span="{ xs: 12, sm: 12, md: 12, lg: 12, xl: 6, xxl: 6 }">
|
||||
<ChainItem
|
||||
:title="$t('dataAnalysis.card.title.totalShare')"
|
||||
quota="share"
|
||||
chart-type="pie"
|
||||
:card-style="{
|
||||
background: isDark
|
||||
? 'linear-gradient(180deg, #312565 0%, #201936 100%)'
|
||||
: 'linear-gradient(180deg, #F7F7FF 0%, #ECECFF 100%)',
|
||||
}"
|
||||
/>
|
||||
</a-grid-item>
|
||||
</a-grid>
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import useThemes from '@/hooks/themes';
|
||||
import ChainItem from './chain-item.vue';
|
||||
|
||||
const { isDark } = useThemes();
|
||||
</script>
|
|
@ -1,65 +0,0 @@
|
|||
<template>
|
||||
<div class="container">
|
||||
<Breadcrumb
|
||||
:items="['menu.visualization', 'menu.visualization.dataAnalysis']"
|
||||
/>
|
||||
<a-space direction="vertical" :size="12" fill>
|
||||
<a-space direction="vertical" :size="16" fill>
|
||||
<div class="space-unit">
|
||||
<PublicOpinion />
|
||||
</div>
|
||||
<div>
|
||||
<a-grid :cols="24" :col-gap="16" :row-gap="16">
|
||||
<a-grid-item
|
||||
:span="{ xs: 24, sm: 24, md: 24, lg: 24, xl: 16, xxl: 16 }"
|
||||
>
|
||||
<ContentPublishRatio />
|
||||
</a-grid-item>
|
||||
<a-grid-item
|
||||
:span="{ xs: 24, sm: 24, md: 24, lg: 24, xl: 8, xxl: 8 }"
|
||||
>
|
||||
<PopularAuthor />
|
||||
</a-grid-item>
|
||||
</a-grid>
|
||||
</div>
|
||||
<div>
|
||||
<ContentPeriodAnalysis />
|
||||
</div>
|
||||
</a-space>
|
||||
</a-space>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import PublicOpinion from './components/public-opinion.vue';
|
||||
import ContentPeriodAnalysis from './components/content-period-analysis.vue';
|
||||
import ContentPublishRatio from './components/content-publish-ratio.vue';
|
||||
import PopularAuthor from './components/popular-author.vue';
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'DataAnalysis',
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.container {
|
||||
padding: 0 20px 20px 20px;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.space-unit {
|
||||
background-color: var(--color-bg-2);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
.title-fix {
|
||||
margin: 0 0 12px 0;
|
||||
font-size: 14;
|
||||
}
|
||||
:deep(.section-title) {
|
||||
margin: 0 0 12px 0;
|
||||
font-size: 14px;
|
||||
}
|
||||
</style>
|
|
@ -1,16 +0,0 @@
|
|||
export default {
|
||||
'menu.visualization.dataAnalysis': 'Analysis',
|
||||
'dataAnalysis.title.publicOpinion': 'Public Opinion Analysis',
|
||||
'dataAnalysis.card.title.allVisitors': 'All Visitors',
|
||||
'dataAnalysis.card.title.contentPublished': 'Content Published',
|
||||
'dataAnalysis.card.title.totalComment': 'Total Comment',
|
||||
'dataAnalysis.card.title.totalShare': 'Total Share',
|
||||
'dataAnalysis.card.yesterday': 'Yesterday',
|
||||
'dataAnalysis.contentPublishRatio': 'Content Publishing Ratio',
|
||||
'dataAnalysis.popularAuthor': 'Popular Author',
|
||||
'dataAnalysis.popularAuthor.column.ranking': 'ranking',
|
||||
'dataAnalysis.popularAuthor.column.author': 'author',
|
||||
'dataAnalysis.popularAuthor.column.content': 'Content Number',
|
||||
'dataAnalysis.popularAuthor.column.click': 'Click Number',
|
||||
'dataAnalysis.contentPeriodAnalysis': 'Content Period Analysis',
|
||||
};
|
|
@ -1,16 +0,0 @@
|
|||
export default {
|
||||
'menu.visualization.dataAnalysis': '分析页',
|
||||
'dataAnalysis.title.publicOpinion': '舆情分析',
|
||||
'dataAnalysis.card.title.allVisitors': '访问总人次',
|
||||
'dataAnalysis.card.title.contentPublished': '内容发布量',
|
||||
'dataAnalysis.card.title.totalComment': '评论总量',
|
||||
'dataAnalysis.card.title.totalShare': '分享总量',
|
||||
'dataAnalysis.card.yesterday': '较昨日',
|
||||
'dataAnalysis.contentPublishRatio': '内容发布比例',
|
||||
'dataAnalysis.popularAuthor': '热门作者榜单',
|
||||
'dataAnalysis.popularAuthor.column.ranking': '排名',
|
||||
'dataAnalysis.popularAuthor.column.author': '作者',
|
||||
'dataAnalysis.popularAuthor.column.content': '内容量',
|
||||
'dataAnalysis.popularAuthor.column.click': '点击量',
|
||||
'dataAnalysis.contentPeriodAnalysis': '内容时段分析',
|
||||
};
|
|
@ -1,108 +0,0 @@
|
|||
import Mock from 'mockjs';
|
||||
import setupMock, { successResponseWrap } from '@/utils/setup-mock';
|
||||
import { PostData } from '@/types/global';
|
||||
|
||||
setupMock({
|
||||
setup() {
|
||||
Mock.mock(
|
||||
new RegExp('/api/public-opinion-analysis'),
|
||||
(params: PostData) => {
|
||||
const { quota = 'visitors' } = JSON.parse(params.body);
|
||||
if (['visitors', 'comment'].includes(quota)) {
|
||||
const year = new Date().getFullYear();
|
||||
const getLineData = (name: number) => {
|
||||
return new Array(12).fill(0).map((_item, index) => ({
|
||||
x: `${index + 1}月`,
|
||||
y: Mock.Random.natural(0, 100),
|
||||
name: String(name),
|
||||
}));
|
||||
};
|
||||
return successResponseWrap({
|
||||
count: 5670,
|
||||
growth: 206.32,
|
||||
chartData: [...getLineData(year), ...getLineData(year - 1)],
|
||||
});
|
||||
}
|
||||
if (['published'].includes(quota)) {
|
||||
const year = new Date().getFullYear();
|
||||
const getLineData = (name: number) => {
|
||||
return new Array(12).fill(0).map((_item, index) => ({
|
||||
x: `${index + 1}日`,
|
||||
y: Mock.Random.natural(20, 100),
|
||||
name: String(name),
|
||||
}));
|
||||
};
|
||||
return successResponseWrap({
|
||||
count: 5670,
|
||||
growth: 206.32,
|
||||
chartData: [...getLineData(year)],
|
||||
});
|
||||
}
|
||||
return successResponseWrap({
|
||||
count: 5670,
|
||||
growth: 206.32,
|
||||
chartData: [
|
||||
// itemStyle for demo
|
||||
{ name: '文本类', value: 25, itemStyle: { color: '#8D4EDA' } },
|
||||
{ name: '图文类', value: 35, itemStyle: { color: '#165DFF' } },
|
||||
{ name: '视频类', value: 40, itemStyle: { color: '#00B2FF' } },
|
||||
],
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
Mock.mock(new RegExp('/api/content-period-analysis'), () => {
|
||||
const getLineData = (name: string) => {
|
||||
return {
|
||||
name,
|
||||
value: new Array(12).fill(0).map(() => Mock.Random.natural(30, 90)),
|
||||
};
|
||||
};
|
||||
return successResponseWrap({
|
||||
xAxis: new Array(12).fill(0).map((_item, index) => `${index * 2}:00`),
|
||||
data: [
|
||||
getLineData('纯文本'),
|
||||
getLineData('图文类'),
|
||||
getLineData('视频类'),
|
||||
],
|
||||
});
|
||||
});
|
||||
|
||||
Mock.mock(new RegExp('/api/content-publish'), () => {
|
||||
const generateLineData = (name: string) => {
|
||||
const result = {
|
||||
name,
|
||||
x: [] as string[],
|
||||
y: [] as number[],
|
||||
};
|
||||
new Array(12).fill(0).forEach((_item, index) => {
|
||||
result.x.push(`${index * 2}:00`);
|
||||
result.y.push(Mock.Random.natural(1000, 3000));
|
||||
});
|
||||
return result;
|
||||
};
|
||||
return successResponseWrap([
|
||||
generateLineData('纯文本'),
|
||||
generateLineData('图文类'),
|
||||
generateLineData('视频类'),
|
||||
]);
|
||||
});
|
||||
|
||||
Mock.mock(new RegExp('/api/popular-author/list'), () => {
|
||||
const generateData = () => {
|
||||
const list = new Array(7).fill(0).map((_item, index) => ({
|
||||
ranking: index + 1,
|
||||
author: Mock.mock('@ctitle(5)'),
|
||||
contentCount: Mock.mock(/[0-9]{4}/),
|
||||
clickCount: Mock.mock(/[0-9]{4}/),
|
||||
}));
|
||||
return {
|
||||
list,
|
||||
};
|
||||
};
|
||||
return successResponseWrap({
|
||||
...generateData(),
|
||||
});
|
||||
});
|
||||
},
|
||||
});
|
|
@ -1,152 +0,0 @@
|
|||
<template>
|
||||
<a-spin :loading="loading" style="width: 100%">
|
||||
<a-card
|
||||
class="general-card"
|
||||
:title="title"
|
||||
:header-style="{ paddingBottom: '12px' }"
|
||||
>
|
||||
<div class="content">
|
||||
<a-statistic
|
||||
:value="count"
|
||||
:show-group-separator="true"
|
||||
:value-from="0"
|
||||
animation
|
||||
/>
|
||||
<a-typography-text
|
||||
class="percent-text"
|
||||
:type="isUp ? 'danger' : 'success'"
|
||||
>
|
||||
{{ growth }}%
|
||||
<icon-arrow-rise v-if="isUp" />
|
||||
<icon-arrow-fall v-else />
|
||||
</a-typography-text>
|
||||
</div>
|
||||
<div class="chart">
|
||||
<Chart :option="chartOption" />
|
||||
</div>
|
||||
</a-card>
|
||||
</a-spin>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import useLoading from '@/hooks/loading';
|
||||
import { queryDataChainGrowth, DataChainGrowth } from '@/api/visualization';
|
||||
import useChartOption from '@/hooks/chart-option';
|
||||
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
quota: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
chartType: {
|
||||
type: String,
|
||||
default: '',
|
||||
},
|
||||
});
|
||||
const { loading, setLoading } = useLoading(true);
|
||||
const count = ref(0);
|
||||
const growth = ref(100);
|
||||
const isUp = computed(() => growth.value > 50);
|
||||
const chartData = ref<any>([]);
|
||||
const { chartOption } = useChartOption(() => {
|
||||
return {
|
||||
grid: {
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 0,
|
||||
},
|
||||
xAxis: {
|
||||
type: 'category',
|
||||
show: false,
|
||||
},
|
||||
yAxis: {
|
||||
show: false,
|
||||
},
|
||||
tooltip: {
|
||||
show: true,
|
||||
trigger: 'axis',
|
||||
formatter: '{c}',
|
||||
},
|
||||
series: [
|
||||
{
|
||||
data: chartData.value,
|
||||
...(props.chartType === 'bar'
|
||||
? {
|
||||
type: 'bar',
|
||||
barWidth: 7,
|
||||
barGap: '0',
|
||||
}
|
||||
: {
|
||||
type: 'line',
|
||||
showSymbol: false,
|
||||
smooth: true,
|
||||
lineStyle: {
|
||||
color: '#4080FF',
|
||||
},
|
||||
}),
|
||||
},
|
||||
],
|
||||
};
|
||||
});
|
||||
const fetchData = async (params: DataChainGrowth) => {
|
||||
try {
|
||||
const { data } = await queryDataChainGrowth(params);
|
||||
const { chartData: resChartData } = data;
|
||||
count.value = data.count;
|
||||
growth.value = data.growth;
|
||||
resChartData.data.value.forEach((el, idx) => {
|
||||
if (props.chartType === 'bar') {
|
||||
chartData.value.push({
|
||||
value: el,
|
||||
itemStyle: {
|
||||
color: idx % 2 ? '#468DFF' : '#86DF6C',
|
||||
},
|
||||
});
|
||||
} else {
|
||||
chartData.value.push(el);
|
||||
}
|
||||
});
|
||||
} catch (err) {
|
||||
// you can report use errorHandler or other
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
fetchData({ quota: props.quota });
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.general-card {
|
||||
min-height: 204px;
|
||||
}
|
||||
.content {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
width: 100%;
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
.percent-text {
|
||||
margin-left: 16px;
|
||||
}
|
||||
.chart {
|
||||
width: 100%;
|
||||
height: 80px;
|
||||
vertical-align: bottom;
|
||||
}
|
||||
|
||||
.unit {
|
||||
padding-left: 8px;
|
||||
font-size: 12px;
|
||||
}
|
||||
|
||||
.label {
|
||||
padding-right: 8px;
|
||||
font-size: 12px;
|
||||
}
|
||||
</style>
|
|
@ -1,230 +0,0 @@
|
|||
<template>
|
||||
<a-spin :loading="loading" style="width: 100%">
|
||||
<a-card
|
||||
class="general-card"
|
||||
:title="$t('multiDAnalysis.card.title.contentPublishingSource')"
|
||||
>
|
||||
<Chart style="width: 100%; height: 300px" :option="chartOption" />
|
||||
</a-card>
|
||||
</a-spin>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import useLoading from '@/hooks/loading';
|
||||
import useChartOption from '@/hooks/chart-option';
|
||||
|
||||
const { chartOption } = useChartOption((isDark) => {
|
||||
const graphicElementStyle = {
|
||||
textAlign: 'center',
|
||||
fill: isDark ? 'rgba(255,255,255,0.7)' : '#4E5969',
|
||||
fontSize: 14,
|
||||
lineWidth: 10,
|
||||
fontWeight: 'bold',
|
||||
};
|
||||
return {
|
||||
legend: {
|
||||
left: 'center',
|
||||
data: ['UGC原创', '国外网站', '转载文章', '行业报告', '其他'],
|
||||
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: '9.6%',
|
||||
top: 'center',
|
||||
style: {
|
||||
text: '纯文本',
|
||||
...graphicElementStyle,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
left: 'center',
|
||||
top: 'center',
|
||||
style: {
|
||||
text: '图文类',
|
||||
...graphicElementStyle,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
left: '86.6%',
|
||||
top: 'center',
|
||||
style: {
|
||||
text: '视频类',
|
||||
...graphicElementStyle,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
series: [
|
||||
{
|
||||
type: 'pie',
|
||||
radius: ['50%', '70%'],
|
||||
center: ['11%', '50%'],
|
||||
label: {
|
||||
formatter: '{d}% ',
|
||||
color: isDark ? 'rgba(255, 255, 255, 0.7)' : '#4E5969',
|
||||
},
|
||||
itemStyle: {
|
||||
borderColor: isDark ? '#000' : '#fff',
|
||||
borderWidth: 1,
|
||||
},
|
||||
data: [
|
||||
{
|
||||
value: [148564],
|
||||
name: 'UGC原创',
|
||||
itemStyle: {
|
||||
color: '#249EFF',
|
||||
},
|
||||
},
|
||||
{
|
||||
value: [334271],
|
||||
name: '国外网站',
|
||||
itemStyle: {
|
||||
color: '#846BCE',
|
||||
},
|
||||
},
|
||||
{
|
||||
value: [445694],
|
||||
name: '转载文章',
|
||||
itemStyle: {
|
||||
color: '#21CCFF',
|
||||
},
|
||||
},
|
||||
{
|
||||
value: [445694],
|
||||
name: '行业报告',
|
||||
itemStyle: {
|
||||
color: '#0E42D2',
|
||||
},
|
||||
},
|
||||
{
|
||||
value: [445694],
|
||||
name: '其他',
|
||||
itemStyle: {
|
||||
color: '#86DF6C',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'pie',
|
||||
radius: ['50%', '70%'],
|
||||
center: ['50%', '50%'],
|
||||
label: {
|
||||
formatter: '{d}% ',
|
||||
color: isDark ? 'rgba(255, 255, 255, 0.7)' : '#4E5969',
|
||||
},
|
||||
itemStyle: {
|
||||
borderColor: isDark ? '#000' : '#fff',
|
||||
borderWidth: 1,
|
||||
},
|
||||
data: [
|
||||
{
|
||||
value: [148564],
|
||||
name: 'UGC原创',
|
||||
itemStyle: {
|
||||
color: '#249EFF',
|
||||
},
|
||||
},
|
||||
{
|
||||
value: [334271],
|
||||
name: '国外网站',
|
||||
itemStyle: {
|
||||
color: '#846BCE',
|
||||
},
|
||||
},
|
||||
{
|
||||
value: [445694],
|
||||
name: '转载文章',
|
||||
itemStyle: {
|
||||
color: '#21CCFF',
|
||||
},
|
||||
},
|
||||
{
|
||||
value: [445694],
|
||||
name: '行业报告',
|
||||
itemStyle: {
|
||||
color: '#0E42D2',
|
||||
},
|
||||
},
|
||||
{
|
||||
value: [445694],
|
||||
name: '其他',
|
||||
itemStyle: {
|
||||
color: '#86DF6C',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
type: 'pie',
|
||||
radius: ['50%', '70%'],
|
||||
center: ['88%', '50%'],
|
||||
label: {
|
||||
formatter: '{d}% ',
|
||||
color: isDark ? 'rgba(255, 255, 255, 0.7)' : '#4E5969',
|
||||
},
|
||||
itemStyle: {
|
||||
borderColor: isDark ? '#000' : '#fff',
|
||||
borderWidth: 1,
|
||||
},
|
||||
data: [
|
||||
{
|
||||
value: [148564],
|
||||
name: 'UGC原创',
|
||||
itemStyle: {
|
||||
color: '#249EFF',
|
||||
},
|
||||
},
|
||||
{
|
||||
value: [334271],
|
||||
name: '国外网站',
|
||||
itemStyle: {
|
||||
color: '#846BCE',
|
||||
},
|
||||
},
|
||||
{
|
||||
value: [445694],
|
||||
name: '转载文章',
|
||||
itemStyle: {
|
||||
color: '#21CCFF',
|
||||
},
|
||||
},
|
||||
{
|
||||
value: [445694],
|
||||
name: '行业报告',
|
||||
itemStyle: {
|
||||
color: '#0E42D2',
|
||||
},
|
||||
},
|
||||
{
|
||||
value: [445694],
|
||||
name: '其他',
|
||||
itemStyle: {
|
||||
color: '#86DF6C',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
});
|
||||
const { loading } = useLoading(false);
|
||||
</script>
|
||||
|
||||
<style scoped lang="less"></style>
|
|
@ -1,103 +0,0 @@
|
|||
<template>
|
||||
<a-card
|
||||
class="general-card"
|
||||
:title="$t('multiDAnalysis.card.title.contentTypeDistribution')"
|
||||
:header-style="{ paddingBottom: 0 }"
|
||||
>
|
||||
<Chart style="height: 222px" :option="chartOption" />
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import useChartOption from '@/hooks/chart-option';
|
||||
|
||||
const { chartOption } = useChartOption((isDark) => {
|
||||
return {
|
||||
grid: {
|
||||
left: 0,
|
||||
right: 0,
|
||||
top: 0,
|
||||
bottom: 20,
|
||||
},
|
||||
legend: {
|
||||
show: true,
|
||||
top: 'center',
|
||||
right: '0',
|
||||
orient: 'vertical',
|
||||
icon: 'circle',
|
||||
itemWidth: 10,
|
||||
itemHeight: 10,
|
||||
itemGap: 20,
|
||||
textStyle: {
|
||||
color: isDark ? '#ffffff' : '#4E5969',
|
||||
},
|
||||
},
|
||||
radar: {
|
||||
center: ['40%', '50%'],
|
||||
radius: 80,
|
||||
indicator: [
|
||||
{ name: '国际', max: 6500 },
|
||||
{ name: '财经', max: 22000 },
|
||||
{ name: '科技', max: 30000 },
|
||||
{ name: '其他', max: 38000 },
|
||||
{ name: '体育', max: 52000 },
|
||||
{ name: '娱乐', max: 25000 },
|
||||
],
|
||||
axisName: {
|
||||
color: isDark ? '#ffffff' : '#1D2129',
|
||||
},
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: isDark ? '#484849' : '#E5E6EB',
|
||||
},
|
||||
},
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
color: isDark ? '#484849' : '#E5E6EB',
|
||||
},
|
||||
},
|
||||
splitArea: {
|
||||
areaStyle: {
|
||||
color: [],
|
||||
},
|
||||
},
|
||||
},
|
||||
series: [
|
||||
{
|
||||
type: 'radar',
|
||||
areaStyle: {
|
||||
opacity: 0.2,
|
||||
},
|
||||
data: [
|
||||
{
|
||||
value: [4850, 19000, 19000, 29500, 35200, 20000],
|
||||
name: '纯文本',
|
||||
symbol: 'none',
|
||||
itemStyle: {
|
||||
color: isDark ? '#6CAAF5' : '#249EFF',
|
||||
},
|
||||
},
|
||||
{
|
||||
value: [2250, 17000, 21000, 23500, 42950, 22000],
|
||||
name: '图文类',
|
||||
symbol: 'none',
|
||||
itemStyle: {
|
||||
color: isDark ? '#A079DC' : '#313CA9',
|
||||
},
|
||||
},
|
||||
{
|
||||
value: [5850, 11000, 26000, 27500, 46950, 18000],
|
||||
name: '视频类',
|
||||
symbol: 'none',
|
||||
itemStyle: {
|
||||
color: isDark ? '#3D72F6' : '#21CCFF',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="less"></style>
|
|
@ -1,38 +0,0 @@
|
|||
<template>
|
||||
<div>
|
||||
<a-row :gutter="16">
|
||||
<a-col :span="6">
|
||||
<ChainItem
|
||||
:title="$t('multiDAnalysis.card.title.retentionTrends')"
|
||||
quota="retentionTrends"
|
||||
chart-type="line"
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<ChainItem
|
||||
:title="$t('multiDAnalysis.card.title.userRetention')"
|
||||
quota="userRetention"
|
||||
chart-type="bar"
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<ChainItem
|
||||
:title="$t('multiDAnalysis.card.title.contentConsumptionTrends')"
|
||||
quota="contentConsumptionTrends"
|
||||
chart-type="line"
|
||||
/>
|
||||
</a-col>
|
||||
<a-col :span="6">
|
||||
<ChainItem
|
||||
:title="$t('multiDAnalysis.card.title.contentConsumption')"
|
||||
quota="contentConsumption"
|
||||
chart-type="bar"
|
||||
/>
|
||||
</a-col>
|
||||
</a-row>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import ChainItem from './chain-item.vue';
|
||||
</script>
|
|
@ -1,310 +0,0 @@
|
|||
<template>
|
||||
<a-spin :loading="loading" style="width: 100%">
|
||||
<a-card
|
||||
class="general-card"
|
||||
:title="$t('multiDAnalysis.card.title.dataOverview')"
|
||||
>
|
||||
<a-row justify="space-between">
|
||||
<a-col v-for="(item, idx) in renderData" :key="idx" :span="6">
|
||||
<a-statistic
|
||||
:title="item.title"
|
||||
:value="item.value"
|
||||
show-group-separator
|
||||
:value-from="0"
|
||||
animation
|
||||
>
|
||||
<template #prefix>
|
||||
<span
|
||||
class="statistic-prefix"
|
||||
:style="{ background: item.prefix.background }"
|
||||
>
|
||||
<component
|
||||
:is="item.prefix.icon"
|
||||
:style="{ color: item.prefix.iconColor }"
|
||||
/>
|
||||
</span>
|
||||
</template>
|
||||
</a-statistic>
|
||||
</a-col>
|
||||
</a-row>
|
||||
<Chart style="height: 328px; margin-top: 20px" :option="chartOption" />
|
||||
</a-card>
|
||||
</a-spin>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import { useI18n } from 'vue-i18n';
|
||||
import { LineSeriesOption } from 'echarts';
|
||||
import { queryDataOverview } from '@/api/visualization';
|
||||
import useLoading from '@/hooks/loading';
|
||||
import { ToolTipFormatterParams } from '@/types/echarts';
|
||||
import useThemes from '@/hooks/themes';
|
||||
import useChartOption from '@/hooks/chart-option';
|
||||
|
||||
const tooltipItemsHtmlString = (items: ToolTipFormatterParams[]) => {
|
||||
return items
|
||||
.map(
|
||||
(el) => `<div class="content-panel">
|
||||
<p>
|
||||
<span style="background-color: ${
|
||||
el.color
|
||||
}" class="tooltip-item-icon"></span><span>${el.seriesName}</span>
|
||||
</p>
|
||||
<span class="tooltip-value">${el.value.toLocaleString()}</span>
|
||||
</div>`
|
||||
)
|
||||
.reverse()
|
||||
.join('');
|
||||
};
|
||||
|
||||
const generateSeries = (
|
||||
name: string,
|
||||
lineColor: string,
|
||||
itemBorderColor: string,
|
||||
data: number[]
|
||||
): LineSeriesOption => {
|
||||
return {
|
||||
name,
|
||||
data,
|
||||
stack: 'Total',
|
||||
type: 'line',
|
||||
smooth: true,
|
||||
symbol: 'circle',
|
||||
symbolSize: 10,
|
||||
itemStyle: {
|
||||
color: lineColor,
|
||||
},
|
||||
emphasis: {
|
||||
focus: 'series',
|
||||
itemStyle: {
|
||||
color: lineColor,
|
||||
borderWidth: 2,
|
||||
borderColor: itemBorderColor,
|
||||
},
|
||||
},
|
||||
lineStyle: {
|
||||
width: 2,
|
||||
color: lineColor,
|
||||
},
|
||||
showSymbol: false,
|
||||
areaStyle: {
|
||||
opacity: 0.1,
|
||||
color: lineColor,
|
||||
},
|
||||
};
|
||||
};
|
||||
const { t } = useI18n();
|
||||
const { loading, setLoading } = useLoading(true);
|
||||
const { isDark } = useThemes();
|
||||
const renderData = computed(() => [
|
||||
{
|
||||
title: t('multiDAnalysis.dataOverview.contentProduction'),
|
||||
value: 1902,
|
||||
prefix: {
|
||||
icon: 'icon-edit',
|
||||
background: isDark.value ? '#593E2F' : '#FFE4BA',
|
||||
iconColor: isDark.value ? '#F29A43' : '#F77234',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('multiDAnalysis.dataOverview.contentClick'),
|
||||
value: 2445,
|
||||
prefix: {
|
||||
icon: 'icon-thumb-up',
|
||||
background: isDark.value ? '#3D5A62' : '#E8FFFB',
|
||||
iconColor: isDark.value ? '#6ED1CE' : '#33D1C9',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('multiDAnalysis.dataOverview.contentExposure'),
|
||||
value: 3034,
|
||||
prefix: {
|
||||
icon: 'icon-heart',
|
||||
background: isDark.value ? '#354276' : '#E8F3FF',
|
||||
iconColor: isDark.value ? '#4A7FF7' : '#165DFF',
|
||||
},
|
||||
},
|
||||
{
|
||||
title: t('multiDAnalysis.dataOverview.activeUsers'),
|
||||
value: 1275,
|
||||
prefix: {
|
||||
icon: 'icon-user',
|
||||
background: isDark.value ? '#3F385E' : '#F5E8FF',
|
||||
iconColor: isDark.value ? '#8558D3' : '#722ED1',
|
||||
},
|
||||
},
|
||||
]);
|
||||
const xAxis = ref<string[]>([]);
|
||||
const contentProductionData = ref<number[]>([]);
|
||||
const contentClickData = ref<number[]>([]);
|
||||
const contentExposureData = ref<number[]>([]);
|
||||
const activeUsersData = ref<number[]>([]);
|
||||
const { chartOption } = useChartOption((dark) => {
|
||||
return {
|
||||
grid: {
|
||||
left: '2.6%',
|
||||
right: '4',
|
||||
top: '40',
|
||||
bottom: '40',
|
||||
},
|
||||
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: false,
|
||||
},
|
||||
axisPointer: {
|
||||
show: true,
|
||||
lineStyle: {
|
||||
color: '#23ADFF',
|
||||
width: 2,
|
||||
},
|
||||
},
|
||||
},
|
||||
yAxis: {
|
||||
type: 'value',
|
||||
axisLine: {
|
||||
show: false,
|
||||
},
|
||||
axisLabel: {
|
||||
formatter(value: number, idx: number) {
|
||||
if (idx === 0) return String(value);
|
||||
return `${value / 1000}k`;
|
||||
},
|
||||
},
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
color: dark ? '#2E2E30' : '#F2F3F5',
|
||||
},
|
||||
},
|
||||
},
|
||||
tooltip: {
|
||||
trigger: 'axis',
|
||||
formatter(params) {
|
||||
const [firstElement] = params as ToolTipFormatterParams[];
|
||||
return `<div>
|
||||
<p class="tooltip-title">${firstElement.axisValueLabel}</p>
|
||||
${tooltipItemsHtmlString(params as ToolTipFormatterParams[])}
|
||||
</div>`;
|
||||
},
|
||||
className: 'echarts-tooltip-diy',
|
||||
},
|
||||
graphic: {
|
||||
elements: [
|
||||
{
|
||||
type: 'text',
|
||||
left: '2.6%',
|
||||
bottom: '18',
|
||||
style: {
|
||||
text: '12.10',
|
||||
textAlign: 'center',
|
||||
fill: '#4E5969',
|
||||
fontSize: 12,
|
||||
},
|
||||
},
|
||||
{
|
||||
type: 'text',
|
||||
right: '0',
|
||||
bottom: '18',
|
||||
style: {
|
||||
text: '12.17',
|
||||
textAlign: 'center',
|
||||
fill: '#4E5969',
|
||||
fontSize: 12,
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
series: [
|
||||
generateSeries(
|
||||
'内容生产量',
|
||||
'#722ED1',
|
||||
'#F5E8FF',
|
||||
contentProductionData.value
|
||||
),
|
||||
generateSeries(
|
||||
'内容点击量',
|
||||
'#F77234',
|
||||
'#FFE4BA',
|
||||
contentClickData.value
|
||||
),
|
||||
generateSeries(
|
||||
'内容曝光量',
|
||||
'#33D1C9',
|
||||
'#E8FFFB',
|
||||
contentExposureData.value
|
||||
),
|
||||
generateSeries(
|
||||
'活跃用户数',
|
||||
'#3469FF',
|
||||
'#E8F3FF',
|
||||
activeUsersData.value
|
||||
),
|
||||
],
|
||||
};
|
||||
});
|
||||
const fetchData = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const { data } = await queryDataOverview();
|
||||
xAxis.value = data.xAxis;
|
||||
data.data.forEach((el) => {
|
||||
if (el.name === '内容生产量') {
|
||||
contentProductionData.value = el.value;
|
||||
} else if (el.name === '内容点击量') {
|
||||
contentClickData.value = el.value;
|
||||
} else if (el.name === '内容曝光量') {
|
||||
contentExposureData.value = el.value;
|
||||
}
|
||||
activeUsersData.value = el.value;
|
||||
});
|
||||
} catch (err) {
|
||||
// you can report use errorHandler or other
|
||||
} finally {
|
||||
setLoading(false);
|
||||
}
|
||||
};
|
||||
fetchData();
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
:deep(.arco-statistic) {
|
||||
.arco-statistic-title {
|
||||
color: rgb(var(--gray-10));
|
||||
font-weight: bold;
|
||||
}
|
||||
.arco-statistic-value {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
}
|
||||
}
|
||||
.statistic-prefix {
|
||||
display: inline-block;
|
||||
width: 32px;
|
||||
height: 32px;
|
||||
margin-right: 8px;
|
||||
color: var(--color-white);
|
||||
font-size: 16px;
|
||||
line-height: 32px;
|
||||
text-align: center;
|
||||
vertical-align: middle;
|
||||
border-radius: 6px;
|
||||
}
|
||||
</style>
|
|
@ -1,76 +0,0 @@
|
|||
<template>
|
||||
<a-card
|
||||
class="general-card"
|
||||
:title="$t('multiDAnalysis.card.title.userActions')"
|
||||
>
|
||||
<Chart height="122px" :option="chartOption" />
|
||||
</a-card>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import useChartOption from '@/hooks/chart-option';
|
||||
|
||||
const { chartOption } = useChartOption((isDark) => {
|
||||
return {
|
||||
grid: {
|
||||
left: 44,
|
||||
right: 20,
|
||||
top: 0,
|
||||
bottom: 20,
|
||||
},
|
||||
xAxis: {
|
||||
type: 'value',
|
||||
axisLabel: {
|
||||
show: true,
|
||||
formatter(value: number, idx: number) {
|
||||
if (idx === 0) return String(value);
|
||||
return `${Number(value) / 1000}k`;
|
||||
},
|
||||
},
|
||||
splitLine: {
|
||||
lineStyle: {
|
||||
color: isDark ? '#484849' : '#E5E8EF',
|
||||
},
|
||||
},
|
||||
},
|
||||
yAxis: {
|
||||
type: 'category',
|
||||
data: ['点赞量', '评论量', '分享量'],
|
||||
axisLabel: {
|
||||
show: true,
|
||||
color: '#4E5969',
|
||||
},
|
||||
axisTick: {
|
||||
show: true,
|
||||
length: 2,
|
||||
lineStyle: {
|
||||
color: '#A9AEB8',
|
||||
},
|
||||
alignWithLabel: true,
|
||||
},
|
||||
axisLine: {
|
||||
lineStyle: {
|
||||
color: isDark ? '#484849' : '#A9AEB8',
|
||||
},
|
||||
},
|
||||
},
|
||||
tooltip: {
|
||||
show: true,
|
||||
trigger: 'axis',
|
||||
},
|
||||
series: [
|
||||
{
|
||||
data: [1033, 1244, 1520],
|
||||
type: 'bar',
|
||||
barWidth: 7,
|
||||
itemStyle: {
|
||||
color: '#4086FF',
|
||||
borderRadius: 4,
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
});
|
||||
</script>
|
||||
|
||||
<style scoped lang="less"></style>
|
|
@ -1,55 +0,0 @@
|
|||
<template>
|
||||
<div class="container">
|
||||
<Breadcrumb
|
||||
:items="[
|
||||
'menu.visualization',
|
||||
'menu.visualization.multiDimensionDataAnalysis',
|
||||
]"
|
||||
/>
|
||||
<a-space direction="vertical" :size="16" fill>
|
||||
<a-grid :cols="24" :col-gap="16" :row-gap="16">
|
||||
<a-grid-item
|
||||
:span="{ xs: 24, sm: 24, md: 24, lg: 18, xl: 18, xxl: 18 }"
|
||||
>
|
||||
<DataOverview />
|
||||
</a-grid-item>
|
||||
<a-grid-item :span="{ xs: 24, sm: 24, md: 24, lg: 6, xl: 6, xxl: 6 }">
|
||||
<UserActions style="margin-bottom: 16px" />
|
||||
<ContentTypeDistribution />
|
||||
</a-grid-item>
|
||||
</a-grid>
|
||||
<DataChainGrowth />
|
||||
<ContentPublishingSource />
|
||||
</a-space>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import DataOverview from './components/data-overview.vue';
|
||||
import DataChainGrowth from './components/data-chain-growth.vue';
|
||||
import UserActions from './components/user-actions.vue';
|
||||
import ContentTypeDistribution from './components/content-type-distribution.vue';
|
||||
import ContentPublishingSource from './components/content-publishing-source.vue';
|
||||
</script>
|
||||
|
||||
<script lang="ts">
|
||||
export default {
|
||||
name: 'MultiDimensionDataAnalysis',
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped lang="less">
|
||||
.container {
|
||||
padding: 0 20px 20px 20px;
|
||||
}
|
||||
|
||||
:deep(.section-title) {
|
||||
margin-top: 0;
|
||||
margin-bottom: 16px;
|
||||
font-size: 16px;
|
||||
}
|
||||
|
||||
:deep(.chart-wrap) {
|
||||
height: 264px;
|
||||
}
|
||||
</style>
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue