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 import { BasicPageParams } from '/@/api/model/baseModel'; 1 import { BasicPageParams } from '/@/api/model/baseModel';
  2 +import { ViewType } from '/@/views/visual/board/config/panelDetail';
2 3
3 export interface BigScreenCenterItemsModel { 4 export interface BigScreenCenterItemsModel {
4 id: string; 5 id: string;
  6 + thumbnail?: string;
5 name: string; 7 name: string;
6 createTime: string; 8 createTime: string;
7 creator: string; 9 creator: string;
@@ -9,6 +11,8 @@ export interface BigScreenCenterItemsModel { @@ -9,6 +11,8 @@ export interface BigScreenCenterItemsModel {
9 state: number; 11 state: number;
10 publicId: string; 12 publicId: string;
11 organizationId?: string; 13 organizationId?: string;
  14 + viewType?: ViewType;
  15 + organizationDTO: { name: string };
12 } 16 }
13 export type queryPageParams = BasicPageParams & { 17 export type queryPageParams = BasicPageParams & {
14 name?: Nullable<string>; 18 name?: Nullable<string>;
@@ -2,15 +2,46 @@ import { BasicPageParams } from '/@/api/model/baseModel'; @@ -2,15 +2,46 @@ import { BasicPageParams } from '/@/api/model/baseModel';
2 2
3 export interface ConfigurationCenterItemsModal { 3 export interface ConfigurationCenterItemsModal {
4 id: string; 4 id: string;
5 - name: string;  
6 - createTime: string;  
7 creator: string; 5 creator: string;
8 - remark: string;  
9 - publicId?: string;  
10 - organizationId?: string;  
11 - platform?: string;  
12 - productIds?: string; 6 + createTime: string;
  7 + updater: string;
  8 + updateTime: string;
  9 + name: string;
  10 + enabled: boolean;
  11 + tenantId: string;
  12 + publicId: string;
  13 + viewType: string;
  14 + accessCredentials: string;
  15 + organizationId: string;
  16 + platform: string;
  17 + thumbnail: string;
  18 + organizationDTO: OrganizationDto;
  19 + templateId: string;
  20 + productAndDevice: ProductAndDevice[];
  21 + remark?: string;
  22 +}
  23 +
  24 +export interface OrganizationDto {
  25 + name: string;
  26 + enabled: boolean;
  27 + sort: number;
  28 + children: any[];
13 } 29 }
  30 +
  31 +export interface ProductAndDevice {
  32 + profileId: string;
  33 + name: string;
  34 + transportType: string;
  35 + deviceType: string;
  36 + deviceList: DeviceList[];
  37 +}
  38 +
  39 +export interface DeviceList {
  40 + deviceId: string;
  41 + name: string;
  42 + codeType: any;
  43 +}
  44 +
14 export type queryPageParams = BasicPageParams & { 45 export type queryPageParams = BasicPageParams & {
15 name?: Nullable<string>; 46 name?: Nullable<string>;
16 organizationId?: Nullable<number>; 47 organizationId?: Nullable<number>;
@@ -15,6 +15,8 @@ export interface UpdateDataBoardParams extends AddDataBoardParams { @@ -15,6 +15,8 @@ export interface UpdateDataBoardParams extends AddDataBoardParams {
15 export interface GetDataBoardParams { 15 export interface GetDataBoardParams {
16 page?: number; 16 page?: number;
17 pageSize?: number; 17 pageSize?: number;
  18 + name?: string;
  19 + organizationId?: string;
18 orderFiled?: string; 20 orderFiled?: string;
19 orderType?: string; 21 orderType?: string;
20 } 22 }
@@ -47,7 +49,9 @@ export interface DataBoardRecord { @@ -47,7 +49,9 @@ export interface DataBoardRecord {
47 layout: Layout[]; 49 layout: Layout[];
48 defaultConfig: string; 50 defaultConfig: string;
49 tenantStatus: string; 51 tenantStatus: string;
  52 + componentNum?: number;
50 publicId: string; 53 publicId: string;
  54 + organizationId?: string;
51 accessCredentials?: string; 55 accessCredentials?: string;
52 } 56 }
53 57
  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 export { default as ModeSwitchButton } from './ModeSwitchButton.vue'; 1 export { default as ModeSwitchButton } from './ModeSwitchButton.vue';
2 export { default as CardLayoutButton } from './CardLayoutButton.vue'; 2 export { default as CardLayoutButton } from './CardLayoutButton.vue';
3 export { default as AuthIcon } from './AuthIcon.vue'; 3 export { default as AuthIcon } from './AuthIcon.vue';
  4 +export { default as AuthDropDown } from './AuthDropDown.vue';
4 export { 5 export {
5 EnumTableCardMode, 6 EnumTableCardMode,
6 EnumTableChartMode, 7 EnumTableChartMode,
  1 +import { WatchStopHandle, onUnmounted, ref, unref, watch } from 'vue';
  2 +import { OrganizationTreeActionType } from '../src/types';
  3 +import { isProdMode } from '/@/utils/env';
  4 +import { error } from '/@/utils/log';
  5 +import { OrganizationTreePropsType } from '../src/props';
  6 +import { getDynamicProps } from '/@/utils';
  7 +
  8 +export function useOrganizationTree(
  9 + organizationTreeProps?: Partial<OrganizationTreePropsType>
  10 +): [(instance: OrganizationTreeActionType) => void, OrganizationTreeActionType] {
  11 + const organizationTreeRef = ref<Nullable<OrganizationTreeActionType>>(null);
  12 + const loadedRef = ref<Nullable<boolean>>(false);
  13 + let stopWatch: WatchStopHandle;
  14 + function register(instance: OrganizationTreeActionType) {
  15 + isProdMode() &&
  16 + onUnmounted(() => {
  17 + organizationTreeRef.value = null;
  18 + loadedRef.value = null;
  19 + });
  20 +
  21 + if (unref(loadedRef) && isProdMode() && instance === unref(organizationTreeRef)) return;
  22 +
  23 + organizationTreeRef.value = instance;
  24 + loadedRef.value = true;
  25 +
  26 + stopWatch?.();
  27 +
  28 + stopWatch = watch(
  29 + () => organizationTreeProps,
  30 + () => {
  31 + organizationTreeProps && instance.setProps(getDynamicProps(organizationTreeProps));
  32 + },
  33 + {
  34 + immediate: true,
  35 + deep: true,
  36 + }
  37 + );
  38 + }
  39 +
  40 + function getOrganizationTreeInstance(): OrganizationTreeActionType {
  41 + const organizationTree = unref(organizationTreeRef);
  42 + if (!organizationTree) {
  43 + error(
  44 + 'The organization tree instance has not been obtained yet, please make sure the table is presented when performing the table operation!'
  45 + );
  46 + }
  47 + return organizationTree as OrganizationTreeActionType;
  48 + }
  49 +
  50 + const organizationTreeActionType: OrganizationTreeActionType = {
  51 + getSelectKey: () => getOrganizationTreeInstance().getSelectKey(),
  52 + clearSelected: () => getOrganizationTreeInstance().clearSelected(),
  53 + setProps: (props: Partial<OrganizationTreePropsType>) =>
  54 + getOrganizationTreeInstance().setProps(props),
  55 + };
  56 +
  57 + return [register, organizationTreeActionType];
  58 +}
1 import { useResetOrganizationTree } from './hooks/useOrganization'; 1 import { useResetOrganizationTree } from './hooks/useOrganization';
2 import OrganizationIdTree from './src/OrganizationIdTree.vue'; 2 import OrganizationIdTree from './src/OrganizationIdTree.vue';
  3 +export { useOrganizationTree } from './hooks/useOrganizationTree';
3 4
4 export { OrganizationIdTree, useResetOrganizationTree }; 5 export { OrganizationIdTree, useResetOrganizationTree };
@@ -17,41 +17,41 @@ @@ -17,41 +17,41 @@
17 </div> 17 </div>
18 </div> 18 </div>
19 <div :style="{ width: foldFlag ? '0px' : '100%' }" class="bg-white mr-0 overflow-hidden h-full"> 19 <div :style="{ width: foldFlag ? '0px' : '100%' }" class="bg-white mr-0 overflow-hidden h-full">
20 - <BasicTree  
21 - title="组织列表"  
22 - toolbar  
23 - search  
24 - :clickRowToExpand="false"  
25 - :treeData="treeData"  
26 - :expandedKeys="treeExpandData"  
27 - :replaceFields="{ key: 'id', title: 'name' }"  
28 - :selectedKeys="selectedKeys"  
29 - @select="handleSelect"  
30 - v-bind="$attrs"  
31 - /> 20 + <BasicTree v-bind="getBindData" />
32 </div> 21 </div>
33 </div> 22 </div>
34 </template> 23 </template>
35 <script lang="ts" setup name="OrganizationIdTree"> 24 <script lang="ts" setup name="OrganizationIdTree">
36 - import { onMounted, ref, unref } from 'vue'; 25 + import { computed, onMounted, ref, unref, useAttrs } from 'vue';
37 import { BasicTree, TreeItem } from '/@/components/Tree'; 26 import { BasicTree, TreeItem } from '/@/components/Tree';
38 import { getOrganizationList } from '/@/api/system/system'; 27 import { getOrganizationList } from '/@/api/system/system';
39 import { CaretRightOutlined } from '@ant-design/icons-vue'; 28 import { CaretRightOutlined } from '@ant-design/icons-vue';
40 import { getBoundingClientRect } from '/@/utils/domUtils'; 29 import { getBoundingClientRect } from '/@/utils/domUtils';
  30 + import { BasicTreePropsType, OrganizationTreeActionType } from './types';
  31 + import { OrganizationTreePropsType } from './props';
  32 +
  33 + const props = defineProps<OrganizationTreePropsType>();
  34 +
  35 + const attrs = useAttrs();
41 36
42 const tree = ref<Nullable<HTMLDivElement>>(); 37 const tree = ref<Nullable<HTMLDivElement>>();
43 - const emit = defineEmits(['select']); 38 + const emit = defineEmits(['select', 'register']);
44 const treeData = ref<TreeItem[]>([]); 39 const treeData = ref<TreeItem[]>([]);
45 const selectedKeys = ref<string[]>(); 40 const selectedKeys = ref<string[]>();
46 - const treeExpandData = ref([]); 41 + const treeExpandData = ref<string[]>([]);
  42 +
  43 + const innerProps = ref<OrganizationTreePropsType>({});
  44 +
47 //获取所有父级id 45 //获取所有父级id
48 - function findForAllId(data = [], arr = []) { 46 + function findForAllId(data: Recordable[] = [], arr: string[] = []) {
49 for (const item of data) { 47 for (const item of data) {
50 arr.push(item.id); 48 arr.push(item.id);
51 } 49 }
52 return arr; 50 return arr;
53 } 51 }
54 - function handleSelect(keys) { 52 +
  53 + function handleSelect(keys: string[]) {
  54 + selectedKeys.value = keys;
55 emit('select', keys[0]); 55 emit('select', keys[0]);
56 } 56 }
57 function resetOrganization() { 57 function resetOrganization() {
@@ -81,9 +81,41 @@ @@ -81,9 +81,41 @@
81 81
82 setTreeHeight(); 82 setTreeHeight();
83 }); 83 });
  84 +
84 defineExpose({ 85 defineExpose({
85 resetOrganization, 86 resetOrganization,
86 }); 87 });
  88 +
  89 + const getProps = computed<OrganizationTreePropsType>(() => ({ ...props, ...unref(innerProps) }));
  90 +
  91 + const getBindData = computed(() => {
  92 + return {
  93 + title: '组织列表',
  94 + toolbar: true,
  95 + search: true,
  96 + clickRowToExpand: false,
  97 + treeData: unref(treeData),
  98 + expandedKeys: unref(treeExpandData),
  99 + replaceFields: { key: 'id', title: 'name' },
  100 + selectedKeys: unref(selectedKeys),
  101 + ...attrs,
  102 + ...unref(getProps),
  103 + onSelect: (keys: string[]) => {
  104 + handleSelect(keys);
  105 + unref(getProps)?.onSelect?.(keys?.[0]);
  106 + },
  107 + } as BasicTreePropsType;
  108 + });
  109 +
  110 + const setProps = (props: Partial<OrganizationTreePropsType>) => {
  111 + innerProps.value = { ...(props || {}) };
  112 + };
  113 +
  114 + emit('register', {
  115 + clearSelected: resetOrganization,
  116 + getSelectKey: () => unref(selectedKeys)?.[0],
  117 + setProps,
  118 + } as OrganizationTreeActionType);
87 </script> 119 </script>
88 120
89 <style scoped lang="less"> 121 <style scoped lang="less">
  1 +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 <script setup lang="ts"> 1 <script setup lang="ts">
2 - import { List, Card, Button, PaginationProps, Tooltip } from 'ant-design-vue';  
3 - import { ReloadOutlined } from '@ant-design/icons-vue';  
4 - import { computed, onMounted, reactive, ref, unref } from 'vue';  
5 - import { OrganizationIdTree, useResetOrganizationTree } from '../../common/organizationIdTree'; 2 + import { BasicCardList, useCardList } from '/@/components/CardList';
6 import { 3 import {
7 deleteConfigurationCenter, 4 deleteConfigurationCenter,
8 getPage, 5 getPage,
9 shareConfiguration, 6 shareConfiguration,
10 } from '/@/api/configuration/center/configurationCenter'; 7 } from '/@/api/configuration/center/configurationCenter';
  8 + import { searchFormSchema, ConfigurationPermission } from './center.data';
11 import { ConfigurationCenterItemsModal } from '/@/api/configuration/center/model/configurationCenterModal'; 9 import { ConfigurationCenterItemsModal } from '/@/api/configuration/center/model/configurationCenterModal';
12 - import { PageWrapper } from '/@/components/Page';  
13 - import { BasicForm, useForm } from '/@/components/Form';  
14 - import { ConfigurationPermission, Platform, searchFormSchema } from './center.data';  
15 - import { useMessage } from '/@/hooks/web/useMessage';  
16 import { Authority } from '/@/components/Authority'; 10 import { Authority } from '/@/components/Authority';
17 - import ConfigurationCenterDrawer from './ConfigurationCenterDrawer.vue';  
18 - import { useDrawer } from '/@/components/Drawer';  
19 - import { getBoundingClientRect } from '/@/utils/domUtils'; 11 + import { Button, Card, Tooltip } from 'ant-design-vue';
  12 + import { useRole } from '/@/hooks/business/useRole';
20 import configurationSrc from '/@/assets/icons/configuration.svg'; 13 import configurationSrc from '/@/assets/icons/configuration.svg';
21 - import { cloneDeep } from 'lodash'; 14 + import { Platform } from '../center/center.data';
  15 + import { computed, unref } from 'vue';
  16 + import { createScadaPageLink, ScadaModeEnum } from '../center/help';
  17 + import { useDrawer } from '/@/components/Drawer';
22 import { usePermission } from '/@/hooks/web/usePermission'; 18 import { usePermission } from '/@/hooks/web/usePermission';
23 - import { AuthIcon, CardLayoutButton } from '/@/components/Widget';  
24 - import AuthDropDown from '/@/components/Widget/AuthDropDown.vue';  
25 - import { ShareModal } from '/@/views/common/ShareModal';  
26 - import { ViewTypeNameEnum } from '../../common/ShareModal/config';  
27 - import { useModal } from '/@/components/Modal';  
28 - import { ViewType } from '../../visual/board/config/panelDetail';  
29 - import { useRole } from '/@/hooks/business/useRole';  
30 - import { useClipboard } from '@vueuse/core'; 19 + import { useMessage } from '/@/hooks/web/useMessage';
31 import { Icon } from '/@/components/Icon'; 20 import { Icon } from '/@/components/Icon';
32 - import { createScadaPageLink, ScadaModeEnum } from './help';  
33 -  
34 - const listColumn = ref(5);  
35 -  
36 - const { createMessage } = useMessage();  
37 -  
38 - const { isCustomerUser } = useRole();  
39 -  
40 - const organizationId = ref<Nullable<number>>(null);  
41 -  
42 - const pagination = reactive<PaginationProps>({  
43 - size: 'small',  
44 - showTotal: (total: number) => `共 ${total} 条数据`,  
45 - current: 1,  
46 - pageSize: unref(listColumn) * 2,  
47 - onChange: (page: number) => {  
48 - pagination.current = page;  
49 - getListData(); 21 + import { AuthIcon, AuthDropDown } from '/@/components/Widget';
  22 + import { cloneDeep } from 'lodash-es';
  23 + import { OrganizationIdTree, useOrganizationTree } from '../../common/organizationIdTree';
  24 + import ConfigurationCenterDrawer from './ConfigurationCenterDrawer.vue';
  25 + import { useClipboard } from '@vueuse/core';
  26 + import { useModal } from '/@/components/Modal';
  27 + import { ShareModal } from '/@/views/common/ShareModal';
  28 + import { ViewTypeEnum, ViewTypeNameEnum } from '../../common/ShareModal/config';
  29 +
  30 + const [register, { reload }] = useCardList({
  31 + api: getPage,
  32 + useSearchForm: true,
  33 + title: '组态列表',
  34 + gutter: 4,
  35 + formConfig: {
  36 + schemas: searchFormSchema,
  37 + labelWidth: 80,
  38 + resetFunc: async () => {
  39 + clearSelected();
  40 + },
  41 + },
  42 + beforeFetch: async (params: Recordable) => {
  43 + return { ...params, organizationId: getSelectKey(), isTemplate: 0 };
50 }, 44 },
51 }); 45 });
52 46
53 - const loading = ref(false);  
54 -  
55 - const dataSource = ref<ConfigurationCenterItemsModal[]>([]);  
56 -  
57 - const [registerForm, { getFieldsValue }] = useForm({  
58 - schemas: searchFormSchema,  
59 - showAdvancedButton: true,  
60 - labelWidth: 100,  
61 - compact: true,  
62 - resetFunc: () => {  
63 - resetFn();  
64 - organizationId.value = null;  
65 - return getListData();  
66 - },  
67 - submitFunc: async () => {  
68 - const value = getFieldsValue();  
69 - getListData(value); 47 + const [registerOrgTree, { getSelectKey, clearSelected }] = useOrganizationTree({
  48 + onSelect: () => {
  49 + reload();
70 }, 50 },
71 }); 51 });
72 52
73 - async function getListData(value: Recordable = {}) {  
74 - try {  
75 - loading.value = true;  
76 - const pageSize = unref(listColumn) * 2;  
77 - const { items, total } = await getPage({  
78 - organizationId: unref(organizationId),  
79 - ...value,  
80 - isTemplate: 0,  
81 - page: pagination.current!,  
82 - pageSize,  
83 - });  
84 -  
85 - dataSource.value = items;  
86 - Object.assign(pagination, { total, pageSize });  
87 - } catch (error) {  
88 - } finally {  
89 - loading.value = false;  
90 - }  
91 - } 53 + const [registerDrawer, { openDrawer }] = useDrawer();
92 54
93 - onMounted(() => {  
94 - getListData();  
95 - }); 55 + const [registerShareModal, { openModal }] = useModal();
96 56
97 - const searchInfo = reactive<Recordable>({});  
98 - const { organizationIdTreeRef, resetFn } = useResetOrganizationTree(searchInfo);  
99 - const handleSelect = (orgId: number) => {  
100 - organizationId.value = orgId;  
101 - getListData(); 57 + const handleOpenShareModal = (record: ConfigurationCenterItemsModal) => {
  58 + openModal(true, { record, href: createShareUrl(record) });
102 }; 59 };
103 60
104 - const [registerDrawer, { openDrawer }] = useDrawer(); 61 + const { createMessage } = useMessage();
  62 +
  63 + const { isCustomerUser } = useRole();
105 64
106 const { hasPermission } = usePermission(); 65 const { hasPermission } = usePermission();
107 66
@@ -141,19 +100,6 @@ @@ -141,19 +100,6 @@
141 createScadaPageLink(record, ScadaModeEnum.DESIGN); 100 createScadaPageLink(record, ScadaModeEnum.DESIGN);
142 }; 101 };
143 102
144 - const handleDelete = async (record: ConfigurationCenterItemsModal) => {  
145 - try {  
146 - await deleteConfigurationCenter([record.id]);  
147 - createMessage.success('删除成功');  
148 - await getListData();  
149 - } catch (error) {}  
150 - };  
151 -  
152 - const handleCardLayoutChange = () => {  
153 - pagination.current = 1;  
154 - getListData();  
155 - };  
156 -  
157 const createShareUrl = (record: ConfigurationCenterItemsModal) => { 103 const createShareUrl = (record: ConfigurationCenterItemsModal) => {
158 return createScadaPageLink(record, ScadaModeEnum.SHARE, false); 104 return createScadaPageLink(record, ScadaModeEnum.SHARE, false);
159 }; 105 };
@@ -168,209 +114,138 @@ @@ -168,209 +114,138 @@
168 } 114 }
169 }; 115 };
170 116
171 - const [registerShareModal, { openModal }] = useModal();  
172 -  
173 - const handleOpenShareModal = (record: ConfigurationCenterItemsModal) => {  
174 - openModal(true, { record, href: createShareUrl(record) }); 117 + const handleDelete = async (record: ConfigurationCenterItemsModal) => {
  118 + try {
  119 + await deleteConfigurationCenter([record.id]);
  120 + createMessage.success('删除成功');
  121 + await reload();
  122 + } catch (error) {}
175 }; 123 };
176 -  
177 - const listEl = ref<Nullable<ComponentElRef>>(null);  
178 -  
179 - onMounted(() => {  
180 - const clientHeight = document.documentElement.clientHeight;  
181 - const rect = getBoundingClientRect(unref(listEl)!.$el! as HTMLElement) as DOMRect;  
182 - // margin-top 24 height 24  
183 - const paginationHeight = 24 + 24 + 8;  
184 - // list pading top 8 maring-top 8 extra slot 56  
185 - const listContainerMarginBottom = 8 + 8 + 56;  
186 - const listContainerHeight =  
187 - clientHeight - rect.top - paginationHeight - listContainerMarginBottom;  
188 - const listContainerEl = (unref(listEl)!.$el as HTMLElement).querySelector(  
189 - '.ant-spin-container'  
190 - ) as HTMLElement;  
191 - listContainerEl &&  
192 - (listContainerEl.style.height = listContainerHeight + 'px') &&  
193 - (listContainerEl.style.overflowY = 'auto') &&  
194 - (listContainerEl.style.overflowX = 'hidden');  
195 - });  
196 </script> 124 </script>
197 125
198 <template> 126 <template>
199 - <PageWrapper dense contentFullHeight contentClass="flex">  
200 - <OrganizationIdTree @select="handleSelect" ref="organizationIdTreeRef" />  
201 - <section class="flex-auto p-4 w-3/4 xl:w-4/5 w-full configuration-list">  
202 - <div class="flex-auto w-full bg-light-50 dark:bg-dark-900 p-4">  
203 - <BasicForm @register="registerForm" />  
204 - </div>  
205 - <List  
206 - ref="listEl"  
207 - :loading="loading"  
208 - class="flex-auto bg-light-50 dark:bg-dark-900 !p-2 !mt-4"  
209 - position="bottom"  
210 - :pagination="pagination"  
211 - :data-source="dataSource"  
212 - :grid="{ gutter: 4, column: listColumn }"  
213 - >  
214 - <template #header>  
215 - <div class="flex gap-3 justify-end">  
216 - <Authority v-if="!isCustomerUser" :value="ConfigurationPermission.CREATE">  
217 - <Button type="primary" @click="handleCreateOrUpdate()">新增组态</Button>  
218 - </Authority>  
219 - <CardLayoutButton v-model:value="listColumn" @change="handleCardLayoutChange" />  
220 - <Tooltip title="刷新">  
221 - <Button type="primary" @click="getListData">  
222 - <ReloadOutlined />  
223 - </Button>  
224 - </Tooltip>  
225 - </div>  
226 - </template>  
227 - <template #renderItem="{ item }">  
228 - <List.Item>  
229 - <Card  
230 - :style="{  
231 - '--viewType': item.viewType === ViewType.PUBLIC_VIEW ? '#1890ff' : '#faad14',  
232 - }"  
233 - hoverable  
234 - class="card-container" 127 + <section class="flex w-full h-full">
  128 + <OrganizationIdTree @register="registerOrgTree" />
  129 + <BasicCardList class="flex-auto p-4 w-3/4 xl:w-4/5 w-full" @register="register">
  130 + <template #toolbar>
  131 + <div class="flex gap-3 justify-end">
  132 + <Authority v-if="!isCustomerUser" :value="ConfigurationPermission.CREATE">
  133 + <Button type="primary" @click="handleCreateOrUpdate()">新增组态</Button>
  134 + </Authority>
  135 + </div>
  136 + </template>
  137 + <template #renderItem="{ item }: CardListRenderItem<ConfigurationCenterItemsModal>">
  138 + <Card
  139 + :style="{
  140 + '--viewType': item.viewType === ViewTypeEnum.PUBLIC_VIEW ? '#faad14' : '#1890ff',
  141 + }"
  142 + hoverable
  143 + class="card-container"
  144 + >
  145 + <template #cover>
  146 + <div
  147 + class="img-container h-full w-full !flex justify-center items-center text-center p-1 relative bg-light-50 rounded-tl-10xl"
