Commit 0fcc123d9a6e4bed76c3383fc6baf56d2007d813

Authored by xp.Huang
2 parents 94ab22ef 80ee6a6f

Merge branch 'feat/add-card-list-component' into 'main_dev'

feat: 新增卡片列表组件

See merge request yunteng/thingskit-front!1040
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
... ...
  1 +export { default as BasicCardList } from './src/BasicCardList.vue';
  2 +export { useCardList } from './src/hooks/useCardList';
... ...
  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 +}
... ...
  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 +}
... ...
  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 +}
... ...
1 1 import { useResetOrganizationTree } from './hooks/useOrganization';
2 2 import OrganizationIdTree from './src/OrganizationIdTree.vue';
  3 +export { useOrganizationTree } from './hooks/useOrganizationTree';
3 4
4 5 export { OrganizationIdTree, useResetOrganizationTree };
... ...
... ... @@ -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 +export interface OrganizationTreePropsType {
  2 + onSelect?: (selectKeys: string) => any;
  3 +}
... ...
  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>>;
... ...
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>
... ...
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>
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 {};
... ...