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