Merge pull request #9000 from dataease/pr@dev-v2@feat_share_uuid_custom

feat: 公共链接后缀可自定义close #8195
This commit is contained in:
fit2cloud-chenyw 2024-04-08 18:23:38 +08:00 committed by GitHub
commit 4066c3bcee
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
7 changed files with 306 additions and 71 deletions

View File

@ -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);

View File

@ -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);
}
} }

View File

@ -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 => {

View File

@ -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;
}
} }
} }
} }

View File

@ -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;

View File

@ -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);
} }

View File

@ -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;
}