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 | import { BasicPageParams } from '/@/api/model/baseModel'; | 1 | import { BasicPageParams } from '/@/api/model/baseModel'; |
2 | +import { ViewType } from '/@/views/visual/board/config/panelDetail'; | ||
2 | 3 | ||
3 | export interface BigScreenCenterItemsModel { | 4 | export interface BigScreenCenterItemsModel { |
4 | id: string; | 5 | id: string; |
6 | + thumbnail?: string; | ||
5 | name: string; | 7 | name: string; |
6 | createTime: string; | 8 | createTime: string; |
7 | creator: string; | 9 | creator: string; |
@@ -9,6 +11,8 @@ export interface BigScreenCenterItemsModel { | @@ -9,6 +11,8 @@ export interface BigScreenCenterItemsModel { | ||
9 | state: number; | 11 | state: number; |
10 | publicId: string; | 12 | publicId: string; |
11 | organizationId?: string; | 13 | organizationId?: string; |
14 | + viewType?: ViewType; | ||
15 | + organizationDTO: { name: string }; | ||
12 | } | 16 | } |
13 | export type queryPageParams = BasicPageParams & { | 17 | export type queryPageParams = BasicPageParams & { |
14 | name?: Nullable<string>; | 18 | name?: Nullable<string>; |
@@ -2,15 +2,46 @@ import { BasicPageParams } from '/@/api/model/baseModel'; | @@ -2,15 +2,46 @@ import { BasicPageParams } from '/@/api/model/baseModel'; | ||
2 | 2 | ||
3 | export interface ConfigurationCenterItemsModal { | 3 | export interface ConfigurationCenterItemsModal { |
4 | id: string; | 4 | id: string; |
5 | - name: string; | ||
6 | - createTime: string; | ||
7 | creator: string; | 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 | export type queryPageParams = BasicPageParams & { | 45 | export type queryPageParams = BasicPageParams & { |
15 | name?: Nullable<string>; | 46 | name?: Nullable<string>; |
16 | organizationId?: Nullable<number>; | 47 | organizationId?: Nullable<number>; |
@@ -15,6 +15,8 @@ export interface UpdateDataBoardParams extends AddDataBoardParams { | @@ -15,6 +15,8 @@ export interface UpdateDataBoardParams extends AddDataBoardParams { | ||
15 | export interface GetDataBoardParams { | 15 | export interface GetDataBoardParams { |
16 | page?: number; | 16 | page?: number; |
17 | pageSize?: number; | 17 | pageSize?: number; |
18 | + name?: string; | ||
19 | + organizationId?: string; | ||
18 | orderFiled?: string; | 20 | orderFiled?: string; |
19 | orderType?: string; | 21 | orderType?: string; |
20 | } | 22 | } |
@@ -47,7 +49,9 @@ export interface DataBoardRecord { | @@ -47,7 +49,9 @@ export interface DataBoardRecord { | ||
47 | layout: Layout[]; | 49 | layout: Layout[]; |
48 | defaultConfig: string; | 50 | defaultConfig: string; |
49 | tenantStatus: string; | 51 | tenantStatus: string; |
52 | + componentNum?: number; | ||
50 | publicId: string; | 53 | publicId: string; |
54 | + organizationId?: string; | ||
51 | accessCredentials?: string; | 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 | export { default as ModeSwitchButton } from './ModeSwitchButton.vue'; | 1 | export { default as ModeSwitchButton } from './ModeSwitchButton.vue'; |
2 | export { default as CardLayoutButton } from './CardLayoutButton.vue'; | 2 | export { default as CardLayoutButton } from './CardLayoutButton.vue'; |
3 | export { default as AuthIcon } from './AuthIcon.vue'; | 3 | export { default as AuthIcon } from './AuthIcon.vue'; |
4 | +export { default as AuthDropDown } from './AuthDropDown.vue'; | ||
4 | export { | 5 | export { |
5 | EnumTableCardMode, | 6 | EnumTableCardMode, |
6 | EnumTableChartMode, | 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 | +} |
1 | import { useResetOrganizationTree } from './hooks/useOrganization'; | 1 | import { useResetOrganizationTree } from './hooks/useOrganization'; |
2 | import OrganizationIdTree from './src/OrganizationIdTree.vue'; | 2 | import OrganizationIdTree from './src/OrganizationIdTree.vue'; |
3 | +export { useOrganizationTree } from './hooks/useOrganizationTree'; | ||
3 | 4 | ||
4 | export { OrganizationIdTree, useResetOrganizationTree }; | 5 | export { OrganizationIdTree, useResetOrganizationTree }; |
@@ -17,41 +17,41 @@ | @@ -17,41 +17,41 @@ | ||
17 | </div> | 17 | </div> |
18 | </div> | 18 | </div> |
19 | <div :style="{ width: foldFlag ? '0px' : '100%' }" class="bg-white mr-0 overflow-hidden h-full"> | 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 | </div> | 21 | </div> |
33 | </div> | 22 | </div> |
34 | </template> | 23 | </template> |
35 | <script lang="ts" setup name="OrganizationIdTree"> | 24 | <script lang="ts" setup name="OrganizationIdTree"> |
36 | - import { onMounted, ref, unref } from 'vue'; | 25 | + import { computed, onMounted, ref, unref, useAttrs } from 'vue'; |
37 | import { BasicTree, TreeItem } from '/@/components/Tree'; | 26 | import { BasicTree, TreeItem } from '/@/components/Tree'; |
38 | import { getOrganizationList } from '/@/api/system/system'; | 27 | import { getOrganizationList } from '/@/api/system/system'; |
39 | import { CaretRightOutlined } from '@ant-design/icons-vue'; | 28 | import { CaretRightOutlined } from '@ant-design/icons-vue'; |
40 | import { getBoundingClientRect } from '/@/utils/domUtils'; | 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 | const tree = ref<Nullable<HTMLDivElement>>(); | 37 | const tree = ref<Nullable<HTMLDivElement>>(); |
43 | - const emit = defineEmits(['select']); | 38 | + const emit = defineEmits(['select', 'register']); |
44 | const treeData = ref<TreeItem[]>([]); | 39 | const treeData = ref<TreeItem[]>([]); |
45 | const selectedKeys = ref<string[]>(); | 40 | const selectedKeys = ref<string[]>(); |
46 | - const treeExpandData = ref([]); | 41 | + const treeExpandData = ref<string[]>([]); |
42 | + | ||
43 | + const innerProps = ref<OrganizationTreePropsType>({}); | ||
44 | + | ||
47 | //获取所有父级id | 45 | //获取所有父级id |
48 | - function findForAllId(data = [], arr = []) { | 46 | + function findForAllId(data: Recordable[] = [], arr: string[] = []) { |
49 | for (const item of data) { | 47 | for (const item of data) { |
50 | arr.push(item.id); | 48 | arr.push(item.id); |
51 | } | 49 | } |
52 | return arr; | 50 | return arr; |
53 | } | 51 | } |
54 | - function handleSelect(keys) { | 52 | + |
53 | + function handleSelect(keys: string[]) { | ||
54 | + selectedKeys.value = keys; | ||
55 | emit('select', keys[0]); | 55 | emit('select', keys[0]); |
56 | } | 56 | } |
57 | function resetOrganization() { | 57 | function resetOrganization() { |
@@ -81,9 +81,41 @@ | @@ -81,9 +81,41 @@ | ||
81 | 81 | ||
82 | setTreeHeight(); | 82 | setTreeHeight(); |
83 | }); | 83 | }); |
84 | + | ||
84 | defineExpose({ | 85 | defineExpose({ |
85 | resetOrganization, | 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 | </script> | 119 | </script> |
88 | 120 | ||
89 | <style scoped lang="less"> | 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 | <script setup lang="ts"> | 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 | import { | 3 | import { |
7 | deleteConfigurationCenter, | 4 | deleteConfigurationCenter, |
8 | getPage, | 5 | getPage, |
9 | shareConfiguration, | 6 | shareConfiguration, |
10 | } from '/@/api/configuration/center/configurationCenter'; | 7 | } from '/@/api/configuration/center/configurationCenter'; |
8 | + import { searchFormSchema, ConfigurationPermission } from './center.data'; | ||
11 | import { ConfigurationCenterItemsModal } from '/@/api/configuration/center/model/configurationCenterModal'; | 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 | import { Authority } from '/@/components/Authority'; | 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 | import configurationSrc from '/@/assets/icons/configuration.svg'; | 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 | import { usePermission } from '/@/hooks/web/usePermission'; | 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 | import { Icon } from '/@/components/Icon'; | 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 | const { hasPermission } = usePermission(); | 65 | const { hasPermission } = usePermission(); |
107 | 66 | ||
@@ -141,19 +100,6 @@ | @@ -141,19 +100,6 @@ | ||
141 | createScadaPageLink(record, ScadaModeEnum.DESIGN); | 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 | const createShareUrl = (record: ConfigurationCenterItemsModal) => { | 103 | const createShareUrl = (record: ConfigurationCenterItemsModal) => { |
158 | return createScadaPageLink(record, ScadaModeEnum.SHARE, false); | 104 | return createScadaPageLink(record, ScadaModeEnum.SHARE, false); |
159 | }; | 105 | }; |
@@ -168,209 +114,138 @@ | @@ -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 | </script> | 124 | </script> |
197 | 125 | ||
198 | <template> | 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 | </div> | 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 | </template> | 243 | </template> |
343 | 244 | ||
344 | <style lang="less" scoped> | 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 | .card-container { | 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 | </style> | 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 | <script setup lang="ts"> | 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 | import { | 3 | import { |
7 | deleteConfigurationCenter, | 4 | deleteConfigurationCenter, |
8 | getPage, | 5 | getPage, |
9 | } from '/@/api/configuration/center/configurationCenter'; | 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 | import { searchFormSchema, ConfigurationTemplatePermission } from './center.data'; | 7 | import { searchFormSchema, ConfigurationTemplatePermission } from './center.data'; |
14 | - import { useMessage } from '/@/hooks/web/useMessage'; | 8 | + import { ConfigurationCenterItemsModal } from '/@/api/configuration/center/model/configurationCenterModal'; |
15 | import { Authority } from '/@/components/Authority'; | 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 | import configurationSrc from '/@/assets/icons/configuration.svg'; | 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 | import { usePermission } from '/@/hooks/web/usePermission'; | 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 | import { Icon } from '/@/components/Icon'; | 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 | const { hasPermission } = usePermission(); | 54 | const { hasPermission } = usePermission(); |
102 | 55 | ||
@@ -136,191 +89,106 @@ | @@ -136,191 +89,106 @@ | ||
136 | try { | 89 | try { |
137 | await deleteConfigurationCenter([record.id]); | 90 | await deleteConfigurationCenter([record.id]); |
138 | createMessage.success('删除成功'); | 91 | createMessage.success('删除成功'); |
139 | - await getListData(); | 92 | + await reload(); |
140 | } catch (error) {} | 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 | </script> | 95 | </script> |
168 | 96 | ||
169 | <template> | 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 | </div> | 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 | </template> | 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 | <script setup lang="ts"> | 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 | import { | 5 | import { |
7 | bigScreenCancelPublish, | 6 | bigScreenCancelPublish, |
8 | bigScreenPublish, | 7 | bigScreenPublish, |
@@ -12,17 +11,15 @@ | @@ -12,17 +11,15 @@ | ||
12 | } from '/@/api/bigscreen/center/bigscreenCenter'; | 11 | } from '/@/api/bigscreen/center/bigscreenCenter'; |
13 | import { BigScreenCenterItemsModel } from '/@/api/bigscreen/center/model/bigscreenCenterModel'; | 12 | import { BigScreenCenterItemsModel } from '/@/api/bigscreen/center/model/bigscreenCenterModel'; |
14 | import { PageWrapper } from '/@/components/Page'; | 13 | import { PageWrapper } from '/@/components/Page'; |
15 | - import { BasicForm, useForm } from '/@/components/Form'; | ||
16 | import { ConfigurationPermission, searchFormSchema } from './config'; | 14 | import { ConfigurationPermission, searchFormSchema } from './config'; |
17 | import { useMessage } from '/@/hooks/web/useMessage'; | 15 | import { useMessage } from '/@/hooks/web/useMessage'; |
18 | import { Authority } from '/@/components/Authority'; | 16 | import { Authority } from '/@/components/Authority'; |
19 | import ConfigurationCenterDrawer from './BigScreenDrawer.vue'; | 17 | import ConfigurationCenterDrawer from './BigScreenDrawer.vue'; |
20 | import { useDrawer } from '/@/components/Drawer'; | 18 | import { useDrawer } from '/@/components/Drawer'; |
21 | - import { getBoundingClientRect } from '/@/utils/domUtils'; | ||
22 | import configurationSrc from '/@/assets/icons/configuration.svg'; | 19 | import configurationSrc from '/@/assets/icons/configuration.svg'; |
23 | import { cloneDeep } from 'lodash'; | 20 | import { cloneDeep } from 'lodash'; |
24 | import { useGlobSetting } from '/@/hooks/setting'; | 21 | import { useGlobSetting } from '/@/hooks/setting'; |
25 | - import { AuthIcon, CardLayoutButton } from '/@/components/Widget'; | 22 | + import { AuthIcon } from '/@/components/Widget'; |
26 | import AuthDropDown from '/@/components/Widget/AuthDropDown.vue'; | 23 | import AuthDropDown from '/@/components/Widget/AuthDropDown.vue'; |
27 | import { PublicApiDrawer } from './publicApi/index'; | 24 | import { PublicApiDrawer } from './publicApi/index'; |
28 | import { useModal } from '/@/components/Modal'; | 25 | import { useModal } from '/@/components/Modal'; |
@@ -33,79 +30,37 @@ | @@ -33,79 +30,37 @@ | ||
33 | import { RoleEnum } from '/@/enums/roleEnum'; | 30 | import { RoleEnum } from '/@/enums/roleEnum'; |
34 | import { useRole } from '/@/hooks/business/useRole'; | 31 | import { useRole } from '/@/hooks/business/useRole'; |
35 | import { useClipboard } from '@vueuse/core'; | 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 | const [registerDrawer, { openDrawer }] = useDrawer(); | 56 | const [registerDrawer, { openDrawer }] = useDrawer(); |
106 | 57 | ||
107 | const [registerPublicDrawer, { openDrawer: openPublicApiDrawer }] = useDrawer(); | 58 | const [registerPublicDrawer, { openDrawer: openPublicApiDrawer }] = useDrawer(); |
108 | 59 | ||
60 | + const { createMessage } = useMessage(); | ||
61 | + | ||
62 | + const { isCustomerUser } = useRole(); | ||
63 | + | ||
109 | const handleCreateOrUpdate = (record?: BigScreenCenterItemsModel) => { | 64 | const handleCreateOrUpdate = (record?: BigScreenCenterItemsModel) => { |
110 | if (record) { | 65 | if (record) { |
111 | openDrawer(true, { | 66 | openDrawer(true, { |
@@ -140,37 +95,10 @@ | @@ -140,37 +95,10 @@ | ||
140 | try { | 95 | try { |
141 | await deleteBigScreenenter([record.id]); | 96 | await deleteBigScreenenter([record.id]); |
142 | createMessage.success('删除成功'); | 97 | createMessage.success('删除成功'); |
143 | - await getListData(); | 98 | + reload(); |
144 | } catch (error) {} | 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 | const [registerShareModal, { openModal }] = useModal(); | 102 | const [registerShareModal, { openModal }] = useModal(); |
175 | const handleOpenShareModal = (record: BigScreenCenterItemsModel) => { | 103 | const handleOpenShareModal = (record: BigScreenCenterItemsModel) => { |
176 | openModal(true, { record, href: createShareUrl(record) }); | 104 | openModal(true, { record, href: createShareUrl(record) }); |
@@ -182,6 +110,7 @@ | @@ -182,6 +110,7 @@ | ||
182 | }; | 110 | }; |
183 | 111 | ||
184 | const userStore = useUserStore(); | 112 | const userStore = useUserStore(); |
113 | + | ||
185 | const hasPublicInterfacePermission = computed(() => { | 114 | const hasPublicInterfacePermission = computed(() => { |
186 | return userStore.getUserInfo.roles![0] !== RoleEnum.CUSTOMER_USER; | 115 | return userStore.getUserInfo.roles![0] !== RoleEnum.CUSTOMER_USER; |
187 | }); | 116 | }); |
@@ -197,166 +126,143 @@ | @@ -197,166 +126,143 @@ | ||
197 | const handlePublish = async ({ id, state }) => { | 126 | const handlePublish = async ({ id, state }) => { |
198 | state === 0 ? await bigScreenPublish(id) : await bigScreenCancelPublish(id); | 127 | state === 0 ? await bigScreenPublish(id) : await bigScreenCancelPublish(id); |
199 | createMessage.success(state === 0 ? '发布成功' : '取消发布成功'); | 128 | createMessage.success(state === 0 ? '发布成功' : '取消发布成功'); |
200 | - getListData(); | 129 | + reload(); |
201 | }; | 130 | }; |
202 | </script> | 131 | </script> |
203 | 132 | ||
204 | <template> | 133 | <template> |
205 | <PageWrapper dense contentFullHeight contentClass="flex"> | 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 | </div> | 186 | </div> |
278 | </div> | 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 | </PageWrapper> | 266 | </PageWrapper> |
361 | </template> | 267 | </template> |
362 | 268 |
1 | -import { getOrganizationList } from '/@/api/system/system'; | ||
2 | import { FormSchema } from '/@/components/Form'; | 1 | import { FormSchema } from '/@/components/Form'; |
3 | import { ColEx } from '/@/components/Form/src/types'; | 2 | import { ColEx } from '/@/components/Form/src/types'; |
4 | import { useGridLayout } from '/@/hooks/component/useGridLayout'; | 3 | import { useGridLayout } from '/@/hooks/component/useGridLayout'; |
5 | -import { copyTransFun } from '/@/utils/fnUtils'; | ||
6 | 4 | ||
7 | export const formSchema: FormSchema[] = [ | 5 | export const formSchema: FormSchema[] = [ |
8 | { | 6 | { |
@@ -11,23 +9,8 @@ export const formSchema: FormSchema[] = [ | @@ -11,23 +9,8 @@ export const formSchema: FormSchema[] = [ | ||
11 | component: 'Input', | 9 | component: 'Input', |
12 | // colProps: { span: 10 }, | 10 | // colProps: { span: 10 }, |
13 | colProps: useGridLayout(2, 3, 4) as unknown as ColEx, | 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 | <script lang="ts" setup> | 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 | import { MoreOutlined, ShareAltOutlined } from '@ant-design/icons-vue'; | 4 | import { MoreOutlined, ShareAltOutlined } from '@ant-design/icons-vue'; |
6 | import { useMessage } from '/@/hooks/web/useMessage'; | 5 | import { useMessage } from '/@/hooks/web/useMessage'; |
7 | import Dropdown from '/@/components/Dropdown/src/Dropdown.vue'; | 6 | import Dropdown from '/@/components/Dropdown/src/Dropdown.vue'; |
@@ -13,12 +12,8 @@ | @@ -13,12 +12,8 @@ | ||
13 | import { DataBoardRecord } from '/@/api/dataBoard/model'; | 12 | import { DataBoardRecord } from '/@/api/dataBoard/model'; |
14 | import { ViewType } from './config/panelDetail'; | 13 | import { ViewType } from './config/panelDetail'; |
15 | import { useRouter } from 'vue-router'; | 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 | import { usePermission } from '/@/hooks/web/usePermission'; | 15 | import { usePermission } from '/@/hooks/web/usePermission'; |
20 | import { encode } from './config/config'; | 16 | import { encode } from './config/config'; |
21 | - import { useForm, BasicForm } from '/@/components/Form'; | ||
22 | import { formSchema } from './config/searchForm'; | 17 | import { formSchema } from './config/searchForm'; |
23 | import { ShareModal } from '/@/views/common/ShareModal'; | 18 | import { ShareModal } from '/@/views/common/ShareModal'; |
24 | import { ModalParamsType } from '/#/utils'; | 19 | import { ModalParamsType } from '/#/utils'; |
@@ -26,62 +21,33 @@ | @@ -26,62 +21,33 @@ | ||
26 | import { useRole } from '/@/hooks/business/useRole'; | 21 | import { useRole } from '/@/hooks/business/useRole'; |
27 | import { useClipboard } from '@vueuse/core'; | 22 | import { useClipboard } from '@vueuse/core'; |
28 | import { DATA_BOARD_SHARE_URL } from '../palette'; | 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 | const router = useRouter(); | 28 | const router = useRouter(); |
32 | 29 | ||
33 | const { createMessage, createConfirm } = useMessage(); | 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 | const createShareUrl = (record: DataBoardRecord) => { | 51 | const createShareUrl = (record: DataBoardRecord) => { |
86 | const { origin } = location; | 52 | const { origin } = location; |
87 | const { id, publicId } = record; | 53 | const { id, publicId } = record; |
@@ -124,22 +90,6 @@ | @@ -124,22 +90,6 @@ | ||
124 | return basicMenu; | 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 | const handleOpenShareModal = (record: DataBoardRecord) => { | 93 | const handleOpenShareModal = (record: DataBoardRecord) => { |
144 | openShareModal(true, { | 94 | openShareModal(true, { |
145 | record, | 95 | record, |
@@ -175,7 +125,7 @@ | @@ -175,7 +125,7 @@ | ||
175 | try { | 125 | try { |
176 | await deleteDataBoard([record.id]); | 126 | await deleteDataBoard([record.id]); |
177 | createMessage.success('删除成功'); | 127 | createMessage.success('删除成功'); |
178 | - await getDatasource(); | 128 | + reload(); |
179 | } catch (error) {} | 129 | } catch (error) {} |
180 | }; | 130 | }; |
181 | 131 | ||
@@ -188,106 +138,70 @@ | @@ -188,106 +138,70 @@ | ||
188 | if (hasDetailPermission) { | 138 | if (hasDetailPermission) { |
189 | const boardId = encode(record.id); | 139 | const boardId = encode(record.id); |
190 | const boardName = encode(record.name); | 140 | const boardName = encode(record.name); |
191 | - const organizationId = encode(record?.organizationId); | 141 | + const organizationId = encode(record!.organizationId!); |
192 | 142 | ||
193 | router.push(`/visual/board/detail/${boardId}/${boardName}/${organizationId}`); | 143 | router.push(`/visual/board/detail/${boardId}/${boardName}/${organizationId}`); |
194 | } else createMessage.warning('没有权限'); | 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 | </script> | 146 | </script> |
218 | 147 | ||
219 | <template> | 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 | </Tooltip> | 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 | </template> | 205 | </template> |
292 | 206 | ||
293 | <style scoped lang="less"> | 207 | <style scoped lang="less"> |
1 | import type { ComputedRef, Ref } from 'vue'; | 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 | export interface ModalParamsType<T = Recordable> { | 5 | export interface ModalParamsType<T = Recordable> { |
9 | mode: DataActionModeEnum; | 6 | mode: DataActionModeEnum; |
@@ -11,9 +8,30 @@ export interface ModalParamsType<T = Recordable> { | @@ -11,9 +8,30 @@ export interface ModalParamsType<T = Recordable> { | ||
11 | [key: string]: any; | 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 | export interface DefineComponentsBasicExpose<T = Recordable> { | 15 | export interface DefineComponentsBasicExpose<T = Recordable> { |
15 | getFieldsValue: () => T; | 16 | getFieldsValue: () => T; |
16 | setFieldsValue: (value: T) => any; | 17 | setFieldsValue: (value: T) => any; |
17 | validate?: () => Promise<any>; | 18 | validate?: () => Promise<any>; |
18 | resetFieldsValue?: (...args) => any; | 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 {}; |