Commit 0fcc123d9a6e4bed76c3383fc6baf56d2007d813
Merge branch 'feat/add-card-list-component' into 'main_dev'
feat: 新增卡片列表组件 See merge request yunteng/thingskit-front!1040
Showing
31 changed files
with
1595 additions
and
1454 deletions
1 | 1 | import { BasicPageParams } from '/@/api/model/baseModel'; |
2 | +import { ViewType } from '/@/views/visual/board/config/panelDetail'; | |
2 | 3 | |
3 | 4 | export interface BigScreenCenterItemsModel { |
4 | 5 | id: string; |
6 | + thumbnail?: string; | |
5 | 7 | name: string; |
6 | 8 | createTime: string; |
7 | 9 | creator: string; |
... | ... | @@ -9,6 +11,8 @@ export interface BigScreenCenterItemsModel { |
9 | 11 | state: number; |
10 | 12 | publicId: string; |
11 | 13 | organizationId?: string; |
14 | + viewType?: ViewType; | |
15 | + organizationDTO: { name: string }; | |
12 | 16 | } |
13 | 17 | export type queryPageParams = BasicPageParams & { |
14 | 18 | name?: Nullable<string>; | ... | ... |
... | ... | @@ -2,15 +2,46 @@ import { BasicPageParams } from '/@/api/model/baseModel'; |
2 | 2 | |
3 | 3 | export interface ConfigurationCenterItemsModal { |
4 | 4 | id: string; |
5 | - name: string; | |
6 | - createTime: string; | |
7 | 5 | creator: string; |
8 | - remark: string; | |
9 | - publicId?: string; | |
10 | - organizationId?: string; | |
11 | - platform?: string; | |
12 | - productIds?: string; | |
6 | + createTime: string; | |
7 | + updater: string; | |
8 | + updateTime: string; | |
9 | + name: string; | |
10 | + enabled: boolean; | |
11 | + tenantId: string; | |
12 | + publicId: string; | |
13 | + viewType: string; | |
14 | + accessCredentials: string; | |
15 | + organizationId: string; | |
16 | + platform: string; | |
17 | + thumbnail: string; | |
18 | + organizationDTO: OrganizationDto; | |
19 | + templateId: string; | |
20 | + productAndDevice: ProductAndDevice[]; | |
21 | + remark?: string; | |
22 | +} | |
23 | + | |
24 | +export interface OrganizationDto { | |
25 | + name: string; | |
26 | + enabled: boolean; | |
27 | + sort: number; | |
28 | + children: any[]; | |
13 | 29 | } |
30 | + | |
31 | +export interface ProductAndDevice { | |
32 | + profileId: string; | |
33 | + name: string; | |
34 | + transportType: string; | |
35 | + deviceType: string; | |
36 | + deviceList: DeviceList[]; | |
37 | +} | |
38 | + | |
39 | +export interface DeviceList { | |
40 | + deviceId: string; | |
41 | + name: string; | |
42 | + codeType: any; | |
43 | +} | |
44 | + | |
14 | 45 | export type queryPageParams = BasicPageParams & { |
15 | 46 | name?: Nullable<string>; |
16 | 47 | organizationId?: Nullable<number>; | ... | ... |
... | ... | @@ -15,6 +15,8 @@ export interface UpdateDataBoardParams extends AddDataBoardParams { |
15 | 15 | export interface GetDataBoardParams { |
16 | 16 | page?: number; |
17 | 17 | pageSize?: number; |
18 | + name?: string; | |
19 | + organizationId?: string; | |
18 | 20 | orderFiled?: string; |
19 | 21 | orderType?: string; |
20 | 22 | } |
... | ... | @@ -47,7 +49,9 @@ export interface DataBoardRecord { |
47 | 49 | layout: Layout[]; |
48 | 50 | defaultConfig: string; |
49 | 51 | tenantStatus: string; |
52 | + componentNum?: number; | |
50 | 53 | publicId: string; |
54 | + organizationId?: string; | |
51 | 55 | accessCredentials?: string; |
52 | 56 | } |
53 | 57 | ... | ... |
src/components/CardList/index.ts
0 → 100644
1 | +<script setup lang="ts"> | |
2 | + import { BasicForm, FormActionType, useForm } from '/@/components/Form'; | |
3 | + import { List, ListProps, Button, Tooltip } from 'ant-design-vue'; | |
4 | + import { computed, Ref, ref, toRaw, unref, useSlots, watch } from 'vue'; | |
5 | + import { useCardListScroll } from './hooks/useCardListScroll'; | |
6 | + import { BasicCardListPropsType, CardListActionType } from './types'; | |
7 | + import CardListLayout from './components/CardListLayout.vue'; | |
8 | + import { usePagination } from './hooks/usePagination'; | |
9 | + import { useLoading } from './hooks/useLoading'; | |
10 | + import { useCardListData } from './hooks/useCardListData'; | |
11 | + import { useListGrid } from './hooks/useListGrid'; | |
12 | + import { FETCH_SETTING } from '../../Table/src/const'; | |
13 | + import { useCardForm } from './hooks/useCardForm'; | |
14 | + import { Icon } from '/@/components/Icon'; | |
15 | + | |
16 | + const emit = defineEmits<{ | |
17 | + (eventName: 'fetchSuccess', result: { items: Recordable[]; total: number }): void; | |
18 | + (eventName: 'fetchError', error: Error): void; | |
19 | + ( | |
20 | + eventName: 'register', | |
21 | + cardListActionType: CardListActionType, | |
22 | + formActions: FormActionType | |
23 | + ): void; | |
24 | + }>(); | |
25 | + | |
26 | + const slots = useSlots(); | |
27 | + | |
28 | + const props = withDefaults(defineProps<BasicCardListPropsType>(), { | |
29 | + showCardListHeader: true, | |
30 | + showCardListSetting: true, | |
31 | + useSearchForm: false, | |
32 | + gutter: 16, | |
33 | + immediate: true, | |
34 | + fetchSetting: () => FETCH_SETTING, | |
35 | + autoCreateKey: true, | |
36 | + }); | |
37 | + | |
38 | + const tableData = ref<Recordable[]>([]); | |
39 | + | |
40 | + const listElRef = ref<Nullable<ComponentElRef>>(null); | |
41 | + | |
42 | + const innerPropsRef = ref<BasicCardListPropsType>(); | |
43 | + | |
44 | + const [registerForm, formActions] = useForm(); | |
45 | + | |
46 | + const getProps = computed<BasicCardListPropsType>(() => { | |
47 | + return { ...props, ...(unref(innerPropsRef) || {}) } as BasicCardListPropsType; | |
48 | + }); | |
49 | + | |
50 | + const { getListGrid, cardListLayout } = useListGrid(getProps); | |
51 | + | |
52 | + const { getPaginationInfo, getPagination, setPagination } = usePagination( | |
53 | + getProps, | |
54 | + cardListLayout | |
55 | + ); | |
56 | + | |
57 | + const { loading, setLoading, getLoading } = useLoading(); | |
58 | + | |
59 | + const { getDataSourceRef, getRowKey, fetch, reload } = useCardListData(getProps, formActions, { | |
60 | + setLoading, | |
61 | + getPaginationInfo, | |
62 | + emit, | |
63 | + tableData, | |
64 | + setPagination, | |
65 | + }); | |
66 | + | |
67 | + const { redoHeight, containerHeight } = useCardListScroll( | |
68 | + listElRef as Ref<Nullable<ComponentElRef>>, | |
69 | + getProps, | |
70 | + getDataSourceRef | |
71 | + ); | |
72 | + | |
73 | + const { replaceFormSlotKey, getFormProps, getFormSlotKeys } = useCardForm( | |
74 | + getProps, | |
75 | + slots, | |
76 | + fetch, | |
77 | + getLoading | |
78 | + ); | |
79 | + | |
80 | + function handleCardListChange() { | |
81 | + reload(); | |
82 | + } | |
83 | + | |
84 | + const getBindData = computed<ListProps>(() => { | |
85 | + const dataSource = unref(getDataSourceRef); | |
86 | + | |
87 | + return { | |
88 | + dataSource, | |
89 | + grid: unref(getListGrid), | |
90 | + loading: unref(loading), | |
91 | + pagination: { | |
92 | + ...toRaw(unref(getPaginationInfo)), | |
93 | + onChange: (current: number, pageSize: number) => { | |
94 | + setPagination({ current, pageSize }); | |
95 | + handleCardListChange(); | |
96 | + }, | |
97 | + }, | |
98 | + rowKey: unref(getRowKey), | |
99 | + } as ListProps; | |
100 | + }); | |
101 | + | |
102 | + watch(cardListLayout, () => { | |
103 | + const pageSize = cardListLayout.row * cardListLayout.col; | |
104 | + setPagination({ pageSize }); | |
105 | + handleCardListChange(); | |
106 | + }); | |
107 | + | |
108 | + const CartListActionType: CardListActionType = { | |
109 | + setProps, | |
110 | + setLoading, | |
111 | + setPagination, | |
112 | + getPagination, | |
113 | + reload, | |
114 | + }; | |
115 | + | |
116 | + function setProps(props: Partial<BasicCardListPropsType> = {}) { | |
117 | + innerPropsRef.value = { ...(unref(innerPropsRef) || {}), ...props } as BasicCardListPropsType; | |
118 | + } | |
119 | + | |
120 | + emit('register', CartListActionType, formActions); | |
121 | +</script> | |
122 | + | |
123 | +<template> | |
124 | + <main class="basic-card-list p-4 flex flex-col w-full h-full"> | |
125 | + <section v-if="getProps.useSearchForm" class="w-full bg-light-50 dark:bg-dark-900 p-4 mb-4"> | |
126 | + <BasicForm | |
127 | + @register="registerForm" | |
128 | + v-bind="getFormProps" | |
129 | + @advanced-change="redoHeight" | |
130 | + @reset="reload()" | |
131 | + @submit="reload()" | |
132 | + > | |
133 | + <template #[replaceFormSlotKey(item)]="data" v-for="item in getFormSlotKeys"> | |
134 | + <slot :name="item" v-bind="data"></slot> | |
135 | + </template> | |
136 | + </BasicForm> | |
137 | + </section> | |
138 | + | |
139 | + <section class="w-full bg-light-50 dark:bg-dark-900 p-4 flex-auto h-full"> | |
140 | + <List ref="listElRef" v-bind="getBindData"> | |
141 | + <template #header> | |
142 | + <section class="flex justify-between items-center"> | |
143 | + <div> | |
144 | + <span v-if="getProps.title" class="font-bold text-base px-4"> | |
145 | + {{ getProps.title }} | |
146 | + </span> | |
147 | + <slot v-else name="title"></slot> | |
148 | + </div> | |
149 | + <div class="flex flex-auto justify-end mr-4"> | |
150 | + <slot name="toolbar"></slot> | |
151 | + </div> | |
152 | + <div v-if="showCardListSetting" class="flex gap-4"> | |
153 | + <CardListLayout v-model:row="cardListLayout.row" v-model:col="cardListLayout.col" /> | |
154 | + <Tooltip title="刷新"> | |
155 | + <Button type="primary" @click="reload()" :loading="loading"> | |
156 | + <Icon icon="ant-design:reload-outlined" :size="20" /> | |
157 | + </Button> | |
158 | + </Tooltip> | |
159 | + </div> | |
160 | + </section> | |
161 | + </template> | |
162 | + <template #renderItem="{ item }"> | |
163 | + <List.Item :style="{ '--totalHeight': containerHeight }"> | |
164 | + <slot name="renderItem" :item="item" :totalHeight="containerHeight"></slot> | |
165 | + </List.Item> | |
166 | + </template> | |
167 | + </List> | |
168 | + </section> | |
169 | + </main> | |
170 | +</template> | |
171 | + | |
172 | +<style lang="less" scoped> | |
173 | + .basic-card-list { | |
174 | + :deep(.ant-list) { | |
175 | + .ant-spin-container { | |
176 | + overflow-x: hidden; | |
177 | + overflow-y: auto; | |
178 | + } | |
179 | + | |
180 | + .ant-list-header { | |
181 | + border-bottom: transparent; | |
182 | + } | |
183 | + | |
184 | + .ant-list-pagination { | |
185 | + margin-top: 16px; | |
186 | + height: 25px; | |
187 | + } | |
188 | + | |
189 | + .ant-list-empty-text { | |
190 | + height: 100%; | |
191 | + display: flex; | |
192 | + align-items: center; | |
193 | + justify-content: center; | |
194 | + } | |
195 | + } | |
196 | + } | |
197 | +</style> | ... | ... |
1 | +<script setup lang="ts"> | |
2 | + import { Button, Popover } from 'ant-design-vue'; | |
3 | + import { reactive, ref, watch } from 'vue'; | |
4 | + import { Icon } from '/@/components/Icon'; | |
5 | + | |
6 | + const emit = defineEmits(['update:row', 'update:col']); | |
7 | + | |
8 | + const props = withDefaults( | |
9 | + defineProps<{ | |
10 | + row?: number; | |
11 | + col?: number; | |
12 | + }>(), | |
13 | + { | |
14 | + row: 2, | |
15 | + col: 5, | |
16 | + } | |
17 | + ); | |
18 | + | |
19 | + const MAX_ROW = 4; | |
20 | + const MAX_COL = 9; | |
21 | + | |
22 | + const visible = ref(false); | |
23 | + | |
24 | + const selectedLayout = reactive({ row: props.row, col: props.col }); | |
25 | + | |
26 | + const handleOver = (row: number, col: number) => { | |
27 | + Object.assign(selectedLayout, { row, col }); | |
28 | + }; | |
29 | + | |
30 | + const handleSelectConfirm = () => { | |
31 | + const { row, col } = selectedLayout; | |
32 | + emit('update:row', row); | |
33 | + emit('update:col', col); | |
34 | + visible.value = false; | |
35 | + }; | |
36 | + | |
37 | + watch( | |
38 | + () => [props.row, props.col], | |
39 | + () => { | |
40 | + selectedLayout.row = props.row; | |
41 | + selectedLayout.col = props.col; | |
42 | + } | |
43 | + ); | |
44 | +</script> | |
45 | + | |
46 | +<template> | |
47 | + <Popover v-model:visible="visible"> | |
48 | + <template #content> | |
49 | + <section class="flex flex-wrap w-36"> | |
50 | + <table class="border-collapse" @click="handleSelectConfirm"> | |
51 | + <tr v-for="row in MAX_ROW" :key="row" class="border border-gray-300 border-solid"> | |
52 | + <td | |
53 | + v-for="col in MAX_COL" | |
54 | + :class="selectedLayout.col >= col && selectedLayout.row >= row ? 'bg-blue-500' : ''" | |
55 | + :key="col" | |
56 | + class="w-4 h-4 border border-gray-300 border-solid cursor-pointer" | |
57 | + @mouseover="handleOver(row, col)" | |
58 | + > | |
59 | + </td> | |
60 | + </tr> | |
61 | + </table> | |
62 | + <div class="text-center w-full"> | |
63 | + {{ selectedLayout.col }} x {{ selectedLayout.row }} 布局 | |
64 | + </div> | |
65 | + </section> | |
66 | + </template> | |
67 | + <Button type="primary"> | |
68 | + <Icon icon="ant-design:layout-filled" :size="20" /> | |
69 | + </Button> | |
70 | + </Popover> | |
71 | +</template> | ... | ... |
1 | +import type { ComputedRef, ExtractPropTypes, Slots } from 'vue'; | |
2 | +import { unref, computed } from 'vue'; | |
3 | +import { isFunction } from '/@/utils/is'; | |
4 | +import { BasicCardListPropsType } from '../types'; | |
5 | +import { FetchParams } from './useCardListData'; | |
6 | +import { basicProps } from '/@/components/Form/src/props'; | |
7 | + | |
8 | +export function useCardForm( | |
9 | + propsRef: ComputedRef<BasicCardListPropsType>, | |
10 | + slots: Slots, | |
11 | + fetch: (opt?: FetchParams | undefined) => Promise<void>, | |
12 | + getLoading: ComputedRef<boolean | undefined> | |
13 | +) { | |
14 | + const getFormProps = computed((): ExtractPropTypes<typeof basicProps> => { | |
15 | + const { formConfig } = unref(propsRef); | |
16 | + const { submitButtonOptions } = formConfig || {}; | |
17 | + return { | |
18 | + showAdvancedButton: true, | |
19 | + ...formConfig, | |
20 | + submitButtonOptions: { loading: unref(getLoading), ...submitButtonOptions }, | |
21 | + compact: true, | |
22 | + } as any; | |
23 | + }); | |
24 | + | |
25 | + const getFormSlotKeys: ComputedRef<string[]> = computed(() => { | |
26 | + const keys = Object.keys(slots); | |
27 | + return keys | |
28 | + .map((item) => (item.startsWith('form-') ? item : null)) | |
29 | + .filter((item) => !!item) as string[]; | |
30 | + }); | |
31 | + | |
32 | + function replaceFormSlotKey(key: string) { | |
33 | + if (!key) return ''; | |
34 | + return key?.replace?.(/form\-/, '') ?? ''; | |
35 | + } | |
36 | + | |
37 | + function handleSearchInfoChange(info: Recordable) { | |
38 | + const { handleSearchInfoFn } = unref(propsRef); | |
39 | + if (handleSearchInfoFn && isFunction(handleSearchInfoFn)) { | |
40 | + info = handleSearchInfoFn(info) || info; | |
41 | + } | |
42 | + fetch({ searchInfo: info, page: 1 }); | |
43 | + } | |
44 | + | |
45 | + return { | |
46 | + getFormProps, | |
47 | + replaceFormSlotKey, | |
48 | + getFormSlotKeys, | |
49 | + handleSearchInfoChange, | |
50 | + }; | |
51 | +} | ... | ... |
1 | +import { WatchStopHandle, onUnmounted, ref, unref, watch } from 'vue'; | |
2 | +import { BasicCardListPropsType, CardListActionType } from '../types'; | |
3 | +import { isProdMode } from '/@/utils/env'; | |
4 | +import { error } from '/@/utils/log'; | |
5 | +import { FormActionType } from '/@/components/Form'; | |
6 | +import { getDynamicProps } from '/@/utils'; | |
7 | +import { PaginationProps } from 'ant-design-vue'; | |
8 | +import { FetchParams } from './useCardListData'; | |
9 | + | |
10 | +type UseTableMethod = CardListActionType & { | |
11 | + getForm: () => FormActionType; | |
12 | +}; | |
13 | + | |
14 | +export function useCardList( | |
15 | + cardListProps?: BasicCardListPropsType | |
16 | +): [(instance: CardListActionType, formInstance: FormActionType) => void, CardListActionType] { | |
17 | + const cardListRef = ref<Nullable<CardListActionType>>(null); | |
18 | + const loadedRef = ref<Nullable<boolean>>(false); | |
19 | + const formRef = ref<Nullable<FormActionType>>(null); | |
20 | + | |
21 | + let stopWatch: WatchStopHandle; | |
22 | + | |
23 | + function register(instance: CardListActionType, formInstance: FormActionType) { | |
24 | + isProdMode() && | |
25 | + onUnmounted(() => { | |
26 | + cardListRef.value = null; | |
27 | + loadedRef.value = null; | |
28 | + }); | |
29 | + | |
30 | + if (unref(loadedRef) && isProdMode() && instance === unref(cardListRef)) return; | |
31 | + | |
32 | + cardListRef.value = instance; | |
33 | + formRef.value = formInstance; | |
34 | + cardListProps && instance.setProps(getDynamicProps(cardListProps)); | |
35 | + loadedRef.value = true; | |
36 | + | |
37 | + stopWatch?.(); | |
38 | + | |
39 | + stopWatch = watch( | |
40 | + () => cardListProps, | |
41 | + () => { | |
42 | + cardListProps && instance.setProps(getDynamicProps(cardListProps)); | |
43 | + }, | |
44 | + { | |
45 | + immediate: true, | |
46 | + deep: true, | |
47 | + } | |
48 | + ); | |
49 | + } | |
50 | + | |
51 | + function getTableInstance(): CardListActionType { | |
52 | + const table = unref(cardListRef); | |
53 | + if (!table) { | |
54 | + error( | |
55 | + 'The CardList instance has not been obtained yet, please make sure the table is presented when performing the table operation!' | |
56 | + ); | |
57 | + } | |
58 | + return table as CardListActionType; | |
59 | + } | |
60 | + | |
61 | + const cardListActionType: UseTableMethod = { | |
62 | + getForm: () => { | |
63 | + return unref(formRef) as unknown as FormActionType; | |
64 | + }, | |
65 | + setLoading: (loading: boolean) => { | |
66 | + return getTableInstance().setLoading(loading); | |
67 | + }, | |
68 | + setProps: (props: Partial<BasicCardListPropsType>) => { | |
69 | + getTableInstance().setProps(props); | |
70 | + }, | |
71 | + getPagination: () => { | |
72 | + return getTableInstance().getPagination(); | |
73 | + }, | |
74 | + setPagination: (pagination: Partial<PaginationProps>) => { | |
75 | + return getTableInstance().setPagination(pagination); | |
76 | + }, | |
77 | + reload: (opt?: FetchParams) => { | |
78 | + return getTableInstance().reload(opt); | |
79 | + }, | |
80 | + }; | |
81 | + | |
82 | + return [register, cardListActionType]; | |
83 | +} | ... | ... |
1 | +import { Ref, computed, onMounted, ref, unref, watch, watchEffect } from 'vue'; | |
2 | +import { BasicCardListPropsType, CardListEmitType, UseLoading, UsePaginationType } from '../types'; | |
3 | +import { FormActionType } from '/@/components/Form'; | |
4 | +import { isBoolean, isFunction } from '/@/utils/is'; | |
5 | +import { FETCH_SETTING, ROW_KEY } from '/@/components/Table/src/const'; | |
6 | +import { PaginationProps } from 'ant-design-vue'; | |
7 | +import { cloneDeep, get } from 'lodash-es'; | |
8 | +import { buildUUID } from '/@/utils/uuid'; | |
9 | +import { useTimeoutFn } from '/@/hooks/core/useTimeout'; | |
10 | + | |
11 | +interface ActionType { | |
12 | + setLoading: UseLoading['setLoading']; | |
13 | + getPaginationInfo: UsePaginationType['getPaginationInfo']; | |
14 | + setPagination: UsePaginationType['setPagination']; | |
15 | + tableData: Ref<Recordable[]>; | |
16 | + emit: CardListEmitType; | |
17 | +} | |
18 | + | |
19 | +export interface FetchParams { | |
20 | + searchInfo?: Recordable; | |
21 | + page?: number; | |
22 | + sortInfo?: Recordable; | |
23 | + filterInfo?: Recordable; | |
24 | +} | |
25 | + | |
26 | +export function useCardListData( | |
27 | + propsRef: Ref<BasicCardListPropsType>, | |
28 | + formActionType: FormActionType, | |
29 | + actionType: ActionType | |
30 | +) { | |
31 | + const { getFieldsValue } = unref(formActionType)! || {}; | |
32 | + const { getPaginationInfo, setLoading, setPagination, tableData, emit } = actionType; | |
33 | + | |
34 | + const dataSourceRef = ref<Recordable[]>([]); | |
35 | + const rawDataSourceRef = ref<Recordable>({}); | |
36 | + | |
37 | + watchEffect(() => { | |
38 | + tableData.value = unref(dataSourceRef); | |
39 | + }); | |
40 | + | |
41 | + watch( | |
42 | + () => unref(propsRef).dataSource, | |
43 | + () => { | |
44 | + const { dataSource, api } = unref(propsRef); | |
45 | + !api && dataSource && (dataSourceRef.value = dataSource); | |
46 | + }, | |
47 | + { | |
48 | + immediate: true, | |
49 | + } | |
50 | + ); | |
51 | + | |
52 | + function setTableKey(items: any[]) { | |
53 | + if (!items || !Array.isArray(items)) return; | |
54 | + items.forEach((item) => { | |
55 | + if (!item[ROW_KEY]) { | |
56 | + item[ROW_KEY] = buildUUID(); | |
57 | + } | |
58 | + if (item.children && item.children.length) { | |
59 | + setTableKey(item.children); | |
60 | + } | |
61 | + }); | |
62 | + } | |
63 | + | |
64 | + const getAutoCreateKey = computed(() => { | |
65 | + return unref(propsRef).autoCreateKey && !unref(propsRef).rowKey; | |
66 | + }); | |
67 | + | |
68 | + const getRowKey = computed(() => { | |
69 | + const { rowKey } = unref(propsRef); | |
70 | + return unref(getAutoCreateKey) ? ROW_KEY : rowKey; | |
71 | + }); | |
72 | + | |
73 | + const getDataSourceRef = computed(() => { | |
74 | + const dataSource = unref(dataSourceRef); | |
75 | + if (!dataSource || dataSource.length === 0) { | |
76 | + return unref(dataSourceRef); | |
77 | + } | |
78 | + if (unref(getAutoCreateKey)) { | |
79 | + const firstItem = dataSource[0]; | |
80 | + const lastItem = dataSource[dataSource.length - 1]; | |
81 | + | |
82 | + if (firstItem && lastItem) { | |
83 | + if (!firstItem[ROW_KEY] || !lastItem[ROW_KEY]) { | |
84 | + const data = cloneDeep(unref(dataSourceRef)); | |
85 | + data.forEach((item) => { | |
86 | + if (!item[ROW_KEY]) { | |
87 | + item[ROW_KEY] = buildUUID(); | |
88 | + } | |
89 | + if (item.children && item.children.length) { | |
90 | + setTableKey(item.children); | |
91 | + } | |
92 | + }); | |
93 | + dataSourceRef.value = data; | |
94 | + } | |
95 | + } | |
96 | + } | |
97 | + return unref(dataSourceRef); | |
98 | + }); | |
99 | + | |
100 | + async function fetch(opt?: FetchParams) { | |
101 | + const { api, searchInfo, fetchSetting, beforeFetch, afterFetch, useSearchForm, pagination } = | |
102 | + unref(propsRef); | |
103 | + | |
104 | + if (!api || !isFunction(api)) return; | |
105 | + | |
106 | + try { | |
107 | + setLoading(true); | |
108 | + const { pageField, sizeField, listField, totalField } = Object.assign( | |
109 | + {}, | |
110 | + FETCH_SETTING, | |
111 | + fetchSetting | |
112 | + ); | |
113 | + let pageParams: Recordable = {}; | |
114 | + | |
115 | + const { current = 1, pageSize = 10 } = unref(getPaginationInfo) as PaginationProps; | |
116 | + | |
117 | + if ((isBoolean(pagination) && !pagination) || isBoolean(getPaginationInfo)) { | |
118 | + pageParams = {}; | |
119 | + } else { | |
120 | + pageParams[pageField] = current; | |
121 | + pageParams[sizeField] = pageSize; | |
122 | + } | |
123 | + | |
124 | + let params: Recordable = { | |
125 | + ...pageParams, | |
126 | + ...(useSearchForm ? getFieldsValue() : {}), | |
127 | + ...searchInfo, | |
128 | + }; | |
129 | + if (beforeFetch && isFunction(beforeFetch)) { | |
130 | + params = (await beforeFetch(params)) || params; | |
131 | + } | |
132 | + | |
133 | + const res = await api(params); | |
134 | + rawDataSourceRef.value = res; | |
135 | + | |
136 | + const isArrayResult = Array.isArray(res); | |
137 | + | |
138 | + let resultItems: Recordable[] = isArrayResult ? res : get(res, listField); | |
139 | + const resultTotal: number = isArrayResult ? 0 : get(res, totalField); | |
140 | + | |
141 | + // 假如数据变少,导致总页数变少并小于当前选中页码,通过getPaginationRef获取到的页码是不正确的,需获取正确的页码再次执行 | |
142 | + if (resultTotal) { | |
143 | + const currentTotalPage = Math.ceil(resultTotal / pageSize); | |
144 | + if (current > currentTotalPage) { | |
145 | + setPagination({ | |
146 | + current: currentTotalPage, | |
147 | + }); | |
148 | + fetch(opt); | |
149 | + } | |
150 | + } | |
151 | + | |
152 | + if (afterFetch && isFunction(afterFetch)) { | |
153 | + resultItems = (await afterFetch(resultItems, res)) || resultItems; | |
154 | + } | |
155 | + dataSourceRef.value = resultItems; | |
156 | + setPagination({ | |
157 | + total: resultTotal || 0, | |
158 | + }); | |
159 | + if (opt && opt.page) { | |
160 | + setPagination({ | |
161 | + current: opt.page || 1, | |
162 | + }); | |
163 | + } | |
164 | + emit('fetchSuccess', { | |
165 | + items: unref(resultItems), | |
166 | + total: resultTotal, | |
167 | + }); | |
168 | + } catch (error) { | |
169 | + emit('fetchError', error as Error); | |
170 | + dataSourceRef.value = []; | |
171 | + setPagination({ | |
172 | + total: 0, | |
173 | + }); | |
174 | + } finally { | |
175 | + setLoading(false); | |
176 | + } | |
177 | + } | |
178 | + | |
179 | + async function reload(opt?: FetchParams) { | |
180 | + await fetch(opt); | |
181 | + } | |
182 | + | |
183 | + onMounted(() => { | |
184 | + useTimeoutFn(() => { | |
185 | + unref(propsRef).immediate && fetch(); | |
186 | + }, 16); | |
187 | + }); | |
188 | + | |
189 | + return { | |
190 | + reload, | |
191 | + fetch, | |
192 | + getRowKey, | |
193 | + getDataSourceRef, | |
194 | + }; | |
195 | +} | ... | ... |
1 | +import { Ref, ref, unref, watch } from 'vue'; | |
2 | +import { getBoundingClientRect } from '/@/utils/domUtils'; | |
3 | +import { BasicCardListPropsType } from '../types'; | |
4 | +import { useDebounceFn } from '@vueuse/core'; | |
5 | + | |
6 | +export function useCardListScroll( | |
7 | + cardListElRef: Ref<Nullable<ComponentElRef>>, | |
8 | + propsRef: Ref<BasicCardListPropsType>, | |
9 | + getDataSourceRef: Ref<Recordable[]> | |
10 | +) { | |
11 | + const containerHeight = ref(0); | |
12 | + | |
13 | + const redoHeight = () => { | |
14 | + const listEl = unref(cardListElRef)?.$el; | |
15 | + if (!listEl) return; | |
16 | + | |
17 | + const listContainerEl = listEl.querySelector('.ant-spin-container') as HTMLDivElement; | |
18 | + | |
19 | + if (!listContainerEl) return; | |
20 | + | |
21 | + const rect = getBoundingClientRect(listContainerEl); | |
22 | + | |
23 | + if (!rect) return; | |
24 | + | |
25 | + const { top } = rect as DOMRect; | |
26 | + | |
27 | + const { offsetHeight: otherOffsetHeight = 0 } = unref(propsRef); | |
28 | + | |
29 | + const totalHeight = document.documentElement.clientHeight; | |
30 | + | |
31 | + const offsetHeight = 32; | |
32 | + | |
33 | + const paginationHeight = 25 + 16; | |
34 | + | |
35 | + const residualHeight = totalHeight - top - offsetHeight - paginationHeight - otherOffsetHeight; | |
36 | + | |
37 | + listContainerEl.style.maxHeight = `${residualHeight}px`; | |
38 | + listContainerEl.style.height = `${residualHeight}px`; | |
39 | + | |
40 | + containerHeight.value = residualHeight; | |
41 | + }; | |
42 | + | |
43 | + const debounceRedoHeight = useDebounceFn(redoHeight, 100); | |
44 | + | |
45 | + watch( | |
46 | + () => [unref(getDataSourceRef)?.length], | |
47 | + () => { | |
48 | + debounceRedoHeight(); | |
49 | + }, | |
50 | + { | |
51 | + flush: 'post', | |
52 | + } | |
53 | + ); | |
54 | + | |
55 | + return { redoHeight, containerHeight }; | |
56 | +} | ... | ... |
1 | +import { ComputedRef, computed, unref } from 'vue'; | |
2 | + | |
3 | +export function useCardListSelected( | |
4 | + getDataSourceRef: ComputedRef<(Recordable & { checked?: boolean })[]> | |
5 | +) { | |
6 | + const getHasSelectedRecordStatus = computed(() => | |
7 | + unref(getDataSourceRef).find((item) => item.checked) | |
8 | + ); | |
9 | + | |
10 | + // function handlerSelect | |
11 | + | |
12 | + return { | |
13 | + getHasSelectedRecordStatus, | |
14 | + }; | |
15 | +} | ... | ... |
1 | +import { Ref, computed, reactive, unref, watch } from 'vue'; | |
2 | +import { BasicCardListPropsType, ListGridType } from '../types'; | |
3 | +import { getListGridByColumn } from '../utils'; | |
4 | +import { screenMap, sizeEnum } from '/@/enums/breakpointEnum'; | |
5 | + | |
6 | +export function useListGrid(getProps: Ref<BasicCardListPropsType>) { | |
7 | + const cardListLayout = reactive({ row: 2, col: 5 }); | |
8 | + | |
9 | + const getListGrid = computed<ListGridType>(() => { | |
10 | + const { col } = cardListLayout; | |
11 | + const { gutter = 16 } = unref(getProps); | |
12 | + return { | |
13 | + column: col, | |
14 | + gutter, | |
15 | + ...getListGridByColumn(col), | |
16 | + } as ListGridType; | |
17 | + }); | |
18 | + | |
19 | + watch( | |
20 | + () => unref(getProps).baseLayout, | |
21 | + () => { | |
22 | + Object.assign(cardListLayout, unref(getProps).baseLayout); | |
23 | + } | |
24 | + ); | |
25 | + | |
26 | + function getScreenSize() { | |
27 | + const width = document.body.clientWidth; | |
28 | + const xs = screenMap.get(sizeEnum.XS)!; | |
29 | + const sm = screenMap.get(sizeEnum.SM)!; | |
30 | + const md = screenMap.get(sizeEnum.MD)!; | |
31 | + const lg = screenMap.get(sizeEnum.LG)!; | |
32 | + const xl = screenMap.get(sizeEnum.XL)!; | |
33 | + if (width < xs) { | |
34 | + return sizeEnum.XS; | |
35 | + } else if (width < sm) { | |
36 | + return sizeEnum.SM; | |
37 | + } else if (width < md) { | |
38 | + return sizeEnum.MD; | |
39 | + } else if (width < lg) { | |
40 | + return sizeEnum.LG; | |
41 | + } else if (width < xl) { | |
42 | + return sizeEnum.XL; | |
43 | + } else { | |
44 | + return sizeEnum.XXL; | |
45 | + } | |
46 | + } | |
47 | + | |
48 | + return { | |
49 | + getListGrid, | |
50 | + cardListLayout, | |
51 | + getScreenSize, | |
52 | + }; | |
53 | +} | ... | ... |
1 | +import { computed, ref, unref } from 'vue'; | |
2 | + | |
3 | +export function useLoading() { | |
4 | + const loading = ref(false); | |
5 | + | |
6 | + const setLoading = (status: boolean) => (loading.value = status); | |
7 | + | |
8 | + const getLoading = computed(() => unref(loading)); | |
9 | + | |
10 | + return { | |
11 | + loading, | |
12 | + getLoading, | |
13 | + setLoading, | |
14 | + }; | |
15 | +} | ... | ... |
1 | +import { PaginationProps } from 'ant-design-vue'; | |
2 | +import { Ref, computed, ref, unref } from 'vue'; | |
3 | +import { LeftOutlined, RightOutlined } from '@ant-design/icons-vue'; | |
4 | +import { BasicCardListPropsType } from '../types'; | |
5 | +import { isBoolean } from '/@/utils/is'; | |
6 | + | |
7 | +interface ItemRender { | |
8 | + page: number; | |
9 | + type: 'page' | 'prev' | 'next'; | |
10 | + originalElement: any; | |
11 | +} | |
12 | + | |
13 | +function itemRender({ page, type, originalElement }: ItemRender) { | |
14 | + if (type === 'prev') { | |
15 | + return page === 0 ? null : <LeftOutlined />; | |
16 | + } else if (type === 'next') { | |
17 | + return page === 1 ? null : <RightOutlined />; | |
18 | + } | |
19 | + return originalElement; | |
20 | +} | |
21 | + | |
22 | +export function usePagination( | |
23 | + propsRef: Ref<BasicCardListPropsType>, | |
24 | + cardLayoutRef: Record<'row' | 'col', number> | |
25 | +) { | |
26 | + const configRef = ref<PaginationProps>({ | |
27 | + hideOnSinglePage: false, | |
28 | + }); | |
29 | + | |
30 | + const getPaginationInfo = computed(() => { | |
31 | + const { pagination } = unref(propsRef); | |
32 | + const { col, row } = cardLayoutRef; | |
33 | + | |
34 | + return { | |
35 | + current: 1, | |
36 | + pageSize: col * row, | |
37 | + size: 'small', | |
38 | + defaultPageSize: col * row, | |
39 | + showTotal: (total: number) => `共 ${total} 条数据`, | |
40 | + showSizeChanger: false, | |
41 | + itemRender: itemRender, | |
42 | + showQuickJumper: true, | |
43 | + ...(isBoolean(pagination) ? {} : pagination), | |
44 | + ...unref(configRef), | |
45 | + } as Partial<PaginationProps>; | |
46 | + }); | |
47 | + | |
48 | + const setPagination = (info: Partial<PaginationProps>) => { | |
49 | + const paginationInfo = unref(getPaginationInfo); | |
50 | + configRef.value = { | |
51 | + ...(!isBoolean(paginationInfo) ? paginationInfo : {}), | |
52 | + ...info, | |
53 | + }; | |
54 | + }; | |
55 | + | |
56 | + function getPagination() { | |
57 | + return unref(getPaginationInfo); | |
58 | + } | |
59 | + | |
60 | + return { getPaginationInfo, setPagination, getPagination }; | |
61 | +} | ... | ... |
src/components/CardList/src/types.ts
0 → 100644
1 | +import { PaginationProps } from 'ant-design-vue'; | |
2 | +import { FormProps } from '../../Form'; | |
3 | +import { FetchSetting } from '../../Table'; | |
4 | +import { useLoading } from './hooks/useLoading'; | |
5 | +import { usePagination } from './hooks/usePagination'; | |
6 | +import { FetchParams, useCardListData } from './hooks/useCardListData'; | |
7 | + | |
8 | +// export interface CardList | |
9 | + | |
10 | +export interface BasicCardListPropsType<T = Recordable> { | |
11 | + title?: string; | |
12 | + formConfig?: FormProps; | |
13 | + showCardListSetting?: boolean; | |
14 | + useSearchForm?: boolean; | |
15 | + api?: (params?: any) => Promise<any>; | |
16 | + dataSource?: any[]; | |
17 | + beforeFetch?: (params: FetchParams) => Promise<Recordable>; | |
18 | + pagination?: PaginationProps; | |
19 | + offsetHeight?: number; | |
20 | + gutter?: number; | |
21 | + searchInfo?: Recordable; | |
22 | + fetchSetting?: FetchSetting; | |
23 | + afterFetch?: (items: T[], result: any) => Promise<any[]>; | |
24 | + autoCreateKey?: boolean; | |
25 | + rowKey?: (item: T) => string | number; | |
26 | + immediate?: boolean; | |
27 | + handleSearchInfoFn?: Fn; | |
28 | + baseLayout?: Record<'row' | 'col', number>; | |
29 | + selections?: boolean; | |
30 | +} | |
31 | + | |
32 | +export type ListGridType = Record<'column' | 'xs' | 'sm' | 'md' | 'lg' | 'xl' | 'xxl', number> & { | |
33 | + gutter: number | [number, number]; | |
34 | +}; | |
35 | + | |
36 | +export type UseLoading = ReturnType<typeof useLoading>; | |
37 | + | |
38 | +export type UsePaginationType = ReturnType<typeof usePagination>; | |
39 | +export type UseCardListDataType = ReturnType<typeof useCardListData>; | |
40 | + | |
41 | +export interface CardListActionType { | |
42 | + setProps: (props: Partial<BasicCardListPropsType>) => void; | |
43 | + setLoading: UseLoading['setLoading']; | |
44 | + setPagination: UsePaginationType['setPagination']; | |
45 | + getPagination: UsePaginationType['getPagination']; | |
46 | + reload: UseCardListDataType['reload']; | |
47 | +} | |
48 | + | |
49 | +export interface CardListEmitType { | |
50 | + (eventName: 'fetchSuccess', result: { items: Recordable[]; total: number }): void; | |
51 | + (eventName: 'fetchError', error: Error): void; | |
52 | +} | |
53 | + | |
54 | +export interface CardListRenderItem<T = Recordable & { checked?: boolean }> { | |
55 | + item: T; | |
56 | + totalHeight: number; | |
57 | +} | |
58 | + | |
59 | +export interface CardListSelectionsType<T = Recordable> { | |
60 | + onSelect?: (record: T, selected: boolean) => any; | |
61 | + onSelectAll: (selectedRecords: T[]) => any; | |
62 | + onSelectInvert: (selectedRecords: T[]) => any; | |
63 | +} | ... | ... |
src/components/CardList/src/utils/index.ts
0 → 100644
1 | +import { ListGridType } from '../types'; | |
2 | + | |
3 | +export const getListGridByColumn = (col: number): Omit<ListGridType, 'gutter' | 'column'> => { | |
4 | + return { | |
5 | + xxl: col, | |
6 | + xl: col, | |
7 | + lg: col, | |
8 | + md: col, | |
9 | + sm: col, | |
10 | + xs: col, | |
11 | + }; | |
12 | + // return { | |
13 | + // xxl: col, | |
14 | + // xl: Math.max(1, col - 1), | |
15 | + // lg: Math.max(1, col - 2), | |
16 | + // md: Math.max(1, col - 3), | |
17 | + // sm: Math.max(1, col - 4), | |
18 | + // xs: Math.max(1, col - 5), | |
19 | + // }; | |
20 | +}; | ... | ... |
1 | 1 | export { default as ModeSwitchButton } from './ModeSwitchButton.vue'; |
2 | 2 | export { default as CardLayoutButton } from './CardLayoutButton.vue'; |
3 | 3 | export { default as AuthIcon } from './AuthIcon.vue'; |
4 | +export { default as AuthDropDown } from './AuthDropDown.vue'; | |
4 | 5 | export { |
5 | 6 | EnumTableCardMode, |
6 | 7 | EnumTableChartMode, | ... | ... |
1 | +import { WatchStopHandle, onUnmounted, ref, unref, watch } from 'vue'; | |
2 | +import { OrganizationTreeActionType } from '../src/types'; | |
3 | +import { isProdMode } from '/@/utils/env'; | |
4 | +import { error } from '/@/utils/log'; | |
5 | +import { OrganizationTreePropsType } from '../src/props'; | |
6 | +import { getDynamicProps } from '/@/utils'; | |
7 | + | |
8 | +export function useOrganizationTree( | |
9 | + organizationTreeProps?: Partial<OrganizationTreePropsType> | |
10 | +): [(instance: OrganizationTreeActionType) => void, OrganizationTreeActionType] { | |
11 | + const organizationTreeRef = ref<Nullable<OrganizationTreeActionType>>(null); | |
12 | + const loadedRef = ref<Nullable<boolean>>(false); | |
13 | + let stopWatch: WatchStopHandle; | |
14 | + function register(instance: OrganizationTreeActionType) { | |
15 | + isProdMode() && | |
16 | + onUnmounted(() => { | |
17 | + organizationTreeRef.value = null; | |
18 | + loadedRef.value = null; | |
19 | + }); | |
20 | + | |
21 | + if (unref(loadedRef) && isProdMode() && instance === unref(organizationTreeRef)) return; | |
22 | + | |
23 | + organizationTreeRef.value = instance; | |
24 | + loadedRef.value = true; | |
25 | + | |
26 | + stopWatch?.(); | |
27 | + | |
28 | + stopWatch = watch( | |
29 | + () => organizationTreeProps, | |
30 | + () => { | |
31 | + organizationTreeProps && instance.setProps(getDynamicProps(organizationTreeProps)); | |
32 | + }, | |
33 | + { | |
34 | + immediate: true, | |
35 | + deep: true, | |
36 | + } | |
37 | + ); | |
38 | + } | |
39 | + | |
40 | + function getOrganizationTreeInstance(): OrganizationTreeActionType { | |
41 | + const organizationTree = unref(organizationTreeRef); | |
42 | + if (!organizationTree) { | |
43 | + error( | |
44 | + 'The organization tree instance has not been obtained yet, please make sure the table is presented when performing the table operation!' | |
45 | + ); | |
46 | + } | |
47 | + return organizationTree as OrganizationTreeActionType; | |
48 | + } | |
49 | + | |
50 | + const organizationTreeActionType: OrganizationTreeActionType = { | |
51 | + getSelectKey: () => getOrganizationTreeInstance().getSelectKey(), | |
52 | + clearSelected: () => getOrganizationTreeInstance().clearSelected(), | |
53 | + setProps: (props: Partial<OrganizationTreePropsType>) => | |
54 | + getOrganizationTreeInstance().setProps(props), | |
55 | + }; | |
56 | + | |
57 | + return [register, organizationTreeActionType]; | |
58 | +} | ... | ... |
... | ... | @@ -17,41 +17,41 @@ |
17 | 17 | </div> |
18 | 18 | </div> |
19 | 19 | <div :style="{ width: foldFlag ? '0px' : '100%' }" class="bg-white mr-0 overflow-hidden h-full"> |
20 | - <BasicTree | |
21 | - title="组织列表" | |
22 | - toolbar | |
23 | - search | |
24 | - :clickRowToExpand="false" | |
25 | - :treeData="treeData" | |
26 | - :expandedKeys="treeExpandData" | |
27 | - :replaceFields="{ key: 'id', title: 'name' }" | |
28 | - :selectedKeys="selectedKeys" | |
29 | - @select="handleSelect" | |
30 | - v-bind="$attrs" | |
31 | - /> | |
20 | + <BasicTree v-bind="getBindData" /> | |
32 | 21 | </div> |
33 | 22 | </div> |
34 | 23 | </template> |
35 | 24 | <script lang="ts" setup name="OrganizationIdTree"> |
36 | - import { onMounted, ref, unref } from 'vue'; | |
25 | + import { computed, onMounted, ref, unref, useAttrs } from 'vue'; | |
37 | 26 | import { BasicTree, TreeItem } from '/@/components/Tree'; |
38 | 27 | import { getOrganizationList } from '/@/api/system/system'; |
39 | 28 | import { CaretRightOutlined } from '@ant-design/icons-vue'; |
40 | 29 | import { getBoundingClientRect } from '/@/utils/domUtils'; |
30 | + import { BasicTreePropsType, OrganizationTreeActionType } from './types'; | |
31 | + import { OrganizationTreePropsType } from './props'; | |
32 | + | |
33 | + const props = defineProps<OrganizationTreePropsType>(); | |
34 | + | |
35 | + const attrs = useAttrs(); | |
41 | 36 | |
42 | 37 | const tree = ref<Nullable<HTMLDivElement>>(); |
43 | - const emit = defineEmits(['select']); | |
38 | + const emit = defineEmits(['select', 'register']); | |
44 | 39 | const treeData = ref<TreeItem[]>([]); |
45 | 40 | const selectedKeys = ref<string[]>(); |
46 | - const treeExpandData = ref([]); | |
41 | + const treeExpandData = ref<string[]>([]); | |
42 | + | |
43 | + const innerProps = ref<OrganizationTreePropsType>({}); | |
44 | + | |
47 | 45 | //获取所有父级id |
48 | - function findForAllId(data = [], arr = []) { | |
46 | + function findForAllId(data: Recordable[] = [], arr: string[] = []) { | |
49 | 47 | for (const item of data) { |
50 | 48 | arr.push(item.id); |
51 | 49 | } |
52 | 50 | return arr; |
53 | 51 | } |
54 | - function handleSelect(keys) { | |
52 | + | |
53 | + function handleSelect(keys: string[]) { | |
54 | + selectedKeys.value = keys; | |
55 | 55 | emit('select', keys[0]); |
56 | 56 | } |
57 | 57 | function resetOrganization() { |
... | ... | @@ -81,9 +81,41 @@ |
81 | 81 | |
82 | 82 | setTreeHeight(); |
83 | 83 | }); |
84 | + | |
84 | 85 | defineExpose({ |
85 | 86 | resetOrganization, |
86 | 87 | }); |
88 | + | |
89 | + const getProps = computed<OrganizationTreePropsType>(() => ({ ...props, ...unref(innerProps) })); | |
90 | + | |
91 | + const getBindData = computed(() => { | |
92 | + return { | |
93 | + title: '组织列表', | |
94 | + toolbar: true, | |
95 | + search: true, | |
96 | + clickRowToExpand: false, | |
97 | + treeData: unref(treeData), | |
98 | + expandedKeys: unref(treeExpandData), | |
99 | + replaceFields: { key: 'id', title: 'name' }, | |
100 | + selectedKeys: unref(selectedKeys), | |
101 | + ...attrs, | |
102 | + ...unref(getProps), | |
103 | + onSelect: (keys: string[]) => { | |
104 | + handleSelect(keys); | |
105 | + unref(getProps)?.onSelect?.(keys?.[0]); | |
106 | + }, | |
107 | + } as BasicTreePropsType; | |
108 | + }); | |
109 | + | |
110 | + const setProps = (props: Partial<OrganizationTreePropsType>) => { | |
111 | + innerProps.value = { ...(props || {}) }; | |
112 | + }; | |
113 | + | |
114 | + emit('register', { | |
115 | + clearSelected: resetOrganization, | |
116 | + getSelectKey: () => unref(selectedKeys)?.[0], | |
117 | + setProps, | |
118 | + } as OrganizationTreeActionType); | |
87 | 119 | </script> |
88 | 120 | |
89 | 121 | <style scoped lang="less"> | ... | ... |
1 | +import { ExtractPropTypes } from 'vue'; | |
2 | +import { basicProps } from '/@/components/Tree/src/props'; | |
3 | +import { OrganizationTreePropsType } from './props'; | |
4 | + | |
5 | +export interface OrganizationTreeActionType { | |
6 | + clearSelected(): void; | |
7 | + getSelectKey(): undefined | string; | |
8 | + setProps(props: Partial<OrganizationTreePropsType>): any; | |
9 | +} | |
10 | + | |
11 | +export type BasicTreePropsType = Partial<ExtractPropTypes<typeof basicProps>>; | ... | ... |
src/views/configuration/center/TableMode.vue
deleted
100644 → 0
1 | -<template> | |
2 | - <div> | |
3 | - <PageWrapper dense contentFullHeight contentClass="flex"> | |
4 | - <OrganizationIdTree @select="handleSelect" ref="organizationIdTreeRef" /> | |
5 | - <BasicTable | |
6 | - style="flex: auto" | |
7 | - :clickToRowSelect="false" | |
8 | - @register="registerTable" | |
9 | - :searchInfo="searchInfo" | |
10 | - class="w-3/4 xl:w-4/5" | |
11 | - > | |
12 | - <template #platform="{ record }"> | |
13 | - <Tag :color="record.platform === Platform.PHONE ? 'cyan' : 'blue'"> | |
14 | - {{ record.platform === Platform.PHONE ? '移动端' : 'PC端' }} | |
15 | - </Tag> | |
16 | - </template> | |
17 | - <template #toolbar> | |
18 | - <Authority value="api:yt:configuration:center:post"> | |
19 | - <a-button type="primary" @click="handleCreateOrEdit(null)"> 新增组态 </a-button> | |
20 | - </Authority> | |
21 | - <Authority value="api:yt:configuration:center:delete"> | |
22 | - <Popconfirm | |
23 | - title="您确定要批量删除数据" | |
24 | - ok-text="确定" | |
25 | - cancel-text="取消" | |
26 | - @confirm="handleDeleteOrBatchDelete(null)" | |
27 | - > | |
28 | - <a-button type="primary" color="error" :disabled="hasBatchDelete"> | |
29 | - 批量删除 | |
30 | - </a-button> | |
31 | - </Popconfirm> | |
32 | - </Authority> | |
33 | - </template> | |
34 | - <template #action="{ record }"> | |
35 | - <TableAction | |
36 | - :actions="[ | |
37 | - { | |
38 | - label: '设计', | |
39 | - auth: 'api:yt:configuration:center:get_configuration_info:get', | |
40 | - icon: 'clarity:note-edit-line', | |
41 | - onClick: handleDesign.bind(null, record), | |
42 | - }, | |
43 | - { | |
44 | - label: '预览', | |
45 | - auth: 'api:yt:configuration:center:get_configuration_info:get', | |
46 | - icon: 'ant-design:eye-outlined', | |
47 | - onClick: handlePreview.bind(null, record), | |
48 | - }, | |
49 | - { | |
50 | - label: '编辑', | |
51 | - auth: 'api:yt:configuration:center:update', | |
52 | - icon: 'clarity:note-edit-line', | |
53 | - onClick: handleCreateOrEdit.bind(null, record), | |
54 | - }, | |
55 | - { | |
56 | - label: '删除', | |
57 | - auth: 'api:yt:configuration:center:delete', | |
58 | - icon: 'ant-design:delete-outlined', | |
59 | - color: 'error', | |
60 | - popConfirm: { | |
61 | - title: '是否确认删除', | |
62 | - confirm: handleDeleteOrBatchDelete.bind(null, record), | |
63 | - }, | |
64 | - }, | |
65 | - ]" | |
66 | - /> | |
67 | - </template> | |
68 | - </BasicTable> | |
69 | - </PageWrapper> | |
70 | - <ContactDrawer @register="registerDrawer" @success="handleSuccess" /> | |
71 | - </div> | |
72 | -</template> | |
73 | - | |
74 | -<script lang="ts"> | |
75 | - import { defineComponent, reactive, nextTick } from 'vue'; | |
76 | - import { BasicTable, useTable, TableAction } from '/@/components/Table'; | |
77 | - import { PageWrapper } from '/@/components/Page'; | |
78 | - import { useDrawer } from '/@/components/Drawer'; | |
79 | - import ContactDrawer from './ConfigurationCenterDrawer.vue'; | |
80 | - import { useResetOrganizationTree, OrganizationIdTree } from '/@/views/common/organizationIdTree'; | |
81 | - import { searchFormSchema, columns, Platform } from './center.data'; | |
82 | - import { | |
83 | - getPage, | |
84 | - deleteConfigurationCenter, | |
85 | - } from '/@/api/configuration/center/configurationCenter'; | |
86 | - import { useBatchDelete } from '/@/hooks/web/useBatchDelete'; | |
87 | - import { isDevMode } from '/@/utils/env'; | |
88 | - import { Authority } from '/@/components/Authority'; | |
89 | - import { Popconfirm } from 'ant-design-vue'; | |
90 | - import { Tag } from 'ant-design-vue'; | |
91 | - import { useGlobSetting } from '/@/hooks/setting'; | |
92 | - export default defineComponent({ | |
93 | - components: { | |
94 | - PageWrapper, | |
95 | - OrganizationIdTree, | |
96 | - BasicTable, | |
97 | - TableAction, | |
98 | - ContactDrawer, | |
99 | - Authority, | |
100 | - Popconfirm, | |
101 | - Tag, | |
102 | - }, | |
103 | - setup() { | |
104 | - const { configurationPrefix } = useGlobSetting(); | |
105 | - const isDev = isDevMode(); | |
106 | - const searchInfo = reactive<Recordable>({}); | |
107 | - const { organizationIdTreeRef, resetFn } = useResetOrganizationTree(searchInfo); | |
108 | - // 表格hooks | |
109 | - const [registerTable, { reload, setProps }] = useTable({ | |
110 | - title: '组态中心列表', | |
111 | - api: getPage, | |
112 | - columns, | |
113 | - clickToRowSelect: false, | |
114 | - formConfig: { | |
115 | - labelWidth: 120, | |
116 | - schemas: searchFormSchema, | |
117 | - resetFunc: resetFn, | |
118 | - }, | |
119 | - showIndexColumn: false, | |
120 | - useSearchForm: true, | |
121 | - showTableSetting: true, | |
122 | - bordered: true, | |
123 | - rowKey: 'id', | |
124 | - actionColumn: { | |
125 | - width: 200, | |
126 | - title: '操作', | |
127 | - dataIndex: 'action', | |
128 | - slots: { customRender: 'action' }, | |
129 | - fixed: 'right', | |
130 | - }, | |
131 | - }); | |
132 | - const { hasBatchDelete, handleDeleteOrBatchDelete, selectionOptions } = useBatchDelete( | |
133 | - deleteConfigurationCenter, | |
134 | - handleSuccess, | |
135 | - setProps | |
136 | - ); | |
137 | - nextTick(() => { | |
138 | - setProps(selectionOptions); | |
139 | - }); | |
140 | - | |
141 | - // 弹框 | |
142 | - const [registerDrawer, { openDrawer }] = useDrawer(); | |
143 | - | |
144 | - // 刷新 | |
145 | - function handleSuccess() { | |
146 | - reload(); | |
147 | - } | |
148 | - // 新增或编辑 | |
149 | - const handleCreateOrEdit = (record: Recordable | null) => { | |
150 | - if (record) { | |
151 | - openDrawer(true, { | |
152 | - isUpdate: true, | |
153 | - record, | |
154 | - }); | |
155 | - } else { | |
156 | - openDrawer(true, { | |
157 | - isUpdate: false, | |
158 | - }); | |
159 | - } | |
160 | - }; | |
161 | - // 树形选择器 | |
162 | - const handleSelect = (organizationId: string) => { | |
163 | - searchInfo.organizationId = organizationId; | |
164 | - handleSuccess(); | |
165 | - }; | |
166 | - | |
167 | - const handlePreview = (record: Recordable | null) => { | |
168 | - window.open( | |
169 | - `${configurationPrefix}/${isDev ? '?dev=1&' : '?'}configurationId=${ | |
170 | - record!.id | |
171 | - }&lightbox=1` | |
172 | - ); | |
173 | - }; | |
174 | - const handleDesign = (record: Recordable | null) => { | |
175 | - window.open( | |
176 | - `${configurationPrefix}/${isDev ? '?dev=1&' : '?'}configurationId=${record!.id}` | |
177 | - ); | |
178 | - }; | |
179 | - | |
180 | - return { | |
181 | - Platform, | |
182 | - searchInfo, | |
183 | - hasBatchDelete, | |
184 | - handleCreateOrEdit, | |
185 | - handleDeleteOrBatchDelete, | |
186 | - handleSelect, | |
187 | - handleSuccess, | |
188 | - handlePreview, | |
189 | - handleDesign, | |
190 | - registerTable, | |
191 | - registerDrawer, | |
192 | - organizationIdTreeRef, | |
193 | - }; | |
194 | - }, | |
195 | - }); | |
196 | -</script> |
1 | 1 | <script setup lang="ts"> |
2 | - import { List, Card, Button, PaginationProps, Tooltip } from 'ant-design-vue'; | |
3 | - import { ReloadOutlined } from '@ant-design/icons-vue'; | |
4 | - import { computed, onMounted, reactive, ref, unref } from 'vue'; | |
5 | - import { OrganizationIdTree, useResetOrganizationTree } from '../../common/organizationIdTree'; | |
2 | + import { BasicCardList, useCardList } from '/@/components/CardList'; | |
6 | 3 | import { |
7 | 4 | deleteConfigurationCenter, |
8 | 5 | getPage, |
9 | 6 | shareConfiguration, |
10 | 7 | } from '/@/api/configuration/center/configurationCenter'; |
8 | + import { searchFormSchema, ConfigurationPermission } from './center.data'; | |
11 | 9 | import { ConfigurationCenterItemsModal } from '/@/api/configuration/center/model/configurationCenterModal'; |
12 | - import { PageWrapper } from '/@/components/Page'; | |
13 | - import { BasicForm, useForm } from '/@/components/Form'; | |
14 | - import { ConfigurationPermission, Platform, searchFormSchema } from './center.data'; | |
15 | - import { useMessage } from '/@/hooks/web/useMessage'; | |
16 | 10 | import { Authority } from '/@/components/Authority'; |
17 | - import ConfigurationCenterDrawer from './ConfigurationCenterDrawer.vue'; | |
18 | - import { useDrawer } from '/@/components/Drawer'; | |
19 | - import { getBoundingClientRect } from '/@/utils/domUtils'; | |
11 | + import { Button, Card, Tooltip } from 'ant-design-vue'; | |
12 | + import { useRole } from '/@/hooks/business/useRole'; | |
20 | 13 | import configurationSrc from '/@/assets/icons/configuration.svg'; |
21 | - import { cloneDeep } from 'lodash'; | |
14 | + import { Platform } from '../center/center.data'; | |
15 | + import { computed, unref } from 'vue'; | |
16 | + import { createScadaPageLink, ScadaModeEnum } from '../center/help'; | |
17 | + import { useDrawer } from '/@/components/Drawer'; | |
22 | 18 | import { usePermission } from '/@/hooks/web/usePermission'; |
23 | - import { AuthIcon, CardLayoutButton } from '/@/components/Widget'; | |
24 | - import AuthDropDown from '/@/components/Widget/AuthDropDown.vue'; | |
25 | - import { ShareModal } from '/@/views/common/ShareModal'; | |
26 | - import { ViewTypeNameEnum } from '../../common/ShareModal/config'; | |
27 | - import { useModal } from '/@/components/Modal'; | |
28 | - import { ViewType } from '../../visual/board/config/panelDetail'; | |
29 | - import { useRole } from '/@/hooks/business/useRole'; | |
30 | - import { useClipboard } from '@vueuse/core'; | |
19 | + import { useMessage } from '/@/hooks/web/useMessage'; | |
31 | 20 | import { Icon } from '/@/components/Icon'; |
32 | - import { createScadaPageLink, ScadaModeEnum } from './help'; | |
33 | - | |
34 | - const listColumn = ref(5); | |
35 | - | |
36 | - const { createMessage } = useMessage(); | |
37 | - | |
38 | - const { isCustomerUser } = useRole(); | |
39 | - | |
40 | - const organizationId = ref<Nullable<number>>(null); | |
41 | - | |
42 | - const pagination = reactive<PaginationProps>({ | |
43 | - size: 'small', | |
44 | - showTotal: (total: number) => `共 ${total} 条数据`, | |
45 | - current: 1, | |
46 | - pageSize: unref(listColumn) * 2, | |
47 | - onChange: (page: number) => { | |
48 | - pagination.current = page; | |
49 | - getListData(); | |
21 | + import { AuthIcon, AuthDropDown } from '/@/components/Widget'; | |
22 | + import { cloneDeep } from 'lodash-es'; | |
23 | + import { OrganizationIdTree, useOrganizationTree } from '../../common/organizationIdTree'; | |
24 | + import ConfigurationCenterDrawer from './ConfigurationCenterDrawer.vue'; | |
25 | + import { useClipboard } from '@vueuse/core'; | |
26 | + import { useModal } from '/@/components/Modal'; | |
27 | + import { ShareModal } from '/@/views/common/ShareModal'; | |
28 | + import { ViewTypeEnum, ViewTypeNameEnum } from '../../common/ShareModal/config'; | |
29 | + | |
30 | + const [register, { reload }] = useCardList({ | |
31 | + api: getPage, | |
32 | + useSearchForm: true, | |
33 | + title: '组态列表', | |
34 | + gutter: 4, | |
35 | + formConfig: { | |
36 | + schemas: searchFormSchema, | |
37 | + labelWidth: 80, | |
38 | + resetFunc: async () => { | |
39 | + clearSelected(); | |
40 | + }, | |
41 | + }, | |
42 | + beforeFetch: async (params: Recordable) => { | |
43 | + return { ...params, organizationId: getSelectKey(), isTemplate: 0 }; | |
50 | 44 | }, |
51 | 45 | }); |
52 | 46 | |
53 | - const loading = ref(false); | |
54 | - | |
55 | - const dataSource = ref<ConfigurationCenterItemsModal[]>([]); | |
56 | - | |
57 | - const [registerForm, { getFieldsValue }] = useForm({ | |
58 | - schemas: searchFormSchema, | |
59 | - showAdvancedButton: true, | |
60 | - labelWidth: 100, | |
61 | - compact: true, | |
62 | - resetFunc: () => { | |
63 | - resetFn(); | |
64 | - organizationId.value = null; | |
65 | - return getListData(); | |
66 | - }, | |
67 | - submitFunc: async () => { | |
68 | - const value = getFieldsValue(); | |
69 | - getListData(value); | |
47 | + const [registerOrgTree, { getSelectKey, clearSelected }] = useOrganizationTree({ | |
48 | + onSelect: () => { | |
49 | + reload(); | |
70 | 50 | }, |
71 | 51 | }); |
72 | 52 | |
73 | - async function getListData(value: Recordable = {}) { | |
74 | - try { | |
75 | - loading.value = true; | |
76 | - const pageSize = unref(listColumn) * 2; | |
77 | - const { items, total } = await getPage({ | |
78 | - organizationId: unref(organizationId), | |
79 | - ...value, | |
80 | - isTemplate: 0, | |
81 | - page: pagination.current!, | |
82 | - pageSize, | |
83 | - }); | |
84 | - | |
85 | - dataSource.value = items; | |
86 | - Object.assign(pagination, { total, pageSize }); | |
87 | - } catch (error) { | |
88 | - } finally { | |
89 | - loading.value = false; | |
90 | - } | |
91 | - } | |
53 | + const [registerDrawer, { openDrawer }] = useDrawer(); | |
92 | 54 | |
93 | - onMounted(() => { | |
94 | - getListData(); | |
95 | - }); | |
55 | + const [registerShareModal, { openModal }] = useModal(); | |
96 | 56 | |
97 | - const searchInfo = reactive<Recordable>({}); | |
98 | - const { organizationIdTreeRef, resetFn } = useResetOrganizationTree(searchInfo); | |
99 | - const handleSelect = (orgId: number) => { | |
100 | - organizationId.value = orgId; | |
101 | - getListData(); | |
57 | + const handleOpenShareModal = (record: ConfigurationCenterItemsModal) => { | |
58 | + openModal(true, { record, href: createShareUrl(record) }); | |
102 | 59 | }; |
103 | 60 | |
104 | - const [registerDrawer, { openDrawer }] = useDrawer(); | |
61 | + const { createMessage } = useMessage(); | |
62 | + | |
63 | + const { isCustomerUser } = useRole(); | |
105 | 64 | |
106 | 65 | const { hasPermission } = usePermission(); |
107 | 66 | |
... | ... | @@ -141,19 +100,6 @@ |
141 | 100 | createScadaPageLink(record, ScadaModeEnum.DESIGN); |
142 | 101 | }; |
143 | 102 | |
144 | - const handleDelete = async (record: ConfigurationCenterItemsModal) => { | |
145 | - try { | |
146 | - await deleteConfigurationCenter([record.id]); | |
147 | - createMessage.success('删除成功'); | |
148 | - await getListData(); | |
149 | - } catch (error) {} | |
150 | - }; | |
151 | - | |
152 | - const handleCardLayoutChange = () => { | |
153 | - pagination.current = 1; | |
154 | - getListData(); | |
155 | - }; | |
156 | - | |
157 | 103 | const createShareUrl = (record: ConfigurationCenterItemsModal) => { |
158 | 104 | return createScadaPageLink(record, ScadaModeEnum.SHARE, false); |
159 | 105 | }; |
... | ... | @@ -168,209 +114,138 @@ |
168 | 114 | } |
169 | 115 | }; |
170 | 116 | |
171 | - const [registerShareModal, { openModal }] = useModal(); | |
172 | - | |
173 | - const handleOpenShareModal = (record: ConfigurationCenterItemsModal) => { | |
174 | - openModal(true, { record, href: createShareUrl(record) }); | |
117 | + const handleDelete = async (record: ConfigurationCenterItemsModal) => { | |
118 | + try { | |
119 | + await deleteConfigurationCenter([record.id]); | |
120 | + createMessage.success('删除成功'); | |
121 | + await reload(); | |
122 | + } catch (error) {} | |
175 | 123 | }; |
176 | - | |
177 | - const listEl = ref<Nullable<ComponentElRef>>(null); | |
178 | - | |
179 | - onMounted(() => { | |
180 | - const clientHeight = document.documentElement.clientHeight; | |
181 | - const rect = getBoundingClientRect(unref(listEl)!.$el! as HTMLElement) as DOMRect; | |
182 | - // margin-top 24 height 24 | |
183 | - const paginationHeight = 24 + 24 + 8; | |
184 | - // list pading top 8 maring-top 8 extra slot 56 | |
185 | - const listContainerMarginBottom = 8 + 8 + 56; | |
186 | - const listContainerHeight = | |
187 | - clientHeight - rect.top - paginationHeight - listContainerMarginBottom; | |
188 | - const listContainerEl = (unref(listEl)!.$el as HTMLElement).querySelector( | |
189 | - '.ant-spin-container' | |
190 | - ) as HTMLElement; | |
191 | - listContainerEl && | |
192 | - (listContainerEl.style.height = listContainerHeight + 'px') && | |
193 | - (listContainerEl.style.overflowY = 'auto') && | |
194 | - (listContainerEl.style.overflowX = 'hidden'); | |
195 | - }); | |
196 | 124 | </script> |
197 | 125 | |
198 | 126 | <template> |
199 | - <PageWrapper dense contentFullHeight contentClass="flex"> | |
200 | - <OrganizationIdTree @select="handleSelect" ref="organizationIdTreeRef" /> | |
201 | - <section class="flex-auto p-4 w-3/4 xl:w-4/5 w-full configuration-list"> | |
202 | - <div class="flex-auto w-full bg-light-50 dark:bg-dark-900 p-4"> | |
203 | - <BasicForm @register="registerForm" /> | |
204 | - </div> | |
205 | - <List | |
206 | - ref="listEl" | |
207 | - :loading="loading" | |
208 | - class="flex-auto bg-light-50 dark:bg-dark-900 !p-2 !mt-4" | |
209 | - position="bottom" | |
210 | - :pagination="pagination" | |
211 | - :data-source="dataSource" | |
212 | - :grid="{ gutter: 4, column: listColumn }" | |
213 | - > | |
214 | - <template #header> | |
215 | - <div class="flex gap-3 justify-end"> | |
216 | - <Authority v-if="!isCustomerUser" :value="ConfigurationPermission.CREATE"> | |
217 | - <Button type="primary" @click="handleCreateOrUpdate()">新增组态</Button> | |
218 | - </Authority> | |
219 | - <CardLayoutButton v-model:value="listColumn" @change="handleCardLayoutChange" /> | |
220 | - <Tooltip title="刷新"> | |
221 | - <Button type="primary" @click="getListData"> | |
222 | - <ReloadOutlined /> | |
223 | - </Button> | |
224 | - </Tooltip> | |
225 | - </div> | |
226 | - </template> | |
227 | - <template #renderItem="{ item }"> | |
228 | - <List.Item> | |
229 | - <Card | |
230 | - :style="{ | |
231 | - '--viewType': item.viewType === ViewType.PUBLIC_VIEW ? '#1890ff' : '#faad14', | |
232 | - }" | |
233 | - hoverable | |
234 | - class="card-container" | |
127 | + <section class="flex w-full h-full"> | |
128 | + <OrganizationIdTree @register="registerOrgTree" /> | |
129 | + <BasicCardList class="flex-auto p-4 w-3/4 xl:w-4/5 w-full" @register="register"> | |
130 | + <template #toolbar> | |
131 | + <div class="flex gap-3 justify-end"> | |
132 | + <Authority v-if="!isCustomerUser" :value="ConfigurationPermission.CREATE"> | |
133 | + <Button type="primary" @click="handleCreateOrUpdate()">新增组态</Button> | |
134 | + </Authority> | |
135 | + </div> | |
136 | + </template> | |
137 | + <template #renderItem="{ item }: CardListRenderItem<ConfigurationCenterItemsModal>"> | |
138 | + <Card | |
139 | + :style="{ | |
140 | + '--viewType': item.viewType === ViewTypeEnum.PUBLIC_VIEW ? '#faad14' : '#1890ff', | |
141 | + }" | |
142 | + hoverable | |
143 | + class="card-container" | |
144 | + > | |
145 | + <template #cover> | |
146 | + <div | |
147 | + class="img-container h-full w-full !flex justify-center items-center text-center p-1 relative bg-light-50 rounded-tl-10xl" | |
235 | 148 | > |
236 | - <template #cover> | |
237 | - <div | |
238 | - class="img-container h-full w-full !flex justify-center items-center text-center p-1 relative" | |
239 | - > | |
240 | - <img | |
241 | - class="w-full h-36" | |
242 | - alt="example" | |
243 | - :src="item.thumbnail || configurationSrc" | |
244 | - @click="handlePreview(item)" | |
149 | + <img | |
150 | + class="w-full max-h-32 h-32 object-contain !rounded-tl-10xl" | |
151 | + alt="example" | |
152 | + :src="item.thumbnail || configurationSrc" | |
153 | + @click="handlePreview(item)" | |
154 | + /> | |
155 | + <span class="absolute top-0 left-0 text-light-50 transform -rotate-45 translate-y-1"> | |
156 | + {{ ViewTypeNameEnum[item.viewType] }} | |
157 | + </span> | |
158 | + </div> | |
159 | + </template> | |
160 | + <template class="ant-card-actions" #actions> | |
161 | + <Tooltip title="预览"> | |
162 | + <AuthIcon | |
163 | + :auth="ConfigurationPermission.PREVIEW" | |
164 | + class="!text-lg" | |
165 | + icon="ant-design:eye-outlined" | |
166 | + @click="handlePreview(item)" | |
167 | + /> | |
168 | + </Tooltip> | |
169 | + <Tooltip v-if="!isCustomerUser" title="设计"> | |
170 | + <AuthIcon | |
171 | + :auth="ConfigurationPermission.DESIGN" | |
172 | + class="!text-lg" | |
173 | + icon="ant-design:edit-outlined" | |
174 | + @click="handleDesign(item)" | |
175 | + /> | |
176 | + </Tooltip> | |
177 | + <Tooltip title="点击复制分享链接"> | |
178 | + <AuthIcon | |
179 | + :auth="ConfigurationPermission.SHARE" | |
180 | + :disabled="!item.publicId" | |
181 | + class="!text-lg" | |
182 | + icon="ant-design:share-alt-outlined" | |
183 | + @click="handleCreateShareUrl(item)" | |
184 | + /> | |
185 | + </Tooltip> | |
186 | + <AuthDropDown | |
187 | + v-if="!isCustomerUser" | |
188 | + :dropMenuList="[ | |
189 | + { | |
190 | + text: '分享', | |
191 | + auth: ConfigurationPermission.SHARE, | |
192 | + icon: 'ant-design:share-alt-outlined', | |
193 | + event: '', | |
194 | + onClick: handleOpenShareModal.bind(null, item), | |
195 | + }, | |
196 | + { | |
197 | + text: '编辑', | |
198 | + auth: ConfigurationPermission.UPDATE, | |
199 | + icon: 'clarity:note-edit-line', | |
200 | + event: '', | |
201 | + onClick: handleCreateOrUpdate.bind(null, item), | |
202 | + }, | |
203 | + { | |
204 | + text: '删除', | |
205 | + auth: ConfigurationPermission.DELETE, | |
206 | + icon: 'ant-design:delete-outlined', | |
207 | + event: '', | |
208 | + popconfirm: { | |
209 | + title: '是否确认删除操作?', | |
210 | + onConfirm: handleDelete.bind(null, item), | |
211 | + }, | |
212 | + }, | |
213 | + ]" | |
214 | + :trigger="['hover']" | |
215 | + /> | |
216 | + </template> | |
217 | + <Card.Meta> | |
218 | + <template #title> | |
219 | + <span class="truncate">{{ item.name }}</span> | |
220 | + </template> | |
221 | + <template #description> | |
222 | + <div class="truncate h-11"> | |
223 | + <div class="truncate flex justify-between items-center"> | |
224 | + <div>{{ item.organizationDTO?.name }}</div> | |
225 | + <Icon | |
226 | + :icon=" | |
227 | + item.platform === Platform.PC | |
228 | + ? 'ri:computer-line' | |
229 | + : 'clarity:mobile-phone-solid' | |
230 | + " | |
245 | 231 | /> |
246 | - <span | |
247 | - class="absolute top-0 left-0 text-light-50 transform -rotate-45 translate-y-1" | |
248 | - > | |
249 | - {{ ViewTypeNameEnum[item.viewType] || ViewTypeNameEnum.PRIVATE_VIEW }} | |
250 | - </span> | |
251 | 232 | </div> |
252 | - </template> | |
253 | - <template class="ant-card-actions" #actions> | |
254 | - <Tooltip title="预览"> | |
255 | - <AuthIcon | |
256 | - :auth="ConfigurationPermission.PREVIEW" | |
257 | - class="!text-lg" | |
258 | - icon="ant-design:eye-outlined" | |
259 | - @click="handlePreview(item)" | |
260 | - /> | |
261 | - </Tooltip> | |
262 | - <Tooltip v-if="!isCustomerUser" title="设计"> | |
263 | - <AuthIcon | |
264 | - :auth="ConfigurationPermission.DESIGN" | |
265 | - class="!text-lg" | |
266 | - icon="ant-design:edit-outlined" | |
267 | - @click="handleDesign(item)" | |
268 | - /> | |
269 | - </Tooltip> | |
270 | - <Tooltip title="点击复制分享链接"> | |
271 | - <AuthIcon | |
272 | - :auth="ConfigurationPermission.SHARE" | |
273 | - :disabled="!item.publicId" | |
274 | - class="!text-lg" | |
275 | - icon="ant-design:share-alt-outlined" | |
276 | - @click="handleCreateShareUrl(item)" | |
277 | - /> | |
278 | - </Tooltip> | |
279 | - <AuthDropDown | |
280 | - v-if="!isCustomerUser" | |
281 | - :dropMenuList="[ | |
282 | - { | |
283 | - text: '分享', | |
284 | - auth: ConfigurationPermission.SHARE, | |
285 | - icon: 'ant-design:share-alt-outlined', | |
286 | - event: '', | |
287 | - onClick: handleOpenShareModal.bind(null, item), | |
288 | - }, | |
289 | - { | |
290 | - text: '编辑', | |
291 | - auth: ConfigurationPermission.UPDATE, | |
292 | - icon: 'clarity:note-edit-line', | |
293 | - event: '', | |
294 | - onClick: handleCreateOrUpdate.bind(null, item), | |
295 | - }, | |
296 | - { | |
297 | - text: '删除', | |
298 | - auth: ConfigurationPermission.DELETE, | |
299 | - icon: 'ant-design:delete-outlined', | |
300 | - event: '', | |
301 | - popconfirm: { | |
302 | - title: '是否确认删除操作?', | |
303 | - onConfirm: handleDelete.bind(null, item), | |
304 | - }, | |
305 | - }, | |
306 | - ]" | |
307 | - :trigger="['hover']" | |
308 | - /> | |
309 | - </template> | |
310 | - <Card.Meta> | |
311 | - <template #title> | |
312 | - <span class="truncate">{{ item.name }}</span> | |
313 | - </template> | |
314 | - <template #description> | |
315 | - <div class="truncate h-11"> | |
316 | - <div class="truncate flex justify-between items-center"> | |
317 | - <div>{{ item.organizationDTO.name }}</div> | |
318 | - <Icon | |
319 | - :icon=" | |
320 | - item.platform === Platform.PC | |
321 | - ? 'ri:computer-line' | |
322 | - : 'clarity:mobile-phone-solid' | |
323 | - " | |
324 | - /> | |
325 | - </div> | |
326 | - <div class="truncate">{{ item.remark || '' }} </div> | |
327 | - </div> | |
328 | - </template> | |
329 | - </Card.Meta> | |
330 | - </Card> | |
331 | - </List.Item> | |
332 | - </template> | |
333 | - </List> | |
334 | - </section> | |
335 | - <ConfigurationCenterDrawer @register="registerDrawer" @success="getListData" /> | |
336 | - <ShareModal | |
337 | - @register="registerShareModal" | |
338 | - :shareApi="shareConfiguration" | |
339 | - @success="getListData" | |
340 | - /> | |
341 | - </PageWrapper> | |
233 | + <div class="truncate">{{ item.remark || '' }} </div> | |
234 | + </div> | |
235 | + </template> | |
236 | + </Card.Meta> | |
237 | + </Card> | |
238 | + </template> | |
239 | + </BasicCardList> | |
240 | + <ConfigurationCenterDrawer @register="registerDrawer" @success="reload()" /> | |
241 | + <ShareModal @register="registerShareModal" :shareApi="shareConfiguration" @success="reload" /> | |
242 | + </section> | |
342 | 243 | </template> |
343 | 244 | |
344 | 245 | <style lang="less" scoped> |
345 | - .configuration-list:deep(.ant-list-header) { | |
346 | - border-bottom: none !important; | |
347 | - } | |
348 | - | |
349 | - .configuration-list:deep(.ant-list-pagination) { | |
350 | - height: 24px; | |
351 | - } | |
352 | - | |
353 | - .configuration-list:deep(.ant-card-body) { | |
354 | - padding: 16px !important; | |
355 | - } | |
356 | - | |
357 | - .configuration-list:deep(.ant-list-empty-text) { | |
358 | - @apply w-full h-full flex justify-center items-center; | |
359 | - } | |
360 | - | |
361 | 246 | .card-container { |
362 | - // background-color: red; | |
363 | - .img-container { | |
364 | - border-top-left-radius: 80px; | |
365 | - background-color: #fff; | |
366 | - | |
367 | - img { | |
368 | - border-top-left-radius: 80px; | |
369 | - } | |
247 | + :deep(.ant-card-cover) { | |
248 | + background-color: var(--viewType); | |
370 | 249 | } |
371 | 250 | } |
372 | - | |
373 | - .card-container:deep(.ant-card-cover) { | |
374 | - background-color: var(--viewType); | |
375 | - } | |
376 | 251 | </style> | ... | ... |
src/views/configuration/template/TableMode.vue
deleted
100644 → 0
1 | -<template> | |
2 | - <div> | |
3 | - <PageWrapper dense contentFullHeight contentClass="flex"> | |
4 | - <OrganizationIdTree @select="handleSelect" ref="organizationIdTreeRef" /> | |
5 | - <BasicTable | |
6 | - style="flex: auto" | |
7 | - :clickToRowSelect="false" | |
8 | - @register="registerTable" | |
9 | - :searchInfo="searchInfo" | |
10 | - class="w-3/4 xl:w-4/5" | |
11 | - > | |
12 | - <template #platform="{ record }"> | |
13 | - <Tag :color="record.platform === Platform.PHONE ? 'cyan' : 'blue'"> | |
14 | - {{ record.platform === Platform.PHONE ? '移动端' : 'PC端' }} | |
15 | - </Tag> | |
16 | - </template> | |
17 | - <template #toolbar> | |
18 | - <Authority value="api:yt:configuration:center:post"> | |
19 | - <a-button type="primary" @click="handleCreateOrEdit(null)"> 新增组态 </a-button> | |
20 | - </Authority> | |
21 | - <Authority value="api:yt:configuration:center:delete"> | |
22 | - <Popconfirm | |
23 | - title="您确定要批量删除数据" | |
24 | - ok-text="确定" | |
25 | - cancel-text="取消" | |
26 | - @confirm="handleDeleteOrBatchDelete(null)" | |
27 | - > | |
28 | - <a-button type="primary" color="error" :disabled="hasBatchDelete"> | |
29 | - 批量删除 | |
30 | - </a-button> | |
31 | - </Popconfirm> | |
32 | - </Authority> | |
33 | - </template> | |
34 | - <template #action="{ record }"> | |
35 | - <TableAction | |
36 | - :actions="[ | |
37 | - { | |
38 | - label: '设计', | |
39 | - auth: 'api:yt:configuration:center:get_configuration_info:get', | |
40 | - icon: 'clarity:note-edit-line', | |
41 | - onClick: handleDesign.bind(null, record), | |
42 | - }, | |
43 | - { | |
44 | - label: '预览', | |
45 | - auth: 'api:yt:configuration:center:get_configuration_info:get', | |
46 | - icon: 'ant-design:eye-outlined', | |
47 | - onClick: handlePreview.bind(null, record), | |
48 | - }, | |
49 | - { | |
50 | - label: '编辑', | |
51 | - auth: 'api:yt:configuration:center:update', | |
52 | - icon: 'clarity:note-edit-line', | |
53 | - onClick: handleCreateOrEdit.bind(null, record), | |
54 | - }, | |
55 | - { | |
56 | - label: '删除', | |
57 | - auth: 'api:yt:configuration:center:delete', | |
58 | - icon: 'ant-design:delete-outlined', | |
59 | - color: 'error', | |
60 | - popConfirm: { | |
61 | - title: '是否确认删除', | |
62 | - confirm: handleDeleteOrBatchDelete.bind(null, record), | |
63 | - }, | |
64 | - }, | |
65 | - ]" | |
66 | - /> | |
67 | - </template> | |
68 | - </BasicTable> | |
69 | - </PageWrapper> | |
70 | - <ContactDrawer @register="registerDrawer" @success="handleSuccess" /> | |
71 | - </div> | |
72 | -</template> | |
73 | - | |
74 | -<script lang="ts"> | |
75 | - import { defineComponent, reactive, nextTick } from 'vue'; | |
76 | - import { BasicTable, useTable, TableAction } from '/@/components/Table'; | |
77 | - import { PageWrapper } from '/@/components/Page'; | |
78 | - import { useDrawer } from '/@/components/Drawer'; | |
79 | - import ContactDrawer from './ConfigurationCenterDrawer.vue'; | |
80 | - import { useResetOrganizationTree, OrganizationIdTree } from '/@/views/common/organizationIdTree'; | |
81 | - import { searchFormSchema, columns } from './center.data'; | |
82 | - import { Platform } from '../center/center.data'; | |
83 | - import { | |
84 | - getPage, | |
85 | - deleteConfigurationCenter, | |
86 | - } from '/@/api/configuration/center/configurationCenter'; | |
87 | - import { useBatchDelete } from '/@/hooks/web/useBatchDelete'; | |
88 | - import { isDevMode } from '/@/utils/env'; | |
89 | - import { Authority } from '/@/components/Authority'; | |
90 | - import { Popconfirm } from 'ant-design-vue'; | |
91 | - import { Tag } from 'ant-design-vue'; | |
92 | - import { useGlobSetting } from '/@/hooks/setting'; | |
93 | - export default defineComponent({ | |
94 | - components: { | |
95 | - PageWrapper, | |
96 | - OrganizationIdTree, | |
97 | - BasicTable, | |
98 | - TableAction, | |
99 | - ContactDrawer, | |
100 | - Authority, | |
101 | - Popconfirm, | |
102 | - Tag, | |
103 | - }, | |
104 | - setup() { | |
105 | - const { configurationPrefix } = useGlobSetting(); | |
106 | - const isDev = isDevMode(); | |
107 | - const searchInfo = reactive<Recordable>({}); | |
108 | - const { organizationIdTreeRef, resetFn } = useResetOrganizationTree(searchInfo); | |
109 | - // 表格hooks | |
110 | - const [registerTable, { reload, setProps }] = useTable({ | |
111 | - title: '组态中心列表', | |
112 | - api: getPage, | |
113 | - columns, | |
114 | - clickToRowSelect: false, | |
115 | - formConfig: { | |
116 | - labelWidth: 120, | |
117 | - schemas: searchFormSchema, | |
118 | - resetFunc: resetFn, | |
119 | - }, | |
120 | - showIndexColumn: false, | |
121 | - useSearchForm: true, | |
122 | - showTableSetting: true, | |
123 | - bordered: true, | |
124 | - rowKey: 'id', | |
125 | - actionColumn: { | |
126 | - width: 200, | |
127 | - title: '操作', | |
128 | - dataIndex: 'action', | |
129 | - slots: { customRender: 'action' }, | |
130 | - fixed: 'right', | |
131 | - }, | |
132 | - }); | |
133 | - const { hasBatchDelete, handleDeleteOrBatchDelete, selectionOptions } = useBatchDelete( | |
134 | - deleteConfigurationCenter, | |
135 | - handleSuccess, | |
136 | - setProps | |
137 | - ); | |
138 | - nextTick(() => { | |
139 | - setProps(selectionOptions); | |
140 | - }); | |
141 | - | |
142 | - // 弹框 | |
143 | - const [registerDrawer, { openDrawer }] = useDrawer(); | |
144 | - | |
145 | - // 刷新 | |
146 | - function handleSuccess() { | |
147 | - reload(); | |
148 | - } | |
149 | - // 新增或编辑 | |
150 | - const handleCreateOrEdit = (record: Recordable | null) => { | |
151 | - if (record) { | |
152 | - openDrawer(true, { | |
153 | - isUpdate: true, | |
154 | - record, | |
155 | - }); | |
156 | - } else { | |
157 | - openDrawer(true, { | |
158 | - isUpdate: false, | |
159 | - }); | |
160 | - } | |
161 | - }; | |
162 | - // 树形选择器 | |
163 | - const handleSelect = (organizationId: string) => { | |
164 | - searchInfo.organizationId = organizationId; | |
165 | - handleSuccess(); | |
166 | - }; | |
167 | - | |
168 | - const handlePreview = (record: Recordable | null) => { | |
169 | - window.open( | |
170 | - `${configurationPrefix}/${isDev ? '?dev=1&' : '?'}configurationId=${ | |
171 | - record!.id | |
172 | - }&lightbox=1` | |
173 | - ); | |
174 | - }; | |
175 | - const handleDesign = (record: Recordable | null) => { | |
176 | - window.open( | |
177 | - `${configurationPrefix}/${isDev ? '?dev=1&' : '?'}configurationId=${record!.id}` | |
178 | - ); | |
179 | - }; | |
180 | - | |
181 | - return { | |
182 | - Platform, | |
183 | - searchInfo, | |
184 | - hasBatchDelete, | |
185 | - handleCreateOrEdit, | |
186 | - handleDeleteOrBatchDelete, | |
187 | - handleSelect, | |
188 | - handleSuccess, | |
189 | - handlePreview, | |
190 | - handleDesign, | |
191 | - registerTable, | |
192 | - registerDrawer, | |
193 | - organizationIdTreeRef, | |
194 | - }; | |
195 | - }, | |
196 | - }); | |
197 | -</script> |
src/views/configuration/template/help.ts
deleted
100644 → 0
1 | -import { Platform } from './center.data'; | |
2 | -import { ConfigurationCenterItemsModal } from '/@/api/configuration/center/model/configurationCenterModal'; | |
3 | -import { useGlobSetting } from '/@/hooks/setting'; | |
4 | - | |
5 | -export enum ScadaModeEnum { | |
6 | - LIGHTBOX = 'lightbox', | |
7 | - DESIGN = 'design', | |
8 | - SHARE = 'share', | |
9 | -} | |
10 | - | |
11 | -interface ScadaLinkParamsType { | |
12 | - configurationId: string; | |
13 | - organizationId: string; | |
14 | - mode: ScadaModeEnum; | |
15 | - platform: Platform; | |
16 | - publicId?: string; | |
17 | -} | |
18 | - | |
19 | -const getRandomString = () => Number(Math.random().toString().substring(2)).toString(36); | |
20 | - | |
21 | -export const encode = (record: Recordable) => { | |
22 | - let hash = JSON.stringify(record); | |
23 | - const mixinString = getRandomString() | |
24 | - .slice(0, 10) | |
25 | - .padEnd(10, getRandomString()) | |
26 | - .split('') | |
27 | - .map((item) => (Math.random() > 0.5 ? item.toUpperCase() : item)) | |
28 | - .join(''); | |
29 | - hash = window.btoa(hash); | |
30 | - hash = hash.substring(0, 6) + mixinString + hash.substring(6); | |
31 | - hash = window.btoa(hash); | |
32 | - return hash; | |
33 | -}; | |
34 | - | |
35 | -export const createScadaPageLink = ( | |
36 | - record: ConfigurationCenterItemsModal, | |
37 | - mode: ScadaModeEnum = ScadaModeEnum.DESIGN, | |
38 | - open = true | |
39 | -) => { | |
40 | - const { configurationPrefix } = useGlobSetting(); | |
41 | - const params: ScadaLinkParamsType = { | |
42 | - configurationId: record.id, | |
43 | - organizationId: record.organizationId!, | |
44 | - mode: mode, | |
45 | - platform: record.platform as Platform, | |
46 | - }; | |
47 | - | |
48 | - if (mode === ScadaModeEnum.SHARE) { | |
49 | - params.publicId = record.publicId; | |
50 | - } | |
51 | - | |
52 | - const href = new URL(location.origin); | |
53 | - href.pathname = configurationPrefix; | |
54 | - href.hash = encode(params); | |
55 | - open && window.open(href.href); | |
56 | - return href.href; | |
57 | -}; |
1 | 1 | <script setup lang="ts"> |
2 | - import { List, Card, Button, PaginationProps, Tooltip } from 'ant-design-vue'; | |
3 | - import { ReloadOutlined } from '@ant-design/icons-vue'; | |
4 | - import { computed, onMounted, reactive, ref, unref } from 'vue'; | |
5 | - import { OrganizationIdTree, useResetOrganizationTree } from '../../common/organizationIdTree'; | |
2 | + import { BasicCardList, useCardList } from '/@/components/CardList'; | |
6 | 3 | import { |
7 | 4 | deleteConfigurationCenter, |
8 | 5 | getPage, |
9 | 6 | } from '/@/api/configuration/center/configurationCenter'; |
10 | - import { ConfigurationCenterItemsModal } from '/@/api/configuration/center/model/configurationCenterModal'; | |
11 | - import { PageWrapper } from '/@/components/Page'; | |
12 | - import { BasicForm, useForm } from '/@/components/Form'; | |
13 | 7 | import { searchFormSchema, ConfigurationTemplatePermission } from './center.data'; |
14 | - import { useMessage } from '/@/hooks/web/useMessage'; | |
8 | + import { ConfigurationCenterItemsModal } from '/@/api/configuration/center/model/configurationCenterModal'; | |
15 | 9 | import { Authority } from '/@/components/Authority'; |
16 | - import ConfigurationCenterDrawer from './ConfigurationCenterDrawer.vue'; | |
17 | - import { useDrawer } from '/@/components/Drawer'; | |
18 | - import { getBoundingClientRect } from '/@/utils/domUtils'; | |
10 | + import { Button, Card, Tooltip } from 'ant-design-vue'; | |
11 | + import { useRole } from '/@/hooks/business/useRole'; | |
19 | 12 | import configurationSrc from '/@/assets/icons/configuration.svg'; |
20 | - import { cloneDeep } from 'lodash'; | |
13 | + import { Platform } from '../center/center.data'; | |
14 | + import { computed, unref } from 'vue'; | |
15 | + import { createScadaPageLink, ScadaModeEnum } from '../center/help'; | |
16 | + import { useDrawer } from '/@/components/Drawer'; | |
21 | 17 | import { usePermission } from '/@/hooks/web/usePermission'; |
22 | - import { AuthIcon, CardLayoutButton } from '/@/components/Widget'; | |
23 | - import AuthDropDown from '/@/components/Widget/AuthDropDown.vue'; | |
24 | - import { useRole } from '/@/hooks/business/useRole'; | |
18 | + import { useMessage } from '/@/hooks/web/useMessage'; | |
25 | 19 | import { Icon } from '/@/components/Icon'; |
26 | - import { createScadaPageLink, ScadaModeEnum } from './help'; | |
27 | - import { Platform } from '../center/center.data'; | |
28 | - | |
29 | - const listColumn = ref(5); | |
30 | - | |
31 | - const { createMessage } = useMessage(); | |
32 | - | |
33 | - const { isCustomerUser } = useRole(); | |
34 | - | |
35 | - const organizationId = ref<Nullable<number>>(null); | |
20 | + import { AuthIcon, AuthDropDown } from '/@/components/Widget'; | |
21 | + import { cloneDeep } from 'lodash-es'; | |
22 | + import { OrganizationIdTree, useOrganizationTree } from '../../common/organizationIdTree'; | |
23 | + import ConfigurationCenterDrawer from './ConfigurationCenterDrawer.vue'; | |
36 | 24 | |
37 | - const pagination = reactive<PaginationProps>({ | |
38 | - size: 'small', | |
39 | - showTotal: (total: number) => `共 ${total} 条数据`, | |
40 | - current: 1, | |
41 | - pageSize: unref(listColumn) * 2, | |
42 | - onChange: (page: number) => { | |
43 | - pagination.current = page; | |
44 | - getListData(); | |
25 | + const [register, { reload }] = useCardList({ | |
26 | + api: getPage, | |
27 | + useSearchForm: true, | |
28 | + title: '模版列表', | |
29 | + gutter: 4, | |
30 | + formConfig: { | |
31 | + schemas: searchFormSchema, | |
32 | + labelWidth: 80, | |
33 | + resetFunc: async () => { | |
34 | + clearSelected(); | |
35 | + }, | |
36 | + }, | |
37 | + beforeFetch: async (params: Recordable) => { | |
38 | + return { ...params, organizationId: getSelectKey(), isTemplate: 1 }; | |
45 | 39 | }, |
46 | 40 | }); |
47 | 41 | |
48 | - const loading = ref(false); | |
49 | - | |
50 | - const dataSource = ref<ConfigurationCenterItemsModal[]>([]); | |
51 | - | |
52 | - const [registerForm, { getFieldsValue }] = useForm({ | |
53 | - schemas: searchFormSchema, | |
54 | - showAdvancedButton: true, | |
55 | - labelWidth: 100, | |
56 | - compact: true, | |
57 | - resetFunc: () => { | |
58 | - resetFn(); | |
59 | - organizationId.value = null; | |
60 | - return getListData(); | |
61 | - }, | |
62 | - submitFunc: async () => { | |
63 | - const value = getFieldsValue(); | |
64 | - getListData(value); | |
42 | + const [registerOrgTree, { getSelectKey, clearSelected }] = useOrganizationTree({ | |
43 | + onSelect: () => { | |
44 | + reload(); | |
65 | 45 | }, |
66 | 46 | }); |
67 | 47 | |
68 | - async function getListData(value: Recordable = {}) { | |
69 | - try { | |
70 | - loading.value = true; | |
71 | - const pageSize = unref(listColumn) * 2; | |
72 | - const { items, total } = await getPage({ | |
73 | - organizationId: unref(organizationId), | |
74 | - ...value, | |
75 | - page: pagination.current!, | |
76 | - pageSize, | |
77 | - isTemplate: 1, | |
78 | - }); | |
79 | - | |
80 | - dataSource.value = items; | |
81 | - Object.assign(pagination, { total, pageSize }); | |
82 | - } catch (error) { | |
83 | - } finally { | |
84 | - loading.value = false; | |
85 | - } | |
86 | - } | |
87 | - | |
88 | - onMounted(() => { | |
89 | - getListData(); | |
90 | - }); | |
48 | + const [registerDrawer, { openDrawer }] = useDrawer(); | |
91 | 49 | |
92 | - const searchInfo = reactive<Recordable>({}); | |
93 | - const { organizationIdTreeRef, resetFn } = useResetOrganizationTree(searchInfo); | |
94 | - const handleSelect = (orgId: number) => { | |
95 | - organizationId.value = orgId; | |
96 | - getListData(); | |
97 | - }; | |
50 | + const { createMessage } = useMessage(); | |
98 | 51 | |
99 | - const [registerDrawer, { openDrawer }] = useDrawer(); | |
52 | + const { isCustomerUser } = useRole(); | |
100 | 53 | |
101 | 54 | const { hasPermission } = usePermission(); |
102 | 55 | |
... | ... | @@ -136,191 +89,106 @@ |
136 | 89 | try { |
137 | 90 | await deleteConfigurationCenter([record.id]); |
138 | 91 | createMessage.success('删除成功'); |
139 | - await getListData(); | |
92 | + await reload(); | |
140 | 93 | } catch (error) {} |
141 | 94 | }; |
142 | - | |
143 | - const handleCardLayoutChange = () => { | |
144 | - pagination.current = 1; | |
145 | - getListData(); | |
146 | - }; | |
147 | - | |
148 | - const listEl = ref<Nullable<ComponentElRef>>(null); | |
149 | - | |
150 | - onMounted(() => { | |
151 | - const clientHeight = document.documentElement.clientHeight; | |
152 | - const rect = getBoundingClientRect(unref(listEl)!.$el! as HTMLElement) as DOMRect; | |
153 | - // margin-top 24 height 24 | |
154 | - const paginationHeight = 24 + 24 + 8; | |
155 | - // list pading top 8 maring-top 8 extra slot 56 | |
156 | - const listContainerMarginBottom = 8 + 8 + 56; | |
157 | - const listContainerHeight = | |
158 | - clientHeight - rect.top - paginationHeight - listContainerMarginBottom; | |
159 | - const listContainerEl = (unref(listEl)!.$el as HTMLElement).querySelector( | |
160 | - '.ant-spin-container' | |
161 | - ) as HTMLElement; | |
162 | - listContainerEl && | |
163 | - (listContainerEl.style.height = listContainerHeight + 'px') && | |
164 | - (listContainerEl.style.overflowY = 'auto') && | |
165 | - (listContainerEl.style.overflowX = 'hidden'); | |
166 | - }); | |
167 | 95 | </script> |
168 | 96 | |
169 | 97 | <template> |
170 | - <PageWrapper dense contentFullHeight contentClass="flex"> | |
171 | - <OrganizationIdTree @select="handleSelect" ref="organizationIdTreeRef" /> | |
172 | - <section class="flex-auto p-4 w-3/4 xl:w-4/5 w-full configuration-list"> | |
173 | - <div class="flex-auto w-full bg-light-50 dark:bg-dark-900 p-4"> | |
174 | - <BasicForm @register="registerForm" /> | |
175 | - </div> | |
176 | - <List | |
177 | - ref="listEl" | |
178 | - :loading="loading" | |
179 | - class="flex-auto bg-light-50 dark:bg-dark-900 !p-2 !mt-4" | |
180 | - position="bottom" | |
181 | - :pagination="pagination" | |
182 | - :data-source="dataSource" | |
183 | - :grid="{ gutter: 4, column: listColumn }" | |
184 | - > | |
185 | - <template #header> | |
186 | - <div class="flex gap-3 justify-end"> | |
187 | - <Authority v-if="!isCustomerUser" :value="ConfigurationTemplatePermission.CREATE"> | |
188 | - <Button type="primary" @click="handleCreateOrUpdate()">新增模板</Button> | |
189 | - </Authority> | |
190 | - <CardLayoutButton v-model:value="listColumn" @change="handleCardLayoutChange" /> | |
191 | - <Tooltip title="刷新"> | |
192 | - <Button type="primary" @click="getListData"> | |
193 | - <ReloadOutlined /> | |
194 | - </Button> | |
195 | - </Tooltip> | |
196 | - </div> | |
197 | - </template> | |
198 | - <template #renderItem="{ item }"> | |
199 | - <List.Item> | |
200 | - <Card | |
201 | - :style="{ | |
202 | - '--viewType': '#1890ff', | |
203 | - }" | |
204 | - hoverable | |
205 | - class="card-container" | |
98 | + <section class="flex w-full h-full"> | |
99 | + <OrganizationIdTree @register="registerOrgTree" /> | |
100 | + <BasicCardList class="flex-auto p-4 w-3/4 xl:w-4/5 w-full" @register="register"> | |
101 | + <template #toolbar> | |
102 | + <div class="flex gap-3 justify-end"> | |
103 | + <Authority v-if="!isCustomerUser" :value="ConfigurationTemplatePermission.CREATE"> | |
104 | + <Button type="primary" @click="handleCreateOrUpdate()">新增模版</Button> | |
105 | + </Authority> | |
106 | + </div> | |
107 | + </template> | |
108 | + <template #renderItem="{ item }: CardListRenderItem<ConfigurationCenterItemsModal>"> | |
109 | + <Card | |
110 | + :style="{ | |
111 | + '--viewType': '#1890ff', | |
112 | + }" | |
113 | + hoverable | |
114 | + class="card-container" | |
115 | + > | |
116 | + <template #cover> | |
117 | + <div | |
118 | + class="img-container h-full w-full !flex justify-center items-center text-center p-1 relative" | |
206 | 119 | > |
207 | - <template #cover> | |
208 | - <div | |
209 | - class="img-container h-full w-full !flex justify-center items-center text-center p-1 relative" | |
210 | - > | |
211 | - <img | |
212 | - class="w-full h-36" | |
213 | - alt="example" | |
214 | - :src="item.thumbnail || configurationSrc" | |
215 | - @click="handlePreview(item)" | |
120 | + <img | |
121 | + class="w-full max-h-32 h-32" | |
122 | + alt="example" | |
123 | + :src="item.thumbnail || configurationSrc" | |
124 | + @click="handlePreview(item)" | |
125 | + /> | |
126 | + </div> | |
127 | + </template> | |
128 | + <template class="ant-card-actions" #actions> | |
129 | + <Tooltip title="预览"> | |
130 | + <AuthIcon | |
131 | + :auth="ConfigurationTemplatePermission.PREVIEW" | |
132 | + class="!text-lg" | |
133 | + icon="ant-design:eye-outlined" | |
134 | + @click="handlePreview(item)" | |
135 | + /> | |
136 | + </Tooltip> | |
137 | + <Tooltip v-if="!isCustomerUser" title="设计"> | |
138 | + <AuthIcon | |
139 | + :auth="ConfigurationTemplatePermission.DESIGN" | |
140 | + class="!text-lg" | |
141 | + icon="ant-design:edit-outlined" | |
142 | + @click="handleDesign(item)" | |
143 | + /> | |
144 | + </Tooltip> | |
145 | + <AuthDropDown | |
146 | + v-if="!isCustomerUser" | |
147 | + :dropMenuList="[ | |
148 | + { | |
149 | + text: '编辑', | |
150 | + auth: ConfigurationTemplatePermission.UPDATE, | |
151 | + icon: 'clarity:note-edit-line', | |
152 | + event: '', | |
153 | + onClick: handleCreateOrUpdate.bind(null, item), | |
154 | + }, | |
155 | + { | |
156 | + text: '删除', | |
157 | + auth: ConfigurationTemplatePermission.DELETE, | |
158 | + icon: 'ant-design:delete-outlined', | |
159 | + event: '', | |
160 | + popconfirm: { | |
161 | + title: '是否确认删除操作?', | |
162 | + onConfirm: handleDelete.bind(null, item), | |
163 | + }, | |
164 | + }, | |
165 | + ]" | |
166 | + :trigger="['hover']" | |
167 | + /> | |
168 | + </template> | |
169 | + <Card.Meta> | |
170 | + <template #title> | |
171 | + <span class="truncate">{{ item.name }}</span> | |
172 | + </template> | |
173 | + <template #description> | |
174 | + <div class="truncate h-11"> | |
175 | + <div class="truncate flex justify-between items-center"> | |
176 | + <div>{{ item.organizationDTO?.name }}</div> | |
177 | + <Icon | |
178 | + :icon=" | |
179 | + item.platform === Platform.PC | |
180 | + ? 'ri:computer-line' | |
181 | + : 'clarity:mobile-phone-solid' | |
182 | + " | |
216 | 183 | /> |
217 | - <span | |
218 | - class="absolute top-0 left-0 text-light-50 transform -rotate-45 translate-y-1" | |
219 | - > | |
220 | - 母版 | |
221 | - </span> | |
222 | 184 | </div> |
223 | - </template> | |
224 | - <template class="ant-card-actions" #actions> | |
225 | - <Tooltip title="预览"> | |
226 | - <AuthIcon | |
227 | - :auth="ConfigurationTemplatePermission.PREVIEW" | |
228 | - class="!text-lg" | |
229 | - icon="ant-design:eye-outlined" | |
230 | - @click="handlePreview(item)" | |
231 | - /> | |
232 | - </Tooltip> | |
233 | - <Tooltip v-if="!isCustomerUser" title="设计"> | |
234 | - <AuthIcon | |
235 | - :auth="ConfigurationTemplatePermission.DESIGN" | |
236 | - class="!text-lg" | |
237 | - icon="ant-design:edit-outlined" | |
238 | - @click="handleDesign(item)" | |
239 | - /> | |
240 | - </Tooltip> | |
241 | - <AuthDropDown | |
242 | - v-if="!isCustomerUser" | |
243 | - :dropMenuList="[ | |
244 | - { | |
245 | - text: '编辑', | |
246 | - auth: ConfigurationTemplatePermission.UPDATE, | |
247 | - icon: 'clarity:note-edit-line', | |
248 | - event: '', | |
249 | - onClick: handleCreateOrUpdate.bind(null, item), | |
250 | - }, | |
251 | - { | |
252 | - text: '删除', | |
253 | - auth: ConfigurationTemplatePermission.DELETE, | |
254 | - icon: 'ant-design:delete-outlined', | |
255 | - event: '', | |
256 | - popconfirm: { | |
257 | - title: '是否确认删除操作?', | |
258 | - onConfirm: handleDelete.bind(null, item), | |
259 | - }, | |
260 | - }, | |
261 | - ]" | |
262 | - :trigger="['hover']" | |
263 | - /> | |
264 | - </template> | |
265 | - <Card.Meta> | |
266 | - <template #title> | |
267 | - <span class="truncate">{{ item.name }}</span> | |
268 | - </template> | |
269 | - <template #description> | |
270 | - <div class="truncate h-11"> | |
271 | - <div class="truncate flex justify-between items-center"> | |
272 | - <div>{{ item.organizationDTO?.name }}</div> | |
273 | - <Icon | |
274 | - :icon=" | |
275 | - item.platform === Platform.PC | |
276 | - ? 'ri:computer-line' | |
277 | - : 'clarity:mobile-phone-solid' | |
278 | - " | |
279 | - /> | |
280 | - </div> | |
281 | - <div class="truncate">{{ item.remark || '' }} </div> | |
282 | - </div> | |
283 | - </template> | |
284 | - </Card.Meta> | |
285 | - </Card> | |
286 | - </List.Item> | |
287 | - </template> | |
288 | - </List> | |
289 | - </section> | |
290 | - <ConfigurationCenterDrawer @register="registerDrawer" @success="getListData" /> | |
291 | - </PageWrapper> | |
185 | + <div class="truncate">{{ item.remark || '' }} </div> | |
186 | + </div> | |
187 | + </template> | |
188 | + </Card.Meta> | |
189 | + </Card> | |
190 | + </template> | |
191 | + </BasicCardList> | |
192 | + <ConfigurationCenterDrawer @register="registerDrawer" @success="reload()" /> | |
193 | + </section> | |
292 | 194 | </template> |
293 | - | |
294 | -<style lang="less" scoped> | |
295 | - .configuration-list:deep(.ant-list-header) { | |
296 | - border-bottom: none !important; | |
297 | - } | |
298 | - | |
299 | - .configuration-list:deep(.ant-list-pagination) { | |
300 | - height: 24px; | |
301 | - } | |
302 | - | |
303 | - .configuration-list:deep(.ant-card-body) { | |
304 | - padding: 16px !important; | |
305 | - } | |
306 | - | |
307 | - .configuration-list:deep(.ant-list-empty-text) { | |
308 | - @apply w-full h-full flex justify-center items-center; | |
309 | - } | |
310 | - | |
311 | - .card-container { | |
312 | - // background-color: red; | |
313 | - .img-container { | |
314 | - border-top-left-radius: 80px; | |
315 | - background-color: #fff; | |
316 | - | |
317 | - img { | |
318 | - border-top-left-radius: 80px; | |
319 | - } | |
320 | - } | |
321 | - } | |
322 | - | |
323 | - .card-container:deep(.ant-card-cover) { | |
324 | - background-color: var(--viewType); | |
325 | - } | |
326 | -</style> | ... | ... |
1 | 1 | <script setup lang="ts"> |
2 | - import { List, Card, Button, PaginationProps, Tooltip } from 'ant-design-vue'; | |
3 | - import { ReloadOutlined } from '@ant-design/icons-vue'; | |
4 | - import { computed, onMounted, reactive, ref, unref } from 'vue'; | |
5 | - import { OrganizationIdTree, useResetOrganizationTree } from '../common/organizationIdTree'; | |
2 | + import { Card, Button, Tooltip } from 'ant-design-vue'; | |
3 | + import { computed, unref } from 'vue'; | |
4 | + import { OrganizationIdTree, useOrganizationTree } from '../common/organizationIdTree'; | |
6 | 5 | import { |
7 | 6 | bigScreenCancelPublish, |
8 | 7 | bigScreenPublish, |
... | ... | @@ -12,17 +11,15 @@ |
12 | 11 | } from '/@/api/bigscreen/center/bigscreenCenter'; |
13 | 12 | import { BigScreenCenterItemsModel } from '/@/api/bigscreen/center/model/bigscreenCenterModel'; |
14 | 13 | import { PageWrapper } from '/@/components/Page'; |
15 | - import { BasicForm, useForm } from '/@/components/Form'; | |
16 | 14 | import { ConfigurationPermission, searchFormSchema } from './config'; |
17 | 15 | import { useMessage } from '/@/hooks/web/useMessage'; |
18 | 16 | import { Authority } from '/@/components/Authority'; |
19 | 17 | import ConfigurationCenterDrawer from './BigScreenDrawer.vue'; |
20 | 18 | import { useDrawer } from '/@/components/Drawer'; |
21 | - import { getBoundingClientRect } from '/@/utils/domUtils'; | |
22 | 19 | import configurationSrc from '/@/assets/icons/configuration.svg'; |
23 | 20 | import { cloneDeep } from 'lodash'; |
24 | 21 | import { useGlobSetting } from '/@/hooks/setting'; |
25 | - import { AuthIcon, CardLayoutButton } from '/@/components/Widget'; | |
22 | + import { AuthIcon } from '/@/components/Widget'; | |
26 | 23 | import AuthDropDown from '/@/components/Widget/AuthDropDown.vue'; |
27 | 24 | import { PublicApiDrawer } from './publicApi/index'; |
28 | 25 | import { useModal } from '/@/components/Modal'; |
... | ... | @@ -33,79 +30,37 @@ |
33 | 30 | import { RoleEnum } from '/@/enums/roleEnum'; |
34 | 31 | import { useRole } from '/@/hooks/business/useRole'; |
35 | 32 | import { useClipboard } from '@vueuse/core'; |
33 | + import { BasicCardList, useCardList } from '/@/components/CardList'; | |
36 | 34 | |
37 | - const listColumn = ref(5); | |
38 | - | |
39 | - const { createMessage } = useMessage(); | |
40 | - | |
41 | - const organizationId = ref<Nullable<number>>(null); | |
42 | - | |
43 | - const pagination = reactive<PaginationProps>({ | |
44 | - size: 'small', | |
45 | - showTotal: (total: number) => `共 ${total} 条数据`, | |
46 | - current: 1, | |
47 | - pageSize: unref(listColumn) * 2, | |
48 | - onChange: (page: number) => { | |
49 | - pagination.current = page; | |
50 | - getListData(); | |
35 | + const [registerOrgTree, { getSelectKey, clearSelected }] = useOrganizationTree({ | |
36 | + onSelect: () => { | |
37 | + reload(); | |
51 | 38 | }, |
52 | 39 | }); |
53 | 40 | |
54 | - const loading = ref(false); | |
55 | - const { isCustomerUser } = useRole(); | |
56 | - | |
57 | - const dataSource = ref<BigScreenCenterItemsModel[]>([]); | |
58 | - | |
59 | - const [registerForm, { getFieldsValue }] = useForm({ | |
60 | - schemas: searchFormSchema, | |
61 | - showAdvancedButton: true, | |
62 | - labelWidth: 100, | |
63 | - compact: true, | |
64 | - resetFunc: () => { | |
65 | - resetFn(); | |
66 | - organizationId.value = null; | |
67 | - return getListData(); | |
41 | + const [registerCardList, { reload }] = useCardList({ | |
42 | + api: getPage, | |
43 | + title: '数据大屏', | |
44 | + useSearchForm: true, | |
45 | + gutter: 4, | |
46 | + formConfig: { | |
47 | + labelWidth: 80, | |
48 | + schemas: searchFormSchema, | |
49 | + resetFunc: async () => clearSelected(), | |
68 | 50 | }, |
69 | - submitFunc: async () => { | |
70 | - const value = getFieldsValue(); | |
71 | - getListData(value); | |
51 | + beforeFetch: async (params: Recordable) => { | |
52 | + return { ...params, organizationId: getSelectKey() }; | |
72 | 53 | }, |
73 | 54 | }); |
74 | 55 | |
75 | - async function getListData(value: Recordable = {}) { | |
76 | - try { | |
77 | - loading.value = true; | |
78 | - const pageSize = unref(listColumn) * 2; | |
79 | - const { items, total } = await getPage({ | |
80 | - organizationId: unref(organizationId), | |
81 | - ...value, | |
82 | - page: pagination.current!, | |
83 | - pageSize, | |
84 | - }); | |
85 | - | |
86 | - dataSource.value = items; | |
87 | - Object.assign(pagination, { total, pageSize }); | |
88 | - } catch (error) { | |
89 | - } finally { | |
90 | - loading.value = false; | |
91 | - } | |
92 | - } | |
93 | - | |
94 | - onMounted(() => { | |
95 | - getListData(); | |
96 | - }); | |
97 | - | |
98 | - const searchInfo = reactive<Recordable>({}); | |
99 | - const { organizationIdTreeRef, resetFn } = useResetOrganizationTree(searchInfo); | |
100 | - const handleSelect = (orgId: number) => { | |
101 | - organizationId.value = orgId; | |
102 | - getListData(); | |
103 | - }; | |
104 | - | |
105 | 56 | const [registerDrawer, { openDrawer }] = useDrawer(); |
106 | 57 | |
107 | 58 | const [registerPublicDrawer, { openDrawer: openPublicApiDrawer }] = useDrawer(); |
108 | 59 | |
60 | + const { createMessage } = useMessage(); | |
61 | + | |
62 | + const { isCustomerUser } = useRole(); | |
63 | + | |
109 | 64 | const handleCreateOrUpdate = (record?: BigScreenCenterItemsModel) => { |
110 | 65 | if (record) { |
111 | 66 | openDrawer(true, { |
... | ... | @@ -140,37 +95,10 @@ |
140 | 95 | try { |
141 | 96 | await deleteBigScreenenter([record.id]); |
142 | 97 | createMessage.success('删除成功'); |
143 | - await getListData(); | |
98 | + reload(); | |
144 | 99 | } catch (error) {} |
145 | 100 | }; |
146 | 101 | |
147 | - const handleCardLayoutChange = () => { | |
148 | - pagination.current = 1; | |
149 | - getListData(); | |
150 | - }; | |
151 | - | |
152 | - const listEl = ref<Nullable<ComponentElRef>>(null); | |
153 | - | |
154 | - onMounted(() => { | |
155 | - const clientHeight = document.documentElement.clientHeight; | |
156 | - const rect = getBoundingClientRect(unref(listEl)!.$el!) as DOMRect; | |
157 | - // margin-top 24 height 24 | |
158 | - const paginationHeight = 24 + 24 + 8; | |
159 | - // list pading top 8 maring-top 8 extra slot 56 | |
160 | - const listContainerMarginBottom = 8 + 8 + 56; | |
161 | - const listContainerHeight = | |
162 | - clientHeight - rect.top - paginationHeight - listContainerMarginBottom; | |
163 | - const listContainerEl = (unref(listEl)!.$el as HTMLElement).querySelector( | |
164 | - '.ant-spin-container' | |
165 | - ) as HTMLElement; | |
166 | - listContainerEl && | |
167 | - (listContainerEl.style.height = listContainerHeight + 'px') && | |
168 | - (listContainerEl.style.overflowY = 'auto') && | |
169 | - (listContainerEl.style.overflowX = 'hidden'); | |
170 | - }); | |
171 | - | |
172 | - const getPublicApiListData = () => {}; | |
173 | - | |
174 | 102 | const [registerShareModal, { openModal }] = useModal(); |
175 | 103 | const handleOpenShareModal = (record: BigScreenCenterItemsModel) => { |
176 | 104 | openModal(true, { record, href: createShareUrl(record) }); |
... | ... | @@ -182,6 +110,7 @@ |
182 | 110 | }; |
183 | 111 | |
184 | 112 | const userStore = useUserStore(); |
113 | + | |
185 | 114 | const hasPublicInterfacePermission = computed(() => { |
186 | 115 | return userStore.getUserInfo.roles![0] !== RoleEnum.CUSTOMER_USER; |
187 | 116 | }); |
... | ... | @@ -197,166 +126,143 @@ |
197 | 126 | const handlePublish = async ({ id, state }) => { |
198 | 127 | state === 0 ? await bigScreenPublish(id) : await bigScreenCancelPublish(id); |
199 | 128 | createMessage.success(state === 0 ? '发布成功' : '取消发布成功'); |
200 | - getListData(); | |
129 | + reload(); | |
201 | 130 | }; |
202 | 131 | </script> |
203 | 132 | |
204 | 133 | <template> |
205 | 134 | <PageWrapper dense contentFullHeight contentClass="flex"> |
206 | - <OrganizationIdTree @select="handleSelect" ref="organizationIdTreeRef" /> | |
207 | - <section class="flex-auto p-4 w-3/4 xl:w-4/5 w-full configuration-list"> | |
208 | - <div class="flex-auto w-full bg-light-50 dark:bg-dark-900 p-4"> | |
209 | - <BasicForm @register="registerForm" /> | |
210 | - </div> | |
211 | - <List | |
212 | - ref="listEl" | |
213 | - :loading="loading" | |
214 | - class="flex-auto bg-light-50 dark:bg-dark-900 !p-2 !mt-4" | |
215 | - position="bottom" | |
216 | - :pagination="pagination" | |
217 | - :data-source="dataSource" | |
218 | - :grid="{ gutter: 4, column: listColumn }" | |
219 | - > | |
220 | - <template #header> | |
221 | - <div class="flex gap-3 justify-end"> | |
222 | - <Authority v-if="!isCustomerUser" :value="ConfigurationPermission.CREATE"> | |
223 | - <Button type="primary" @click="handleCreateOrUpdate()"> 新增大屏 </Button> | |
224 | - </Authority> | |
225 | - <Authority | |
226 | - v-if="hasPublicInterfacePermission" | |
227 | - :value="ConfigurationPermission.PUBLISH_INTERFACE" | |
228 | - > | |
229 | - <Button type="primary" @click="handleCreateOrUpdatePublicApi()">公共接口管理</Button> | |
230 | - </Authority> | |
231 | - <CardLayoutButton v-model:value="listColumn" @change="handleCardLayoutChange" /> | |
232 | - <Tooltip title="刷新"> | |
233 | - <Button type="primary" @click="getListData"> | |
234 | - <ReloadOutlined /> | |
235 | - </Button> | |
236 | - </Tooltip> | |
237 | - </div> | |
238 | - </template> | |
239 | - <template #renderItem="{ item }"> | |
240 | - <List.Item> | |
241 | - <Card | |
242 | - :style="{ | |
243 | - '--viewType': item.viewType === ViewType.PUBLIC_VIEW ? '#1890ff' : '#faad14', | |
244 | - }" | |
245 | - class="card-container" | |
246 | - > | |
247 | - <template #cover> | |
248 | - <div class="h-full w-full relative hover-show-modal-content img-container"> | |
249 | - <img | |
250 | - style="position: relative" | |
251 | - class="w-full h-45 hover-show-modal" | |
252 | - alt="example" | |
253 | - :src="item.thumbnail || configurationSrc" | |
254 | - @click="handlePreview(item)" | |
255 | - /> | |
256 | - <span | |
257 | - class="absolute top-0 left-0 text-light-50 transform -rotate-45 translate-y-1" | |
135 | + <OrganizationIdTree @register="registerOrgTree" /> | |
136 | + <BasicCardList | |
137 | + @register="registerCardList" | |
138 | + class="flex-auto p-4 w-3/4 xl:w-4/5 w-full configuration-list" | |
139 | + > | |
140 | + <template #toolbar> | |
141 | + <section class="flex gap-4"> | |
142 | + <Authority v-if="!isCustomerUser" :value="ConfigurationPermission.CREATE"> | |
143 | + <Button type="primary" @click="handleCreateOrUpdate()"> 新增大屏 </Button> | |
144 | + </Authority> | |
145 | + <Authority | |
146 | + v-if="hasPublicInterfacePermission" | |
147 | + :value="ConfigurationPermission.PUBLISH_INTERFACE" | |
148 | + > | |
149 | + <Button type="primary" @click="handleCreateOrUpdatePublicApi()">公共接口管理</Button> | |
150 | + </Authority> | |
151 | + </section> | |
152 | + </template> | |
153 | + <template #renderItem="{ item }: CardListRenderItem<BigScreenCenterItemsModel>"> | |
154 | + <Card | |
155 | + :style="{ | |
156 | + '--viewType': item.viewType === ViewType.PUBLIC_VIEW ? '#1890ff' : '#faad14', | |
157 | + }" | |
158 | + class="card-container" | |
159 | + > | |
160 | + <template #cover> | |
161 | + <div class="h-full w-full relative hover-show-modal-content img-container"> | |
162 | + <img | |
163 | + style="position: relative" | |
164 | + class="w-full h-45 hover-show-modal" | |
165 | + alt="example" | |
166 | + :src="item.thumbnail || configurationSrc" | |
167 | + @click="handlePreview(item)" | |
168 | + /> | |
169 | + <span class="absolute top-0 left-0 text-light-50 transform -rotate-45 translate-y-1"> | |
170 | + {{ | |
171 | + item.viewType ? ViewTypeNameEnum[item.viewType] : ViewTypeNameEnum.PRIVATE_VIEW | |
172 | + }} | |
173 | + </span> | |
174 | + <div class="masker-content"> | |
175 | + <div class="masker-text"> | |
176 | + <div | |
177 | + ><span>{{ item.name }}</span></div | |
258 | 178 | > |
259 | - {{ ViewTypeNameEnum[item.viewType] || ViewTypeNameEnum.PRIVATE_VIEW }} | |
260 | - </span> | |
261 | - <div class="masker-content"> | |
262 | - <div class="masker-text"> | |
263 | - <div | |
264 | - ><span>{{ item.name }}</span></div | |
265 | - > | |
266 | - <div> | |
267 | - <span class="masker-text-org" | |
268 | - >所属组织:{{ item?.organizationDTO?.name }}</span | |
269 | - > | |
270 | - </div> | |
271 | - <div> | |
272 | - <span class="masker-text-state" | |
273 | - >发布状态:{{ item.state === 1 ? '已发布' : '未发布' }}</span | |
274 | - > | |
275 | - </div> | |
276 | - </div> | |
179 | + <div> | |
180 | + <span class="masker-text-org">所属组织:{{ item?.organizationDTO?.name }}</span> | |
181 | + </div> | |
182 | + <div> | |
183 | + <span class="masker-text-state" | |
184 | + >发布状态:{{ item.state === 1 ? '已发布' : '未发布' }}</span | |
185 | + > | |
277 | 186 | </div> |
278 | 187 | </div> |
279 | - </template> | |
280 | - <template class="ant-card-actions" #actions> | |
281 | - <Tooltip title="预览"> | |
282 | - <AuthIcon | |
283 | - class="!text-lg" | |
284 | - icon="ant-design:eye-outlined" | |
285 | - @click="handlePreview(item)" | |
286 | - /> | |
287 | - </Tooltip> | |
288 | - <Tooltip v-if="!isCustomerUser" title="设计"> | |
289 | - <AuthIcon | |
290 | - :auth="ConfigurationPermission.DESIGN" | |
291 | - :disabled="item.state === 1" | |
292 | - icon="ant-design:edit-outlined" | |
293 | - @click="handleDesign(item)" | |
294 | - /> | |
295 | - </Tooltip> | |
296 | - <Tooltip title="点击复制分享链接"> | |
297 | - <AuthIcon | |
298 | - :auth="ConfigurationPermission.SHARE" | |
299 | - :disabled="!item.publicId" | |
300 | - class="!text-lg" | |
301 | - icon="ant-design:share-alt-outlined" | |
302 | - @click="handleCreateShareUrl(item)" | |
303 | - /> | |
304 | - </Tooltip> | |
305 | - <AuthDropDown | |
306 | - v-if="!isCustomerUser" | |
307 | - :dropMenuList="[ | |
308 | - { | |
309 | - text: '分享', | |
310 | - auth: ConfigurationPermission.SHARE, | |
311 | - icon: 'ant-design:share-alt-outlined', | |
312 | - event: '', | |
313 | - onClick: handleOpenShareModal.bind(null, item), | |
314 | - }, | |
315 | - { | |
316 | - text: item.state == 0 ? '发布' : '取消发布', | |
317 | - auth: ConfigurationPermission.PUBLISH, | |
318 | - icon: | |
319 | - item.state == 0 | |
320 | - ? 'ant-design:node-expand-outlined' | |
321 | - : 'ant-design:node-collapse-outlined', | |
322 | - event: '', | |
323 | - onClick: handlePublish.bind(null, item), | |
324 | - }, | |
325 | - { | |
326 | - text: '编辑', | |
327 | - auth: ConfigurationPermission.UPDATE, | |
328 | - icon: 'clarity:note-edit-line', | |
329 | - event: '', | |
330 | - onClick: handleCreateOrUpdate.bind(null, item), | |
331 | - disabled: item.state === 0 ? false : true, | |
332 | - }, | |
333 | - { | |
334 | - text: '删除', | |
335 | - auth: ConfigurationPermission.DELETE, | |
336 | - icon: 'ant-design:delete-outlined', | |
337 | - disabled: item.state === 0 ? false : true, | |
338 | - event: '', | |
339 | - popconfirm: { | |
340 | - title: '是否确认删除操作?', | |
341 | - onConfirm: handleDelete.bind(null, item), | |
342 | - }, | |
343 | - }, | |
344 | - ]" | |
345 | - :trigger="['hover']" | |
346 | - /> | |
347 | - </template> | |
348 | - </Card> | |
349 | - </List.Item> | |
350 | - </template> | |
351 | - </List> | |
352 | - </section> | |
353 | - <ShareModal | |
354 | - @register="registerShareModal" | |
355 | - :shareApi="shareLargeScreen" | |
356 | - @success="getListData" | |
357 | - /> | |
358 | - <ConfigurationCenterDrawer @register="registerDrawer" @success="getListData" /> | |
359 | - <PublicApiDrawer @register="registerPublicDrawer" @success="getPublicApiListData" /> | |
188 | + </div> | |
189 | + </div> | |
190 | + </template> | |
191 | + <template class="ant-card-actions" #actions> | |
192 | + <Tooltip title="预览"> | |
193 | + <AuthIcon | |
194 | + class="!text-lg" | |
195 | + icon="ant-design:eye-outlined" | |
196 | + @click="handlePreview(item)" | |
197 | + /> | |
198 | + </Tooltip> | |
199 | + <Tooltip v-if="!isCustomerUser" title="设计"> | |
200 | + <AuthIcon | |
201 | + :auth="ConfigurationPermission.DESIGN" | |
202 | + :disabled="item.state === 1" | |
203 | + icon="ant-design:edit-outlined" | |
204 | + @click="handleDesign(item)" | |
205 | + /> | |
206 | + </Tooltip> | |
207 | + <Tooltip title="点击复制分享链接"> | |
208 | + <AuthIcon | |
209 | + :auth="ConfigurationPermission.SHARE" | |
210 | + :disabled="!item.publicId" | |
211 | + class="!text-lg" | |
212 | + icon="ant-design:share-alt-outlined" | |
213 | + @click="handleCreateShareUrl(item)" | |
214 | + /> | |
215 | + </Tooltip> | |
216 | + <AuthDropDown | |
217 | + v-if="!isCustomerUser" | |
218 | + :dropMenuList="[ | |
219 | + { | |
220 | + text: '分享', | |
221 | + auth: ConfigurationPermission.SHARE, | |
222 | + icon: 'ant-design:share-alt-outlined', | |
223 | + event: '', | |
224 | + onClick: handleOpenShareModal.bind(null, item), | |
225 | + }, | |
226 | + { | |
227 | + text: item.state == 0 ? '发布' : '取消发布', | |
228 | + auth: ConfigurationPermission.PUBLISH, | |
229 | + icon: | |
230 | + item.state == 0 | |
231 | + ? 'ant-design:node-expand-outlined' | |
232 | + : 'ant-design:node-collapse-outlined', | |
233 | + event: '', | |
234 | + onClick: handlePublish.bind(null, item), | |
235 | + }, | |
236 | + { | |
237 | + text: '编辑', | |
238 | + auth: ConfigurationPermission.UPDATE, | |
239 | + icon: 'clarity:note-edit-line', | |
240 | + event: '', | |
241 | + onClick: handleCreateOrUpdate.bind(null, item), | |
242 | + disabled: item.state === 0 ? false : true, | |
243 | + }, | |
244 | + { | |
245 | + text: '删除', | |
246 | + auth: ConfigurationPermission.DELETE, | |
247 | + icon: 'ant-design:delete-outlined', | |
248 | + disabled: item.state === 0 ? false : true, | |
249 | + event: '', | |
250 | + popconfirm: { | |
251 | + title: '是否确认删除操作?', | |
252 | + onConfirm: handleDelete.bind(null, item), | |
253 | + }, | |
254 | + }, | |
255 | + ]" | |
256 | + :trigger="['hover']" | |
257 | + /> | |
258 | + </template> | |
259 | + </Card> | |
260 | + </template> | |
261 | + </BasicCardList> | |
262 | + | |
263 | + <ShareModal @register="registerShareModal" :shareApi="shareLargeScreen" @success="reload()" /> | |
264 | + <ConfigurationCenterDrawer @register="registerDrawer" @success="reload()" /> | |
265 | + <PublicApiDrawer @register="registerPublicDrawer" /> | |
360 | 266 | </PageWrapper> |
361 | 267 | </template> |
362 | 268 | ... | ... |
1 | -import { getOrganizationList } from '/@/api/system/system'; | |
2 | 1 | import { FormSchema } from '/@/components/Form'; |
3 | 2 | import { ColEx } from '/@/components/Form/src/types'; |
4 | 3 | import { useGridLayout } from '/@/hooks/component/useGridLayout'; |
5 | -import { copyTransFun } from '/@/utils/fnUtils'; | |
6 | 4 | |
7 | 5 | export const formSchema: FormSchema[] = [ |
8 | 6 | { |
... | ... | @@ -11,23 +9,8 @@ export const formSchema: FormSchema[] = [ |
11 | 9 | component: 'Input', |
12 | 10 | // colProps: { span: 10 }, |
13 | 11 | colProps: useGridLayout(2, 3, 4) as unknown as ColEx, |
14 | - }, | |
15 | - { | |
16 | - field: 'organizationId', | |
17 | - component: 'ApiTreeSelect', | |
18 | - label: '组织', | |
19 | - // colProps: { span: 10 }, | |
20 | - colProps: useGridLayout(2, 3, 4) as unknown as ColEx, | |
21 | - componentProps() { | |
22 | - return { | |
23 | - placeholder: '请选择组织', | |
24 | - api: async () => { | |
25 | - const data = await getOrganizationList(); | |
26 | - copyTransFun(data as any as any[]); | |
27 | - return data; | |
28 | - }, | |
29 | - getPopupContainer: () => document.body, | |
30 | - }; | |
12 | + componentProps: { | |
13 | + placeholder: '请输入看板名称', | |
31 | 14 | }, |
32 | 15 | }, |
33 | 16 | ]; | ... | ... |
1 | 1 | <script lang="ts" setup> |
2 | - import { List, Card, Statistic, Button, Tooltip, Spin } from 'ant-design-vue'; | |
3 | - import { onMounted, ref, unref } from 'vue'; | |
4 | - import { PageWrapper } from '/@/components/Page'; | |
2 | + import { Card, Statistic, Button, Tooltip } from 'ant-design-vue'; | |
3 | + import { unref, computed } from 'vue'; | |
5 | 4 | import { MoreOutlined, ShareAltOutlined } from '@ant-design/icons-vue'; |
6 | 5 | import { useMessage } from '/@/hooks/web/useMessage'; |
7 | 6 | import Dropdown from '/@/components/Dropdown/src/Dropdown.vue'; |
... | ... | @@ -13,12 +12,8 @@ |
13 | 12 | import { DataBoardRecord } from '/@/api/dataBoard/model'; |
14 | 13 | import { ViewType } from './config/panelDetail'; |
15 | 14 | import { useRouter } from 'vue-router'; |
16 | - import { getBoundingClientRect } from '/@/utils/domUtils'; | |
17 | - import Authority from '/@/components/Authority/src/Authority.vue'; | |
18 | - import { computed } from '@vue/reactivity'; | |
19 | 15 | import { usePermission } from '/@/hooks/web/usePermission'; |
20 | 16 | import { encode } from './config/config'; |
21 | - import { useForm, BasicForm } from '/@/components/Form'; | |
22 | 17 | import { formSchema } from './config/searchForm'; |
23 | 18 | import { ShareModal } from '/@/views/common/ShareModal'; |
24 | 19 | import { ModalParamsType } from '/#/utils'; |
... | ... | @@ -26,62 +21,33 @@ |
26 | 21 | import { useRole } from '/@/hooks/business/useRole'; |
27 | 22 | import { useClipboard } from '@vueuse/core'; |
28 | 23 | import { DATA_BOARD_SHARE_URL } from '../palette'; |
24 | + import { BasicCardList, useCardList } from '/@/components/CardList'; | |
25 | + import Authority from '/@/components/Authority/src/Authority.vue'; | |
26 | + import { OrganizationIdTree, useOrganizationTree } from '../../common/organizationIdTree'; | |
29 | 27 | |
30 | - const ListItem = List.Item; | |
31 | 28 | const router = useRouter(); |
32 | 29 | |
33 | 30 | const { createMessage, createConfirm } = useMessage(); |
34 | 31 | |
35 | - const listEL = ref(); | |
36 | - const loading = ref(false); | |
37 | - const dataBoardList = ref<DataBoardRecord[]>([]); | |
32 | + const [registerOrgTree, { clearSelected, getSelectKey }] = useOrganizationTree({ | |
33 | + onSelect: () => reload(), | |
34 | + }); | |
38 | 35 | |
39 | - const [searchFormRegister, searchFormMethod] = useForm({ | |
40 | - schemas: formSchema, | |
41 | - labelWidth: 80, | |
42 | - layout: 'inline', | |
43 | - submitButtonOptions: { | |
44 | - loading: loading as unknown as boolean, | |
36 | + const [registerCardList, { reload }] = useCardList({ | |
37 | + title: '数据看板', | |
38 | + api: getDataBoardList, | |
39 | + baseLayout: { col: 3, row: 3 }, | |
40 | + useSearchForm: true, | |
41 | + formConfig: { | |
42 | + schemas: formSchema, | |
43 | + labelWidth: 80, | |
44 | + resetFunc: async () => clearSelected(), | |
45 | 45 | }, |
46 | - submitFunc: async () => { | |
47 | - try { | |
48 | - const params = searchFormMethod.getFieldsValue(); | |
49 | - await getDatasource(params); | |
50 | - } catch (error) {} | |
51 | - }, | |
52 | - resetFunc: async () => { | |
53 | - try { | |
54 | - await getDatasource(); | |
55 | - } catch (error) {} | |
46 | + beforeFetch: async (params: Recordable) => { | |
47 | + return { ...params, organizationId: getSelectKey() }; | |
56 | 48 | }, |
57 | 49 | }); |
58 | 50 | |
59 | - // about pagination | |
60 | - const page = ref(1); | |
61 | - const pageSize = ref(10); | |
62 | - const total = ref(0); | |
63 | - const paginationProp = ref({ | |
64 | - showSizeChanger: false, | |
65 | - showQuickJumper: true, | |
66 | - pageSize, | |
67 | - current: page, | |
68 | - size: 'small', | |
69 | - total, | |
70 | - showTotal: (total) => `总 ${total} 条`, | |
71 | - onChange: pageChange, | |
72 | - onShowSizeChange: pageSizeChange, | |
73 | - }); | |
74 | - | |
75 | - function pageChange(p, pz) { | |
76 | - page.value = p; | |
77 | - pageSize.value = pz; | |
78 | - getDatasource(); | |
79 | - } | |
80 | - function pageSizeChange(_current, size) { | |
81 | - pageSize.value = size; | |
82 | - getDatasource(); | |
83 | - } | |
84 | - | |
85 | 51 | const createShareUrl = (record: DataBoardRecord) => { |
86 | 52 | const { origin } = location; |
87 | 53 | const { id, publicId } = record; |
... | ... | @@ -124,22 +90,6 @@ |
124 | 90 | return basicMenu; |
125 | 91 | }); |
126 | 92 | |
127 | - const getDatasource = async (params: Recordable = {}) => { | |
128 | - try { | |
129 | - loading.value = true; | |
130 | - const { total, items } = await getDataBoardList({ | |
131 | - page: unref(paginationProp).current, | |
132 | - pageSize: unref(paginationProp).pageSize, | |
133 | - ...params, | |
134 | - }); | |
135 | - dataBoardList.value = items; | |
136 | - paginationProp.value.total = total; | |
137 | - } catch (error) { | |
138 | - } finally { | |
139 | - loading.value = false; | |
140 | - } | |
141 | - }; | |
142 | - | |
143 | 93 | const handleOpenShareModal = (record: DataBoardRecord) => { |
144 | 94 | openShareModal(true, { |
145 | 95 | record, |
... | ... | @@ -175,7 +125,7 @@ |
175 | 125 | try { |
176 | 126 | await deleteDataBoard([record.id]); |
177 | 127 | createMessage.success('删除成功'); |
178 | - await getDatasource(); | |
128 | + reload(); | |
179 | 129 | } catch (error) {} |
180 | 130 | }; |
181 | 131 | |
... | ... | @@ -188,106 +138,70 @@ |
188 | 138 | if (hasDetailPermission) { |
189 | 139 | const boardId = encode(record.id); |
190 | 140 | const boardName = encode(record.name); |
191 | - const organizationId = encode(record?.organizationId); | |
141 | + const organizationId = encode(record!.organizationId!); | |
192 | 142 | |
193 | 143 | router.push(`/visual/board/detail/${boardId}/${boardName}/${organizationId}`); |
194 | 144 | } else createMessage.warning('没有权限'); |
195 | 145 | }; |
196 | - | |
197 | - const handlePagenationPosition = () => { | |
198 | - const clientHeight = document.documentElement.clientHeight; | |
199 | - const rect = getBoundingClientRect(unref(listEL).$el!) as DOMRect; | |
200 | - const paginationHeight = 32 + 24 + 16; | |
201 | - const listContainerMarginBottom = 16; | |
202 | - const listContainerHeight = | |
203 | - clientHeight - rect.top - paginationHeight - listContainerMarginBottom; | |
204 | - const listContainerEl = (unref(listEL).$el as HTMLElement).querySelector( | |
205 | - '.ant-spin-container' | |
206 | - ) as HTMLElement; | |
207 | - listContainerEl && | |
208 | - (listContainerEl.style.height = listContainerHeight + 'px') && | |
209 | - (listContainerEl.style.overflowY = 'auto') && | |
210 | - (listContainerEl.style.overflowX = 'hidden'); | |
211 | - }; | |
212 | - | |
213 | - onMounted(() => { | |
214 | - getDatasource(); | |
215 | - handlePagenationPosition(); | |
216 | - }); | |
217 | 146 | </script> |
218 | 147 | |
219 | 148 | <template> |
220 | - <PageWrapper> | |
221 | - <div class="flex items-center mb-3 bg-light-100 h-78px dark:text-gray-300 dark:bg-dark-900"> | |
222 | - <div class="text-lg ml-30px mr-9px font-bold">自定义看板</div> | |
223 | - <Authority value="api:yt:data_board:add:post"> | |
224 | - <Button v-if="!isCustomerUser" type="primary" @click="handleOpenDetailModal" | |
225 | - >创建看板</Button | |
226 | - > | |
227 | - </Authority> | |
228 | - </div> | |
229 | - <div class="bg-light-100 mb-6 w-full p-3 search-form dark:text-gray-300 dark:bg-dark-900"> | |
230 | - <BasicForm class="flex-auto w-full" @register="searchFormRegister" /> | |
231 | - </div> | |
232 | - <Spin :spinning="loading"> | |
233 | - <List | |
234 | - ref="listEL" | |
235 | - :pagination="paginationProp" | |
236 | - :data-source="dataBoardList" | |
237 | - :grid="{ gutter: 20, column: 4, xs: 1, sm: 2, md: 2, lg: 3, xl: 3, xxl: 3 }" | |
238 | - class="data-board-list" | |
239 | - > | |
240 | - <template #renderItem="{ item }"> | |
241 | - <ListItem> | |
242 | - <Card class="data-card cursor-pointer"> | |
243 | - <template #title> | |
244 | - <div class="font-bold">{{ item.name }}</div> | |
245 | - </template> | |
246 | - <template #extra> | |
247 | - <Dropdown | |
248 | - v-if="!isCustomerUser && dropMenuList.length" | |
249 | - :trigger="['click']" | |
250 | - @menu-event="(event) => handleMenuEvent(event, item)" | |
251 | - :drop-menu-list="dropMenuList" | |
252 | - > | |
253 | - <MoreOutlined class="rotate-90 transform cursor-pointer" /> | |
254 | - </Dropdown> | |
255 | - </template> | |
256 | - <section @click="handleViewBoard(item)"> | |
257 | - <div class="flex data-card__info"> | |
258 | - <div> | |
259 | - <div>组件数量</div> | |
260 | - <Statistic style="font-size: 22px" :value="item.componentNum"> | |
261 | - <template #suffix> | |
262 | - <span class="text-sm">个</span> | |
263 | - </template> | |
264 | - </Statistic> | |
265 | - </div> | |
266 | - </div> | |
267 | - <div class="flex justify-between mt-4 text-sm" style="color: #999"> | |
268 | - <div class="flex min-w-20 mr-3"> | |
269 | - <span> | |
270 | - {{ item.viewType === ViewType.PRIVATE_VIEW ? '私有看板' : '公共看板' }} | |
271 | - </span> | |
272 | - <span v-if="item.viewType === ViewType.PUBLIC_VIEW"> | |
273 | - <Tooltip title="点击复制分享链接"> | |
274 | - <ShareAltOutlined class="ml-1" @click.stop="handleCopyShareUrl(item)" /> | |
275 | - </Tooltip> | |
276 | - </span> | |
277 | - </div> | |
278 | - <Tooltip placement="topLeft" :title="item.createTime"> | |
279 | - <div class="truncate">{{ item.createTime }}</div> | |
149 | + <section class="flex"> | |
150 | + <OrganizationIdTree @register="registerOrgTree" /> | |
151 | + <BasicCardList class="flex-auto p-4 w-3/4 xl:w-4/5 w-full" @register="registerCardList"> | |
152 | + <template #toolbar> | |
153 | + <Authority :value="VisualBoardPermission.CREATE"> | |
154 | + <Button type="primary" @click="handleOpenDetailModal">新增看板</Button> | |
155 | + </Authority> | |
156 | + </template> | |
157 | + <template #renderItem="{ item }: CardListRenderItem<DataBoardRecord>"> | |
158 | + <Card class="data-card cursor-pointer"> | |
159 | + <template #title> | |
160 | + <div class="font-bold">{{ item.name }}</div> | |
161 | + </template> | |
162 | + <template #extra> | |
163 | + <Dropdown | |
164 | + v-if="!isCustomerUser && dropMenuList.length" | |
165 | + :trigger="['click']" | |
166 | + @menu-event="(event) => handleMenuEvent(event, item)" | |
167 | + :drop-menu-list="dropMenuList" | |
168 | + > | |
169 | + <MoreOutlined class="rotate-90 transform cursor-pointer" /> | |
170 | + </Dropdown> | |
171 | + </template> | |
172 | + <section @click="handleViewBoard(item)"> | |
173 | + <div class="flex data-card__info"> | |
174 | + <div> | |
175 | + <div>组件数量</div> | |
176 | + <Statistic class="text-2xl" :value="item.componentNum"> | |
177 | + <template #suffix> | |
178 | + <span class="text-sm">个</span> | |
179 | + </template> | |
180 | + </Statistic> | |
181 | + </div> | |
182 | + </div> | |
183 | + <div class="flex justify-between mt-4 text-sm" style="color: #999"> | |
184 | + <div class="flex min-w-20 mr-3"> | |
185 | + <span> | |
186 | + {{ item.viewType === ViewType.PRIVATE_VIEW ? '私有看板' : '公共看板' }} | |
187 | + </span> | |
188 | + <span v-if="item.viewType === ViewType.PUBLIC_VIEW"> | |
189 | + <Tooltip title="点击复制分享链接"> | |
190 | + <ShareAltOutlined class="ml-1" @click.stop="handleCopyShareUrl(item)" /> | |
280 | 191 | </Tooltip> |
281 | - </div> | |
282 | - </section> | |
283 | - </Card> | |
284 | - </ListItem> | |
285 | - </template> | |
286 | - </List> | |
287 | - </Spin> | |
288 | - <ShareModal @register="registerShareModal" :shareApi="shareBoard" @success="getDatasource" /> | |
289 | - <PanelDetailModal @register="registerModal" @change="getDatasource" /> | |
290 | - </PageWrapper> | |
192 | + </span> | |
193 | + </div> | |
194 | + <Tooltip placement="topLeft" :title="item.createTime"> | |
195 | + <div class="truncate">{{ item.createTime }}</div> | |
196 | + </Tooltip> | |
197 | + </div> | |
198 | + </section> | |
199 | + </Card> | |
200 | + </template> | |
201 | + </BasicCardList> | |
202 | + <ShareModal @register="registerShareModal" :shareApi="shareBoard" @success="reload()" /> | |
203 | + <PanelDetailModal @register="registerModal" @change="reload()" /> | |
204 | + </section> | |
291 | 205 | </template> |
292 | 206 | |
293 | 207 | <style scoped lang="less"> | ... | ... |
1 | 1 | import type { ComputedRef, Ref } from 'vue'; |
2 | -import { DataActionModeEnum } from '/@/enums/toolEnum'; | |
3 | 2 | |
4 | -export type DynamicProps<T> = { | |
5 | - [P in keyof T]: Ref<T[P]> | T[P] | ComputedRef<T[P]>; | |
6 | -}; | |
3 | +import { DataActionModeEnum } from '/@/enums/toolEnum'; | |
7 | 4 | |
8 | 5 | export interface ModalParamsType<T = Recordable> { |
9 | 6 | mode: DataActionModeEnum; |
... | ... | @@ -11,9 +8,30 @@ export interface ModalParamsType<T = Recordable> { |
11 | 8 | [key: string]: any; |
12 | 9 | } |
13 | 10 | |
11 | +export type DynamicProps<T> = { | |
12 | + [P in keyof T]: Ref<T[P]> | T[P] | ComputedRef<T[P]>; | |
13 | +}; | |
14 | + | |
14 | 15 | export interface DefineComponentsBasicExpose<T = Recordable> { |
15 | 16 | getFieldsValue: () => T; |
16 | 17 | setFieldsValue: (value: T) => any; |
17 | 18 | validate?: () => Promise<any>; |
18 | 19 | resetFieldsValue?: (...args) => any; |
19 | 20 | } |
21 | + | |
22 | +declare global { | |
23 | + interface CardListRenderItem<T = Recordable & { checked?: boolean }> { | |
24 | + item: T; | |
25 | + totalHeight: number; | |
26 | + } | |
27 | + | |
28 | + import { DataActionModeEnum } from '/@/enums/toolEnum'; | |
29 | + | |
30 | + interface ModalParamsType<T = Recordable> { | |
31 | + mode: DataActionModeEnum; | |
32 | + record: T; | |
33 | + [key: string]: any; | |
34 | + } | |
35 | +} | |
36 | + | |
37 | +export {}; | ... | ... |