feat: 分享功能密码增强close #8593
This commit is contained in:
parent
9732a99ace
commit
657bbf55e6
@ -5,11 +5,11 @@ import java.io.Serializable;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
*
|
* 公共链接
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @author fit2cloud
|
* @author fit2cloud
|
||||||
* @since 2023-09-22
|
* @since 2024-04-07
|
||||||
*/
|
*/
|
||||||
@TableName("xpack_share")
|
@TableName("xpack_share")
|
||||||
public class XpackShare implements Serializable {
|
public class XpackShare implements Serializable {
|
||||||
@ -61,6 +61,11 @@ public class XpackShare implements Serializable {
|
|||||||
*/
|
*/
|
||||||
private Integer type;
|
private Integer type;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 自动生成密码
|
||||||
|
*/
|
||||||
|
private Boolean autoPwd;
|
||||||
|
|
||||||
public Long getId() {
|
public Long getId() {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
@ -133,6 +138,14 @@ public class XpackShare implements Serializable {
|
|||||||
this.type = type;
|
this.type = type;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Boolean getAutoPwd() {
|
||||||
|
return autoPwd;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setAutoPwd(Boolean autoPwd) {
|
||||||
|
this.autoPwd = autoPwd;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "XpackShare{" +
|
return "XpackShare{" +
|
||||||
@ -145,6 +158,7 @@ public class XpackShare implements Serializable {
|
|||||||
", resourceId = " + resourceId +
|
", resourceId = " + resourceId +
|
||||||
", oid = " + oid +
|
", oid = " + oid +
|
||||||
", type = " + type +
|
", type = " + type +
|
||||||
|
", autoPwd = " + autoPwd +
|
||||||
"}";
|
"}";
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,11 +6,11 @@ import org.apache.ibatis.annotations.Mapper;
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* <p>
|
* <p>
|
||||||
* Mapper 接口
|
* 公共链接 Mapper 接口
|
||||||
* </p>
|
* </p>
|
||||||
*
|
*
|
||||||
* @author fit2cloud
|
* @author fit2cloud
|
||||||
* @since 2023-09-22
|
* @since 2024-04-07
|
||||||
*/
|
*/
|
||||||
@Mapper
|
@Mapper
|
||||||
public interface XpackShareMapper extends BaseMapper<XpackShare> {
|
public interface XpackShareMapper extends BaseMapper<XpackShare> {
|
||||||
|
|||||||
@ -82,12 +82,13 @@ public class XpackShareManage {
|
|||||||
xpackShareMapper.updateById(originData);
|
xpackShareMapper.updateById(originData);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void editPwd(Long resourceId, String pwd) {
|
public void editPwd(Long resourceId, String pwd, Boolean autoPwd) {
|
||||||
XpackShare originData = queryByResource(resourceId);
|
XpackShare originData = queryByResource(resourceId);
|
||||||
if (ObjectUtils.isEmpty(originData)) {
|
if (ObjectUtils.isEmpty(originData)) {
|
||||||
DEException.throwException("share instance not exist");
|
DEException.throwException("share instance not exist");
|
||||||
}
|
}
|
||||||
originData.setPwd(pwd);
|
originData.setPwd(pwd);
|
||||||
|
originData.setAutoPwd(ObjectUtils.isEmpty(autoPwd) || autoPwd);
|
||||||
xpackShareMapper.updateById(originData);
|
xpackShareMapper.updateById(originData);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -169,8 +170,7 @@ 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 len = text.length();
|
int splitIndex = 8;
|
||||||
int splitIndex = len - 4;
|
|
||||||
String pwd = text.substring(splitIndex);
|
String pwd = text.substring(splitIndex);
|
||||||
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);
|
||||||
@ -178,8 +178,7 @@ public class XpackShareManage {
|
|||||||
|
|
||||||
public boolean validatePwd(XpackSharePwdValidator validator) {
|
public boolean validatePwd(XpackSharePwdValidator validator) {
|
||||||
String ciphertext = RsaUtils.decryptStr(validator.getCiphertext());
|
String ciphertext = RsaUtils.decryptStr(validator.getCiphertext());
|
||||||
int len = ciphertext.length();
|
int splitIndex = 8;
|
||||||
int splitIndex = len - 4;
|
|
||||||
String pwd = ciphertext.substring(splitIndex);
|
String pwd = ciphertext.substring(splitIndex);
|
||||||
String uuid = ciphertext.substring(0, splitIndex);
|
String uuid = ciphertext.substring(0, splitIndex);
|
||||||
QueryWrapper<XpackShare> queryWrapper = new QueryWrapper<>();
|
QueryWrapper<XpackShare> queryWrapper = new QueryWrapper<>();
|
||||||
|
|||||||
@ -44,7 +44,7 @@ public class XpackShareServer implements XpackShareApi {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void editPwd(XpackSharePwdRequest request) {
|
public void editPwd(XpackSharePwdRequest request) {
|
||||||
xpackShareManage.editPwd(request.getResourceId(), request.getPwd());
|
xpackShareManage.editPwd(request.getResourceId(), request.getPwd(), request.getAutoPwd());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|||||||
@ -0,0 +1,2 @@
|
|||||||
|
ALTER TABLE `xpack_share`
|
||||||
|
ADD COLUMN `auto_pwd` tinyint(1) NOT NULL DEFAULT 1 COMMENT '自动生成密码' AFTER `type`;
|
||||||
13
core/core-frontend/src/directive/ClickOutside/index.ts
Normal file
13
core/core-frontend/src/directive/ClickOutside/index.ts
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
export const vClickOutside = {
|
||||||
|
beforeMount(el, binding) {
|
||||||
|
el.clickOutsideEvent = function (event) {
|
||||||
|
if (!(el === event.target || el.contains(event.target))) {
|
||||||
|
binding.value(event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
document.addEventListener('click', el.clickOutsideEvent)
|
||||||
|
},
|
||||||
|
unmounted(el) {
|
||||||
|
document.removeEventListener('click', el.clickOutsideEvent)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,5 +1,7 @@
|
|||||||
import { checkPermission } from './Permission'
|
import { checkPermission } from './Permission'
|
||||||
|
import { vClickOutside } from './ClickOutside'
|
||||||
import type { App } from 'vue'
|
import type { App } from 'vue'
|
||||||
export const installDirective = (app: App<Element>) => {
|
export const installDirective = (app: App<Element>) => {
|
||||||
app.directive('permission', checkPermission)
|
app.directive('permission', checkPermission)
|
||||||
|
app.directive('click-outside', vClickOutside)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1884,6 +1884,7 @@ export default {
|
|||||||
copy_short_link: '复制短链接',
|
copy_short_link: '复制短链接',
|
||||||
copy_short_link_passwd: '复制短链接及密码',
|
copy_short_link_passwd: '复制短链接及密码',
|
||||||
passwd_protect: '密码保护',
|
passwd_protect: '密码保护',
|
||||||
|
auto_pwd: '自动生成密码',
|
||||||
link: '链接',
|
link: '链接',
|
||||||
over_time: '有效期',
|
over_time: '有效期',
|
||||||
link_expire: '链接已过期!',
|
link_expire: '链接已过期!',
|
||||||
|
|||||||
@ -15,7 +15,8 @@
|
|||||||
<el-form-item label="" prop="password">
|
<el-form-item label="" prop="password">
|
||||||
<CustomPassword
|
<CustomPassword
|
||||||
v-model="form.password"
|
v-model="form.password"
|
||||||
maxlength="4"
|
maxlength="10"
|
||||||
|
minlength="4"
|
||||||
show-password
|
show-password
|
||||||
class="real-input"
|
class="real-input"
|
||||||
:placeholder="t('pblink.input_placeholder')"
|
:placeholder="t('pblink.input_placeholder')"
|
||||||
@ -64,7 +65,7 @@ const rule = reactive<FormRules>({
|
|||||||
{ required: true, message: t('pblink.key_pwd'), trigger: 'blur' },
|
{ required: true, message: t('pblink.key_pwd'), trigger: 'blur' },
|
||||||
{
|
{
|
||||||
required: true,
|
required: true,
|
||||||
pattern: /^[a-zA-Z0-9]{4}$/,
|
pattern: /^(?=.*[A-Za-z])(?=.*\d)(?=.*[!@#$%^&*()_+])[A-Za-z\d!@#$%^&*()_+]{4,10}$/,
|
||||||
message: t('pblink.pwd_format_error'),
|
message: t('pblink.pwd_format_error'),
|
||||||
trigger: 'blur'
|
trigger: 'blur'
|
||||||
}
|
}
|
||||||
|
|||||||
@ -20,6 +20,7 @@
|
|||||||
v-model="dialogVisible"
|
v-model="dialogVisible"
|
||||||
:close-on-click-modal="true"
|
:close-on-click-modal="true"
|
||||||
:append-to-body="true"
|
:append-to-body="true"
|
||||||
|
:before-close="beforeClose"
|
||||||
title="公共链接分享"
|
title="公共链接分享"
|
||||||
width="480px"
|
width="480px"
|
||||||
:show-close="false"
|
:show-close="false"
|
||||||
@ -62,13 +63,35 @@
|
|||||||
@change="pwdEnableSwitcher"
|
@change="pwdEnableSwitcher"
|
||||||
:label="t('visualization.passwd_protect')"
|
:label="t('visualization.passwd_protect')"
|
||||||
/>
|
/>
|
||||||
|
<div class="auto-pwd-container" v-if="passwdEnable">
|
||||||
<div class="inline-share-item" v-if="state.detailInfo.pwd">
|
<el-checkbox
|
||||||
<el-input v-model="state.detailInfo.pwd" readonly size="small">
|
:disabled="!shareEnable"
|
||||||
|
v-model="state.detailInfo.autoPwd"
|
||||||
|
@change="autoEnableSwitcher"
|
||||||
|
:label="t('visualization.auto_pwd')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="inline-share-item" v-if="passwdEnable">
|
||||||
|
<el-input
|
||||||
|
ref="pwdRef"
|
||||||
|
v-model="state.detailInfo.pwd"
|
||||||
|
:readonly="state.detailInfo.autoPwd"
|
||||||
|
size="small"
|
||||||
|
@blur="validatePwdFormat"
|
||||||
|
>
|
||||||
<template #append>
|
<template #append>
|
||||||
<div @click.stop="resetPwd" class="share-reset-container">
|
<div class="share-pwd-opt">
|
||||||
|
<div
|
||||||
|
v-if="state.detailInfo.autoPwd"
|
||||||
|
@click.stop="resetPwd"
|
||||||
|
class="share-reset-container"
|
||||||
|
>
|
||||||
<span>{{ t('commons.reset') }}</span>
|
<span>{{ t('commons.reset') }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div @click.stop="copyPwd" class="share-reset-container">
|
||||||
|
<span>{{ t('commons.copy') }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-input>
|
</el-input>
|
||||||
</div>
|
</div>
|
||||||
@ -87,7 +110,7 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { useI18n } from '@/hooks/web/useI18n'
|
import { useI18n } from '@/hooks/web/useI18n'
|
||||||
import { ref, reactive, onMounted, computed } from 'vue'
|
import { ref, reactive, onMounted, computed, nextTick } from 'vue'
|
||||||
import request from '@/config/axios'
|
import request from '@/config/axios'
|
||||||
import { propTypes } from '@/utils/propTypes'
|
import { propTypes } from '@/utils/propTypes'
|
||||||
import { ShareInfo, SHARE_BASE, shortcuts } from './option'
|
import { ShareInfo, SHARE_BASE, shortcuts } from './option'
|
||||||
@ -102,6 +125,7 @@ const props = defineProps({
|
|||||||
weight: propTypes.number.def(0),
|
weight: propTypes.number.def(0),
|
||||||
isButton: propTypes.bool.def(false)
|
isButton: propTypes.bool.def(false)
|
||||||
})
|
})
|
||||||
|
const pwdRef = ref(null)
|
||||||
const loadingInstance = ref<any>(null)
|
const loadingInstance = ref<any>(null)
|
||||||
const dialogVisible = ref(false)
|
const dialogVisible = ref(false)
|
||||||
const overTimeEnable = ref(false)
|
const overTimeEnable = ref(false)
|
||||||
@ -114,7 +138,8 @@ const state = reactive({
|
|||||||
id: '',
|
id: '',
|
||||||
uuid: '',
|
uuid: '',
|
||||||
pwd: '',
|
pwd: '',
|
||||||
exp: 0
|
exp: 0,
|
||||||
|
autoPwd: true
|
||||||
} as ShareInfo
|
} as ShareInfo
|
||||||
})
|
})
|
||||||
const emits = defineEmits(['loaded'])
|
const emits = defineEmits(['loaded'])
|
||||||
@ -122,7 +147,22 @@ const shareTips = computed(
|
|||||||
() =>
|
() =>
|
||||||
`开启后,用户可以通过该链接访问${props.resourceType === 'dashboard' ? '仪表板' : '数据大屏'}`
|
`开启后,用户可以通过该链接访问${props.resourceType === 'dashboard' ? '仪表板' : '数据大屏'}`
|
||||||
)
|
)
|
||||||
|
const copyPwd = async () => {
|
||||||
|
if (shareEnable.value && passwdEnable.value) {
|
||||||
|
if (!state.detailInfo.autoPwd && existErrorMsg()) {
|
||||||
|
ElMessage.warning('密码格式错误,请重新填写!')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await toClipboard(state.detailInfo.pwd)
|
||||||
|
ElMessage.success(t('common.copy_success'))
|
||||||
|
} catch (e) {
|
||||||
|
ElMessage.warning(t('common.copy_unsupported'))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ElMessage.warning(t('common.copy_unsupported'))
|
||||||
|
}
|
||||||
|
}
|
||||||
const copyInfo = async () => {
|
const copyInfo = async () => {
|
||||||
if (shareEnable.value) {
|
if (shareEnable.value) {
|
||||||
try {
|
try {
|
||||||
@ -220,22 +260,88 @@ const expChangeHandler = exp => {
|
|||||||
loadShareInfo()
|
loadShareInfo()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
const beforeClose = done => {
|
||||||
|
if (validatePwdFormat()) {
|
||||||
|
done()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const validatePwdFormat = () => {
|
||||||
|
if (!shareEnable.value || state.detailInfo.autoPwd) {
|
||||||
|
showPageError(null)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
const val = state.detailInfo.pwd
|
||||||
|
if (!val) {
|
||||||
|
showPageError('密码不能为空,请重新输入!')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
const regex = /^(?=.*[A-Za-z])(?=.*\d)(?=.*[!@#$%^&*()_+])[A-Za-z\d!@#$%^&*()_+]{4,10}$/
|
||||||
|
if (!regex.test(val)) {
|
||||||
|
showPageError('密码必须是包含数字、字母、特殊字符[!@#$%^&*()_+]的4-10位字符串')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
showPageError(null)
|
||||||
|
resetPwdHandler(val, false)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
const showPageError = msg => {
|
||||||
|
const domRef = pwdRef
|
||||||
|
if (!domRef.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const e = domRef.value.input
|
||||||
|
if (!msg) {
|
||||||
|
e.style = null
|
||||||
|
e.style.borderColor = null
|
||||||
|
const child = e.parentElement.querySelector('.link-pwd-error-msg')
|
||||||
|
if (child) {
|
||||||
|
e.parentElement['style'] = null
|
||||||
|
e.parentElement.removeChild(child)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
e.style.color = 'red'
|
||||||
|
e.style.borderColor = 'red'
|
||||||
|
e.parentElement['style']['box-shadow'] = '0 0 0 1px red inset'
|
||||||
|
const child = e.parentElement.querySelector('.link-pwd-error-msg')
|
||||||
|
if (!child) {
|
||||||
|
const errorDom = document.createElement('div')
|
||||||
|
errorDom.className = 'link-pwd-error-msg'
|
||||||
|
errorDom.innerText = msg
|
||||||
|
e.parentElement.appendChild(errorDom)
|
||||||
|
} else {
|
||||||
|
child.innerText = msg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const existErrorMsg = () => {
|
||||||
|
return document.getElementsByClassName('link-pwd-error-msg')?.length
|
||||||
|
}
|
||||||
|
const autoEnableSwitcher = val => {
|
||||||
|
if (val) {
|
||||||
|
showPageError(null)
|
||||||
|
resetPwd()
|
||||||
|
} else {
|
||||||
|
state.detailInfo.pwd = ''
|
||||||
|
nextTick(() => {
|
||||||
|
pwdRef.value.input.focus()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
const pwdEnableSwitcher = val => {
|
const pwdEnableSwitcher = val => {
|
||||||
let pwd = ''
|
let pwd = ''
|
||||||
if (val) {
|
if (val) {
|
||||||
pwd = getUuid()
|
pwd = getUuid()
|
||||||
}
|
}
|
||||||
resetPwdHandler(pwd)
|
resetPwdHandler(pwd, true)
|
||||||
}
|
}
|
||||||
const resetPwd = () => {
|
const resetPwd = () => {
|
||||||
const pwd = getUuid()
|
const pwd = getUuid()
|
||||||
resetPwdHandler(pwd)
|
resetPwdHandler(pwd, true)
|
||||||
}
|
}
|
||||||
const resetPwdHandler = (pwd?: string) => {
|
const resetPwdHandler = (pwd?: string, autoPwd?: boolean) => {
|
||||||
const resourceId = props.resourceId
|
const resourceId = props.resourceId
|
||||||
const url = '/share/editPwd'
|
const url = '/share/editPwd'
|
||||||
const data = { resourceId, pwd }
|
const data = { resourceId, pwd, autoPwd }
|
||||||
request.post({ url, data }).then(() => {
|
request.post({ url, data }).then(() => {
|
||||||
loadShareInfo()
|
loadShareInfo()
|
||||||
})
|
})
|
||||||
@ -314,6 +420,9 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.pwd-container {
|
.pwd-container {
|
||||||
|
.auto-pwd-container {
|
||||||
|
padding: 0 25px 6px;
|
||||||
|
}
|
||||||
.ed-checkbox {
|
.ed-checkbox {
|
||||||
margin-right: 10px;
|
margin-right: 10px;
|
||||||
}
|
}
|
||||||
@ -322,15 +431,20 @@ onMounted(() => {
|
|||||||
width: 220px;
|
width: 220px;
|
||||||
|
|
||||||
:deep(.ed-input-group__append) {
|
:deep(.ed-input-group__append) {
|
||||||
width: 45px !important;
|
width: initial !important;
|
||||||
background: none;
|
background: none;
|
||||||
color: #1f2329;
|
color: #1f2329;
|
||||||
padding: 0px 0px !important;
|
padding: 0px 0px !important;
|
||||||
|
.share-pwd-opt {
|
||||||
|
display: flex;
|
||||||
|
padding: 1px;
|
||||||
.share-reset-container {
|
.share-reset-container {
|
||||||
width: 100%;
|
&:not(:first-child) {
|
||||||
|
border-left: 1px solid var(--ed-input-border-color) !important;
|
||||||
|
}
|
||||||
|
width: 45px;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
|
||||||
&:hover {
|
&:hover {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
background-color: #f5f6f7;
|
background-color: #f5f6f7;
|
||||||
@ -342,6 +456,18 @@ onMounted(() => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
:deep(.link-pwd-error-msg) {
|
||||||
|
color: red;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 9;
|
||||||
|
font-size: 10px;
|
||||||
|
height: 10px;
|
||||||
|
top: 21px;
|
||||||
|
width: 350px;
|
||||||
|
left: 0px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.copy-link {
|
.copy-link {
|
||||||
font-weight: 400;
|
font-weight: 400;
|
||||||
|
|||||||
@ -1,22 +1,26 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-button secondary ref="shareButtonRef" v-if="props.weight >= 7" v-click-outside="openShare">
|
|
||||||
<template #icon>
|
|
||||||
<icon name="icon_share-label_outlined"></icon>
|
|
||||||
</template>
|
|
||||||
{{ t('visualization.share') }}
|
|
||||||
</el-button>
|
|
||||||
<el-popover
|
<el-popover
|
||||||
ref="sharePopoverRef"
|
:visible="popoverVisible"
|
||||||
:virtual-ref="shareButtonRef"
|
|
||||||
trigger="click"
|
|
||||||
title=""
|
title=""
|
||||||
virtual-triggering
|
|
||||||
width="480"
|
width="480"
|
||||||
placement="bottom-start"
|
placement="bottom-start"
|
||||||
:show-arrow="false"
|
:show-arrow="false"
|
||||||
popper-class="share-popover"
|
popper-class="share-popover"
|
||||||
@show="share"
|
@show="share"
|
||||||
>
|
>
|
||||||
|
<template #reference>
|
||||||
|
<el-button
|
||||||
|
secondary
|
||||||
|
v-if="props.weight >= 7"
|
||||||
|
@click="openPopover"
|
||||||
|
v-click-outside="clickOutPopover"
|
||||||
|
>
|
||||||
|
<template #icon>
|
||||||
|
<icon name="icon_share-label_outlined"></icon>
|
||||||
|
</template>
|
||||||
|
{{ t('visualization.share') }}
|
||||||
|
</el-button>
|
||||||
|
</template>
|
||||||
<div class="share-container">
|
<div class="share-container">
|
||||||
<div class="share-title share-padding">公共链接分享</div>
|
<div class="share-title share-padding">公共链接分享</div>
|
||||||
<div class="open-share flex-align-center share-padding">
|
<div class="open-share flex-align-center share-padding">
|
||||||
@ -58,12 +62,35 @@
|
|||||||
@change="pwdEnableSwitcher"
|
@change="pwdEnableSwitcher"
|
||||||
:label="t('visualization.passwd_protect')"
|
:label="t('visualization.passwd_protect')"
|
||||||
/>
|
/>
|
||||||
<div class="inline-share-item" v-if="state.detailInfo.pwd">
|
<div class="auto-pwd-container" v-if="passwdEnable">
|
||||||
<el-input v-model="state.detailInfo.pwd" readonly size="small">
|
<el-checkbox
|
||||||
|
:disabled="!shareEnable"
|
||||||
|
v-model="state.detailInfo.autoPwd"
|
||||||
|
@change="autoEnableSwitcher"
|
||||||
|
:label="t('visualization.auto_pwd')"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<div class="inline-share-item" v-if="passwdEnable">
|
||||||
|
<el-input
|
||||||
|
ref="pwdRef"
|
||||||
|
v-model="state.detailInfo.pwd"
|
||||||
|
:readonly="state.detailInfo.autoPwd"
|
||||||
|
size="small"
|
||||||
|
@blur="validatePwdFormat"
|
||||||
|
>
|
||||||
<template #append>
|
<template #append>
|
||||||
<div @click="resetPwd" class="share-reset-container">
|
<div class="share-pwd-opt">
|
||||||
|
<div
|
||||||
|
v-if="state.detailInfo.autoPwd"
|
||||||
|
@click.stop="resetPwd"
|
||||||
|
class="share-reset-container"
|
||||||
|
>
|
||||||
<span>{{ t('commons.reset') }}</span>
|
<span>{{ t('commons.reset') }}</span>
|
||||||
</div>
|
</div>
|
||||||
|
<div @click.stop="copyPwd" class="share-reset-container">
|
||||||
|
<span>{{ t('commons.copy') }}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</template>
|
</template>
|
||||||
</el-input>
|
</el-input>
|
||||||
</div>
|
</div>
|
||||||
@ -81,7 +108,7 @@
|
|||||||
|
|
||||||
<script lang="ts" setup>
|
<script lang="ts" setup>
|
||||||
import { useI18n } from '@/hooks/web/useI18n'
|
import { useI18n } from '@/hooks/web/useI18n'
|
||||||
import { ref, reactive, unref, computed } from 'vue'
|
import { ref, reactive, computed, nextTick, watch } from 'vue'
|
||||||
import request from '@/config/axios'
|
import request from '@/config/axios'
|
||||||
import { propTypes } from '@/utils/propTypes'
|
import { propTypes } from '@/utils/propTypes'
|
||||||
import { ShareInfo, SHARE_BASE, shortcuts } from './option'
|
import { ShareInfo, SHARE_BASE, shortcuts } from './option'
|
||||||
@ -94,11 +121,9 @@ const props = defineProps({
|
|||||||
resourceType: propTypes.string.def(''),
|
resourceType: propTypes.string.def(''),
|
||||||
weight: propTypes.number.def(0)
|
weight: propTypes.number.def(0)
|
||||||
})
|
})
|
||||||
const shareButtonRef = ref()
|
const popoverVisible = ref(false)
|
||||||
const sharePopoverRef = ref()
|
const pwdRef = ref(null)
|
||||||
|
|
||||||
const loadingInstance = ref<any>(null)
|
const loadingInstance = ref<any>(null)
|
||||||
const dialogVisible = ref(false)
|
|
||||||
const overTimeEnable = ref(false)
|
const overTimeEnable = ref(false)
|
||||||
const passwdEnable = ref(false)
|
const passwdEnable = ref(false)
|
||||||
const shareEnable = ref(false)
|
const shareEnable = ref(false)
|
||||||
@ -109,14 +134,34 @@ const state = reactive({
|
|||||||
id: '',
|
id: '',
|
||||||
uuid: '',
|
uuid: '',
|
||||||
pwd: '',
|
pwd: '',
|
||||||
exp: 0
|
exp: 0,
|
||||||
|
autoPwd: true
|
||||||
} as ShareInfo
|
} as ShareInfo
|
||||||
})
|
})
|
||||||
|
|
||||||
const openShare = () => {
|
watch(
|
||||||
unref(sharePopoverRef).popperRef?.delayHide?.()
|
() => props.resourceId,
|
||||||
|
() => {
|
||||||
|
popoverVisible.value = false
|
||||||
|
}
|
||||||
|
)
|
||||||
|
const hideShare = () => {
|
||||||
|
if (validatePwdFormat()) {
|
||||||
|
popoverVisible.value = false
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const clickOutPopover = e => {
|
||||||
|
if (!popoverVisible.value || e.target.closest('[class*="share-popover"]')) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
hideShare()
|
||||||
|
}
|
||||||
|
const openPopover = () => {
|
||||||
|
if (!popoverVisible.value) {
|
||||||
|
popoverVisible.value = true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const shareTips = computed(
|
const shareTips = computed(
|
||||||
() =>
|
() =>
|
||||||
`开启后,用户可以通过该链接访问${props.resourceType === 'dashboard' ? '仪表板' : '数据大屏'}`
|
`开启后,用户可以通过该链接访问${props.resourceType === 'dashboard' ? '仪表板' : '数据大屏'}`
|
||||||
@ -133,8 +178,7 @@ const copyInfo = async () => {
|
|||||||
} else {
|
} else {
|
||||||
ElMessage.warning(t('common.copy_unsupported'))
|
ElMessage.warning(t('common.copy_unsupported'))
|
||||||
}
|
}
|
||||||
dialogVisible.value = false
|
hideShare()
|
||||||
openShare()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const disabledDate = date => {
|
const disabledDate = date => {
|
||||||
@ -149,7 +193,6 @@ const closeLoading = () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const share = () => {
|
const share = () => {
|
||||||
dialogVisible.value = true
|
|
||||||
loadShareInfo()
|
loadShareInfo()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -230,28 +273,126 @@ const pwdEnableSwitcher = val => {
|
|||||||
if (val) {
|
if (val) {
|
||||||
pwd = getUuid()
|
pwd = getUuid()
|
||||||
}
|
}
|
||||||
resetPwdHandler(pwd)
|
resetPwdHandler(pwd, true)
|
||||||
}
|
}
|
||||||
const resetPwd = () => {
|
const resetPwd = () => {
|
||||||
const pwd = getUuid()
|
const pwd = getUuid()
|
||||||
resetPwdHandler(pwd)
|
resetPwdHandler(pwd, true)
|
||||||
}
|
}
|
||||||
const resetPwdHandler = (pwd?: string) => {
|
const resetPwdHandler = (pwd?: string, autoPwd?: boolean) => {
|
||||||
const resourceId = props.resourceId
|
const resourceId = props.resourceId
|
||||||
const url = '/share/editPwd'
|
const url = '/share/editPwd'
|
||||||
const data = { resourceId, pwd }
|
const data = { resourceId, pwd, autoPwd }
|
||||||
request.post({ url, data }).then(() => {
|
request.post({ url, data }).then(() => {
|
||||||
loadShareInfo()
|
loadShareInfo()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
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 validatePwdFormat = () => {
|
||||||
|
if (!shareEnable.value || !passwdEnable.value || state.detailInfo.autoPwd) {
|
||||||
|
showPageError(null)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
const val = state.detailInfo.pwd
|
||||||
|
if (!val) {
|
||||||
|
showPageError('密码不能为空,请重新输入!')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
const regex = /^(?=.*[A-Za-z])(?=.*\d)(?=.*[!@#$%^&*()_+])[A-Za-z\d!@#$%^&*()_+]{4,10}$/
|
||||||
|
if (!regex.test(val)) {
|
||||||
|
showPageError('密码必须是包含数字、字母、特殊字符[!@#$%^&*()_+]的4-10位字符串')
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
showPageError(null)
|
||||||
|
resetPwdHandler(val, false)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
const showPageError = msg => {
|
||||||
|
const domRef = pwdRef
|
||||||
|
if (!domRef.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const e = domRef.value.input
|
||||||
|
if (!msg) {
|
||||||
|
e.style = null
|
||||||
|
e.style.borderColor = null
|
||||||
|
const child = e.parentElement.querySelector('.link-pwd-error-msg')
|
||||||
|
if (child) {
|
||||||
|
e.parentElement['style'] = null
|
||||||
|
e.parentElement.removeChild(child)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
e.style.color = 'red'
|
||||||
|
e.style.borderColor = 'red'
|
||||||
|
e.parentElement['style']['box-shadow'] = '0 0 0 1px red inset'
|
||||||
|
const child = e.parentElement.querySelector('.link-pwd-error-msg')
|
||||||
|
if (!child) {
|
||||||
|
const errorDom = document.createElement('div')
|
||||||
|
errorDom.className = 'link-pwd-error-msg'
|
||||||
|
errorDom.innerText = msg
|
||||||
|
e.parentElement.appendChild(errorDom)
|
||||||
|
} else {
|
||||||
|
child.innerText = msg
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const existErrorMsg = () => {
|
||||||
|
return document.getElementsByClassName('link-pwd-error-msg')?.length
|
||||||
|
}
|
||||||
|
const autoEnableSwitcher = val => {
|
||||||
|
if (val) {
|
||||||
|
showPageError(null)
|
||||||
|
resetPwd()
|
||||||
|
} else {
|
||||||
|
state.detailInfo.pwd = ''
|
||||||
|
nextTick(() => {
|
||||||
|
pwdRef.value.input.focus()
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const copyPwd = async () => {
|
||||||
|
if (shareEnable.value && passwdEnable.value) {
|
||||||
|
if (!state.detailInfo.autoPwd && existErrorMsg()) {
|
||||||
|
ElMessage.warning('密码格式错误,请重新填写!')
|
||||||
|
return
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await toClipboard(state.detailInfo.pwd)
|
||||||
|
ElMessage.success(t('common.copy_success'))
|
||||||
|
} catch (e) {
|
||||||
|
ElMessage.warning(t('common.copy_unsupported'))
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ElMessage.warning(t('common.copy_unsupported'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const execute = () => {
|
const execute = () => {
|
||||||
share()
|
share()
|
||||||
@ -315,15 +456,21 @@ defineExpose({
|
|||||||
width: 220px;
|
width: 220px;
|
||||||
|
|
||||||
:deep(.ed-input-group__append) {
|
:deep(.ed-input-group__append) {
|
||||||
width: 45px !important;
|
width: initial !important;
|
||||||
background: none;
|
background: none;
|
||||||
color: #1f2329;
|
color: #1f2329;
|
||||||
padding: 0px 0px !important;
|
padding: 0px 0px !important;
|
||||||
|
|
||||||
|
.share-pwd-opt {
|
||||||
|
display: flex;
|
||||||
|
padding: 1px;
|
||||||
.share-reset-container {
|
.share-reset-container {
|
||||||
width: 100%;
|
&:not(:first-child) {
|
||||||
|
border-left: 1px solid var(--ed-input-border-color) !important;
|
||||||
|
}
|
||||||
|
width: 45px;
|
||||||
display: flex;
|
display: flex;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
}
|
|
||||||
&:hover {
|
&:hover {
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
background-color: #f5f6f7;
|
background-color: #f5f6f7;
|
||||||
@ -334,4 +481,16 @@ defineExpose({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
:deep(.link-pwd-error-msg) {
|
||||||
|
color: red;
|
||||||
|
position: absolute;
|
||||||
|
z-index: 9;
|
||||||
|
font-size: 10px;
|
||||||
|
height: 10px;
|
||||||
|
top: 21px;
|
||||||
|
width: 350px;
|
||||||
|
left: 0px;
|
||||||
|
}
|
||||||
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -3,6 +3,7 @@ export interface ShareInfo {
|
|||||||
exp?: number
|
exp?: number
|
||||||
uuid: string
|
uuid: string
|
||||||
pwd?: string
|
pwd?: string
|
||||||
|
autoPwd: boolean
|
||||||
}
|
}
|
||||||
|
|
||||||
export const SHARE_BASE = '/de-link/'
|
export const SHARE_BASE = '/de-link/'
|
||||||
|
|||||||
@ -20,4 +20,6 @@ public class XpackSharePwdRequest implements Serializable {
|
|||||||
|
|
||||||
@Schema(description = "密码")
|
@Schema(description = "密码")
|
||||||
private String pwd;
|
private String pwd;
|
||||||
|
@Schema(description = "自动生成密码")
|
||||||
|
private Boolean autoPwd = true;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -27,4 +27,6 @@ public class XpackShareVO implements Serializable {
|
|||||||
private String uuid;
|
private String uuid;
|
||||||
@Schema(description = "分享密码")
|
@Schema(description = "分享密码")
|
||||||
private String pwd;
|
private String pwd;
|
||||||
|
@Schema(description = "自动生成密码")
|
||||||
|
private Boolean autoPwd = true;
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user