perf(仪表板): 分享功能增加全局禁用以及有效期密码必填设置 #12815 #12816

This commit is contained in:
fit2cloud-chenyw 2024-10-29 16:48:46 +08:00
parent ad75afa456
commit e7d2cc82b9
22 changed files with 471 additions and 58 deletions

View File

@ -3,6 +3,7 @@ package io.dataease.share.manage;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import io.dataease.api.system.vo.ShareBaseVO;
import io.dataease.api.visualization.request.VisualizationWorkbranchQueryRequest;
import io.dataease.api.xpack.share.request.XpackShareProxyRequest;
import io.dataease.api.xpack.share.request.XpackSharePwdValidator;
@ -21,6 +22,7 @@ import io.dataease.share.dao.auto.mapper.XpackShareMapper;
import io.dataease.share.dao.ext.mapper.XpackShareExtMapper;
import io.dataease.share.dao.ext.po.XpackSharePO;
import io.dataease.share.util.LinkTokenUtil;
import io.dataease.system.manage.SysParameterManage;
import io.dataease.utils.*;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
@ -51,6 +53,9 @@ public class XpackShareManage {
@Resource
private ShareTicketManage shareTicketManage;
@Resource
private SysParameterManage sysParameterManage;
public XpackShare queryByResource(Long resourceId) {
Long userId = AuthUtils.getUser().getUserId();
QueryWrapper<XpackShare> queryWrapper = new QueryWrapper<>();
@ -190,7 +195,20 @@ public class XpackShareManage {
return CommonBeanFactory.getBean(this.getClass());
}
private boolean peRequireValid(ShareBaseVO sharedBase, XpackShare share) {
if (ObjectUtils.isEmpty(sharedBase) || !sharedBase.isPeRequire()) return true;
Long exp = share.getExp();
String pwd = share.getPwd();
return StringUtils.isNotBlank(pwd) && ObjectUtils.isNotEmpty(exp);
}
public XpackShareProxyVO proxyInfo(XpackShareProxyRequest request) {
ShareBaseVO sharedBase = sysParameterManage.shareBase();
if (ObjectUtils.isNotEmpty(sharedBase) && sharedBase.isDisable()) {
XpackShareProxyVO vo = new XpackShareProxyVO();
vo.setShareDisable(true);
return vo;
}
boolean inIframeError = request.isInIframe() && !LicenseUtil.licenseValid();
if (inIframeError) {
return new XpackShareProxyVO();
@ -200,13 +218,18 @@ public class XpackShareManage {
XpackShare xpackShare = xpackShareMapper.selectOne(queryWrapper);
if (ObjectUtils.isEmpty(xpackShare))
return null;
if (!peRequireValid(sharedBase, xpackShare)) {
XpackShareProxyVO vo = new XpackShareProxyVO();
vo.setPeRequireValid(false);
return vo;
}
String linkToken = LinkTokenUtil.generate(xpackShare.getCreator(), xpackShare.getResourceId(), xpackShare.getExp(), xpackShare.getPwd(), xpackShare.getOid());
HttpServletResponse response = ServletUtils.response();
response.addHeader(AuthConstant.LINK_TOKEN_KEY, linkToken);
Integer type = xpackShare.getType();
String typeText = (ObjectUtils.isNotEmpty(type) && type == 1) ? "dashboard" : "dataV";
TicketValidVO validVO = shareTicketManage.validateTicket(request.getTicket(), xpackShare);
return new XpackShareProxyVO(xpackShare.getResourceId(), xpackShare.getCreator(), linkExp(xpackShare), pwdValid(xpackShare, request.getCiphertext()), typeText, inIframeError, validVO);
return new XpackShareProxyVO(xpackShare.getResourceId(), xpackShare.getCreator(), linkExp(xpackShare), pwdValid(xpackShare, request.getCiphertext()), typeText, inIframeError, false, true, validVO);
}
private boolean linkExp(XpackShare xpackShare) {

View File

@ -3,6 +3,7 @@ package io.dataease.system.manage;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import io.dataease.api.system.request.OnlineMapEditor;
import io.dataease.api.system.vo.SettingItemVO;
import io.dataease.api.system.vo.ShareBaseVO;
import io.dataease.datasource.server.DatasourceServer;
import io.dataease.license.config.XpackInteract;
import io.dataease.system.dao.auto.entity.CoreSysSetting;
@ -20,7 +21,6 @@ import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.CollectionUtils;
import java.beans.PropertyDescriptor;
import java.util.*;
import java.util.stream.Collectors;
@ -159,4 +159,17 @@ public class SysParameterManage {
private SysParameterManage proxy() {
return CommonBeanFactory.getBean(SysParameterManage.class);
}
public ShareBaseVO shareBase() {
String disableText = singleVal("basic.shareDisable");
String requireText = singleVal("basic.sharePeRequire");
ShareBaseVO vo = new ShareBaseVO();
if (StringUtils.isNotBlank(disableText) && StringUtils.equals("true", disableText)) {
vo.setDisable(true);
}
if (StringUtils.isNotBlank(requireText) && StringUtils.equals("true", requireText)) {
vo.setPeRequire(true);
}
return vo;
}
}

View File

@ -3,6 +3,7 @@ package io.dataease.system.server;
import io.dataease.api.system.SysParameterApi;
import io.dataease.api.system.request.OnlineMapEditor;
import io.dataease.api.system.vo.SettingItemVO;
import io.dataease.api.system.vo.ShareBaseVO;
import io.dataease.constant.XpackSettingConstants;
import io.dataease.system.dao.auto.entity.CoreSysSetting;
import io.dataease.system.manage.SysParameterManage;
@ -69,4 +70,9 @@ public class SysParameterServer implements SysParameterApi {
public Integer defaultLogin() {
return sysParameterManage.defaultLogin();
}
@Override
public ShareBaseVO shareBase() {
return sysParameterManage.shareBase();
}
}

View File

@ -1,14 +1,24 @@
INSERT INTO area (id, level, name, pid) VALUES ('156440315', 'district', '大鹏新区', '156440300');
INSERT INTO area (id, level, name, pid)
VALUES ('156440315', 'district', '大鹏新区', '156440300');
DELETE ccv
FROM
core_chart_view ccv
INNER JOIN data_visualization_info dvi ON ccv.scene_id = dvi.id
WHERE
dvi.delete_flag =1;
delete from data_visualization_info dvi where dvi.delete_flag =1;
DELETE FROM area where pid = '156710100' OR id = '156710100';
FROM core_chart_view ccv
INNER JOIN data_visualization_info dvi ON ccv.scene_id = dvi.id
WHERE dvi.delete_flag = 1;
delete
from data_visualization_info dvi
where dvi.delete_flag = 1;
DELETE
FROM area
where pid = '156710100'
OR id = '156710100';
ALTER TABLE `core_chart_view`
ADD COLUMN `custom_attr_mobile` longtext NULL COMMENT '图形属性_移动端' AFTER `custom_attr`,
ADD COLUMN `custom_style_mobile` longtext NULL COMMENT '组件样式_移动端' AFTER `custom_style`;
ADD COLUMN `custom_attr_mobile` longtext NULL COMMENT '图形属性_移动端' AFTER `custom_attr`,
ADD COLUMN `custom_style_mobile` longtext NULL COMMENT '组件样式_移动端' AFTER `custom_style`;
INSERT INTO `core_sys_setting`(`id`, `pkey`, `pval`, `type`, `sort`)
VALUES (1048232869488627717, 'basic.shareDisable', 'false', 'text', 11);
INSERT INTO `core_sys_setting`(`id`, `pkey`, `pval`, `type`, `sort`)
VALUES (1048232869488627718, 'basic.sharePeRequire', 'false', 'text', 12);

View File

@ -121,3 +121,10 @@ export const queryOuterParamsDsInfo = async dvId => {
loading: false
})
}
export const queryShareBaseApi = () => {
return request.get({
url: '/sysParameter/shareBase',
loading: false
})
}

View File

@ -3,8 +3,11 @@ import { Icon } from '@/components/icon-custom'
import icon_more_outlined from '@/assets/svg/icon_more_outlined.svg'
import { propTypes } from '@/utils/propTypes'
import type { Placement } from 'element-plus-secondary'
import { ref, PropType } from 'vue'
import { ref, PropType, computed } from 'vue'
import ShareHandler from '@/views/share/share/ShareHandler.vue'
import { useShareStoreWithOut } from '@/store/modules/share'
const shareStore = useShareStoreWithOut()
export interface Menu {
svgName?: string
label?: string
@ -34,6 +37,10 @@ const props = defineProps({
anyManage: propTypes.bool.def(false)
})
const shareDisable = computed(() => {
return shareStore.getShareDisable
})
const shareComponent = ref(null)
const menus = ref([
...props.menuList.map(item => {
@ -52,6 +59,9 @@ const handleCommand = (command: string | number | object) => {
emit('handleCommand', command)
}
const callBack = param => {
if (shareDisable.value) {
return
}
if (props.node.leaf && props.node?.weight >= 7) {
menus.value[0]['divided'] = true
menus.value.splice(0, 0, param)
@ -89,6 +99,7 @@ const emit = defineEmits(['handleCommand'])
</template>
</el-dropdown>
<ShareHandler
v-if="!shareDisable"
ref="shareComponent"
:resource-id="props.node.id"
:resource-type="props.resourceType"

View File

@ -2617,7 +2617,9 @@ export default {
pwdStrategy: '开启密码策略',
dip: '禁用初始密码',
pvp: '密码有效期',
defaultLogin: '默认登录方式'
defaultLogin: '默认登录方式',
shareDisable: '禁用分享',
sharePeRequire: '分享有效期密码必填'
},
setting_email: {
title: '邮件设置',

View File

@ -0,0 +1,34 @@
import { defineStore } from 'pinia'
import { store } from '@/store/index'
interface ShareState {
shareDisable: boolean
sharePeRequire: boolean
}
export const useShareStore = defineStore('shareStore', {
state: (): ShareState => {
return {
shareDisable: false,
sharePeRequire: false
}
},
getters: {
getShareDisable(): boolean {
return this.shareDisable
},
getSharePeRequire(): boolean {
return this.sharePeRequire
}
},
actions: {
setData(data: ShareState) {
this.shareDisable = data.shareDisable
this.sharePeRequire = data.sharePeRequire
}
}
})
export const useShareStoreWithOut = () => {
return useShareStore(store)
}

View File

@ -17,7 +17,12 @@ import dvFolder from '@/assets/svg/dv-folder.svg'
import icon_operationAnalysis_outlined from '@/assets/svg/icon_operation-analysis_outlined.svg'
import icon_edit_outlined from '@/assets/svg/icon_edit_outlined.svg'
import { onMounted, reactive, ref, toRefs, watch, nextTick, computed } from 'vue'
import { copyResource, deleteLogic, ResourceOrFolder } from '@/api/visualization/dataVisualization'
import {
copyResource,
deleteLogic,
ResourceOrFolder,
queryShareBaseApi
} from '@/api/visualization/dataVisualization'
import { ElIcon, ElMessage, ElMessageBox, ElScrollbar } from 'element-plus-secondary'
import { Icon } from '@/components/icon-custom'
import { useEmitt } from '@/hooks/web/useEmitt'
@ -30,6 +35,8 @@ import { useAppStoreWithOut } from '@/store/modules/app'
import { storeToRefs } from 'pinia'
import DvHandleMore from '@/components/handle-more/src/DvHandleMore.vue'
import { interactiveStoreWithOut } from '@/store/modules/interactive'
import { useShareStoreWithOut } from '@/store/modules/share'
const shareStore = useShareStoreWithOut()
const interactiveStore = interactiveStoreWithOut()
import router from '@/router'
import { useI18n } from '@/hooks/web/useI18n'
@ -503,9 +510,20 @@ const loadInit = () => {
state.curSortType = historyTreeSort
}
}
const loadShareBase = () => {
queryShareBaseApi().then(res => {
const param = {
shareDisable: res.data?.disable,
sharePeRequire: res.data?.peRequire
}
shareStore.setData(param)
})
}
onMounted(() => {
loadInit()
getTree()
loadShareBase()
})
defineExpose({

View File

@ -17,6 +17,9 @@ import { ref, watch, computed } from 'vue'
import ShareVisualHead from '@/views/share/share/ShareVisualHead.vue'
import { XpackComponent } from '@/components/plugin'
import { useEmitt } from '@/hooks/web/useEmitt'
import { useShareStoreWithOut } from '@/store/modules/share'
const shareStore = useShareStoreWithOut()
const dvMainStore = dvMainStoreWithOut()
const appStore = useAppStoreWithOut()
const { dvInfo } = storeToRefs(dvMainStore)
@ -38,6 +41,7 @@ const preview = () => {
}
const isDataEaseBi = computed(() => appStore.getIsDataEaseBi)
const isIframe = computed(() => appStore.getIsIframe)
const shareDisable = computed(() => shareStore.getShareDisable)
const reload = () => {
emit('reload', dvInfo.value.id)
@ -156,6 +160,7 @@ const initOpenHandler = newWindow => {
预览</el-button
>
<ShareVisualHead
v-if="!shareDisable"
:resource-id="dvInfo.id"
:weight="dvInfo.weight"
:resource-type="dvInfo.type"

View File

@ -0,0 +1,10 @@
<script lang="ts" setup>
import EmptyBackground from '@/components/empty-background/src/EmptyBackground.vue'
import { propTypes } from '@/utils/propTypes'
const props = defineProps({
msg: propTypes.string.def('')
})
</script>
<template>
<EmptyBackground img-type="noneWhite" :description="props.msg" />
</template>

View File

@ -15,6 +15,8 @@ export interface ProxyInfo {
pwdValid?: boolean
type: string
inIframeError: boolean
shareDisable: boolean
peRequireValid: boolean
ticketValidVO: TicketValidVO
}
class ShareProxy {

View File

@ -3,7 +3,13 @@
class="link-container"
v-loading="loading || requestStore.loadingMap[permissionStore.currentPath]"
>
<IframeError v-if="!loading && iframeError" />
<ErrorTemplate
v-if="!loading && (disableError || peRequireError)"
:msg="
disableError ? '已禁用分享功能,请联系管理员!' : '已设置有效期密码必填,当前链接无效!'
"
/>
<IframeError v-else-if="!loading && iframeError" />
<LinkError v-else-if="!loading && !linkExist" />
<Exp v-else-if="!loading && linkExp" />
<PwdTips v-else-if="!loading && !pwdValid" />
@ -30,10 +36,13 @@ import LinkError from './error.vue'
import PwdTips from './pwd.vue'
import IframeError from './IframeError.vue'
import TicketError from './TicketError.vue'
import ErrorTemplate from './ErrorTemplate.vue'
const requestStore = useRequestStoreWithOut()
const permissionStore = usePermissionStoreWithOut()
const pcanvas = ref(null)
const iframeError = ref(true)
const disableError = ref(true)
const peRequireError = ref(true)
const linkExist = ref(false)
const loading = ref(true)
const linkExp = ref(false)
@ -47,12 +56,24 @@ const state = reactive({
})
onMounted(async () => {
const proxyInfo = (await shareProxy.loadProxy()) as ProxyInfo
if (proxyInfo?.shareDisable) {
loading.value = false
disableError.value = true
return
}
disableError.value = false
if (proxyInfo?.inIframeError) {
loading.value = false
iframeError.value = true
return
}
iframeError.value = false
if (proxyInfo && !proxyInfo.peRequireValid) {
loading.value = false
peRequireError.value = true
return
}
peRequireError.value = false
if (!proxyInfo?.resourceId) {
loading.value = false
return

View File

@ -56,11 +56,18 @@
<div v-if="shareEnable" class="exp-container">
<el-checkbox
ref="expCheckbox"
:disabled="!shareEnable"
v-model="overTimeEnable"
@change="expEnableSwitcher"
:label="t('visualization.over_time')"
/>
>
<div class="checkbox-span">
<span>{{ t('visualization.over_time') }}</span>
<span class="pe-require" :class="{ 'pe-tips-hidden': !sharePeRequire }">
<span>*</span>
</span>
</div>
</el-checkbox>
<div class="inline-share-item-picker">
<el-date-picker
:clearable="false"
@ -80,11 +87,18 @@
</div>
<div v-if="shareEnable" class="pwd-container">
<el-checkbox
ref="pwdCheckbox"
:disabled="!shareEnable"
v-model="passwdEnable"
@change="pwdEnableSwitcher"
:label="t('visualization.passwd_protect')"
/>
>
<div class="checkbox-span">
<span>{{ t('visualization.passwd_protect') }}</span>
<span class="pe-require" :class="{ 'pe-tips-hidden': !sharePeRequire }">
<span>*</span>
</span>
</div>
</el-checkbox>
<div class="auto-pwd-container" v-if="passwdEnable">
<el-checkbox
:disabled="!shareEnable"
@ -153,6 +167,8 @@ import { ElMessage, ElLoading } from 'element-plus-secondary'
import useClipboard from 'vue-clipboard3'
import ShareTicket from './ShareTicket.vue'
import { useEmbedded } from '@/store/modules/embedded'
import { useShareStoreWithOut } from '@/store/modules/share'
const shareStore = useShareStoreWithOut()
const embeddedStore = useEmbedded()
const { toClipboard } = useClipboard()
const { t } = useI18n()
@ -164,6 +180,8 @@ const props = defineProps({
isButton: propTypes.bool.def(false)
})
const pwdRef = ref(null)
const expCheckbox = ref()
const pwdCheckbox = ref()
const loadingInstance = ref<any>(null)
const dialogVisible = ref(false)
const overTimeEnable = ref(false)
@ -189,6 +207,8 @@ const shareTips = computed(
() =>
`开启后,用户可以通过该链接访问${props.resourceType === 'dashboard' ? '仪表板' : '数据大屏'}`
)
const shareDisable = computed(() => shareStore.getShareDisable)
const sharePeRequire = computed(() => shareStore.getSharePeRequire)
const editUuid = () => {
linkCustom.value = true
nextTick(() => {
@ -274,10 +294,10 @@ const closeLoading = () => {
const share = () => {
dialogVisible.value = true
loadShareInfo()
loadShareInfo(validatePeRequire)
}
const loadShareInfo = () => {
const loadShareInfo = cb => {
showLoading()
const resourceId = props.resourceId
const url = `/share/detail/${resourceId}`
@ -289,6 +309,7 @@ const loadShareInfo = () => {
})
.finally(() => {
closeLoading()
cb && cb()
})
}
@ -305,7 +326,7 @@ const enableSwitcher = () => {
const resourceId = props.resourceId
const url = `/share/switcher/${resourceId}`
request.post({ url }).then(() => {
loadShareInfo()
loadShareInfo(null)
})
}
@ -335,6 +356,7 @@ const expEnableSwitcher = val => {
exp = now.getTime()
state.detailInfo.exp = exp
}
validateExpRequire()
expChangeHandler(exp)
}
@ -348,7 +370,7 @@ const expChangeHandler = exp => {
const url = '/share/editExp'
const data = { resourceId, exp }
request.post({ url, data }).then(() => {
loadShareInfo()
loadShareInfo(null)
})
}
const beforeClose = async done => {
@ -357,6 +379,12 @@ const beforeClose = async done => {
done()
return
}
if (sharePeRequire.value) {
const peRequireValid = validatePeRequire()
if (!peRequireValid) {
return
}
}
const pwdValid = validatePwdFormat()
const uuidValid = await validateUuid()
if (pwdValid && uuidValid) {
@ -364,6 +392,32 @@ const beforeClose = async done => {
done()
}
}
const validatePeRequire = () => {
if (shareEnable.value && sharePeRequire.value) {
const expRequireValid = validateExpRequire()
const pwdRequireValid = validatePwdRequire()
return expRequireValid && pwdRequireValid
}
return true
}
const validateExpRequire = () => {
if (!sharePeRequire.value || overTimeEnable.value) {
showCheckboxError(null, expCheckbox)
return true
}
showCheckboxError('必填', expCheckbox)
return false
}
const validatePwdRequire = () => {
if (!sharePeRequire.value || passwdEnable.value) {
showCheckboxError(null, pwdCheckbox)
return true
}
showCheckboxError('必填', pwdCheckbox)
return false
}
const validatePwdFormat = () => {
if (!shareEnable.value || state.detailInfo.autoPwd) {
showPageError(null, pwdRef)
@ -383,6 +437,34 @@ const validatePwdFormat = () => {
resetPwdHandler(val, false)
return true
}
const showCheckboxError = (msg, target, className?: string) => {
if (!target.value) {
return
}
className = className || 'checkbox-span-require'
const fullClassName = `.${className}`
const e = target.value.$el
if (!msg) {
e.style = null
e.children[0].children[1].style.borderColor = null
const child = e.children[1].children[0].querySelector(fullClassName)
if (child) {
e.children[1].children[0].removeChild(child)
}
} else {
e.style.color = 'red'
e.children[0].children[1].style.borderColor = 'red'
const child = e.children[1].children[0].querySelector(fullClassName)
if (!child) {
const errorDom = document.createElement('span')
errorDom.className = className
errorDom.innerText = msg
e.children[1].children[0].appendChild(errorDom)
} else {
child.innerText = msg
}
}
}
const showPageError = (msg, target, className?: string) => {
className = className || 'link-pwd-error-msg'
const fullClassName = `.${className}`
@ -433,6 +515,7 @@ const pwdEnableSwitcher = val => {
if (val) {
pwd = getUuid()
}
validatePwdRequire()
resetPwdHandler(pwd, true)
}
const resetPwd = () => {
@ -444,7 +527,7 @@ const resetPwdHandler = (pwd?: string, autoPwd?: boolean) => {
const url = '/share/editPwd'
const data = { resourceId, pwd, autoPwd }
request.post({ url, data }).then(() => {
loadShareInfo()
loadShareInfo(null)
})
}
@ -642,4 +725,20 @@ onMounted(() => {
display: none;
}
}
:deep(.checkbox-span) {
display: flex;
align-items: center;
.pe-require {
color: red;
font-size: 10px;
line-height: 32px;
margin: 0 4px;
}
.checkbox-span-require {
font-size: 10px;
}
.pe-tips-hidden {
display: none;
}
}
</style>

View File

@ -23,7 +23,11 @@
{{ t('visualization.share') }}
</el-button>
</template>
<div class="share-container" :class="{ 'hidden-link-container': showTicket }">
<div
v-if="!shareDisable"
class="share-container"
:class="{ 'hidden-link-container': showTicket }"
>
<div class="share-title share-padding">公共链接分享</div>
<div class="open-share flex-align-center share-padding">
<el-switch size="small" v-model="shareEnable" @change="enableSwitcher" />
@ -51,11 +55,18 @@
</div>
<div v-if="shareEnable" class="exp-container share-padding">
<el-checkbox
ref="expCheckbox"
:disabled="!shareEnable"
v-model="overTimeEnable"
@change="expEnableSwitcher"
:label="t('visualization.over_time')"
/>
>
<div class="checkbox-span">
<span>{{ t('visualization.over_time') }}</span>
<span class="pe-require" :class="{ 'pe-tips-hidden': !sharePeRequire }">
<span>*</span>
</span>
</div>
</el-checkbox>
<div class="inline-share-item-picker">
<el-date-picker
:clearable="false"
@ -76,11 +87,18 @@
</div>
<div v-if="shareEnable" class="pwd-container share-padding">
<el-checkbox
ref="pwdCheckbox"
:disabled="!shareEnable"
v-model="passwdEnable"
@change="pwdEnableSwitcher"
:label="t('visualization.passwd_protect')"
/>
>
<div class="checkbox-span">
<span>{{ t('visualization.passwd_protect') }}</span>
<span class="pe-require" :class="{ 'pe-tips-hidden': !sharePeRequire }">
<span>*</span>
</span>
</div>
</el-checkbox>
<div class="auto-pwd-container" v-if="passwdEnable">
<el-checkbox
:disabled="!shareEnable"
@ -123,7 +141,13 @@
</el-button>
</div>
</div>
<div v-if="shareEnable && showTicket" class="share-ticket-container">
<div v-else class="share-container">
<div class="share-title share-padding">公共链接分享</div>
<div class="open-share flex-align-center share-padding">
<span>已经开启全局禁用分享分享功能暂不可用请联系管理员</span>
</div>
</div>
<div v-if="!shareDisable && shareEnable && showTicket" class="share-ticket-container">
<share-ticket
:uuid="state.detailInfo.uuid"
:resource-id="props.resourceId"
@ -147,6 +171,8 @@ import { ElMessage, ElLoading } from 'element-plus-secondary'
import useClipboard from 'vue-clipboard3'
import ShareTicket from './ShareTicket.vue'
import { useEmbedded } from '@/store/modules/embedded'
import { useShareStoreWithOut } from '@/store/modules/share'
const shareStore = useShareStoreWithOut()
const embeddedStore = useEmbedded()
const { toClipboard } = useClipboard()
const { t } = useI18n()
@ -157,6 +183,8 @@ const props = defineProps({
})
const popoverVisible = ref(false)
const pwdRef = ref(null)
const expCheckbox = ref()
const pwdCheckbox = ref()
const loadingInstance = ref<any>(null)
const overTimeEnable = ref(false)
const passwdEnable = ref(false)
@ -187,6 +215,12 @@ const hideShare = async () => {
popoverVisible.value = false
return
}
if (sharePeRequire.value) {
const peRequireValid = validatePeRequire()
if (!peRequireValid) {
return
}
}
const pwdValid = validatePwdFormat()
const uuidValid = await validateUuid()
if (pwdValid && uuidValid) {
@ -210,6 +244,8 @@ const shareTips = computed(
() =>
`开启后,用户可以通过该链接访问${props.resourceType === 'dashboard' ? '仪表板' : '数据大屏'}`
)
const shareDisable = computed(() => shareStore.getShareDisable)
const sharePeRequire = computed(() => shareStore.getSharePeRequire)
const copyInfo = async () => {
if (shareEnable.value) {
@ -242,10 +278,10 @@ const closeLoading = () => {
}
const share = () => {
loadShareInfo()
loadShareInfo(validatePeRequire)
}
const loadShareInfo = () => {
const loadShareInfo = cb => {
showLoading()
const resourceId = props.resourceId
const url = `/share/detail/${resourceId}`
@ -257,6 +293,7 @@ const loadShareInfo = () => {
})
.finally(() => {
closeLoading()
cb && cb()
})
}
@ -277,7 +314,7 @@ const enableSwitcher = () => {
const resourceId = props.resourceId
const url = `/share/switcher/${resourceId}`
request.post({ url }).then(() => {
loadShareInfo()
loadShareInfo(null)
})
}
@ -307,6 +344,7 @@ const expEnableSwitcher = val => {
exp = now.getTime()
state.detailInfo.exp = exp
}
validateExpRequire()
expChangeHandler(exp)
}
@ -320,7 +358,7 @@ const expChangeHandler = exp => {
const url = '/share/editExp'
const data = { resourceId, exp }
request.post({ url, data }).then(() => {
loadShareInfo()
loadShareInfo(null)
})
}
@ -329,6 +367,7 @@ const pwdEnableSwitcher = val => {
if (val) {
pwd = getUuid()
}
validatePwdRequire()
resetPwdHandler(pwd, true)
}
const resetPwd = () => {
@ -340,7 +379,7 @@ const resetPwdHandler = (pwd?: string, autoPwd?: boolean) => {
const url = '/share/editPwd'
const data = { resourceId, pwd, autoPwd }
request.post({ url, data }).then(() => {
loadShareInfo()
loadShareInfo(null)
})
}
@ -369,7 +408,32 @@ const getUuid = () => {
.join('')
return result
}
const validatePeRequire = () => {
if (shareEnable.value && sharePeRequire.value) {
const expRequireValid = validateExpRequire()
const pwdRequireValid = validatePwdRequire()
return expRequireValid && pwdRequireValid
}
return true
}
const validateExpRequire = () => {
if (!sharePeRequire.value || overTimeEnable.value) {
showCheckboxError(null, expCheckbox)
return true
}
showCheckboxError('必填', expCheckbox)
return false
}
const validatePwdRequire = () => {
if (!sharePeRequire.value || passwdEnable.value) {
showCheckboxError(null, pwdCheckbox)
return true
}
showCheckboxError('必填', pwdCheckbox)
return false
}
const validatePwdFormat = () => {
if (!shareEnable.value || !passwdEnable.value || state.detailInfo.autoPwd) {
showPageError(null, pwdRef)
@ -389,6 +453,34 @@ const validatePwdFormat = () => {
resetPwdHandler(val, false)
return true
}
const showCheckboxError = (msg, target, className?: string) => {
if (!target.value) {
return
}
className = className || 'checkbox-span-require'
const fullClassName = `.${className}`
const e = target.value.$el
if (!msg) {
e.style = null
e.children[0].children[1].style.borderColor = null
const child = e.children[1].children[0].querySelector(fullClassName)
if (child) {
e.children[1].children[0].removeChild(child)
}
} else {
e.style.color = 'red'
e.children[0].children[1].style.borderColor = 'red'
const child = e.children[1].children[0].querySelector(fullClassName)
if (!child) {
const errorDom = document.createElement('span')
errorDom.className = className
errorDom.innerText = msg
e.children[1].children[0].appendChild(errorDom)
} else {
child.innerText = msg
}
}
}
const showPageError = (msg, target, className?: string) => {
className = className || 'link-pwd-error-msg'
const fullClassName = `.${className}`
@ -578,6 +670,23 @@ defineExpose({
}
}
}
:deep(.checkbox-span) {
display: flex;
align-items: center;
.pe-require {
color: red;
font-size: 10px;
line-height: 32px;
margin: 0 4px;
}
.checkbox-span-require {
font-size: 10px;
}
.pe-tips-hidden {
display: none;
}
}
.inline-share-item-picker {
display: flex;
align-items: center;

View File

@ -234,7 +234,11 @@ defineExpose({
<el-switch
class="de-basic-switch"
v-if="
item.pkey === 'autoCreateUser' || item.pkey === 'pwdStrategy' || item.pkey === 'dip'
item.pkey === 'autoCreateUser' ||
item.pkey === 'pwdStrategy' ||
item.pkey === 'dip' ||
item.pkey === 'shareDisable' ||
item.pkey === 'sharePeRequire'
"
active-value="true"
inactive-value="false"

View File

@ -42,6 +42,10 @@ const tooltips = [
{
key: 'setting_basic.platformRid',
val: '作用域包括认证设置和平台对接'
},
{
key: 'setting_basic.shareDisable',
val: '开启后仪表板以及大屏分享无效'
}
]
const state = reactive({
@ -87,7 +91,9 @@ const search = cb => {
if (
item.pkey === 'basic.autoCreateUser' ||
item.pkey === 'basic.dip' ||
item.pkey === 'basic.pwdStrategy'
item.pkey === 'basic.pwdStrategy' ||
item.pkey === 'basic.shareDisable' ||
item.pkey === 'basic.sharePeRequire'
) {
item.pval = item.pval === 'true' ? '开启' : '未开启'
} else if (item.pkey === 'basic.platformOid') {

View File

@ -16,7 +16,6 @@ import GridTable from '@/components/grid-table/src/GridTable.vue'
import { useRouter } from 'vue-router'
import dayjs from 'dayjs'
import { shortcutOption } from './ShortcutOption'
/* import { XpackComponent } from '@/components/plugin' */
import { interactiveStoreWithOut } from '@/store/modules/interactive'
import { storeApi } from '@/api/visualization/dataVisualization'
import { useCache } from '@/hooks/web/useCache'
@ -33,6 +32,9 @@ const interactiveStore = interactiveStoreWithOut()
const { wsCache } = useCache()
const appStore = useAppStoreWithOut()
const embeddedStore = useEmbedded()
import { useShareStoreWithOut } from '@/store/modules/share'
const shareStore = useShareStoreWithOut()
const { push } = useRouter()
defineProps({
expand: {
@ -50,6 +52,9 @@ const state = reactive({
tableColumn: []
})
const busiDataMap = computed(() => interactiveStore.getData)
const shareDisable = computed(() => {
return shareStore.getShareDisable
})
const iconMap = {
panel: icon_dashboard_outlined,
panelMobile: dvDashboardSpineMobile,
@ -135,14 +140,6 @@ const loadTableData = () => {
})
}
/* const panelLoad = paneInfo => {
tablePaneList.value.push({
title: paneInfo.title,
name: paneInfo.name,
disabled: tablePaneList.value[1].disabled
})
} */
const tablePaneList = ref([
{ title: t('work_branch.recently_used'), name: 'recent', disabled: false },
{ title: t('work_branch.my_collection'), name: 'store', disabled: false },
@ -258,7 +255,7 @@ const getEmptyDesc = (): string => {
>
<el-tabs v-model="activeName" class="dashboard-type-tabs" @tab-click="handleClick">
<el-tab-pane
v-for="item in tablePaneList"
v-for="item in tablePaneList.filter(pannel => !shareDisable || pannel.name !== 'share')"
:key="item.name"
:disabled="item.disabled"
:label="item.title"
@ -278,9 +275,7 @@ const getEmptyDesc = (): string => {
</template>
</el-tab-pane>
</el-tabs>
<!-- <XpackComponent jsname="c2hhcmUtcGFuZWw=" @loaded="panelLoad" /> -->
<!-- <XpackComponent :active-name="activeName" jsname="c2hhcmU=" @set-loading="setLoading" /> -->
<share-grid :active-name="activeName" @set-loading="setLoading" />
<share-grid v-if="!shareDisable" :active-name="activeName" @set-loading="setLoading" />
<el-row v-if="activeName === 'recent' || activeName === 'store'">
<el-col :span="12">
<el-select
@ -396,18 +391,12 @@ const getEmptyDesc = (): string => {
</el-icon>
</el-tooltip>
<ShareHandler
v-if="!shareDisable"
:in-grid="true"
:weight="scope.row.weight"
:resource-id="activeName === 'recent' ? scope.row.id : scope.row.resourceId"
:resource-type="scope.row.type"
/>
<!-- <XpackComponent
:in-grid="true"
jsname="c2hhcmUtaGFuZGxlcg=="
:weight="scope.row.weight"
:resource-id="activeName === 'recent' ? scope.row.id : scope.row.resourceId"
:resource-type="scope.row.type"
/> -->
<el-tooltip
v-if="activeName === 'store'"
effect="dark"

View File

@ -22,6 +22,11 @@ import DeResourceCreateOptV2 from '@/views/common/DeResourceCreateOptV2.vue'
import { Base64 } from 'js-base64'
import { useEmbedded } from '@/store/modules/embedded'
import { useAppStoreWithOut } from '@/store/modules/app'
import { useShareStoreWithOut } from '@/store/modules/share'
import { queryShareBaseApi } from '@/api/visualization/dataVisualization'
const shareStore = useShareStoreWithOut()
const userStore = useUserStoreWithOut()
const interactiveStore = interactiveStoreWithOut()
const permissionStore = usePermissionStoreWithOut()
@ -281,8 +286,19 @@ const toTemplateMarketAdd = () => {
}
}
const loadShareBase = () => {
queryShareBaseApi().then(res => {
const param = {
shareDisable: res.data?.disable,
sharePeRequire: res.data?.peRequire
}
shareStore.setData(param)
})
}
fillCardInfo()
initMarketTemplate()
loadShareBase()
</script>
<template>

View File

@ -3,6 +3,7 @@ package io.dataease.api.system;
import com.github.xiaoymin.knife4j.annotations.ApiSupport;
import io.dataease.api.system.request.OnlineMapEditor;
import io.dataease.api.system.vo.SettingItemVO;
import io.dataease.api.system.vo.ShareBaseVO;
import io.swagger.v3.oas.annotations.Hidden;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
@ -52,4 +53,8 @@ public interface SysParameterApi {
@GetMapping("/defaultLogin")
Integer defaultLogin();
@GetMapping("/shareBase")
@Operation(summary = "查询分享设置")
ShareBaseVO shareBase();
}

View File

@ -0,0 +1,19 @@
package io.dataease.api.system.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
@Schema(description = "分享设置参数项")
@Data
public class ShareBaseVO implements Serializable {
@Serial
private static final long serialVersionUID = -8418758128428770485L;
@Schema(description = "禁用分享")
private boolean disable;
@Schema(description = "有效期密码必填")
private boolean peRequire;
}

View File

@ -33,5 +33,9 @@ public class XpackShareProxyVO implements Serializable {
private String type;
private boolean inIframeError = true;
private boolean shareDisable = false;
private boolean peRequireValid = true;
private TicketValidVO ticketValidVO;
}