Commit 393a1439abbb35d2778897e9cc612f605c983c3e

Authored by fengwotao
1 parent 89a0ad8e

feat:TK平台新增大屏设计器CRUD

  1 +import { defHttp } from '/@/utils/http/axios';
  2 +import type {
  3 + queryPageParams,
  4 + BigScreenCenterParams,
  5 + ConfigurationCenterInfo,
  6 + BigScreenCenterItemsModal,
  7 +} from './model/bigscreenCenterModal';
  8 +import { getPageData } from '../../base';
  9 +import { FileUploadResponse } from '../../oem/model';
  10 +enum API {
  11 + basicUrl = '/data_view',
  12 + UPLOAD = '/oss/upload',
  13 +}
  14 +
  15 +export const getPage = (params: queryPageParams) => {
  16 + return getPageData<BigScreenCenterItemsModal>(params, API.basicUrl);
  17 +};
  18 +
  19 +export const saveConfigurationCenter = (params: BigScreenCenterParams) => {
  20 + return defHttp.post({
  21 + url: API.basicUrl,
  22 + data: params,
  23 + });
  24 +};
  25 +
  26 +export const updateConfigurationCenter = (params: BigScreenCenterParams) => {
  27 + return defHttp.put({
  28 + url: API.basicUrl,
  29 + data: params,
  30 + });
  31 +};
  32 +
  33 +export const deleteBigScreenenter = (ids: string[]) => {
  34 + return defHttp.delete({
  35 + url: API.basicUrl,
  36 + data: {
  37 + ids: ids,
  38 + },
  39 + });
  40 +};
  41 +
  42 +export const saveOrUpdateBigScreenCenter = (params: ConfigurationCenterInfo, isUpdate: boolean) => {
  43 + return isUpdate ? updateConfigurationCenter(params) : saveConfigurationCenter(params);
  44 +};
  45 +
  46 +export const uploadThumbnail = (file: FormData) => {
  47 + return defHttp.post<FileUploadResponse>({ url: API.UPLOAD, params: file });
  48 +};
... ...
  1 +import { BasicPageParams } from '/@/api/model/baseModel';
  2 +
  3 +export interface BigScreenCenterItemsModal {
  4 + id: string;
  5 + name: string;
  6 + createTime: string;
  7 + creator: string;
  8 + remark: string;
  9 +}
  10 +export type queryPageParams = BasicPageParams & {
  11 + name?: Nullable<string>;
  12 + organizationId?: Nullable<number>;
  13 +};
  14 +
  15 +export interface ConfigurationModal {
  16 + items: BigScreenCenterItemsModal[];
  17 + total: number;
  18 +}
  19 +
  20 +export interface BigScreenCenterParams {
  21 + name: string;
  22 + createTime: string;
  23 + creator: string;
  24 + remark: string;
  25 + defaultContent?: string;
  26 +}
  27 +export type ConfigurationCenterInfo = BigScreenCenterParams;
