Merge pull request #9000 from dataease/pr@dev-v2@feat_share_uuid_custom
feat: 公共链接后缀可自定义close #8195
This commit is contained in:
commit
4066c3bcee
@ -6,6 +6,7 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
|||||||
import io.dataease.api.visualization.request.VisualizationWorkbranchQueryRequest;
|
import io.dataease.api.visualization.request.VisualizationWorkbranchQueryRequest;
|
||||||
import io.dataease.api.xpack.share.request.XpackShareProxyRequest;
|
import io.dataease.api.xpack.share.request.XpackShareProxyRequest;
|
||||||
import io.dataease.api.xpack.share.request.XpackSharePwdValidator;
|
import io.dataease.api.xpack.share.request.XpackSharePwdValidator;
|
||||||
|
import io.dataease.api.xpack.share.request.XpackShareUuidEditor;
|
||||||
import io.dataease.api.xpack.share.vo.XpackShareGridVO;
|
import io.dataease.api.xpack.share.vo.XpackShareGridVO;
|
||||||
import io.dataease.api.xpack.share.vo.XpackShareProxyVO;
|
import io.dataease.api.xpack.share.vo.XpackShareProxyVO;
|
||||||
import io.dataease.auth.bo.TokenUserBO;
|
import io.dataease.auth.bo.TokenUserBO;
|
||||||
@ -31,6 +32,8 @@ import java.util.ArrayList;
|
|||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.regex.Matcher;
|
||||||
|
import java.util.regex.Pattern;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
@Component("xpackShareManage")
|
@Component("xpackShareManage")
|
||||||
@ -70,6 +73,35 @@ public class XpackShareManage {
|
|||||||
xpackShareMapper.insert(xpackShare);
|
xpackShareMapper.insert(xpackShare);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public String editUuid(XpackShareUuidEditor editor) {
|
||||||
|
Long resourceId = editor.getResourceId();
|
||||||
|
String uuid = editor.getUuid();
|
||||||
|
XpackShare originData = queryByResource(resourceId);
|
||||||
|
if (ObjectUtils.isEmpty(originData)) {
|
||||||
|
return "公共链接不存在,请先创建!";
|
||||||
|
}
|
||||||
|
if (StringUtils.isBlank(uuid)) {
|
||||||
|
return "不能为空!";
|
||||||
|
}
|
||||||
|
if (StringUtils.equals(uuid, originData.getUuid())) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
QueryWrapper<XpackShare> queryWrapper = new QueryWrapper<>();
|
||||||
|
queryWrapper.eq("uuid", uuid);
|
||||||
|
if (xpackShareMapper.selectCount(queryWrapper) > 0) {
|
||||||
|
return "已存在相同的链接,请重新输入!";
|
||||||
|
}
|
||||||
|
String regex = "^[a-zA-Z0-9]{8,16}$";
|
||||||
|
Pattern pattern = Pattern.compile(regex);
|
||||||
|
Matcher matcher = pattern.matcher(uuid);
|
||||||
|
if (!matcher.matches()) {
|
||||||
|
return "仅支持8-16位(字母数字),请重新输入!";
|
||||||
|
}
|
||||||
|
originData.setUuid(uuid);
|
||||||
|
xpackShareMapper.updateById(originData);
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
public void editExp(Long resourceId, Long exp) {
|
public void editExp(Long resourceId, Long exp) {
|
||||||
XpackShare originData = queryByResource(resourceId);
|
XpackShare originData = queryByResource(resourceId);
|
||||||
if (ObjectUtils.isEmpty(originData)) {
|
if (ObjectUtils.isEmpty(originData)) {
|
||||||
@ -92,6 +124,8 @@ public class XpackShareManage {
|
|||||||
xpackShareMapper.updateById(originData);
|
xpackShareMapper.updateById(originData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
public IPage<XpackSharePO> querySharePage(int goPage, int pageSize, VisualizationWorkbranchQueryRequest request) {
|
public IPage<XpackSharePO> querySharePage(int goPage, int pageSize, VisualizationWorkbranchQueryRequest request) {
|
||||||
Long uid = AuthUtils.getUser().getUserId();
|
Long uid = AuthUtils.getUser().getUserId();
|
||||||
QueryWrapper<Object> queryWrapper = new QueryWrapper<>();
|
QueryWrapper<Object> queryWrapper = new QueryWrapper<>();
|
||||||
@ -170,16 +204,16 @@ public class XpackShareManage {
|
|||||||
if (StringUtils.isBlank(xpackShare.getPwd())) return true;
|
if (StringUtils.isBlank(xpackShare.getPwd())) return true;
|
||||||
if (StringUtils.isBlank(ciphertext)) return false;
|
if (StringUtils.isBlank(ciphertext)) return false;
|
||||||
String text = RsaUtils.decryptStr(ciphertext);
|
String text = RsaUtils.decryptStr(ciphertext);
|
||||||
int splitIndex = 8;
|
int splitIndex = text.indexOf(",");
|
||||||
String pwd = text.substring(splitIndex);
|
String pwd = text.substring(splitIndex + 1);
|
||||||
String uuid = text.substring(0, splitIndex);
|
String uuid = text.substring(0, splitIndex);
|
||||||
return StringUtils.equals(xpackShare.getUuid(), uuid) && StringUtils.equals(xpackShare.getPwd(), pwd);
|
return StringUtils.equals(xpackShare.getUuid(), uuid) && StringUtils.equals(xpackShare.getPwd(), pwd);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean validatePwd(XpackSharePwdValidator validator) {
|
public boolean validatePwd(XpackSharePwdValidator validator) {
|
||||||
String ciphertext = RsaUtils.decryptStr(validator.getCiphertext());
|
String ciphertext = RsaUtils.decryptStr(validator.getCiphertext());
|
||||||
int splitIndex = 8;
|
int splitIndex = ciphertext.indexOf(",");
|
||||||
String pwd = ciphertext.substring(splitIndex);
|
String pwd = ciphertext.substring(splitIndex + 1);
|
||||||
String uuid = ciphertext.substring(0, splitIndex);
|
String uuid = ciphertext.substring(0, splitIndex);
|
||||||
QueryWrapper<XpackShare> queryWrapper = new QueryWrapper<>();
|
QueryWrapper<XpackShare> queryWrapper = new QueryWrapper<>();
|
||||||
queryWrapper.eq("uuid", uuid);
|
queryWrapper.eq("uuid", uuid);
|
||||||
|
|||||||
@ -2,10 +2,7 @@ package io.dataease.share.server;
|
|||||||
|
|
||||||
import io.dataease.api.visualization.request.VisualizationWorkbranchQueryRequest;
|
import io.dataease.api.visualization.request.VisualizationWorkbranchQueryRequest;
|
||||||
import io.dataease.api.xpack.share.XpackShareApi;
|
import io.dataease.api.xpack.share.XpackShareApi;
|
||||||
import io.dataease.api.xpack.share.request.XpackShareExpRequest;
|
import io.dataease.api.xpack.share.request.*;
|
||||||
import io.dataease.api.xpack.share.request.XpackShareProxyRequest;
|
|
||||||
import io.dataease.api.xpack.share.request.XpackSharePwdRequest;
|
|
||||||
import io.dataease.api.xpack.share.request.XpackSharePwdValidator;
|
|
||||||
import io.dataease.api.xpack.share.vo.XpackShareGridVO;
|
import io.dataease.api.xpack.share.vo.XpackShareGridVO;
|
||||||
import io.dataease.api.xpack.share.vo.XpackShareProxyVO;
|
import io.dataease.api.xpack.share.vo.XpackShareProxyVO;
|
||||||
import io.dataease.api.xpack.share.vo.XpackShareVO;
|
import io.dataease.api.xpack.share.vo.XpackShareVO;
|
||||||
@ -70,7 +67,12 @@ public class XpackShareServer implements XpackShareApi {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Map<String, String> queryRelationByUserId(@PathVariable("uid") Long uid) {
|
public Map<String, String> queryRelationByUserId(Long uid) {
|
||||||
return xpackShareManage.queryRelationByUserId(uid);
|
return xpackShareManage.queryRelationByUserId(uid);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String editUuid(XpackShareUuidEditor editor) {
|
||||||
|
return xpackShareManage.editUuid(editor);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -50,6 +50,8 @@ import { rsaEncryp } from '@/utils/encryption'
|
|||||||
import { useCache } from '@/hooks/web/useCache'
|
import { useCache } from '@/hooks/web/useCache'
|
||||||
import { queryDekey } from '@/api/login'
|
import { queryDekey } from '@/api/login'
|
||||||
import { CustomPassword } from '@/components/custom-password'
|
import { CustomPassword } from '@/components/custom-password'
|
||||||
|
import { useRoute } from 'vue-router'
|
||||||
|
const route = useRoute()
|
||||||
const { wsCache } = useCache()
|
const { wsCache } = useCache()
|
||||||
const appStore = useAppStoreWithOut()
|
const appStore = useAppStoreWithOut()
|
||||||
|
|
||||||
@ -76,15 +78,9 @@ const refresh = async (formEl: FormInstance | undefined) => {
|
|||||||
if (!formEl) return
|
if (!formEl) return
|
||||||
await formEl.validate((valid, fields) => {
|
await formEl.validate((valid, fields) => {
|
||||||
if (valid) {
|
if (valid) {
|
||||||
const curLocation = window.location.href
|
const uuid = route.params.uuid
|
||||||
const paramIndex = curLocation.indexOf('?')
|
|
||||||
const uuidIndex = curLocation.indexOf('de-link/') + 8
|
|
||||||
const uuid = curLocation.substring(
|
|
||||||
uuidIndex,
|
|
||||||
paramIndex !== -1 ? paramIndex : curLocation.length
|
|
||||||
)
|
|
||||||
const pwd = form.value.password
|
const pwd = form.value.password
|
||||||
const text = uuid + pwd
|
const text = `${uuid},${pwd}`
|
||||||
const ciphertext = rsaEncryp(text)
|
const ciphertext = rsaEncryp(text)
|
||||||
request.post({ url: '/share/validate', data: { ciphertext } }).then(res => {
|
request.post({ url: '/share/validate', data: { ciphertext } }).then(res => {
|
||||||
if (res.data) {
|
if (res.data) {
|
||||||
@ -100,6 +96,7 @@ const refresh = async (formEl: FormInstance | undefined) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
|
debugger
|
||||||
if (!wsCache.get(appStore.getDekey)) {
|
if (!wsCache.get(appStore.getDekey)) {
|
||||||
queryDekey()
|
queryDekey()
|
||||||
.then(res => {
|
.then(res => {
|
||||||
|
|||||||
@ -31,7 +31,26 @@
|
|||||||
<el-switch size="small" v-model="shareEnable" @change="enableSwitcher" />
|
<el-switch size="small" v-model="shareEnable" @change="enableSwitcher" />
|
||||||
{{ shareTips }}
|
{{ shareTips }}
|
||||||
</div>
|
</div>
|
||||||
<div v-if="shareEnable" class="text">{{ linkAddr }}</div>
|
<div v-if="shareEnable" class="custom-link-line">
|
||||||
|
<el-input
|
||||||
|
ref="linkUuidRef"
|
||||||
|
placeholder=""
|
||||||
|
v-model="state.detailInfo.uuid"
|
||||||
|
:disabled="!linkCustom"
|
||||||
|
@blur="finishEditUuid"
|
||||||
|
>
|
||||||
|
<template #prefix>
|
||||||
|
{{ formatLinkBase() }}
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
<el-button v-if="linkCustom" text @click="finishEditUuid">完成</el-button>
|
||||||
|
<el-button v-else @click="editUuid" size="default" plain>
|
||||||
|
<template #icon>
|
||||||
|
<icon name="icon_admin_outlined"></icon>
|
||||||
|
</template>
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
|
||||||
<div v-if="shareEnable" class="exp-container">
|
<div v-if="shareEnable" class="exp-container">
|
||||||
<el-checkbox
|
<el-checkbox
|
||||||
:disabled="!shareEnable"
|
:disabled="!shareEnable"
|
||||||
@ -133,6 +152,8 @@ const passwdEnable = ref(false)
|
|||||||
const shareEnable = ref(false)
|
const shareEnable = ref(false)
|
||||||
const linkAddr = ref('')
|
const linkAddr = ref('')
|
||||||
const expError = ref(false)
|
const expError = ref(false)
|
||||||
|
const linkCustom = ref(false)
|
||||||
|
const linkUuidRef = ref(null)
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
detailInfo: {
|
detailInfo: {
|
||||||
id: '',
|
id: '',
|
||||||
@ -147,9 +168,46 @@ const shareTips = computed(
|
|||||||
() =>
|
() =>
|
||||||
`开启后,用户可以通过该链接访问${props.resourceType === 'dashboard' ? '仪表板' : '数据大屏'}`
|
`开启后,用户可以通过该链接访问${props.resourceType === 'dashboard' ? '仪表板' : '数据大屏'}`
|
||||||
)
|
)
|
||||||
|
const editUuid = () => {
|
||||||
|
linkCustom.value = true
|
||||||
|
nextTick(() => {
|
||||||
|
if (linkUuidRef?.value) {
|
||||||
|
linkUuidRef.value.input.focus()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const validateUuid = async () => {
|
||||||
|
const val = state.detailInfo.uuid
|
||||||
|
const className = 'link-uuid-error-msg'
|
||||||
|
if (!val) {
|
||||||
|
showPageError('不能为空!', linkUuidRef, className)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
const regex = /^[a-zA-Z0-9]{8,16}$/
|
||||||
|
const result = regex.test(val)
|
||||||
|
if (!result) {
|
||||||
|
showPageError('仅支持8-16位(字母数字),请重新输入!', linkUuidRef, className)
|
||||||
|
} else {
|
||||||
|
const msg = await uuidValidateApi(val)
|
||||||
|
showPageError(msg, linkUuidRef, className)
|
||||||
|
return !msg
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
const uuidValidateApi = async val => {
|
||||||
|
const url = '/share/editUuid'
|
||||||
|
const data = { resourceId: props.resourceId, uuid: val }
|
||||||
|
const res = await request.post({ url, data })
|
||||||
|
return res.data
|
||||||
|
}
|
||||||
|
const finishEditUuid = async () => {
|
||||||
|
const uuidValid = await validateUuid()
|
||||||
|
linkCustom.value = !uuidValid
|
||||||
|
}
|
||||||
const copyPwd = async () => {
|
const copyPwd = async () => {
|
||||||
if (shareEnable.value && passwdEnable.value) {
|
if (shareEnable.value && passwdEnable.value) {
|
||||||
if (!state.detailInfo.autoPwd && existErrorMsg()) {
|
if (!state.detailInfo.autoPwd && existErrorMsg('link-pwd-error-msg')) {
|
||||||
ElMessage.warning('密码格式错误,请重新填写!')
|
ElMessage.warning('密码格式错误,请重新填写!')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -166,6 +224,10 @@ const copyPwd = async () => {
|
|||||||
const copyInfo = async () => {
|
const copyInfo = async () => {
|
||||||
if (shareEnable.value) {
|
if (shareEnable.value) {
|
||||||
try {
|
try {
|
||||||
|
if (existErrorMsg('link-uuid-error-msg')) {
|
||||||
|
ElMessage.warning('链接格式错误,请重新填写!')
|
||||||
|
return
|
||||||
|
}
|
||||||
await toClipboard(linkAddr.value)
|
await toClipboard(linkAddr.value)
|
||||||
ElMessage.success(t('common.copy_success'))
|
ElMessage.success(t('common.copy_success'))
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -226,6 +288,9 @@ const enableSwitcher = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const formatLinkAddr = () => {
|
const formatLinkAddr = () => {
|
||||||
|
linkAddr.value = formatLinkBase() + state.detailInfo.uuid
|
||||||
|
}
|
||||||
|
const formatLinkBase = () => {
|
||||||
let prefix = '/'
|
let prefix = '/'
|
||||||
if (window.DataEaseBi?.baseUrl) {
|
if (window.DataEaseBi?.baseUrl) {
|
||||||
prefix = window.DataEaseBi.baseUrl + '#'
|
prefix = window.DataEaseBi.baseUrl + '#'
|
||||||
@ -233,7 +298,7 @@ const formatLinkAddr = () => {
|
|||||||
const href = window.location.href
|
const href = window.location.href
|
||||||
prefix = href.substring(0, href.indexOf('#') + 1)
|
prefix = href.substring(0, href.indexOf('#') + 1)
|
||||||
}
|
}
|
||||||
linkAddr.value = prefix + SHARE_BASE + state.detailInfo.uuid
|
return prefix + SHARE_BASE
|
||||||
}
|
}
|
||||||
|
|
||||||
const expEnableSwitcher = val => {
|
const expEnableSwitcher = val => {
|
||||||
@ -260,32 +325,36 @@ const expChangeHandler = exp => {
|
|||||||
loadShareInfo()
|
loadShareInfo()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
const beforeClose = done => {
|
const beforeClose = async done => {
|
||||||
if (validatePwdFormat()) {
|
const pwdValid = validatePwdFormat()
|
||||||
|
const uuidValid = await validateUuid()
|
||||||
|
if (pwdValid && uuidValid) {
|
||||||
done()
|
done()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const validatePwdFormat = () => {
|
const validatePwdFormat = () => {
|
||||||
if (!shareEnable.value || state.detailInfo.autoPwd) {
|
if (!shareEnable.value || state.detailInfo.autoPwd) {
|
||||||
showPageError(null)
|
showPageError(null, pwdRef)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
const val = state.detailInfo.pwd
|
const val = state.detailInfo.pwd
|
||||||
if (!val) {
|
if (!val) {
|
||||||
showPageError('密码不能为空,请重新输入!')
|
showPageError('密码不能为空,请重新输入!', pwdRef)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
const regex = /^(?=.*[A-Za-z])(?=.*\d)(?=.*[!@#$%^&*()_+])[A-Za-z\d!@#$%^&*()_+]{4,10}$/
|
const regex = /^(?=.*[A-Za-z])(?=.*\d)(?=.*[!@#$%^&*()_+])[A-Za-z\d!@#$%^&*()_+]{4,10}$/
|
||||||
if (!regex.test(val)) {
|
if (!regex.test(val)) {
|
||||||
showPageError('密码必须是包含数字、字母、特殊字符[!@#$%^&*()_+]的4-10位字符串')
|
showPageError('密码必须是包含数字、字母、特殊字符[!@#$%^&*()_+]的4-10位字符串', pwdRef)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
showPageError(null)
|
showPageError(null, pwdRef)
|
||||||
resetPwdHandler(val, false)
|
resetPwdHandler(val, false)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
const showPageError = msg => {
|
const showPageError = (msg, target, className?: string) => {
|
||||||
const domRef = pwdRef
|
className = className || 'link-pwd-error-msg'
|
||||||
|
const fullClassName = `.${className}`
|
||||||
|
const domRef = target || pwdRef
|
||||||
if (!domRef.value) {
|
if (!domRef.value) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -293,7 +362,7 @@ const showPageError = msg => {
|
|||||||
if (!msg) {
|
if (!msg) {
|
||||||
e.style = null
|
e.style = null
|
||||||
e.style.borderColor = null
|
e.style.borderColor = null
|
||||||
const child = e.parentElement.querySelector('.link-pwd-error-msg')
|
const child = e.parentElement.querySelector(fullClassName)
|
||||||
if (child) {
|
if (child) {
|
||||||
e.parentElement['style'] = null
|
e.parentElement['style'] = null
|
||||||
e.parentElement.removeChild(child)
|
e.parentElement.removeChild(child)
|
||||||
@ -302,10 +371,10 @@ const showPageError = msg => {
|
|||||||
e.style.color = 'red'
|
e.style.color = 'red'
|
||||||
e.style.borderColor = 'red'
|
e.style.borderColor = 'red'
|
||||||
e.parentElement['style']['box-shadow'] = '0 0 0 1px red inset'
|
e.parentElement['style']['box-shadow'] = '0 0 0 1px red inset'
|
||||||
const child = e.parentElement.querySelector('.link-pwd-error-msg')
|
const child = e.parentElement.querySelector(fullClassName)
|
||||||
if (!child) {
|
if (!child) {
|
||||||
const errorDom = document.createElement('div')
|
const errorDom = document.createElement('div')
|
||||||
errorDom.className = 'link-pwd-error-msg'
|
errorDom.className = className
|
||||||
errorDom.innerText = msg
|
errorDom.innerText = msg
|
||||||
e.parentElement.appendChild(errorDom)
|
e.parentElement.appendChild(errorDom)
|
||||||
} else {
|
} else {
|
||||||
@ -313,12 +382,12 @@ const showPageError = msg => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const existErrorMsg = () => {
|
const existErrorMsg = (className: string) => {
|
||||||
return document.getElementsByClassName('link-pwd-error-msg')?.length
|
return document.getElementsByClassName(className)?.length
|
||||||
}
|
}
|
||||||
const autoEnableSwitcher = val => {
|
const autoEnableSwitcher = val => {
|
||||||
if (val) {
|
if (val) {
|
||||||
showPageError(null)
|
showPageError(null, pwdRef)
|
||||||
resetPwd()
|
resetPwd()
|
||||||
} else {
|
} else {
|
||||||
state.detailInfo.pwd = ''
|
state.detailInfo.pwd = ''
|
||||||
@ -348,11 +417,29 @@ const resetPwdHandler = (pwd?: string, autoPwd?: boolean) => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const getUuid = () => {
|
const getUuid = () => {
|
||||||
return 'xyxy'.replace(/[xy]/g, function (c) {
|
const length = 10
|
||||||
var r = (Math.random() * 16) | 0,
|
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()_+'
|
||||||
v = c == 'x' ? r : (r & 0x3) | 0x8
|
let result = ''
|
||||||
return v.toString(16)
|
const specialChars = '!@#$%^&*()_+'
|
||||||
})
|
let hasSpecialChar = false
|
||||||
|
|
||||||
|
for (let i = 0; i < length; i++) {
|
||||||
|
if (i === 0) {
|
||||||
|
result += characters.charAt(Math.floor(Math.random() * characters.length))
|
||||||
|
} else {
|
||||||
|
if (!hasSpecialChar && i < length - 2) {
|
||||||
|
result += specialChars.charAt(Math.floor(Math.random() * specialChars.length))
|
||||||
|
hasSpecialChar = true
|
||||||
|
} else {
|
||||||
|
result += characters.charAt(Math.floor(Math.random() * characters.length))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
result = result
|
||||||
|
.split('')
|
||||||
|
.sort(() => 0.5 - Math.random())
|
||||||
|
.join('')
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
const execute = () => {
|
const execute = () => {
|
||||||
@ -483,18 +570,26 @@ onMounted(() => {
|
|||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.custom-link-line {
|
||||||
.text {
|
display: flex;
|
||||||
border-radius: 4px;
|
|
||||||
border: 1px solid #bbbfc4;
|
|
||||||
background: #eff0f1;
|
|
||||||
margin-bottom: 16px;
|
margin-bottom: 16px;
|
||||||
height: 32px;
|
align-items: center;
|
||||||
padding: 5px 12px;
|
button {
|
||||||
color: #8f959e;
|
width: 40px;
|
||||||
font-size: 14px;
|
min-width: 40px;
|
||||||
font-style: normal;
|
margin-left: 8px;
|
||||||
line-height: 22px;
|
height: 100%;
|
||||||
|
}
|
||||||
|
:deep(.link-uuid-error-msg) {
|
||||||
|
color: red;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 9;
|
||||||
|
font-size: 10px;
|
||||||
|
height: 10px;
|
||||||
|
top: 25px;
|
||||||
|
width: 350px;
|
||||||
|
left: 0px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,8 +27,27 @@
|
|||||||
<el-switch size="small" v-model="shareEnable" @change="enableSwitcher" />
|
<el-switch size="small" v-model="shareEnable" @change="enableSwitcher" />
|
||||||
{{ shareTips }}
|
{{ shareTips }}
|
||||||
</div>
|
</div>
|
||||||
<div v-if="shareEnable" class="text share-padding">
|
<!-- <div v-if="shareEnable" class="text share-padding">
|
||||||
<el-input v-model="linkAddr" disabled />
|
<el-input v-model="linkAddr" disabled />
|
||||||
|
</div> -->
|
||||||
|
<div v-if="shareEnable" class="custom-link-line share-padding">
|
||||||
|
<el-input
|
||||||
|
ref="linkUuidRef"
|
||||||
|
placeholder=""
|
||||||
|
v-model="state.detailInfo.uuid"
|
||||||
|
:disabled="!linkCustom"
|
||||||
|
@blur="finishEditUuid"
|
||||||
|
>
|
||||||
|
<template #prefix>
|
||||||
|
{{ formatLinkBase() }}
|
||||||
|
</template>
|
||||||
|
</el-input>
|
||||||
|
<el-button v-if="linkCustom" text @click.stop="finishEditUuid">完成</el-button>
|
||||||
|
<el-button v-else @click.stop="editUuid" size="default" plain>
|
||||||
|
<template #icon>
|
||||||
|
<icon name="icon_admin_outlined"></icon>
|
||||||
|
</template>
|
||||||
|
</el-button>
|
||||||
</div>
|
</div>
|
||||||
<div v-if="shareEnable" class="exp-container share-padding">
|
<div v-if="shareEnable" class="exp-container share-padding">
|
||||||
<el-checkbox
|
<el-checkbox
|
||||||
@ -129,6 +148,8 @@ const passwdEnable = ref(false)
|
|||||||
const shareEnable = ref(false)
|
const shareEnable = ref(false)
|
||||||
const linkAddr = ref('')
|
const linkAddr = ref('')
|
||||||
const expError = ref(false)
|
const expError = ref(false)
|
||||||
|
const linkCustom = ref(false)
|
||||||
|
const linkUuidRef = ref(null)
|
||||||
const state = reactive({
|
const state = reactive({
|
||||||
detailInfo: {
|
detailInfo: {
|
||||||
id: '',
|
id: '',
|
||||||
@ -145,8 +166,10 @@ watch(
|
|||||||
popoverVisible.value = false
|
popoverVisible.value = false
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
const hideShare = () => {
|
const hideShare = async () => {
|
||||||
if (validatePwdFormat()) {
|
const pwdValid = validatePwdFormat()
|
||||||
|
const uuidValid = await validateUuid()
|
||||||
|
if (pwdValid && uuidValid) {
|
||||||
popoverVisible.value = false
|
popoverVisible.value = false
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -170,6 +193,10 @@ const shareTips = computed(
|
|||||||
const copyInfo = async () => {
|
const copyInfo = async () => {
|
||||||
if (shareEnable.value) {
|
if (shareEnable.value) {
|
||||||
try {
|
try {
|
||||||
|
if (existErrorMsg('link-uuid-error-msg')) {
|
||||||
|
ElMessage.warning('链接格式错误,请重新填写!')
|
||||||
|
return
|
||||||
|
}
|
||||||
await toClipboard(linkAddr.value)
|
await toClipboard(linkAddr.value)
|
||||||
ElMessage.success(t('common.copy_success'))
|
ElMessage.success(t('common.copy_success'))
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
@ -233,6 +260,9 @@ const enableSwitcher = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const formatLinkAddr = () => {
|
const formatLinkAddr = () => {
|
||||||
|
linkAddr.value = formatLinkBase() + state.detailInfo.uuid
|
||||||
|
}
|
||||||
|
const formatLinkBase = () => {
|
||||||
let prefix = '/'
|
let prefix = '/'
|
||||||
if (window.DataEaseBi?.baseUrl) {
|
if (window.DataEaseBi?.baseUrl) {
|
||||||
prefix = window.DataEaseBi.baseUrl + '#'
|
prefix = window.DataEaseBi.baseUrl + '#'
|
||||||
@ -240,7 +270,7 @@ const formatLinkAddr = () => {
|
|||||||
const href = window.location.href
|
const href = window.location.href
|
||||||
prefix = href.substring(0, href.indexOf('#') + 1)
|
prefix = href.substring(0, href.indexOf('#') + 1)
|
||||||
}
|
}
|
||||||
linkAddr.value = prefix + SHARE_BASE + state.detailInfo.uuid
|
return prefix + SHARE_BASE
|
||||||
}
|
}
|
||||||
|
|
||||||
const expEnableSwitcher = val => {
|
const expEnableSwitcher = val => {
|
||||||
@ -316,25 +346,27 @@ const getUuid = () => {
|
|||||||
|
|
||||||
const validatePwdFormat = () => {
|
const validatePwdFormat = () => {
|
||||||
if (!shareEnable.value || !passwdEnable.value || state.detailInfo.autoPwd) {
|
if (!shareEnable.value || !passwdEnable.value || state.detailInfo.autoPwd) {
|
||||||
showPageError(null)
|
showPageError(null, pwdRef)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
const val = state.detailInfo.pwd
|
const val = state.detailInfo.pwd
|
||||||
if (!val) {
|
if (!val) {
|
||||||
showPageError('密码不能为空,请重新输入!')
|
showPageError('密码不能为空,请重新输入!', pwdRef)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
const regex = /^(?=.*[A-Za-z])(?=.*\d)(?=.*[!@#$%^&*()_+])[A-Za-z\d!@#$%^&*()_+]{4,10}$/
|
const regex = /^(?=.*[A-Za-z])(?=.*\d)(?=.*[!@#$%^&*()_+])[A-Za-z\d!@#$%^&*()_+]{4,10}$/
|
||||||
if (!regex.test(val)) {
|
if (!regex.test(val)) {
|
||||||
showPageError('密码必须是包含数字、字母、特殊字符[!@#$%^&*()_+]的4-10位字符串')
|
showPageError('密码必须是包含数字、字母、特殊字符[!@#$%^&*()_+]的4-10位字符串', pwdRef)
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
showPageError(null)
|
showPageError(null, pwdRef)
|
||||||
resetPwdHandler(val, false)
|
resetPwdHandler(val, false)
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
const showPageError = msg => {
|
const showPageError = (msg, target, className?: string) => {
|
||||||
const domRef = pwdRef
|
className = className || 'link-pwd-error-msg'
|
||||||
|
const fullClassName = `.${className}`
|
||||||
|
const domRef = target || pwdRef
|
||||||
if (!domRef.value) {
|
if (!domRef.value) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -342,7 +374,7 @@ const showPageError = msg => {
|
|||||||
if (!msg) {
|
if (!msg) {
|
||||||
e.style = null
|
e.style = null
|
||||||
e.style.borderColor = null
|
e.style.borderColor = null
|
||||||
const child = e.parentElement.querySelector('.link-pwd-error-msg')
|
const child = e.parentElement.querySelector(fullClassName)
|
||||||
if (child) {
|
if (child) {
|
||||||
e.parentElement['style'] = null
|
e.parentElement['style'] = null
|
||||||
e.parentElement.removeChild(child)
|
e.parentElement.removeChild(child)
|
||||||
@ -351,10 +383,10 @@ const showPageError = msg => {
|
|||||||
e.style.color = 'red'
|
e.style.color = 'red'
|
||||||
e.style.borderColor = 'red'
|
e.style.borderColor = 'red'
|
||||||
e.parentElement['style']['box-shadow'] = '0 0 0 1px red inset'
|
e.parentElement['style']['box-shadow'] = '0 0 0 1px red inset'
|
||||||
const child = e.parentElement.querySelector('.link-pwd-error-msg')
|
const child = e.parentElement.querySelector(fullClassName)
|
||||||
if (!child) {
|
if (!child) {
|
||||||
const errorDom = document.createElement('div')
|
const errorDom = document.createElement('div')
|
||||||
errorDom.className = 'link-pwd-error-msg'
|
errorDom.className = className
|
||||||
errorDom.innerText = msg
|
errorDom.innerText = msg
|
||||||
e.parentElement.appendChild(errorDom)
|
e.parentElement.appendChild(errorDom)
|
||||||
} else {
|
} else {
|
||||||
@ -362,12 +394,12 @@ const showPageError = msg => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const existErrorMsg = () => {
|
const existErrorMsg = (className: string) => {
|
||||||
return document.getElementsByClassName('link-pwd-error-msg')?.length
|
return document.getElementsByClassName(className)?.length
|
||||||
}
|
}
|
||||||
const autoEnableSwitcher = val => {
|
const autoEnableSwitcher = val => {
|
||||||
if (val) {
|
if (val) {
|
||||||
showPageError(null)
|
showPageError(null, pwdRef)
|
||||||
resetPwd()
|
resetPwd()
|
||||||
} else {
|
} else {
|
||||||
state.detailInfo.pwd = ''
|
state.detailInfo.pwd = ''
|
||||||
@ -379,7 +411,7 @@ const autoEnableSwitcher = val => {
|
|||||||
|
|
||||||
const copyPwd = async () => {
|
const copyPwd = async () => {
|
||||||
if (shareEnable.value && passwdEnable.value) {
|
if (shareEnable.value && passwdEnable.value) {
|
||||||
if (!state.detailInfo.autoPwd && existErrorMsg()) {
|
if (!state.detailInfo.autoPwd && existErrorMsg('link-pwd-error-msg')) {
|
||||||
ElMessage.warning('密码格式错误,请重新填写!')
|
ElMessage.warning('密码格式错误,请重新填写!')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@ -393,6 +425,43 @@ const copyPwd = async () => {
|
|||||||
ElMessage.warning(t('common.copy_unsupported'))
|
ElMessage.warning(t('common.copy_unsupported'))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const editUuid = () => {
|
||||||
|
linkCustom.value = true
|
||||||
|
nextTick(() => {
|
||||||
|
if (linkUuidRef?.value) {
|
||||||
|
linkUuidRef.value.input.focus()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const validateUuid = async () => {
|
||||||
|
const val = state.detailInfo.uuid
|
||||||
|
const className = 'link-uuid-error-msg'
|
||||||
|
if (!val) {
|
||||||
|
showPageError('不能为空!', linkUuidRef, className)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
const regex = /^[a-zA-Z0-9]{8,16}$/
|
||||||
|
const result = regex.test(val)
|
||||||
|
if (!result) {
|
||||||
|
showPageError('仅支持8-16位(字母数字),请重新输入!', linkUuidRef, className)
|
||||||
|
} else {
|
||||||
|
const msg = await uuidValidateApi(val)
|
||||||
|
showPageError(msg, linkUuidRef, className)
|
||||||
|
return !msg
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
const uuidValidateApi = async val => {
|
||||||
|
const url = '/share/editUuid'
|
||||||
|
const data = { resourceId: props.resourceId, uuid: val }
|
||||||
|
const res = await request.post({ url, data })
|
||||||
|
return res.data
|
||||||
|
}
|
||||||
|
const finishEditUuid = async () => {
|
||||||
|
const uuidValid = await validateUuid()
|
||||||
|
linkCustom.value = !uuidValid
|
||||||
|
}
|
||||||
|
|
||||||
const execute = () => {
|
const execute = () => {
|
||||||
share()
|
share()
|
||||||
@ -436,6 +505,27 @@ defineExpose({
|
|||||||
.text {
|
.text {
|
||||||
padding-bottom: 5px !important;
|
padding-bottom: 5px !important;
|
||||||
}
|
}
|
||||||
|
.custom-link-line {
|
||||||
|
display: flex;
|
||||||
|
margin-bottom: 16px;
|
||||||
|
align-items: center;
|
||||||
|
button {
|
||||||
|
width: 40px;
|
||||||
|
min-width: 40px;
|
||||||
|
margin-left: 8px;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
:deep(.link-uuid-error-msg) {
|
||||||
|
color: red;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 9;
|
||||||
|
font-size: 10px;
|
||||||
|
height: 10px;
|
||||||
|
top: 25px;
|
||||||
|
width: 350px;
|
||||||
|
left: 0px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
.inline-share-item-picker {
|
.inline-share-item-picker {
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|||||||
@ -1,10 +1,7 @@
|
|||||||
package io.dataease.api.xpack.share;
|
package io.dataease.api.xpack.share;
|
||||||
|
|
||||||
import io.dataease.api.xpack.share.request.XpackShareExpRequest;
|
import io.dataease.api.xpack.share.request.*;
|
||||||
import io.dataease.api.xpack.share.request.XpackShareProxyRequest;
|
|
||||||
import io.dataease.api.xpack.share.request.XpackSharePwdRequest;
|
|
||||||
import io.dataease.api.visualization.request.VisualizationWorkbranchQueryRequest;
|
import io.dataease.api.visualization.request.VisualizationWorkbranchQueryRequest;
|
||||||
import io.dataease.api.xpack.share.request.XpackSharePwdValidator;
|
|
||||||
import io.dataease.api.xpack.share.vo.XpackShareGridVO;
|
import io.dataease.api.xpack.share.vo.XpackShareGridVO;
|
||||||
import io.dataease.api.xpack.share.vo.XpackShareProxyVO;
|
import io.dataease.api.xpack.share.vo.XpackShareProxyVO;
|
||||||
import io.dataease.api.xpack.share.vo.XpackShareVO;
|
import io.dataease.api.xpack.share.vo.XpackShareVO;
|
||||||
@ -61,4 +58,8 @@ public interface XpackShareApi {
|
|||||||
@Operation(summary = "", hidden = true)
|
@Operation(summary = "", hidden = true)
|
||||||
@GetMapping("/queryRelationByUserId/{uid}")
|
@GetMapping("/queryRelationByUserId/{uid}")
|
||||||
Map<String, String> queryRelationByUserId(@PathVariable("uid") Long uid);
|
Map<String, String> queryRelationByUserId(@PathVariable("uid") Long uid);
|
||||||
|
|
||||||
|
@Operation(summary = "编辑分享uuid")
|
||||||
|
@PostMapping("/editUuid")
|
||||||
|
String editUuid(@RequestBody XpackShareUuidEditor editor);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -0,0 +1,16 @@
|
|||||||
|
package io.dataease.api.xpack.share.request;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
import java.io.Serializable;
|
||||||
|
|
||||||
|
@Schema(description = "分享UUID编辑器")
|
||||||
|
@Data
|
||||||
|
public class XpackShareUuidEditor implements Serializable {
|
||||||
|
|
||||||
|
@Schema(description = "资源ID", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
private Long resourceId;
|
||||||
|
@Schema(description = "分享UUID", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||||
|
private String uuid;
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user