Commit bf667931225e146acd012da994a239cb2a0a4b7f

Authored by ww
1 parent 9a4aa30b

perf: 优化产品页面使用组件

... ... @@ -43,120 +43,7 @@ export interface IDeviceConfigAddOrEditModel {
43 43 icon?: string;
44 44 id?: string;
45 45 name?: string;
46   - profileData?: {
47   - configuration: {};
48   - transportConfiguration: {};
49   - provisionConfiguration: {
50   - provisionDeviceSecret: string;
51   - };
52   - //报警类型字段
53   - alarms: [
54   - {
55   - id: 'highTemperatureAlarmID';
56   - alarmType: 'High Temperature Alarm';
57   - createRules: {
58   - additionalProp1: {
59   - condition: {
60   - condition: [
61   - {
62   - key: {
63   - type: 'TIME_SERIES';
64   - key: 'temp';
65   - };
66   - valueType: 'NUMERIC';
67   - value: {};
68   - predicate: {};
69   - }
70   - ];
71   - spec: {};
72   - };
73   - schedule: {
74   - type: 'ANY_TIME';
75   - };
76   - alarmDetails: string;
77   - dashboardId: {
78   - id: '784f394c-42b6-435a-983c-b7beff2784f9';
79   - entityType: 'DASHBOARD';
80   - };
81   - };
82   - additionalProp2: {
83   - condition: {
84   - condition: [
85   - {
86   - key: {
87   - type: 'TIME_SERIES';
88   - key: 'temp';
89   - };
90   - valueType: 'NUMERIC';
91   - value: {};
92   - predicate: {};
93   - }
94   - ];
95   - spec: {};
96   - };
97   - schedule: {
98   - type: 'ANY_TIME';
99   - };
100   - alarmDetails: string;
101   - dashboardId: {
102   - id: '784f394c-42b6-435a-983c-b7beff2784f9';
103   - entityType: 'DASHBOARD';
104   - };
105   - };
106   - additionalProp3: {
107   - condition: {
108   - condition: [
109   - {
110   - key: {
111   - type: 'TIME_SERIES';
112   - key: 'temp';
113   - };
114   - valueType: 'NUMERIC';
115   - value: {};
116   - predicate: {};
117   - }
118   - ];
119   - spec: {};
120   - };
121   - schedule: {
122   - type: 'ANY_TIME';
123   - };
124   - alarmDetails: string;
125   - dashboardId: {
126   - id: '784f394c-42b6-435a-983c-b7beff2784f9';
127   - entityType: 'DASHBOARD';
128   - };
129   - };
130   - };
131   - clearRule: {
132   - condition: {
133   - condition: [
134   - {
135   - key: {
136   - type: 'TIME_SERIES';
137   - key: 'temp';
138   - };
139   - valueType: 'NUMERIC';
140   - value: {};
141   - predicate: {};
142   - }
143   - ];
144   - spec: {};
145   - };
146   - schedule: {
147   - type: 'ANY_TIME';
148   - };
149   - alarmDetails: string;
150   - dashboardId: {
151   - id: '784f394c-42b6-435a-983c-b7beff2784f9';
152   - entityType: 'DASHBOARD';
153   - };
154   - };
155   - propagate: true;
156   - propagateRelationTypes: [string];
157   - }
158   - ];
159   - };
  46 + profileData?: ProfileData;
160 47 roleIds?: [string];
161 48 tbProfileId?: string;
162 49 tenantExpireTime?: '2021-12-15T02:17:26.645Z';
... ... @@ -219,7 +106,7 @@ export interface ProfileData {
219 106 configuration: Configuration;
220 107 transportConfiguration: TransportConfiguration;
221 108 provisionConfiguration: ProvisionConfiguration;
222   - alarms?: any;
  109 + alarms: any;
223 110 }
224 111
225 112 export interface ProfileRecord {
... ... @@ -297,8 +184,3 @@ export interface Configuration {
297 184 export interface TransportConfiguration {
298 185 type: string;
299 186 }
300   -
301   -export interface ProvisionConfiguration {
302   - type: string;
303   - provisionDeviceSecret: any;
304   -}
... ...
1 1 <script setup lang="ts">
2 2 import { BasicForm, FormActionType, useForm } from '/@/components/Form';
3   - import { List, ListProps, Button, Tooltip } from 'ant-design-vue';
  3 + import { List, ListProps, Button, Tooltip, DropdownButton, Menu } from 'ant-design-vue';
4 4 import { computed, Ref, ref, toRaw, unref, useSlots, watch } from 'vue';
5 5 import { useCardListScroll } from './hooks/useCardListScroll';
6 6 import { BasicCardListPropsType, CardListActionType } from './types';
... ... @@ -96,6 +96,7 @@
96 96 clearSelectedKeys,
97 97 getSelectedKeys,
98 98 getSelectedRecords,
  99 + getSelections,
99 100 } = useCardListSelected(getDataSourceRef, getProps, {
100 101 updateTableDataRecord,
101 102 findCardDataRecord,
... ... @@ -142,6 +143,7 @@
142 143 clearSelectedKeys,
143 144 getSelectedKeys,
144 145 getSelectedRecords,
  146 + getSelections,
145 147 };
146 148
147 149 function setProps(props: Partial<BasicCardListPropsType> = {}) {
... ... @@ -177,13 +179,21 @@
177 179 </span>
178 180 <slot v-else name="title"></slot>
179 181 </div>
180   - <div class="flex flex-auto justify-end mr-4">
  182 + <div class="flex flex-auto justify-end mr-4 gap-4">
181 183 <slot name="toolbar"></slot>
182 184 </div>
183 185 <div v-if="showCardListSetting" class="flex gap-4">
184   - <Button v-if="getProps.selections" @click="selectAllToggle" type="primary">
185   - {{ selectedKeys.length === getDataSourceRef.length ? '反选' : '全选' }}
186   - </Button>
  186 + <DropdownButton v-if="getProps.selections" @click="selectAllToggle" type="primary">
  187 + <template #overlay>
  188 + <Menu>
  189 + <Menu.Item @click="selectedAll">全选</Menu.Item>
  190 + <Menu.Item @click="clearSelectedKeys">反选</Menu.Item>
  191 + </Menu>
  192 + </template>
  193 + <span>
  194 + {{ selectedKeys.length === getDataSourceRef.length ? '反选' : '全选' }}
  195 + </span>
  196 + </DropdownButton>
187 197 <CardListLayout v-model:row="cardListLayout.row" v-model:col="cardListLayout.col" />
188 198 <Tooltip title="刷新">
189 199 <Button type="primary" @click="reload()" :loading="loading">
... ... @@ -193,7 +203,7 @@
193 203 </div>
194 204 </section>
195 205 </template>
196   - <template #renderItem="{ item }: CardListRenderItem">
  206 + <template #renderItem="{ item }: BasicCardListRenderItem<any>">
197 207 <List.Item
198 208 :class="selectedKeys.includes(item[getRowKey]) ? selectedClass : ''"
199 209 @click="handlerSelected($event, item)"
... ...
... ... @@ -8,7 +8,7 @@ import { PaginationProps } from 'ant-design-vue';
8 8 import { FetchParams } from './useCardListData';
9 9 import { tryOnUnmounted } from '@vueuse/core';
10 10
11   -type UseTableMethod = CardListActionType & {
  11 +type UseCardListMethod = CardListActionType & {
12 12 getForm: () => FormActionType;
13 13 };
14 14
... ... @@ -49,46 +49,49 @@ export function useCardList(
49 49 );
50 50 }
51 51
52   - function getTableInstance(): CardListActionType {
53   - const table = unref(cardListRef);
54   - if (!table) {
  52 + function getCardListInstance(): CardListActionType {
  53 + const cardList = unref(cardListRef);
  54 + if (!cardList) {
55 55 error(
56   - 'The CardList instance has not been obtained yet, please make sure the table is presented when performing the table operation!'
  56 + 'The CardList instance has not been obtained yet, please make sure the card list is presented when performing the card list operation!'
57 57 );
58 58 }
59   - return table as CardListActionType;
  59 + return cardList as CardListActionType;
60 60 }
61 61
62   - const cardListActionType: UseTableMethod = {
  62 + const cardListActionType: UseCardListMethod = {
63 63 getForm: () => {
64 64 return unref(formRef) as unknown as FormActionType;
65 65 },
66 66 setLoading: (loading: boolean) => {
67   - return getTableInstance().setLoading(loading);
  67 + return getCardListInstance().setLoading(loading);
68 68 },
69 69 setProps: (props: Partial<BasicCardListPropsType>) => {
70   - getTableInstance().setProps(props);
  70 + getCardListInstance().setProps(props);
71 71 },
72 72 getPagination: () => {
73   - return getTableInstance().getPagination();
  73 + return getCardListInstance().getPagination();
74 74 },
75 75 setPagination: (pagination: Partial<PaginationProps>) => {
76   - return getTableInstance().setPagination(pagination);
  76 + return getCardListInstance().setPagination(pagination);
77 77 },
78 78 reload: (opt?: FetchParams) => {
79   - return getTableInstance().reload(opt);
  79 + return getCardListInstance().reload(opt);
80 80 },
81 81 selectedAll: () => {
82   - return getTableInstance().selectedAll();
  82 + return getCardListInstance().selectedAll();
83 83 },
84 84 clearSelectedKeys: () => {
85   - return getTableInstance().clearSelectedKeys();
  85 + return getCardListInstance().clearSelectedKeys();
86 86 },
87 87 getSelectedKeys: () => {
88   - return getTableInstance().getSelectedKeys();
  88 + return getCardListInstance().getSelectedKeys();
89 89 },
90 90 getSelectedRecords: () => {
91   - return getTableInstance().getSelectedRecords();
  91 + return getCardListInstance().getSelectedRecords();
  92 + },
  93 + getSelections: () => {
  94 + return getCardListInstance().getSelections();
92 95 },
93 96 };
94 97
... ...
1 1 import { ComputedRef, computed, ref, toRaw, unref, watch } from 'vue';
2 2 import { BasicCardListPropsType, CardListSelectionsType, UseCardListDataType } from '../types';
3   -import { isBoolean } from '/@/utils/is';
  3 +import { isBoolean, isFunction } from '/@/utils/is';
4 4 import { cloneDeep } from 'lodash-es';
5 5
6 6 export const CHECKED_FIELD = 'CARD_LIST_CHECKED_STATUS';
... ... @@ -23,18 +23,27 @@ export function useCardListSelected(
23 23
24 24 const selectedClass = ref('');
25 25
  26 + const selectionRef = ref<CardListSelectionsType>({});
  27 +
  28 + const getSelectionsRef = computed<CardListSelectionsType & { enabled: boolean }>(() => {
  29 + const { selections } = unref(propsRef);
  30 + return {
  31 + enabled: !!selections,
  32 + ...unref(selectionRef),
  33 + ...(isBoolean(selections) ? {} : selections),
  34 + };
  35 + });
  36 +
26 37 const getHasSelectedRecordStatus = computed(() =>
27 38 unref(getDataSourceRef).find((item) => item.checked)
28 39 );
29 40
30 41 const getShouldUseDefaultStyle = computed(
31   - () =>
32   - isBoolean(unref(propsRef).selections) ||
33   - !(unref(propsRef).selections as CardListSelectionsType).customCheckedStyle
  42 + () => unref(getSelectionsRef).enabled && !unref(getSelectionsRef).customCheckedStyle
34 43 );
35 44
36 45 watch(
37   - () => unref(propsRef).selections,
  46 + getSelectionsRef,
38 47 () => {
39 48 selectedClass.value =
40 49 isBoolean(unref(propsRef).selections) ||
... ... @@ -48,24 +57,33 @@ export function useCardListSelected(
48 57 );
49 58
50 59 function handlerSelected(_event: MouseEvent, record: Recordable) {
51   - if (!unref(propsRef).selections) return;
  60 + if (!unref(getSelectionsRef).enabled) return;
  61 +
  62 + if (unref(getSelectionsRef).beforeSelectValidate) {
  63 + if (!unref(getSelectionsRef).beforeSelectValidate?.(record)) return;
  64 + }
  65 +
52 66 const rowKey = record[unref(getRowKey)];
53 67 const index = unref(selectedKeys).findIndex((key) => key === rowKey);
54 68 ~index ? selectedKeys.value.splice(index, 1) : unref(selectedKeys).push(rowKey);
55 69
56   - if (isBoolean(unref(propsRef).selections)) return;
  70 + if (!unref(getSelectionsRef).onSelect) return;
57 71
58   - (unref(propsRef).selections as CardListSelectionsType)?.onSelect?.(record, !!~index);
  72 + unref(getSelectionsRef)?.onSelect?.(record, !!~index);
59 73 }
60 74
61 75 function selectedAll() {
62   - selectedKeys.value = unref(getDataSourceRef).map((item) => item[unref(getRowKey)]);
  76 + selectedKeys.value = unref(getDataSourceRef)
  77 + .filter(
  78 + (item) =>
  79 + isFunction(unref(getSelectionsRef).beforeSelectValidate) &&
  80 + unref(getSelectionsRef).beforeSelectValidate?.(item)
  81 + )
  82 + .map((item) => item[unref(getRowKey)]);
63 83
64   - if (isBoolean(unref(propsRef).selections)) return;
  84 + if (!unref(getSelectionsRef).onSelect) return;
65 85
66   - (unref(propsRef).selections as CardListSelectionsType)?.onSelectAll?.(
67   - toRaw(unref(getDataSourceRef))
68   - );
  86 + unref(getSelectionsRef)?.onSelectAll?.(toRaw(unref(getDataSourceRef)));
69 87 }
70 88
71 89 function clearSelectedKeys() {
... ... @@ -87,11 +105,16 @@ export function useCardListSelected(
87 105 : selectedAll();
88 106 }
89 107
  108 + function getSelections() {
  109 + return unref(getSelectionsRef);
  110 + }
  111 +
90 112 return {
91 113 selectedKeys,
92 114 selectedClass,
93 115 getShouldUseDefaultStyle,
94 116 getHasSelectedRecordStatus,
  117 + getSelections,
95 118 handlerSelected,
96 119 selectedAll,
97 120 selectAllToggle,
... ...
... ... @@ -52,6 +52,7 @@ export interface CardListActionType {
52 52 clearSelectedKeys: UseCardListSelected['clearSelectedKeys'];
53 53 getSelectedKeys: UseCardListSelected['getSelectedKeys'];
54 54 getSelectedRecords: UseCardListSelected['getSelectedRecords'];
  55 + getSelections: UseCardListSelected['getSelections'];
55 56 }
56 57
57 58 export interface CardListEmitType {
... ... @@ -61,6 +62,7 @@ export interface CardListEmitType {
61 62
62 63 export interface CardListSelectionsType<T = Recordable> {
63 64 customCheckedStyle?: boolean;
  65 + beforeSelectValidate?: (record: Recordable) => boolean;
64 66 onSelect?: (record: T, selected: boolean) => any;
65   - onSelectAll: (selectedRecords: T[]) => any;
  67 + onSelectAll?: (selectedRecords: T[]) => any;
66 68 }
... ...
... ... @@ -134,7 +134,7 @@
134 134 </Authority>
135 135 </div>
136 136 </template>
137   - <template #renderItem="{ item }: CardListRenderItem<ConfigurationCenterItemsModal>">
  137 + <template #renderItem="{ item }: BasicCardListRenderItem<ConfigurationCenterItemsModal>">
138 138 <Card
139 139 :style="{
140 140 '--viewType': item.viewType === ViewTypeEnum.PUBLIC_VIEW ? '#faad14' : '#1890ff',
... ...
... ... @@ -105,7 +105,7 @@
105 105 </Authority>
106 106 </div>
107 107 </template>
108   - <template #renderItem="{ item }: CardListRenderItem<ConfigurationCenterItemsModal>">
  108 + <template #renderItem="{ item }: BasicCardListRenderItem<ConfigurationCenterItemsModal>">
109 109 <Card
110 110 :style="{
111 111 '--viewType': '#1890ff',
... ...
... ... @@ -150,7 +150,7 @@
150 150 </Authority>
151 151 </section>
152 152 </template>
153   - <template #renderItem="{ item }: CardListRenderItem<BigScreenCenterItemsModel>">
  153 + <template #renderItem="{ item }: BasicCardListRenderItem<BigScreenCenterItemsModel>">
154 154 <Card
155 155 :style="{
156 156 '--viewType': item.viewType === ViewType.PUBLIC_VIEW ? '#1890ff' : '#faad14',
... ...
1 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, Popconfirm } from 'ant-design-vue';
5   - import { ReloadOutlined } from '@ant-design/icons-vue';
6   - import { computed, onMounted, reactive, ref, unref } from 'vue';
7   - import {
8   - AuthIcon,
9   - CardLayoutButton,
10   - EnumTableCardMode,
11   - ModeSwitchButton,
12   - } from '/@/components/Widget';
  2 + import { Button, Tooltip, Card, Image, Popconfirm } from 'ant-design-vue';
  3 + import { AuthIcon, EnumTableCardMode, ModeSwitchButton } from '/@/components/Widget';
13 4 import { Authority } from '/@/components/Authority';
14 5 import {
15 6 deviceConfigDelete,
... ... @@ -29,8 +20,9 @@
29 20 import { useModal } from '/@/components/Modal';
30 21 import { useDrawer } from '/@/components/Drawer';
31 22 import productDefault from '/@/assets/icons/product-default.svg';
32   - import { useRoute } from 'vue-router';
33 23 import AuthDropDown from '/@/components/Widget/AuthDropDown.vue';
  24 + import { BasicCardList, useCardList } from '/@/components/CardList';
  25 + import { useRoute } from 'vue-router';
34 26
35 27 defineProps<{
36 28 mode: EnumTableCardMode;
... ... @@ -42,94 +34,39 @@
42 34 SET_DEFAULT = 'setDefault',
43 35 DELETE = 'delete',
44 36 }
  37 +
45 38 const IMAGE_FALLBACK = productDefault;
46 39
47 40 const { createMessage } = useMessage();
48 41
49   - const [register, { getFieldsValue, setFieldsValue }] = useForm({
50   - showAdvancedButton: true,
51   - labelWidth: 100,
52   - compact: true,
53   - baseColProps: { span: 8 },
54   - schemas: searchFormSchema,
55   - resetFunc: async () => {
56   - getDataSource({ name: '' });
  42 + const { query } = useRoute();
  43 +
  44 + const [registerCardList, { reload, getSelectedKeys, clearSelectedKeys }] = useCardList({
  45 + api: deviceConfigGetQuery,
  46 + useSearchForm: true,
  47 + gutter: 4,
  48 + rowKey: 'id',
  49 + formConfig: {
  50 + schemas: searchFormSchema,
  51 + labelWidth: 100,
  52 + model: {
  53 + name: (query as Recordable)?.name ? decodeURIComponent((query as Recordable)?.name) : null,
  54 + },
57 55 },
58   - submitFunc: async () => {
59   - getDataSource({ pageSize: pagination.pageSize, page: 1 });
  56 + selections: {
  57 + beforeSelectValidate: (record: ProfileRecord) => {
  58 + return !record.default;
  59 + },
60 60 },
61 61 });
62 62
63 63 const [registerModal, { openModal }] = useModal();
64 64 const [registerDrawer, { openDrawer }] = useDrawer();
65 65
66   - const loading = ref(false);
67   -
68   - const colNumber = ref(5);
69   -
70   - const pagination = reactive<PaginationProps>({
71   - size: 'small',
72   - showTotal: (total: number) => `共 ${total} 条数据`,
73   - current: 1,
74   - pageSize: unref(colNumber) * 2,
75   - onChange: (page: number) => {
76   - pagination.current = page;
77   - getDataSource();
78   - },
79   - });
80   -
81   - const dataSource = ref<ProfileRecord[]>([]);
82   -
83   - const getSelectAllFlag = computed(() => {
84   - return unref(dataSource).every((item) => item.checked || item.default);
85   - });
86   -
87   - const getCheckedRecord = computed(() => {
88   - return unref(dataSource)
89   - .filter((item) => item.checked && !item.default)
90   - .map((item) => item.id);
91   - });
92   -
93   - const getDataSource = async (otherParams: Recordable = {}) => {
94   - try {
95   - loading.value = true;
96   - if (otherParams) {
97   - setFieldsValue(otherParams);
98   - }
99   - const params = getFieldsValue();
100   - const pageSize = unref(colNumber) * 2;
101   - const { items, total } = await deviceConfigGetQuery({
102   - page: pagination.current,
103   - pageSize: unref(colNumber) * 2,
104   - ...params,
105   - ...otherParams,
106   - });
107   - Object.assign(pagination, { total, pageSize });
108   - dataSource.value = items.map((item) => ({ ...item, checked: false }));
109   - } catch (error) {
110   - } finally {
111   - loading.value = false;
112   - }
113   - };
114   -
115 66 const handleModeChange = (mode: EnumTableCardMode) => {
116 67 emit('changeMode', mode);
117 68 };
118 69
119   - const handleCheckCard = (item: ProfileRecord) => {
120   - if (item.default || item.name == 'default') return;
121   - item.checked = !item.checked;
122   - };
123   -
124   - const handleSelectAll = () => {
125   - dataSource.value = unref(dataSource).map((item) => {
126   - return {
127   - ...item,
128   - checked: !item.default ? !unref(getSelectAllFlag) : false,
129   - };
130   - });
131   - };
132   -
133 70 const handleCreate = () => {
134 71 openModal(true, {
135 72 isUpdate: false,
... ... @@ -147,11 +84,13 @@
147 84 });
148 85 };
149 86
150   - const handleDelete = async (id: string[]) => {
  87 + const handleDelete = async () => {
151 88 try {
152   - await deviceConfigDelete(id);
  89 + const ids = getSelectedKeys();
  90 + await deviceConfigDelete(ids);
153 91 createMessage.success('删除成功');
154   - await getDataSource();
  92 + clearSelectedKeys();
  93 + await reload();
155 94 } catch (error) {
156 95 throw error;
157 96 }
... ... @@ -163,155 +102,108 @@
163 102 const data = await setDeviceProfileIsDefaultApi(tbProfileId, 'default', defaultObj);
164 103 if (!data) return createMessage.error('设置该产品为默认失败');
165 104 createMessage.success('设置该产品为默认成功');
166   - await getDataSource();
  105 + await reload();
167 106 } catch (error) {
168 107 throw error;
169 108 }
170 109 };
171   -
172   - const handleCardLayoutChange = () => {
173   - pagination.current = 1;
174   - getDataSource();
175   - };
176   -
177   - const { query: routeParams } = useRoute();
178   - onMounted(() => {
179   - routeParams.name = decodeURIComponent(
180   - ((routeParams as unknown as Recordable) || {})?.name || ''
181   - );
182   - getDataSource(routeParams);
183   - });
184 110 </script>
185 111
186 112 <template>
187   - <PageWrapper dense contentFullHeight contentClass="flex">
188   - <section class="flex-auto p-4 w-full profile-list">
189   - <div class="flex-auto w-full bg-light-50 dark:bg-dark-900 p-4">
190   - <BasicForm @register="register" />
191   - </div>
192   - <List
193   - ref="listEl"
194   - :loading="loading"
195   - class="flex-auto bg-light-50 dark:bg-dark-900 !p-2 !mt-4"
196   - position="bottom"
197   - :pagination="pagination"
198   - :data-source="dataSource"
199   - :grid="{ gutter: 4, column: colNumber }"
200   - >
201   - <template #header>
202   - <div class="flex gap-3 justify-end">
203   - <Authority :value="ProductPermission.CREATE">
204   - <Button type="primary" @click="handleCreate">新增产品</Button>
205   - </Authority>
206   -
207   - <Button type="primary" @click="handleSelectAll">
208   - {{ getSelectAllFlag ? '反选' : '全选' }}
209   - </Button>
210   - <Authority :value="ProductPermission.DELETE">
211   - <Popconfirm
212   - title="您确定要批量删除数据"
213   - ok-text="确定"
214   - cancel-text="取消"
215   - @confirm="handleDelete(getCheckedRecord)"
216   - :disabled="!getCheckedRecord.length"
217   - >
218   - <Button type="primary" danger :disabled="!getCheckedRecord.length">
219   - 批量删除
220   - </Button>
221   - </Popconfirm>
222   - </Authority>
223   -
224   - <ModeSwitchButton :value="$props.mode" @change="handleModeChange" />
225   - <CardLayoutButton v-model:value="colNumber" @change="handleCardLayoutChange" />
226   -
227   - <Tooltip title="刷新">
228   - <Button type="primary" @click="getDataSource">
229   - <ReloadOutlined :spin="loading" />
230   - </Button>
  113 + <section>
  114 + <BasicCardList @register="registerCardList">
  115 + <template #toolbar>
  116 + <Authority :value="ProductPermission.CREATE">
  117 + <Button type="primary" @click="handleCreate">新增产品</Button>
  118 + </Authority>
  119 +
  120 + <ModeSwitchButton :value="$props.mode" @change="handleModeChange" />
  121 +
  122 + <Authority :value="ProductPermission.DELETE">
  123 + <Popconfirm
  124 + title="您确定要批量删除数据"
  125 + ok-text="确定"
  126 + cancel-text="取消"
  127 + @confirm="handleDelete"
  128 + >
  129 + <Button type="primary" danger> 批量删除 </Button>
  130 + </Popconfirm>
  131 + </Authority>
  132 + </template>
  133 + <template #renderItem="{ item }: BasicCardListRenderItem<ProfileRecord>">
  134 + <Card hoverable>
  135 + <template #cover>
  136 + <div class="h-full w-full !flex justify-center items-center text-center p-1">
  137 + <Image
  138 + @click.stop
  139 + class="!w-32"
  140 + :src="item.image || IMAGE_FALLBACK"
  141 + placeholder
  142 + :fallback="IMAGE_FALLBACK"
  143 + />
  144 + </div>
  145 + </template>
  146 + <template class="ant-card-actions" #actions>
  147 + <Tooltip title="详情">
  148 + <AuthIcon
  149 + :auth="ProductPermission.DETAIL"
  150 + class="!text-lg"
  151 + icon="ant-design:eye-outlined"
  152 + @click.stop="handleShowDetail(item)"
  153 + />
  154 + </Tooltip>
  155 + <Tooltip title="编辑">
  156 + <AuthIcon
  157 + :auth="ProductPermission.UPDATE"
  158 + class="!text-lg"
  159 + icon="ant-design:form-outlined"
  160 + @click.stop="handleUpdate(item)"
  161 + :disabled="item.name == 'default'"
  162 + />
231 163 </Tooltip>
232   - </div>
233   - </template>
234   - <template #renderItem="{ item }">
235   - <List.Item>
236   - <Card
237   - hoverable
238   - @click="handleCheckCard(item)"
239   - :class="item.checked ? '!border-blue-500 !border-2' : ''"
240   - >
241   - <template #cover>
242   - <div class="h-full w-full !flex justify-center items-center text-center p-1">
243   - <Image
244   - @click.stop
245   - :height="144"
246   - :src="item.image || IMAGE_FALLBACK"
247   - placeholder
248   - :fallback="IMAGE_FALLBACK"
249   - />
250   - </div>
251   - </template>
252   - <template class="ant-card-actions" #actions>
253   - <Tooltip title="详情">
254   - <AuthIcon
255   - :auth="ProductPermission.DETAIL"
256   - class="!text-lg"
257   - icon="ant-design:eye-outlined"
258   - @click.stop="handleShowDetail(item)"
259   - />
260   - </Tooltip>
261   - <Tooltip title="编辑">
262   - <AuthIcon
263   - :auth="ProductPermission.UPDATE"
264   - class="!text-lg"
265   - icon="ant-design:form-outlined"
266   - @click.stop="handleUpdate(item)"
267   - :disabled="item.name == 'default'"
268   - />
269   - </Tooltip>
270   - <AuthDropDown
271   - @click.stop
272   - :trigger="['hover']"
273   - :drop-menu-list="[
274   - {
275   - text: '默认',
276   - event: DropMenuEvent.SET_DEFAULT,
277   - icon: 'ant-design:unordered-list-outlined',
278   - onClick: handleSetDefault.bind(null, item),
279   - disabled: item.default,
280   - },
281   - {
282   - text: '删除',
283   - event: DropMenuEvent.DELETE,
284   - auth: ProductPermission.DELETE,
285   - icon: 'ant-design:delete-outlined',
286   - popconfirm: {
287   - title: '是否确认删除操作?',
288   - onConfirm: handleDelete.bind(null, [item.id]),
289   - disabled: item.default,
290   - },
291   - disabled: item.default || item.name == 'default',
292   - },
293   - ]"
294   - />
295   - </template>
296   - <Card.Meta>
297   - <template #title>
298   - <span class="truncate"> {{ item.name }} </span>
299   - </template>
300   - <template #description>
301   - <div class="truncate h-11">
302   - <div class="truncate">{{ DeviceTypeName[item.deviceType] }} </div>
303   - <div class="truncate">{{ item.transportType }} </div>
304   - </div>
305   - </template>
306   - </Card.Meta>
307   - </Card>
308   - </List.Item>
309   - </template>
310   - </List>
311   - </section>
312   - <DeviceProfileModal @register="registerModal" @success="getDataSource" />
  164 + <AuthDropDown
  165 + @click.stop
  166 + :trigger="['hover']"
  167 + :drop-menu-list="[
  168 + {
  169 + text: '默认',
  170 + event: DropMenuEvent.SET_DEFAULT,
  171 + icon: 'ant-design:unordered-list-outlined',
  172 + onClick: handleSetDefault.bind(null, item),
  173 + disabled: item.default,
  174 + },
  175 + {
  176 + text: '删除',
  177 + event: DropMenuEvent.DELETE,
  178 + auth: ProductPermission.DELETE,
  179 + icon: 'ant-design:delete-outlined',
  180 + popconfirm: {
  181 + title: '是否确认删除操作?',
  182 + onConfirm: handleDelete.bind(null, [item.id]),
  183 + disabled: item.default,
  184 + },
  185 + disabled: item.default || item.name == 'default',
  186 + },
  187 + ]"
  188 + />
  189 + </template>
  190 + <Card.Meta>
  191 + <template #title>
  192 + <span class="truncate"> {{ item.name }} </span>
  193 + </template>
  194 + <template #description>
  195 + <div class="truncate h-11">
  196 + <div class="truncate">{{ DeviceTypeName[item.deviceType] }} </div>
  197 + <div class="truncate">{{ item.transportType }} </div>
  198 + </div>
  199 + </template>
  200 + </Card.Meta>
  201 + </Card>
  202 + </template>
  203 + </BasicCardList>
  204 + <DeviceProfileModal @register="registerModal" @success="reload" />
313 205 <DeviceProfileDrawer @register="registerDrawer" />
314   - </PageWrapper>
  206 + </section>
315 207 </template>
316 208
317 209 <style lang="less" scoped>
... ... @@ -322,8 +214,4 @@
322 214 .profile-list:deep(.ant-card-body) {
323 215 @apply !p-4;
324 216 }
325   -
326   - :deep(.ant-spin-nested-loading) {
327   - height: 40rem;
328   - }
329 217 </style>
... ...
... ... @@ -154,7 +154,7 @@
154 154 <Button type="primary" @click="handleOpenDetailModal">新增看板</Button>
155 155 </Authority>
156 156 </template>
157   - <template #renderItem="{ item }: CardListRenderItem<DataBoardRecord>">
  157 + <template #renderItem="{ item }: BasicCardListRenderItem<DataBoardRecord>">
158 158 <Card class="data-card cursor-pointer">
159 159 <template #title>
160 160 <div class="font-bold">{{ item.name }}</div>
... ...
... ... @@ -20,7 +20,7 @@ export interface DefineComponentsBasicExpose<T = Recordable> {
20 20 }
21 21
22 22 declare global {
23   - interface CardListRenderItem<T = Recordable & { checked?: boolean }> {
  23 + interface BasicCardListRenderItem<T = Recordable> {
24 24 item: T;
25 25 totalHeight: number;
26 26 }
... ...