forked from github/dataease
feat: 分享功能密码增强close #8593
This commit is contained in:
parent
9732a99ace
commit
657bbf55e6
@ -5,11 +5,11 @@ import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
*
|
||||
* 公共链接
|
||||
* </p>
|
||||
*
|
||||
* @author fit2cloud
|
||||
* @since 2023-09-22
|
||||
* @since 2024-04-07
|
||||
*/
|
||||
@TableName("xpack_share")
|
||||
public class XpackShare implements Serializable {
|
||||
@ -61,6 +61,11 @@ public class XpackShare implements Serializable {
|
||||
*/
|
||||
private Integer type;
|
||||
|
||||
/**
|
||||
* 自动生成密码
|
||||
*/
|
||||
private Boolean autoPwd;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
@ -133,6 +138,14 @@ public class XpackShare implements Serializable {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
public Boolean getAutoPwd() {
|
||||
return autoPwd;
|
||||
}
|
||||
|
||||
public void setAutoPwd(Boolean autoPwd) {
|
||||
this.autoPwd = autoPwd;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "XpackShare{" +
|
||||
@ -145,6 +158,7 @@ public class XpackShare implements Serializable {
|
||||
", resourceId = " + resourceId +
|
||||
", oid = " + oid +
|
||||
", type = " + type +
|
||||
", autoPwd = " + autoPwd +
|
||||
"}";
|
||||
}
|
||||
}
|
||||
|
@ -6,11 +6,11 @@ import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Mapper 接口
|
||||
* 公共链接 Mapper 接口
|
||||
* </p>
|
||||
*
|
||||
* @author fit2cloud
|
||||
* @since 2023-09-22
|
||||
* @since 2024-04-07
|
||||
*/
|
||||
@Mapper
|
||||
public interface XpackShareMapper extends BaseMapper<XpackShare> {
|
||||
|
@ -82,12 +82,13 @@ public class XpackShareManage {
|
||||
xpackShareMapper.updateById(originData);
|
||||
}
|
||||
|
||||
public void editPwd(Long resourceId, String pwd) {
|
||||
public void editPwd(Long resourceId, String pwd, Boolean autoPwd) {
|
||||
XpackShare originData = queryByResource(resourceId);
|
||||
if (ObjectUtils.isEmpty(originData)) {
|
||||
DEException.throwException("share instance not exist");
|
||||
}
|
||||
originData.setPwd(pwd);
|
||||
originData.setAutoPwd(ObjectUtils.isEmpty(autoPwd) || autoPwd);
|
||||
xpackShareMapper.updateById(originData);
|
||||
}
|
||||
|
||||
@ -169,8 +170,7 @@ public class XpackShareManage {
|
||||
if (StringUtils.isBlank(xpackShare.getPwd())) return true;
|
||||
if (StringUtils.isBlank(ciphertext)) return false;
|
||||
String text = RsaUtils.decryptStr(ciphertext);
|
||||
int len = text.length();
|
||||
int splitIndex = len - 4;
|
||||
int splitIndex = 8;
|
||||
String pwd = text.substring(splitIndex);
|
||||
String uuid = text.substring(0, splitIndex);
|
||||
return StringUtils.equals(xpackShare.getUuid(), uuid) && StringUtils.equals(xpackShare.getPwd(), pwd);
|
||||
@ -178,8 +178,7 @@ public class XpackShareManage {
|
||||
|
||||
public boolean validatePwd(XpackSharePwdValidator validator) {
|
||||
String ciphertext = RsaUtils.decryptStr(validator.getCiphertext());
|
||||
int len = ciphertext.length();
|
||||
int splitIndex = len - 4;
|
||||
int splitIndex = 8;
|
||||
String pwd = ciphertext.substring(splitIndex);
|
||||
String uuid = ciphertext.substring(0, splitIndex);
|
||||
QueryWrapper<XpackShare> queryWrapper = new QueryWrapper<>();
|
||||
|
@ -44,7 +44,7 @@ public class XpackShareServer implements XpackShareApi {
|
||||
|
||||
@Override
|
||||
public void editPwd(XpackSharePwdRequest request) {
|
||||
xpackShareManage.editPwd(request.getResourceId(), request.getPwd());
|
||||
xpackShareManage.editPwd(request.getResourceId(), request.getPwd(), request.getAutoPwd());
|
||||
}
|
||||
|
||||
@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 { vClickOutside } from './ClickOutside'
|
||||
import type { App } from 'vue'
|
||||
export const installDirective = (app: App<Element>) => {
|
||||
app.directive('permission', checkPermission)
|
||||
app.directive('click-outside', vClickOutside)
|
||||
}
|
||||
|
@ -1884,6 +1884,7 @@ export default {
|
||||
copy_short_link: '复制短链接',
|
||||
copy_short_link_passwd: '复制短链接及密码',
|
||||
passwd_protect: '密码保护',
|
||||
auto_pwd: '自动生成密码',
|
||||
link: '链接',
|
||||
over_time: '有效期',
|
||||
link_expire: '链接已过期!',
|
||||
|
@ -15,7 +15,8 @@
|
||||
<el-form-item label="" prop="password">
|
||||
<CustomPassword
|
||||
v-model="form.password"
|
||||
maxlength="4"
|
||||
maxlength="10"
|
||||
minlength="4"
|
||||
show-password
|
||||
class="real-input"
|
||||
:placeholder="t('pblink.input_placeholder')"
|
||||
@ -64,7 +65,7 @@ const rule = reactive<FormRules>({
|
||||
{ required: true, message: t('pblink.key_pwd'), trigger: 'blur' },
|
||||
{
|
||||
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'),
|
||||
trigger: 'blur'
|
||||
}
|
||||
|
@ -20,6 +20,7 @@
|
||||
v-model="dialogVisible"
|
||||
:close-on-click-modal="true"
|
||||
:append-to-body="true"
|
||||
:before-close="beforeClose"
|
||||
title="公共链接分享"
|
||||
width="480px"
|
||||
:show-close="false"
|
||||
@ -62,12 +63,34 @@
|
||||
@change="pwdEnableSwitcher"
|
||||
:label="t('visualization.passwd_protect')"
|
||||
/>
|
||||
|
||||
<div class="inline-share-item" v-if="state.detailInfo.pwd">
|
||||
<el-input v-model="state.detailInfo.pwd" readonly size="small">
|
||||
<div class="auto-pwd-container" v-if="passwdEnable">
|
||||
<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>
|
||||
<div @click.stop="resetPwd" class="share-reset-container">
|
||||
<span>{{ t('commons.reset') }}</span>
|
||||
<div class="share-pwd-opt">
|
||||
<div
|
||||
v-if="state.detailInfo.autoPwd"
|
||||
@click.stop="resetPwd"
|
||||
class="share-reset-container"
|
||||
>
|
||||
<span>{{ t('commons.reset') }}</span>
|
||||
</div>
|
||||
<div @click.stop="copyPwd" class="share-reset-container">
|
||||
<span>{{ t('commons.copy') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-input>
|
||||
@ -87,7 +110,7 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
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 { propTypes } from '@/utils/propTypes'
|
||||
import { ShareInfo, SHARE_BASE, shortcuts } from './option'
|
||||
@ -102,6 +125,7 @@ const props = defineProps({
|
||||
weight: propTypes.number.def(0),
|
||||
isButton: propTypes.bool.def(false)
|
||||
})
|
||||
const pwdRef = ref(null)
|
||||
const loadingInstance = ref<any>(null)
|
||||
const dialogVisible = ref(false)
|
||||
const overTimeEnable = ref(false)
|
||||
@ -114,7 +138,8 @@ const state = reactive({
|
||||
id: '',
|
||||
uuid: '',
|
||||
pwd: '',
|
||||
exp: 0
|
||||
exp: 0,
|
||||
autoPwd: true
|
||||
} as ShareInfo
|
||||
})
|
||||
const emits = defineEmits(['loaded'])
|
||||
@ -122,7 +147,22 @@ const shareTips = computed(
|
||||
() =>
|
||||
`开启后,用户可以通过该链接访问${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 () => {
|
||||
if (shareEnable.value) {
|
||||
try {
|
||||
@ -220,22 +260,88 @@ const expChangeHandler = exp => {
|
||||
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 => {
|
||||
let pwd = ''
|
||||
if (val) {
|
||||
pwd = getUuid()
|
||||
}
|
||||
resetPwdHandler(pwd)
|
||||
resetPwdHandler(pwd, true)
|
||||
}
|
||||
const resetPwd = () => {
|
||||
const pwd = getUuid()
|
||||
resetPwdHandler(pwd)
|
||||
resetPwdHandler(pwd, true)
|
||||
}
|
||||
const resetPwdHandler = (pwd?: string) => {
|
||||
const resetPwdHandler = (pwd?: string, autoPwd?: boolean) => {
|
||||
const resourceId = props.resourceId
|
||||
const url = '/share/editPwd'
|
||||
const data = { resourceId, pwd }
|
||||
const data = { resourceId, pwd, autoPwd }
|
||||
request.post({ url, data }).then(() => {
|
||||
loadShareInfo()
|
||||
})
|
||||
@ -314,6 +420,9 @@ onMounted(() => {
|
||||
}
|
||||
}
|
||||
.pwd-container {
|
||||
.auto-pwd-container {
|
||||
padding: 0 25px 6px;
|
||||
}
|
||||
.ed-checkbox {
|
||||
margin-right: 10px;
|
||||
}
|
||||
@ -322,24 +431,41 @@ onMounted(() => {
|
||||
width: 220px;
|
||||
|
||||
:deep(.ed-input-group__append) {
|
||||
width: 45px !important;
|
||||
width: initial !important;
|
||||
background: none;
|
||||
color: #1f2329;
|
||||
padding: 0px 0px !important;
|
||||
.share-reset-container {
|
||||
width: 100%;
|
||||
.share-pwd-opt {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
background-color: #f5f6f7;
|
||||
}
|
||||
&:active {
|
||||
cursor: pointer;
|
||||
background-color: #eff0f1;
|
||||
padding: 1px;
|
||||
.share-reset-container {
|
||||
&:not(:first-child) {
|
||||
border-left: 1px solid var(--ed-input-border-color) !important;
|
||||
}
|
||||
width: 45px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
background-color: #f5f6f7;
|
||||
}
|
||||
&:active {
|
||||
cursor: pointer;
|
||||
background-color: #eff0f1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
:deep(.link-pwd-error-msg) {
|
||||
color: red;
|
||||
position: absolute;
|
||||
z-index: 9;
|
||||
font-size: 10px;
|
||||
height: 10px;
|
||||
top: 21px;
|
||||
width: 350px;
|
||||
left: 0px;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,22 +1,26 @@
|
||||
<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
|
||||
ref="sharePopoverRef"
|
||||
:virtual-ref="shareButtonRef"
|
||||
trigger="click"
|
||||
:visible="popoverVisible"
|
||||
title=""
|
||||
virtual-triggering
|
||||
width="480"
|
||||
placement="bottom-start"
|
||||
:show-arrow="false"
|
||||
popper-class="share-popover"
|
||||
@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-title share-padding">公共链接分享</div>
|
||||
<div class="open-share flex-align-center share-padding">
|
||||
@ -58,11 +62,34 @@
|
||||
@change="pwdEnableSwitcher"
|
||||
:label="t('visualization.passwd_protect')"
|
||||
/>
|
||||
<div class="inline-share-item" v-if="state.detailInfo.pwd">
|
||||
<el-input v-model="state.detailInfo.pwd" readonly size="small">
|
||||
<div class="auto-pwd-container" v-if="passwdEnable">
|
||||
<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>
|
||||
<div @click="resetPwd" class="share-reset-container">
|
||||
<span>{{ t('commons.reset') }}</span>
|
||||
<div class="share-pwd-opt">
|
||||
<div
|
||||
v-if="state.detailInfo.autoPwd"
|
||||
@click.stop="resetPwd"
|
||||
class="share-reset-container"
|
||||
>
|
||||
<span>{{ t('commons.reset') }}</span>
|
||||
</div>
|
||||
<div @click.stop="copyPwd" class="share-reset-container">
|
||||
<span>{{ t('commons.copy') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-input>
|
||||
@ -81,7 +108,7 @@
|
||||
|
||||
<script lang="ts" setup>
|
||||
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 { propTypes } from '@/utils/propTypes'
|
||||
import { ShareInfo, SHARE_BASE, shortcuts } from './option'
|
||||
@ -94,11 +121,9 @@ const props = defineProps({
|
||||
resourceType: propTypes.string.def(''),
|
||||
weight: propTypes.number.def(0)
|
||||
})
|
||||
const shareButtonRef = ref()
|
||||
const sharePopoverRef = ref()
|
||||
|
||||
const popoverVisible = ref(false)
|
||||
const pwdRef = ref(null)
|
||||
const loadingInstance = ref<any>(null)
|
||||
const dialogVisible = ref(false)
|
||||
const overTimeEnable = ref(false)
|
||||
const passwdEnable = ref(false)
|
||||
const shareEnable = ref(false)
|
||||
@ -109,14 +134,34 @@ const state = reactive({
|
||||
id: '',
|
||||
uuid: '',
|
||||
pwd: '',
|
||||
exp: 0
|
||||
exp: 0,
|
||||
autoPwd: true
|
||||
} as ShareInfo
|
||||
})
|
||||
|
||||
const openShare = () => {
|
||||
unref(sharePopoverRef).popperRef?.delayHide?.()
|
||||
watch(
|
||||
() => 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(
|
||||
() =>
|
||||
`开启后,用户可以通过该链接访问${props.resourceType === 'dashboard' ? '仪表板' : '数据大屏'}`
|
||||
@ -133,8 +178,7 @@ const copyInfo = async () => {
|
||||
} else {
|
||||
ElMessage.warning(t('common.copy_unsupported'))
|
||||
}
|
||||
dialogVisible.value = false
|
||||
openShare()
|
||||
hideShare()
|
||||
}
|
||||
|
||||
const disabledDate = date => {
|
||||
@ -149,7 +193,6 @@ const closeLoading = () => {
|
||||
}
|
||||
|
||||
const share = () => {
|
||||
dialogVisible.value = true
|
||||
loadShareInfo()
|
||||
}
|
||||
|
||||
@ -230,27 +273,125 @@ const pwdEnableSwitcher = val => {
|
||||
if (val) {
|
||||
pwd = getUuid()
|
||||
}
|
||||
resetPwdHandler(pwd)
|
||||
resetPwdHandler(pwd, true)
|
||||
}
|
||||
const resetPwd = () => {
|
||||
const pwd = getUuid()
|
||||
resetPwdHandler(pwd)
|
||||
resetPwdHandler(pwd, true)
|
||||
}
|
||||
const resetPwdHandler = (pwd?: string) => {
|
||||
const resetPwdHandler = (pwd?: string, autoPwd?: boolean) => {
|
||||
const resourceId = props.resourceId
|
||||
const url = '/share/editPwd'
|
||||
const data = { resourceId, pwd }
|
||||
const data = { resourceId, pwd, autoPwd }
|
||||
request.post({ url, data }).then(() => {
|
||||
loadShareInfo()
|
||||
})
|
||||
}
|
||||
|
||||
const getUuid = () => {
|
||||
return 'xyxy'.replace(/[xy]/g, function (c) {
|
||||
var r = (Math.random() * 16) | 0,
|
||||
v = c == 'x' ? r : (r & 0x3) | 0x8
|
||||
return v.toString(16)
|
||||
})
|
||||
const length = 10
|
||||
const characters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789!@#$%^&*()_+'
|
||||
let result = ''
|
||||
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 = () => {
|
||||
@ -315,23 +456,41 @@ defineExpose({
|
||||
width: 220px;
|
||||
|
||||
:deep(.ed-input-group__append) {
|
||||
width: 45px !important;
|
||||
width: initial !important;
|
||||
background: none;
|
||||
color: #1f2329;
|
||||
padding: 0px 0px !important;
|
||||
.share-reset-container {
|
||||
width: 100%;
|
||||
|
||||
.share-pwd-opt {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
background-color: #f5f6f7;
|
||||
}
|
||||
&:active {
|
||||
cursor: pointer;
|
||||
background-color: #eff0f1;
|
||||
padding: 1px;
|
||||
.share-reset-container {
|
||||
&:not(:first-child) {
|
||||
border-left: 1px solid var(--ed-input-border-color) !important;
|
||||
}
|
||||
width: 45px;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
background-color: #f5f6f7;
|
||||
}
|
||||
&:active {
|
||||
cursor: pointer;
|
||||
background-color: #eff0f1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
:deep(.link-pwd-error-msg) {
|
||||
color: red;
|
||||
position: absolute;
|
||||
z-index: 9;
|
||||
font-size: 10px;
|
||||
height: 10px;
|
||||
top: 21px;
|
||||
width: 350px;
|
||||
left: 0px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -3,6 +3,7 @@ export interface ShareInfo {
|
||||
exp?: number
|
||||
uuid: string
|
||||
pwd?: string
|
||||
autoPwd: boolean
|
||||
}
|
||||
|
||||
export const SHARE_BASE = '/de-link/'
|
||||
|
@ -20,4 +20,6 @@ public class XpackSharePwdRequest implements Serializable {
|
||||
|
||||
@Schema(description = "密码")
|
||||
private String pwd;
|
||||
@Schema(description = "自动生成密码")
|
||||
private Boolean autoPwd = true;
|
||||
}
|
||||
|
@ -27,4 +27,6 @@ public class XpackShareVO implements Serializable {
|
||||
private String uuid;
|
||||
@Schema(description = "分享密码")
|
||||
private String pwd;
|
||||
@Schema(description = "自动生成密码")
|
||||
private Boolean autoPwd = true;
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user