... ...
  1 +<template>
  2 + <BasicDrawer
  3 + v-bind="$attrs"
  4 + @register="registerDrawer"
  5 + showFooter
  6 + :title="getTitle"
  7 + width="30%"
  8 + @ok="handleSubmit"
  9 + >
  10 + <BasicForm @register="registerForm" />
  11 + </BasicDrawer>
  12 +</template>
  13 +<script lang="ts">
  14 + import { defineComponent, ref, computed, unref } from 'vue';
  15 + import { BasicForm, useForm } from '/@/components/Form';
  16 + import { formSchema, PC_DEFAULT_CONTENT, PHONE_DEFAULT_CONTENT, Platform } from './config';
  17 + import { BasicDrawer, useDrawerInner } from '/@/components/Drawer';
  18 + import { useMessage } from '/@/hooks/web/useMessage';
  19 + import { saveOrUpdateBigScreenCenter } from '/@/api/bigscreen/center/bigscreenCenter';
  20 + import { FileItem } from '/@/components/Form/src/components/ApiUpload.vue';
  21 + import { buildUUID } from '/@/utils/uuid';
  22 +
  23 + export default defineComponent({
  24 + name: 'ConfigurationDrawer',
  25 + components: { BasicDrawer, BasicForm },
  26 + emits: ['success', 'register'],
  27 + setup(_, { emit }) {
  28 + const isUpdate = ref(true);
  29 +
  30 + const [registerForm, { validate, setFieldsValue, resetFields }] = useForm({
  31 + labelWidth: 120,
  32 + schemas: formSchema,
  33 + showActionButtonGroup: false,
  34 + });
  35 +
  36 + const [registerDrawer, { setDrawerProps, closeDrawer }] = useDrawerInner(async (data) => {
  37 + await resetFields();
  38 + setDrawerProps({ confirmLoading: false });
  39 + isUpdate.value = !!data?.isUpdate;
  40 + if (unref(isUpdate)) {
  41 + if (data.record.thumbnail) {
  42 + data.record.thumbnail = [
  43 + { uid: buildUUID(), name: 'name', url: data.record.thumbnail } as FileItem,
  44 + ];
  45 + }
  46 + if (data.record.organizationDTO) {
  47 + await setFieldsValue(data.record);
  48 + } else {
  49 + Reflect.deleteProperty(data.record, 'organizationId');
  50 + await setFieldsValue(data.record);
  51 + }
  52 + }
  53 + });
  54 +
  55 + const getTitle = computed(() => (!unref(isUpdate) ? '新增大屏' : '编辑大屏'));
  56 +
  57 + const getDefaultContent = (platform: Platform) => {
  58 + if (platform === Platform.PC) {
  59 + return PC_DEFAULT_CONTENT;
  60 + }
  61 + return PHONE_DEFAULT_CONTENT;
  62 + };
  63 +
  64 + async function handleSubmit() {
  65 + try {
  66 + const { createMessage } = useMessage();
  67 + const values = await validate();
  68 + if (Reflect.has(values, 'thumbnail')) {
  69 + const file = (values.thumbnail || []).at(0) || {};
  70 + values.thumbnail = file.url || null;
  71 + }
  72 + setDrawerProps({ confirmLoading: true });
  73 + let saveMessage = '添加成功';
  74 + let updateMessage = '修改成功';
  75 + values.defaultContent = getDefaultContent(values.platform);
  76 + await saveOrUpdateBigScreenCenter(values, unref(isUpdate));
  77 + closeDrawer();
  78 + emit('success');
  79 + createMessage.success(unref(isUpdate) ? updateMessage : saveMessage);
  80 + } finally {
  81 + setDrawerProps({ confirmLoading: false });
  82 + }
  83 + }
  84 +
  85 + return {
  86 + getTitle,
  87 + registerDrawer,
  88 + registerForm,
  89 + handleSubmit,
  90 + };
  91 + },
  92 + });
  93 +</script>
