mirror of
https://github.com/dataease/dataease.git
synced 2026-06-13 17:14:48 +08:00
perf(仪表板): 分享 Ticket增加分页机制
This commit is contained in:
@@ -2,6 +2,8 @@ package io.dataease.share.dao.ext.mapper;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import io.dataease.api.xpack.share.vo.TicketVO;
|
||||
import io.dataease.share.dao.auto.entity.CoreShareTicket;
|
||||
import io.dataease.share.dao.ext.po.XpackSharePO;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
@@ -32,4 +34,10 @@ public interface XpackShareExtMapper {
|
||||
|
||||
@Update("update core_share_ticket set uuid = #{ticketUuid} where uuid = #{originUuid}")
|
||||
void updateTicketUuid(@Param("originUuid") String originUuid, @Param("ticketUuid") String ticketUuid);
|
||||
|
||||
@Select("""
|
||||
select * from core_share_ticket
|
||||
${ew.customSqlSegment}
|
||||
""")
|
||||
IPage<CoreShareTicket> pager(IPage<TicketVO> page, @Param("ew") QueryWrapper<CoreShareTicket> ew);
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package io.dataease.share.manage;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import io.dataease.api.xpack.share.request.TicketCreator;
|
||||
import io.dataease.api.xpack.share.request.TicketDelRequest;
|
||||
import io.dataease.api.xpack.share.request.TicketSwitchRequest;
|
||||
@@ -15,15 +17,16 @@ import io.dataease.share.dao.auto.mapper.XpackShareMapper;
|
||||
import io.dataease.share.dao.ext.mapper.XpackShareExtMapper;
|
||||
import io.dataease.utils.AuthUtils;
|
||||
import io.dataease.utils.BeanUtils;
|
||||
import io.dataease.utils.CommonBeanFactory;
|
||||
import io.dataease.utils.IDUtils;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.apache.commons.collections4.CollectionUtils;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
|
||||
@Component
|
||||
public class ShareTicketManage {
|
||||
@@ -37,12 +40,14 @@ public class ShareTicketManage {
|
||||
@Resource
|
||||
private XpackShareExtMapper xpackShareExtMapper;
|
||||
|
||||
|
||||
public CoreShareTicket getByTicket(String ticket) {
|
||||
QueryWrapper<CoreShareTicket> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq("ticket", ticket);
|
||||
return coreShareTicketMapper.selectOne(queryWrapper);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public String saveTicket(TicketCreator creator) {
|
||||
String ticket = creator.getTicket();
|
||||
if (StringUtils.isNotBlank(ticket)) {
|
||||
@@ -51,6 +56,9 @@ public class ShareTicketManage {
|
||||
if (creator.isGenerateNew()) {
|
||||
ticketEntity.setAccessTime(null);
|
||||
ticketEntity.setTicket(CodingUtil.shortUuid());
|
||||
coreShareTicketMapper.deleteById(ticketEntity);
|
||||
coreShareTicketMapper.insert(ticketEntity);
|
||||
return ticketEntity.getTicket();
|
||||
}
|
||||
ticketEntity.setArgs(creator.getArgs());
|
||||
ticketEntity.setExp(creator.getExp());
|
||||
@@ -60,17 +68,23 @@ public class ShareTicketManage {
|
||||
return ticketEntity.getTicket();
|
||||
}
|
||||
}
|
||||
ticket = CodingUtil.shortUuid();
|
||||
if (StringUtils.isBlank(ticket)) {
|
||||
ticket = CodingUtil.shortUuid();
|
||||
}
|
||||
CoreShareTicket linkTicket = new CoreShareTicket();
|
||||
linkTicket.setId(IDUtils.snowID());
|
||||
linkTicket.setTicket(ticket);
|
||||
linkTicket.setArgs(creator.getArgs());
|
||||
linkTicket.setExp(creator.getExp());
|
||||
linkTicket.setUuid(creator.getUuid());
|
||||
coreShareTicketMapper.insert(linkTicket);
|
||||
Objects.requireNonNull(CommonBeanFactory.proxy(this.getClass())).saveDao(linkTicket);
|
||||
return ticket;
|
||||
}
|
||||
|
||||
public void saveDao(CoreShareTicket ticket) {
|
||||
coreShareTicketMapper.insert(ticket);
|
||||
}
|
||||
|
||||
public void deleteTicket(TicketDelRequest request) {
|
||||
String ticket = request.getTicket();
|
||||
if (StringUtils.isBlank(ticket)) {
|
||||
@@ -92,7 +106,7 @@ public class ShareTicketManage {
|
||||
xpackShareMapper.updateById(xpackShare);
|
||||
}
|
||||
|
||||
public List<TicketVO> query(Long resourceId) {
|
||||
public IPage<TicketVO> query(Long resourceId, Page<TicketVO> page) {
|
||||
QueryWrapper<XpackShare> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq("resource_id", resourceId);
|
||||
queryWrapper.eq("creator", AuthUtils.getUser().getUserId());
|
||||
@@ -102,9 +116,16 @@ public class ShareTicketManage {
|
||||
if (StringUtils.isBlank(uuid)) return null;
|
||||
QueryWrapper<CoreShareTicket> ticketQueryWrapper = new QueryWrapper<>();
|
||||
ticketQueryWrapper.eq("uuid", uuid);
|
||||
List<CoreShareTicket> coreShareTickets = coreShareTicketMapper.selectList(ticketQueryWrapper);
|
||||
if (CollectionUtils.isEmpty(coreShareTickets)) return null;
|
||||
return coreShareTickets.stream().map(item -> BeanUtils.copyBean(new TicketVO(), item)).toList();
|
||||
IPage<CoreShareTicket> pager = xpackShareExtMapper.pager(page, ticketQueryWrapper);
|
||||
List<CoreShareTicket> records = pager.getRecords();
|
||||
IPage<TicketVO> iPage = new Page<>();
|
||||
iPage.setPages(pager.getPages());
|
||||
iPage.setTotal(pager.getTotal());
|
||||
iPage.setCurrent(pager.getCurrent());
|
||||
iPage.setSize(pager.getSize());
|
||||
List<TicketVO> vos = records.stream().map(record -> BeanUtils.copyBean(new TicketVO(), record)).toList();
|
||||
iPage.setRecords(vos);
|
||||
return iPage;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
@@ -151,4 +172,14 @@ public class ShareTicketManage {
|
||||
vo.setTicketExp(time > expTime);
|
||||
return vo;
|
||||
}
|
||||
|
||||
public Integer getLimit() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
public long ticketCount(String uuid) {
|
||||
QueryWrapper<CoreShareTicket> ticketQueryWrapper = new QueryWrapper<>();
|
||||
ticketQueryWrapper.eq("uuid", uuid);
|
||||
return coreShareTicketMapper.selectCount(ticketQueryWrapper);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,15 +1,17 @@
|
||||
package io.dataease.share.server;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||
import io.dataease.api.xpack.share.ShareTicketApi;
|
||||
import io.dataease.api.xpack.share.request.TicketCreator;
|
||||
import io.dataease.api.xpack.share.request.TicketDelRequest;
|
||||
import io.dataease.api.xpack.share.request.TicketSwitchRequest;
|
||||
import io.dataease.api.xpack.share.vo.TicketVO;
|
||||
import io.dataease.commons.utils.CodingUtil;
|
||||
import io.dataease.share.manage.ShareTicketManage;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
import java.util.List;
|
||||
|
||||
@RestController
|
||||
@RequestMapping("/ticket")
|
||||
@@ -34,7 +36,18 @@ public class ShareTicketServer implements ShareTicketApi {
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TicketVO> query(Long resourceId) {
|
||||
return shareTicketManage.query(resourceId);
|
||||
public IPage<TicketVO> pager(Long resourceId, int goPage, int pageSize) {
|
||||
Page<TicketVO> page = new Page<>(goPage, pageSize);
|
||||
return shareTicketManage.query(resourceId, page);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String tempTicket() {
|
||||
return CodingUtil.shortUuid();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Integer limit() {
|
||||
return shareTicketManage.getLimit();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,7 +15,8 @@ const props = defineProps({
|
||||
tableData: propTypes.array,
|
||||
emptyDesc: propTypes.string,
|
||||
emptyImg: propTypes.string,
|
||||
border: propTypes.bool.def(false)
|
||||
border: propTypes.bool.def(false),
|
||||
showEmptyImg: propTypes.bool.def(true)
|
||||
})
|
||||
|
||||
const attrs = useAttrs()
|
||||
@@ -136,6 +137,7 @@ defineExpose({
|
||||
</table-body>
|
||||
<template #empty>
|
||||
<empty-background
|
||||
v-if="props.showEmptyImg"
|
||||
:description="props.emptyDesc ? props.emptyDesc : '暂无数据'"
|
||||
:img-type="imgType || 'noneWhite'"
|
||||
/>
|
||||
|
||||
@@ -3475,7 +3475,10 @@ Scatter chart (bubble) chart: {a} (series name), {b} (data name), {c} (value arr
|
||||
back: 'Return to the public link settings page',
|
||||
refresh: 'Refresh',
|
||||
time_tips:
|
||||
'Unit: minutes, range: [0-1440], 0 means no time limit, starting from the first access using the ticket'
|
||||
'Unit: minutes, range: [0-1440], 0 means no time limit, starting from the first access using the ticket',
|
||||
arg_val_tips: 'Please enter parameter values',
|
||||
arg_format_tips:
|
||||
'Please use JSON format string, example single valued argVal, multi valued [argVal1, argVal2]'
|
||||
},
|
||||
pblink: {
|
||||
key_pwd: 'Please enter the password to open the link',
|
||||
|
||||
@@ -3384,7 +3384,9 @@ export default {
|
||||
require: '必選',
|
||||
back: '返回公共連結設定頁',
|
||||
refresh: '刷新',
|
||||
time_tips: '單位: 分鐘,範圍: [0-1440],0代表無期限,自首次使用ticket訪問開始'
|
||||
time_tips: '單位: 分鐘,範圍: [0-1440],0代表無期限,自首次使用ticket訪問開始',
|
||||
arg_val_tips: '請輸入參數值',
|
||||
arg_format_tips: '請使用JSON格式字符串,示例單值argVal,多值[argVal1, argVal2]'
|
||||
},
|
||||
pblink: {
|
||||
key_pwd: '請輸入密碼開啟連結',
|
||||
|
||||
@@ -3387,7 +3387,9 @@ export default {
|
||||
require: '必选',
|
||||
back: '返回公共链接设置页面',
|
||||
refresh: '刷新',
|
||||
time_tips: '单位: 分钟,范围: [0-1440],0代表无期限,自首次使用ticket访问开始'
|
||||
time_tips: '单位: 分钟,范围: [0-1440],0代表无期限,自首次使用ticket访问开始',
|
||||
arg_val_tips: '请输入参数值',
|
||||
arg_format_tips: '请使用JSON格式字符串,示例单值argVal,多值[argVal1, argVal2]'
|
||||
},
|
||||
pblink: {
|
||||
key_pwd: '请输入密码打开链接',
|
||||
|
||||
115
core/core-frontend/src/views/share/share/CustomLinkPwd.vue
Normal file
115
core/core-frontend/src/views/share/share/CustomLinkPwd.vue
Normal file
@@ -0,0 +1,115 @@
|
||||
<template>
|
||||
<div class="link-pwd-dialog-container">
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
:title="t('user.change_password')"
|
||||
width="420"
|
||||
:append-to-body="true"
|
||||
:before-close="handleClose"
|
||||
>
|
||||
<div class="link-pwd-container">
|
||||
<el-form ref="pwdForm" :model="state.form" :rules="rule" label-position="top">
|
||||
<el-form-item :label="t('system.new_password')" prop="pwd">
|
||||
<el-input v-model="state.form.pwd" :placeholder="t('commons.input_password')" />
|
||||
<div class="tips ed-form-item__error">
|
||||
{{ t('work_branch.password_hint') }}
|
||||
</div>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
</div>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button secondary @click.stop="cancel">{{ t('common.cancel') }}</el-button>
|
||||
<el-button type="primary" @click.stop="save">
|
||||
{{ t('common.save') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref, reactive } from 'vue'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
const dialogVisible = ref(false)
|
||||
const pwdForm = ref()
|
||||
const originPwd = ref('')
|
||||
const state = reactive({
|
||||
form: reactive<any>({
|
||||
pwd: ''
|
||||
})
|
||||
})
|
||||
const rule = reactive<any>({
|
||||
pwd: [
|
||||
{ required: true, message: t('work_branch.password_null_hint'), trigger: 'blur' },
|
||||
{
|
||||
pattern: /^(?=.*[A-Za-z])(?=.*\d)(?=.*[!@#$%^&*()_+])[A-Za-z\d!@#$%^&*()_+]{4,10}$/,
|
||||
message: t('work_branch.password_hint'),
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
const handleClose = done => {
|
||||
state.form.pwd = ''
|
||||
originPwd.value = ''
|
||||
pwdForm.value.resetFields()
|
||||
done()
|
||||
}
|
||||
const cancel = () => {
|
||||
dialogVisible.value = false
|
||||
}
|
||||
const emits = defineEmits(['pwdChange'])
|
||||
const save = () => {
|
||||
const formEl = pwdForm.value
|
||||
if (!formEl) return
|
||||
formEl.validate(valid => {
|
||||
if (valid) {
|
||||
if (originPwd.value !== state.form.pwd) {
|
||||
emits('pwdChange', state.form.pwd)
|
||||
}
|
||||
cancel()
|
||||
}
|
||||
})
|
||||
}
|
||||
const open = (pwd: string) => {
|
||||
state.form.pwd = pwd
|
||||
originPwd.value = pwd
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
open
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.link-pwd-container {
|
||||
width: 100%;
|
||||
:deep(.ed-form-item) {
|
||||
margin-bottom: 2px;
|
||||
height: 108px;
|
||||
}
|
||||
:deep(.ed-form-item__label) {
|
||||
line-height: 22px !important;
|
||||
height: 22px;
|
||||
}
|
||||
:deep(.ed-form-item__error:not(.tips)) {
|
||||
display: none;
|
||||
}
|
||||
.tips {
|
||||
color: #8f959e;
|
||||
line-height: 22px;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
}
|
||||
:deep(.is-error) {
|
||||
.tips {
|
||||
color: red !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -17,8 +17,7 @@
|
||||
v-if="dialogVisible && props.weight >= 7"
|
||||
class="copy-link_dialog"
|
||||
:class="{
|
||||
'hidden-footer': !shareEnable || showTicket,
|
||||
'is-ticket-dialog': shareEnable && showTicket
|
||||
'hidden-footer': !shareEnable
|
||||
}"
|
||||
v-model="dialogVisible"
|
||||
:close-on-click-modal="true"
|
||||
@@ -28,7 +27,7 @@
|
||||
width="480px"
|
||||
:show-close="false"
|
||||
>
|
||||
<div class="share-dialog-container" :class="{ 'hidden-link-container': showTicket }">
|
||||
<div class="share-dialog-container">
|
||||
<div class="copy-link">
|
||||
<div class="open-share flex-align-center">
|
||||
<el-switch size="small" v-model="shareEnable" @change="enableSwitcher" />
|
||||
@@ -36,24 +35,53 @@
|
||||
</div>
|
||||
<div v-if="shareEnable" class="custom-link-line">
|
||||
<el-input
|
||||
:class="!linkCustom ? 'link-input-readlonly' : ''"
|
||||
ref="linkUuidRef"
|
||||
placeholder=""
|
||||
v-model="state.detailInfo.uuid"
|
||||
:disabled="!linkCustom"
|
||||
@blur="finishEditUuid"
|
||||
:readonly="!linkCustom"
|
||||
@blur="validateUuid"
|
||||
>
|
||||
<template v-if="!linkCustom" #prefix>
|
||||
{{ formatLinkBase() }}
|
||||
</template>
|
||||
</el-input>
|
||||
<el-button v-if="linkCustom" text @click="finishEditUuid">{{
|
||||
t('components.complete')
|
||||
}}</el-button>
|
||||
<el-button v-else @click="editUuid" size="default" plain>
|
||||
<template #icon>
|
||||
<icon name="icon_admin_outlined"><icon_admin_outlined class="svg-icon" /></icon>
|
||||
|
||||
<template #suffix>
|
||||
<div class="share-input-suffix">
|
||||
<span class="suffix-split" />
|
||||
<div class="input-suffix-btn" v-if="!linkCustom" @click="editUuid">
|
||||
<el-tooltip
|
||||
class="item"
|
||||
effect="dark"
|
||||
:content="t('commons.edit') + t('chart.indicator_suffix')"
|
||||
placement="top"
|
||||
>
|
||||
<Icon name="icon_edit_outlined"><icon_edit_outlined class="svg-icon" /></Icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<div class="input-suffix-btn" v-if="linkCustom" @click.stop="resetUuid">
|
||||
<el-tooltip
|
||||
class="item"
|
||||
effect="dark"
|
||||
:content="t('commons.cancel')"
|
||||
placement="top"
|
||||
>
|
||||
<Icon name="icon_close_outlined"><icon_close_outlined class="svg-icon" /></Icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<div class="input-suffix-btn done" v-if="linkCustom" @click="finishEditUuid">
|
||||
<el-tooltip
|
||||
class="item"
|
||||
effect="dark"
|
||||
:content="t('commons.save')"
|
||||
placement="top"
|
||||
>
|
||||
<Icon name="icon_done_outlined"><icon_done_outlined class="svg-icon" /></Icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-button>
|
||||
</el-input>
|
||||
</div>
|
||||
|
||||
<div v-if="shareEnable" class="exp-container">
|
||||
@@ -73,7 +101,6 @@
|
||||
<div class="inline-share-item-picker">
|
||||
<el-date-picker
|
||||
:clearable="false"
|
||||
size="small"
|
||||
v-if="state.detailInfo.exp"
|
||||
class="share-exp-picker"
|
||||
v-model="state.detailInfo.exp"
|
||||
@@ -112,39 +139,44 @@
|
||||
<div class="inline-share-item" v-if="passwdEnable">
|
||||
<el-input
|
||||
ref="pwdRef"
|
||||
class="link-input-readlonly"
|
||||
v-model="state.detailInfo.pwd"
|
||||
:readonly="state.detailInfo.autoPwd"
|
||||
size="small"
|
||||
@blur="validatePwdFormat"
|
||||
readonly
|
||||
>
|
||||
<template #append>
|
||||
<div class="share-pwd-opt">
|
||||
<div
|
||||
v-if="state.detailInfo.autoPwd"
|
||||
@click.stop="resetPwd"
|
||||
class="share-reset-container"
|
||||
>
|
||||
<span>{{ t('commons.reset') }}</span>
|
||||
<template #suffix>
|
||||
<div class="share-input-suffix">
|
||||
<span class="suffix-split" />
|
||||
<div class="input-suffix-btn" @click="copyPwd">
|
||||
<el-tooltip
|
||||
class="item"
|
||||
effect="dark"
|
||||
:content="t('commons.copy')"
|
||||
placement="top"
|
||||
>
|
||||
<Icon name="de-copy"><deCopy class="svg-icon" /></Icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<div @click.stop="copyPwd" class="share-reset-container">
|
||||
<span>{{ t('commons.copy') }}</span>
|
||||
<div class="input-suffix-btn" @click="resetPwd">
|
||||
<el-tooltip
|
||||
class="item"
|
||||
effect="dark"
|
||||
:content="t('commons.reset')"
|
||||
placement="top"
|
||||
>
|
||||
<Icon name="icon_refresh_outlined"
|
||||
><icon_refresh_outlined class="svg-icon"
|
||||
/></Icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-input>
|
||||
|
||||
<el-button secondary @click="openPwdDialog">{{ t('user.change_password') }}</el-button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="shareEnable && showTicket" class="share-ticket-container">
|
||||
<share-ticket
|
||||
:uuid="state.detailInfo.uuid"
|
||||
:resource-id="props.resourceId"
|
||||
:ticket-require="state.detailInfo.ticketRequire"
|
||||
@require-change="updateRequireTicket"
|
||||
@close="closeTicket"
|
||||
/>
|
||||
</div>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button secondary @click="openTicket">{{ t('work_branch.ticket_setting') }}</el-button>
|
||||
@@ -154,12 +186,28 @@
|
||||
</span>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<custom-link-pwd ref="customPwdRef" @pwd-change="customPwdChange" />
|
||||
<ticket-dialog ref="ticketDialogRef">
|
||||
<div v-if="shareEnable && showTicket">
|
||||
<share-ticket
|
||||
:uuid="state.detailInfo.uuid"
|
||||
:resource-id="props.resourceId"
|
||||
:ticket-require="state.detailInfo.ticketRequire"
|
||||
@require-change="updateRequireTicket"
|
||||
@close="closeTicket"
|
||||
/>
|
||||
</div>
|
||||
</ticket-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import dvShare from '@/assets/svg/dv-share.svg'
|
||||
import icon_shareLabel_outlined from '@/assets/svg/icon_share-label_outlined.svg'
|
||||
import icon_admin_outlined from '@/assets/svg/icon_admin_outlined.svg'
|
||||
import deCopy from '@/assets/svg/de-copy.svg'
|
||||
import icon_refresh_outlined from '@/assets/svg/icon_refresh_outlined.svg'
|
||||
import icon_edit_outlined from '@/assets/svg/icon_edit_outlined.svg'
|
||||
import icon_close_outlined from '@/assets/svg/icon_close_outlined.svg'
|
||||
import icon_done_outlined from '@/assets/svg/icon_done_outlined.svg'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { ref, reactive, onMounted, computed, nextTick } from 'vue'
|
||||
import request from '@/config/axios'
|
||||
@@ -170,6 +218,9 @@ import useClipboard from 'vue-clipboard3'
|
||||
import ShareTicket from './ShareTicket.vue'
|
||||
import { useEmbedded } from '@/store/modules/embedded'
|
||||
import { useShareStoreWithOut } from '@/store/modules/share'
|
||||
import CustomLinkPwd from './CustomLinkPwd.vue'
|
||||
import TicketDialog from './TicketDialog.vue'
|
||||
|
||||
const shareStore = useShareStoreWithOut()
|
||||
const embeddedStore = useEmbedded()
|
||||
const { toClipboard } = useClipboard()
|
||||
@@ -181,6 +232,9 @@ const props = defineProps({
|
||||
weight: propTypes.number.def(0),
|
||||
isButton: propTypes.bool.def(false)
|
||||
})
|
||||
const originUuid = ref('')
|
||||
const customPwdRef = ref()
|
||||
const ticketDialogRef = ref()
|
||||
const pwdRef = ref(null)
|
||||
const expCheckbox = ref()
|
||||
const pwdCheckbox = ref()
|
||||
@@ -251,6 +305,11 @@ const finishEditUuid = async () => {
|
||||
const uuidValid = await validateUuid()
|
||||
linkCustom.value = !uuidValid
|
||||
}
|
||||
const resetUuid = event => {
|
||||
event.stopPropagation()
|
||||
state.detailInfo.uuid = originUuid.value
|
||||
finishEditUuid()
|
||||
}
|
||||
const copyPwd = async () => {
|
||||
if (shareEnable.value && passwdEnable.value) {
|
||||
if (!state.detailInfo.autoPwd && existErrorMsg('link-pwd-error-msg')) {
|
||||
@@ -320,6 +379,7 @@ const loadShareInfo = cb => {
|
||||
.get({ url })
|
||||
.then(res => {
|
||||
state.detailInfo = { ...res.data }
|
||||
originUuid.value = res.data.uuid
|
||||
setPageInfo()
|
||||
})
|
||||
.finally(() => {
|
||||
@@ -400,9 +460,8 @@ const beforeClose = async done => {
|
||||
return
|
||||
}
|
||||
}
|
||||
const pwdValid = validatePwdFormat()
|
||||
const uuidValid = await validateUuid()
|
||||
if (pwdValid && uuidValid) {
|
||||
if (uuidValid) {
|
||||
showTicket.value = false
|
||||
done()
|
||||
}
|
||||
@@ -433,26 +492,6 @@ const validatePwdRequire = () => {
|
||||
showCheckboxError(t('common.required'), pwdCheckbox)
|
||||
return false
|
||||
}
|
||||
const validatePwdFormat = () => {
|
||||
if (!shareEnable.value || state.detailInfo.autoPwd) {
|
||||
showPageError(null, pwdRef)
|
||||
return true
|
||||
}
|
||||
const val = state.detailInfo.pwd
|
||||
if (!val) {
|
||||
showPageError(t('work_branch.password_null_hint'), pwdRef)
|
||||
return false
|
||||
}
|
||||
const regex = /^(?=.*[A-Za-z])(?=.*\d)(?=.*[!@#$%^&*()_+])[A-Za-z\d!@#$%^&*()_+]{4,10}$/
|
||||
const regep = new RegExp(regex)
|
||||
if (!regep.test(val)) {
|
||||
showPageError(t('work_branch.password_hint'), pwdRef)
|
||||
return false
|
||||
}
|
||||
showPageError(null, pwdRef)
|
||||
resetPwdHandler(val, false)
|
||||
return true
|
||||
}
|
||||
const showCheckboxError = (msg, target, className?: string) => {
|
||||
if (!target.value) {
|
||||
return
|
||||
@@ -575,8 +614,10 @@ const getUuid = () => {
|
||||
|
||||
const openTicket = () => {
|
||||
showTicket.value = true
|
||||
ticketDialogRef.value.open()
|
||||
}
|
||||
const closeTicket = () => {
|
||||
ticketDialogRef.value.close()
|
||||
showTicket.value = false
|
||||
}
|
||||
const updateRequireTicket = val => {
|
||||
@@ -586,6 +627,15 @@ const updateRequireTicket = val => {
|
||||
const execute = () => {
|
||||
share()
|
||||
}
|
||||
|
||||
const openPwdDialog = () => {
|
||||
customPwdRef.value.open(state.detailInfo.pwd)
|
||||
}
|
||||
const customPwdChange = val => {
|
||||
state.detailInfo.pwd = val
|
||||
resetPwdHandler(val, false)
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
execute
|
||||
})
|
||||
@@ -660,45 +710,10 @@ onMounted(() => {
|
||||
margin-right: 10px;
|
||||
}
|
||||
.inline-share-item {
|
||||
display: inline-flex;
|
||||
column-gap: 12px;
|
||||
margin-left: 25px;
|
||||
width: 220px;
|
||||
|
||||
:deep(.ed-input-group__append) {
|
||||
width: initial !important;
|
||||
background: none;
|
||||
color: #1f2329;
|
||||
padding: 0px 0px !important;
|
||||
.share-pwd-opt {
|
||||
display: flex;
|
||||
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;
|
||||
}
|
||||
width: 332px;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -737,9 +752,6 @@ onMounted(() => {
|
||||
}
|
||||
}
|
||||
}
|
||||
.hidden-link-container {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
:deep(.checkbox-span) {
|
||||
display: flex;
|
||||
@@ -757,4 +769,52 @@ onMounted(() => {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
|
||||
.share-input-suffix {
|
||||
display: flex;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
column-gap: 4px;
|
||||
align-items: center;
|
||||
.suffix-split {
|
||||
height: 30px;
|
||||
width: 1px;
|
||||
display: inline-block;
|
||||
background-color: #bbbfc4;
|
||||
margin-right: 4px;
|
||||
}
|
||||
.done {
|
||||
color: #3370ff;
|
||||
&:hover {
|
||||
background-color: #3370ff1a !important;
|
||||
}
|
||||
}
|
||||
.input-suffix-btn {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
&:hover {
|
||||
background-color: #1f23291a;
|
||||
}
|
||||
svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.link-input-readlonly {
|
||||
:deep(.ed-input__wrapper) {
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
color: #8f959e;
|
||||
&:hover {
|
||||
box-shadow: 0 0 0 1px var(--ed-input-border-color, var(--ed-border-color)) inset;
|
||||
}
|
||||
input {
|
||||
color: #646a73;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -2,15 +2,6 @@
|
||||
<div class="ticket">
|
||||
<div class="ticket-model">
|
||||
<div class="ticket-model-start">
|
||||
<el-tooltip class="item" effect="dark" :content="$t('link_ticket.back')" placement="top">
|
||||
<span class="back-tips">
|
||||
<el-icon class="custom-el-icon back-icon" @click.stop="close">
|
||||
<Icon class="toolbar-icon" name="icon_left_outlined"
|
||||
><icon_left_outlined class="svg-icon toolbar-icon"
|
||||
/></Icon>
|
||||
</el-icon>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
<span class="ticket-title">{{ 'Ticket ' + t('commons.setting') }}</span>
|
||||
</div>
|
||||
<div class="ticket-model-end">
|
||||
@@ -19,6 +10,11 @@
|
||||
@change="requireTicketChange"
|
||||
:label="t('link_ticket.require')"
|
||||
/>
|
||||
|
||||
<span class="top-split" />
|
||||
<span class="top-close" @click.stop="finish">
|
||||
<icon name="icon_close_outlined"><icon_close_outlined class="svg-icon" /></icon>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ticket-add">
|
||||
@@ -26,12 +22,20 @@
|
||||
<template #icon>
|
||||
<icon name="icon_add_outlined"><icon_add_outlined class="svg-icon" /></icon>
|
||||
</template>
|
||||
{{ t('commons.create') }}
|
||||
{{ t('commons.add') }}
|
||||
</el-button>
|
||||
</div>
|
||||
<div class="ticket-table">
|
||||
<el-table :data="state.tableData" style="width: 100%" size="small">
|
||||
<el-table-column prop="ticket" label="Ticket" width="130">
|
||||
<grid-table
|
||||
ref="multipleTableRef"
|
||||
:show-empty-img="false"
|
||||
:table-data="state.tableData"
|
||||
:pagination="state.paginationConfig"
|
||||
class="popper-max-width"
|
||||
@current-change="pageChange"
|
||||
@size-change="sizeChange"
|
||||
>
|
||||
<el-table-column prop="ticket" label="Ticket">
|
||||
<template v-slot="scope">
|
||||
<div class="ticket-row">
|
||||
<span :title="scope.row.ticket">{{ scope.row.ticket }}</span>
|
||||
@@ -60,7 +64,7 @@
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="exp" :label="$t('visualization.over_time')" width="100">
|
||||
<el-table-column prop="exp" :label="$t('visualization.over_time')" width="117">
|
||||
<template v-slot:header>
|
||||
<div class="ticket-exp-head">
|
||||
<span>{{ t('visualization.over_time') }}</span>
|
||||
@@ -77,43 +81,31 @@
|
||||
</div>
|
||||
</template>
|
||||
<template v-slot="scope">
|
||||
<el-input
|
||||
v-if="scope.row.isEdit"
|
||||
:ref="el => setExpRef(el, scope.$index)"
|
||||
v-model="scope.row.exp"
|
||||
type="number"
|
||||
:placeholder="$t('commons.input_content')"
|
||||
min="0"
|
||||
max="1440"
|
||||
size="small"
|
||||
@input="v => handleInput(v, scope.$index)"
|
||||
@change="val => validateExp(val, scope.$index)"
|
||||
/>
|
||||
<span v-else>
|
||||
<span>
|
||||
{{ scope.row.exp }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="args" :label="$t('dataset.param')">
|
||||
<el-table-column prop="args" :label="$t('dataset.param')" width="117">
|
||||
<template v-slot="scope">
|
||||
<el-input
|
||||
v-if="scope.row.isEdit"
|
||||
:ref="el => setArgRef(el, scope.$index)"
|
||||
v-model="scope.row.args"
|
||||
type="text"
|
||||
:placeholder="$t('commons.input_content')"
|
||||
maxlength="200"
|
||||
size="small"
|
||||
@change="val => validateArgs(val, scope.$index)"
|
||||
/>
|
||||
<span v-else>
|
||||
{{ scope.row.args || '-' }}
|
||||
</span>
|
||||
<el-tooltip class="box-item" effect="light" :content="scope.row.args" placement="top">
|
||||
<span style="color: #3370ff">
|
||||
{{ getArgCount(scope.row) }}
|
||||
</span>
|
||||
</el-tooltip>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('commons.operating')" width="80">
|
||||
<template v-slot="scope">
|
||||
<div class="ticket-row">
|
||||
<el-tooltip class="item" effect="dark" :content="$t('commons.edit')" placement="top">
|
||||
<el-button text @click.stop="editRow(scope.row)">
|
||||
<template #icon>
|
||||
<Icon name="icon_edit_outlined"><icon_edit_outlined class="svg-icon" /></Icon>
|
||||
</template>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
|
||||
<el-tooltip class="item" effect="dark" :content="t('commons.delete')" placement="top">
|
||||
<el-button text @click.stop="deleteTicket(scope.row, scope.$index)">
|
||||
<template #icon>
|
||||
@@ -123,43 +115,23 @@
|
||||
</template>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip
|
||||
class="item"
|
||||
effect="dark"
|
||||
:content="scope.row.isEdit ? $t('commons.save') : $t('commons.edit')"
|
||||
placement="top"
|
||||
>
|
||||
<el-button v-if="!scope.row.isEdit" text @click.stop="editRow(scope.row)">
|
||||
<template #icon>
|
||||
<Icon name="icon_edit_outlined"><icon_edit_outlined class="svg-icon" /></Icon>
|
||||
</template>
|
||||
</el-button>
|
||||
<el-button v-else text @click.stop="saveRow(scope.row, scope.$index)">
|
||||
<template #icon>
|
||||
<Icon name="edit-done"><editDone class="svg-icon" /></Icon>
|
||||
</template>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<div class="ticket-btn">
|
||||
<el-button type="primary" @click.stop="finish"> {{ $t('components.complete') }} </el-button>
|
||||
</grid-table>
|
||||
</div>
|
||||
</div>
|
||||
<ticket-edit ref="ticketEditor" :uuid="props.uuid" @saved="loadTicketData" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import icon_left_outlined from '@/assets/svg/icon_left_outlined.svg'
|
||||
import icon_close_outlined from '@/assets/svg/icon_close_outlined.svg'
|
||||
import deCopy from '@/assets/svg/de-copy.svg'
|
||||
import icon_refresh_outlined from '@/assets/svg/icon_refresh_outlined.svg'
|
||||
import dvInfo from '@/assets/svg/dv-info.svg'
|
||||
import icon_deleteTrash_outlined from '@/assets/svg/icon_delete-trash_outlined.svg'
|
||||
import icon_edit_outlined from '@/assets/svg/icon_edit_outlined.svg'
|
||||
import editDone from '@/assets/svg/edit-done.svg'
|
||||
|
||||
import icon_add_outlined from '@/assets/svg/icon_add_outlined.svg'
|
||||
import { ref, reactive, onMounted, toRefs } from 'vue'
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
@@ -169,7 +141,8 @@ import { ElMessage, ElMessageBox } from 'element-plus-secondary'
|
||||
import useClipboard from 'vue-clipboard3'
|
||||
import { useEmbedded } from '@/store/modules/embedded'
|
||||
import { SHARE_BASE } from './option'
|
||||
|
||||
import GridTable from '@/components/grid-table/src/GridTable.vue'
|
||||
import TicketEdit from './TicketEdit.vue'
|
||||
const embeddedStore = useEmbedded()
|
||||
const { toClipboard } = useClipboard()
|
||||
const { t } = useI18n()
|
||||
@@ -178,13 +151,16 @@ const props = defineProps({
|
||||
resourceId: propTypes.string.def(null),
|
||||
ticketRequire: propTypes.bool
|
||||
})
|
||||
|
||||
const ticketEditor = ref()
|
||||
const { ticketRequire } = toRefs(props)
|
||||
const expRefs = ref({})
|
||||
const argRefs = ref({})
|
||||
|
||||
const state = reactive({
|
||||
tableData: []
|
||||
tableData: [],
|
||||
paginationConfig: {
|
||||
currentPage: 1,
|
||||
pageSize: 10,
|
||||
total: 0
|
||||
}
|
||||
})
|
||||
const emits = defineEmits(['close', 'requireChange'])
|
||||
|
||||
@@ -192,18 +168,6 @@ const close = () => {
|
||||
emits('close')
|
||||
}
|
||||
|
||||
const setExpRef = (el, index) => {
|
||||
if (el) {
|
||||
expRefs.value[index] = el
|
||||
}
|
||||
}
|
||||
|
||||
const setArgRef = (el, index) => {
|
||||
if (el) {
|
||||
argRefs.value[index] = el
|
||||
}
|
||||
}
|
||||
|
||||
const requireTicketChange = val => {
|
||||
const url = '/ticket/enableTicket'
|
||||
const data = {
|
||||
@@ -231,22 +195,21 @@ const createLimit = (count?: number) => {
|
||||
}
|
||||
return true
|
||||
}
|
||||
const getArgCount = row => {
|
||||
const args = row.args
|
||||
if (!args) {
|
||||
return 0
|
||||
}
|
||||
try {
|
||||
const obj = JSON.parse(args)
|
||||
return Object.keys(obj).length
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
return 0
|
||||
}
|
||||
}
|
||||
const addRow = () => {
|
||||
if (!createLimit()) {
|
||||
return
|
||||
}
|
||||
const row = {
|
||||
ticket: '',
|
||||
exp: 30,
|
||||
args: '',
|
||||
uuid: props.uuid
|
||||
}
|
||||
const url = '/ticket/saveTicket'
|
||||
request.post({ url, data: row }).then(res => {
|
||||
row.ticket = res.data
|
||||
row['isEdit'] = false
|
||||
state.tableData.splice(0, 0, row)
|
||||
})
|
||||
ticketEditor.value.edit(null, formatLinkAddr())
|
||||
}
|
||||
const formatLinkAddr = () => {
|
||||
return formatLinkBase() + props.uuid
|
||||
@@ -282,64 +245,6 @@ const refreshTicket = row => {
|
||||
row.ticket = res.data
|
||||
})
|
||||
}
|
||||
const handleInput = (val, index) => {
|
||||
if (val === null || val === '') {
|
||||
return
|
||||
}
|
||||
state.tableData[index]['exp'] = val.replace(/[^\d]/g, '')
|
||||
}
|
||||
const validateExp = (val, index) => {
|
||||
const cref = expRefs.value[index]
|
||||
const e = cref.input
|
||||
if (val === null || val === '' || typeof val === 'undefined') {
|
||||
state.tableData[index]['exp'] = 0
|
||||
return true
|
||||
}
|
||||
if (val > 1440 || val < 0) {
|
||||
e.style.color = 'red'
|
||||
e.parentNode.setAttribute('style', 'box-shadow: 0 0 0 1px red inset;')
|
||||
return false
|
||||
} else {
|
||||
e.style.color = null
|
||||
e.parentNode.removeAttribute('style')
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
const validateArgs = (val, index) => {
|
||||
const cref = argRefs.value[index]
|
||||
const e = cref.input
|
||||
if (val === null || val === '' || typeof val === 'undefined') {
|
||||
e.style.color = null
|
||||
e.parentNode.removeAttribute('style')
|
||||
const child = e.parentNode.querySelector('.error-msg')
|
||||
if (child) {
|
||||
e.parentNode.removeChild(child)
|
||||
}
|
||||
return true
|
||||
}
|
||||
try {
|
||||
JSON.parse(val)
|
||||
e.style.color = null
|
||||
e.parentNode.removeAttribute('style')
|
||||
const child = e.parentNode.querySelector('.error-msg')
|
||||
if (child) {
|
||||
e.parentNode.removeChild(child)
|
||||
}
|
||||
return true
|
||||
} catch (error) {
|
||||
e.style.color = 'red'
|
||||
e.parentNode.setAttribute('style', 'box-shadow: 0 0 0 1px red inset;')
|
||||
const child = e.parentNode.querySelector('.error-msg')
|
||||
if (!child) {
|
||||
const errorDom = document.createElement('div')
|
||||
errorDom.className = 'error-msg'
|
||||
errorDom.innerText = '格式错误'
|
||||
e.parentNode.appendChild(errorDom)
|
||||
}
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const deleteTicket = (row, index) => {
|
||||
const param = { ticket: row.ticket }
|
||||
@@ -349,29 +254,32 @@ const deleteTicket = (row, index) => {
|
||||
})
|
||||
}
|
||||
|
||||
const saveRow = (row, index) => {
|
||||
const url = '/ticket/saveTicket'
|
||||
validateExp(row.exp, index) &&
|
||||
validateArgs(row.args, index) &&
|
||||
request.post({ url, data: row }).then(() => {
|
||||
row.isEdit = false
|
||||
})
|
||||
}
|
||||
const editRow = row => {
|
||||
row.isEdit = true
|
||||
ticketEditor.value.edit(row, formatLinkAddr())
|
||||
}
|
||||
|
||||
const finish = () => {
|
||||
close()
|
||||
}
|
||||
|
||||
const loadTicketData = () => {
|
||||
const resourceId = props.resourceId
|
||||
const url = `/ticket/query/${resourceId}`
|
||||
request.get({ url }).then(res => {
|
||||
state.tableData = res.data || []
|
||||
const url = `/ticket/pager/${resourceId}/${state.paginationConfig.currentPage}/${state.paginationConfig.pageSize}`
|
||||
request.post({ url }).then(res => {
|
||||
state.tableData = res.data?.records || []
|
||||
state.paginationConfig.total = res.data.total
|
||||
})
|
||||
}
|
||||
const pageChange = index => {
|
||||
if (typeof index !== 'number') {
|
||||
return
|
||||
}
|
||||
state.paginationConfig.currentPage = index
|
||||
loadTicketData()
|
||||
}
|
||||
const sizeChange = size => {
|
||||
state.paginationConfig.pageSize = size
|
||||
loadTicketData()
|
||||
}
|
||||
onMounted(() => {
|
||||
loadTicketData()
|
||||
})
|
||||
@@ -379,7 +287,8 @@ onMounted(() => {
|
||||
|
||||
<style lang="less" scoped>
|
||||
.ticket {
|
||||
min-height: 280px;
|
||||
height: auto;
|
||||
max-height: 560px;
|
||||
.ticket-model {
|
||||
display: flex;
|
||||
height: 22px;
|
||||
@@ -415,10 +324,35 @@ onMounted(() => {
|
||||
}
|
||||
.ticket-model-end {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
label {
|
||||
height: 22px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
.top-split {
|
||||
height: 18px;
|
||||
width: 1px;
|
||||
display: inline-block;
|
||||
background-color: #bbbfc4;
|
||||
margin: 0 20px;
|
||||
}
|
||||
.top-close {
|
||||
height: 22px;
|
||||
line-height: 22px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #646a73;
|
||||
&:hover {
|
||||
cursor: pointer;
|
||||
background-color: #1f23291a;
|
||||
border-radius: 4px;
|
||||
}
|
||||
svg {
|
||||
width: 20px;
|
||||
height: 20px;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.ticket-add {
|
||||
@@ -437,15 +371,6 @@ onMounted(() => {
|
||||
overflow-y: overlay;
|
||||
position: relative;
|
||||
height: calc(100% - 124px);
|
||||
:deep(.error-msg) {
|
||||
color: red;
|
||||
position: fixed;
|
||||
z-index: 9;
|
||||
font-size: 10px;
|
||||
height: 10px;
|
||||
margin-bottom: 12px;
|
||||
margin-right: -80px;
|
||||
}
|
||||
:deep(.ticket-exp-head) {
|
||||
display: flex;
|
||||
line-height: 22px;
|
||||
@@ -466,7 +391,7 @@ onMounted(() => {
|
||||
align-items: center;
|
||||
height: 22px;
|
||||
span {
|
||||
width: 66px;
|
||||
width: 126px;
|
||||
margin-right: 8px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
@@ -511,14 +436,6 @@ onMounted(() => {
|
||||
}
|
||||
}
|
||||
}
|
||||
:deep(.ed-input__inner) {
|
||||
height: 18px;
|
||||
line-height: 18px;
|
||||
}
|
||||
}
|
||||
.ticket-btn {
|
||||
margin: 16px 0;
|
||||
float: right;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
width="480"
|
||||
placement="bottom-end"
|
||||
:show-arrow="false"
|
||||
:popper-class="`share-popover ${showTicket ? 'share-ticket-popover' : ''}`"
|
||||
popper-class="share-popover"
|
||||
@show="share"
|
||||
>
|
||||
<template #reference>
|
||||
@@ -23,11 +23,7 @@
|
||||
{{ t('visualization.share') }}
|
||||
</el-button>
|
||||
</template>
|
||||
<div
|
||||
v-if="!shareDisable"
|
||||
class="share-container"
|
||||
:class="{ 'hidden-link-container': showTicket }"
|
||||
>
|
||||
<div v-if="!shareDisable" class="share-container">
|
||||
<div class="share-title share-padding">{{ t('work_branch.public_link_share') }}</div>
|
||||
<div class="open-share flex-align-center share-padding">
|
||||
<el-switch size="small" v-model="shareEnable" @change="enableSwitcher" />
|
||||
@@ -37,23 +33,48 @@
|
||||
<el-input
|
||||
ref="linkUuidRef"
|
||||
placeholder=""
|
||||
:class="!linkCustom && 'maxW380'"
|
||||
:class="!linkCustom ? 'link-input-readlonly' : ''"
|
||||
v-model="state.detailInfo.uuid"
|
||||
:disabled="!linkCustom"
|
||||
@blur="finishEditUuid"
|
||||
:readonly="!linkCustom"
|
||||
@blur="validateUuid"
|
||||
>
|
||||
<template v-if="!linkCustom" #prefix>
|
||||
{{ formatLinkBase() }}
|
||||
</template>
|
||||
</el-input>
|
||||
<el-button v-if="linkCustom" text @click.stop="finishEditUuid">{{
|
||||
t('components.complete')
|
||||
}}</el-button>
|
||||
<el-button v-else @click.stop="editUuid" size="default" plain>
|
||||
<template #icon>
|
||||
<icon name="icon_admin_outlined"><icon_admin_outlined class="svg-icon" /></icon>
|
||||
|
||||
<template #suffix>
|
||||
<div class="share-input-suffix">
|
||||
<span class="suffix-split" />
|
||||
<div class="input-suffix-btn edit-uuid-icon" v-if="!linkCustom" @click="editUuid">
|
||||
<el-tooltip
|
||||
class="item"
|
||||
effect="dark"
|
||||
:content="t('commons.edit') + t('chart.indicator_suffix')"
|
||||
placement="top"
|
||||
>
|
||||
<Icon name="icon_edit_outlined" class="edit-uuid-icon">
|
||||
<icon_edit_outlined class="svg-icon edit-uuid-icon" />
|
||||
</Icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<div class="input-suffix-btn" v-if="linkCustom" @click="resetUuid">
|
||||
<el-tooltip
|
||||
class="item"
|
||||
effect="dark"
|
||||
:content="t('commons.cancel')"
|
||||
placement="top"
|
||||
>
|
||||
<Icon name="icon_close_outlined"><icon_close_outlined class="svg-icon" /></Icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<div class="input-suffix-btn done" v-if="linkCustom" @click="finishEditUuid">
|
||||
<el-tooltip class="item" effect="dark" :content="t('commons.save')" placement="top">
|
||||
<Icon name="icon_done_outlined"><icon_done_outlined class="svg-icon" /></Icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-button>
|
||||
</el-input>
|
||||
</div>
|
||||
<div v-if="shareEnable" class="exp-container share-padding">
|
||||
<el-checkbox
|
||||
@@ -72,7 +93,6 @@
|
||||
<div class="inline-share-item-picker">
|
||||
<el-date-picker
|
||||
:clearable="false"
|
||||
size="small"
|
||||
class="share-exp-picker"
|
||||
v-if="state.detailInfo.exp"
|
||||
v-model="state.detailInfo.exp"
|
||||
@@ -112,26 +132,40 @@
|
||||
<div class="inline-share-item" v-if="passwdEnable">
|
||||
<el-input
|
||||
ref="pwdRef"
|
||||
class="link-input-readlonly"
|
||||
v-model="state.detailInfo.pwd"
|
||||
:readonly="state.detailInfo.autoPwd"
|
||||
size="small"
|
||||
@blur="validatePwdFormat"
|
||||
>
|
||||
<template #append>
|
||||
<div class="share-pwd-opt">
|
||||
<div
|
||||
v-if="state.detailInfo.autoPwd"
|
||||
@click.stop="resetPwd"
|
||||
class="share-reset-container"
|
||||
>
|
||||
<span>{{ t('commons.reset') }}</span>
|
||||
<template #suffix>
|
||||
<div class="share-input-suffix">
|
||||
<span class="suffix-split" />
|
||||
<div class="input-suffix-btn" @click="copyPwd">
|
||||
<el-tooltip
|
||||
class="item"
|
||||
effect="dark"
|
||||
:content="t('commons.copy')"
|
||||
placement="top"
|
||||
>
|
||||
<Icon name="de-copy"><deCopy class="svg-icon" /></Icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<div @click.stop="copyPwd" class="share-reset-container">
|
||||
<span>{{ t('commons.copy') }}</span>
|
||||
<div class="input-suffix-btn" @click="resetPwd">
|
||||
<el-tooltip
|
||||
class="item"
|
||||
effect="dark"
|
||||
:content="t('commons.reset')"
|
||||
placement="top"
|
||||
>
|
||||
<Icon name="icon_refresh_outlined"
|
||||
><icon_refresh_outlined class="svg-icon"
|
||||
/></Icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</el-input>
|
||||
|
||||
<el-button secondary @click="openPwdDialog">{{ t('user.change_password') }}</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -149,7 +183,10 @@
|
||||
<span>{{ t('work_branch.cannot_share_link') }}</span>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!shareDisable && shareEnable && showTicket" class="share-ticket-container">
|
||||
</el-popover>
|
||||
<custom-link-pwd ref="customPwdRef" @pwd-change="customPwdChange" />
|
||||
<ticket-dialog ref="ticketDialogRef">
|
||||
<div v-if="!shareDisable && shareEnable && showTicket">
|
||||
<share-ticket
|
||||
:uuid="state.detailInfo.uuid"
|
||||
:resource-id="props.resourceId"
|
||||
@@ -158,12 +195,16 @@
|
||||
@close="closeTicket"
|
||||
/>
|
||||
</div>
|
||||
</el-popover>
|
||||
</ticket-dialog>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import icon_shareLabel_outlined from '@/assets/svg/icon_share-label_outlined.svg'
|
||||
import icon_admin_outlined from '@/assets/svg/icon_admin_outlined.svg'
|
||||
import icon_edit_outlined from '@/assets/svg/icon_edit_outlined.svg'
|
||||
import icon_close_outlined from '@/assets/svg/icon_close_outlined.svg'
|
||||
import icon_done_outlined from '@/assets/svg/icon_done_outlined.svg'
|
||||
import deCopy from '@/assets/svg/de-copy.svg'
|
||||
import icon_refresh_outlined from '@/assets/svg/icon_refresh_outlined.svg'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { ref, reactive, computed, nextTick, watch } from 'vue'
|
||||
import request from '@/config/axios'
|
||||
@@ -174,6 +215,8 @@ import useClipboard from 'vue-clipboard3'
|
||||
import ShareTicket from './ShareTicket.vue'
|
||||
import { useEmbedded } from '@/store/modules/embedded'
|
||||
import { useShareStoreWithOut } from '@/store/modules/share'
|
||||
import CustomLinkPwd from './CustomLinkPwd.vue'
|
||||
import TicketDialog from './TicketDialog.vue'
|
||||
const shareStore = useShareStoreWithOut()
|
||||
const embeddedStore = useEmbedded()
|
||||
const { toClipboard } = useClipboard()
|
||||
@@ -196,6 +239,9 @@ const expError = ref(false)
|
||||
const linkCustom = ref(false)
|
||||
const linkUuidRef = ref(null)
|
||||
const showTicket = ref(false)
|
||||
const originUuid = ref('')
|
||||
const customPwdRef = ref()
|
||||
const ticketDialogRef = ref()
|
||||
const state = reactive({
|
||||
detailInfo: {
|
||||
id: '',
|
||||
@@ -223,15 +269,19 @@ const hideShare = async () => {
|
||||
return
|
||||
}
|
||||
}
|
||||
const pwdValid = validatePwdFormat()
|
||||
const uuidValid = await validateUuid()
|
||||
if (pwdValid && uuidValid) {
|
||||
if (uuidValid) {
|
||||
popoverVisible.value = false
|
||||
return
|
||||
}
|
||||
}
|
||||
const clickOutPopover = e => {
|
||||
if (!popoverVisible.value || e.target.closest('[class*="share-popover"]')) {
|
||||
if (
|
||||
!popoverVisible.value ||
|
||||
e.target.closest('[class*="share-popover"]') ||
|
||||
e.target.closest('[class*="ed-overlay-dialog"]') ||
|
||||
e.target.classList?.toString()?.includes('edit-uuid-icon')
|
||||
) {
|
||||
return
|
||||
}
|
||||
hideShare()
|
||||
@@ -305,6 +355,7 @@ const loadShareInfo = cb => {
|
||||
.get({ url })
|
||||
.then(res => {
|
||||
state.detailInfo = { ...res.data }
|
||||
originUuid.value = res.data.uuid
|
||||
setPageInfo()
|
||||
})
|
||||
.finally(() => {
|
||||
@@ -450,26 +501,6 @@ const validatePwdRequire = () => {
|
||||
showCheckboxError(t('common.required'), pwdCheckbox)
|
||||
return false
|
||||
}
|
||||
const validatePwdFormat = () => {
|
||||
if (!shareEnable.value || !passwdEnable.value || state.detailInfo.autoPwd) {
|
||||
showPageError(null, pwdRef)
|
||||
return true
|
||||
}
|
||||
const val = state.detailInfo.pwd
|
||||
if (!val) {
|
||||
showPageError(t('work_branch.password_null_hint'), pwdRef)
|
||||
return false
|
||||
}
|
||||
const regex = /^(?=.*[A-Za-z])(?=.*\d)(?=.*[!@#$%^&*()_+])[A-Za-z\d!@#$%^&*()_+]{4,10}$/
|
||||
const regep = new RegExp(regex)
|
||||
if (!regep.test(val)) {
|
||||
showPageError(t('work_branch.password_hint'), pwdRef)
|
||||
return false
|
||||
}
|
||||
showPageError(null, pwdRef)
|
||||
resetPwdHandler(val, false)
|
||||
return true
|
||||
}
|
||||
const showCheckboxError = (msg, target, className?: string) => {
|
||||
if (!target.value) {
|
||||
return
|
||||
@@ -597,11 +628,17 @@ const finishEditUuid = async () => {
|
||||
const uuidValid = await validateUuid()
|
||||
linkCustom.value = !uuidValid
|
||||
}
|
||||
|
||||
const resetUuid = event => {
|
||||
event.stopPropagation()
|
||||
state.detailInfo.uuid = originUuid.value
|
||||
finishEditUuid()
|
||||
}
|
||||
const openTicket = () => {
|
||||
showTicket.value = true
|
||||
ticketDialogRef.value.open()
|
||||
}
|
||||
const closeTicket = () => {
|
||||
ticketDialogRef.value.close()
|
||||
showTicket.value = false
|
||||
}
|
||||
const updateRequireTicket = val => {
|
||||
@@ -611,27 +648,26 @@ const updateRequireTicket = val => {
|
||||
const execute = () => {
|
||||
share()
|
||||
}
|
||||
|
||||
const openPwdDialog = () => {
|
||||
customPwdRef.value.open(state.detailInfo.pwd)
|
||||
}
|
||||
const customPwdChange = val => {
|
||||
state.detailInfo.pwd = val
|
||||
resetPwdHandler(val, false)
|
||||
}
|
||||
defineExpose({
|
||||
execute
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.share-popover:not(.share-ticket-popover) {
|
||||
.share-popover {
|
||||
padding: 16px 0px !important;
|
||||
}
|
||||
.share-ticket-popover {
|
||||
padding: 0 !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.hidden-link-container {
|
||||
display: none;
|
||||
}
|
||||
.share-ticket-container {
|
||||
padding: 16px;
|
||||
}
|
||||
.share-container {
|
||||
.share-title {
|
||||
font-weight: 500;
|
||||
@@ -719,35 +755,17 @@ defineExpose({
|
||||
}
|
||||
}
|
||||
.inline-share-item {
|
||||
margin-left: 25px;
|
||||
width: 220px;
|
||||
display: inline-flex;
|
||||
column-gap: 12px;
|
||||
margin-left: 25px;
|
||||
width: 332px;
|
||||
|
||||
:deep(.ed-input-group__append) {
|
||||
width: initial !important;
|
||||
background: none;
|
||||
color: #1f2329;
|
||||
padding: 0px 0px !important;
|
||||
|
||||
.share-pwd-opt {
|
||||
display: flex;
|
||||
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;
|
||||
@@ -760,4 +778,52 @@ defineExpose({
|
||||
left: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
.share-input-suffix {
|
||||
display: flex;
|
||||
height: 30px;
|
||||
line-height: 30px;
|
||||
column-gap: 4px;
|
||||
align-items: center;
|
||||
.suffix-split {
|
||||
height: 30px;
|
||||
width: 1px;
|
||||
display: inline-block;
|
||||
background-color: #bbbfc4;
|
||||
margin-right: 4px;
|
||||
}
|
||||
.done {
|
||||
color: #3370ff;
|
||||
&:hover {
|
||||
background-color: #3370ff1a !important;
|
||||
}
|
||||
}
|
||||
.input-suffix-btn {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
&:hover {
|
||||
background-color: #1f23291a;
|
||||
}
|
||||
svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.link-input-readlonly {
|
||||
:deep(.ed-input__wrapper) {
|
||||
background-color: rgba(0, 0, 0, 0.1);
|
||||
color: #8f959e;
|
||||
&:hover {
|
||||
box-shadow: 0 0 0 1px var(--ed-input-border-color, var(--ed-border-color)) inset;
|
||||
}
|
||||
input {
|
||||
color: #646a73;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
46
core/core-frontend/src/views/share/share/TicketDialog.vue
Normal file
46
core/core-frontend/src/views/share/share/TicketDialog.vue
Normal file
@@ -0,0 +1,46 @@
|
||||
<template>
|
||||
<div class="ticket-dialog-container">
|
||||
<el-dialog
|
||||
v-model="dialogVisible"
|
||||
:title="t('user.change_password')"
|
||||
width="600"
|
||||
class="is-ticket-dialog hidden-footer"
|
||||
:append-to-body="true"
|
||||
:before-close="handleClose"
|
||||
>
|
||||
<slot />
|
||||
</el-dialog>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { ref } from 'vue'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
|
||||
const { t } = useI18n()
|
||||
const dialogVisible = ref(false)
|
||||
|
||||
const handleClose = done => {
|
||||
done()
|
||||
}
|
||||
const open = () => {
|
||||
dialogVisible.value = true
|
||||
}
|
||||
const close = () => {
|
||||
dialogVisible.value = false
|
||||
}
|
||||
defineExpose({
|
||||
open,
|
||||
close
|
||||
})
|
||||
</script>
|
||||
<style lang="less" scoped>
|
||||
.ticket-dialog-container {
|
||||
width: 100%;
|
||||
:deep(.is-ticket-dialog) {
|
||||
.ed-dialog__header {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
424
core/core-frontend/src/views/share/share/TicketEdit.vue
Normal file
424
core/core-frontend/src/views/share/share/TicketEdit.vue
Normal file
@@ -0,0 +1,424 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive } from 'vue'
|
||||
import { ElMessage, ElLoading } from 'element-plus-secondary'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import type { FormInstance, FormRules } from 'element-plus-secondary'
|
||||
import request from '@/config/axios'
|
||||
import deCopy from '@/assets/svg/de-copy.svg'
|
||||
import icon_refresh_outlined from '@/assets/svg/icon_refresh_outlined.svg'
|
||||
import icon_deleteTrash_outlined from '@/assets/svg/icon_delete-trash_outlined.svg'
|
||||
import icon_add_outlined from '@/assets/svg/icon_add_outlined.svg'
|
||||
import dvInfo from '@/assets/svg/dv-info.svg'
|
||||
import useClipboard from 'vue-clipboard3'
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
const { toClipboard } = useClipboard()
|
||||
const { t } = useI18n()
|
||||
const dialogVisible = ref(false)
|
||||
const loadingInstance = ref(null)
|
||||
const ticketForm = ref<FormInstance>()
|
||||
const props = defineProps({
|
||||
uuid: propTypes.string.def('')
|
||||
})
|
||||
interface TicketItem {
|
||||
ticket: string
|
||||
exp: number
|
||||
args: string
|
||||
}
|
||||
interface TicketArg {
|
||||
name: string
|
||||
val: string
|
||||
}
|
||||
const isEdit = ref(false)
|
||||
const linkUrl = ref('')
|
||||
const state = reactive({
|
||||
form: reactive<TicketItem>({
|
||||
ticket: '',
|
||||
exp: 30,
|
||||
args: ''
|
||||
}),
|
||||
argList: reactive<TicketArg[]>([{ name: '', val: '' }] as TicketArg[])
|
||||
})
|
||||
|
||||
const rule = reactive<FormRules>({
|
||||
exp: [
|
||||
{
|
||||
required: true,
|
||||
message: t('common.require'),
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
const emits = defineEmits(['saved'])
|
||||
const submitForm = async (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return
|
||||
await formEl.validate(valid => {
|
||||
if (valid) {
|
||||
argList2Args()
|
||||
const param = {
|
||||
uuid: props.uuid,
|
||||
ticket: state.form.ticket,
|
||||
exp: state.form.exp,
|
||||
args: state.form.args
|
||||
}
|
||||
showLoading()
|
||||
request
|
||||
.post({ url: '/ticket/saveTicket', data: param })
|
||||
.then(res => {
|
||||
if (!res.msg) {
|
||||
ElMessage.success(t('common.save_success'))
|
||||
emits('saved')
|
||||
reset()
|
||||
}
|
||||
closeLoading()
|
||||
})
|
||||
.catch(() => {
|
||||
closeLoading()
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const resetForm = (formEl: FormInstance | undefined) => {
|
||||
if (!formEl) return
|
||||
formEl.resetFields()
|
||||
dialogVisible.value = false
|
||||
}
|
||||
|
||||
const reset = () => {
|
||||
resetForm(ticketForm.value)
|
||||
}
|
||||
|
||||
const showLoading = () => {
|
||||
loadingInstance.value = ElLoading.service({ target: '.ticket-param-drawer' })
|
||||
}
|
||||
const closeLoading = () => {
|
||||
loadingInstance.value?.close()
|
||||
}
|
||||
|
||||
const edit = (row, link) => {
|
||||
resetFormData()
|
||||
linkUrl.value = link
|
||||
isEdit.value = !!row?.ticket
|
||||
if (!isEdit.value) {
|
||||
generateTicket()
|
||||
dialogVisible.value = true
|
||||
return
|
||||
}
|
||||
for (const key in row) {
|
||||
if (state.form.hasOwnProperty(key)) {
|
||||
state.form[key] = row[key]
|
||||
}
|
||||
}
|
||||
if (state.form.args) {
|
||||
args2ArgList()
|
||||
}
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
const generateTicket = () => {
|
||||
const url = '/ticket/tempTicket'
|
||||
request.get({ url }).then(res => {
|
||||
state.form.ticket = res.data
|
||||
})
|
||||
}
|
||||
|
||||
const addArgRow = () => {
|
||||
const row: TicketArg = { name: '', val: '' }
|
||||
state.argList.push(row)
|
||||
}
|
||||
const delArgRow = index => {
|
||||
const last = state.argList.length === 1
|
||||
if (last) {
|
||||
state.argList[index]['name'] = ''
|
||||
state.argList[index]['val'] = ''
|
||||
return
|
||||
}
|
||||
state.argList.splice(index, 1)
|
||||
}
|
||||
|
||||
const args2ArgList = () => {
|
||||
if (!state.form.args) {
|
||||
return
|
||||
}
|
||||
const argObj = JSON.parse(state.form.args)
|
||||
for (const key in argObj) {
|
||||
const val = argObj[key]
|
||||
if (val && Array.isArray(val)) {
|
||||
const row = { name: key, val: JSON.stringify(val) }
|
||||
state.argList.push(row)
|
||||
} else {
|
||||
const row = { name: key, val: argObj[key] }
|
||||
state.argList.push(row)
|
||||
}
|
||||
}
|
||||
if (state.argList?.length > 1 && !state.argList[0]['name']) {
|
||||
state.argList.splice(0, 1)
|
||||
}
|
||||
}
|
||||
|
||||
const argList2Args = () => {
|
||||
if (!state.argList?.length) {
|
||||
state.form.args = ''
|
||||
return
|
||||
}
|
||||
const argObj = {}
|
||||
state.argList.forEach(row => {
|
||||
if (row.name && row.val) {
|
||||
argObj[row.name] = row.val
|
||||
}
|
||||
})
|
||||
state.form.args = JSON.stringify(argObj)
|
||||
}
|
||||
const copyTicket = async ticket => {
|
||||
const url = `${linkUrl.value}?ticket=${ticket}`
|
||||
try {
|
||||
await toClipboard(url)
|
||||
ElMessage.success(t('common.copy_success'))
|
||||
} catch (e) {
|
||||
ElMessage.warning(t('common.copy_unsupported'), e)
|
||||
}
|
||||
}
|
||||
const refreshTicket = () => {
|
||||
if (!isEdit.value) {
|
||||
generateTicket()
|
||||
return
|
||||
}
|
||||
const url = '/ticket/saveTicket'
|
||||
const param = { ticket: state.form.ticket }
|
||||
param['generateNew'] = true
|
||||
request.post({ url, data: param }).then(res => {
|
||||
state.form.ticket = res.data
|
||||
emits('saved')
|
||||
})
|
||||
}
|
||||
const resetFormData = () => {
|
||||
state.form = {
|
||||
ticket: '',
|
||||
exp: 30,
|
||||
args: ''
|
||||
}
|
||||
state.argList = [{ name: '', val: '' }]
|
||||
isEdit.value = false
|
||||
linkUrl.value = ''
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
edit
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<el-drawer
|
||||
:title="`${t('commons.add')} Ticket`"
|
||||
v-model="dialogVisible"
|
||||
custom-class="ticket-param-drawer"
|
||||
size="600px"
|
||||
direction="rtl"
|
||||
>
|
||||
<el-form
|
||||
ref="ticketForm"
|
||||
require-asterisk-position="right"
|
||||
:model="state.form"
|
||||
:rules="rule"
|
||||
label-width="80px"
|
||||
label-position="top"
|
||||
>
|
||||
<div class="ticket-tips-label"><span>Ticket</span></div>
|
||||
<div class="ticket-row ticket-tips-label">
|
||||
<span :title="state.form.ticket">{{ state.form.ticket }}</span>
|
||||
<el-tooltip class="item" effect="dark" :content="t('commons.copy')" placement="top">
|
||||
<el-button text @click.stop="copyTicket(state.form.ticket)">
|
||||
<template #icon>
|
||||
<Icon name="de-copy"><deCopy class="svg-icon" /></Icon>
|
||||
</template>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip
|
||||
class="item"
|
||||
effect="dark"
|
||||
:content="`${t('link_ticket.refresh')} ticket`"
|
||||
placement="top"
|
||||
>
|
||||
<el-button text @click.stop="refreshTicket">
|
||||
<template #icon>
|
||||
<Icon name="icon_refresh_outlined">
|
||||
<icon_refresh_outlined class="svg-icon" />
|
||||
</Icon>
|
||||
</template>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
<el-form-item prop="exp" :label="t('visualization.over_time')">
|
||||
<template v-slot:label>
|
||||
<div class="ticket-form-info-tips">
|
||||
<span class="custom-form-item__label">{{ t('visualization.over_time') }}</span>
|
||||
<el-tooltip effect="dark" :content="t('link_ticket.time_tips')" placement="top">
|
||||
<el-icon>
|
||||
<Icon name="dv-info"><dvInfo class="svg-icon" /></Icon>
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
<el-input-number
|
||||
v-model="state.form.exp"
|
||||
autocomplete="off"
|
||||
step-strictly
|
||||
class="text-left edit-all-line"
|
||||
:min="0"
|
||||
:max="1440"
|
||||
:placeholder="t('common.inputText')"
|
||||
controls-position="right"
|
||||
type="number"
|
||||
/>
|
||||
</el-form-item>
|
||||
|
||||
<el-form-item prop="args" :label="t('dataset.param')">
|
||||
<template v-slot:label>
|
||||
<div class="ticket-form-info-tips">
|
||||
<span class="custom-form-item__label">{{ t('dataset.param') }}</span>
|
||||
<el-tooltip effect="dark" :content="t('link_ticket.arg_format_tips')" placement="top">
|
||||
<el-icon>
|
||||
<Icon name="dv-info"><dvInfo class="svg-icon" /></Icon>
|
||||
</el-icon>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
<div class="args-line" v-for="(row, index) in state.argList" :key="index">
|
||||
<el-input v-model="row['name']" :placeholder="t('visualization.input_param_name')" />
|
||||
<el-input v-model="row['val']" :placeholder="t('link_ticket.arg_val_tips')" />
|
||||
<div class="arg-del-btn" @click.stop="delArgRow(index)">
|
||||
<Icon name="icon_delete-trash_outlined">
|
||||
<icon_deleteTrash_outlined class="svg-icon" />
|
||||
</Icon>
|
||||
</div>
|
||||
</div>
|
||||
</el-form-item>
|
||||
|
||||
<div class="ticket-add">
|
||||
<el-button @click.stop="addArgRow" text>
|
||||
<template #icon>
|
||||
<icon name="icon_add_outlined"><icon_add_outlined class="svg-icon" /></icon>
|
||||
</template>
|
||||
{{ t('commons.add') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<span class="dialog-footer">
|
||||
<el-button secondary @click="resetForm(ticketForm)">{{ t('common.cancel') }}</el-button>
|
||||
<el-button type="primary" @click="submitForm(ticketForm)">
|
||||
{{ t('commons.save') }}
|
||||
</el-button>
|
||||
</span>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</template>
|
||||
<style lang="less">
|
||||
.ticket-param-drawer {
|
||||
.ed-drawer__body {
|
||||
padding: 24px !important;
|
||||
}
|
||||
.ed-drawer__footer {
|
||||
box-shadow: 0 -1px 4px #1f232926 !important;
|
||||
height: 64px !important;
|
||||
padding: 16px 24px !important;
|
||||
.dialog-footer {
|
||||
height: 32px;
|
||||
line-height: 32px;
|
||||
}
|
||||
}
|
||||
.ed-form-item__label {
|
||||
line-height: 22px !important;
|
||||
height: 22px !important;
|
||||
.ticket-form-info-tips {
|
||||
width: fit-content;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
column-gap: 4px;
|
||||
}
|
||||
}
|
||||
.ed-form-item {
|
||||
&.is-required.asterisk-right {
|
||||
.ed-form-item__label:after {
|
||||
display: none;
|
||||
}
|
||||
.ticket-form-info-tips {
|
||||
.custom-form-item__label:after {
|
||||
content: '*';
|
||||
color: var(--ed-color-danger);
|
||||
margin-left: 2px;
|
||||
font-family: var(--de-custom_font, 'PingFang');
|
||||
font-size: 14px;
|
||||
font-style: normal;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style scoped lang="less">
|
||||
.ticket-param-drawer {
|
||||
.ed-form-item {
|
||||
margin-bottom: 16px;
|
||||
}
|
||||
.is-error {
|
||||
margin-bottom: 40px !important;
|
||||
}
|
||||
.edit-all-line {
|
||||
width: 552px !important;
|
||||
}
|
||||
.args-line {
|
||||
width: 100%;
|
||||
align-items: center;
|
||||
display: flex;
|
||||
line-height: 32px;
|
||||
column-gap: 8px;
|
||||
.arg-del-btn {
|
||||
padding: 4px;
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
border-radius: 4px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
cursor: pointer;
|
||||
&:hover {
|
||||
background-color: #1f23291a;
|
||||
}
|
||||
svg {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
}
|
||||
&:not(:last-child) {
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
}
|
||||
.ticket-tips-label {
|
||||
line-height: 22px;
|
||||
height: 22px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
margin-bottom: 8px;
|
||||
}
|
||||
.ticket-row {
|
||||
margin-bottom: 16px !important;
|
||||
span {
|
||||
margin-right: 8px;
|
||||
overflow: hidden;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
button {
|
||||
height: 16px;
|
||||
line-height: 16px;
|
||||
width: 16px;
|
||||
}
|
||||
.ed-button + .ed-button {
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
2
de-xpack
2
de-xpack
Submodule de-xpack updated: 2d267e3804...448e9c1684
@@ -1,9 +1,11 @@
|
||||
package io.dataease.api.xpack.share;
|
||||
|
||||
import com.baomidou.mybatisplus.core.metadata.IPage;
|
||||
import io.dataease.api.xpack.share.request.TicketCreator;
|
||||
import io.dataease.api.xpack.share.request.TicketDelRequest;
|
||||
import io.dataease.api.xpack.share.request.TicketSwitchRequest;
|
||||
import io.dataease.api.xpack.share.vo.TicketVO;
|
||||
import io.swagger.v3.oas.annotations.Hidden;
|
||||
import io.swagger.v3.oas.annotations.Operation;
|
||||
import io.swagger.v3.oas.annotations.Parameter;
|
||||
import io.swagger.v3.oas.annotations.enums.ParameterIn;
|
||||
@@ -13,8 +15,6 @@ import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Tag(name = "可视化管理:分享:TICKET")
|
||||
public interface ShareTicketApi {
|
||||
|
||||
@@ -30,8 +30,16 @@ public interface ShareTicketApi {
|
||||
@Operation(summary = "切换Ticket必填状态")
|
||||
void switchRequire(@RequestBody TicketSwitchRequest request);
|
||||
|
||||
@GetMapping("/query/{resourceId}")
|
||||
@PostMapping("/pager/{resourceId}/{goPage}/{pageSize}")
|
||||
@Operation(summary = "根据资源查询Ticket")
|
||||
@Parameter(name = "resourceId", description = "资源ID", required = true, in = ParameterIn.PATH)
|
||||
List<TicketVO> query(@PathVariable("resourceId") Long resourceId);
|
||||
IPage<TicketVO> pager(@PathVariable("resourceId") Long resourceId, @PathVariable("goPage") int goPage, @PathVariable("pageSize") int pageSize);
|
||||
|
||||
@GetMapping("/tempTicket")
|
||||
@Operation(summary = "生成临时Ticket")
|
||||
String tempTicket();
|
||||
|
||||
@GetMapping("/limit")
|
||||
@Hidden
|
||||
Integer limit();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user