Commit ca2665aa9db1745c1d8569f98bd43f982cf62821

Authored by xp.Huang
2 parents 18ee5481 c9651bd8

Merge branch 'main_dev' into 'main'

Main dev

See merge request yunteng/thingskit-front!1505
Showing 36 changed files with 274 additions and 224 deletions
@@ -34,4 +34,7 @@ @@ -34,4 +34,7 @@
34 "vueuse", 34 "vueuse",
35 "windicss" 35 "windicss"
36 ], 36 ],
  37 + "[vue]": {
  38 + "editor.defaultFormatter": "Vue.volar"
  39 + },
37 } 40 }
1 -import { ApplicationRecordPageParams, CallStatisticsItemType } from './model/record'; 1 +import {
  2 + ApplicationRecordPageParams,
  3 + CallStatisticsItemType,
  4 + ClassifyItemType,
  5 +} from './model/record';
2 import { PaginationResult } from '/#/axios'; 6 import { PaginationResult } from '/#/axios';
3 import { defHttp } from '/@/utils/http/axios'; 7 import { defHttp } from '/@/utils/http/axios';
4 8
@@ -40,7 +44,7 @@ export const getApplicationRecordTop = () => { @@ -40,7 +44,7 @@ export const getApplicationRecordTop = () => {
40 44
41 export const getApplicationRecordClassify = (type?: string) => { 45 export const getApplicationRecordClassify = (type?: string) => {
42 const joinUrlParams = type ? `?type=${type}` : ''; 46 const joinUrlParams = type ? `?type=${type}` : '';
43 - return defHttp.get({ 47 + return defHttp.get<ClassifyItemType[]>({
44 url: `${ApplicationRecordManageApi.OPEN_API_RECORD}/getClassify${joinUrlParams}`, 48 url: `${ApplicationRecordManageApi.OPEN_API_RECORD}/getClassify${joinUrlParams}`,
45 }); 49 });
46 }; 50 };
@@ -70,7 +70,7 @@ @@ -70,7 +70,7 @@
70 70
71 <style lang="less" scoped> 71 <style lang="less" scoped>
72 .form-input { 72 .form-input {
73 - :deep .ant-input-number { 73 + :deep(.ant-input-number) {
74 width: 29% !important; 74 width: 29% !important;
75 } 75 }
76 } 76 }
@@ -177,7 +177,7 @@ @@ -177,7 +177,7 @@
177 class="p-2 bg-gray-200 flex flex-col" 177 class="p-2 bg-gray-200 flex flex-col"
178 :style="{ height: getHeight }" 178 :style="{ height: getHeight }"
179 > 179 >
180 - <head class="flex justify-between h-8 items-center"> 180 + <header class="flex justify-between h-8 items-center">
181 <div class="font-bold"> 181 <div class="font-bold">
182 <span>function</span> 182 <span>function</span>
183 <span class="ml-1">{{ functionName }}</span> 183 <span class="ml-1">{{ functionName }}</span>
@@ -203,7 +203,7 @@ @@ -203,7 +203,7 @@
203 </Tooltip> 203 </Tooltip>
204 <slot name="afterFullScreen"></slot> 204 <slot name="afterFullScreen"></slot>
205 </div> 205 </div>
206 - </head> 206 + </header>
207 <main ref="javaScriptEditorElRef" class="flex-auto"> </main> 207 <main ref="javaScriptEditorElRef" class="flex-auto"> </main>
208 <footer class="font-bold">}</footer> 208 <footer class="font-bold">}</footer>
209 </section> 209 </section>
@@ -17,6 +17,7 @@ export default { @@ -17,6 +17,7 @@ export default {
17 numberOfFailedAttempts: 'Number of failed attempts', 17 numberOfFailedAttempts: 'Number of failed attempts',
18 lineTitle: 'Top Five API calls', 18 lineTitle: 'Top Five API calls',
19 pieTitle: 'Proportion of API calls', 19 pieTitle: 'Proportion of API calls',
  20 + pieShowTitle: 'Proportion App',
20 timeLineTitle: 'Interface call history', 21 timeLineTitle: 'Interface call history',
21 timeSelect: { 22 timeSelect: {
22 oneHour: 'One Hour', 23 oneHour: 'One Hour',
@@ -176,4 +176,11 @@ export default { @@ -176,4 +176,11 @@ export default {
176 intervalErrorText: 'Please select a time interval', 176 intervalErrorText: 'Please select a time interval',
177 tcpAddressCodeHelpText: 177 tcpAddressCodeHelpText:
178 'When TCP gateway sub devices are online, device identification or address codes cannot be modified', 178 'When TCP gateway sub devices are online, device identification or address codes cannot be modified',
  179 + videoChannel: {
  180 + preview: 'Video preview',
  181 + videoLoadding: 'Video loading...',
  182 + controlHelp:
  183 + 'Long press the button to call the API, due to the streaming provided by video streaming media, there is a certain delay, please wait patiently.',
  184 + console: 'Pan-tilt control',
  185 + },
179 }; 186 };
@@ -15,7 +15,8 @@ export default { @@ -15,7 +15,8 @@ export default {
15 numberOfSuccessfulAttempts: '成功次数', 15 numberOfSuccessfulAttempts: '成功次数',
16 numberOfFailedAttempts: '失败次数', 16 numberOfFailedAttempts: '失败次数',
17 lineTitle: 'API调用Top5', 17 lineTitle: 'API调用Top5',
18 - pieTitle: 'API调用占比', 18 + pieTitle: 'API调用总占比',
  19 + pieShowTitle: '应用占比',
19 timeLineTitle: '接口调用历史', 20 timeLineTitle: '接口调用历史',
20 timeSelect: { 21 timeSelect: {
21 oneHour: '1小时', 22 oneHour: '1小时',
@@ -173,4 +173,11 @@ export default { @@ -173,4 +173,11 @@ export default {
173 commandIssuance: '命令下发', 173 commandIssuance: '命令下发',
174 intervalErrorText: '请选择时间间隔', 174 intervalErrorText: '请选择时间间隔',
175 tcpAddressCodeHelpText: 'tcp网关子设备在线时,不能修改设备标识或地址码', 175 tcpAddressCodeHelpText: 'tcp网关子设备在线时,不能修改设备标识或地址码',
  176 +
  177 + videoChannel: {
  178 + preview: '视频预览',
  179 + videoLoadding: '视频加载中...',
  180 + controlHelp: '长按按钮调用API,由于视频流媒体提供的流,有一定时延,请耐心等待。',
  181 + console: '云台控制',
  182 + },
176 }; 183 };
@@ -4,8 +4,8 @@ export default { @@ -4,8 +4,8 @@ export default {
4 splitScreenModeText: '分屏模式', 4 splitScreenModeText: '分屏模式',
5 listModeText: '列表模式', 5 listModeText: '列表模式',
6 fullscreenText: '全屏', 6 fullscreenText: '全屏',
7 - createConfigurationText: '@:common.createText 视频配置',  
8 - editConfigurationText: '@:common.editText 视频配置', 7 + createConfigurationText: '创建视频配置',
  8 + editConfigurationText: '编辑视频配置',
9 createStreamConfigurationText: '@:common.createText 流媒体配置', 9 createStreamConfigurationText: '@:common.createText 流媒体配置',
10 streamConfigurationPlaceholderText: '@:common.chooseText 流媒体配置', 10 streamConfigurationPlaceholderText: '@:common.chooseText 流媒体配置',
11 11
@@ -3,6 +3,7 @@ import { findDictItemByCode } from '/@/api/system/dict'; @@ -3,6 +3,7 @@ import { findDictItemByCode } from '/@/api/system/dict';
3 import { FormSchema } from '/@/components/Form'; 3 import { FormSchema } from '/@/components/Form';
4 import { DictEnum } from '/@/enums/dictEnum'; 4 import { DictEnum } from '/@/enums/dictEnum';
5 import { useI18n } from '/@/hooks/web/useI18n'; 5 import { useI18n } from '/@/hooks/web/useI18n';
  6 +import HelpMessage from '../HelpMessage.vue';
6 7
7 export enum FormFieldsEnum { 8 export enum FormFieldsEnum {
8 NAME = 'name', 9 NAME = 'name',
@@ -62,23 +63,9 @@ export const formSchema: FormSchema[] = [ @@ -62,23 +63,9 @@ export const formSchema: FormSchema[] = [
62 }, 63 },
63 { 64 {
64 field: FormFieldsEnum.INTERFACE_ADDRESS, 65 field: FormFieldsEnum.INTERFACE_ADDRESS,
65 - label: t('application.api.text.interfaceAddress'), 66 + label: h(HelpMessage) as unknown as string,
66 component: 'Input', 67 component: 'Input',
67 required: true, 68 required: true,
68 - helpMessage: [  
69 - h(  
70 - 'span',  
71 - {  
72 - style: {  
73 - cursor: 'pointer',  
74 - },  
75 - onClick: () => {  
76 - window.open('https://yunteng.yuque.com/avshoi/open_api');  
77 - },  
78 - },  
79 - t('common.documentUrl')  
80 - ) as any as string,  
81 - ],  
82 componentProps: { 69 componentProps: {
83 maxLength: 255, 70 maxLength: 255,
84 placeholder: t('application.api.search.interfaceAddressPlaceholder'), 71 placeholder: t('application.api.search.interfaceAddressPlaceholder'),
  1 +<script lang="ts" setup>
  2 + import { BasicHelp } from '/@/components/Basic';
  3 + import { useI18n } from '/@/hooks/web/useI18n';
  4 +
  5 + function handleLinkOpenApiDoc() {
  6 + window.open('https://yunteng.yuque.com/avshoi/open_api');
  7 + }
  8 +
  9 + const { t } = useI18n();
  10 +</script>
  11 +
  12 +<template>
  13 + <div>{{ t('application.api.text.interfaceAddress') }}</div>
  14 +
  15 + <BasicHelp
  16 + placement="top"
  17 + @click="handleLinkOpenApiDoc"
  18 + class="mx-1"
  19 + :text="t('common.documentUrl')"
  20 + />
  21 +</template>
1 -<script setup lang="ts">  
2 - import { Descriptions, Progress, Skeleton, Tooltip, DescriptionsItem } from 'ant-design-vue';  
3 -  
4 - defineProps({  
5 - seriesData: {  
6 - type: Array as PropType<Recordable[]>,  
7 - default: () => [],  
8 - },  
9 - });  
10 -</script>  
11 -<template>  
12 - <div class="m-16">  
13 - <Skeleton active :paragraph="{ rows: 10 }" :loading="!seriesData">  
14 - <Descriptions :column="1">  
15 - <template v-for="(item, index) in seriesData" :key="item.label">  
16 - <DescriptionsItem>  
17 - <span  
18 - class="mr-2 item-span"  
19 - :style="{  
20 - color:  
21 - index === 0  
22 - ? '#f0a16e'  
23 - : index === 1  
24 - ? '#868585'  
25 - : index === 2  
26 - ? '#e78739'  
27 - : '#4e84f5',  
28 - backgroundColor:  
29 - index === 0 ? '#FAD672' : index === 1 ? '#CBCAC9' : index === 2 ? '#F1B889' : '',  
30 - borderColor:  
31 - index === 0  
32 - ? '#fdee7d'  
33 - : index === 1  
34 - ? '#e6e6e5'  
35 - : index === 2  
36 - ? '#f8c296'  
37 - : '#0b55f1',  
38 - }"  
39 - >{{ index + 1 }}</span  
40 - >  
41 - <div class="flex justify-between" style="width: 100%">  
42 - <div class="label-span">  
43 - <Tooltip :title="item.label">  
44 - {{ item.label }}  
45 - </Tooltip>  
46 - </div>  
47 - <div class="flex w-7/10">  
48 - <Progress  
49 - :showInfo="false"  
50 - size="small"  
51 - style="width: 70%"  
52 - :percent="(item.value / seriesData[0].value) * 100"  
53 - :strokeWidth="12"  
54 - :strokeColor="  
55 - index === 0  
56 - ? '#ea5b42'  
57 - : index === 1  
58 - ? '#666'  
59 - : index === 2  
60 - ? '#e4751a'  
61 - : '#b5b6b6'  
62 - "  
63 - />  
64 - <span  
65 - class="ml-2"  
66 - :style="{ color: index === 0 ? '#EA5B42' : index === 2 ? '#E4751A' : '#666' }"  
67 - >  
68 - {{ item.value }}  
69 - </span>  
70 - </div>  
71 - </div>  
72 - </DescriptionsItem>  
73 - </template>  
74 - </Descriptions>  
75 - </Skeleton>  
76 - </div>  
77 -</template>  
78 -  
79 -<style scoped lang="less">  
80 - .item-span {  
81 - width: 1.4rem;  
82 - height: 1.25rem;  
83 - border: 1px solid;  
84 - color: #0b55f1;  
85 - border-radius: 50%;  
86 - display: flex;  
87 - align-items: center;  
88 - justify-content: center;  
89 - }  
90 -  
91 - .label-span {  
92 - width: 10rem;  
93 - white-space: nowrap;  
94 - text-overflow: ellipsis;  
95 - overflow: hidden;  
96 - word-break: break-all;  
97 - }  
98 -</style>  
@@ -4,14 +4,15 @@ @@ -4,14 +4,15 @@
4 import { useAppStore } from '/@/store/modules/app'; 4 import { useAppStore } from '/@/store/modules/app';
5 import { useI18n } from '/@/hooks/web/useI18n'; 5 import { useI18n } from '/@/hooks/web/useI18n';
6 6
7 - const { t } = useI18n();  
8 -  
9 const props = defineProps({ 7 const props = defineProps({
10 seriesData: { 8 seriesData: {
11 type: Array, 9 type: Array,
12 default: () => [], 10 default: () => [],
13 }, 11 },
14 }); 12 });
  13 +
  14 + const { t } = useI18n();
  15 +
15 const appStore = useAppStore(); 16 const appStore = useAppStore();
16 17
17 const skinName = computed(() => { 18 const skinName = computed(() => {
@@ -36,11 +37,11 @@ @@ -36,11 +37,11 @@
36 }, 37 },
37 legend: { 38 legend: {
38 top: 'bottom', 39 top: 'bottom',
39 - padding: 5, 40 + type: 'scroll',
40 }, 41 },
41 title: [ 42 title: [
42 { 43 {
43 - text: t('application.record.text.totalNumberOfCalls'), 44 + text: t('application.record.text.pieShowTitle'),
44 textStyle: { 45 textStyle: {
45 color: '#333', 46 color: '#333',
46 fontWeight: 'bold', 47 fontWeight: 'bold',
@@ -69,7 +70,9 @@ @@ -69,7 +70,9 @@
69 textStyle: { 70 textStyle: {
70 fontSize: 13, 71 fontSize: 13,
71 }, 72 },
72 - formatter: '{d}%', 73 + formatter: (params) => {
  74 + return params.value + '%';
  75 + },
73 }, 76 },
74 }, 77 },
75 }, 78 },
@@ -3,15 +3,21 @@ @@ -3,15 +3,21 @@
3 import { useECharts } from '/@/hooks/web/useECharts'; 3 import { useECharts } from '/@/hooks/web/useECharts';
4 import { useAppStore } from '/@/store/modules/app'; 4 import { useAppStore } from '/@/store/modules/app';
5 import { useI18n } from '/@/hooks/web/useI18n'; 5 import { useI18n } from '/@/hooks/web/useI18n';
  6 + import { Empty } from 'ant-design-vue';
  7 + import { EChartsOption, SeriesOption } from 'echarts';
6 8
7 const { t } = useI18n(); 9 const { t } = useI18n();
8 10
9 - const props = defineProps({  
10 - seriesData: {  
11 - type: Array,  
12 - default: () => [],  
13 - },  
14 - }); 11 + const props = withDefaults(
  12 + defineProps<{
  13 + timeType: string;
  14 + chartsData?: Partial<EChartsOption>;
  15 + }>(),
  16 + {
  17 + timeType: 'week',
  18 + chartsData: () => ({}),
  19 + }
  20 + );
15 21
16 const emits = defineEmits(['emitTimeRange']); 22 const emits = defineEmits(['emitTimeRange']);
17 23
@@ -21,10 +27,11 @@ @@ -21,10 +27,11 @@
21 27
22 const timeRangeList = ref([ 28 const timeRangeList = ref([
23 { label: `1${t('home.index.timeUnit.hour')}`, value: 'hour', interval: 1000 * 60 * 5 }, 29 { label: `1${t('home.index.timeUnit.hour')}`, value: 'hour', interval: 1000 * 60 * 5 },
  30 + { label: `1${t('home.index.timeUnit.day')}`, value: 'day', interval: 1000 * 60 * 60 * 1 },
24 { 31 {
25 label: `7${t('home.index.timeUnit.day')}`, 32 label: `7${t('home.index.timeUnit.day')}`,
26 value: 'week', 33 value: 'week',
27 - interval: 1000 * 60 * 60 * 2, 34 + interval: 1000 * 60 * 60 * 1,
28 }, 35 },
29 { 36 {
30 label: `30${t('home.index.timeUnit.day')}`, 37 label: `30${t('home.index.timeUnit.day')}`,
@@ -41,7 +48,8 @@ @@ -41,7 +48,8 @@
41 48
42 const { setOptions, resize } = useECharts(chartRef as Ref<HTMLDivElement>); 49 const { setOptions, resize } = useECharts(chartRef as Ref<HTMLDivElement>);
43 50
44 - const getOptions: any = () => { 51 + const getOptions = (): EChartsOption => {
  52 + const { xAxis, series } = props.chartsData || {};
45 return { 53 return {
46 backgroundColor: skinName.value, 54 backgroundColor: skinName.value,
47 tooltip: { 55 tooltip: {
@@ -53,26 +61,12 @@ @@ -53,26 +61,12 @@
53 bottom: '1%', 61 bottom: '1%',
54 containLabel: true, 62 containLabel: true,
55 }, 63 },
56 - xAxis: {  
57 - type: 'time',  
58 - splitLine: { show: false },  
59 - lineStyle: {  
60 - width: 2,  
61 - },  
62 - axisTick: {  
63 - show: false,  
64 - },  
65 - axisLabel: {  
66 - formatter: '{yyyy}-{MM}-{dd}',  
67 - showMinLabel: true,  
68 - showMaxLabel: true, // 固定显示X轴的最后一条数据  
69 - },  
70 - }, 64 + xAxis,
71 yAxis: { 65 yAxis: {
72 type: 'value', 66 type: 'value',
73 - axisLabel: { formatter: '{value} %' }, 67 + axisLabel: { formatter: '{value}' },
74 }, 68 },
75 - series: props.seriesData, 69 + series,
76 }; 70 };
77 }; 71 };
78 72
@@ -89,19 +83,23 @@ @@ -89,19 +83,23 @@
89 ); 83 );
90 84
91 watch( 85 watch(
92 - () => props.seriesData, 86 + () => props.chartsData,
93 () => { 87 () => {
94 setOptions(getOptions()); 88 setOptions(getOptions());
95 }, 89 },
96 { 90 {
97 deep: true, 91 deep: true,
  92 + immediate: true,
98 } 93 }
99 ); 94 );
100 95
101 onMounted(() => { 96 onMounted(() => {
102 setOptions(getOptions()); 97 setOptions(getOptions());
103 //自适应 98 //自适应
104 - window.addEventListener('resize', () => resize()); 99 + window.addEventListener('resize', () => {
  100 + resize();
  101 + setOptions(getOptions());
  102 + });
105 }); 103 });
106 104
107 const handleTimeRangeChange = (e: any) => { 105 const handleTimeRangeChange = (e: any) => {
@@ -122,7 +120,17 @@ @@ -122,7 +120,17 @@
122 </template> 120 </template>
123 </a-radio-group> 121 </a-radio-group>
124 </template> 122 </template>
125 - <div ref="chartRef" class="w-full h-80"></div> 123 + <div
  124 + v-show="(chartsData?.series as SeriesOption[])?.length"
  125 + ref="chartRef"
  126 + class="w-full h-80"
  127 + ></div>
  128 + <div
  129 + v-show="!(chartsData?.series as SeriesOption[])?.length"
  130 + class="w-full h-72 flex justify-center items-center"
  131 + >
  132 + <Empty :image="Empty.PRESENTED_IMAGE_SIMPLE" />
  133 + </div>
126 </a-card> 134 </a-card>
127 </template> 135 </template>
128 136
  1 +import { EChartsOption } from 'echarts';
  2 +import { formatClassifyText } from '../../api/config';
  3 +import { ClassifyDtsItemType, ClassifyItemType } from '/@/api/application/model/record';
1 import { DescItem } from '/@/components/Description'; 4 import { DescItem } from '/@/components/Description';
2 import { BasicColumn } from '/@/components/Table'; 5 import { BasicColumn } from '/@/components/Table';
3 import { useI18n } from '/@/hooks/web/useI18n'; 6 import { useI18n } from '/@/hooks/web/useI18n';
  7 +import { dateUtil } from '/@/utils/dateUtil';
  8 +
  9 +export type DateInterval = 'hour' | 'day' | 'week' | 'month';
4 10
5 const { t } = useI18n(); 11 const { t } = useI18n();
6 12
@@ -45,3 +51,92 @@ export const formSchema: DescItem[] = [ @@ -45,3 +51,92 @@ export const formSchema: DescItem[] = [
45 label: t('application.record.text.numberOfFailedAttempts'), 51 label: t('application.record.text.numberOfFailedAttempts'),
46 }, 52 },
47 ]; 53 ];
  54 +
  55 +function generateFullXAxis(type: DateInterval) {
  56 + if (type === 'hour') {
  57 + let minute = dateUtil().minute();
  58 + minute = Math.floor(minute / 5) * 5;
  59 +
  60 + return Array.from({ length: 60 / 5 }, (_, index) =>
  61 + dateUtil()
  62 + .minute(minute)
  63 + .subtract(index * 5, 'minute')
  64 + .startOf('minute')
  65 + .valueOf()
  66 + );
  67 + } else if (type === 'day') {
  68 + let hour = dateUtil().hour();
  69 + hour = Math.floor(hour / 2) * 2;
  70 + return Array.from({ length: 24 / 2 }, (_, index) =>
  71 + dateUtil()
  72 + .hour(hour)
  73 + .subtract(index * 2, 'hour')
  74 + .startOf('hour')
  75 + .valueOf()
  76 + );
  77 + } else if (type === 'week') {
  78 + return Array.from({ length: 7 }, (_, index) =>
  79 + dateUtil().subtract(index, 'day').startOf('day').valueOf()
  80 + );
  81 + } else {
  82 + return Array.from({ length: 30 }, (_, index) =>
  83 + dateUtil().subtract(index, 'day').startOf('day').valueOf()
  84 + );
  85 + }
  86 +}
  87 +
  88 +export function formatDateFromType(date: number, type: DateInterval) {
  89 + if (type === 'hour') {
  90 + return dateUtil(date).format('YYYY-MM-DD HH:mm');
  91 + } else if (type === 'day') {
  92 + return dateUtil(date).format('YYYY-MM-DD HH:mm');
  93 + } else {
  94 + return dateUtil(date).format('YYYY-MM-DD');
  95 + }
  96 +}
  97 +
  98 +export function transformClassifyItem(data: ClassifyItemType[], type: DateInterval) {
  99 + const res: ClassifyItemType[] = [];
  100 + const fullXAxis = generateFullXAxis(type);
  101 +
  102 + for (const { dts, classify } of data) {
  103 + const valueMap = dts.reduce((prev, next) => {
  104 + const key = dateUtil(next.time).valueOf();
  105 +
  106 + return { ...prev, [key]: next };
  107 + }, {} as Record<number, ClassifyDtsItemType>);
  108 +
  109 + const transformDts: ClassifyDtsItemType[] = fullXAxis.map((timeSpan) => {
  110 + const exist = valueMap[timeSpan];
  111 +
  112 + return exist || { time: formatDateFromType(timeSpan, type), count: 0, classify };
  113 + });
  114 +
  115 + res.push({ classify, dts: transformDts });
  116 + }
  117 +
  118 + return res;
  119 +}
  120 +
  121 +export function transformChartsOptionsByClassifyRecord(
  122 + data: ClassifyItemType[],
  123 + type: DateInterval
  124 +): Partial<EChartsOption> {
  125 + const series: EChartsOption['series'] = data.map(({ classify, dts }) => {
  126 + return {
  127 + type: 'line',
  128 + name: formatClassifyText[classify],
  129 + data: dts.map(({ count }) => count).reverse(),
  130 + };
  131 + });
  132 +
  133 + const xAxisData = generateFullXAxis(type).map((timeSpan) => formatDateFromType(timeSpan, type));
  134 +
  135 + return {
  136 + series,
  137 + xAxis: {
  138 + type: 'category',
  139 + data: xAxisData.reverse(),
  140 + },
  141 + };
  142 +}
1 <script setup lang="ts"> 1 <script setup lang="ts">
  2 + import { ref } from 'vue';
2 import LineChart from './components/LineChart.vue'; 3 import LineChart from './components/LineChart.vue';
3 import PieChart from './components/PieChart.vue'; 4 import PieChart from './components/PieChart.vue';
4 import Table from './components/Table.vue'; 5 import Table from './components/Table.vue';
@@ -7,33 +8,32 @@ @@ -7,33 +8,32 @@
7 import { useI18n } from '/@/hooks/web/useI18n'; 8 import { useI18n } from '/@/hooks/web/useI18n';
8 import { onMounted, reactive } from 'vue'; 9 import { onMounted, reactive } from 'vue';
9 import { getApplicationRecordClassify, getApplicationRecordTop } from '/@/api/application/record'; 10 import { getApplicationRecordClassify, getApplicationRecordTop } from '/@/api/application/record';
10 - import {  
11 - ApplicationRecordTopItemType,  
12 - ClassifyDtsItemType,  
13 - ClassifyItemType,  
14 - TopItemType,  
15 - } from '/@/api/application/model/record'; 11 + import { ApplicationRecordTopItemType, TopItemType } from '/@/api/application/model/record';
16 import { Empty } from 'ant-design-vue'; 12 import { Empty } from 'ant-design-vue';
17 - import moment from 'moment';  
18 - import { formatClassifyText } from '../api/config';  
19 import total1 from '/@/assets/images/total1.png'; 13 import total1 from '/@/assets/images/total1.png';
20 import total2 from '/@/assets/images/total2.png'; 14 import total2 from '/@/assets/images/total2.png';
21 import total3 from '/@/assets/images/total3.png'; 15 import total3 from '/@/assets/images/total3.png';
  16 + import {
  17 + DateInterval,
  18 + transformClassifyItem,
  19 + transformChartsOptionsByClassifyRecord,
  20 + } from './config';
  21 + import { EChartsOption } from 'echarts';
22 22
23 const { t } = useI18n(); 23 const { t } = useI18n();
24 24
25 interface ChartData { 25 interface ChartData {
26 lineChartData: Recordable[]; 26 lineChartData: Recordable[];
27 pieChartData: Recordable[]; 27 pieChartData: Recordable[];
28 - timeLineChartData: Recordable[];  
29 totalData: Recordable[]; 28 totalData: Recordable[];
  29 + timeLineChartOptions?: Partial<EChartsOption>;
30 } 30 }
31 31
32 const chartData = reactive<ChartData>({ 32 const chartData = reactive<ChartData>({
33 lineChartData: [], 33 lineChartData: [],
34 pieChartData: [], 34 pieChartData: [],
35 - timeLineChartData: [],  
36 totalData: [], 35 totalData: [],
  36 + timeLineChartOptions: {},
37 }); 37 });
38 38
39 // API统计 39 // API统计
@@ -61,36 +61,42 @@ @@ -61,36 +61,42 @@
61 label: topItem.name, 61 label: topItem.name,
62 value: topItem.recordNum, 62 value: topItem.recordNum,
63 })); 63 }));
64 - chartData.pieChartData = res.tops?.slice(0, 4)?.map((topItem: TopItemType) => ({ 64 + // 取服务端前5个数据
  65 + chartData.pieChartData = res.tops?.slice(0, 5)?.map((topItem: TopItemType) => ({
65 name: topItem.name, 66 name: topItem.name,
66 value: topItem.recordProportion, 67 value: topItem.recordProportion,
67 })); 68 }));
68 - // 总共Top5,取服务端前4个数据,加上其他应用  
69 - const otherApplicationCount = chartData.pieChartData 69 + // 其他应用等于取服务端数组长度为5后面所有数值相加
  70 + const otherApplicationCount = res?.tops
  71 + ?.slice(5)
  72 + ?.map((topItem: TopItemType) => ({
  73 + name: topItem.name,
  74 + value: topItem.recordProportion,
  75 + }))
70 ?.map((topItem) => topItem.value) 76 ?.map((topItem) => topItem.value)
71 ?.reduce((sum, curr) => sum + curr); 77 ?.reduce((sum, curr) => sum + curr);
72 - chartData.pieChartData.push({  
73 - name: '其他应用',  
74 - value: 100 - otherApplicationCount,  
75 - }); 78 + // 相加总共
  79 + if (res.tops && res.tops.length > 5) {
  80 + chartData.pieChartData.push({
  81 + name: '其他应用',
  82 + value: otherApplicationCount,
  83 + });
  84 + }
76 } 85 }
77 }; 86 };
78 87
  88 + const timeType = ref('');
  89 +
79 // 接口调用历史 90 // 接口调用历史
80 - const getClassify = async (type?: string) => {  
81 - const res = (await getApplicationRecordClassify(type)) as any as ClassifyItemType[];  
82 - chartData.timeLineChartData = res?.map((classifyItem: ClassifyItemType) => ({  
83 - type: 'line',  
84 - name: formatClassifyText[classifyItem.classify],  
85 - data: classifyItem?.dts?.map((dtsItem: ClassifyDtsItemType) => [  
86 - moment(dtsItem.time).format('YYYY-MM-DD HH:mm:ss'),  
87 - dtsItem.count,  
88 - ]),  
89 - connectNulls: true,  
90 - })); 91 + const getClassify = async (type: DateInterval) => {
  92 + timeType.value = type;
  93 + const res = await getApplicationRecordClassify(type);
  94 +
  95 + const transformData = transformClassifyItem(res, type);
  96 + chartData.timeLineChartOptions = transformChartsOptionsByClassifyRecord(transformData, type);
91 }; 97 };
92 98
93 - const handleReceiveTimeRange = (value: string) => { 99 + const handleReceiveTimeRange = (value: DateInterval) => {
94 getClassify(value); 100 getClassify(value);
95 }; 101 };
96 102
@@ -105,28 +111,23 @@ @@ -105,28 +111,23 @@
105 <Total :seriesData="chartData.totalData" /> 111 <Total :seriesData="chartData.totalData" />
106 <a-card :title="t('application.record.text.pieTitle')" class="h-99" style="width: 40%"> 112 <a-card :title="t('application.record.text.pieTitle')" class="h-99" style="width: 40%">
107 <PieChart v-if="chartData.pieChartData.length" :seriesData="chartData.pieChartData" /> 113 <PieChart v-if="chartData.pieChartData.length" :seriesData="chartData.pieChartData" />
108 - <div class="w-full h-72 flex justify-center items-center" v-else  
109 - ><Empty :image="Empty.PRESENTED_IMAGE_SIMPLE"  
110 - /></div> 114 + <div class="w-full h-72 flex justify-center items-center" v-else>
  115 + <Empty :image="Empty.PRESENTED_IMAGE_SIMPLE" />
  116 + </div>
111 </a-card> 117 </a-card>
112 <a-card :title="t('application.record.text.lineTitle')" class="h-99" style="width: 45%"> 118 <a-card :title="t('application.record.text.lineTitle')" class="h-99" style="width: 45%">
113 <LineChart v-if="chartData.lineChartData.length" :seriesData="chartData.lineChartData" /> 119 <LineChart v-if="chartData.lineChartData.length" :seriesData="chartData.lineChartData" />
114 - <div class="w-full h-72 flex justify-center items-center" v-else  
115 - ><Empty :image="Empty.PRESENTED_IMAGE_SIMPLE"  
116 - /></div> 120 + <div class="w-full h-72 flex justify-center items-center" v-else>
  121 + <Empty :image="Empty.PRESENTED_IMAGE_SIMPLE" />
  122 + </div>
117 </a-card> 123 </a-card>
118 </div> 124 </div>
119 <div> 125 <div>
120 <TimeLineChart 126 <TimeLineChart
121 - v-if="chartData.timeLineChartData.length"  
122 @emitTimeRange="handleReceiveTimeRange" 127 @emitTimeRange="handleReceiveTimeRange"
123 - :seriesData="chartData.timeLineChartData" 128 + :charts-data="(chartData.timeLineChartOptions as EChartsOption)"
  129 + :timeType="timeType"
124 /> 130 />
125 - <a-card v-else :title="t('application.record.text.timeLineTitle')" class="w-full h-120">  
126 - <div class="w-full h-72 flex justify-center items-center"  
127 - ><Empty :image="Empty.PRESENTED_IMAGE_SIMPLE"  
128 - /></div>  
129 - </a-card>  
130 </div> 131 </div>
131 <div> 132 <div>
132 <a-card class="w-full h-140"> 133 <a-card class="w-full h-140">
  1 +import moment from 'moment';
1 import { formatClassifyText } from '../../../api/config'; 2 import { formatClassifyText } from '../../../api/config';
2 import { DescItem } from '/@/components/Description'; 3 import { DescItem } from '/@/components/Description';
3 import { useI18n } from '/@/hooks/web/useI18n'; 4 import { useI18n } from '/@/hooks/web/useI18n';
@@ -27,5 +28,8 @@ export const formSchema: DescItem[] = [ @@ -27,5 +28,8 @@ export const formSchema: DescItem[] = [
27 { 28 {
28 field: 'requestTime', 29 field: 'requestTime',
29 label: t('application.record.text.callTime'), 30 label: t('application.record.text.callTime'),
  31 + render: (text) => {
  32 + return moment(text).format('YYYY-MM-DD HH:mm:ss');
  33 + },
30 }, 34 },
31 ]; 35 ];
@@ -15,7 +15,7 @@ @@ -15,7 +15,7 @@
15 15
16 const onCloseVal = ref(0); 16 const onCloseVal = ref(0);
17 17
18 - const [registerTable, { reload, getForm, setTableData }] = useTable({ 18 + const [registerTable, { reload, getForm, setTableData, setPagination }] = useTable({
19 api: applicationRecordPage, 19 api: applicationRecordPage,
20 immediate: false, 20 immediate: false,
21 columns, 21 columns,
@@ -79,13 +79,14 @@ @@ -79,13 +79,14 @@
79 79
80 const setInitQueryTable = async () => { 80 const setInitQueryTable = async () => {
81 if (getPathUrlName.value !== '') { 81 if (getPathUrlName.value !== '') {
82 - const { items } = (await applicationRecordPage({ 82 + const { items, total } = (await applicationRecordPage({
83 page: 1, 83 page: 1,
84 pageSize: 10, 84 pageSize: 10,
85 applicationName: getPathUrlName.value, 85 applicationName: getPathUrlName.value,
86 })) as any; 86 })) as any;
87 nextTick(() => { 87 nextTick(() => {
88 setTableData(items); 88 setTableData(items);
  89 + setPagination({ total });
89 const { setFieldsValue, resetFields } = getForm(); 90 const { setFieldsValue, resetFields } = getForm();
90 setFieldsValue({ 91 setFieldsValue({
91 applicationName: getPathUrlName.value, 92 applicationName: getPathUrlName.value,
@@ -290,7 +290,7 @@ @@ -290,7 +290,7 @@
290 class="video-container-mask pointer-events-none absolute top-0 left-0 z-50 text-lg w-full text-light-50 flex justify-center" 290 class="video-container-mask pointer-events-none absolute top-0 left-0 z-50 text-lg w-full text-light-50 flex justify-center"
291 style="height: 100%; background-color: rgba(0, 0, 0, 0.5)" 291 style="height: 100%; background-color: rgba(0, 0, 0, 0.5)"
292 > 292 >
293 - <span>{{ item.name }}{{ pagination.colNumber }}</span> 293 + <span>{{ item.name }}</span>
294 </div> 294 </div>
295 </div> 295 </div>
296 </div> 296 </div>
@@ -3,7 +3,7 @@ @@ -3,7 +3,7 @@
3 <div ref="chartRef" :style="{ height, width }"></div> 3 <div ref="chartRef" :style="{ height, width }"></div>
4 </template> 4 </template>
5 <script lang="ts" setup> 5 <script lang="ts" setup>
6 - import { ref, Ref, withDefaults, onMounted, watch } from 'vue'; 6 + import { ref, Ref, onMounted, watch } from 'vue';
7 import { useECharts } from '/@/hooks/web/useECharts'; 7 import { useECharts } from '/@/hooks/web/useECharts';
8 import { getTrendData } from '/@/api/dashboard'; 8 import { getTrendData } from '/@/api/dashboard';
9 import { t } from '/@/hooks/web/useI18n'; 9 import { t } from '/@/hooks/web/useI18n';
@@ -5,7 +5,7 @@ @@ -5,7 +5,7 @@
5 </div> 5 </div>
6 </template> 6 </template>
7 <script lang="ts" setup> 7 <script lang="ts" setup>
8 - import { onMounted, ref, Ref, withDefaults, watch } from 'vue'; 8 + import { onMounted, ref, Ref, watch } from 'vue';
9 import { useECharts } from '/@/hooks/web/useECharts'; 9 import { useECharts } from '/@/hooks/web/useECharts';
10 import { Empty } from 'ant-design-vue'; 10 import { Empty } from 'ant-design-vue';
11 import { useI18n } from '/@/hooks/web/useI18n'; 11 import { useI18n } from '/@/hooks/web/useI18n';
@@ -5,7 +5,7 @@ @@ -5,7 +5,7 @@
5 </div> 5 </div>
6 </template> 6 </template>
7 <script lang="ts" setup> 7 <script lang="ts" setup>
8 - import { ref, Ref, watch, withDefaults, onMounted } from 'vue'; 8 + import { ref, Ref, watch, onMounted } from 'vue';
9 import { useECharts } from '/@/hooks/web/useECharts'; 9 import { useECharts } from '/@/hooks/web/useECharts';
10 import { Empty } from 'ant-design-vue'; 10 import { Empty } from 'ant-design-vue';
11 import { useI18n } from '/@/hooks/web/useI18n'; 11 import { useI18n } from '/@/hooks/web/useI18n';
@@ -11,7 +11,7 @@ export type VideoCancelModalParamsType = { @@ -11,7 +11,7 @@ export type VideoCancelModalParamsType = {
11 channelId?: string; 11 channelId?: string;
12 id?: string; 12 id?: string;
13 playerProps?: Recordable; 13 playerProps?: Recordable;
14 - getPlayUrl: () => Promise<Record<'url' | 'type', string>>; 14 + getPlayUrl: () => Promise<Record<'url' | 'type', string> & { withToken?: string }>;
15 }; 15 };
16 16
17 //视频通道权限标识枚举 17 //视频通道权限标识枚举
@@ -120,9 +120,11 @@ @@ -120,9 +120,11 @@
120 > 120 >
121 <Tooltip> 121 <Tooltip>
122 <template #title> 122 <template #title>
123 - 长按按钮调用API,由于视频流媒体提供的流,有一定时延,请耐心等待。 123 + {{ t('deviceManagement.device.videoChannel.controlHelp') }}
124 </template> 124 </template>
125 - <label class="validate-dot">云台控制</label> 125 + <label class="validate-dot">
  126 + {{ t('deviceManagement.device.videoChannel.console') }}
  127 + </label>
126 <QuestionCircleOutlined :style="{ fontSize: '14px', marginLeft: '5px' }" /> 128 <QuestionCircleOutlined :style="{ fontSize: '14px', marginLeft: '5px' }" />
127 </Tooltip> 129 </Tooltip>
128 130
@@ -5,7 +5,7 @@ @@ -5,7 +5,7 @@
5 destroyOnClose 5 destroyOnClose
6 :height="800" 6 :height="800"
7 @register="register" 7 @register="register"
8 - title="视频预览" 8 + :title="t('deviceManagement.device.videoChannel.preview')"
9 :showOkBtn="false" 9 :showOkBtn="false"
10 @cancel="handleCancel" 10 @cancel="handleCancel"
11 > 11 >
@@ -19,8 +19,9 @@ @@ -19,8 +19,9 @@
19 import { BasicModal, useModalInner } from '/@/components/Modal'; 19 import { BasicModal, useModalInner } from '/@/components/Modal';
20 import VideoPlayer from './video.vue'; 20 import VideoPlayer from './video.vue';
21 import { VideoCancelModalParamsType } from '/@/views/device/list/cpns/tabs/VideoChannel/config'; 21 import { VideoCancelModalParamsType } from '/@/views/device/list/cpns/tabs/VideoChannel/config';
  22 + import { useI18n } from '/@/hooks/web/useI18n';
22 const emit = defineEmits(['reloadTable', 'register']); 23 const emit = defineEmits(['reloadTable', 'register']);
23 - 24 + const { t } = useI18n();
24 const playUrl = ref<Nullable<string>>(); 25 const playUrl = ref<Nullable<string>>();
25 const options = ref<Nullable<VideoCancelModalParamsType>>(); 26 const options = ref<Nullable<VideoCancelModalParamsType>>();
26 const [register, { setModalProps }] = useModalInner( 27 const [register, { setModalProps }] = useModalInner(
@@ -29,13 +30,17 @@ @@ -29,13 +30,17 @@
29 try { 30 try {
30 playUrl.value = null; 31 playUrl.value = null;
31 options.value = null; 32 options.value = null;
32 - setModalProps({ loading: true, loadingTip: '视频加载中...' });  
33 - const { url, type } = await record.getPlayUrl(); 33 + setModalProps({
  34 + loading: true,
  35 + loadingTip: t('deviceManagement.device.videoChannel.videoLoadding'),
  36 + });
  37 + const { url, type, withToken } = await record.getPlayUrl();
34 playUrl.value = url; 38 playUrl.value = url;
35 options.value = record; 39 options.value = record;
36 options.value.playerProps = { 40 options.value.playerProps = {
37 ...(options.value?.playerProps || {}), 41 ...(options.value?.playerProps || {}),
38 streamType: type, 42 streamType: type,
  43 + withToken: withToken,
39 }; 44 };
40 } catch (error) { 45 } catch (error) {
41 } finally { 46 } finally {
@@ -23,7 +23,7 @@ @@ -23,7 +23,7 @@
23 } 23 }
24 24
25 .device-icon-style { 25 .device-icon-style {
26 - :deep .ant-upload-select-picture-card { 26 + :deep(.ant-upload-select-picture-card) {
27 display: inherit; 27 display: inherit;
28 float: none; 28 float: none;
29 width: 4.9vw; 29 width: 4.9vw;
@@ -84,11 +84,9 @@ @@ -84,11 +84,9 @@
84 84
85 <style lang="less" scoped> 85 <style lang="less" scoped>
86 .tcp-configuration-form { 86 .tcp-configuration-form {
87 - :deep {  
88 - .ant-input-number {  
89 - min-width: initial;  
90 - width: 100%;  
91 - } 87 + :deep(.ant-input-number) {
  88 + min-width: initial;
  89 + width: 100%;
92 } 90 }
93 } 91 }
94 </style> 92 </style>
@@ -41,7 +41,7 @@ @@ -41,7 +41,7 @@
41 </script> 41 </script>
42 <style lang="less" scoped> 42 <style lang="less" scoped>
43 .tenant-class { 43 .tenant-class {
44 - :deep .ant-input-number { 44 + :deep(.ant-input-number) {
45 width: 95% !important; 45 width: 95% !important;
46 } 46 }
47 } 47 }
@@ -41,7 +41,7 @@ @@ -41,7 +41,7 @@
41 </script> 41 </script>
42 <style lang="less" scoped> 42 <style lang="less" scoped>
43 .tenant-class { 43 .tenant-class {
44 - :deep .ant-input-number { 44 + :deep(.ant-input-number) {
45 width: 95% !important; 45 width: 95% !important;
46 } 46 }
47 } 47 }
@@ -41,7 +41,7 @@ @@ -41,7 +41,7 @@
41 </script> 41 </script>
42 <style lang="less" scoped> 42 <style lang="less" scoped>
43 .tenant-class { 43 .tenant-class {
44 - :deep .ant-input-number { 44 + :deep(.ant-input-number) {
45 width: 95% !important; 45 width: 95% !important;
46 } 46 }
47 } 47 }
@@ -41,7 +41,7 @@ @@ -41,7 +41,7 @@
41 </script> 41 </script>
42 <style lang="less" scoped> 42 <style lang="less" scoped>
43 .tenant-class { 43 .tenant-class {
44 - :deep .ant-input-number { 44 + :deep(.ant-input-number) {
45 width: 95% !important; 45 width: 95% !important;
46 } 46 }
47 } 47 }
@@ -41,7 +41,7 @@ @@ -41,7 +41,7 @@
41 </script> 41 </script>
42 <style lang="less" scoped> 42 <style lang="less" scoped>
43 .tenant-class { 43 .tenant-class {
44 - :deep .ant-input-number { 44 + :deep(.ant-input-number) {
45 width: 95% !important; 45 width: 95% !important;
46 } 46 }
47 } 47 }
@@ -41,7 +41,7 @@ @@ -41,7 +41,7 @@
41 </script> 41 </script>
42 <style lang="less" scoped> 42 <style lang="less" scoped>
43 .tenant-class { 43 .tenant-class {
44 - :deep .ant-input-number { 44 + :deep(.ant-input-number) {
45 width: 95% !important; 45 width: 95% !important;
46 } 46 }
47 } 47 }
@@ -41,7 +41,7 @@ @@ -41,7 +41,7 @@
41 </script> 41 </script>
42 <style lang="less" scoped> 42 <style lang="less" scoped>
43 .tenant-class { 43 .tenant-class {
44 - :deep .ant-input-number { 44 + :deep(.ant-input-number) {
45 width: 95% !important; 45 width: 95% !important;
46 } 46 }
47 } 47 }
@@ -47,7 +47,7 @@ @@ -47,7 +47,7 @@
47 </script> 47 </script>
48 <style lang="less" scoped> 48 <style lang="less" scoped>
49 .tenant-class { 49 .tenant-class {
50 - :deep .ant-input-number { 50 + :deep(.ant-input-number) {
51 width: 95% !important; 51 width: 95% !important;
52 } 52 }
53 } 53 }
@@ -558,7 +558,7 @@ @@ -558,7 +558,7 @@
558 .drawer-class { 558 .drawer-class {
559 .ant-row { 559 .ant-row {
560 .ant-col { 560 .ant-col {
561 - :deep .ant-input-number { 561 + :deep(.ant-input-number) {
562 width: 34vw !important; 562 width: 34vw !important;
563 } 563 }
564 } 564 }