Commit 8e3c843458ef058a2c186e53a47bc7bc75816e65

Authored by ww
1 parent d405e83c

feat: 新增看板管理公共看板

@@ -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",
@@ -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 {
  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 { 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 '../../visual/board/config/share';
  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 +<script lang="ts" setup>
  2 + import { BasicForm, useForm } from '/@/components/Form';
  3 + import { BasicModal, useModalInner } from '/@/components/Modal';
  4 + import { FieldsEnum, schemas } from '../config/share';
  5 + import { DataBoardRecord } from '/@/api/dataBoard/model';
  6 + import { Alert } from 'ant-design-vue';
  7 + import { ref, unref } from 'vue';
  8 + import { shareBoard } from '/@/api/dataBoard';
  9 + import { ViewType } from '../config/panelDetail';
  10 + import { nextTick } from 'vue';
  11 +
  12 + const loading = ref(false);
  13 + const record = ref<DataBoardRecord>({} as unknown as DataBoardRecord);
  14 +
  15 + const [register, { closeModal }] = useModalInner(async (data: DataBoardRecord) => {
  16 + record.value = data;
  17 + await nextTick();
  18 + setFieldsValue({
  19 + [FieldsEnum.IS_SHARE]: data.viewType === ViewType.PUBLIC_VIEW,
  20 + [FieldsEnum.ACCESS_CREDENTIALS]: data.accessCredentials,
  21 + });
  22 + });
  23 +
  24 + const [registerForm, { getFieldsValue, setFieldsValue }] = useForm({
  25 + schemas,
  26 + showActionButtonGroup: false,
  27 + layout: 'inline',
  28 + labelWidth: 120,
  29 + });
  30 +
  31 + const emit = defineEmits(['register', 'success']);
  32 +
  33 + const handleSubmit = async () => {
  34 + try {
  35 + loading.value = true;
  36 + const value = getFieldsValue();
  37 + const { id } = unref(record);
  38 + await shareBoard({ id, ...value });
  39 + closeModal();
  40 + emit('success');
  41 + } finally {
  42 + loading.value = false;
  43 + }
  44 + };
  45 +</script>
  46 +
  47 +<template>
  48 + <BasicModal title="分享" @register="register" @ok="handleSubmit" :loading="loading">
  49 + <Alert
  50 + class="!mb-4"
  51 + type="info"
  52 + message="私有视图只有项目成员可以浏览,公开视图拥有一个公开的 URL,任何人无需登录即可浏览."
  53 + />
  54 + <BasicForm @register="registerForm" />
  55 + </BasicModal>
  56 +</template>
  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',
  1 +import { FormSchema } from '/@/components/Form';
  2 +
  3 +export enum FieldsEnum {
  4 + IS_SHARE = 'isShare',
  5 + ACCESS_CREDENTIALS = 'accessCredentials',
  6 +}
  7 +
  8 +export const schemas: FormSchema[] = [
  9 + {
  10 + field: FieldsEnum.IS_SHARE,
  11 + label: '公开性',
  12 + component: 'Switch',
  13 + defaultValue: false,
  14 + },
  15 + {
  16 + field: FieldsEnum.ACCESS_CREDENTIALS,
  17 + label: '访问凭证',
  18 + component: 'Input',
  19 + ifShow: ({ model }) => model[FieldsEnum.IS_SHARE],
  20 + componentProps: {
  21 + maxLength: 64,
  22 + },
  23 + },
  24 +];
@@ -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
@@ -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 './components/ShareModal.vue';
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" @success="getDatasource" />
258 <PanelDetailModal @register="registerModal" @change="getDatasource" /> 271 <PanelDetailModal @register="registerModal" @change="getDatasource" />
259 </PageWrapper> 272 </PageWrapper>
260 </template> 273 </template>