Commit bf03da27976dd84f791f387a4b95358d0b3dc05f

Authored by fengwotao
2 parents a1b40fa9 886c794c

Merge branch 'main_dev' into ft

@@ -12,6 +12,7 @@ @@ -12,6 +12,7 @@
12 "MQTT", 12 "MQTT",
13 "notif", 13 "notif",
14 "PROTOBUF", 14 "PROTOBUF",
  15 + "SCADA",
15 "SNMP", 16 "SNMP",
16 "unref", 17 "unref",
17 "vben", 18 "vben",
@@ -10,6 +10,7 @@ import { FileUploadResponse } from '../../oem/model'; @@ -10,6 +10,7 @@ import { FileUploadResponse } from '../../oem/model';
10 enum API { 10 enum API {
11 basicUrl = '/configuration/center', 11 basicUrl = '/configuration/center',
12 UPLOAD = '/oss/upload', 12 UPLOAD = '/oss/upload',
  13 + SHARE = '/configuration/center/share',
13 } 14 }
14 15
15 export const getPage = (params: queryPageParams) => { 16 export const getPage = (params: queryPageParams) => {
@@ -49,3 +50,20 @@ export const saveOrUpdateConfigurationCenter = ( @@ -49,3 +50,20 @@ export const saveOrUpdateConfigurationCenter = (
49 export const uploadThumbnail = (file: FormData) => { 50 export const uploadThumbnail = (file: FormData) => {
50 return defHttp.post<FileUploadResponse>({ url: API.UPLOAD, params: file }); 51 return defHttp.post<FileUploadResponse>({ url: API.UPLOAD, params: file });
51 }; 52 };
  53 +
  54 +export const shareConfiguration = (record: {
  55 + id: string;
  56 + isShare: boolean;
  57 + accessCredentials: string;
  58 +}) => {
  59 + const { accessCredentials, isShare, id } = record;
  60 + return defHttp.post(
  61 + {
  62 + url: `${API.SHARE}/${id}`,
  63 + params: { isShare, ...(accessCredentials ? { accessCredentials } : {}) },
  64 + },
  65 + {
  66 + joinParamsToUrl: true,
  67 + }
  68 + );
  69 +};
@@ -6,6 +6,7 @@ export interface ConfigurationCenterItemsModal { @@ -6,6 +6,7 @@ export interface ConfigurationCenterItemsModal {
6 createTime: string; 6 createTime: string;
7 creator: string; 7 creator: string;
8 remark: string; 8 remark: string;
  9 + publicId?: string;
9 } 10 }
10 export type queryPageParams = BasicPageParams & { 11 export type queryPageParams = BasicPageParams & {
11 name?: Nullable<string>; 12 name?: Nullable<string>;
@@ -22,6 +22,7 @@ enum DataBoardUrl { @@ -22,6 +22,7 @@ enum DataBoardUrl {
22 DELETE_DATA_BOARD = '/data_board', 22 DELETE_DATA_BOARD = '/data_board',
23 UPDATE_DATA_BOARD = '/data_board/update', 23 UPDATE_DATA_BOARD = '/data_board/update',
24 UPDATE_DATA_BOARD_LAYOUT = '/data_board', 24 UPDATE_DATA_BOARD_LAYOUT = '/data_board',
  25 + SHARE_DATA_BOARD = '/data_board/share',
25 } 26 }
26 27
27 enum DataComponentUrl { 28 enum DataComponentUrl {
@@ -232,3 +233,16 @@ export const getDeviceRelation = (params: { deviceId: string; isSlave: boolean } @@ -232,3 +233,16 @@ export const getDeviceRelation = (params: { deviceId: string; isSlave: boolean }
232 params, 233 params,
233 }); 234 });
234 }; 235 };
  236 +
  237 +export const shareBoard = (record: {
  238 + id: string;
  239 + accessCredentials?: string;
  240 + isShare?: boolean;
  241 +}) => {
  242 + const { id, isShare, accessCredentials } = record;
  243 + return defHttp.post({
  244 + url: `${DataBoardUrl.SHARE_DATA_BOARD}/${id}?isShare=${isShare}${
  245 + accessCredentials ? `&accessCredentials=${accessCredentials}` : ''
  246 + }`,
  247 + });
  248 +};
@@ -48,6 +48,8 @@ export interface DataBoardRecord { @@ -48,6 +48,8 @@ export interface DataBoardRecord {
48 layout: Layout[]; 48 layout: Layout[];
49 defaultConfig: string; 49 defaultConfig: string;
50 tenantStatus: string; 50 tenantStatus: string;
  51 + publicId: string;
  52 + accessCredentials?: string;
51 } 53 }
52 54
53 export interface DataBoardList { 55 export interface DataBoardList {
@@ -36,6 +36,10 @@ enum DeviceManagerApi { @@ -36,6 +36,10 @@ enum DeviceManagerApi {
36 GATEWAY_DEVICE = '/device/gateway/list', 36 GATEWAY_DEVICE = '/device/gateway/list',
37 37
38 DEVICE_STATE_LOG_URL = '/device/state/log', 38 DEVICE_STATE_LOG_URL = '/device/state/log',
  39 +
  40 + DEVICE_PUBLIC = '/customer/public/device',
  41 +
  42 + DEVICE_PRIVATE = '/customer/device',
39 } 43 }
40 44
41 export const devicePage = (params: DeviceQueryParam) => { 45 export const devicePage = (params: DeviceQueryParam) => {
@@ -308,3 +312,21 @@ export const deviceStateLogPost = (data) => { @@ -308,3 +312,21 @@ export const deviceStateLogPost = (data) => {
308 // } 312 // }
309 ); 313 );
310 }; 314 };
  315 +
  316 +export const publicDevice = (tbDeviceId: string) => {
  317 + return defHttp.post(
  318 + {
  319 + url: `${DeviceManagerApi.DEVICE_PUBLIC}/${tbDeviceId}`,
  320 + },
  321 + { joinPrefix: false }
  322 + );
  323 +};
  324 +
  325 +export const privateDevice = (tbDeviceId: string) => {
  326 + return defHttp.delete(
  327 + {
  328 + url: `${DeviceManagerApi.DEVICE_PRIVATE}/${tbDeviceId}`,
  329 + },
  330 + { joinPrefix: false }
  331 + );
  332 +};
@@ -186,6 +186,9 @@ export interface DeviceRecord { @@ -186,6 +186,9 @@ export interface DeviceRecord {
186 name: string; 186 name: string;
187 transportType: string; 187 transportType: string;
188 }; 188 };
  189 + customerAdditionalInfo?: {
  190 + isPublic?: boolean;
  191 + };
189 } 192 }
190 193
191 export interface DeviceModelOfMatterAttrs { 194 export interface DeviceModelOfMatterAttrs {
  1 +import { ViewTypeEnum } from '/@/views/sys/share/config/config';
  2 +
  3 +export interface ShareRouteParams {
  4 + id: string;
  5 + viewType: ViewTypeEnum;
  6 + publicId: string;
  7 +}
  1 +import { defHttp } from '/@/utils/http/axios';
  2 +import { ViewTypeEnum } from '/@/views/sys/share/config/config';
  3 +
  4 +enum Api {
  5 + CHECK = '/share/check',
  6 + PUBLIC_LOGIN = '/auth/login/public',
  7 + SHARE_CONTENT = '/share',
  8 +}
  9 +
  10 +export const checkShareAccessToken = (type: ViewTypeEnum, id: string) => {
  11 + return defHttp.get<Record<'data', boolean>>({
  12 + url: `${Api.CHECK}/${type}/${id}`,
  13 + });
  14 +};
  15 +
  16 +export const sharePageLogin = (publicId: string) => {
  17 + return defHttp.post<Record<'token' | 'refreshToken', string>>(
  18 + {
  19 + url: Api.PUBLIC_LOGIN,
  20 + data: { publicId },
  21 + },
  22 + {
  23 + joinPrefix: false,
  24 + }
  25 + );
  26 +};
  27 +
  28 +export const getShareContent = (record: Record<'accessCredentials' | 'id', string>) => {
  29 + const { id, accessCredentials } = record;
  30 + return defHttp.get({
  31 + url: `${Api.SHARE_CONTENT}/${ViewTypeEnum.DATA_BOARD}/share_data/${id}`,
  32 + params: { accessCredentials },
  33 + });
  34 +};
@@ -14,4 +14,6 @@ export const PageEnum = { @@ -14,4 +14,6 @@ export const PageEnum = {
14 //设备配置 14 //设备配置
15 DEVICE_PROFILE: '/product/profiles', 15 DEVICE_PROFILE: '/product/profiles',
16 DEVICE_LIST: '/device/list', 16 DEVICE_LIST: '/device/list',
  17 +
  18 + SHARE_PAGE: '/share/:viewType/:id/:publicId',
17 }; 19 };
@@ -10,7 +10,8 @@ import { getAuthCache } from '/@/utils/auth'; @@ -10,7 +10,8 @@ import { getAuthCache } from '/@/utils/auth';
10 10
11 const LOGIN_PATH = PageEnum.BASE_LOGIN; 11 const LOGIN_PATH = PageEnum.BASE_LOGIN;
12 const ROOT_PATH = RootRoute.path; 12 const ROOT_PATH = RootRoute.path;
13 -const whitePathList: string[] = [LOGIN_PATH]; 13 +const SHARE_PATH = PageEnum.SHARE_PAGE;
  14 +const whitePathList: string[] = [LOGIN_PATH, SHARE_PATH];
14 // const userInfo1 = getAuthCache(USER_INFO_KEY); 15 // const userInfo1 = getAuthCache(USER_INFO_KEY);
15 // const userInfo = ref(userInfo1); 16 // const userInfo = ref(userInfo1);
16 17
@@ -32,7 +33,7 @@ export function createPermissionGuard(router: Router) { @@ -32,7 +33,7 @@ export function createPermissionGuard(router: Router) {
32 const token = userStore.getJwtToken; 33 const token = userStore.getJwtToken;
33 // Whitelist can be directly entered 34 // Whitelist can be directly entered
34 // 路由守卫拦截, 如果是已经登陆情况, 就不要回到登陆页面了; 35 // 路由守卫拦截, 如果是已经登陆情况, 就不要回到登陆页面了;
35 - if (whitePathList.includes(to.path as PageEnum)) { 36 + if (to.matched.find((item) => whitePathList.includes(item.path))) {
36 if (to.path === LOGIN_PATH && token) { 37 if (to.path === LOGIN_PATH && token) {
37 const isSessionTimeout = userStore.getSessionTimeout; 38 const isSessionTimeout = userStore.getSessionTimeout;
38 try { 39 try {
@@ -4,6 +4,7 @@ import { mainOutRoutes } from './mainOut'; @@ -4,6 +4,7 @@ import { mainOutRoutes } from './mainOut';
4 import { PageEnum } from '/@/enums/pageEnum'; 4 import { PageEnum } from '/@/enums/pageEnum';
5 import { t } from '/@/hooks/web/useI18n'; 5 import { t } from '/@/hooks/web/useI18n';
6 import { LAYOUT } from '../constant'; 6 import { LAYOUT } from '../constant';
  7 +import { PUBLIC_PAGE_ROUTER } from './public';
7 8
8 const modules = import.meta.globEager('./modules/**/*.ts'); 9 const modules = import.meta.globEager('./modules/**/*.ts');
9 const routeModuleList: AppRouteModule[] = []; 10 const routeModuleList: AppRouteModule[] = [];
@@ -86,4 +87,5 @@ export const basicRoutes = [ @@ -86,4 +87,5 @@ export const basicRoutes = [
86 REDIRECT_ROUTE, 87 REDIRECT_ROUTE,
87 PAGE_NOT_FOUND_ROUTE, 88 PAGE_NOT_FOUND_ROUTE,
88 DATA_BOARD_SHARE, 89 DATA_BOARD_SHARE,
  90 + PUBLIC_PAGE_ROUTER,
89 ]; 91 ];
  1 +import { AppRouteRecordRaw } from '../types';
  2 +import { PageEnum } from '/@/enums/pageEnum';
  3 +
  4 +export const PUBLIC_PAGE_ROUTER: AppRouteRecordRaw = {
  5 + path: PageEnum.SHARE_PAGE,
  6 + name: 'publicPage',
  7 + component: () => import('/@/views/sys/share/index.vue'),
  8 + meta: {
  9 + title: '公开',
  10 + hideBreadcrumb: true,
  11 + hideChildrenInMenu: true,
  12 + },
  13 +};
  1 +import { FormSchema } from '/@/components/Form';
  2 +
  3 +export enum ViewTypeEnum {
  4 + PRIVATE_VIEW = 'PRIVATE_VIEW',
  5 + PUBLIC_VIEW = 'PUBLIC_VIEW',
  6 +}
  7 +
  8 +export enum ViewTypeNameEnum {
  9 + PRIVATE_VIEW = '私有',
  10 + PUBLIC_VIEW = '公共',
  11 +}
  12 +
  13 +export enum FieldsEnum {
  14 + IS_SHARE = 'isShare',
  15 + ACCESS_CREDENTIALS = 'accessCredentials',
  16 +}
  17 +
  18 +export const schemas: FormSchema[] = [
  19 + {
  20 + field: FieldsEnum.IS_SHARE,
  21 + label: '公开性',
  22 + component: 'Switch',
  23 + defaultValue: false,
  24 + },
  25 + {
  26 + field: FieldsEnum.ACCESS_CREDENTIALS,
  27 + label: '访问凭证',
  28 + component: 'Input',
  29 + ifShow: ({ model }) => model[FieldsEnum.IS_SHARE],
  30 + componentProps: {
  31 + maxLength: 64,
  32 + },
  33 + },
  34 +];
  1 +export { default as ShareModal } from './index.vue';
  1 +<script lang="ts" setup>
  2 + import { BasicForm, useForm } from '/@/components/Form';
  3 + import { BasicModal, useModalInner } from '/@/components/Modal';
  4 + import { FieldsEnum, schemas, ViewTypeEnum } from './config';
  5 + import { DataBoardRecord } from '/@/api/dataBoard/model';
  6 + import { Alert } from 'ant-design-vue';
  7 + import { ref, unref } from 'vue';
  8 + import { nextTick } from 'vue';
  9 +
  10 + const props = defineProps<{
  11 + shareApi?: (...args: any[]) => Promise<any>;
  12 + }>();
  13 +
  14 + const loading = ref(false);
  15 + const record = ref<DataBoardRecord>({} as unknown as DataBoardRecord);
  16 +
  17 + const [register, { closeModal }] = useModalInner(async (data: DataBoardRecord) => {
  18 + record.value = data;
  19 + await nextTick();
  20 + setFieldsValue({
  21 + [FieldsEnum.IS_SHARE]: data.viewType === ViewTypeEnum.PUBLIC_VIEW,
  22 + [FieldsEnum.ACCESS_CREDENTIALS]: data.accessCredentials,
  23 + });
  24 + });
  25 +
  26 + const [registerForm, { getFieldsValue, setFieldsValue }] = useForm({
  27 + schemas,
  28 + showActionButtonGroup: false,
  29 + layout: 'inline',
  30 + labelWidth: 120,
  31 + });
  32 +
  33 + const emit = defineEmits(['register', 'success']);
  34 +
  35 + const handleSubmit = async () => {
  36 + try {
  37 + loading.value = true;
  38 + const value = getFieldsValue();
  39 + const { id } = unref(record);
  40 + await props?.shareApi?.({ id, ...value });
  41 + closeModal();
  42 + emit('success');
  43 + } finally {
  44 + loading.value = false;
  45 + }
  46 + };
  47 +</script>
  48 +
  49 +<template>
  50 + <BasicModal title="分享" @register="register" @ok="handleSubmit" :okButtonProps="{ loading }">
  51 + <Alert
  52 + class="!mb-4"
  53 + type="info"
  54 + message="私有视图只有项目成员可以浏览,公开视图拥有一个公开的 URL,任何人无需登录即可浏览."
  55 + />
  56 + <BasicForm @register="registerForm" />
  57 + </BasicModal>
  58 +</template>
@@ -15,6 +15,8 @@ export enum ConfigurationPermission { @@ -15,6 +15,8 @@ export enum ConfigurationPermission {
15 DELETE = 'api:yt:configuration:center:delete', 15 DELETE = 'api:yt:configuration:center:delete',
16 DESIGN = 'api:yt:configuration:center:get_configuration_info:design', 16 DESIGN = 'api:yt:configuration:center:get_configuration_info:design',
17 PREVIEW = 'api:yt:configuration:center:get_configuration_info:preview', 17 PREVIEW = 'api:yt:configuration:center:get_configuration_info:preview',
  18 + SHARE = 'api:yt:configuration:center:share',
  19 + UN_SHARE = 'api:yt:configuration:center:monopoly',
18 } 20 }
19 21
20 export const PC_DEFAULT_CONTENT = 22 export const PC_DEFAULT_CONTENT =
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 {  
7 - deleteConfigurationCenter,  
8 - getPage,  
9 - } 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 { ConfigurationPermission, searchFormSchema } from './center.data';  
14 - import { useMessage } from '/@/hooks/web/useMessage';  
15 - import { Authority } from '/@/components/Authority';  
16 - import { isDevMode } from '/@/utils/env';  
17 - import ConfigurationCenterDrawer from './ConfigurationCenterDrawer.vue';  
18 - import { useDrawer } from '/@/components/Drawer';  
19 - import { getBoundingClientRect } from '/@/utils/domUtils';  
20 - import configurationSrc from '/@/assets/icons/configuration.svg';  
21 - import { cloneDeep } from 'lodash';  
22 - import { usePermission } from '/@/hooks/web/usePermission';  
23 - import { useGlobSetting } from '/@/hooks/setting';  
24 - import { AuthIcon, CardLayoutButton } from '/@/components/Widget';  
25 - import AuthDropDown from '/@/components/Widget/AuthDropDown.vue';  
26 -  
27 - const listColumn = ref(5);  
28 -  
29 - const { createMessage } = useMessage();  
30 -  
31 - const organizationId = ref<Nullable<number>>(null);  
32 -  
33 - const pagination = reactive<PaginationProps>({  
34 - size: 'small',  
35 - showTotal: (total: number) => `共 ${total} 条数据`,  
36 - current: 1,  
37 - pageSize: unref(listColumn) * 2,  
38 - onChange: (page: number) => {  
39 - pagination.current = page;  
40 - getListData();  
41 - },  
42 - });  
43 -  
44 - const loading = ref(false);  
45 -  
46 - const dataSource = ref<ConfigurationCenterItemsModal[]>([]);  
47 -  
48 - const [registerForm, { getFieldsValue }] = useForm({  
49 - schemas: searchFormSchema,  
50 - showAdvancedButton: true,  
51 - labelWidth: 100,  
52 - compact: true,  
53 - resetFunc: () => {  
54 - resetFn();  
55 - organizationId.value = null;  
56 - return getListData();  
57 - },  
58 - submitFunc: async () => {  
59 - const value = getFieldsValue();  
60 - getListData(value);  
61 - },  
62 - });  
63 -  
64 - async function getListData(value: Recordable = {}) {  
65 - try {  
66 - loading.value = true;  
67 - const pageSize = unref(listColumn) * 2;  
68 - const { items, total } = await getPage({  
69 - organizationId: unref(organizationId),  
70 - ...value,  
71 - page: pagination.current!,  
72 - pageSize,  
73 - });  
74 -  
75 - dataSource.value = items;  
76 - Object.assign(pagination, { total, pageSize });  
77 - } catch (error) {  
78 - } finally {  
79 - loading.value = false;  
80 - }  
81 - }  
82 -  
83 - onMounted(() => {  
84 - getListData();  
85 - });  
86 -  
87 - const searchInfo = reactive<Recordable>({});  
88 - const { organizationIdTreeRef, resetFn } = useResetOrganizationTree(searchInfo);  
89 - const handleSelect = (orgId: number) => {  
90 - organizationId.value = orgId;  
91 - getListData();  
92 - };  
93 -  
94 - const [registerDrawer, { openDrawer }] = useDrawer();  
95 -  
96 - const { hasPermission } = usePermission();  
97 -  
98 - const getPreviewFlag = computed(() => {  
99 - return hasPermission(ConfigurationPermission.PREVIEW);  
100 - });  
101 -  
102 - const getDesignFlag = computed(() => {  
103 - return hasPermission(ConfigurationPermission.DESIGN);  
104 - });  
105 -  
106 - const handleCreateOrUpdate = (record?: ConfigurationCenterItemsModal) => {  
107 - if (record) {  
108 - openDrawer(true, {  
109 - isUpdate: true,  
110 - record: cloneDeep(record),  
111 - });  
112 - } else {  
113 - openDrawer(true, {  
114 - isUpdate: false,  
115 - });  
116 - }  
117 - };  
118 -  
119 - const { configurationPrefix } = useGlobSetting();  
120 - const isDev = isDevMode();  
121 -  
122 - const handlePreview = (record: ConfigurationCenterItemsModal) => {  
123 - if (!unref(getPreviewFlag)) return;  
124 - window.open(  
125 - `${configurationPrefix}/${isDev ? '?dev=1&' : '?'}configurationId=${record!.id}&lightbox=1`  
126 - );  
127 - };  
128 -  
129 - const handleDesign = (record: ConfigurationCenterItemsModal) => {  
130 - if (!unref(getDesignFlag)) return;  
131 - window.open(`${configurationPrefix}/${isDev ? '?dev=1&' : '?'}configurationId=${record!.id}`);  
132 - };  
133 -  
134 - const handleDelete = async (record: ConfigurationCenterItemsModal) => {  
135 - try {  
136 - await deleteConfigurationCenter([record.id]);  
137 - createMessage.success('删除成功');  
138 - await getListData();  
139 - } catch (error) {}  
140 - };  
141 -  
142 - const handleCardLayoutChange = () => {  
143 - pagination.current = 1;  
144 - getListData();  
145 - };  
146 -  
147 - const listEl = ref<Nullable<ComponentElRef>>(null);  
148 -  
149 - onMounted(() => {  
150 - const clientHeight = document.documentElement.clientHeight;  
151 - const rect = getBoundingClientRect(unref(listEl)!.$el!) as DOMRect;  
152 - // margin-top 24 height 24  
153 - const paginationHeight = 24 + 24 + 8;  
154 - // list pading top 8 maring-top 8 extra slot 56  
155 - const listContainerMarginBottom = 8 + 8 + 56;  
156 - const listContainerHeight =  
157 - clientHeight - rect.top - paginationHeight - listContainerMarginBottom;  
158 - const listContainerEl = (unref(listEl)!.$el as HTMLElement).querySelector(  
159 - '.ant-spin-container'  
160 - ) as HTMLElement;  
161 - listContainerEl &&  
162 - (listContainerEl.style.height = listContainerHeight + 'px') &&  
163 - (listContainerEl.style.overflowY = 'auto') &&  
164 - (listContainerEl.style.overflowX = 'hidden');  
165 - });  
166 -</script>  
167 -  
168 -<template>  
169 - <PageWrapper dense contentFullHeight contentClass="flex">  
170 - <OrganizationIdTree @select="handleSelect" ref="organizationIdTreeRef" />  
171 - <section class="flex-auto p-4 w-3/4 xl:w-4/5 w-full configuration-list">  
172 - <div class="flex-auto w-full bg-light-50 dark:bg-dark-900 p-4">  
173 - <BasicForm @register="registerForm" />  
174 - </div>  
175 - <List  
176 - ref="listEl"  
177 - :loading="loading"  
178 - class="flex-auto bg-light-50 dark:bg-dark-900 !p-2 !mt-4"  
179 - position="bottom"  
180 - :pagination="pagination"  
181 - :data-source="dataSource"  
182 - :grid="{ gutter: 4, column: listColumn }"  
183 - >  
184 - <template #header>  
185 - <div class="flex gap-3 justify-end">  
186 - <Authority :value="ConfigurationPermission.CREATE">  
187 - <Button type="primary" @click="handleCreateOrUpdate()">新增组态</Button>  
188 - </Authority>  
189 - <CardLayoutButton v-model:value="listColumn" @change="handleCardLayoutChange" />  
190 - <Tooltip title="刷新">  
191 - <Button type="primary" @click="getListData">  
192 - <ReloadOutlined />  
193 - </Button>  
194 - </Tooltip>  
195 - </div>  
196 - </template>  
197 - <template #renderItem="{ item }">  
198 - <List.Item>  
199 - <Card hoverable>  
200 - <template #cover>  
201 - <div class="h-full w-full !flex justify-center items-center text-center p-1">  
202 - <img  
203 - class="w-full h-36"  
204 - alt="example"  
205 - :src="item.thumbnail || configurationSrc"  
206 - @click="handlePreview(item)"  
207 - />  
208 - </div>  
209 - </template>  
210 - <template class="ant-card-actions" #actions>  
211 - <Tooltip title="预览">  
212 - <AuthIcon  
213 - :auth="ConfigurationPermission.PREVIEW"  
214 - class="!text-lg"  
215 - icon="ant-design:eye-outlined"  
216 - @click="handlePreview(item)"  
217 - />  
218 - </Tooltip>  
219 - <Tooltip title="设计">  
220 - <AuthIcon  
221 - :auth="ConfigurationPermission.DESIGN"  
222 - class="!text-lg"  
223 - icon="ant-design:edit-outlined"  
224 - @click="handleDesign(item)"  
225 - />  
226 - </Tooltip>  
227 - <AuthDropDown  
228 - :dropMenuList="[  
229 - {  
230 - text: '编辑',  
231 - auth: ConfigurationPermission.UPDATE,  
232 - icon: 'clarity:note-edit-line',  
233 - event: '',  
234 - onClick: handleCreateOrUpdate.bind(null, item),  
235 - },  
236 - {  
237 - text: '删除',  
238 - auth: ConfigurationPermission.DELETE,  
239 - icon: 'ant-design:delete-outlined',  
240 - event: '',  
241 - popconfirm: {  
242 - title: '是否确认删除操作?',  
243 - onConfirm: handleDelete.bind(null, item),  
244 - },  
245 - },  
246 - ]"  
247 - :trigger="['hover']"  
248 - />  
249 - </template>  
250 - <Card.Meta>  
251 - <template #title>  
252 - <span class="truncate">{{ item.name }}</span>  
253 - </template>  
254 - <template #description>  
255 - <div class="truncate h-11">  
256 - <div class="truncate">{{ item.organizationDTO.name }}</div>  
257 - <div class="truncate">{{ item.remark || '' }} </div>  
258 - </div>  
259 - </template>  
260 - </Card.Meta>  
261 - </Card>  
262 - </List.Item>  
263 - </template>  
264 - </List>  
265 - </section>  
266 - <ConfigurationCenterDrawer @register="registerDrawer" @success="getListData" />  
267 - </PageWrapper>  
268 -</template>  
269 -  
270 -<style lang="less" scoped>  
271 - .configuration-list:deep(.ant-list-header) {  
272 - border-bottom: none !important;  
273 - }  
274 -  
275 - .configuration-list:deep(.ant-list-pagination) {  
276 - height: 24px;  
277 - }  
278 -  
279 - .configuration-list:deep(.ant-card-body) {  
280 - padding: 16px !important;  
281 - }  
282 -  
283 - .configuration-list:deep(.ant-list-empty-text) {  
284 - @apply w-full h-full flex justify-center items-center;  
285 - }  
286 -</style> 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 {
  7 + deleteConfigurationCenter,
  8 + getPage,
  9 + shareConfiguration,
  10 + } from '/@/api/configuration/center/configurationCenter';
  11 + import { ConfigurationCenterItemsModal } from '/@/api/configuration/center/model/configurationCenterModal';
  12 + import { PageWrapper } from '/@/components/Page';
  13 + import { BasicForm, useForm } from '/@/components/Form';
  14 + import { ConfigurationPermission, searchFormSchema } from './center.data';
  15 + import { useMessage } from '/@/hooks/web/useMessage';
  16 + import { Authority } from '/@/components/Authority';
  17 + import { isDevMode } from '/@/utils/env';
  18 + import ConfigurationCenterDrawer from './ConfigurationCenterDrawer.vue';
  19 + import { useDrawer } from '/@/components/Drawer';
  20 + import { getBoundingClientRect } from '/@/utils/domUtils';
  21 + import configurationSrc from '/@/assets/icons/configuration.svg';
  22 + import { cloneDeep } from 'lodash';
  23 + import { usePermission } from '/@/hooks/web/usePermission';
  24 + import { useGlobSetting } from '/@/hooks/setting';
  25 + import { AuthIcon, CardLayoutButton } from '/@/components/Widget';
  26 + import AuthDropDown from '/@/components/Widget/AuthDropDown.vue';
  27 + import { ShareModal } from '/@/views/common/ShareModal';
  28 + import { ViewTypeNameEnum } from '../../common/ShareModal/config';
  29 + import { useModal } from '/@/components/Modal';
  30 + import { useCopyToClipboard } from '/@/hooks/web/useCopyToClipboard';
  31 +
  32 + const listColumn = ref(5);
  33 +
  34 + const { createMessage } = useMessage();
  35 +
  36 + const organizationId = ref<Nullable<number>>(null);
  37 +
  38 + const pagination = reactive<PaginationProps>({
  39 + size: 'small',
  40 + showTotal: (total: number) => `共 ${total} 条数据`,
  41 + current: 1,
  42 + pageSize: unref(listColumn) * 2,
  43 + onChange: (page: number) => {
  44 + pagination.current = page;
  45 + getListData();
  46 + },
  47 + });
  48 +
  49 + const loading = ref(false);
  50 +
  51 + const dataSource = ref<ConfigurationCenterItemsModal[]>([]);
  52 +
  53 + const [registerForm, { getFieldsValue }] = useForm({
  54 + schemas: searchFormSchema,
  55 + showAdvancedButton: true,
  56 + labelWidth: 100,
  57 + compact: true,
  58 + resetFunc: () => {
  59 + resetFn();
  60 + organizationId.value = null;
  61 + return getListData();
  62 + },
  63 + submitFunc: async () => {
  64 + const value = getFieldsValue();
  65 + getListData(value);
  66 + },
  67 + });
  68 +
  69 + async function getListData(value: Recordable = {}) {
  70 + try {
  71 + loading.value = true;
  72 + const pageSize = unref(listColumn) * 2;
  73 + const { items, total } = await getPage({
  74 + organizationId: unref(organizationId),
  75 + ...value,
  76 + page: pagination.current!,
  77 + pageSize,
  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 + });
  91 +
  92 + const searchInfo = reactive<Recordable>({});
  93 + const { organizationIdTreeRef, resetFn } = useResetOrganizationTree(searchInfo);
  94 + const handleSelect = (orgId: number) => {
  95 + organizationId.value = orgId;
  96 + getListData();
  97 + };
  98 +
  99 + const [registerDrawer, { openDrawer }] = useDrawer();
  100 +
  101 + const { hasPermission } = usePermission();
  102 +
  103 + const getPreviewFlag = computed(() => {
  104 + return hasPermission(ConfigurationPermission.PREVIEW);
  105 + });
  106 +
  107 + const getDesignFlag = computed(() => {
  108 + return hasPermission(ConfigurationPermission.DESIGN);
  109 + });
  110 +
  111 + const getShareFlag = computed(() => {
  112 + return hasPermission(ConfigurationPermission.SHARE);
  113 + });
  114 +
  115 + const handleCreateOrUpdate = (record?: ConfigurationCenterItemsModal) => {
  116 + if (record) {
  117 + openDrawer(true, {
  118 + isUpdate: true,
  119 + record: cloneDeep(record),
  120 + });
  121 + } else {
  122 + openDrawer(true, {
  123 + isUpdate: false,
  124 + });
  125 + }
  126 + };
  127 +
  128 + const { configurationPrefix } = useGlobSetting();
  129 + const isDev = isDevMode();
  130 +
  131 + const handlePreview = (record: ConfigurationCenterItemsModal) => {
  132 + if (!unref(getPreviewFlag)) return;
  133 + window.open(
  134 + `${configurationPrefix}/${isDev ? '?dev=1&' : '?'}configurationId=${record!.id}&lightbox=1`
  135 + );
  136 + };
  137 +
  138 + const handleDesign = (record: ConfigurationCenterItemsModal) => {
  139 + if (!unref(getDesignFlag)) return;
  140 + window.open(`${configurationPrefix}/${isDev ? '?dev=1&' : '?'}configurationId=${record!.id}`);
  141 + };
  142 +
  143 + const handleDelete = async (record: ConfigurationCenterItemsModal) => {
  144 + try {
  145 + await deleteConfigurationCenter([record.id]);
  146 + createMessage.success('删除成功');
  147 + await getListData();
  148 + } catch (error) {}
  149 + };
  150 +
  151 + const handleCardLayoutChange = () => {
  152 + pagination.current = 1;
  153 + getListData();
  154 + };
  155 +
  156 + const { clipboardRef, isSuccessRef } = useCopyToClipboard();
  157 + const handleCreateShareUrl = (record: ConfigurationCenterItemsModal) => {
  158 + if (!unref(getShareFlag)) return;
  159 + const { origin } = location;
  160 + const searchParams = new URLSearchParams();
  161 + isDev && searchParams.set('dev', '1');
  162 + searchParams.set('share', 'SCADA');
  163 + searchParams.set('configurationId', record.id);
  164 + searchParams.set('publicId', record.publicId || '');
  165 + searchParams.set('lightbox', '1');
  166 + const url = `${origin}${configurationPrefix}/?${searchParams.toString()}`;
  167 + clipboardRef.value = url;
  168 + if (unref(isSuccessRef)) {
  169 + createMessage.success('复制成功~');
  170 + }
  171 + };
  172 +
  173 + const [registerShareModal, { openModal }] = useModal();
  174 +
  175 + const handleOpenShareModal = (record: ConfigurationCenterItemsModal) => {
  176 + openModal(true, record);
  177 + };
  178 +
  179 + const listEl = ref<Nullable<ComponentElRef>>(null);
  180 +
  181 + onMounted(() => {
  182 + const clientHeight = document.documentElement.clientHeight;
  183 + const rect = getBoundingClientRect(unref(listEl)!.$el!) as DOMRect;
  184 + // margin-top 24 height 24
  185 + const paginationHeight = 24 + 24 + 8;
  186 + // list pading top 8 maring-top 8 extra slot 56
  187 + const listContainerMarginBottom = 8 + 8 + 56;
  188 + const listContainerHeight =
  189 + clientHeight - rect.top - paginationHeight - listContainerMarginBottom;
  190 + const listContainerEl = (unref(listEl)!.$el as HTMLElement).querySelector(
  191 + '.ant-spin-container'
  192 + ) as HTMLElement;
  193 + listContainerEl &&
  194 + (listContainerEl.style.height = listContainerHeight + 'px') &&
  195 + (listContainerEl.style.overflowY = 'auto') &&
  196 + (listContainerEl.style.overflowX = 'hidden');
  197 + });
  198 +</script>
  199 +
  200 +<template>
  201 + <PageWrapper dense contentFullHeight contentClass="flex">
  202 + <OrganizationIdTree @select="handleSelect" ref="organizationIdTreeRef" />
  203 + <section class="flex-auto p-4 w-3/4 xl:w-4/5 w-full configuration-list">
  204 + <div class="flex-auto w-full bg-light-50 dark:bg-dark-900 p-4">
  205 + <BasicForm @register="registerForm" />
  206 + </div>
  207 + <List
  208 + ref="listEl"
  209 + :loading="loading"
  210 + class="flex-auto bg-light-50 dark:bg-dark-900 !p-2 !mt-4"
  211 + position="bottom"
  212 + :pagination="pagination"
  213 + :data-source="dataSource"
  214 + :grid="{ gutter: 4, column: listColumn }"
  215 + >
  216 + <template #header>
  217 + <div class="flex gap-3 justify-end">
  218 + <Authority :value="ConfigurationPermission.CREATE">
  219 + <Button type="primary" @click="handleCreateOrUpdate()">新增组态</Button>
  220 + </Authority>
  221 + <CardLayoutButton v-model:value="listColumn" @change="handleCardLayoutChange" />
  222 + <Tooltip title="刷新">
  223 + <Button type="primary" @click="getListData">
  224 + <ReloadOutlined />
  225 + </Button>
  226 + </Tooltip>
  227 + </div>
  228 + </template>
  229 + <template #renderItem="{ item }">
  230 + <List.Item>
  231 + <Card hoverable class="card-container">
  232 + <template #cover>
  233 + <div
  234 + class="img-container h-full w-full !flex justify-center items-center text-center p-1 relative"
  235 + >
  236 + <img
  237 + class="w-full h-36"
  238 + alt="example"
  239 + :src="item.thumbnail || configurationSrc"
  240 + @click="handlePreview(item)"
  241 + />
  242 + <span
  243 + class="absolute top-0 left-0 text-light-50 transform -rotate-45 translate-y-1"
  244 + >
  245 + {{ ViewTypeNameEnum[item.viewType] }}
  246 + </span>
  247 + </div>
  248 + </template>
  249 + <template class="ant-card-actions" #actions>
  250 + <Tooltip title="预览">
  251 + <AuthIcon
  252 + :auth="ConfigurationPermission.PREVIEW"
  253 + class="!text-lg"
  254 + icon="ant-design:eye-outlined"
  255 + @click="handlePreview(item)"
  256 + />
  257 + </Tooltip>
  258 + <Tooltip title="设计">
  259 + <AuthIcon
  260 + :auth="ConfigurationPermission.DESIGN"
  261 + class="!text-lg"
  262 + icon="ant-design:edit-outlined"
  263 + @click="handleDesign(item)"
  264 + />
  265 + </Tooltip>
  266 + <Tooltip title="分享">
  267 + <AuthIcon
  268 + :auth="ConfigurationPermission.SHARE"
  269 + class="!text-lg"
  270 + icon="ant-design:share-alt-outlined"
  271 + @click="handleCreateShareUrl(item)"
  272 + />
  273 + </Tooltip>
  274 + <AuthDropDown
  275 + :dropMenuList="[
  276 + {
  277 + text: '分享',
  278 + auth: ConfigurationPermission.SHARE,
  279 + icon: 'ant-design:share-alt-outlined',
  280 + event: '',
  281 + onClick: handleOpenShareModal.bind(null, item),
  282 + },
  283 + {
  284 + text: '编辑',
  285 + auth: ConfigurationPermission.UPDATE,
  286 + icon: 'clarity:note-edit-line',
  287 + event: '',
  288 + onClick: handleCreateOrUpdate.bind(null, item),
  289 + },
  290 + {
  291 + text: '删除',
  292 + auth: ConfigurationPermission.DELETE,
  293 + icon: 'ant-design:delete-outlined',
  294 + event: '',
  295 + popconfirm: {
  296 + title: '是否确认删除操作?',
  297 + onConfirm: handleDelete.bind(null, item),
  298 + },
  299 + },
  300 + ]"
  301 + :trigger="['hover']"
  302 + />
  303 + </template>
  304 + <Card.Meta>
  305 + <template #title>
  306 + <span class="truncate">{{ item.name }}</span>
  307 + </template>
  308 + <template #description>
  309 + <div class="truncate h-11">
  310 + <div class="truncate">{{ item.organizationDTO.name }}</div>
  311 + <div class="truncate">{{ item.remark || '' }} </div>
  312 + </div>
  313 + </template>
  314 + </Card.Meta>
  315 + </Card>
  316 + </List.Item>
  317 + </template>
  318 + </List>
  319 + </section>
  320 + <ConfigurationCenterDrawer @register="registerDrawer" @success="getListData" />
  321 + <ShareModal
  322 + @register="registerShareModal"
  323 + :shareApi="shareConfiguration"
  324 + @success="getListData"
  325 + />
  326 + </PageWrapper>
  327 +</template>
  328 +
  329 +<style lang="less" scoped>
  330 + .configuration-list:deep(.ant-list-header) {
  331 + border-bottom: none !important;
  332 + }
  333 +
  334 + .configuration-list:deep(.ant-list-pagination) {
  335 + height: 24px;
  336 + }
  337 +
  338 + .configuration-list:deep(.ant-card-body) {
  339 + padding: 16px !important;
  340 + }
  341 +
  342 + .configuration-list:deep(.ant-list-empty-text) {
  343 + @apply w-full h-full flex justify-center items-center;
  344 + }
  345 +
  346 + .card-container {
  347 + // background-color: red;
  348 + .img-container {
  349 + border-top-left-radius: 80px;
  350 + background-color: #fff;
  351 + }
  352 + }
  353 +
  354 + .card-container:deep(.ant-card-cover) {
  355 + background-color: red;
  356 + }
  357 +</style>
1 import { formatToDate } from '/@/utils/dateUtil'; 1 import { formatToDate } from '/@/utils/dateUtil';
2 import { BasicColumn } from '/@/components/Table'; 2 import { BasicColumn } from '/@/components/Table';
3 import { FormSchema } from '/@/components/Table'; 3 import { FormSchema } from '/@/components/Table';
4 -import { DeviceTypeEnum, DeviceState } from '/@/api/device/model/deviceModel'; 4 +import { DeviceTypeEnum, DeviceState, DeviceRecord } from '/@/api/device/model/deviceModel';
5 import { deviceProfile } from '/@/api/device/deviceManager'; 5 import { deviceProfile } from '/@/api/device/deviceManager';
  6 +import { h } from 'vue';
  7 +import { Tag } from 'ant-design-vue';
6 8
7 // 表格列数据 9 // 表格列数据
8 export const columns: BasicColumn[] = [ 10 export const columns: BasicColumn[] = [
@@ -50,9 +52,13 @@ export const columns: BasicColumn[] = [ @@ -50,9 +52,13 @@ export const columns: BasicColumn[] = [
50 width: 100, 52 width: 100,
51 }, 53 },
52 { 54 {
53 - title: '标签',  
54 - dataIndex: 'label', 55 + title: '公开',
  56 + dataIndex: 'public',
55 width: 100, 57 width: 100,
  58 + customRender({ record }: { record: DeviceRecord }) {
  59 + const flag = record?.customerAdditionalInfo?.isPublic;
  60 + return h(Tag, { color: flag ? 'blue' : 'orange' }, () => (flag ? '公开' : '私有'));
  61 + },
56 }, 62 },
57 { 63 {
58 title: '最后连接时间', 64 title: '最后连接时间',
@@ -146,6 +146,13 @@ @@ -146,6 +146,13 @@
146 onClick: handleDispatchCustomer.bind(null, record), 146 onClick: handleDispatchCustomer.bind(null, record),
147 }, 147 },
148 { 148 {
  149 + label: record?.customerAdditionalInfo?.isPublic ? '私有' : '公开',
  150 + icon: record?.customerAdditionalInfo?.isPublic
  151 + ? 'ant-design:lock-outlined'
  152 + : 'ant-design:unlock-outlined',
  153 + onClick: handlePublicDevice.bind(null, record),
  154 + },
  155 + {
149 label: '上下线记录', 156 label: '上下线记录',
150 icon: 'ant-design:rise-outlined', 157 icon: 'ant-design:rise-outlined',
151 onClick: handleUpAndDownRecord.bind(null, record), 158 onClick: handleUpAndDownRecord.bind(null, record),
@@ -198,6 +205,8 @@ @@ -198,6 +205,8 @@
198 checkDeviceOccupied, 205 checkDeviceOccupied,
199 cancelDispatchCustomer, 206 cancelDispatchCustomer,
200 getGATEWAY, 207 getGATEWAY,
  208 + privateDevice,
  209 + publicDevice,
201 } from '/@/api/device/deviceManager'; 210 } from '/@/api/device/deviceManager';
202 import { PageEnum } from '/@/enums/pageEnum'; 211 import { PageEnum } from '/@/enums/pageEnum';
203 import { useGo } from '/@/hooks/web/usePage'; 212 import { useGo } from '/@/hooks/web/usePage';
@@ -218,6 +227,7 @@ @@ -218,6 +227,7 @@
218 import { Authority } from '/@/components/Authority'; 227 import { Authority } from '/@/components/Authority';
219 import { useRoute, useRouter } from 'vue-router'; 228 import { useRoute, useRouter } from 'vue-router';
220 import { useBatchOperation } from '/@/utils/useBatchOperation'; 229 import { useBatchOperation } from '/@/utils/useBatchOperation';
  230 + import { useSyncConfirm } from '/@/hooks/component/useSyncConfirm';
221 231
222 export default defineComponent({ 232 export default defineComponent({
223 name: 'DeviceManagement', 233 name: 'DeviceManagement',
@@ -440,6 +450,28 @@ @@ -440,6 +450,28 @@
440 reload(); 450 reload();
441 }; 451 };
442 452
  453 + const { createSyncConfirm } = useSyncConfirm();
  454 + const handlePublicDevice = async (record: DeviceRecord) => {
  455 + try {
  456 + const publicFlag = record?.customerAdditionalInfo?.isPublic;
  457 + const type = publicFlag ? '私有' : '公开';
  458 + const flag = await createSyncConfirm({
  459 + iconType: 'warning',
  460 + title: `您确定要将设备 '${record.name}' 设为${type}吗?`,
  461 + content: `确认后,设备及其所有数据将被设为${type}并${
  462 + publicFlag ? '不' : ''
  463 + }可被其他人访问。`,
  464 + });
  465 + if (!flag) return;
  466 + if (publicFlag) {
  467 + await privateDevice(record.tbDeviceId);
  468 + } else {
  469 + await publicDevice(record.tbDeviceId);
  470 + }
  471 + reload();
  472 + } catch (error) {}
  473 + };
  474 +
443 onMounted(() => { 475 onMounted(() => {
444 const queryParams = ROUTE.query as Record<'deviceProfileId', undefined | string>; 476 const queryParams = ROUTE.query as Record<'deviceProfileId', undefined | string>;
445 const { setFieldsValue } = getForm(); 477 const { setFieldsValue } = getForm();
@@ -479,6 +511,7 @@ @@ -479,6 +511,7 @@
479 registerImportModal, 511 registerImportModal,
480 handleBatchImport, 512 handleBatchImport,
481 handleImportFinally, 513 handleImportFinally,
  514 + handlePublicDevice,
482 }; 515 };
483 }, 516 },
484 }); 517 });
  1 +import { useRouter } from 'vue-router';
  2 +
  3 +export enum ViewTypeEnum {
  4 + DATA_BOARD = 'DATA_BOARD',
  5 + LARGE_SCREEN = 'LARGE_SCREEN',
  6 + SCADA = 'SCADA',
  7 +}
  8 +
  9 +export const goShareUrl = (options: { type: ViewTypeEnum; id: string }, openNew?: false) => {
  10 + const { type, id } = options;
  11 + const ROUTER = useRouter();
  12 + const { origin } = location;
  13 + const path = `/share/${type}/${id}`;
  14 + openNew ? ROUTER.push(path) : open(`${origin}${path}`);
  15 +};
  1 +<script lang="ts" setup>
  2 + import { onMounted } from 'vue';
  3 + import { BasicModal, useModal } from '/@/components/Modal';
  4 + import { Spin } from 'ant-design-vue';
  5 + import { ref } from 'vue';
  6 + import { useRoute } from 'vue-router';
  7 + import { checkShareAccessToken, sharePageLogin, getShareContent } from '/@/api/sys/share';
  8 + import { ShareRouteParams } from '/@/api/sys/model/shareModel';
  9 + import { useUserStore } from '/@/store/modules/user';
  10 + import { BasicForm, useForm } from '/@/components/Form';
  11 + import { FieldsEnum } from '/@/views/common/ShareModal/config';
  12 + import BoardDetail from '/@/views/visual/board/detail/index.vue';
  13 +
  14 + const loading = ref(true);
  15 + const ROUTE = useRoute();
  16 + const contentData = ref<any>();
  17 + const canLoadComponent = ref(false);
  18 +
  19 + const [register, { openModal }] = useModal();
  20 +
  21 + const [registerForm, { getFieldsValue }] = useForm({
  22 + schemas: [
  23 + {
  24 + field: FieldsEnum.ACCESS_CREDENTIALS,
  25 + component: 'Input',
  26 + label: '访问令牌',
  27 + },
  28 + ],
  29 + showActionButtonGroup: false,
  30 + layout: 'inline',
  31 + labelWidth: 100,
  32 + });
  33 +
  34 + const userStore = useUserStore();
  35 +
  36 + const getShareToken = async () => {
  37 + const { params } = ROUTE;
  38 + const { publicId } = params as Partial<ShareRouteParams>;
  39 + const { token, refreshToken } = await sharePageLogin(publicId!);
  40 + userStore.storeToken(token, refreshToken);
  41 + };
  42 +
  43 + const getCheckNeedAccessToken = async () => {
  44 + const { params } = ROUTE;
  45 + loading.value = true;
  46 + const { viewType, id } = params as Partial<ShareRouteParams>;
  47 + const { data } = await checkShareAccessToken(viewType!, id!);
  48 + data ? openModal(data) : await getContentData();
  49 + };
  50 +
  51 + const getContentLoading = ref(false);
  52 + const getContentData = async () => {
  53 + try {
  54 + getContentLoading.value = true;
  55 + const { params } = ROUTE;
  56 + const { id } = params as unknown as ShareRouteParams;
  57 + const value = getFieldsValue() as Record<'accessCredentials', string>;
  58 + const result = await getShareContent({ id, ...value });
  59 + contentData.value = result;
  60 + loading.value = false;
  61 + canLoadComponent.value = true;
  62 + openModal(false);
  63 + } catch (error) {
  64 + } finally {
  65 + getContentLoading.value = false;
  66 + }
  67 + };
  68 +
  69 + const handleSubmit = async () => {
  70 + await getContentData();
  71 + };
  72 +
  73 + onMounted(async () => {
  74 + await getShareToken();
  75 + await getCheckNeedAccessToken();
  76 + });
  77 +</script>
  78 +
  79 +<template>
  80 + <BasicModal
  81 + @register="register"
  82 + title="公开"
  83 + @ok="handleSubmit"
  84 + :showCancelBtn="false"
  85 + :okButtonProps="{ loading: getContentLoading }"
  86 + >
  87 + <BasicForm @register="registerForm" />
  88 + </BasicModal>
  89 + <Spin
  90 + :spinning="loading"
  91 + tip="正在加载中..."
  92 + size="large"
  93 + class="!flex justify-center items-center w-full h-full share-full-loading"
  94 + >
  95 + <BoardDetail v-if="canLoadComponent" :value="contentData" />
  96 + </Spin>
  97 +</template>
  98 +
  99 +<style lang="less" scoped></style>
@@ -30,10 +30,18 @@ @@ -30,10 +30,18 @@
30 30
31 const { createMessage } = useMessage(); 31 const { createMessage } = useMessage();
32 32
  33 + const handleSubmitValue = (value: Recordable) => {
  34 + if (!Reflect.get(value, 'accessCredentials')) {
  35 + Reflect.deleteProperty(value, 'accessCredentials');
  36 + }
  37 + return value as any;
  38 + };
  39 +
33 const handleCreatePanel = async () => { 40 const handleCreatePanel = async () => {
34 await method.validate(); 41 await method.validate();
35 try { 42 try {
36 - const value = method.getFieldsValue() as AddDataBoardParams; 43 + let value = method.getFieldsValue() as AddDataBoardParams;
  44 + value = handleSubmitValue(value);
37 loading.value = true; 45 loading.value = true;
38 changeLoading(true); 46 changeLoading(true);
39 await addDataBoard(value); 47 await addDataBoard(value);
@@ -52,7 +60,8 @@ @@ -52,7 +60,8 @@
52 await method.validate(); 60 await method.validate();
53 try { 61 try {
54 loading.value = true; 62 loading.value = true;
55 - const value = method.getFieldsValue() as UpdateDataBoardParams; 63 + let value = method.getFieldsValue() as UpdateDataBoardParams;
  64 + value = handleSubmitValue(value);
56 value.id = unref(recordId) as string; 65 value.id = unref(recordId) as string;
57 changeLoading(true); 66 changeLoading(true);
58 await updateDataBoard(value); 67 await updateDataBoard(value);
  1 +import { ViewTypeEnum } from '/@/views/sys/share/config/config';
  2 +
1 export enum MoreActionEvent { 3 export enum MoreActionEvent {
2 EDIT = 'edit', 4 EDIT = 'edit',
3 COPY = 'copy', 5 COPY = 'copy',
4 DELETE = 'delete', 6 DELETE = 'delete',
  7 + SHARE = 'share',
5 } 8 }
6 9
7 export enum VisualBoardPermission { 10 export enum VisualBoardPermission {
@@ -24,11 +27,8 @@ export const DEFAULT_WIDGET_HEIGHT = 6; @@ -24,11 +27,8 @@ export const DEFAULT_WIDGET_HEIGHT = 6;
24 export const DEFAULT_MIN_HEIGHT = 5; 27 export const DEFAULT_MIN_HEIGHT = 5;
25 export const DEFAULT_MIN_WIDTH = 3; 28 export const DEFAULT_MIN_WIDTH = 3;
26 29
27 -export const DATA_BOARD_SHARE_URL = (  
28 - boardId = ':boardId',  
29 - tenantId = ':tenantId',  
30 - name = ':boardName?'  
31 -) => `/data/board/share/${boardId}/${tenantId}/${name}`; 30 +export const DATA_BOARD_SHARE_URL = (id: string, publicId: string) =>
  31 + `/share/${ViewTypeEnum.DATA_BOARD}/${id}/${publicId}`;
32 32
33 export const isBataBoardSharePage = (url: string) => { 33 export const isBataBoardSharePage = (url: string) => {
34 const reg = /^\/data\/board\/share/g; 34 const reg = /^\/data\/board\/share/g;
@@ -35,22 +35,6 @@ export const formSchema: FormSchema[] = [ @@ -35,22 +35,6 @@ export const formSchema: FormSchema[] = [
35 }, 35 },
36 }, 36 },
37 { 37 {
38 - field: 'viewType',  
39 - label: '公开性',  
40 - component: 'RadioGroup',  
41 - defaultValue: ViewType.PRIVATE_VIEW,  
42 - helpMessage: [  
43 - '私有视图只有项目成员可以浏览。公开视图拥有一个公开的 URL,任何人无需登录即可浏览。 ',  
44 - ],  
45 - componentProps: {  
46 - placeholder: '请选择公开性',  
47 - options: [  
48 - { label: '私有看板', value: ViewType.PRIVATE_VIEW },  
49 - { label: '公开看板', value: ViewType.PUBLIC_VIEW, disabled: true },  
50 - ],  
51 - },  
52 - },  
53 - {  
54 field: 'remark', 38 field: 'remark',
55 label: '备注', 39 label: '备注',
56 component: 'InputTextArea', 40 component: 'InputTextArea',
@@ -14,7 +14,6 @@ @@ -14,7 +14,6 @@
14 DEFAULT_MIN_WIDTH, 14 DEFAULT_MIN_WIDTH,
15 DEFAULT_WIDGET_HEIGHT, 15 DEFAULT_WIDGET_HEIGHT,
16 DEFAULT_WIDGET_WIDTH, 16 DEFAULT_WIDGET_WIDTH,
17 - isBataBoardSharePage,  
18 MoreActionEvent, 17 MoreActionEvent,
19 VisualComponentPermission, 18 VisualComponentPermission,
20 } from '../config/config'; 19 } from '../config/config';
@@ -22,7 +21,6 @@ @@ -22,7 +21,6 @@
22 addDataComponent, 21 addDataComponent,
23 deleteDataComponent, 22 deleteDataComponent,
24 getDataComponent, 23 getDataComponent,
25 - getShareBoardComponentInfo,  
26 updateDataBoardLayout, 24 updateDataBoardLayout,
27 } from '/@/api/dataBoard'; 25 } from '/@/api/dataBoard';
28 import { useRoute, useRouter } from 'vue-router'; 26 import { useRoute, useRouter } from 'vue-router';
@@ -48,6 +46,10 @@ @@ -48,6 +46,10 @@
48 import { useScript } from '/@/hooks/web/useScript'; 46 import { useScript } from '/@/hooks/web/useScript';
49 import { BAI_DU_MAP_GL_LIB, BAI_DU_MAP_TRACK_ANIMATION } from '/@/utils/fnUtils'; 47 import { BAI_DU_MAP_GL_LIB, BAI_DU_MAP_TRACK_ANIMATION } from '/@/utils/fnUtils';
50 48
  49 + const props = defineProps<{
  50 + value: Recordable;
  51 + }>();
  52 +
51 const ROUTE = useRoute(); 53 const ROUTE = useRoute();
52 54
53 const ROUTER = useRouter(); 55 const ROUTER = useRouter();
@@ -67,13 +69,12 @@ @@ -67,13 +69,12 @@
67 return decode((ROUTE.params as { boardName: string }).boardName || ''); 69 return decode((ROUTE.params as { boardName: string }).boardName || '');
68 }); 70 });
69 71
70 - const getSharePageParams = computed(() => {  
71 - const { boardId, tenantId } = ROUTE.params as { boardId: string; tenantId: string };  
72 - return { boardId: decode(boardId), tenantId: decode(tenantId) }; 72 + const getSharePageData = computed(() => {
  73 + return props.value;
73 }); 74 });
74 75
75 const getIsSharePage = computed(() => { 76 const getIsSharePage = computed(() => {
76 - return isBataBoardSharePage(ROUTE.path); 77 + return ROUTE.matched.find((item) => item.path === '/share/:viewType/:id/:publicId');
77 }); 78 });
78 79
79 const widgetEl = new Map<string, Fn>(); 80 const widgetEl = new Map<string, Fn>();
@@ -220,19 +221,9 @@ @@ -220,19 +221,9 @@
220 return {} as ComponentInfoDetail; 221 return {} as ComponentInfoDetail;
221 }; 222 };
222 223
223 - const getSharePageComponentData = async () => {  
224 - try {  
225 - const params = unref(getSharePageParams);  
226 - return await getShareBoardComponentInfo(params);  
227 - } catch (error) {}  
228 - return {} as ComponentInfoDetail;  
229 - };  
230 -  
231 const getDataBoradDetail = async () => { 224 const getDataBoradDetail = async () => {
232 try { 225 try {
233 - return unref(getIsSharePage)  
234 - ? await getSharePageComponentData()  
235 - : await getBasePageComponentData(); 226 + return unref(getIsSharePage) ? unref(getSharePageData) : await getBasePageComponentData();
236 } catch (error) {} 227 } catch (error) {}
237 return {} as ComponentInfoDetail; 228 return {} as ComponentInfoDetail;
238 }; 229 };
@@ -388,7 +379,7 @@ @@ -388,7 +379,7 @@
388 379
389 <template> 380 <template>
390 <section class="flex flex-col overflow-hidden h-full w-full board-detail"> 381 <section class="flex flex-col overflow-hidden h-full w-full board-detail">
391 - <PageHeader> 382 + <PageHeader v-if="!getIsSharePage">
392 <template #title> 383 <template #title>
393 <div class="flex items-center"> 384 <div class="flex items-center">
394 <img 385 <img
@@ -10,7 +10,7 @@ @@ -10,7 +10,7 @@
10 import { DATA_BOARD_SHARE_URL, MoreActionEvent, VisualBoardPermission } from './config/config'; 10 import { DATA_BOARD_SHARE_URL, MoreActionEvent, VisualBoardPermission } from './config/config';
11 import { useModal } from '/@/components/Modal'; 11 import { useModal } from '/@/components/Modal';
12 import PanelDetailModal from './components/PanelDetailModal.vue'; 12 import PanelDetailModal from './components/PanelDetailModal.vue';
13 - import { getDataBoardList, deleteDataBoard } from '/@/api/dataBoard'; 13 + import { getDataBoardList, deleteDataBoard, shareBoard } from '/@/api/dataBoard';
14 import { DataBoardRecord } from '/@/api/dataBoard/model'; 14 import { DataBoardRecord } from '/@/api/dataBoard/model';
15 import { ViewType } from './config/panelDetail'; 15 import { ViewType } from './config/panelDetail';
16 import { useRouter } from 'vue-router'; 16 import { useRouter } from 'vue-router';
@@ -21,6 +21,7 @@ @@ -21,6 +21,7 @@
21 import { encode } from './config/config'; 21 import { encode } from './config/config';
22 import { useForm, BasicForm } from '/@/components/Form'; 22 import { useForm, BasicForm } from '/@/components/Form';
23 import { formSchema } from './config/searchForm'; 23 import { formSchema } from './config/searchForm';
  24 + import { ShareModal } from '/@/views/common/ShareModal';
24 25
25 const ListItem = List.Item; 26 const ListItem = List.Item;
26 const router = useRouter(); 27 const router = useRouter();
@@ -77,14 +78,15 @@ @@ -77,14 +78,15 @@
77 getDatasource(); 78 getDatasource();
78 } 79 }
79 80
80 - const createShareUrl = (boardId: string, tenantId: string, name: string) => { 81 + const createShareUrl = (record: DataBoardRecord) => {
81 const { origin } = location; 82 const { origin } = location;
82 - return `${origin}${DATA_BOARD_SHARE_URL(encode(boardId), encode(tenantId), encode(name))}`; 83 + const { id, publicId } = record;
  84 + return `${origin}${DATA_BOARD_SHARE_URL(id, publicId)}`;
83 }; 85 };
84 86
85 const { clipboardRef } = useCopyToClipboard(); 87 const { clipboardRef } = useCopyToClipboard();
86 const handleCopyShareUrl = (record: DataBoardRecord) => { 88 const handleCopyShareUrl = (record: DataBoardRecord) => {
87 - clipboardRef.value = createShareUrl(record.id, record.tenantId, record.name); 89 + clipboardRef.value = createShareUrl(record);
88 unref(clipboardRef) ? createMessage.success('复制成功') : createMessage.error('未找到分享链接'); 90 unref(clipboardRef) ? createMessage.success('复制成功') : createMessage.error('未找到分享链接');
89 }; 91 };
90 92
@@ -92,7 +94,13 @@ @@ -92,7 +94,13 @@
92 const dropMenuList = computed<DropMenu[]>(() => { 94 const dropMenuList = computed<DropMenu[]>(() => {
93 const hasUpdatePermission = hasPermission(VisualBoardPermission.UPDATE); 95 const hasUpdatePermission = hasPermission(VisualBoardPermission.UPDATE);
94 const hasDeletePermission = hasPermission(VisualBoardPermission.DELETE); 96 const hasDeletePermission = hasPermission(VisualBoardPermission.DELETE);
95 - const basicMenu: DropMenu[] = []; 97 + const basicMenu: DropMenu[] = [
  98 + {
  99 + text: '分享',
  100 + event: MoreActionEvent.SHARE,
  101 + icon: 'ant-design:share-alt-outlined',
  102 + },
  103 + ];
96 if (hasUpdatePermission) 104 if (hasUpdatePermission)
97 basicMenu.push({ 105 basicMenu.push({
98 text: '编辑', 106 text: '编辑',
@@ -124,6 +132,10 @@ @@ -124,6 +132,10 @@
124 } 132 }
125 }; 133 };
126 134
  135 + const handleOpenShareModal = (record: DataBoardRecord) => {
  136 + openShareModal(true, record);
  137 + };
  138 +
127 const handleMenuEvent = (event: DropMenu, record: DataBoardRecord) => { 139 const handleMenuEvent = (event: DropMenu, record: DataBoardRecord) => {
128 if (event.event === MoreActionEvent.EDIT) handleEdit(record); 140 if (event.event === MoreActionEvent.EDIT) handleEdit(record);
129 if (event.event === MoreActionEvent.DELETE) { 141 if (event.event === MoreActionEvent.DELETE) {
@@ -133,6 +145,7 @@ @@ -133,6 +145,7 @@
133 onOk: () => handleRemove(record), 145 onOk: () => handleRemove(record),
134 }); 146 });
135 } 147 }
  148 + if (event.event === MoreActionEvent.SHARE) handleOpenShareModal(record);
136 }; 149 };
137 150
138 const handleEdit = (record: DataBoardRecord) => { 151 const handleEdit = (record: DataBoardRecord) => {
@@ -156,6 +169,8 @@ @@ -156,6 +169,8 @@
156 169
157 const [registerModal, { openModal }] = useModal(); 170 const [registerModal, { openModal }] = useModal();
158 171
  172 + const [registerShareModal, { openModal: openShareModal }] = useModal();
  173 +
159 const handleViewBoard = (record: DataBoardRecord) => { 174 const handleViewBoard = (record: DataBoardRecord) => {
160 const hasDetailPermission = hasPermission(VisualBoardPermission.DETAIL); 175 const hasDetailPermission = hasPermission(VisualBoardPermission.DETAIL);
161 if (hasDetailPermission) { 176 if (hasDetailPermission) {
@@ -238,10 +253,7 @@ @@ -238,10 +253,7 @@
238 <span> 253 <span>
239 {{ item.viewType === ViewType.PRIVATE_VIEW ? '私有看板' : '公共看板' }} 254 {{ item.viewType === ViewType.PRIVATE_VIEW ? '私有看板' : '公共看板' }}
240 </span> 255 </span>
241 - <span  
242 - style="display: none"  
243 - v-if="item.viewType === ViewType.PUBLIC_VIEW && false"  
244 - > 256 + <span v-if="item.viewType === ViewType.PUBLIC_VIEW">
245 <Tooltip title="点击复制分享链接"> 257 <Tooltip title="点击复制分享链接">
246 <ShareAltOutlined class="ml-2" @click.stop="handleCopyShareUrl(item)" /> 258 <ShareAltOutlined class="ml-2" @click.stop="handleCopyShareUrl(item)" />
247 </Tooltip> 259 </Tooltip>
@@ -255,6 +267,7 @@ @@ -255,6 +267,7 @@
255 </template> 267 </template>
256 </List> 268 </List>
257 </Spin> 269 </Spin>
  270 + <ShareModal @register="registerShareModal" :shareApi="shareBoard" @success="getDatasource" />
258 <PanelDetailModal @register="registerModal" @change="getDatasource" /> 271 <PanelDetailModal @register="registerModal" @change="getDatasource" />
259 </PageWrapper> 272 </PageWrapper>
260 </template> 273 </template>