235 > 148 >
236 - <template #cover>  
237 - <div  
238 - class="img-container h-full w-full !flex justify-center items-center text-center p-1 relative"  
239 - >  
240 - <img  
241 - class="w-full h-36"  
242 - alt="example"  
243 - :src="item.thumbnail || configurationSrc"  
244 - @click="handlePreview(item)" 149 + <img
  150 + class="w-full max-h-32 h-32 object-contain !rounded-tl-10xl"
  151 + alt="example"
  152 + :src="item.thumbnail || configurationSrc"
  153 + @click="handlePreview(item)"
  154 + />
  155 + <span class="absolute top-0 left-0 text-light-50 transform -rotate-45 translate-y-1">
  156 + {{ ViewTypeNameEnum[item.viewType] }}
  157 + </span>
  158 + </div>
  159 + </template>
  160 + <template class="ant-card-actions" #actions>
  161 + <Tooltip title="预览">
  162 + <AuthIcon
  163 + :auth="ConfigurationPermission.PREVIEW"
  164 + class="!text-lg"
  165 + icon="ant-design:eye-outlined"
  166 + @click="handlePreview(item)"
  167 + />
  168 + </Tooltip>
  169 + <Tooltip v-if="!isCustomerUser" title="设计">
  170 + <AuthIcon
  171 + :auth="ConfigurationPermission.DESIGN"
  172 + class="!text-lg"
  173 + icon="ant-design:edit-outlined"
  174 + @click="handleDesign(item)"
  175 + />
  176 + </Tooltip>
  177 + <Tooltip title="点击复制分享链接">
  178 + <AuthIcon
  179 + :auth="ConfigurationPermission.SHARE"
  180 + :disabled="!item.publicId"
  181 + class="!text-lg"
  182 + icon="ant-design:share-alt-outlined"
  183 + @click="handleCreateShareUrl(item)"
  184 + />
  185 + </Tooltip>
  186 + <AuthDropDown
  187 + v-if="!isCustomerUser"
  188 + :dropMenuList="[
  189 + {
  190 + text: '分享',
  191 + auth: ConfigurationPermission.SHARE,
  192 + icon: 'ant-design:share-alt-outlined',
  193 + event: '',
  194 + onClick: handleOpenShareModal.bind(null, item),
  195 + },
  196 + {
  197 + text: '编辑',
  198 + auth: ConfigurationPermission.UPDATE,
  199 + icon: 'clarity:note-edit-line',
  200 + event: '',
  201 + onClick: handleCreateOrUpdate.bind(null, item),
  202 + },
  203 + {
  204 + text: '删除',
  205 + auth: ConfigurationPermission.DELETE,
  206 + icon: 'ant-design:delete-outlined',
  207 + event: '',
  208 + popconfirm: {
  209 + title: '是否确认删除操作?',
  210 + onConfirm: handleDelete.bind(null, item),
  211 + },
  212 + },
  213 + ]"
  214 + :trigger="['hover']"
  215 + />
  216 + </template>
  217 + <Card.Meta>
  218 + <template #title>
  219 + <span class="truncate">{{ item.name }}</span>
  220 + </template>
  221 + <template #description>
  222 + <div class="truncate h-11">
  223 + <div class="truncate flex justify-between items-center">
  224 + <div>{{ item.organizationDTO?.name }}</div>
  225 + <Icon
  226 + :icon="
  227 + item.platform === Platform.PC
  228 + ? 'ri:computer-line'
  229 + : 'clarity:mobile-phone-solid'
  230 + "
