Showing
27 changed files
with
777 additions
and
341 deletions
@@ -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 { |
src/api/sys/model/shareModel.ts
0 → 100644
src/api/sys/share.ts
0 → 100644
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 | ]; |
src/router/routes/public.ts
0 → 100644
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 | +}; |
src/views/common/ShareModal/config/index.ts
0 → 100644
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 | +]; |
src/views/common/ShareModal/index.ts
0 → 100644
1 | +export { default as ShareModal } from './index.vue'; |
src/views/common/ShareModal/index.vue
0 → 100644
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 | }); |
src/views/sys/share/config/config.ts
0 → 100644
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 | +}; |
src/views/sys/share/index.vue
0 → 100644
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> |