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 34 "vueuse",
35 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 6 import { PaginationResult } from '/#/axios';
3 7 import { defHttp } from '/@/utils/http/axios';
4 8
... ... @@ -40,7 +44,7 @@ export const getApplicationRecordTop = () => {
40 44
41 45 export const getApplicationRecordClassify = (type?: string) => {
42 46 const joinUrlParams = type ? `?type=${type}` : '';
43   - return defHttp.get({
  47 + return defHttp.get<ClassifyItemType[]>({
44 48 url: `${ApplicationRecordManageApi.OPEN_API_RECORD}/getClassify${joinUrlParams}`,
45 49 });
46 50 };
... ...
... ... @@ -70,7 +70,7 @@
70 70
71 71 <style lang="less" scoped>
72 72 .form-input {
73   - :deep .ant-input-number {
  73 + :deep(.ant-input-number) {
74 74 width: 29% !important;
75 75 }
76 76 }
... ...
... ... @@ -177,7 +177,7 @@
177 177 class="p-2 bg-gray-200 flex flex-col"
178 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 181 <div class="font-bold">
182 182 <span>function</span>
183 183 <span class="ml-1">{{ functionName }}</span>
... ... @@ -203,7 +203,7 @@
203 203 </Tooltip>
204 204 <slot name="afterFullScreen"></slot>
205 205 </div>
206   - </head>
  206 + </header>
207 207 <main ref="javaScriptEditorElRef" class="flex-auto"> </main>
208 208 <footer class="font-bold">}</footer>
209 209 </section>
... ...
... ... @@ -17,6 +17,7 @@ export default {
17 17 numberOfFailedAttempts: 'Number of failed attempts',
18 18 lineTitle: 'Top Five API calls',
19 19 pieTitle: 'Proportion of API calls',
  20 + pieShowTitle: 'Proportion App',
20 21 timeLineTitle: 'Interface call history',
21 22 timeSelect: {
22 23 oneHour: 'One Hour',
... ...
... ... @@ -176,4 +176,11 @@ export default {
176 176 intervalErrorText: 'Please select a time interval',
177 177 tcpAddressCodeHelpText:
178 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 15 numberOfSuccessfulAttempts: '成功次数',
16 16 numberOfFailedAttempts: '失败次数',
17 17 lineTitle: 'API调用Top5',
18   - pieTitle: 'API调用占比',
  18 + pieTitle: 'API调用总占比',
  19 + pieShowTitle: '应用占比',
19 20 timeLineTitle: '接口调用历史',
20 21 timeSelect: {
21 22 oneHour: '1小时',
... ...
... ... @@ -173,4 +173,11 @@ export default {
173 173 commandIssuance: '命令下发',
174 174 intervalErrorText: '请选择时间间隔',
175 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 4 splitScreenModeText: '分屏模式',
5 5 listModeText: '列表模式',
6 6 fullscreenText: '全屏',
7   - createConfigurationText: '@:common.createText 视频配置',
8   - editConfigurationText: '@:common.editText 视频配置',
  7 + createConfigurationText: '创建视频配置',
  8 + editConfigurationText: '编辑视频配置',
9 9 createStreamConfigurationText: '@:common.createText 流媒体配置',
10 10 streamConfigurationPlaceholderText: '@:common.chooseText 流媒体配置',
11 11
... ...
... ... @@ -3,6 +3,7 @@ import { findDictItemByCode } from '/@/api/system/dict';
3 3 import { FormSchema } from '/@/components/Form';
4 4 import { DictEnum } from '/@/enums/dictEnum';
5 5 import { useI18n } from '/@/hooks/web/useI18n';
  6 +import HelpMessage from '../HelpMessage.vue';
6 7
7 8 export enum FormFieldsEnum {
8 9 NAME = 'name',
... ... @@ -62,23 +63,9 @@ export const formSchema: FormSchema[] = [
62 63 },
63 64 {
64 65 field: FormFieldsEnum.INTERFACE_ADDRESS,
65   - label: t('application.api.text.interfaceAddress'),
  66 + label: h(HelpMessage) as unknown as string,
66 67 component: 'Input',
67 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 69 componentProps: {
83 70 maxLength: 255,
84 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 4 import { useAppStore } from '/@/store/modules/app';
5 5 import { useI18n } from '/@/hooks/web/useI18n';
6 6
7   - const { t } = useI18n();
8   -
9 7 const props = defineProps({
10 8 seriesData: {
11 9 type: Array,
12 10 default: () => [],
13 11 },
14 12 });
  13 +
  14 + const { t } = useI18n();
  15 +
15 16 const appStore = useAppStore();
16 17
17 18 const skinName = computed(() => {
... ... @@ -36,11 +37,11 @@
36 37 },
37 38 legend: {
38 39 top: 'bottom',
39   - padding: 5,
  40 + type: 'scroll',
40 41 },
41 42 title: [
42 43 {
43   - text: t('application.record.text.totalNumberOfCalls'),
  44 + text: t('application.record.text.pieShowTitle'),
44 45 textStyle: {
45 46 color: '#333',
46 47 fontWeight: 'bold',
... ... @@ -69,7 +70,9 @@
69 70 textStyle: {
70 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 3 import { useECharts } from '/@/hooks/web/useECharts';
4 4 import { useAppStore } from '/@/store/modules/app';
5 5 import { useI18n } from '/@/hooks/web/useI18n';
  6 + import { Empty } from 'ant-design-vue';
  7 + import { EChartsOption, SeriesOption } from 'echarts';
6 8
7 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 22 const emits = defineEmits(['emitTimeRange']);
17 23
... ... @@ -21,10 +27,11 @@
21 27
22 28 const timeRangeList = ref([
23 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 32 label: `7${t('home.index.timeUnit.day')}`,
26 33 value: 'week',
27   - interval: 1000 * 60 * 60 * 2,
  34 + interval: 1000 * 60 * 60 * 1,
28 35 },
29 36 {
30 37 label: `30${t('home.index.timeUnit.day')}`,
... ... @@ -41,7 +48,8 @@
41 48
42 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 53 return {
46 54 backgroundColor: skinName.value,
47 55 tooltip: {
... ... @@ -53,26 +61,12 @@
53 61 bottom: '1%',
54 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 65 yAxis: {
72 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 83 );
90 84
91 85 watch(
92   - () => props.seriesData,
  86 + () => props.chartsData,
93 87 () => {
94 88 setOptions(getOptions());
95 89 },
96 90 {
97 91 deep: true,
  92 + immediate: true,
98 93 }
99 94 );
100 95
101 96 onMounted(() => {
102 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 105 const handleTimeRangeChange = (e: any) => {
... ... @@ -122,7 +120,17 @@
122 120 </template>
123 121 </a-radio-group>
124 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 134 </a-card>
127 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 4 import { DescItem } from '/@/components/Description';
2 5 import { BasicColumn } from '/@/components/Table';
3 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 11 const { t } = useI18n();
6 12
... ... @@ -45,3 +51,92 @@ export const formSchema: DescItem[] = [
45 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 1 <script setup lang="ts">
  2 + import { ref } from 'vue';
2 3 import LineChart from './components/LineChart.vue';
3 4 import PieChart from './components/PieChart.vue';
4 5 import Table from './components/Table.vue';
... ... @@ -7,33 +8,32 @@
7 8 import { useI18n } from '/@/hooks/web/useI18n';
8 9 import { onMounted, reactive } from 'vue';
9 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 12 import { Empty } from 'ant-design-vue';
17   - import moment from 'moment';
18   - import { formatClassifyText } from '../api/config';
19 13 import total1 from '/@/assets/images/total1.png';
20 14 import total2 from '/@/assets/images/total2.png';
21 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 23 const { t } = useI18n();
24 24
25 25 interface ChartData {
26 26 lineChartData: Recordable[];
27 27 pieChartData: Recordable[];
28   - timeLineChartData: Recordable[];
29 28 totalData: Recordable[];
  29 + timeLineChartOptions?: Partial<EChartsOption>;
30 30 }
31 31
32 32 const chartData = reactive<ChartData>({
33 33 lineChartData: [],
34 34 pieChartData: [],
35   - timeLineChartData: [],
36 35 totalData: [],
  36 + timeLineChartOptions: {},
37 37 });
38 38
39 39 // API统计
... ... @@ -61,36 +61,42 @@
61 61 label: topItem.name,
62 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 66 name: topItem.name,
66 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 76 ?.map((topItem) => topItem.value)
71 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 100 getClassify(value);
95 101 };
96 102
... ... @@ -105,28 +111,23 @@
105 111 <Total :seriesData="chartData.totalData" />
106 112 <a-card :title="t('application.record.text.pieTitle')" class="h-99" style="width: 40%">
107 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 117 </a-card>
112 118 <a-card :title="t('application.record.text.lineTitle')" class="h-99" style="width: 45%">
113 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 123 </a-card>
118 124 </div>
119 125 <div>
120 126 <TimeLineChart
121   - v-if="chartData.timeLineChartData.length"
122 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 131 </div>
131 132 <div>
132 133 <a-card class="w-full h-140">
... ...
  1 +import moment from 'moment';
1 2 import { formatClassifyText } from '../../../api/config';
2 3 import { DescItem } from '/@/components/Description';
3 4 import { useI18n } from '/@/hooks/web/useI18n';
... ... @@ -27,5 +28,8 @@ export const formSchema: DescItem[] = [
27 28 {
28 29 field: 'requestTime',
29 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 15
16 16 const onCloseVal = ref(0);
17 17
18   - const [registerTable, { reload, getForm, setTableData }] = useTable({
  18 + const [registerTable, { reload, getForm, setTableData, setPagination }] = useTable({
19 19 api: applicationRecordPage,
20 20 immediate: false,
21 21 columns,
... ... @@ -79,13 +79,14 @@
79 79
80 80 const setInitQueryTable = async () => {
81 81 if (getPathUrlName.value !== '') {
82   - const { items } = (await applicationRecordPage({
  82 + const { items, total } = (await applicationRecordPage({
83 83 page: 1,
84 84 pageSize: 10,
85 85 applicationName: getPathUrlName.value,
86 86 })) as any;
87 87 nextTick(() => {
88 88 setTableData(items);
  89 + setPagination({ total });
89 90 const { setFieldsValue, resetFields } = getForm();
90 91 setFieldsValue({
91 92 applicationName: getPathUrlName.value,
... ...
... ... @@ -290,7 +290,7 @@
290 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 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 294 </div>
295 295 </div>
296 296 </div>
... ...
... ... @@ -3,7 +3,7 @@
3 3 <div ref="chartRef" :style="{ height, width }"></div>
4 4 </template>
5 5 <script lang="ts" setup>
6   - import { ref, Ref, withDefaults, onMounted, watch } from 'vue';
  6 + import { ref, Ref, onMounted, watch } from 'vue';
7 7 import { useECharts } from '/@/hooks/web/useECharts';
8 8 import { getTrendData } from '/@/api/dashboard';
9 9 import { t } from '/@/hooks/web/useI18n';
... ...
... ... @@ -5,7 +5,7 @@
5 5 </div>
6 6 </template>
7 7 <script lang="ts" setup>
8   - import { onMounted, ref, Ref, withDefaults, watch } from 'vue';
  8 + import { onMounted, ref, Ref, watch } from 'vue';
9 9 import { useECharts } from '/@/hooks/web/useECharts';
10 10 import { Empty } from 'ant-design-vue';
11 11 import { useI18n } from '/@/hooks/web/useI18n';
... ...
... ... @@ -5,7 +5,7 @@
5 5 </div>
6 6 </template>
7 7 <script lang="ts" setup>
8   - import { ref, Ref, watch, withDefaults, onMounted } from 'vue';
  8 + import { ref, Ref, watch, onMounted } from 'vue';
9 9 import { useECharts } from '/@/hooks/web/useECharts';
10 10 import { Empty } from 'ant-design-vue';
11 11 import { useI18n } from '/@/hooks/web/useI18n';
... ...
... ... @@ -11,7 +11,7 @@ export type VideoCancelModalParamsType = {
11 11 channelId?: string;
12 12 id?: string;
13 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 120 >
121 121 <Tooltip>
122 122 <template #title>
123   - 长按按钮调用API,由于视频流媒体提供的流,有一定时延,请耐心等待。
  123 + {{ t('deviceManagement.device.videoChannel.controlHelp') }}
124 124 </template>
125   - <label class="validate-dot">云台控制</label>
  125 + <label class="validate-dot">
  126 + {{ t('deviceManagement.device.videoChannel.console') }}
  127 + </label>
126 128 <QuestionCircleOutlined :style="{ fontSize: '14px', marginLeft: '5px' }" />
127 129 </Tooltip>
128 130
... ...
... ... @@ -5,7 +5,7 @@
5 5 destroyOnClose
6 6 :height="800"
7 7 @register="register"
8   - title="视频预览"
  8 + :title="t('deviceManagement.device.videoChannel.preview')"
9 9 :showOkBtn="false"
10 10 @cancel="handleCancel"
11 11 >
... ... @@ -19,8 +19,9 @@
19 19 import { BasicModal, useModalInner } from '/@/components/Modal';
20 20 import VideoPlayer from './video.vue';
21 21 import { VideoCancelModalParamsType } from '/@/views/device/list/cpns/tabs/VideoChannel/config';
  22 + import { useI18n } from '/@/hooks/web/useI18n';
22 23 const emit = defineEmits(['reloadTable', 'register']);
23   -
  24 + const { t } = useI18n();
24 25 const playUrl = ref<Nullable<string>>();
25 26 const options = ref<Nullable<VideoCancelModalParamsType>>();
26 27 const [register, { setModalProps }] = useModalInner(
... ... @@ -29,13 +30,17 @@
29 30 try {
30 31 playUrl.value = null;
31 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 38 playUrl.value = url;
35 39 options.value = record;
36 40 options.value.playerProps = {
37 41 ...(options.value?.playerProps || {}),
38 42 streamType: type,
  43 + withToken: withToken,
39 44 };
40 45 } catch (error) {
41 46 } finally {
... ...
... ... @@ -23,7 +23,7 @@
23 23 }
24 24
25 25 .device-icon-style {
26   - :deep .ant-upload-select-picture-card {
  26 + :deep(.ant-upload-select-picture-card) {
27 27 display: inherit;
28 28 float: none;
29 29 width: 4.9vw;
... ...
... ... @@ -84,11 +84,9 @@
84 84
85 85 <style lang="less" scoped>
86 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 92 </style>
... ...
... ... @@ -41,7 +41,7 @@
41 41 </script>
42 42 <style lang="less" scoped>
43 43 .tenant-class {
44   - :deep .ant-input-number {
  44 + :deep(.ant-input-number) {
45 45 width: 95% !important;
46 46 }
47 47 }
... ...
... ... @@ -41,7 +41,7 @@
41 41 </script>
42 42 <style lang="less" scoped>
43 43 .tenant-class {
44   - :deep .ant-input-number {
  44 + :deep(.ant-input-number) {
45 45 width: 95% !important;
46 46 }
47 47 }
... ...
... ... @@ -41,7 +41,7 @@
41 41 </script>
42 42 <style lang="less" scoped>
43 43 .tenant-class {
44   - :deep .ant-input-number {
  44 + :deep(.ant-input-number) {
45 45 width: 95% !important;
46 46 }
47 47 }
... ...
... ... @@ -41,7 +41,7 @@
41 41 </script>
42 42 <style lang="less" scoped>
43 43 .tenant-class {
44   - :deep .ant-input-number {
  44 + :deep(.ant-input-number) {
45 45 width: 95% !important;
46 46 }
47 47 }
... ...
... ... @@ -41,7 +41,7 @@
41 41 </script>
42 42 <style lang="less" scoped>
43 43 .tenant-class {
44   - :deep .ant-input-number {
  44 + :deep(.ant-input-number) {
45 45 width: 95% !important;
46 46 }
47 47 }
... ...
... ... @@ -41,7 +41,7 @@
41 41 </script>
42 42 <style lang="less" scoped>
43 43 .tenant-class {
44   - :deep .ant-input-number {
  44 + :deep(.ant-input-number) {
45 45 width: 95% !important;
46 46 }
47 47 }
... ...
... ... @@ -41,7 +41,7 @@
41 41 </script>
42 42 <style lang="less" scoped>
43 43 .tenant-class {
44   - :deep .ant-input-number {
  44 + :deep(.ant-input-number) {
45 45 width: 95% !important;
46 46 }
47 47 }
... ...
... ... @@ -47,7 +47,7 @@
47 47 </script>
48 48 <style lang="less" scoped>
49 49 .tenant-class {
50   - :deep .ant-input-number {
  50 + :deep(.ant-input-number) {
51 51 width: 95% !important;
52 52 }
53 53 }
... ...
... ... @@ -558,7 +558,7 @@
558 558 .drawer-class {
559 559 .ant-row {
560 560 .ant-col {
561   - :deep .ant-input-number {
  561 + :deep(.ant-input-number) {
562 562 width: 34vw !important;
563 563 }
564 564 }
... ...