245 /> 231 />
246 - <span  
247 - class="absolute top-0 left-0 text-light-50 transform -rotate-45 translate-y-1"  
248 - >  
249 - {{ ViewTypeNameEnum[item.viewType] || ViewTypeNameEnum.PRIVATE_VIEW }}  
250 - </span>  
251 </div> 232 </div>
252 - </template>  
253 - <template class="ant-card-actions" #actions>  
254 - <Tooltip title="预览">  
255 - <AuthIcon  
256 - :auth="ConfigurationPermission.PREVIEW"  
257 - class="!text-lg"  
258 - icon="ant-design:eye-outlined"  
259 - @click="handlePreview(item)"  
260 - />  
261 - </Tooltip>  
262 - <Tooltip v-if="!isCustomerUser" title="设计">  
263 - <AuthIcon  
264 - :auth="ConfigurationPermission.DESIGN"  
265 - class="!text-lg"  
266 - icon="ant-design:edit-outlined"  
267 - @click="handleDesign(item)"  
268 - />  
269 - </Tooltip>  
270 - <Tooltip title="点击复制分享链接">  
271 - <AuthIcon  
272 - :auth="ConfigurationPermission.SHARE"  
273 - :disabled="!item.publicId"  
274 - class="!text-lg"  
275 - icon="ant-design:share-alt-outlined"  
276 - @click="handleCreateShareUrl(item)"  
277 - />  
278 - </Tooltip>  
279 - <AuthDropDown  
280 - v-if="!isCustomerUser"  
281 - :dropMenuList="[  
282 - {  
283 - text: '分享',  
284 - auth: ConfigurationPermission.SHARE,  
285 - icon: 'ant-design:share-alt-outlined',  
286 - event: '',  
287 - onClick: handleOpenShareModal.bind(null, item),  
288 - },  
289 - {  
290 - text: '编辑',  
291 - auth: ConfigurationPermission.UPDATE,  
292 - icon: 'clarity:note-edit-line',  
293 - event: '',  
294 - onClick: handleCreateOrUpdate.bind(null, item),  
295 - },  
296 - {  
297 - text: '删除',  
298 - auth: ConfigurationPermission.DELETE,  
299 - icon: 'ant-design:delete-outlined',  
300 - event: '',  
301 - popconfirm: {  
302 - title: '是否确认删除操作?',  
303 - onConfirm: handleDelete.bind(null, item),  
304 - },  
305 - },  
306 - ]"  
307 - :trigger="['hover']"  
308 - />  
309 - </template>  
310 - <Card.Meta>  
311 - <template #title>  
312 - <span class="truncate">{{ item.name }}</span>  
313 - </template>  
314 - <template #description>  
315 - <div class="truncate h-11">  
316 - <div class="truncate flex justify-between items-center">  
317 - <div>{{ item.organizationDTO.name }}</div>  
318 - <Icon  
319 - :icon="  
320 - item.platform === Platform.PC  
321 - ? 'ri:computer-line'  
322 - : 'clarity:mobile-phone-solid'  
323 - "  
324 - />  
325 - </div>  
326 - <div class="truncate">{{ item.remark || '' }} </div>  
327 - </div>  
328 - </template>  
329 - </Card.Meta>  
330 - </Card>  
331 - </List.Item>  
332 - </template>  
333 - </List>  
334 - </section>  
335 - <ConfigurationCenterDrawer @register="registerDrawer" @success="getListData" />  
336 - <ShareModal  
337 - @register="registerShareModal"  
338 - :shareApi="shareConfiguration"  
339 - @success="getListData"  
340 - />  
341 - </PageWrapper> 233 + <div class="truncate">{{ item.remark || '' }} </div>
  234 + </div>
  235 + </template>
  236 + </Card.Meta>
  237 + </Card>
  238 + </template>
  239 + </BasicCardList>
  240 + <ConfigurationCenterDrawer @register="registerDrawer" @success="reload()" />
  241 + <ShareModal @register="registerShareModal" :shareApi="shareConfiguration" @success="reload" />
  242 + </section>