... ...
  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 './BigScreenDrawer.vue';
  80 + import { useResetOrganizationTree, OrganizationIdTree } from '/@/views/common/organizationIdTree';
  81 + import { searchFormSchema, columns, Platform } from './config';
  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 +import { BasicColumn, FormSchema } from '/@/components/Table';
  2 +import { getOrganizationList } from '/@/api/system/system';
  3 +import { copyTransFun } from '/@/utils/fnUtils';
  4 +import { FileItem } from '/@/components/Form/src/components/ApiUpload.vue';
  5 +import { createImgPreview } from '/@/components/Preview';
  6 +import { uploadThumbnail } from '/@/api/configuration/center/configurationCenter';
  7 +export enum Platform {
  8 + PHONE = 0,
  9 + PC = 1,
  10 +}
  11 +
  12 +export enum ConfigurationPermission {
  13 + CREATE = 'api:yt:dataview:center:post',
  14 + UPDATE = 'api:yt:dataview:center:update',
  15 + DELETE = 'api:yt:dataview:center:delete',
  16 + DESIGN = 'api:yt:dataview:center:get_configuration_info:design',
  17 + PREVIEW = 'api:yt:dataview:center:get_configuration_info:preview',
  18 +}
  19 +
  20 +export const PC_DEFAULT_CONTENT =
  21 + '<mxfile><diagram>dZHBDsIgDIafhvuEzOh5Tr142sEzGXWQsHVhmKFP7xbAidMT5fv/UtoSVrTuZHgvLyhAE5oJR9iBUMrybT4dM3l4stnTzJPGKBHYAir1hACj7a4EDInRImqr+hTW2HVQ24RxY3BMbTfUadWeN7ACVc31ml6VsPK7jVk4g2pkLJ3tgtLy6A5gkFzg+IFYSVhhEK2PWleAnscXB+Pzjn/U988MdPZHwhQsb0+XZEesfAE=</diagram></mxfile>';
  22 +
  23 +export const PHONE_DEFAULT_CONTENT =
  24 + '<mxfile><diagram>dZHBEoIgEEC/hru6lXU2q0snD50Z2YQZdB2k0fr6dMCMsU4sb9+ysDDI6uFseCuvJFCzJBIDgyNLkjjdw7hM5OnIYRc5UBklvLSAQr3Qw1l7KIFdIFoibVUbwpKaBksbMG4M9aF2Jx12bXmFK1CUXK/pTQkrHd3E24VfUFXSd04hdYmaz65/SCe5oP4LQc4gM0TWRfWQoZ5mN4/F1Z3+ZD/3MtjYHwVjsJw9boIPgvwN</diagram></mxfile>';
  25 +// 表格列数据
  26 +export const columns: BasicColumn[] = [
  27 + {
  28 + title: '组态名称',
  29 + dataIndex: 'name',
  30 + width: 120,
  31 + },
  32 + {
  33 + title: '所属组织',
  34 + dataIndex: 'organizationDTO.name',
  35 + width: 160,
  36 + },
  37 + {
  38 + title: '平台',
  39 + dataIndex: 'platform',
  40 + width: 100,
  41 + slots: { customRender: 'platform' },
  42 + },
  43 + {
  44 + title: '备注',
  45 + dataIndex: 'remark',
  46 + width: 200,
  47 + },
  48 + {
  49 + title: '创建时间',
  50 + dataIndex: 'createTime',
  51 + width: 120,
  52 + },
  53 + {
  54 + title: '更新时间',
  55 + dataIndex: 'updateTime',
  56 + width: 120,
  57 + },
  58 + {
  59 + title: '操作',
  60 + dataIndex: 'action',
  61 + flag: 'ACTION',
  62 + width: 260,
  63 + slots: { customRender: 'action' },
  64 + },
  65 +];
  66 +
  67 +// 查询字段
  68 +export const searchFormSchema: FormSchema[] = [
  69 + {
  70 + field: 'name',
  71 + label: '大屏名称',
  72 + component: 'Input',
  73 + colProps: { span: 8 },
  74 + componentProps: {
  75 + maxLength: 36,
  76 + placeholder: '请输入大屏名称',
  77 + },
  78 + },
  79 +];
  80 +
  81 +export const formSchema: FormSchema[] = [
  82 + {
  83 + field: 'thumbnail',
  84 + label: '缩略图',
  85 + component: 'ApiUpload',
  86 + changeEvent: 'update:fileList',
  87 + valueField: 'fileList',
  88 + componentProps: () => {
  89 + return {
  90 + listType: 'picture-card',
  91 + maxFileLimit: 1,
  92 + api: async (file: File) => {
  93 + try {
  94 + const formData = new FormData();
  95 + formData.set('file', file);
  96 + const { fileStaticUri, fileName } = await uploadThumbnail(formData);
  97 + return {
  98 + uid: fileStaticUri,
  99 + name: fileName,
  100 + url: fileStaticUri,
  101 + } as FileItem;
  102 + } catch (error) {
  103 + return {};
  104 + }
  105 + },
  106 + onPreview: (fileList: FileItem) => {
  107 + createImgPreview({ imageList: [fileList.url!] });
  108 + },
  109 + };
  110 + },
  111 + },
  112 +
  113 + {
  114 + field: 'name',
  115 + label: '大屏名称',
  116 + required: true,
  117 + component: 'Input',
  118 + componentProps: {
  119 + placeholder: '请输入大屏名称',
  120 + maxLength: 255,
  121 + },
  122 + },
  123 + {
  124 + field: 'organizationId',
  125 + label: '所属组织',
  126 + required: true,
  127 + component: 'ApiTreeSelect',
  128 + componentProps: {
  129 + api: async () => {
  130 + const data = await getOrganizationList();
  131 + copyTransFun(data as any as any[]);
  132 + return data;
  133 + },
  134 + },
  135 + },
  136 + {
  137 + field: 'state',
  138 + label: '发布状态',
  139 + required: true,
  140 + component: 'RadioGroup',
  141 + defaultValue: Platform.PC,
  142 + componentProps: {
  143 + defaultValue: Platform.PC,
  144 + options: [
  145 + { label: '未发布', value: Platform.PC },
  146 + { label: '已发布', value: Platform.PHONE },
  147 + ],
  148 + },
  149 + },
  150 + {
  151 + field: 'remark',
  152 + label: '备注',
  153 + component: 'InputTextArea',
  154 + componentProps: {
  155 + placeholder: '请输入备注',
  156 + maxLength: 255,
  157 + },
  158 + },
  159 + {
  160 + field: 'id',
  161 + label: '',
  162 + component: 'Input',
  163 + show: false,
  164 + componentProps: {
  165 + maxLength: 36,
  166 + placeholder: 'id',
  167 + },
  168 + },
  169 +];
