Commit 63b3580b7a943dee98aa46331fd772857a7469c4

Authored by xp.Huang
2 parents 9dbe312e 4b5ae137

Merge branch 'ww' into 'main'

feat: product profile add card mode && fix bug in teambition

See merge request huang/yun-teng-iot-front!445
... ... @@ -2,7 +2,9 @@ import { defHttp } from '/@/utils/http/axios';
2 2 import {
3 3 TDeviceConfigParams,
4 4 IDeviceConfigAddOrEditModel,
  5 + ProfileRecord,
5 6 } from '/@/api/device/model/deviceConfigModel';
  7 +import { PaginationResult } from '/#/axios';
6 8
7 9 enum EDeviceConfigApi {
8 10 /**
... ... @@ -52,7 +54,7 @@ export const alarmContactGetPage = () => {
52 54 * 分页查询设备配置页面
53 55 */
54 56 export const deviceConfigGetQuery = (params?: TDeviceConfigParams) => {
55   - return defHttp.get({
  57 + return defHttp.get<PaginationResult<ProfileRecord>>({
56 58 url: EDeviceConfigApi.DEVICE_CONFIG_GET_PAGE,
57 59 params,
58 60 });
... ...
... ... @@ -199,3 +199,45 @@ export interface AlarmLogItem {
199 199 organizationId: string;
200 200 organizationName: string;
201 201 }
  202 +
  203 +export interface Configuration {
  204 + type: string;
  205 +}
  206 +
  207 +export interface TransportConfiguration {
  208 + type: string;
  209 +}
  210 +
  211 +export interface ProvisionConfiguration {
  212 + type: string;
  213 + provisionDeviceSecret?: any;
  214 +}
  215 +
  216 +export interface ProfileData {
  217 + configuration: Configuration;
  218 + transportConfiguration: TransportConfiguration;
  219 + provisionConfiguration: ProvisionConfiguration;
  220 + alarms?: any;
  221 +}
  222 +
  223 +export interface ProfileRecord {
  224 + id: string;
  225 + creator: string;
  226 + createTime: string;
  227 + updater: string;
  228 + updateTime: string;
  229 + name: string;
  230 + tenantId: string;
  231 + transportType: string;
  232 + provisionType: string;
  233 + deviceType: string;
  234 + tbProfileId: string;
  235 + profileData: ProfileData;
  236 + defaultRuleChainId: string;
  237 + defaultQueueName: string;
  238 + image: string;
  239 + type: string;
  240 + default: boolean;
  241 +
  242 + checked?: boolean;
  243 +}
... ...
  1 +<svg t="1671442803003" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="4002" width="200" height="200"><path d="M0 0m178.086957 0l667.826086 0q178.086957 0 178.086957 178.086957l0 667.826086q0 178.086957-178.086957 178.086957l-667.826086 0q-178.086957 0-178.086957-178.086957l0-667.826086q0-178.086957 178.086957-178.086957Z" fill="#2E7BFC" p-id="4003"></path><path d="M758.717217 394.373565v268.866783l-238.191304 134.455652v-268.911304l124.14887-70.054957v94.497391l51.956869-29.985391V429.412174l62.107826-35.038609z m-493.434434 0l238.191304 134.433392v268.866782l-238.191304-134.433391V394.373565zM512 226.304l246.717217 139.241739L512 504.787478l-246.717217-139.241739L512 226.326261z" fill="#FFFFFF" p-id="4004"></path></svg>
... ...
... ... @@ -13,7 +13,7 @@
13 13 const iconList = IconData.icons;
14 14 const [register, { openDrawer, closeDrawer }] = useDrawer();
15 15 const getIcon = computed(() => {
16   - return props.value || 'temperature';
  16 + return props.value || 'shuiwen';
17 17 });
18 18
19 19 const getColor = computed(() => {
... ...
  1 +<script lang="ts" setup>
  2 + import { Popover, Slider, Button } from 'ant-design-vue';
  3 + import { LayoutOutlined } from '@ant-design/icons-vue';
  4 + import { computed } from 'vue';
  5 +
  6 + const props = withDefaults(
  7 + defineProps<{
  8 + min?: number;
  9 + max?: number;
  10 + value?: number;
  11 + }>(),
  12 + {
  13 + min: 4,
  14 + max: 12,
  15 + value: 4,
  16 + }
  17 + );
  18 +
  19 + const emit = defineEmits(['change', 'update:value']);
  20 +
  21 + const generateLayoutMarks = (min: number, max: number) => {
  22 + const marks = {};
  23 + Array.from({ length: max - min + 1 }).forEach((_, index) => {
  24 + const key = index + min;
  25 + marks[key] = key;
  26 + });
  27 + return marks;
  28 + };
  29 +
  30 + const getMarks = computed(() => {
  31 + const { min, max } = props;
  32 + return generateLayoutMarks(min, max);
  33 + });
  34 +
  35 + const handleChange = (value: number) => {
  36 + emit('update:value', value);
  37 + emit('change', value);
  38 + };
  39 +</script>
  40 +
  41 +<template>
  42 + <Popover :trigger="['hover']">
  43 + <template #content>
  44 + <div class="w-60">
  45 + <div>每行显示数量</div>
  46 + <Slider
  47 + :value="props.value"
  48 + :max="props.max"
  49 + :min="props.min"
  50 + :marks="getMarks"
  51 + @change="handleChange"
  52 + />
  53 + </div>
  54 + </template>
  55 + <Button type="primary">
  56 + <LayoutOutlined />
  57 + </Button>
  58 + </Popover>
  59 +</template>
... ...
  1 +<script lang="ts" setup>
  2 + import { RadioButton, RadioGroup, Tooltip } from 'ant-design-vue';
  3 + import { TABLE_CARD_MODE_LIST } from './const';
  4 + import { ModeList } from './types';
  5 + import Icon from '../Icon';
  6 + const props = withDefaults(
  7 + defineProps<{
  8 + value: string;
  9 + mode?: ModeList[];
  10 + }>(),
  11 + {
  12 + mode: () => TABLE_CARD_MODE_LIST,
  13 + }
  14 + );
  15 +
  16 + const emit = defineEmits(['change']);
  17 +
  18 + const handleChange = (event: Event) => {
  19 + const value = (event.target as HTMLInputElement).value;
  20 + emit('change', value);
  21 + };
  22 +</script>
  23 +
  24 +<template>
  25 + <RadioGroup :value="props.value" button-style="solid" @change="handleChange">
  26 + <template v-for="item in $props.mode" :key="item.value">
  27 + <Tooltip :title="item.label">
  28 + <RadioButton class="cursor-pointer" :value="item.value">
  29 + <Icon :icon="item.icon" />
  30 + </RadioButton>
  31 + </Tooltip>
  32 + </template>
  33 + </RadioGroup>
  34 +</template>
... ...
  1 +import { ModeList } from './types';
  2 +
  3 +export enum EnumTableCardMode {
  4 + CARD = 'CARD',
  5 + TABLE = 'TABLE',
  6 +}
  7 +
  8 +export enum EnumTableChartMode {
  9 + TABLE = 'TABLE',
  10 + CHART = 'CHART',
  11 +}
  12 +
  13 +export const TABLE_CARD_MODE_LIST: ModeList[] = [
  14 + {
  15 + label: '卡片模式',
  16 + value: EnumTableCardMode.CARD,
  17 + icon: 'ant-design:appstore-outlined',
  18 + },
  19 + {
  20 + label: '列表模式',
  21 + value: EnumTableCardMode.TABLE,
  22 + icon: 'ant-design:unordered-list-outlined',
  23 + },
  24 +];
  25 +
  26 +export const TABLE_CHART_MODE_LIST: ModeList[] = [
  27 + {
  28 + label: '图表模式',
  29 + value: EnumTableChartMode.CHART,
  30 + icon: 'ant-design:line-chart-outlined',
  31 + },
  32 + {
  33 + label: '列表模式',
  34 + value: EnumTableChartMode.TABLE,
  35 + icon: 'ant-design:unordered-list-outlined',
  36 + },
  37 +];
... ...
  1 +export { default as ModeSwitchButton } from './ModeSwitchButton.vue';
  2 +export { default as CardLayoutButton } from './CardLayoutButton.vue';
  3 +export {
  4 + EnumTableCardMode,
  5 + EnumTableChartMode,
  6 + TABLE_CARD_MODE_LIST,
  7 + TABLE_CHART_MODE_LIST,
  8 +} from './const';
  9 +export type { ModeList } from './types';
... ...
  1 +import { ExtractPropTypes } from 'vue';
  2 +import Icon from '../Icon';
  3 +
  4 +export interface ModeList {
  5 + value: string;
  6 + label: string;
  7 + icon?: string;
  8 + iconProps?: ExtractPropTypes<typeof Icon>;
  9 +}
... ...
... ... @@ -11,10 +11,10 @@
11 11 >
12 12 <template #toolbar>
13 13 <a-button type="primary" @click="handleSwitchMode">分屏模式</a-button>
14   - <Authority value="api:yt:video:post">
  14 + <Authority :value="CameraPermission.CREATE">
15 15 <a-button type="primary" @click="handleCreateOrEdit(null)"> 新增视频 </a-button>
16 16 </Authority>
17   - <Authority value="api:yt:video:delete">
  17 + <Authority :value="CameraPermission.DELETE">
18 18 <Popconfirm
19 19 title="您确定要批量删除数据"
20 20 ok-text="确定"
... ... @@ -50,12 +50,12 @@
50 50 {
51 51 label: '预览',
52 52 icon: 'clarity:note-edit-line',
53   - auth: 'api:yt:video:get',
  53 + auth: CameraPermission.PREVIEW,
54 54 onClick: handleViewVideo.bind(null, record),
55 55 },
56 56 {
57 57 label: '编辑',
58   - auth: 'api:yt:video:update',
  58 + auth: CameraPermission.UPDATE,
59 59 icon: 'clarity:note-edit-line',
60 60 onClick: handleCreateOrEdit.bind(null, record),
61 61 },
... ... @@ -63,7 +63,7 @@
63 63 :drop-down-actions="[
64 64 {
65 65 label: '删除',
66   - auth: 'api:yt:video:delete',
  66 + auth: CameraPermission.DELETE,
67 67 icon: 'ant-design:delete-outlined',
68 68 color: 'error',
69 69 popConfirm: {
... ... @@ -89,7 +89,7 @@
89 89 import CameraDrawer from './CameraDrawer.vue';
90 90 import { useResetOrganizationTree, OrganizationIdTree } from '/@/views/common/organizationIdTree';
91 91 import { cameraPage, deleteCameraManage } from '/@/api/camera/cameraManager';
92   - import { searchFormSchema, columns, AccessMode, PageMode } from './config.data';
  92 + import { searchFormSchema, columns, AccessMode, PageMode, CameraPermission } from './config.data';
93 93 import VideoPreviewModal from './DialogPreviewVideo.vue';
94 94 import { useModal } from '/@/components/Modal';
95 95 import { Authority } from '/@/components/Authority';
... ... @@ -199,6 +199,7 @@
199 199 registerModal,
200 200 AccessMode,
201 201 handleSwitchMode,
  202 + CameraPermission,
202 203 };
203 204 },
204 205 });
... ...
... ... @@ -8,13 +8,14 @@
8 8 import { useFullscreen } from '@vueuse/core';
9 9 import CameraDrawer from './CameraDrawer.vue';
10 10 import { useDrawer } from '/@/components/Drawer';
11   - import { AccessMode, PageMode } from './config.data';
  11 + import { AccessMode, CameraPermission, PageMode } from './config.data';
12 12 import SvgIcon from '/@/components/Icon/src/SvgIcon.vue';
13 13 import { getStreamingPlayUrl } from '/@/api/camera/cameraManager';
14 14 import { buildUUID } from '/@/utils/uuid';
15 15 import { BasicVideoPlay, getVideoTypeByUrl } from '/@/components/Video';
16 16 import { VideoJsPlayerOptions } from 'video.js';
17 17 import { getBoundingClientRect } from '/@/utils/domUtils';
  18 + import { Authority } from '/@/components/Authority';
18 19
19 20 type CameraRecordItem = CameraRecord & {
20 21 canPlay?: boolean;
... ... @@ -220,7 +221,9 @@
220 221 </div>
221 222 <div class="flex items-center gap-4">
222 223 <div class="flex">
223   - <Button type="primary" @click="handleAddCamera">新增视频</Button>
  224 + <Authority :value="CameraPermission.CREATE">
  225 + <Button type="primary" @click="handleAddCamera">新增视频</Button>
  226 + </Authority>
224 227 </div>
225 228 <Space>
226 229 <Button type="primary" @click="handleChangeMode(PageMode.SPLIT_SCREEN_MODE)">
... ...
... ... @@ -6,6 +6,14 @@ import type { FormSchema as QFormSchema } from '/@/components/Form/index';
6 6 import { CameraVideoUrl, CameraMaxLength, MediaTypeValidate } from '/@/utils/rules';
7 7 import { h } from 'vue';
8 8 import SnHelpMessage from './SnHelpMessage.vue';
  9 +
  10 +export enum CameraPermission {
  11 + PREVIEW = 'api:yt:video:get',
  12 + CREATE = 'api:yt:video:post',
  13 + UPDATE = 'api:yt:video:update',
  14 + DELETE = 'api:yt:video:delete',
  15 +}
  16 +
9 17 export enum AccessMode {
10 18 ManuallyEnter = 0,
11 19 Streaming = 1,
... ...
... ... @@ -9,6 +9,14 @@ export enum Platform {
9 9 PC = 'pc',
10 10 }
11 11
  12 +export enum ConfigurationPermission {
  13 + CREATE = 'api:yt:configuration:center:post',
  14 + UPDATE = 'api:yt:configuration:center:update',
  15 + DELETE = 'api:yt:configuration:center:delete',
  16 + DESIGN = 'api:yt:configuration:center:get_configuration_info:design',
  17 + PREVIEW = 'api:yt:configuration:center:get_configuration_info:preview',
  18 +}
  19 +
12 20 export const PC_DEFAULT_CONTENT =
13 21 '<mxfile><diagram>dZHBDsIgDIafhvuEzOh5Tr142sEzGXWQsHVhmKFP7xbAidMT5fv/UtoSVrTuZHgvLyhAE5oJR9iBUMrybT4dM3l4stnTzJPGKBHYAir1hACj7a4EDInRImqr+hTW2HVQ24RxY3BMbTfUadWeN7ACVc31ml6VsPK7jVk4g2pkLJ3tgtLy6A5gkFzg+IFYSVhhEK2PWleAnscXB+Pzjn/U988MdPZHwhQsb0+XZEesfAE=</diagram></mxfile>';
14 22
... ...
... ... @@ -7,7 +7,7 @@
7 7 EditOutlined,
8 8 EllipsisOutlined,
9 9 } from '@ant-design/icons-vue';
10   - import { onMounted, reactive, ref, unref } from 'vue';
  10 + import { computed, onMounted, reactive, ref, unref } from 'vue';
11 11 import { OrganizationIdTree, useResetOrganizationTree } from '../../common/organizationIdTree';
12 12 import {
13 13 deleteConfigurationCenter,
... ... @@ -17,7 +17,7 @@
17 17 import { PageWrapper } from '/@/components/Page';
18 18 import { Dropdown } from '/@/components/Dropdown';
19 19 import { BasicForm, useForm } from '/@/components/Form';
20   - import { searchFormSchema } from './center.data';
  20 + import { ConfigurationPermission, searchFormSchema } from './center.data';
21 21 import { useMessage } from '/@/hooks/web/useMessage';
22 22 import { Authority } from '/@/components/Authority';
23 23 import { isDevMode } from '/@/utils/env';
... ... @@ -98,6 +98,16 @@
98 98
99 99 const [registerDrawer, { openDrawer }] = useDrawer();
100 100
  101 + const { hasPermission } = usePermission();
  102 +
  103 + const getPreviewFlag = computed(() => {
  104 + return hasPermission(ConfigurationPermission.PREVIEW);
  105 + });
  106 +
  107 + const getDesignFlag = computed(() => {
  108 + return hasPermission(ConfigurationPermission.DESIGN);
  109 + });
  110 +
101 111 const handleCreateOrUpdate = (record?: ConfigurationCenterItemsModal) => {
102 112 if (record) {
103 113 openDrawer(true, {
... ... @@ -114,15 +124,15 @@
114 124 const { configurationPrefix } = useGlobSetting();
115 125 const isDev = isDevMode();
116 126
117   - const { hasPermission } = usePermission();
118 127 const handlePreview = (record: ConfigurationCenterItemsModal) => {
119   - if (!hasPermission('api:yt:configuration:center:get_configuration_info:get')) return;
  128 + if (!unref(getPreviewFlag)) return;
120 129 window.open(
121 130 `${configurationPrefix}/${isDev ? '?dev=1&' : '?'}configurationId=${record!.id}&lightbox=1`
122 131 );
123 132 };
124 133
125 134 const handleDesign = (record: ConfigurationCenterItemsModal) => {
  135 + if (!unref(getDesignFlag)) return;
126 136 window.open(`${configurationPrefix}/${isDev ? '?dev=1&' : '?'}configurationId=${record!.id}`);
127 137 };
128 138
... ... @@ -175,7 +185,9 @@
175 185 >
176 186 <template #header>
177 187 <div class="flex gap-3 justify-end">
178   - <Button type="primary" @click="handleCreateOrUpdate()">新增组态</Button>
  188 + <Authority :value="ConfigurationPermission.CREATE">
  189 + <Button type="primary" @click="handleCreateOrUpdate()">新增组态</Button>
  190 + </Authority>
179 191 <Popover :trigger="['hover']">
180 192 <template #content>
181 193 <div class="w-50">
... ... @@ -204,7 +216,7 @@
204 216 <List.Item>
205 217 <Card hoverable>
206 218 <template #cover>
207   - <div class="h-full w-full !flex justify-center items-center text-center">
  219 + <div class="h-full w-full !flex justify-center items-center text-center p-1">
208 220 <img
209 221 class="w-full h-36"
210 222 alt="example"
... ... @@ -214,29 +226,35 @@
214 226 </div>
215 227 </template>
216 228 <template class="ant-card-actions" #actions>
217   - <Authority value="api:yt:configuration:center:get_configuration_info:get">
218   - <Tooltip title="预览">
219   - <EyeOutlined key="setting" @click="handlePreview(item)" />
220   - </Tooltip>
221   - </Authority>
222   - <Authority value="api:yt:configuration:center:get_configuration_info:get">
223   - <Tooltip title="设计">
224   - <EditOutlined key="edit" @click="handleDesign(item)" />
225   - </Tooltip>
226   - </Authority>
  229 + <Tooltip title="预览">
  230 + <EyeOutlined
  231 + :class="getPreviewFlag ? '' : '!cursor-not-allowed !text-gray-200'"
  232 + key="setting"
  233 + @click="handlePreview(item)"
  234 + />
  235 + </Tooltip>
  236 + <Tooltip title="设计">
  237 + <EditOutlined
  238 + :class="getDesignFlag ? '' : '!cursor-not-allowed !text-gray-200'"
  239 + key="edit"
  240 + @click="handleDesign(item)"
  241 + />
  242 + </Tooltip>
227 243 <Dropdown
228 244 :dropMenuList="[
229 245 {
230 246 text: '编辑',
231   - auth: 'api:yt:configuration:center:update',
  247 + auth: ConfigurationPermission.UPDATE,
232 248 icon: 'clarity:note-edit-line',
  249 + event: '',
233 250 onClick: handleCreateOrUpdate.bind(null, item),
234 251 },
235 252 {
236 253 text: '删除',
237   - auth: 'api:yt:configuration:center:delete',
  254 + auth: ConfigurationPermission.DELETE,
238 255 icon: 'ant-design:delete-outlined',
239 256 color: 'error',
  257 + event: '',
240 258 onClick: handleDelete.bind(null, item),
241 259 },
242 260 ]"
... ...
1 1 <script lang="ts" setup>
2 2 import { computed, nextTick, onMounted, onUnmounted, Ref, ref, unref } from 'vue';
3 3 import { getDeviceHistoryInfo } from '/@/api/alarm/position';
4   - import { Empty, Spin, Tooltip, Button } from 'ant-design-vue';
  4 + import { Empty, Spin } from 'ant-design-vue';
5 5 import { useECharts } from '/@/hooks/web/useECharts';
6 6 import { AggregateDataEnum, selectDeviceAttrSchema } from '/@/views/device/localtion/config.data';
7 7 import { useTimePeriodForm } from '/@/views/device/localtion/cpns/TimePeriodForm';
... ... @@ -13,8 +13,12 @@
13 13 import { useHistoryData } from '../../hook/useHistoryData';
14 14 import { formatToDateTime } from '/@/utils/dateUtil';
15 15 import { useTable, BasicTable } from '/@/components/Table';
16   - import { LineChartOutlined, BarsOutlined } from '@ant-design/icons-vue';
17 16 import { DataTypeEnum } from '/@/components/Form/src/externalCompns/components/StructForm/config';
  17 + import {
  18 + ModeSwitchButton,
  19 + TABLE_CHART_MODE_LIST,
  20 + EnumTableChartMode,
  21 + } from '/@/components/Widget';
18 22
19 23 interface DeviceDetail {
20 24 tbDeviceId: string;
... ... @@ -26,12 +30,7 @@
26 30 attr?: string;
27 31 }>();
28 32
29   - enum Mode {
30   - TABLE = 'table',
31   - CHART = 'chart',
32   - }
33   -
34   - const mode = ref<Mode>(Mode.CHART);
  33 + const mode = ref<EnumTableChartMode>(EnumTableChartMode.CHART);
35 34
36 35 const chartRef = ref();
37 36
... ... @@ -151,9 +150,9 @@
151 150 attrInfo?.detail.dataType.type as unknown as DataTypeEnum
152 151 )
153 152 ) {
154   - mode.value = Mode.TABLE;
  153 + mode.value = EnumTableChartMode.TABLE;
155 154 } else {
156   - mode.value = Mode.CHART;
  155 + mode.value = EnumTableChartMode.CHART;
157 156 }
158 157 }
159 158 } catch (error) {}
... ... @@ -200,7 +199,7 @@
200 199 setOptions(setChartOptions(res, selectedKeys));
201 200 };
202 201
203   - const switchMode = (flag: Mode) => {
  202 + const switchMode = (flag: EnumTableChartMode) => {
204 203 mode.value = flag;
205 204 };
206 205
... ... @@ -221,55 +220,32 @@
221 220 </section>
222 221 <section class="bg-white p-3">
223 222 <Spin :spinning="loading" :absolute="true">
224   - <div v-show="mode === Mode.CHART" class="flex h-70px items-center justify-end p-2">
225   - <Tooltip title="图表模式">
226   - <Button
227   - :class="[mode === Mode.CHART && '!bg-blue-500 svg:text-light-50']"
228   - class="!p-2 !children:flex flex justify-center items-center border-r-0"
229   - @click="switchMode(Mode.CHART)"
230   - >
231   - <LineChartOutlined />
232   - </Button>
233   - </Tooltip>
234   -
235   - <Tooltip title="列表模式">
236   - <Button
237   - class="!p-2 !children:flex flex justify-center items-center"
238   - @click="switchMode(Mode.TABLE)"
239   - >
240   - <BarsOutlined />
241   - </Button>
242   - </Tooltip>
  223 + <div
  224 + v-show="mode === EnumTableChartMode.CHART"
  225 + class="flex h-70px items-center justify-end p-2"
  226 + >
  227 + <ModeSwitchButton
  228 + v-model:value="mode"
  229 + :mode="TABLE_CHART_MODE_LIST"
  230 + @change="switchMode"
  231 + />
243 232 </div>
244 233 <div
245   - v-show="isNull && mode === Mode.CHART"
  234 + v-show="isNull && mode === EnumTableChartMode.CHART"
246 235 ref="chartRef"
247 236 :style="{ height: '400px', width: '100%' }"
248 237 >
249 238 </div>
250   - <Empty v-show="!isNull && mode === Mode.CHART" />
  239 + <Empty v-show="!isNull && mode === EnumTableChartMode.CHART" />
251 240
252   - <BasicTable v-show="mode === Mode.TABLE" @register="registerTable">
  241 + <BasicTable v-show="mode === EnumTableChartMode.TABLE" @register="registerTable">
253 242 <template #toolbar>
254   - <div v-show="mode === Mode.TABLE" class="flex h-70px items-center justify-end p-2">
255   - <Tooltip title="图表模式">
256   - <Button
257   - class="!p-2 !children:flex flex justify-center items-center border-r-0"
258   - @click="switchMode(Mode.CHART)"
259   - >
260   - <LineChartOutlined />
261   - </Button>
262   - </Tooltip>
263   -
264   - <Tooltip title="列表模式">
265   - <Button
266   - :class="[mode === Mode.TABLE && '!bg-blue-500 svg:text-light-50']"
267   - class="!p-2 !children:flex flex justify-center items-center"
268   - @click="switchMode(Mode.TABLE)"
269   - >
270   - <BarsOutlined />
271   - </Button>
272   - </Tooltip>
  243 + <div class="flex h-70px items-center justify-end p-2">
  244 + <ModeSwitchButton
  245 + v-model:value="mode"
  246 + :mode="TABLE_CHART_MODE_LIST"
  247 + @change="switchMode"
  248 + />
273 249 </div>
274 250 </template>
275 251 </BasicTable>
... ...
1 1 <script lang="ts" setup>
2 2 import { nextTick, onUnmounted, reactive, ref, unref } from 'vue';
3   - import { List, Button, Tooltip, Card } from 'ant-design-vue';
  3 + import { List, Button, Card } from 'ant-design-vue';
4 4 import { PageWrapper } from '/@/components/Page';
5   - import { AppstoreOutlined, BarsOutlined } from '@ant-design/icons-vue';
6 5 import { BasicTable, useTable } from '/@/components/Table';
7 6 import { realTimeDataColumns } from '../../config/detail.config';
8 7 import { useWebSocket } from '@vueuse/core';
... ... @@ -20,6 +19,7 @@
20 19 import { isArray, isObject } from '/@/utils/is';
21 20 import { DataTypeEnum } from '/@/components/Form/src/externalCompns/components/StructForm/config';
22 21 import { useGlobSetting } from '/@/hooks/setting';
  22 + import { ModeSwitchButton, EnumTableCardMode } from '/@/components/Widget';
23 23
24 24 interface ReceiveMessage {
25 25 data: {
... ... @@ -112,14 +112,9 @@
112 112
113 113 const [registerModal, { openModal }] = useModal();
114 114
115   - enum Mode {
116   - TABLE = 'table',
117   - CARD = 'card',
118   - }
119   -
120   - const mode = ref<Mode>(Mode.CARD);
  115 + const mode = ref<EnumTableCardMode>(EnumTableCardMode.CARD);
121 116
122   - const switchMode = async (value: Mode) => {
  117 + const switchMode = async (value: EnumTableCardMode) => {
123 118 mode.value = value;
124 119 await nextTick();
125 120 setTableData(socketInfo.dataSource);
... ... @@ -210,28 +205,14 @@
210 205 </div>
211 206 </section>
212 207 <section class="bg-light-50">
213   - <div v-show="mode === Mode.CARD" class="flex h-70px items-center justify-end p-2">
214   - <Tooltip title="卡片模式">
215   - <Button
216   - :class="[mode === Mode.CARD && '!bg-blue-500 svg:text-light-50']"
217   - class="!p-2 !children:flex flex justify-center items-center border-r-0"
218   - @click="switchMode(Mode.CARD)"
219   - >
220   - <AppstoreOutlined />
221   - </Button>
222   - </Tooltip>
223   -
224   - <Tooltip title="列表模式">
225   - <Button
226   - class="!p-2 !children:flex flex justify-center items-center"
227   - @click="switchMode(Mode.TABLE)"
228   - >
229   - <BarsOutlined />
230   - </Button>
231   - </Tooltip>
  208 + <div
  209 + v-show="mode === EnumTableCardMode.CARD"
  210 + class="flex h-70px items-center justify-end p-2"
  211 + >
  212 + <ModeSwitchButton v-model:value="mode" @change="switchMode" />
232 213 </div>
233 214 <List
234   - v-if="mode === Mode.CARD"
  215 + v-if="mode === EnumTableCardMode.CARD"
235 216 class="list-mode !px-2"
236 217 :data-source="socketInfo.dataSource"
237 218 :grid="grid"
... ... @@ -260,27 +241,13 @@
260 241 </List>
261 242 </section>
262 243
263   - <BasicTable v-if="mode === Mode.TABLE" @register="registerTable">
  244 + <BasicTable v-if="mode === EnumTableCardMode.TABLE" @register="registerTable">
264 245 <template #toolbar>
265   - <div v-show="mode === Mode.TABLE" class="flex h-70px items-center justify-end p-2">
266   - <Tooltip title="卡片模式">
267   - <Button
268   - class="!p-2 !children:flex flex justify-center items-center border-r-0"
269   - @click="switchMode(Mode.CARD)"
270   - >
271   - <AppstoreOutlined />
272   - </Button>
273   - </Tooltip>
274   -
275   - <Tooltip title="列表模式">
276   - <Button
277   - :class="[mode === Mode.TABLE && '!bg-blue-500 svg:text-light-50']"
278   - class="!p-2 !children:flex flex justify-center items-center"
279   - @click="switchMode(Mode.TABLE)"
280   - >
281   - <BarsOutlined />
282   - </Button>
283   - </Tooltip>
  246 + <div
  247 + v-show="mode === EnumTableCardMode.TABLE"
  248 + class="flex h-70px items-center justify-end p-2"
  249 + >
  250 + <ModeSwitchButton v-model:value="mode" @change="switchMode" />
284 251 </div>
285 252 </template>
286 253 </BasicTable>
... ...
... ... @@ -50,28 +50,18 @@
50 50 <TimePeriodForm @register="timePeriodRegister" />
51 51 <section>
52 52 <Spin :spinning="loading">
53   - <div v-show="mode === Mode.CHART" class="flex h-70px items-center justify-end p-2">
54   - <Tooltip title="图表模式">
55   - <Button
56   - :class="[mode === Mode.CHART && '!bg-blue-500 svg:text-light-50']"
57   - class="!p-2 !children:flex flex justify-center items-center border-r-0"
58   - @click="switchMode(Mode.CHART)"
59   - >
60   - <LineChartOutlined />
61   - </Button>
62   - </Tooltip>
63   -
64   - <Tooltip title="列表模式">
65   - <Button
66   - class="!p-2 !children:flex flex justify-center items-center"
67   - @click="switchMode(Mode.TABLE)"
68   - >
69   - <BarsOutlined />
70   - </Button>
71   - </Tooltip>
  53 + <div
  54 + v-show="mode === EnumTableChartMode.CHART"
  55 + class="flex h-70px items-center justify-end p-2"
  56 + >
  57 + <ModeSwitchButton
  58 + v-model:value="mode"
  59 + :mode="TABLE_CHART_MODE_LIST"
  60 + @change="switchMode"
  61 + />
72 62 </div>
73 63 <div
74   - v-show="isNull && mode === Mode.CHART"
  64 + v-show="isNull && mode === EnumTableChartMode.CHART"
75 65 ref="chartRef"
76 66 :style="{ height: '400px', width }"
77 67 >
... ... @@ -79,30 +69,17 @@
79 69 <Empty
80 70 :style="{ height: '550px', width }"
81 71 class="flex flex-col justify-center items-center"
82   - v-show="!isNull && mode === Mode.CHART"
  72 + v-show="!isNull && mode === EnumTableChartMode.CHART"
83 73 />
84 74
85   - <BasicTable v-show="mode === Mode.TABLE" @register="registerAttrTable">
  75 + <BasicTable v-show="mode === EnumTableChartMode.TABLE" @register="registerAttrTable">
86 76 <template #toolbar>
87   - <div v-show="mode === Mode.TABLE" class="flex h-70px items-center justify-end p-2">
88   - <Tooltip title="图表模式">
89   - <Button
90   - class="!p-2 !children:flex flex justify-center items-center border-r-0"
91   - @click="switchMode(Mode.CHART)"
92   - >
93   - <LineChartOutlined />
94   - </Button>
95   - </Tooltip>
96   -
97   - <Tooltip title="列表模式">
98   - <Button
99   - :class="[mode === Mode.TABLE && '!bg-blue-500 svg:text-light-50']"
100   - class="!p-2 !children:flex flex justify-center items-center"
101   - @click="switchMode(Mode.TABLE)"
102   - >
103   - <BarsOutlined />
104   - </Button>
105   - </Tooltip>
  77 + <div class="flex h-70px items-center justify-end p-2">
  78 + <ModeSwitchButton
  79 + v-model:value="mode"
  80 + :mode="TABLE_CHART_MODE_LIST"
  81 + @change="switchMode"
  82 + />
106 83 </div>
107 84 </template>
108 85 </BasicTable>
... ... @@ -134,7 +111,7 @@
134 111 import { formSchema, columns } from './config.data';
135 112 import { BasicTable, useTable } from '/@/components/Table';
136 113 import { devicePage } from '/@/api/alarm/contact/alarmContact';
137   - import { Tag, Empty, Spin, Tooltip, Button } from 'ant-design-vue';
  114 + import { Tag, Empty, Spin } from 'ant-design-vue';
138 115 import { DeviceState } from '/@/api/device/model/deviceModel';
139 116 import { BAI_DU_MAP_URL } from '/@/utils/fnUtils';
140 117 import { useModal, BasicModal } from '/@/components/Modal';
... ... @@ -157,8 +134,12 @@
157 134 import { QueryWay, SchemaFiled, AggregateDataEnum } from './cpns/TimePeriodForm/config';
158 135 import { useAsyncQueue } from './useAsyncQueue';
159 136 import { useHistoryData } from '../list/hook/useHistoryData';
160   - import { LineChartOutlined, BarsOutlined } from '@ant-design/icons-vue';
161 137 import { formatToDateTime } from '/@/utils/dateUtil';
  138 + import {
  139 + TABLE_CHART_MODE_LIST,
  140 + EnumTableChartMode,
  141 + ModeSwitchButton,
  142 + } from '/@/components/Widget';
162 143
163 144 interface DeviceInfo {
164 145 alarmStatus: 0 | 1;
... ... @@ -183,10 +164,7 @@
183 164 DeviceDetailDrawer,
184 165 TimePeriodForm,
185 166 Spin,
186   - Tooltip,
187   - Button,
188   - LineChartOutlined,
189   - BarsOutlined,
  167 + ModeSwitchButton,
190 168 },
191 169 props: {
192 170 width: {
... ... @@ -199,12 +177,7 @@
199 177 },
200 178 },
201 179 setup() {
202   - enum Mode {
203   - TABLE = 'table',
204   - CHART = 'chart',
205   - }
206   -
207   - const mode = ref<Mode>(Mode.CHART);
  180 + const mode = ref<EnumTableChartMode>(EnumTableChartMode.CHART);
208 181 let entityId = '';
209 182 let globalRecord: any = {};
210 183 const wrapRef = ref<HTMLDivElement | null>(null);
... ... @@ -571,13 +544,14 @@
571 544 openGatewayDetailDrawer(true, data);
572 545 };
573 546
574   - const switchMode = (flag: Mode) => {
  547 + const switchMode = (flag: EnumTableChartMode) => {
575 548 mode.value = flag;
576 549 };
577 550
578 551 return {
579 552 mode,
580   - Mode,
  553 + EnumTableChartMode,
  554 + TABLE_CHART_MODE_LIST,
581 555 wrapRef,
582 556 registerTable,
583 557 deviceRowClick,
... ...
  1 +<script lang="ts" setup>
  2 + import { PageWrapper } from '/@/components/Page';
  3 + import { BasicForm, useForm } from '/@/components/Form';
  4 + import { List, Button, Tooltip, Card, PaginationProps, Image } from 'ant-design-vue';
  5 + import { ReloadOutlined, EyeOutlined, FormOutlined, MoreOutlined } from '@ant-design/icons-vue';
  6 + import { computed, onMounted, reactive, ref, unref } from 'vue';
  7 + import { CardLayoutButton, EnumTableCardMode, ModeSwitchButton } from '/@/components/Widget';
  8 + import { Authority } from '/@/components/Authority';
  9 + import {
  10 + deviceConfigDelete,
  11 + deviceConfigGetQuery,
  12 + setDeviceProfileIsDefaultApi,
  13 + } from '/@/api/device/deviceConfigApi';
  14 + import { ProfileRecord } from '/@/api/device/model/deviceConfigModel';
  15 + import { Dropdown } from '/@/components/Dropdown';
  16 + import {
  17 + defaultObj,
  18 + searchFormSchema,
  19 + DeviceTypeName,
  20 + ProductPermission,
  21 + } from './device.profile.data';
  22 + import { useMessage } from '/@/hooks/web/useMessage';
  23 + import { useSyncConfirm } from '/@/hooks/component/useSyncConfirm';
  24 + import DeviceProfileModal from './DeviceProfileModal.vue';
  25 + import DeviceProfileDrawer from './DeviceProfileDrawer.vue';
  26 + import { useModal } from '/@/components/Modal';
  27 + import { useDrawer } from '/@/components/Drawer';
  28 + import productDefault from '/@/assets/icons/product-default.svg';
  29 + import { usePermission } from '/@/hooks/web/usePermission';
  30 +
  31 + defineProps<{
  32 + mode: EnumTableCardMode;
  33 + }>();
  34 +
  35 + const emit = defineEmits(['changeMode']);
  36 +
  37 + enum DropMenuEvent {
  38 + SET_DEFAULT = 'setDefault',
  39 + DELETE = 'delete',
  40 + }
  41 + const IMAGE_FALLBACK = productDefault;
  42 +
  43 + const { createMessage } = useMessage();
  44 + const { createSyncConfirm } = useSyncConfirm();
  45 +
  46 + const [register, { getFieldsValue }] = useForm({
  47 + showAdvancedButton: true,
  48 + labelWidth: 100,
  49 + compact: true,
  50 + baseColProps: { span: 8 },
  51 + schemas: searchFormSchema,
  52 + submitFunc: async () => {
  53 + getDataSource();
  54 + },
  55 + });
  56 +
  57 + const [registerModal, { openModal }] = useModal();
  58 + const [registerDrawer, { openDrawer }] = useDrawer();
  59 +
  60 + const loading = ref(false);
  61 +
  62 + const pagination = reactive<PaginationProps>({
  63 + size: 'small',
  64 + showTotal: (total: number) => `共 ${total} 条数据`,
  65 + current: 1,
  66 + onChange: (page: number) => {
  67 + pagination.current = page;
  68 + getDataSource();
  69 + },
  70 + });
  71 +
  72 + const dataSource = ref<ProfileRecord[]>([]);
  73 +
  74 + const colNumber = ref(4);
  75 +
  76 + const getSelectAllFlag = computed(() => {
  77 + return unref(dataSource).every((item) => item.checked);
  78 + });
  79 +
  80 + const getCheckedRecord = computed(() => {
  81 + return unref(dataSource)
  82 + .filter((item) => item.checked)
  83 + .map((item) => item.id);
  84 + });
  85 +
  86 + const getDataSource = async () => {
  87 + try {
  88 + loading.value = true;
  89 + const params = getFieldsValue();
  90 + const { items, total } = await deviceConfigGetQuery({
  91 + page: pagination.current,
  92 + pageSize: unref(colNumber) * 2,
  93 + ...params,
  94 + });
  95 + pagination.total = total;
  96 + dataSource.value = items.map((item) => ({ ...item, checked: false }));
  97 + } catch (error) {
  98 + } finally {
  99 + loading.value = false;
  100 + }
  101 + };
  102 +
  103 + const { hasPermission } = usePermission();
  104 +
  105 + const getHasDeleteFlag = computed(() => {
  106 + return hasPermission(ProductPermission.DELETE);
  107 + });
  108 +
  109 + const getHasDetailFlag = computed(() => {
  110 + return hasPermission(ProductPermission.DETAIL);
  111 + });
  112 +
  113 + const getHasUpdateFlag = computed(() => {
  114 + return hasPermission(ProductPermission.UPDATE);
  115 + });
  116 +
  117 + const getDropDownList = (record: ProfileRecord) => {
  118 + const list = [
  119 + {
  120 + text: '默认',
  121 + event: DropMenuEvent.SET_DEFAULT,
  122 + icon: 'ant-design:unordered-list-outlined',
  123 + onClick: handleSetDefault.bind(null, record),
  124 + },
  125 + ];
  126 + if (unref(getHasDeleteFlag)) {
  127 + list.push({
  128 + text: '删除',
  129 + event: DropMenuEvent.DELETE,
  130 + icon: 'ant-design:delete-outlined',
  131 + onClick: handleDelete.bind(null, [record.id]),
  132 + });
  133 + }
  134 + return list;
  135 + };
  136 +
  137 + const handleModeChange = (mode: EnumTableCardMode) => {
  138 + emit('changeMode', mode);
  139 + };
  140 +
  141 + const handleCheckCard = (item: ProfileRecord) => {
  142 + item.checked = !item.checked;
  143 + };
  144 +
  145 + const handleSelectAll = () => {
  146 + dataSource.value = unref(dataSource).map((item) => {
  147 + return {
  148 + ...item,
  149 + checked: !unref(getSelectAllFlag),
  150 + };
  151 + });
  152 + };
  153 +
  154 + const handleCreate = () => {
  155 + openModal(true, {
  156 + isUpdate: false,
  157 + });
  158 + };
  159 +
  160 + const handleShowDetail = (record: ProfileRecord) => {
  161 + if (!unref(getHasDetailFlag)) return;
  162 + openDrawer(true, { record });
  163 + };
  164 +
  165 + const handleUpdate = (record: ProfileRecord) => {
  166 + if (!unref(getHasUpdateFlag)) return;
  167 + openModal(true, {
  168 + record,
  169 + isUpdate: true,
  170 + });
  171 + };
  172 +
  173 + const handleDelete = async (id: string[]) => {
  174 + try {
  175 + await createSyncConfirm({ iconType: 'warning', content: '是否确认删除操作?' });
  176 + await deviceConfigDelete(id);
  177 + createMessage.success('删除成功');
  178 + await getDataSource();
  179 + } catch (error) {
  180 + throw error;
  181 + }
  182 + };
  183 +
  184 + const handleSetDefault = async (record: ProfileRecord) => {
  185 + try {
  186 + const { tbProfileId } = record;
  187 + const data = await setDeviceProfileIsDefaultApi(tbProfileId, 'default', defaultObj);
  188 + if (!data) return createMessage.error('设置该产品为默认失败');
  189 + createMessage.success('设置该产品为默认成功');
  190 + await getDataSource();
  191 + } catch (error) {
  192 + throw error;
  193 + }
  194 + };
  195 +
  196 + onMounted(() => {
  197 + getDataSource();
  198 + });
  199 +</script>
  200 +
  201 +<template>
  202 + <PageWrapper dense contentFullHeight contentClass="flex">
  203 + <section class="flex-auto p-4 w-full profile-list">
  204 + <div class="flex-auto w-full bg-light-50 dark:bg-dark-900 p-4">
  205 + <BasicForm @register="register" />
  206 + </div>
  207 + <List
  208 + ref="listEl"
  209 + :loading="loading"
  210 + class="flex-auto bg-light-50 dark:bg-dark-900 !p-2 !mt-4"
  211 + position="bottom"
  212 + :pagination="pagination"
  213 + :data-source="dataSource"
  214 + :grid="{ gutter: 4, column: colNumber }"
  215 + >
  216 + <template #header>
  217 + <div class="flex gap-3 justify-end">
  218 + <Authority :value="ProductPermission.CREATE">
  219 + <Button type="primary" @click="handleCreate">新增产品</Button>
  220 + </Authority>
  221 +
  222 + <Button type="primary" @click="handleSelectAll">
  223 + {{ getSelectAllFlag ? '反选' : '全选' }}
  224 + </Button>
  225 + <Authority :value="ProductPermission.DELETE">
  226 + <Button
  227 + type="primary"
  228 + danger
  229 + :disabled="!getCheckedRecord.length"
  230 + @click="handleDelete(getCheckedRecord)"
  231 + >
  232 + 批量删除
  233 + </Button>
  234 + </Authority>
  235 +
  236 + <ModeSwitchButton :value="$props.mode" @change="handleModeChange" />
  237 + <CardLayoutButton v-model:value="colNumber" @change="getDataSource" />
  238 +
  239 + <Tooltip title="刷新">
  240 + <Button type="primary" @click="getDataSource">
  241 + <ReloadOutlined :spin="loading" />
  242 + </Button>
  243 + </Tooltip>
  244 + </div>
  245 + </template>
  246 + <template #renderItem="{ item }">
  247 + <List.Item>
  248 + <Card
  249 + hoverable
  250 + @click="handleCheckCard(item)"
  251 + :class="item.checked ? '!border-blue-500 !border-2' : ''"
  252 + >
  253 + <template #cover>
  254 + <div class="h-full w-full !flex justify-center items-center text-center p-1">
  255 + <Image
  256 + @click.stop
  257 + :height="144"
  258 + :src="item.image"
  259 + placeholder
  260 + :fallback="IMAGE_FALLBACK"
  261 + />
  262 + </div>
  263 + </template>
  264 + <template class="ant-card-actions" #actions>
  265 + <Tooltip title="详情">
  266 + <EyeOutlined
  267 + :class="getHasDetailFlag ? '' : '!cursor-not-allowed !text-gray-200'"
  268 + key="setting"
  269 + @click.stop="handleShowDetail(item)"
  270 + />
  271 + </Tooltip>
  272 + <Tooltip title="编辑">
  273 + <FormOutlined
  274 + :class="getHasUpdateFlag ? '' : '!cursor-not-allowed !text-gray-200'"
  275 + key="edit"
  276 + @click.stop="handleUpdate(item)"
  277 + />
  278 + </Tooltip>
  279 + <Dropdown :trigger="['hover']" :drop-menu-list="getDropDownList(item)">
  280 + <MoreOutlined @click.stop class="transform rotate-90" />
  281 + </Dropdown>
  282 + </template>
  283 + <Card.Meta>
  284 + <template #title>
  285 + <span class="truncate"> {{ item.name }} </span>
  286 + </template>
  287 + <template #description>
  288 + <div class="truncate h-11">
  289 + <div class="truncate">{{ DeviceTypeName[item.deviceType] }} </div>
  290 + <div class="truncate">{{ item.transportType }} </div>
  291 + </div>
  292 + </template>
  293 + </Card.Meta>
  294 + </Card>
  295 + </List.Item>
  296 + </template>
  297 + </List>
  298 + </section>
  299 + <DeviceProfileModal @register="registerModal" @success="getDataSource" />
  300 + <DeviceProfileDrawer @register="registerDrawer" />
  301 + </PageWrapper>
  302 +</template>
  303 +
  304 +<style lang="less" scoped>
  305 + .profile-list:deep(.ant-image-img) {
  306 + width: 100% !important;
  307 + height: 100% !important;
  308 + }
  309 +</style>
... ...
  1 +<template>
  2 + <div>
  3 + <BasicTable
  4 + class="devide-profiles"
  5 + @register="registerTable"
  6 + :rowSelection="{ type: 'checkbox' }"
  7 + :clickToRowSelect="false"
  8 + >
  9 + <template #toolbar>
  10 + <Authority :value="ProductPermission.CREATE">
  11 + <a-button type="primary" @click="handleCreate"> 新增产品 </a-button>
  12 + </Authority>
  13 + <Authority :value="ProductPermission.IMPORT">
  14 + <ImpExcel @success="loadDataSuccess" dateFormat="YYYY-MM-DD">
  15 + <a-button @click="handleImport"> 导入产品 </a-button>
  16 + </ImpExcel>
  17 + </Authority>
  18 + <Authority :value="ProductPermission.DELETE">
  19 + <Popconfirm
  20 + title="您确定要批量删除数据"
  21 + ok-text="确定"
  22 + cancel-text="取消"
  23 + @confirm="handleDeleteOrBatchDelete(null)"
  24 + >
  25 + <a-button type="primary" color="error" :disabled="hasBatchDelete"> 批量删除 </a-button>
  26 + </Popconfirm>
  27 + </Authority>
  28 + <ModeSwitchButton :value="$props.mode" @change="handleModeChange" />
  29 + </template>
  30 + <template #img="{ record }">
  31 + <TableImg
  32 + :size="30"
  33 + :showBadge="false"
  34 + :simpleShow="true"
  35 + :imgList="
  36 + typeof record.image !== 'undefined' && record.image !== '' && record.image != null
  37 + ? [record.image]
  38 + : null
  39 + "
  40 + />
  41 + </template>
  42 + <template #action="{ record }">
  43 + <TableAction
  44 + :actions="[
  45 + {
  46 + label: '详情',
  47 + auth: ProductPermission.DETAIL,
  48 + icon: 'ant-design:eye-outlined',
  49 + onClick: handleDetailView.bind(null, record),
  50 + },
  51 + {
  52 + label: '编辑',
  53 + auth: ProductPermission.UPDATE,
  54 + icon: 'clarity:note-edit-line',
  55 + onClick: handleEdit.bind(null, record),
  56 + ifShow: () => {
  57 + return record.name !== 'default' ? true : false;
  58 + },
  59 + },
  60 + ]"
  61 + :drop-down-actions="[
  62 + {
  63 + label: '默认',
  64 + icon: 'ant-design:profile-outlined',
  65 + onClick: handleSetDefault.bind(null, record),
  66 + ifShow: () => {
  67 + return record.default === false;
  68 + },
  69 + },
  70 + {
  71 + label: '导出',
  72 + auth: ProductPermission.EXPORT,
  73 + icon: 'ant-design:login-outlined',
  74 + onClick: handleExport.bind(null, record),
  75 + },
  76 + {
  77 + label: '删除',
  78 + auth: ProductPermission.DELETE,
  79 + icon: 'ant-design:delete-outlined',
  80 + color: 'error',
  81 + popConfirm: {
  82 + title: '是否确认删除',
  83 + confirm: handleDeleteOrBatchDelete.bind(null, record),
  84 + },
  85 + ifShow: () => {
  86 + return record.default === false && record.name !== 'default';
  87 + },
  88 + },
  89 + ]"
  90 + />
  91 + </template>
  92 + </BasicTable>
  93 + <DeviceProfileModal @register="registerModal" @success="handleSuccess" />
  94 + <DeviceProfileDrawer @register="registerDrawer" />
  95 + <ExpExcelModal
  96 + ref="expExcelModalRef"
  97 + @register="registerExportModal"
  98 + @success="defaultHeader"
  99 + />
  100 + </div>
  101 +</template>
  102 +<script lang="ts" setup>
  103 + import { ref, nextTick, onUnmounted } from 'vue';
  104 + import { BasicTable, TableImg, useTable, TableAction, BasicColumn } from '/@/components/Table';
  105 + import { columns, searchFormSchema, defaultObj, ProductPermission } from './device.profile.data';
  106 + import { useMessage } from '/@/hooks/web/useMessage';
  107 + import {
  108 + deviceConfigGetQuery,
  109 + deviceConfigDelete,
  110 + setDeviceProfileIsDefaultApi,
  111 + } from '/@/api/device/deviceConfigApi';
  112 + import { useModal } from '/@/components/Modal';
  113 + import { useDrawer } from '/@/components/Drawer';
  114 + import DeviceProfileModal from '/@/views/device/profiles/DeviceProfileModal.vue';
  115 + import { ImpExcel, ExcelData } from '/@/components/Excel';
  116 + import { jsonToSheetXlsx, ExpExcelModal, ExportModalResult } from '/@/components/Excel';
  117 + import { Authority } from '/@/components/Authority';
  118 + import { useBatchDelete } from '/@/hooks/web/useBatchDelete';
  119 + import { Popconfirm } from 'ant-design-vue';
  120 + import DeviceProfileDrawer from './DeviceProfileDrawer.vue';
  121 + import { EnumTableCardMode, ModeSwitchButton } from '/@/components/Widget';
  122 +
  123 + defineProps<{
  124 + mode: EnumTableCardMode;
  125 + }>();
  126 +
  127 + const emit = defineEmits(['changeMode']);
  128 +
  129 + const exportData: any = ref([]);
  130 + const expExcelModalRef: any = ref(null);
  131 + const getPathUrl = ref('');
  132 + const getPathUrlName = ref('');
  133 + const disabled = ref(true);
  134 + const onCloseVal = ref(0);
  135 + const immediateStatus = ref(false);
  136 + const { createMessage } = useMessage();
  137 + const [registerModal, { openModal }] = useModal();
  138 + const [registerExportModal, { openModal: openModalExcel }] = useModal();
  139 + const [registerTable, { setProps, reload, setTableData, getForm }] = useTable({
  140 + title: '产品列表',
  141 + clickToRowSelect: false,
  142 + api: deviceConfigGetQuery,
  143 + immediate: immediateStatus.value,
  144 + columns,
  145 + formConfig: {
  146 + labelWidth: 120,
  147 + schemas: searchFormSchema,
  148 + },
  149 + rowKey: 'id',
  150 + useSearchForm: true,
  151 + showTableSetting: true,
  152 + bordered: true,
  153 + showIndexColumn: false,
  154 + actionColumn: {
  155 + width: 200,
  156 + title: '操作',
  157 + dataIndex: 'action',
  158 + slots: { customRender: 'action' },
  159 + fixed: 'right',
  160 + },
  161 + });
  162 + const { hasBatchDelete, handleDeleteOrBatchDelete, selectionOptions } = useBatchDelete(
  163 + deviceConfigDelete,
  164 + handleSuccess,
  165 + setProps
  166 + );
  167 + selectionOptions.rowSelection.getCheckboxProps = (record: Recordable) => {
  168 + // Demo:status为1的选择框禁用
  169 + if (record.default === true) {
  170 + return { disabled: true };
  171 + } else if (record.name == 'default') {
  172 + return { disabled: true };
  173 + } else {
  174 + return { disabled: false };
  175 + }
  176 + };
  177 + nextTick(() => {
  178 + setProps(selectionOptions);
  179 + });
  180 + /**
  181 + *@param url,name
  182 + **/
  183 + function getParam(url, name) {
  184 + try {
  185 + let reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)');
  186 + let r = url.split('?')[1].match(reg);
  187 + if (r != null) {
  188 + return r[2];
  189 + }
  190 + return ''; //如果此处只写return;则返回的是undefined
  191 + } catch (e) {
  192 + return ''; //如果此处只写return;则返回的是undefined
  193 + }
  194 + }
  195 + getPathUrl.value = window.location.href;
  196 + const name = 'name';
  197 + const getName = getParam(getPathUrl.value, name);
  198 + getPathUrlName.value = decodeURIComponent(getName);
  199 +
  200 + const setRowClassName = async () => {
  201 + if (getPathUrlName.value !== '') {
  202 + const { items } = await deviceConfigGetQuery({
  203 + page: 1,
  204 + pageSize: 10,
  205 + name: getPathUrlName.value,
  206 + });
  207 + nextTick(() => {
  208 + setTableData(items);
  209 + const { setFieldsValue, resetFields } = getForm();
  210 + setFieldsValue({
  211 + name: getPathUrlName.value,
  212 + });
  213 + if (onCloseVal.value == 1) {
  214 + resetFields();
  215 + }
  216 + });
  217 + } else {
  218 + setTimeout(() => {
  219 + reload();
  220 + }, 80);
  221 + }
  222 + };
  223 + setRowClassName();
  224 + onUnmounted(() => {
  225 + getPathUrlName.value = '';
  226 + onCloseVal.value = 1;
  227 + });
  228 + const tableListRef = ref<
  229 + {
  230 + title: string;
  231 + columns?: any[];
  232 + dataSource?: any[];
  233 + }[]
  234 + >([]);
  235 +
  236 + function loadDataSuccess(excelDataList: ExcelData[]) {
  237 + tableListRef.value = [];
  238 + console.log(excelDataList);
  239 + for (const excelData of excelDataList) {
  240 + const {
  241 + header,
  242 + results,
  243 + meta: { sheetName },
  244 + } = excelData;
  245 + const columns: BasicColumn[] = [];
  246 + for (const title of header) {
  247 + columns.push({ title, dataIndex: title });
  248 + }
  249 + tableListRef.value.push({ title: sheetName, dataSource: results, columns });
  250 + }
  251 + }
  252 + //新增
  253 + function handleCreate() {
  254 + openModal(true, {
  255 + isUpdate: false,
  256 + });
  257 + }
  258 + //编辑
  259 + function handleEdit(record: Recordable) {
  260 + openModal(true, {
  261 + record,
  262 + isUpdate: true,
  263 + });
  264 + }
  265 +
  266 + const [registerDrawer, { openDrawer }] = useDrawer();
  267 + //详情
  268 + function handleDetailView(record: Recordable) {
  269 + openDrawer(true, { record });
  270 + }
  271 +
  272 + function defaultHeader({ filename, bookType }: ExportModalResult) {
  273 + // 默认Object.keys(data[0])作为header
  274 + const data = exportData.value;
  275 + jsonToSheetXlsx({
  276 + data,
  277 + filename,
  278 + write2excelOpts: {
  279 + bookType,
  280 + },
  281 + });
  282 + }
  283 + //导出
  284 + const handleExport = (record: Recordable) => {
  285 + exportData.value = [];
  286 + exportData.value.push({
  287 + createTime: record.createTime,
  288 + description: record.description,
  289 + name: record.name,
  290 + });
  291 + nextTick(() => {
  292 + openModalExcel();
  293 + expExcelModalRef.value?.clearFieldFunc();
  294 + });
  295 + };
  296 + //导入
  297 + function handleImport() {
  298 + console.log('record');
  299 + }
  300 + function handleSuccess() {
  301 + reload();
  302 + }
  303 +
  304 + const handleSetDefault = async (record: Recordable) => {
  305 + let id = record.tbProfileId;
  306 + const data = await setDeviceProfileIsDefaultApi(id, 'default', defaultObj);
  307 + if (!data) return createMessage.error('设置该产品为默认失败');
  308 + createMessage.success('设置该产品为默认成功');
  309 + reload();
  310 + disabled.value = true;
  311 + };
  312 +
  313 + const handleModeChange = (value: EnumTableCardMode) => {
  314 + emit('changeMode', value);
  315 + };
  316 +</script>
  317 +
  318 +<style lang="css">
  319 + .devide-profiles .rowcolor {
  320 + color: red;
  321 + }
  322 +
  323 + .devide-profiles .rowcolor2 {
  324 + background: #a2c3e6;
  325 + }
  326 +</style>
... ...
... ... @@ -9,6 +9,26 @@ import { FormField, FunctionType } from './step/cpns/physical/cpns/config';
9 9 import { h } from 'vue';
10 10 import { Tag } from 'ant-design-vue';
11 11
  12 +export enum Mode {
  13 + CARD = 'card',
  14 + TABLE = 'table',
  15 +}
  16 +
  17 +export enum DeviceTypeName {
  18 + DIRECT_CONNECTION = '直连设备',
  19 + GATEWAY = '网关设备',
  20 + SENSOR = '网关子设备',
  21 +}
  22 +export enum ProductPermission {
  23 + DETAIL = 'api:yt:deviceProfile:get',
  24 + CREATE = 'api:yt:deviceProfile:post',
  25 + UPDATE = 'api:yt:deviceProfile:update',
  26 + DELETE = 'api:yt:deviceProfile:delete',
  27 + SET_DEFAULT = '',
  28 + IMPORT = 'api:yt:deviceProfile:import',
  29 + EXPORT = 'api:yt:deviceProfile:export',
  30 +}
  31 +
12 32 export enum ModelOfMatterPermission {
13 33 CREATE = 'api:yt:things_model:post',
14 34 UPDATE = 'api:yt:things_model:put',
... ...
1   -<template>
2   - <div>
3   - <BasicTable
4   - class="devide-profiles"
5   - @register="registerTable"
6   - :rowSelection="{ type: 'checkbox' }"
7   - :clickToRowSelect="false"
8   - >
9   - <template #toolbar>
10   - <Authority value="api:yt:deviceProfile:post">
11   - <a-button type="primary" @click="handleCreate"> 新增产品 </a-button>
12   - </Authority>
13   - <Authority value="api:yt:deviceProfile:import">
14   - <ImpExcel @success="loadDataSuccess" dateFormat="YYYY-MM-DD">
15   - <a-button @click="handleImport"> 导入产品 </a-button>
16   - </ImpExcel>
17   - </Authority>
18   - <Authority value="api:yt:deviceProfile:delete">
19   - <Popconfirm
20   - title="您确定要批量删除数据"
21   - ok-text="确定"
22   - cancel-text="取消"
23   - @confirm="handleDeleteOrBatchDelete(null)"
24   - >
25   - <a-button type="primary" color="error" :disabled="hasBatchDelete"> 批量删除 </a-button>
26   - </Popconfirm>
27   - </Authority>
28   - </template>
29   - <template #img="{ record }">
30   - <TableImg
31   - :size="30"
32   - :showBadge="false"
33   - :simpleShow="true"
34   - :imgList="
35   - typeof record.image !== 'undefined' && record.image !== '' && record.image != null
36   - ? [record.image]
37   - : null
38   - "
39   - />
40   - </template>
41   - <template #action="{ record }">
42   - <TableAction
43   - :actions="[
44   - {
45   - label: '详情',
46   - auth: 'api:yt:deviceProfile:get',
47   - icon: 'ant-design:eye-outlined',
48   - onClick: handleDetailView.bind(null, record),
49   - },
50   - {
51   - label: '编辑',
52   - auth: 'api:yt:deviceProfile:update',
53   - icon: 'clarity:note-edit-line',
54   - onClick: handleEdit.bind(null, record),
55   - ifShow: () => {
56   - return record.name !== 'default' ? true : false;
57   - },
58   - },
59   - ]"
60   - :drop-down-actions="[
61   - {
62   - label: '默认',
63   - icon: 'ant-design:profile-outlined',
64   - onClick: handleSetDefault.bind(null, record),
65   - ifShow: () => {
66   - return record.default === false;
67   - },
68   - },
69   - {
70   - label: '导出',
71   - auth: 'api:yt:deviceProfile:export',
72   - icon: 'ant-design:login-outlined',
73   - onClick: handleExport.bind(null, record),
74   - },
75   - {
76   - label: '删除',
77   - auth: 'api:yt:deviceProfile:delete',
78   - icon: 'ant-design:delete-outlined',
79   - color: 'error',
80   - popConfirm: {
81   - title: '是否确认删除',
82   - confirm: handleDeleteOrBatchDelete.bind(null, record),
83   - },
84   - ifShow: () => {
85   - return record.default === false && record.name !== 'default';
86   - },
87   - },
88   - ]"
89   - />
90   - </template>
91   - </BasicTable>
92   - <DeviceProfileModal @register="registerModal" @success="handleSuccess" />
93   - <DeviceProfileDrawer @register="registerDrawer" />
94   - <ExpExcelModal
95   - ref="expExcelModalRef"
96   - @register="registerExportModal"
97   - @success="defaultHeader"
98   - />
99   - </div>
100   -</template>
101 1 <script lang="ts" setup>
102   - import { ref, nextTick, onUnmounted } from 'vue';
103   - import { BasicTable, TableImg, useTable, TableAction, BasicColumn } from '/@/components/Table';
104   - import { columns, searchFormSchema, defaultObj } from './device.profile.data';
105   - import { useMessage } from '/@/hooks/web/useMessage';
106   - import {
107   - deviceConfigGetQuery,
108   - deviceConfigDelete,
109   - setDeviceProfileIsDefaultApi,
110   - } from '/@/api/device/deviceConfigApi';
111   - import { useModal } from '/@/components/Modal';
112   - import { useDrawer } from '/@/components/Drawer';
113   - import DeviceProfileModal from '/@/views/device/profiles/DeviceProfileModal.vue';
114   - import { ImpExcel, ExcelData } from '/@/components/Excel';
115   - import { jsonToSheetXlsx, ExpExcelModal, ExportModalResult } from '/@/components/Excel';
116   - import { Authority } from '/@/components/Authority';
117   - import { useBatchDelete } from '/@/hooks/web/useBatchDelete';
118   - import { Popconfirm } from 'ant-design-vue';
119   - import DeviceProfileDrawer from './DeviceProfileDrawer.vue';
120   -
121   - const exportData: any = ref([]);
122   - const expExcelModalRef: any = ref(null);
123   - const getPathUrl = ref('');
124   - const getPathUrlName = ref('');
125   - const disabled = ref(true);
126   - const onCloseVal = ref(0);
127   - const immediateStatus = ref(false);
128   - const { createMessage } = useMessage();
129   - const [registerModal, { openModal }] = useModal();
130   - const [registerExportModal, { openModal: openModalExcel }] = useModal();
131   - const [registerTable, { setProps, reload, setTableData, getForm }] = useTable({
132   - title: '产品列表',
133   - clickToRowSelect: false,
134   - api: deviceConfigGetQuery,
135   - immediate: immediateStatus.value,
136   - columns,
137   - formConfig: {
138   - labelWidth: 120,
139   - schemas: searchFormSchema,
140   - },
141   - rowKey: 'id',
142   - useSearchForm: true,
143   - showTableSetting: true,
144   - bordered: true,
145   - showIndexColumn: false,
146   - actionColumn: {
147   - width: 200,
148   - title: '操作',
149   - dataIndex: 'action',
150   - slots: { customRender: 'action' },
151   - fixed: 'right',
152   - },
153   - });
154   - const { hasBatchDelete, handleDeleteOrBatchDelete, selectionOptions } = useBatchDelete(
155   - deviceConfigDelete,
156   - handleSuccess,
157   - setProps
158   - );
159   - selectionOptions.rowSelection.getCheckboxProps = (record: Recordable) => {
160   - // Demo:status为1的选择框禁用
161   - if (record.default === true) {
162   - return { disabled: true };
163   - } else if (record.name == 'default') {
164   - return { disabled: true };
165   - } else {
166   - return { disabled: false };
167   - }
168   - };
169   - nextTick(() => {
170   - setProps(selectionOptions);
171   - });
172   - /**
173   - *@param url,name
174   - **/
175   - function getParam(url, name) {
176   - try {
177   - let reg = new RegExp('(^|&)' + name + '=([^&]*)(&|$)');
178   - let r = url.split('?')[1].match(reg);
179   - if (r != null) {
180   - return r[2];
181   - }
182   - return ''; //如果此处只写return;则返回的是undefined
183   - } catch (e) {
184   - return ''; //如果此处只写return;则返回的是undefined
185   - }
186   - }
187   - getPathUrl.value = window.location.href;
188   - const name = 'name';
189   - const getName = getParam(getPathUrl.value, name);
190   - getPathUrlName.value = decodeURIComponent(getName);
191   -
192   - const setRowClassName = async () => {
193   - if (getPathUrlName.value !== '') {
194   - const { items } = await deviceConfigGetQuery({
195   - page: 1,
196   - pageSize: 10,
197   - name: getPathUrlName.value,
198   - });
199   - nextTick(() => {
200   - setTableData(items);
201   - const { setFieldsValue, resetFields } = getForm();
202   - setFieldsValue({
203   - name: getPathUrlName.value,
204   - });
205   - if (onCloseVal.value == 1) {
206   - resetFields();
207   - }
208   - });
209   - } else {
210   - setTimeout(() => {
211   - reload();
212   - }, 80);
213   - }
214   - };
215   - setRowClassName();
216   - onUnmounted(() => {
217   - getPathUrlName.value = '';
218   - onCloseVal.value = 1;
219   - });
220   - const tableListRef = ref<
221   - {
222   - title: string;
223   - columns?: any[];
224   - dataSource?: any[];
225   - }[]
226   - >([]);
227   -
228   - function loadDataSuccess(excelDataList: ExcelData[]) {
229   - tableListRef.value = [];
230   - console.log(excelDataList);
231   - for (const excelData of excelDataList) {
232   - const {
233   - header,
234   - results,
235   - meta: { sheetName },
236   - } = excelData;
237   - const columns: BasicColumn[] = [];
238   - for (const title of header) {
239   - columns.push({ title, dataIndex: title });
240   - }
241   - tableListRef.value.push({ title: sheetName, dataSource: results, columns });
242   - }
243   - }
244   - //新增
245   - function handleCreate() {
246   - openModal(true, {
247   - isUpdate: false,
248   - });
249   - }
250   - //编辑
251   - function handleEdit(record: Recordable) {
252   - openModal(true, {
253   - record,
254   - isUpdate: true,
255   - });
256   - }
257   -
258   - const [registerDrawer, { openDrawer }] = useDrawer();
259   - //详情
260   - function handleDetailView(record: Recordable) {
261   - openDrawer(true, { record });
262   - }
263   -
264   - function defaultHeader({ filename, bookType }: ExportModalResult) {
265   - // 默认Object.keys(data[0])作为header
266   - const data = exportData.value;
267   - jsonToSheetXlsx({
268   - data,
269   - filename,
270   - write2excelOpts: {
271   - bookType,
272   - },
273   - });
274   - }
275   - //导出
276   - const handleExport = (record: Recordable) => {
277   - exportData.value = [];
278   - exportData.value.push({
279   - createTime: record.createTime,
280   - description: record.description,
281   - name: record.name,
282   - });
283   - nextTick(() => {
284   - openModalExcel();
285   - expExcelModalRef.value?.clearFieldFunc();
286   - });
287   - };
288   - //导入
289   - function handleImport() {
290   - console.log('record');
291   - }
292   - function handleSuccess() {
293   - reload();
294   - }
295   -
296   - const handleSetDefault = async (record: Recordable) => {
297   - let id = record.tbProfileId;
298   - const data = await setDeviceProfileIsDefaultApi(id, 'default', defaultObj);
299   - if (!data) return createMessage.error('设置该产品为默认失败');
300   - createMessage.success('设置该产品为默认成功');
301   - reload();
302   - disabled.value = true;
  2 + import { ref } from 'vue';
  3 + import TableMode from './TableMode.vue';
  4 + import CardMode from './CardMode.vue';
  5 + import { EnumTableCardMode } from '/@/components/Widget';
  6 + const mode = ref(EnumTableCardMode.CARD);
  7 +
  8 + const handleChangeMode = (flag: EnumTableCardMode) => {
  9 + mode.value = flag;
303 10 };
304 11 </script>
305 12
306   -<style lang="css">
307   - .devide-profiles .rowcolor {
308   - color: red;
309   - }
310   -
311   - .devide-profiles .rowcolor2 {
312   - background: #a2c3e6;
313   - }
314   -</style>
  13 +<template>
  14 + <section>
  15 + <CardMode v-if="mode === EnumTableCardMode.CARD" :mode="mode" @change-mode="handleChangeMode" />
  16 + <TableMode
  17 + v-if="mode === EnumTableCardMode.TABLE"
  18 + :mode="mode"
  19 + @change-mode="handleChangeMode"
  20 + />
  21 + </section>
  22 +</template>
... ...
... ... @@ -152,6 +152,17 @@
152 152 field: 'transportType',
153 153 componentProps: {
154 154 disabled: status,
  155 + options: [
  156 + { label: '默认', value: 'DEFAULT' },
  157 + { label: 'MQTT', value: 'MQTT' },
  158 + { label: 'CoAP', value: 'COAP' },
  159 + { label: 'LWM2M', value: 'LWM2M' },
  160 + { label: 'SNMP', value: 'SNMP' },
  161 + { label: 'TCP', value: 'TCP' },
  162 + ],
  163 + onChange(e) {
  164 + isMqttType.value = e;
  165 + },
155 166 },
156 167 });
157 168 };
... ...
1 1 <script lang="ts" setup>
2 2 import { computed, nextTick, Ref, ref, unref } from 'vue';
3 3 import { getDeviceHistoryInfo } from '/@/api/alarm/position';
4   - import { Empty, Tooltip, Button, Spin } from 'ant-design-vue';
  4 + import { Empty, Spin } from 'ant-design-vue';
5 5 import { useECharts } from '/@/hooks/web/useECharts';
6 6 import { AggregateDataEnum } from '/@/views/device/localtion/config.data';
7 7 import { useGridLayout } from '/@/hooks/component/useGridLayout';
... ... @@ -14,19 +14,18 @@
14 14 import { getAllDeviceByOrg } from '/@/api/dataBoard';
15 15 import { useHistoryData } from '/@/views/device/list/hook/useHistoryData';
16 16 import { BasicTable, useTable } from '/@/components/Table';
17   - import { LineChartOutlined, BarsOutlined } from '@ant-design/icons-vue';
18 17 import { formatToDateTime } from '/@/utils/dateUtil';
  18 + import {
  19 + ModeSwitchButton,
  20 + TABLE_CHART_MODE_LIST,
  21 + EnumTableChartMode,
  22 + } from '/@/components/Widget';
19 23
20 24 type DeviceOption = Record<'label' | 'value' | 'organizationId', string>;
21 25
22 26 defineEmits(['register']);
23 27
24   - enum Mode {
25   - TABLE = 'table',
26   - CHART = 'chart',
27   - }
28   -
29   - const mode = ref<Mode>(Mode.CHART);
  28 + const mode = ref<EnumTableChartMode>(EnumTableChartMode.CHART);
30 29
31 30 const chartRef = ref();
32 31
... ... @@ -245,7 +244,7 @@
245 244 destory();
246 245 };
247 246
248   - const switchMode = (flag: Mode) => {
  247 + const switchMode = (flag: EnumTableChartMode) => {
249 248 mode.value = flag;
250 249 };
251 250 </script>
... ... @@ -269,61 +268,38 @@
269 268 </section>
270 269 <section class="bg-white p-3" style="min-height: 350px">
271 270 <Spin :spinning="loading" :absolute="true">
272   - <div v-show="mode === Mode.CHART" class="flex h-70px items-center justify-end p-2">
273   - <Tooltip title="图表模式">
274   - <Button
275   - :class="[mode === Mode.CHART && '!bg-blue-500 svg:text-light-50']"
276   - class="!p-2 !children:flex flex justify-center items-center border-r-0"
277   - @click="switchMode(Mode.CHART)"
278   - >
279   - <LineChartOutlined />
280   - </Button>
281   - </Tooltip>
282   -
283   - <Tooltip title="列表模式">
284   - <Button
285   - class="!p-2 !children:flex flex justify-center items-center"
286   - @click="switchMode(Mode.TABLE)"
287   - >
288   - <BarsOutlined />
289   - </Button>
290   - </Tooltip>
  271 + <div
  272 + v-show="mode === EnumTableChartMode.CHART"
  273 + class="flex h-70px items-center justify-end p-2"
  274 + >
  275 + <ModeSwitchButton
  276 + v-model:value="mode"
  277 + :mode="TABLE_CHART_MODE_LIST"
  278 + @change="switchMode"
  279 + />
291 280 </div>
292 281
293 282 <div
294   - v-show="isNull && mode === Mode.CHART"
  283 + v-show="isNull && mode === EnumTableChartMode.CHART"
295 284 ref="chartRef"
296 285 :style="{ height: '350px', width: '100%' }"
297 286 >
298 287 </div>
299 288 <Empty
300   - v-if="mode === Mode.CHART"
  289 + v-if="mode === EnumTableChartMode.CHART"
301 290 class="h-350px flex flex-col justify-center items-center"
302 291 description="暂无数据,请选择设备查询"
303 292 v-show="!isNull"
304 293 />
305 294
306   - <BasicTable v-show="mode === Mode.TABLE" @register="registerTable">
  295 + <BasicTable v-show="mode === EnumTableChartMode.TABLE" @register="registerTable">
307 296 <template #toolbar>
308   - <div v-show="mode === Mode.TABLE" class="flex h-70px items-center justify-end p-2">
309   - <Tooltip title="图表模式">
310   - <Button
311   - class="!p-2 !children:flex flex justify-center items-center border-r-0"
312   - @click="switchMode(Mode.CHART)"
313   - >
314   - <LineChartOutlined />
315   - </Button>
316   - </Tooltip>
317   -
318   - <Tooltip title="列表模式">
319   - <Button
320   - :class="[mode === Mode.TABLE && '!bg-blue-500 svg:text-light-50']"
321   - class="!p-2 !children:flex flex justify-center items-center"
322   - @click="switchMode(Mode.TABLE)"
323   - >
324   - <BarsOutlined />
325   - </Button>
326   - </Tooltip>
  297 + <div class="flex h-70px items-center justify-end p-2">
  298 + <ModeSwitchButton
  299 + v-model:value="mode"
  300 + :mode="TABLE_CHART_MODE_LIST"
  301 + @change="switchMode"
  302 + />
327 303 </div>
328 304 </template>
329 305 </BasicTable>
... ...