342 </template> 243 </template>
343 244
344 <style lang="less" scoped> 245 <style lang="less" scoped>
345 - .configuration-list:deep(.ant-list-header) {  
346 - border-bottom: none !important;  
347 - }  
348 -  
349 - .configuration-list:deep(.ant-list-pagination) {  
350 - height: 24px;  
351 - }  
352 -  
353 - .configuration-list:deep(.ant-card-body) {  
354 - padding: 16px !important;  
355 - }  
356 -  
357 - .configuration-list:deep(.ant-list-empty-text) {  
358 - @apply w-full h-full flex justify-center items-center;  
359 - }  
360 -  
361 .card-container { 246 .card-container {
362 - // background-color: red;  
363 - .img-container {  
364 - border-top-left-radius: 80px;  
365 - background-color: #fff;  
366 -  
367 - img {  
368 - border-top-left-radius: 80px;  
369 - } 247 + :deep(.ant-card-cover) {
  248 + background-color: var(--viewType);
370 } 249 }
371 } 250 }
372 -  
373 - .card-container:deep(.ant-card-cover) {  
374 - background-color: var(--viewType);  
375 - }  
376 </style> 251 </style>
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 <script setup lang="ts"> 1 <script setup lang="ts">
2 - import { List, Card, Button, PaginationProps, Tooltip } from 'ant-design-vue';  
3 - import { ReloadOutlined } from '@ant-design/icons-vue';  
4 - import { computed, onMounted, reactive, ref, unref } from 'vue';  
5 - import { OrganizationIdTree, useResetOrganizationTree } from '../../common/organizationIdTree'; 2 + import { BasicCardList, useCardList } from '/@/components/CardList';
6 import { 3 import {
7 deleteConfigurationCenter, 4 deleteConfigurationCenter,
8 getPage, 5 getPage,
9 } from '/@/api/configuration/center/configurationCenter'; 6 } from '/@/api/configuration/center/configurationCenter';
10 - import { ConfigurationCenterItemsModal } from '/@/api/configuration/center/model/configurationCenterModal';  
11 - import { PageWrapper } from '/@/components/Page';  
12 - import { BasicForm, useForm } from '/@/components/Form';  
13 import { searchFormSchema, ConfigurationTemplatePermission } from './center.data'; 7 import { searchFormSchema, ConfigurationTemplatePermission } from './center.data';
14 - import { useMessage } from '/@/hooks/web/useMessage'; 8 + import { ConfigurationCenterItemsModal } from '/@/api/configuration/center/model/configurationCenterModal';
15 import { Authority } from '/@/components/Authority'; 9 import { Authority } from '/@/components/Authority';
16 - import ConfigurationCenterDrawer from './ConfigurationCenterDrawer.vue';  
17 - import { useDrawer } from '/@/components/Drawer';  
18 - import { getBoundingClientRect } from '/@/utils/domUtils'; 10 + import { Button, Card, Tooltip } from 'ant-design-vue';
  11 + import { useRole } from '/@/hooks/business/useRole';
19 import configurationSrc from '/@/assets/icons/configuration.svg'; 12 import configurationSrc from '/@/assets/icons/configuration.svg';
20 - import { cloneDeep } from 'lodash'; 13 + import { Platform } from '../center/center.data';
  14 + import { computed, unref } from 'vue';
  15 + import { createScadaPageLink, ScadaModeEnum } from '../center/help';
  16 + import { useDrawer } from '/@/components/Drawer';
21 import { usePermission } from '/@/hooks/web/usePermission'; 17 import { usePermission } from '/@/hooks/web/usePermission';
22 - import { AuthIcon, CardLayoutButton } from '/@/components/Widget';  
23 - import AuthDropDown from '/@/components/Widget/AuthDropDown.vue';  
24 - import { useRole } from '/@/hooks/business/useRole'; 18 + import { useMessage } from '/@/hooks/web/useMessage';
25 import { Icon } from '/@/components/Icon'; 19 import { Icon } from '/@/components/Icon';
26 - import { createScadaPageLink, ScadaModeEnum } from './help';  
27 - import { Platform } from '../center/center.data';  
28 -  
29 - const listColumn = ref(5);  
30 -  
31 - const { createMessage } = useMessage();  
32 -  
33 - const { isCustomerUser } = useRole();  
34 -  
35 - const organizationId = ref<Nullable<number>>(null); 20 + import { AuthIcon, AuthDropDown } from '/@/components/Widget';
  21 + import { cloneDeep } from 'lodash-es';
  22 + import { OrganizationIdTree, useOrganizationTree } from '../../common/organizationIdTree';
  23 + import ConfigurationCenterDrawer from './ConfigurationCenterDrawer.vue';
36 24
37 - const pagination = reactive<PaginationProps>({  
38 - size: 'small',  
39 - showTotal: (total: number) => `共 ${total} 条数据`,  
40 - current: 1,  
41 - pageSize: unref(listColumn) * 2,  
42 - onChange: (page: number) => {  
43 - pagination.current = page;  
44 - getListData(); 25 + const [register, { reload }] = useCardList({
  26 + api: getPage,
  27 + useSearchForm: true,
  28 + title: '模版列表',
  29 + gutter: 4,
  30 + formConfig: {
  31 + schemas: searchFormSchema,
  32 + labelWidth: 80,
  33 + resetFunc: async () => {
  34 + clearSelected();
  35 + },
  36 + },
  37 + beforeFetch: async (params: Recordable) => {
  38 + return { ...params, organizationId: getSelectKey(), isTemplate: 1 };
45 }, 39 },
46 }); 40 });
47 41
48 - const loading = ref(false);  
49 -  
50 - const dataSource = ref<ConfigurationCenterItemsModal[]>([]);  
51 -  
52 - const [registerForm, { getFieldsValue }] = useForm({  
53 - schemas: searchFormSchema,  
54 - showAdvancedButton: true,  
55 - labelWidth: 100,  
56 - compact: true,  
57 - resetFunc: () => {  
58 - resetFn();  
59 - organizationId.value = null;  
60 - return getListData();  
61 - },  
62 - submitFunc: async () => {  
63 - const value = getFieldsValue();  
64 - getListData(value); 42 + const [registerOrgTree, { getSelectKey, clearSelected }] = useOrganizationTree({
  43 + onSelect: () => {
  44 + reload();
65 }, 45 },
66 }); 46 });
67 47
68 - async function getListData(value: Recordable = {}) {  
69 - try {  
70 - loading.value = true;  
71 - const pageSize = unref(listColumn) * 2;  
72 - const { items, total } = await getPage({  
73 - organizationId: unref(organizationId),  
74 - ...value,  
75 - page: pagination.current!,  
76 - pageSize,  
77 - isTemplate: 1,  
78 - });  
79 -  
80 - dataSource.value = items;  
81 - Object.assign(pagination, { total, pageSize });  
82 - } catch (error) {  
83 - } finally {  
84 - loading.value = false;  
85 - }  
86 - }  
87 -  
88 - onMounted(() => {  
89 - getListData();  
90 - }); 48 + const [registerDrawer, { openDrawer }] = useDrawer();
91 49
92 - const searchInfo = reactive<Recordable>({});  
93 - const { organizationIdTreeRef, resetFn } = useResetOrganizationTree(searchInfo);  
94 - const handleSelect = (orgId: number) => {  
95 - organizationId.value = orgId;  
96 - getListData();  
97 - }; 50 + const { createMessage } = useMessage();
98 51
99 - const [registerDrawer, { openDrawer }] = useDrawer(); 52 + const { isCustomerUser } = useRole();
100 53
101 const { hasPermission } = usePermission(); 54 const { hasPermission } = usePermission();
102 55
@@ -136,191 +89,106 @@ @@ -136,191 +89,106 @@
136 try { 89 try {
137 await deleteConfigurationCenter([record.id]); 90 await deleteConfigurationCenter([record.id]);
138 createMessage.success('删除成功'); 91 createMessage.success('删除成功');
139 - await getListData(); 92 + await reload();
140 } catch (error) {} 93 } catch (error) {}
141 }; 94 };
142 -  
143 - const handleCardLayoutChange = () => {  
144 - pagination.current = 1;  
145 - getListData();  
146 - };  
147 -  
148 - const listEl = ref<Nullable<ComponentElRef>>(null);  
149 -  
150 - onMounted(() => {  
151 - const clientHeight = document.documentElement.clientHeight;  
152 - const rect = getBoundingClientRect(unref(listEl)!.$el! as HTMLElement) as DOMRect;  
153 - // margin-top 24 height 24  
154 - const paginationHeight = 24 + 24 + 8;  
155 - // list pading top 8 maring-top 8 extra slot 56  
156 - const listContainerMarginBottom = 8 + 8 + 56;  
157 - const listContainerHeight =  
158 - clientHeight - rect.top - paginationHeight - listContainerMarginBottom;  
159 - const listContainerEl = (unref(listEl)!.$el as HTMLElement).querySelector(  
160 - '.ant-spin-container'  
161 - ) as HTMLElement;  
162 - listContainerEl &&  
163 - (listContainerEl.style.height = listContainerHeight + 'px') &&  
164 - (listContainerEl.style.overflowY = 'auto') &&  
165 - (listContainerEl.style.overflowX = 'hidden');  
166 - });  
167 </script> 95 </script>
168 96
169 <template> 97 <template>
170 - <PageWrapper dense contentFullHeight contentClass="flex">  
171 - <OrganizationIdTree @select="handleSelect" ref="organizationIdTreeRef" />  
172 - <section class="flex-auto p-4 w-3/4 xl:w-4/5 w-full configuration-list">  
173 - <div class="flex-auto w-full bg-light-50 dark:bg-dark-900 p-4">  
174 - <BasicForm @register="registerForm" />  
175 - </div>  
176 - <List  
177 - ref="listEl"  
178 - :loading="loading"  
179 - class="flex-auto bg-light-50 dark:bg-dark-900 !p-2 !mt-4"  
180 - position="bottom"  
181 - :pagination="pagination"  
182 - :data-source="dataSource"  
183 - :grid="{ gutter: 4, column: listColumn }"  
184 - >  
185 - <template #header>  
186 - <div class="flex gap-3 justify-end">  
187 - <Authority v-if="!isCustomerUser" :value="ConfigurationTemplatePermission.CREATE">  
188 - <Button type="primary" @click="handleCreateOrUpdate()">新增模板</Button>  
189 - </Authority>  
190 - <CardLayoutButton v-model:value="listColumn" @change="handleCardLayoutChange" />  
191 - <Tooltip title="刷新">  
192 - <Button type="primary" @click="getListData">  
193 - <ReloadOutlined />  
194 - </Button>  
195 - </Tooltip>  
196 - </div>  
197 - </template>  
198 - <template #renderItem="{ item }">  
199 - <List.Item>  
200 - <Card  
201 - :style="{  
202 - '--viewType': '#1890ff',  
203 - }"  
204 - hoverable  
205 - class="card-container" 98 + <section class="flex w-full h-full">
  99 + <OrganizationIdTree @register="registerOrgTree" />
  100 + <BasicCardList class="flex-auto p-4 w-3/4 xl:w-4/5 w-full" @register="register">
  101 + <template #toolbar>
  102 + <div class="flex gap-3 justify-end">
  103 + <Authority v-if="!isCustomerUser" :value="ConfigurationTemplatePermission.CREATE">
  104 + <Button type="primary" @click="handleCreateOrUpdate()">新增模版</Button>
  105 + </Authority>
  106 + </div>
  107 + </template>
  108 + <template #renderItem="{ item }: CardListRenderItem<ConfigurationCenterItemsModal>">
  109 + <Card
  110 + :style="{
  111 + '--viewType': '#1890ff',
  112 + }"
  113 + hoverable
  114 + class="card-container"
  115 + >
  116 + <template #cover>
  117 + <div
  118 + class="img-container h-full w-full !flex justify-center items-center text-center p-1 relative"