... ...
  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';
  6 + import { deleteBigScreenenter, getPage } from '/@/api/bigscreen/center/bigscreenCenter';
  7 + import { BigScreenCenterItemsModal } from '/@/api/bigscreen/center/model/configurationCenterModal';
  8 + import { PageWrapper } from '/@/components/Page';
  9 + import { BasicForm, useForm } from '/@/components/Form';
  10 + import { ConfigurationPermission, searchFormSchema } from './config';
  11 + import { useMessage } from '/@/hooks/web/useMessage';
  12 + import { Authority } from '/@/components/Authority';
  13 + import { isDevMode } from '/@/utils/env';
  14 + import ConfigurationCenterDrawer from './BigScreenDrawer.vue';
  15 + import { useDrawer } from '/@/components/Drawer';
  16 + import { getBoundingClientRect } from '/@/utils/domUtils';
  17 + import configurationSrc from '/@/assets/icons/configuration.svg';
  18 + import { cloneDeep } from 'lodash';
  19 + import { usePermission } from '/@/hooks/web/usePermission';
  20 + import { useGlobSetting } from '/@/hooks/setting';
  21 + import { AuthIcon, CardLayoutButton } from '/@/components/Widget';
  22 + import AuthDropDown from '/@/components/Widget/AuthDropDown.vue';
  23 +
  24 + const listColumn = ref(5);
  25 +
  26 + const { createMessage } = useMessage();
  27 +
  28 + const organizationId = ref<Nullable<number>>(null);
  29 +
  30 + const pagination = reactive<PaginationProps>({
  31 + size: 'small',
  32 + showTotal: (total: number) => `共 ${total} 条数据`,
  33 + current: 1,
  34 + pageSize: unref(listColumn) * 2,
  35 + onChange: (page: number) => {
  36 + pagination.current = page;
  37 + getListData();
  38 + },
  39 + });
  40 +
  41 + const loading = ref(false);
  42 +
  43 + const dataSource = ref<BigScreenCenterItemsModal[]>([]);
  44 +
  45 + const [registerForm, { getFieldsValue }] = useForm({
  46 + schemas: searchFormSchema,
  47 + showAdvancedButton: true,
  48 + labelWidth: 100,
  49 + compact: true,
  50 + resetFunc: () => {
  51 + resetFn();
  52 + organizationId.value = null;
  53 + return getListData();
  54 + },
  55 + submitFunc: async () => {
  56 + const value = getFieldsValue();
  57 + getListData(value);
  58 + },
  59 + });
  60 +
  61 + async function getListData(value: Recordable = {}) {
  62 + try {
  63 + loading.value = true;
  64 + const pageSize = unref(listColumn) * 2;
  65 + const { items, total } = await getPage({
  66 + organizationId: unref(organizationId),
  67 + ...value,
  68 + page: pagination.current!,
  69 + pageSize,
  70 + });
  71 +
  72 + dataSource.value = items;
  73 + Object.assign(pagination, { total, pageSize });
  74 + } catch (error) {
  75 + } finally {
  76 + loading.value = false;
  77 + }
  78 + }
  79 +
  80 + onMounted(() => {
  81 + getListData();
  82 + });
  83 +
  84 + const searchInfo = reactive<Recordable>({});
  85 + const { organizationIdTreeRef, resetFn } = useResetOrganizationTree(searchInfo);
  86 + const handleSelect = (orgId: number) => {
  87 + organizationId.value = orgId;
  88 + getListData();
  89 + };
  90 +
  91 + const [registerDrawer, { openDrawer }] = useDrawer();
  92 +
  93 + const { hasPermission } = usePermission();
  94 +
  95 + const getPreviewFlag = computed(() => {
  96 + return hasPermission(ConfigurationPermission.PREVIEW);
  97 + });
  98 +
  99 + const getDesignFlag = computed(() => {
  100 + return hasPermission(ConfigurationPermission.DESIGN);
  101 + });
  102 +
  103 + const handleCreateOrUpdate = (record?: ConfigurationCenterItemsModal) => {
  104 + if (record) {
  105 + openDrawer(true, {
  106 + isUpdate: true,
  107 + record: cloneDeep(record),
  108 + });
  109 + } else {
  110 + openDrawer(true, {
  111 + isUpdate: false,
  112 + });
  113 + }
  114 + };
  115 +
  116 + const { configurationPrefix } = useGlobSetting();
  117 + const isDev = isDevMode();
  118 +
  119 + const handlePreview = (record: BigScreenCenterItemsModal) => {
  120 + if (!unref(getPreviewFlag)) return;
  121 + window.open(
  122 + `${configurationPrefix}/${isDev ? '?dev=1&' : '?'}configurationId=${record!.id}&lightbox=1`
  123 + );
  124 + };
  125 +
  126 + const handleDesign = (record: BigScreenCenterItemsModal) => {
  127 + if (!unref(getDesignFlag)) return;
  128 + window.open(`${configurationPrefix}/${isDev ? '?dev=1&' : '?'}configurationId=${record!.id}`);
  129 + };
  130 +
  131 + const handleDelete = async (record: BigScreenCenterItemsModal) => {
  132 + try {
  133 + await deleteBigScreenenter([record.id]);
  134 + createMessage.success('删除成功');
  135 + await getListData();
  136 + } catch (error) {}
  137 + };
  138 +
  139 + const handleCardLayoutChange = () => {
  140 + pagination.current = 1;
  141 + getListData();
  142 + };
  143 +
  144 + const listEl = ref<Nullable<ComponentElRef>>(null);
  145 +
  146 + onMounted(() => {
  147 + const clientHeight = document.documentElement.clientHeight;
  148 + const rect = getBoundingClientRect(unref(listEl)!.$el!) as DOMRect;
  149 + // margin-top 24 height 24
  150 + const paginationHeight = 24 + 24 + 8;
  151 + // list pading top 8 maring-top 8 extra slot 56
  152 + const listContainerMarginBottom = 8 + 8 + 56;
  153 + const listContainerHeight =
  154 + clientHeight - rect.top - paginationHeight - listContainerMarginBottom;
  155 + const listContainerEl = (unref(listEl)!.$el as HTMLElement).querySelector(
  156 + '.ant-spin-container'
  157 + ) as HTMLElement;
  158 + listContainerEl &&
  159 + (listContainerEl.style.height = listContainerHeight + 'px') &&
  160 + (listContainerEl.style.overflowY = 'auto') &&
  161 + (listContainerEl.style.overflowX = 'hidden');
  162 + });
  163 +</script>
  164 +
  165 +<template>
  166 + <PageWrapper dense contentFullHeight contentClass="flex">
  167 + <OrganizationIdTree @select="handleSelect" ref="organizationIdTreeRef" />
  168 + <section class="flex-auto p-4 w-3/4 xl:w-4/5 w-full configuration-list">
  169 + <div class="flex-auto w-full bg-light-50 dark:bg-dark-900 p-4">
  170 + <BasicForm @register="registerForm" />
  171 + </div>
  172 + <List
  173 + ref="listEl"
  174 + :loading="loading"
  175 + class="flex-auto bg-light-50 dark:bg-dark-900 !p-2 !mt-4"
  176 + position="bottom"
  177 + :pagination="pagination"
  178 + :data-source="dataSource"
  179 + :grid="{ gutter: 4, column: listColumn }"
  180 + >
  181 + <template #header>
  182 + <div class="flex gap-3 justify-end">
  183 + <Authority :value="ConfigurationPermission.CREATE">
  184 + <Button type="primary" @click="handleCreateOrUpdate()">新增大屏</Button>
  185 + </Authority>
  186 + <CardLayoutButton v-model:value="listColumn" @change="handleCardLayoutChange" />
  187 + <Tooltip title="刷新">
  188 + <Button type="primary" @click="getListData">
  189 + <ReloadOutlined @click="getListData" />
  190 + </Button>
  191 + </Tooltip>
  192 + </div>
  193 + </template>
  194 + <template #renderItem="{ item }">
  195 + <List.Item>
  196 + <Card hoverable>
  197 + <template #cover>
  198 + <div class="h-full w-full !flex justify-center items-center text-center p-1">
  199 + <img
  200 + class="w-full h-36"
  201 + alt="example"
  202 + :src="item.thumbnail || configurationSrc"
  203 + @click="handlePreview(item)"
  204 + />
  205 + </div>
  206 + </template>
  207 + <template class="ant-card-actions" #actions>
  208 + <Tooltip title="预览">
  209 + <AuthIcon
  210 + :auth="ConfigurationPermission.PREVIEW"
  211 + class="!text-lg"
  212 + icon="ant-design:eye-outlined"
  213 + @click="handlePreview(item)"
  214 + />
  215 + </Tooltip>
  216 + <Tooltip title="设计">
  217 + <AuthIcon
  218 + :auth="ConfigurationPermission.DESIGN"
  219 + class="!text-lg"
  220 + icon="ant-design:edit-outlined"
  221 + @click="handleDesign(item)"
  222 + />
  223 + </Tooltip>
  224 + <AuthDropDown
  225 + :dropMenuList="[
  226 + {
  227 + text: '编辑',
  228 + auth: ConfigurationPermission.UPDATE,
  229 + icon: 'clarity:note-edit-line',
  230 + event: '',
  231 + onClick: handleCreateOrUpdate.bind(null, item),
  232 + },
  233 + {
  234 + text: '删除',
  235 + auth: ConfigurationPermission.DELETE,
  236 + icon: 'ant-design:delete-outlined',
  237 + event: '',
  238 + popconfirm: {
  239 + title: '是否确认删除操作?',
  240 + onConfirm: handleDelete.bind(null, item),
  241 + },
  242 + },
  243 + ]"
  244 + :trigger="['hover']"
  245 + />
  246 + </template>
  247 + <Card.Meta>
  248 + <template #title>
  249 + <span class="truncate">{{ item.name }}</span>
  250 + </template>
  251 + <template #description>
  252 + <div class="truncate h-11">
  253 + <div class="truncate">{{ item.organizationDTO.name }}</div>
  254 + <div class="truncate">{{ item.remark || '' }} </div>
  255 + </div>
  256 + </template>
  257 + </Card.Meta>
  258 + </Card>
  259 + </List.Item>
  260 + </template>
  261 + </List>
  262 + </section>
  263 + <ConfigurationCenterDrawer @register="registerDrawer" @success="getListData" />
  264 + </PageWrapper>
  265 +</template>
  266 +
  267 +<style lang="less" scoped>
  268 + .configuration-list:deep(.ant-list-header) {
  269 + border-bottom: none !important;
  270 + }
  271 +
  272 + .configuration-list:deep(.ant-list-pagination) {
  273 + height: 24px;
  274 + }
  275 +
  276 + .configuration-list:deep(.ant-card-body) {
  277 + padding: 16px !important;
  278 + }
  279 +
  280 + .configuration-list:deep(.ant-list-empty-text) {
  281 + @apply w-full h-full flex justify-center items-center;
  282 + }
  283 +</style>
... ...