206 > 119 >
207 - <template #cover>  
208 - <div  
209 - class="img-container h-full w-full !flex justify-center items-center text-center p-1 relative"  
210 - >  
211 - <img  
212 - class="w-full h-36"  
213 - alt="example"  
214 - :src="item.thumbnail || configurationSrc"  
215 - @click="handlePreview(item)" 120 + <img
  121 + class="w-full max-h-32 h-32"
  122 + alt="example"
  123 + :src="item.thumbnail || configurationSrc"
  124 + @click="handlePreview(item)"
  125 + />
  126 + </div>
  127 + </template>
  128 + <template class="ant-card-actions" #actions>
  129 + <Tooltip title="预览">
  130 + <AuthIcon
  131 + :auth="ConfigurationTemplatePermission.PREVIEW"
  132 + class="!text-lg"
  133 + icon="ant-design:eye-outlined"
  134 + @click="handlePreview(item)"
  135 + />
  136 + </Tooltip>
  137 + <Tooltip v-if="!isCustomerUser" title="设计">
  138 + <AuthIcon
  139 + :auth="ConfigurationTemplatePermission.DESIGN"
  140 + class="!text-lg"
  141 + icon="ant-design:edit-outlined"
  142 + @click="handleDesign(item)"
  143 + />
  144 + </Tooltip>
  145 + <AuthDropDown
  146 + v-if="!isCustomerUser"
  147 + :dropMenuList="[
  148 + {
  149 + text: '编辑',
  150 + auth: ConfigurationTemplatePermission.UPDATE,
  151 + icon: 'clarity:note-edit-line',
  152 + event: '',
  153 + onClick: handleCreateOrUpdate.bind(null, item),
  154 + },
  155 + {
  156 + text: '删除',
  157 + auth: ConfigurationTemplatePermission.DELETE,
  158 + icon: 'ant-design:delete-outlined',
  159 + event: '',
  160 + popconfirm: {
  161 + title: '是否确认删除操作?',
  162 + onConfirm: handleDelete.bind(null, item),
  163 + },
  164 + },
  165 + ]"
  166 + :trigger="['hover']"
  167 + />
  168 + </template>
  169 + <Card.Meta>
  170 + <template #title>
  171 + <span class="truncate">{{ item.name }}</span>
  172 + </template>
  173 + <template #description>
  174 + <div class="truncate h-11">
  175 + <div class="truncate flex justify-between items-center">
  176 + <div>{{ item.organizationDTO?.name }}</div>
  177 + <Icon
  178 + :icon="
  179 + item.platform === Platform.PC
  180 + ? 'ri:computer-line'
  181 + : 'clarity:mobile-phone-solid'
  182 + "
216 /> 183 />
217 - <span  
218 - class="absolute top-0 left-0 text-light-50 transform -rotate-45 translate-y-1"  
219 - >  
220 - 母版  
221 - </span>  
222 </div> 184 </div>
223 - </template>  
224 - <template class="ant-card-actions" #actions>  
225 - <Tooltip title="预览">  
226 - <AuthIcon  
227 - :auth="ConfigurationTemplatePermission.PREVIEW"  
228 - class="!text-lg"  
229 - icon="ant-design:eye-outlined"  
230 - @click="handlePreview(item)"  
231 - />  
232 - </Tooltip>  
233 - <Tooltip v-if="!isCustomerUser" title="设计">  
234 - <AuthIcon  
235 - :auth="ConfigurationTemplatePermission.DESIGN"  
236 - class="!text-lg"  
237 - icon="ant-design:edit-outlined"  
238 - @click="handleDesign(item)"  
239 - />  
240 - </Tooltip>  
241 - <AuthDropDown  
242 - v-if="!isCustomerUser"  
243 - :dropMenuList="[  
244 - {  
245 - text: '编辑',  
246 - auth: ConfigurationTemplatePermission.UPDATE,  
247 - icon: 'clarity:note-edit-line',  
248 - event: '',  
249 - onClick: handleCreateOrUpdate.bind(null, item),  
250 - },  
251 - {  
252 - text: '删除',  
253 - auth: ConfigurationTemplatePermission.DELETE,  
254 - icon: 'ant-design:delete-outlined',  
255 - event: '',  
256 - popconfirm: {  
257 - title: '是否确认删除操作?',  
258 - onConfirm: handleDelete.bind(null, item),  
259 - },  
260 - },  
261 - ]"  
262 - :trigger="['hover']"  
263 - />  
264 - </template>  
265 - <Card.Meta>  
266 - <template #title>  
267 - <span class="truncate">{{ item.name }}</span>  
268 - </template>  
269 - <template #description>  
270 - <div class="truncate h-11">  
271 - <div class="truncate flex justify-between items-center">  
272 - <div>{{ item.organizationDTO?.name }}</div>  
273 - <Icon  
274 - :icon="  
275 - item.platform === Platform.PC  
276 - ? 'ri:computer-line'  
277 - : 'clarity:mobile-phone-solid'  
278 - "  
279 - />  
280 - </div>  
281 - <div class="truncate">{{ item.remark || '' }} </div>  
282 - </div>  
283 - </template>  
284 - </Card.Meta>  
285 - </Card>  
286 - </List.Item>  
287 - </template>  
288 - </List>  
289 - </section>  
290 - <ConfigurationCenterDrawer @register="registerDrawer" @success="getListData" />  
291 - </PageWrapper> 185 + <div class="truncate">{{ item.remark || '' }} </div>
  186 + </div>
  187 + </template>
  188 + </Card.Meta>
  189 + </Card>
  190 + </template>
  191 + </BasicCardList>
  192 + <ConfigurationCenterDrawer @register="registerDrawer" @success="reload()" />
  193 + </section>
292 </template> 194 </template>
293 -  
294 -<style lang="less" scoped>  
295 - .configuration-list:deep(.ant-list-header) {  
296 - border-bottom: none !important;  
297 - }  
298 -  
299 - .configuration-list:deep(.ant-list-pagination) {  
300 - height: 24px;  
301 - }  
302 -  
303 - .configuration-list:deep(.ant-card-body) {  
304 - padding: 16px !important;  
305 - }  
306 -  
307 - .configuration-list:deep(.ant-list-empty-text) {  
308 - @apply w-full h-full flex justify-center items-center;  
309 - }  
310 -  
311 - .card-container {  
312 - // background-color: red;  
313 - .img-container {  
314 - border-top-left-radius: 80px;  
315 - background-color: #fff;  
316 -  
317 - img {  
318 - border-top-left-radius: 80px;  
319 - }  
320 - }  
321 - }  
322 -  
323 - .card-container:deep(.ant-card-cover) {  
324 - background-color: var(--viewType);  
325 - }  
326 -</style>  
1 <script setup lang="ts"> 1 <script setup lang="ts">
2 - import { List, Card, Button, PaginationProps, Tooltip } from 'ant-design-vue';  
3 - import { ReloadOutlined } from '@ant-design/icons-vue';  
4 - import { computed, onMounted, reactive, ref, unref } from 'vue';  
5 - import { OrganizationIdTree, useResetOrganizationTree } from '../common/organizationIdTree'; 2 + import { Card, Button, Tooltip } from 'ant-design-vue';
  3 + import { computed, unref } from 'vue';
  4 + import { OrganizationIdTree, useOrganizationTree } from '../common/organizationIdTree';
6 import { 5 import {
7 bigScreenCancelPublish, 6 bigScreenCancelPublish,
8 bigScreenPublish, 7 bigScreenPublish,
@@ -12,17 +11,15 @@ @@ -12,17 +11,15 @@
12 } from '/@/api/bigscreen/center/bigscreenCenter'; 11 } from '/@/api/bigscreen/center/bigscreenCenter';
13 import { BigScreenCenterItemsModel } from '/@/api/bigscreen/center/model/bigscreenCenterModel'; 12 import { BigScreenCenterItemsModel } from '/@/api/bigscreen/center/model/bigscreenCenterModel';
14 import { PageWrapper } from '/@/components/Page'; 13 import { PageWrapper } from '/@/components/Page';
15 - import { BasicForm, useForm } from '/@/components/Form';  
16 import { ConfigurationPermission, searchFormSchema } from './config'; 14 import { ConfigurationPermission, searchFormSchema } from './config';
17 import { useMessage } from '/@/hooks/web/useMessage'; 15 import { useMessage } from '/@/hooks/web/useMessage';
18 import { Authority } from '/@/components/Authority'; 16 import { Authority } from '/@/components/Authority';
19 import ConfigurationCenterDrawer from './BigScreenDrawer.vue'; 17 import ConfigurationCenterDrawer from './BigScreenDrawer.vue';
20 import { useDrawer } from '/@/components/Drawer'; 18 import { useDrawer } from '/@/components/Drawer';
21 - import { getBoundingClientRect } from '/@/utils/domUtils';  
22 import configurationSrc from '/@/assets/icons/configuration.svg'; 19 import configurationSrc from '/@/assets/icons/configuration.svg';
23 import { cloneDeep } from 'lodash'; 20 import { cloneDeep } from 'lodash';
24 import { useGlobSetting } from '/@/hooks/setting'; 21 import { useGlobSetting } from '/@/hooks/setting';
25 - import { AuthIcon, CardLayoutButton } from '/@/components/Widget'; 22 + import { AuthIcon } from '/@/components/Widget';
26 import AuthDropDown from '/@/components/Widget/AuthDropDown.vue'; 23 import AuthDropDown from '/@/components/Widget/AuthDropDown.vue';
27 import { PublicApiDrawer } from './publicApi/index'; 24 import { PublicApiDrawer } from './publicApi/index';
28 import { useModal } from '/@/components/Modal'; 25 import { useModal } from '/@/components/Modal';
@@ -33,79 +30,37 @@ @@ -33,79 +30,37 @@
33 import { RoleEnum } from '/@/enums/roleEnum'; 30 import { RoleEnum } from '/@/enums/roleEnum';
34 import { useRole } from '/@/hooks/business/useRole'; 31 import { useRole } from '/@/hooks/business/useRole';
35 import { useClipboard } from '@vueuse/core'; 32 import { useClipboard } from '@vueuse/core';
  33 + import { BasicCardList, useCardList } from '/@/components/CardList';
36 34
37 - const listColumn = ref(5);  
38 -  
39 - const { createMessage } = useMessage();  
40 -  
41 - const organizationId = ref<Nullable<number>>(null);  
42 -  
43 - const pagination = reactive<PaginationProps>({  
44 - size: 'small',  
45 - showTotal: (total: number) => `共 ${total} 条数据`,  
46 - current: 1,  
47 - pageSize: unref(listColumn) * 2,  
48 - onChange: (page: number) => {  
49 - pagination.current = page;  
50 - getListData(); 35 + const [registerOrgTree, { getSelectKey, clearSelected }] = useOrganizationTree({
  36 + onSelect: () => {
  37 + reload();
51 }, 38 },
52 }); 39 });
53 40
54 - const loading = ref(false);  
55 - const { isCustomerUser } = useRole();  
56 -  
57 - const dataSource = ref<BigScreenCenterItemsModel[]>([]);  
58 -  
59 - const [registerForm, { getFieldsValue }] = useForm({  
60 - schemas: searchFormSchema,  
61 - showAdvancedButton: true,  
62 - labelWidth: 100,  
63 - compact: true,  
64 - resetFunc: () => {  
65 - resetFn();  
66 - organizationId.value = null;  
67 - return getListData(); 41 + const [registerCardList, { reload }] = useCardList({
  42 + api: getPage,
  43 + title: '数据大屏',
  44 + useSearchForm: true,
  45 + gutter: 4,
  46 + formConfig: {
  47 + labelWidth: 80,
  48 + schemas: searchFormSchema,
  49 + resetFunc: async () => clearSelected(),
68 }, 50 },
69 - submitFunc: async () => {  
70 - const value = getFieldsValue();  
71 - getListData(value); 51 + beforeFetch: async (params: Recordable) => {
  52 + return { ...params, organizationId: getSelectKey() };
72 }, 53 },
73 }); 54 });
74 55
75 - async function getListData(value: Recordable = {}) {  
76 - try {  
77 - loading.value = true;  
78 - const pageSize = unref(listColumn) * 2;  
79 - const { items, total } = await getPage({  
80 - organizationId: unref(organizationId),  
81 - ...value,  
82 - page: pagination.current!,  
83 - pageSize,  
84 - });  
85 -  
86 - dataSource.value = items;  
87 - Object.assign(pagination, { total, pageSize });  
88 - } catch (error) {  
89 - } finally {  
90 - loading.value = false;  
91 - }  
92 - }  
93 -  
94 - onMounted(() => {  
95 - getListData();  
96 - });  
97 -  
98 - const searchInfo = reactive<Recordable>({});  
99 - const { organizationIdTreeRef, resetFn } = useResetOrganizationTree(searchInfo);  
100 - const handleSelect = (orgId: number) => {  
101 - organizationId.value = orgId;  
102 - getListData();  
103 - };  
104 -  
105 const [registerDrawer, { openDrawer }] = useDrawer(); 56 const [registerDrawer, { openDrawer }] = useDrawer();
106 57
107 const [registerPublicDrawer, { openDrawer: openPublicApiDrawer }] = useDrawer(); 58 const [registerPublicDrawer, { openDrawer: openPublicApiDrawer }] = useDrawer();
108 59
  60 + const { createMessage } = useMessage();
  61 +
  62 + const { isCustomerUser } = useRole();
  63 +
109 const handleCreateOrUpdate = (record?: BigScreenCenterItemsModel) => { 64 const handleCreateOrUpdate = (record?: BigScreenCenterItemsModel) => {
110 if (record) { 65 if (record) {
111 openDrawer(true, { 66 openDrawer(true, {
@@ -140,37 +95,10 @@ @@ -140,37 +95,10 @@
140 try { 95 try {
141 await deleteBigScreenenter([record.id]); 96 await deleteBigScreenenter([record.id]);
142 createMessage.success('删除成功'); 97 createMessage.success('删除成功');
143 - await getListData(); 98 + reload();
144 } catch (error) {} 99 } catch (error) {}
145 }; 100 };
146 101
147 - const handleCardLayoutChange = () => {  
148 - pagination.current = 1;  
149 - getListData();  
150 - };  
151 -  
152 - const listEl = ref<Nullable<ComponentElRef>>(null);  
153 -  
154 - onMounted(() => {  
155 - const clientHeight = document.documentElement.clientHeight;  
156 - const rect = getBoundingClientRect(unref(listEl)!.$el!) as DOMRect;  
157 - // margin-top 24 height 24  
158 - const paginationHeight = 24 + 24 + 8;  
159 - // list pading top 8 maring-top 8 extra slot 56  
160 - const listContainerMarginBottom = 8 + 8 + 56;  
161 - const listContainerHeight =  
162 - clientHeight - rect.top - paginationHeight - listContainerMarginBottom;  
163 - const listContainerEl = (unref(listEl)!.$el as HTMLElement).querySelector(  
164 - '.ant-spin-container'  
165 - ) as HTMLElement;  
166 - listContainerEl &&  
167 - (listContainerEl.style.height = listContainerHeight + 'px') &&  
168 - (listContainerEl.style.overflowY = 'auto') &&  
169 - (listContainerEl.style.overflowX = 'hidden');  
170 - });  
171 -  
172 - const getPublicApiListData = () => {};  
173 -  
174 const [registerShareModal, { openModal }] = useModal(); 102 const [registerShareModal, { openModal }] = useModal();
175 const handleOpenShareModal = (record: BigScreenCenterItemsModel) => { 103 const handleOpenShareModal = (record: BigScreenCenterItemsModel) => {
176 openModal(true, { record, href: createShareUrl(record) }); 104 openModal(true, { record, href: createShareUrl(record) });
@@ -182,6 +110,7 @@ @@ -182,6 +110,7 @@
182 }; 110 };
183 111
184 const userStore = useUserStore(); 112 const userStore = useUserStore();
  113 +
185 const hasPublicInterfacePermission = computed(() => { 114 const hasPublicInterfacePermission = computed(() => {
186 return userStore.getUserInfo.roles![0] !== RoleEnum.CUSTOMER_USER; 115 return userStore.getUserInfo.roles![0] !== RoleEnum.CUSTOMER_USER;
187 }); 116 });
@@ -197,166 +126,143 @@ @@ -197,166 +126,143 @@
197 const handlePublish = async ({ id, state }) => { 126 const handlePublish = async ({ id, state }) => {
198 state === 0 ? await bigScreenPublish(id) : await bigScreenCancelPublish(id); 127 state === 0 ? await bigScreenPublish(id) : await bigScreenCancelPublish(id);
199 createMessage.success(state === 0 ? '发布成功' : '取消发布成功'); 128 createMessage.success(state === 0 ? '发布成功' : '取消发布成功');
200 - getListData(); 129 + reload();
201 }; 130 };
202 </script> 131 </script>
203 132
204 <template> 133 <template>
205 <PageWrapper dense contentFullHeight contentClass="flex"> 134 <PageWrapper dense contentFullHeight contentClass="flex">
206 - <OrganizationIdTree @select="handleSelect" ref="organizationIdTreeRef" />  
207 - <section class="flex-auto p-4 w-3/4 xl:w-4/5 w-full configuration-list">  
208 - <div class="flex-auto w-full bg-light-50 dark:bg-dark-900 p-4">  
209 - <BasicForm @register="registerForm" />  
210 - </div>  
211 - <List  
212 - ref="listEl"  
213 - :loading="loading"  
214 - class="flex-auto bg-light-50 dark:bg-dark-900 !p-2 !mt-4"  
215 - position="bottom"  
216 - :pagination="pagination"  
217 - :data-source="dataSource"  
218 - :grid="{ gutter: 4, column: listColumn }"  
219 - >  
220 - <template #header>  
221 - <div class="flex gap-3 justify-end">  
222 - <Authority v-if="!isCustomerUser" :value="ConfigurationPermission.CREATE">  
223 - <Button type="primary" @click="handleCreateOrUpdate()"> 新增大屏 </Button>  
224 - </Authority>  
225 - <Authority  
226 - v-if="hasPublicInterfacePermission"  
227 - :value="ConfigurationPermission.PUBLISH_INTERFACE"  
228 - >  
229 - <Button type="primary" @click="handleCreateOrUpdatePublicApi()">公共接口管理</Button>  
230 - </Authority>  
231 - <CardLayoutButton v-model:value="listColumn" @change="handleCardLayoutChange" />  
232 - <Tooltip title="刷新">  
233 - <Button type="primary" @click="getListData">  
234 - <ReloadOutlined />  
235 - </Button>  
236 - </Tooltip>  
237 - </div>  
238 - </template>  
239 - <template #renderItem="{ item }">  
240 - <List.Item>  
241 - <Card  
242 - :style="{  
243 - '--viewType': item.viewType === ViewType.PUBLIC_VIEW ? '#1890ff' : '#faad14',  
244 - }"  
245 - class="card-container"  
246 - >  
247 - <template #cover>  
248 - <div class="h-full w-full relative hover-show-modal-content img-container">  
249 - <img  
250 - style="position: relative"  
251 - class="w-full h-45 hover-show-modal"  
252 - alt="example"  
253 - :src="item.thumbnail || configurationSrc"  
254 - @click="handlePreview(item)"  
255 - />  
256 - <span  
257 - class="absolute top-0 left-0 text-light-50 transform -rotate-45 translate-y-1" 135 + <OrganizationIdTree @register="registerOrgTree" />
  136 + <BasicCardList
  137 + @register="registerCardList"
  138 + class="flex-auto p-4 w-3/4 xl:w-4/5 w-full configuration-list"
  139 + >
  140 + <template #toolbar>
  141 + <section class="flex gap-4">
  142 + <Authority v-if="!isCustomerUser" :value="ConfigurationPermission.CREATE">
  143 + <Button type="primary" @click="handleCreateOrUpdate()"> 新增大屏 </Button>
  144 + </Authority>
  145 + <Authority
  146 + v-if="hasPublicInterfacePermission"
  147 + :value="ConfigurationPermission.PUBLISH_INTERFACE"
  148 + >
  149 + <Button type="primary" @click="handleCreateOrUpdatePublicApi()">公共接口管理</Button>
  150 + </Authority>
  151 + </section>
  152 + </template>
  153 + <template #renderItem="{ item }: CardListRenderItem<BigScreenCenterItemsModel>">
  154 + <Card
  155 + :style="{
  156 + '--viewType': item.viewType === ViewType.PUBLIC_VIEW ? '#1890ff' : '#faad14',
  157 + }"
  158 + class="card-container"
  159 + >
  160 + <template #cover>
  161 + <div class="h-full w-full relative hover-show-modal-content img-container">
  162 + <img
  163 + style="position: relative"
  164 + class="w-full h-45 hover-show-modal"
  165 + alt="example"
  166 + :src="item.thumbnail || configurationSrc"
  167 + @click="handlePreview(item)"
  168 + />
  169 + <span class="absolute top-0 left-0 text-light-50 transform -rotate-45 translate-y-1">
  170 + {{
  171 + item.viewType ? ViewTypeNameEnum[item.viewType] : ViewTypeNameEnum.PRIVATE_VIEW
  172 + }}
  173 + </span>
  174 + <div class="masker-content">
  175 + <div class="masker-text">
  176 + <div
  177 + ><span>{{ item.name }}</span></div
258 > 178 >
259 - {{ ViewTypeNameEnum[item.viewType] || ViewTypeNameEnum.PRIVATE_VIEW }}  
260 - </span>  
261 - <div class="masker-content">  
262 - <div class="masker-text">  
263 - <div  
264 - ><span>{{ item.name }}</span></div  
265 - >  
266 - <div>  
267 - <span class="masker-text-org"  
268 - >所属组织:{{ item?.organizationDTO?.name }}</span  
269 - >  
270 - </div>  
271 - <div>  
272 - <span class="masker-text-state"  
273 - >发布状态:{{ item.state === 1 ? '已发布' : '未发布' }}</span  
274 - >  
275 - </div>  
276 - </div> 179 + <div>
  180 + <span class="masker-text-org">所属组织:{{ item?.organizationDTO?.name }}</span>
  181 + </div>
  182 + <div>
  183 + <span class="masker-text-state"
  184 + >发布状态:{{ item.state === 1 ? '已发布' : '未发布' }}</span
  185 + >
277 </div> 186 </div>
278 </div> 187 </div>
279 - </template>  
280 - <template class="ant-card-actions" #actions>  
281 - <Tooltip title="预览">  
282 - <AuthIcon  
283 - class="!text-lg"  
284 - icon="ant-design:eye-outlined"  
285 - @click="handlePreview(item)"  
286 - />  
287 - </Tooltip>  
288 - <Tooltip v-if="!isCustomerUser" title="设计">  
289 - <AuthIcon  
290 - :auth="ConfigurationPermission.DESIGN"  
291 - :disabled="item.state === 1"  
292 - icon="ant-design:edit-outlined"  
293 - @click="handleDesign(item)"  
294 - />  
295 - </Tooltip>  
296 - <Tooltip title="点击复制分享链接">  
297 - <AuthIcon  
298 - :auth="ConfigurationPermission.SHARE"  
299 - :disabled="!item.publicId"  
300 - class="!text-lg"  
301 - icon="ant-design:share-alt-outlined"  
302 - @click="handleCreateShareUrl(item)"  
303 - />  
304 - </Tooltip>  
305 - <AuthDropDown  
306 - v-if="!isCustomerUser"  
307 - :dropMenuList="[  
308 - {  
309 - text: '分享',  
310 - auth: ConfigurationPermission.SHARE,  
311 - icon: 'ant-design:share-alt-outlined',  
312 - event: '',  
313 - onClick: handleOpenShareModal.bind(null, item),  
314 - },  
315 - {  
316 - text: item.state == 0 ? '发布' : '取消发布',  
317 - auth: ConfigurationPermission.PUBLISH,  
318 - icon:  
319 - item.state == 0  
320 - ? 'ant-design:node-expand-outlined'  
321 - : 'ant-design:node-collapse-outlined',  
322 - event: '',  
323 - onClick: handlePublish.bind(null, item),  
324 - },  
325 - {  
326 - text: '编辑',  
327 - auth: ConfigurationPermission.UPDATE,  
328 - icon: 'clarity:note-edit-line',  
329 - event: '',  
330 - onClick: handleCreateOrUpdate.bind(null, item),  
331 - disabled: item.state === 0 ? false : true,  
332 - },  
333 - {  
334 - text: '删除',  
335 - auth: ConfigurationPermission.DELETE,  
336 - icon: 'ant-design:delete-outlined',  
337 - disabled: item.state === 0 ? false : true,  
338 - event: '',  
339 - popconfirm: {  
340 - title: '是否确认删除操作?',  
341 - onConfirm: handleDelete.bind(null, item),  
342 - },  
343 - },  
344 - ]"  
345 - :trigger="['hover']"  
346 - />  
347 - </template>  
348 - </Card>  
349 - </List.Item>  
350 - </template>  
351 - </List>  
352 - </section>  
353 - <ShareModal  
354 - @register="registerShareModal"  
355 - :shareApi="shareLargeScreen"  
356 - @success="getListData"  
357 - />  
358 - <ConfigurationCenterDrawer @register="registerDrawer" @success="getListData" />  
359 - <PublicApiDrawer @register="registerPublicDrawer" @success="getPublicApiListData" /> 188 + </div>
  189 + </div>
  190 + </template>
  191 + <template class="ant-card-actions" #actions>
  192 + <Tooltip title="预览">
  193 + <AuthIcon
  194 + class="!text-lg"
  195 + icon="ant-design:eye-outlined"
  196 + @click="handlePreview(item)"
  197 + />
  198 + </Tooltip>
  199 + <Tooltip v-if="!isCustomerUser" title="设计">
  200 + <AuthIcon
  201 + :auth="ConfigurationPermission.DESIGN"
  202 + :disabled="item.state === 1"
  203 + icon="ant-design:edit-outlined"
  204 + @click="handleDesign(item)"
  205 + />
  206 + </Tooltip>
  207 + <Tooltip title="点击复制分享链接">
  208 + <AuthIcon
  209 + :auth="ConfigurationPermission.SHARE"
  210 + :disabled="!item.publicId"
  211 + class="!text-lg"
  212 + icon="ant-design:share-alt-outlined"
  213 + @click="handleCreateShareUrl(item)"
  214 + />
  215 + </Tooltip>
  216 + <AuthDropDown
  217 + v-if="!isCustomerUser"
  218 + :dropMenuList="[
  219 + {
  220 + text: '分享',
  221 + auth: ConfigurationPermission.SHARE,
  222 + icon: 'ant-design:share-alt-outlined',
  223 + event: '',
  224 + onClick: handleOpenShareModal.bind(null, item),
  225 + },
  226 + {
  227 + text: item.state == 0 ? '发布' : '取消发布',
  228 + auth: ConfigurationPermission.PUBLISH,
  229 + icon:
  230 + item.state == 0
  231 + ? 'ant-design:node-expand-outlined'
  232 + : 'ant-design:node-collapse-outlined',
  233 + event: '',
  234 + onClick: handlePublish.bind(null, item),
  235 + },
  236 + {
  237 + text: '编辑',
  238 + auth: ConfigurationPermission.UPDATE,
  239 + icon: 'clarity:note-edit-line',
  240 + event: '',
  241 + onClick: handleCreateOrUpdate.bind(null, item),
  242 + disabled: item.state === 0 ? false : true,
  243 + },
  244 + {
  245 + text: '删除',
  246 + auth: ConfigurationPermission.DELETE,
  247 + icon: 'ant-design:delete-outlined',
  248 + disabled: item.state === 0 ? false : true,
  249 + event: '',
  250 + popconfirm: {
  251 + title: '是否确认删除操作?',
  252 + onConfirm: handleDelete.bind(null, item),
  253 + },
  254 + },
  255 + ]"
  256 + :trigger="['hover']"
  257 + />
  258 + </template>
  259 + </Card>
  260 + </template>
  261 + </BasicCardList>
  262 +
  263 + <ShareModal @register="registerShareModal" :shareApi="shareLargeScreen" @success="reload()" />
  264 + <ConfigurationCenterDrawer @register="registerDrawer" @success="reload()" />
  265 + <PublicApiDrawer @register="registerPublicDrawer" />
360 </PageWrapper> 266 </PageWrapper>
361 </template> 267 </template>
362 268
1 -import { getOrganizationList } from '/@/api/system/system';  
2 import { FormSchema } from '/@/components/Form'; 1 import { FormSchema } from '/@/components/Form';
3 import { ColEx } from '/@/components/Form/src/types'; 2 import { ColEx } from '/@/components/Form/src/types';
4 import { useGridLayout } from '/@/hooks/component/useGridLayout'; 3 import { useGridLayout } from '/@/hooks/component/useGridLayout';
5 -import { copyTransFun } from '/@/utils/fnUtils';  
6 4
7 export const formSchema: FormSchema[] = [ 5 export const formSchema: FormSchema[] = [
8 { 6 {
@@ -11,23 +9,8 @@ export const formSchema: FormSchema[] = [ @@ -11,23 +9,8 @@ export const formSchema: FormSchema[] = [
11 component: 'Input', 9 component: 'Input',
12 // colProps: { span: 10 }, 10 // colProps: { span: 10 },
13 colProps: useGridLayout(2, 3, 4) as unknown as ColEx, 11 colProps: useGridLayout(2, 3, 4) as unknown as ColEx,
14 - },  
15 - {  
16 - field: 'organizationId',  
17 - component: 'ApiTreeSelect',  
18 - label: '组织',  
19 - // colProps: { span: 10 },  
20 - colProps: useGridLayout(2, 3, 4) as unknown as ColEx,  
21 - componentProps() {  
22 - return {  
23 - placeholder: '请选择组织',  
24 - api: async () => {  
25 - const data = await getOrganizationList();  
26 - copyTransFun(data as any as any[]);  
27 - return data;  
28 - },  
29 - getPopupContainer: () => document.body,  
30 - }; 12 + componentProps: {
  13 + placeholder: '请输入看板名称',
31 }, 14 },
32 }, 15 },
33 ]; 16 ];
1 <script lang="ts" setup> 1 <script lang="ts" setup>
2 - import { List, Card, Statistic, Button, Tooltip, Spin } from 'ant-design-vue';  
3 - import { onMounted, ref, unref } from 'vue';  
4 - import { PageWrapper } from '/@/components/Page'; 2 + import { Card, Statistic, Button, Tooltip } from 'ant-design-vue';
  3 + import { unref, computed } from 'vue';
5 import { MoreOutlined, ShareAltOutlined } from '@ant-design/icons-vue'; 4 import { MoreOutlined, ShareAltOutlined } from '@ant-design/icons-vue';
6 import { useMessage } from '/@/hooks/web/useMessage'; 5 import { useMessage } from '/@/hooks/web/useMessage';
7 import Dropdown from '/@/components/Dropdown/src/Dropdown.vue'; 6 import Dropdown from '/@/components/Dropdown/src/Dropdown.vue';
@@ -13,12 +12,8 @@ @@ -13,12 +12,8 @@
13 import { DataBoardRecord } from '/@/api/dataBoard/model'; 12 import { DataBoardRecord } from '/@/api/dataBoard/model';
14 import { ViewType } from './config/panelDetail'; 13 import { ViewType } from './config/panelDetail';
15 import { useRouter } from 'vue-router'; 14 import { useRouter } from 'vue-router';
16 - import { getBoundingClientRect } from '/@/utils/domUtils';  
17 - import Authority from '/@/components/Authority/src/Authority.vue';  
18 - import { computed } from '@vue/reactivity';  
19 import { usePermission } from '/@/hooks/web/usePermission'; 15 import { usePermission } from '/@/hooks/web/usePermission';
20 import { encode } from './config/config'; 16 import { encode } from './config/config';
21 - import { useForm, BasicForm } from '/@/components/Form';  
22 import { formSchema } from './config/searchForm'; 17 import { formSchema } from './config/searchForm';
23 import { ShareModal } from '/@/views/common/ShareModal'; 18 import { ShareModal } from '/@/views/common/ShareModal';
24 import { ModalParamsType } from '/#/utils'; 19 import { ModalParamsType } from '/#/utils';
@@ -26,62 +21,33 @@ @@ -26,62 +21,33 @@
26 import { useRole } from '/@/hooks/business/useRole'; 21 import { useRole } from '/@/hooks/business/useRole';
27 import { useClipboard } from '@vueuse/core'; 22 import { useClipboard } from '@vueuse/core';
28 import { DATA_BOARD_SHARE_URL } from '../palette'; 23 import { DATA_BOARD_SHARE_URL } from '../palette';
  24 + import { BasicCardList, useCardList } from '/@/components/CardList';
  25 + import Authority from '/@/components/Authority/src/Authority.vue';
  26 + import { OrganizationIdTree, useOrganizationTree } from '../../common/organizationIdTree';
29 27
30 - const ListItem = List.Item;  
31 const router = useRouter(); 28 const router = useRouter();
32 29
33 const { createMessage, createConfirm } = useMessage(); 30 const { createMessage, createConfirm } = useMessage();
34 31
35 - const listEL = ref();  
36 - const loading = ref(false);  
37 - const dataBoardList = ref<DataBoardRecord[]>([]); 32 + const [registerOrgTree, { clearSelected, getSelectKey }] = useOrganizationTree({
  33 + onSelect: () => reload(),
  34 + });
38 35
39 - const [searchFormRegister, searchFormMethod] = useForm({  
40 - schemas: formSchema,  
41 - labelWidth: 80,  
42 - layout: 'inline',  
43 - submitButtonOptions: {  
44 - loading: loading as unknown as boolean, 36 + const [registerCardList, { reload }] = useCardList({
  37 + title: '数据看板',
  38 + api: getDataBoardList,
  39 + baseLayout: { col: 3, row: 3 },
  40 + useSearchForm: true,
  41 + formConfig: {
  42 + schemas: formSchema,
  43 + labelWidth: 80,
  44 + resetFunc: async () => clearSelected(),
45 }, 45 },
46 - submitFunc: async () => {  
47 - try {  
48 - const params = searchFormMethod.getFieldsValue();  
49 - await getDatasource(params);  
50 - } catch (error) {}  
51 - },  
52 - resetFunc: async () => {  
53 - try {  
54 - await getDatasource();  
55 - } catch (error) {} 46 + beforeFetch: async (params: Recordable) => {
  47 + return { ...params, organizationId: getSelectKey() };
56 }, 48 },
57 }); 49 });
58 50
59 - // about pagination  
60 - const page = ref(1);  
61 - const pageSize = ref(10);  
62 - const total = ref(0);  
63 - const paginationProp = ref({  
64 - showSizeChanger: false,  
65 - showQuickJumper: true,  
66 - pageSize,  
67 - current: page,  
68 - size: 'small',  
69 - total,  
70 - showTotal: (total) => `总 ${total} 条`,  
71 - onChange: pageChange,  
72 - onShowSizeChange: pageSizeChange,  
73 - });  
74 -  
75 - function pageChange(p, pz) {  
76 - page.value = p;  
77 - pageSize.value = pz;  
78 - getDatasource();  
79 - }  
80 - function pageSizeChange(_current, size) {  
81 - pageSize.value = size;  
82 - getDatasource();  
83 - }  
84 -  
85 const createShareUrl = (record: DataBoardRecord) => { 51 const createShareUrl = (record: DataBoardRecord) => {
86 const { origin } = location; 52 const { origin } = location;
87 const { id, publicId } = record; 53 const { id, publicId } = record;
@@ -124,22 +90,6 @@ @@ -124,22 +90,6 @@
124 return basicMenu; 90 return basicMenu;
125 }); 91 });
126 92
127 - const getDatasource = async (params: Recordable = {}) => {  
128 - try {  
129 - loading.value = true;  
130 - const { total, items } = await getDataBoardList({  
131 - page: unref(paginationProp).current,  
132 - pageSize: unref(paginationProp).pageSize,  
133 - ...params,  
134 - });  
135 - dataBoardList.value = items;  
136 - paginationProp.value.total = total;  
137 - } catch (error) {  
138 - } finally {  
139 - loading.value = false;  
140 - }  
141 - };  
142 -  
143 const handleOpenShareModal = (record: DataBoardRecord) => { 93 const handleOpenShareModal = (record: DataBoardRecord) => {
144 openShareModal(true, { 94 openShareModal(true, {
145 record, 95 record,
@@ -175,7 +125,7 @@ @@ -175,7 +125,7 @@
175 try { 125 try {
176 await deleteDataBoard([record.id]); 126 await deleteDataBoard([record.id]);
177 createMessage.success('删除成功'); 127 createMessage.success('删除成功');
178 - await getDatasource(); 128 + reload();
179 } catch (error) {} 129 } catch (error) {}
180 }; 130 };
181 131
@@ -188,106 +138,70 @@ @@ -188,106 +138,70 @@
188 if (hasDetailPermission) { 138 if (hasDetailPermission) {
189 const boardId = encode(record.id); 139 const boardId = encode(record.id);
190 const boardName = encode(record.name); 140 const boardName = encode(record.name);
191 - const organizationId = encode(record?.organizationId); 141 + const organizationId = encode(record!.organizationId!);
192 142
193 router.push(`/visual/board/detail/${boardId}/${boardName}/${organizationId}`); 143 router.push(`/visual/board/detail/${boardId}/${boardName}/${organizationId}`);
194 } else createMessage.warning('没有权限'); 144 } else createMessage.warning('没有权限');
195 }; 145 };
196 -  
197 - const handlePagenationPosition = () => {  
198 - const clientHeight = document.documentElement.clientHeight;  
199 - const rect = getBoundingClientRect(unref(listEL).$el!) as DOMRect;  
200 - const paginationHeight = 32 + 24 + 16;  
201 - const listContainerMarginBottom = 16;  
202 - const listContainerHeight =  
203 - clientHeight - rect.top - paginationHeight - listContainerMarginBottom;  
204 - const listContainerEl = (unref(listEL).$el as HTMLElement).querySelector(  
205 - '.ant-spin-container'  
206 - ) as HTMLElement;  
207 - listContainerEl &&  
208 - (listContainerEl.style.height = listContainerHeight + 'px') &&  
209 - (listContainerEl.style.overflowY = 'auto') &&  
210 - (listContainerEl.style.overflowX = 'hidden');  
211 - };  
212 -  
213 - onMounted(() => {  
214 - getDatasource();  
215 - handlePagenationPosition();  
216 - });  
217 </script> 146 </script>
218 147
219 <template> 148 <template>
220 - <PageWrapper>  
221 - <div class="flex items-center mb-3 bg-light-100 h-78px dark:text-gray-300 dark:bg-dark-900">  
222 - <div class="text-lg ml-30px mr-9px font-bold">自定义看板</div>  
223 - <Authority value="api:yt:data_board:add:post">  
224 - <Button v-if="!isCustomerUser" type="primary" @click="handleOpenDetailModal"  
225 - >创建看板</Button  
226 - >  
227 - </Authority>  
228 - </div>  
229 - <div class="bg-light-100 mb-6 w-full p-3 search-form dark:text-gray-300 dark:bg-dark-900">  
230 - <BasicForm class="flex-auto w-full" @register="searchFormRegister" />  
231 - </div>  
232 - <Spin :spinning="loading">  
233 - <List  
234 - ref="listEL"  
235 - :pagination="paginationProp"  
236 - :data-source="dataBoardList"  
237 - :grid="{ gutter: 20, column: 4, xs: 1, sm: 2, md: 2, lg: 3, xl: 3, xxl: 3 }"  
238 - class="data-board-list"  
239 - >  
240 - <template #renderItem="{ item }">  
241 - <ListItem>  
242 - <Card class="data-card cursor-pointer">  
243 - <template #title>  
244 - <div class="font-bold">{{ item.name }}</div>  
245 - </template>  
246 - <template #extra>  
247 - <Dropdown  
248 - v-if="!isCustomerUser && dropMenuList.length"  
249 - :trigger="['click']"  
250 - @menu-event="(event) => handleMenuEvent(event, item)"  
251 - :drop-menu-list="dropMenuList"  
252 - >  
253 - <MoreOutlined class="rotate-90 transform cursor-pointer" />  
254 - </Dropdown>  
255 - </template>  
256 - <section @click="handleViewBoard(item)">  
257 - <div class="flex data-card__info">  
258 - <div>  
259 - <div>组件数量</div>  
260 - <Statistic style="font-size: 22px" :value="item.componentNum">  
261 - <template #suffix>  
262 - <span class="text-sm">个</span>  
263 - </template>  
264 - </Statistic>  
265 - </div>  
266 - </div>  
267 - <div class="flex justify-between mt-4 text-sm" style="color: #999">  
268 - <div class="flex min-w-20 mr-3">  
269 - <span>  
270 - {{ item.viewType === ViewType.PRIVATE_VIEW ? '私有看板' : '公共看板' }}  
271 - </span>  
272 - <span v-if="item.viewType === ViewType.PUBLIC_VIEW">  
273 - <Tooltip title="点击复制分享链接">  
274 - <ShareAltOutlined class="ml-1" @click.stop="handleCopyShareUrl(item)" />  
275 - </Tooltip>  
276 - </span>  
277 - </div>  
278 - <Tooltip placement="topLeft" :title="item.createTime">  
279 - <div class="truncate">{{ item.createTime }}</div> 149 + <section class="flex">
  150 + <OrganizationIdTree @register="registerOrgTree" />
  151 + <BasicCardList class="flex-auto p-4 w-3/4 xl:w-4/5 w-full" @register="registerCardList">
  152 + <template #toolbar>
  153 + <Authority :value="VisualBoardPermission.CREATE">
  154 + <Button type="primary" @click="handleOpenDetailModal">新增看板</Button>
  155 + </Authority>
  156 + </template>
  157 + <template #renderItem="{ item }: CardListRenderItem<DataBoardRecord>">
  158 + <Card class="data-card cursor-pointer">
  159 + <template #title>
  160 + <div class="font-bold">{{ item.name }}</div>
  161 + </template>
  162 + <template #extra>
  163 + <Dropdown
  164 + v-if="!isCustomerUser && dropMenuList.length"
  165 + :trigger="['click']"
  166 + @menu-event="(event) => handleMenuEvent(event, item)"
  167 + :drop-menu-list="dropMenuList"
  168 + >
  169 + <MoreOutlined class="rotate-90 transform cursor-pointer" />
  170 + </Dropdown>
  171 + </template>
  172 + <section @click="handleViewBoard(item)">
  173 + <div class="flex data-card__info">
  174 + <div>
  175 + <div>组件数量</div>
  176 + <Statistic class="text-2xl" :value="item.componentNum">
  177 + <template #suffix>
  178 + <span class="text-sm">个</span>
  179 + </template>
  180 + </Statistic>
  181 + </div>
  182 + </div>
  183 + <div class="flex justify-between mt-4 text-sm" style="color: #999">
  184 + <div class="flex min-w-20 mr-3">
  185 + <span>
  186 + {{ item.viewType === ViewType.PRIVATE_VIEW ? '私有看板' : '公共看板' }}
  187 + </span>
  188 + <span v-if="item.viewType === ViewType.PUBLIC_VIEW">
  189 + <Tooltip title="点击复制分享链接">
  190 + <ShareAltOutlined class="ml-1" @click.stop="handleCopyShareUrl(item)" />
280 </Tooltip> 191 </Tooltip>
281 - </div>  
282 - </section>  
283 - </Card>  
284 - </ListItem>  
285 - </template>  
286 - </List>  
287 - </Spin>  
288 - <ShareModal @register="registerShareModal" :shareApi="shareBoard" @success="getDatasource" />  
289 - <PanelDetailModal @register="registerModal" @change="getDatasource" />  
290 - </PageWrapper> 192 + </span>
  193 + </div>
  194 + <Tooltip placement="topLeft" :title="item.createTime">
  195 + <div class="truncate">{{ item.createTime }}</div>
  196 + </Tooltip>
  197 + </div>
  198 + </section>
  199 + </Card>
  200 + </template>
  201 + </BasicCardList>
  202 + <ShareModal @register="registerShareModal" :shareApi="shareBoard" @success="reload()" />
  203 + <PanelDetailModal @register="registerModal" @change="reload()" />
  204 + </section>
291 </template> 205 </template>
292 206
293 <style scoped lang="less"> 207 <style scoped lang="less">
1 import type { ComputedRef, Ref } from 'vue'; 1 import type { ComputedRef, Ref } from 'vue';
2 -import { DataActionModeEnum } from '/@/enums/toolEnum';  
3 2
4 -export type DynamicProps<T> = {  
5 - [P in keyof T]: Ref<T[P]> | T[P] | ComputedRef<T[P]>;  
6 -}; 3 +import { DataActionModeEnum } from '/@/enums/toolEnum';
7 4
8 export interface ModalParamsType<T = Recordable> { 5 export interface ModalParamsType<T = Recordable> {
9 mode: DataActionModeEnum; 6 mode: DataActionModeEnum;
@@ -11,9 +8,30 @@ export interface ModalParamsType<T = Recordable> { @@ -11,9 +8,30 @@ export interface ModalParamsType<T = Recordable> {
11 [key: string]: any; 8 [key: string]: any;
12 } 9 }
13 10
  11 +export type DynamicProps<T> = {
  12 + [P in keyof T]: Ref<T[P]> | T[P] | ComputedRef<T[P]>;
  13 +};
  14 +
14 export interface DefineComponentsBasicExpose<T = Recordable> { 15 export interface DefineComponentsBasicExpose<T = Recordable> {
15 getFieldsValue: () => T; 16 getFieldsValue: () => T;
16 setFieldsValue: (value: T) => any; 17 setFieldsValue: (value: T) => any;
17 validate?: () => Promise<any>; 18 validate?: () => Promise<any>;
18 resetFieldsValue?: (...args) => any; 19 resetFieldsValue?: (...args) => any;
19 } 20 }
  21 +
  22 +declare global {
  23 + interface CardListRenderItem<T = Recordable & { checked?: boolean }> {
  24 + item: T;
  25 + totalHeight: number;
  26 + }
  27 +
  28 + import { DataActionModeEnum } from '/@/enums/toolEnum';
  29 +
  30 + interface ModalParamsType<T = Recordable> {
  31 + mode: DataActionModeEnum;
  32 + record: T;
  33 + [key: string]: any;
  34 + }
  35 +}
  36 +
  37 +export {};