feat: 分享功能从xpack下放到core

This commit is contained in:
fit2cloud-chenyw 2024-01-15 18:16:53 +08:00
parent b5eb2d8835
commit 20e44797f4
22 changed files with 1901 additions and 19 deletions

View File

@ -0,0 +1,150 @@
package io.dataease.share.dao.auto.entity;
import com.baomidou.mybatisplus.annotation.TableName;
import java.io.Serializable;
/**
* <p>
*
* </p>
*
* @author fit2cloud
* @since 2023-09-22
*/
@TableName("xpack_share")
public class XpackShare implements Serializable {
private static final long serialVersionUID = 1L;
/**
* ID
*/
private Long id;
/**
* 创建人
*/
private Long creator;
/**
* 创建时间
*/
private Long time;
/**
* 过期时间
*/
private Long exp;
/**
* uuid
*/
private String uuid;
/**
* 密码
*/
private String pwd;
/**
* 资源ID
*/
private Long resourceId;
/**
* 组织ID
*/
private Long oid;
/**
* 业务类型
*/
private Integer type;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Long getCreator() {
return creator;
}
public void setCreator(Long creator) {
this.creator = creator;
}
public Long getTime() {
return time;
}
public void setTime(Long time) {
this.time = time;
}
public Long getExp() {
return exp;
}
public void setExp(Long exp) {
this.exp = exp;
}
public String getUuid() {
return uuid;
}
public void setUuid(String uuid) {
this.uuid = uuid;
}
public String getPwd() {
return pwd;
}
public void setPwd(String pwd) {
this.pwd = pwd;
}
public Long getResourceId() {
return resourceId;
}
public void setResourceId(Long resourceId) {
this.resourceId = resourceId;
}
public Long getOid() {
return oid;
}
public void setOid(Long oid) {
this.oid = oid;
}
public Integer getType() {
return type;
}
public void setType(Integer type) {
this.type = type;
}
@Override
public String toString() {
return "XpackShare{" +
"id = " + id +
", creator = " + creator +
", time = " + time +
", exp = " + exp +
", uuid = " + uuid +
", pwd = " + pwd +
", resourceId = " + resourceId +
", oid = " + oid +
", type = " + type +
"}";
}
}

View File

@ -0,0 +1,18 @@
package io.dataease.share.dao.auto.mapper;
import io.dataease.share.dao.auto.entity.XpackShare;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import org.apache.ibatis.annotations.Mapper;
/**
* <p>
* Mapper 接口
* </p>
*
* @author fit2cloud
* @since 2023-09-22
*/
@Mapper
public interface XpackShareMapper extends BaseMapper<XpackShare> {
}

View File

@ -0,0 +1,30 @@
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.share.dao.ext.po.XpackSharePO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
@Mapper
public interface XpackShareExtMapper {
@Select("""
select
s.id as share_id,
v.id as resource_id,
v.type,
s.creator,
s.time,
s.exp,
v.name
from xpack_share s
left join data_visualization_info v on s.resource_id = v.id
${ew.customSqlSegment}
""")
IPage<XpackSharePO> query(IPage<XpackSharePO> page, @Param("ew") QueryWrapper<Object> ew);
@Select("select type from data_visualization_info where id = #{id}")
String visualizationType(@Param("id") Long id);
}

View File

@ -0,0 +1,31 @@
package io.dataease.share.dao.ext.po;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.io.Serial;
import java.io.Serializable;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class XpackSharePO implements Serializable {
@Serial
private static final long serialVersionUID = 7929343371768885789L;
private Long shareId;
private Long resourceId;
private String name;
private String type;
private Long creator;
private Long time;
private Long exp;
}

View File

@ -0,0 +1,201 @@
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.visualization.request.VisualizationWorkbranchQueryRequest;
import io.dataease.api.xpack.share.request.XpackShareProxyRequest;
import io.dataease.api.xpack.share.request.XpackSharePwdValidator;
import io.dataease.api.xpack.share.vo.XpackShareGridVO;
import io.dataease.api.xpack.share.vo.XpackShareProxyVO;
import io.dataease.auth.bo.TokenUserBO;
import io.dataease.constant.AuthConstant;
import io.dataease.constant.BusiResourceEnum;
import io.dataease.exception.DEException;
import io.dataease.license.config.XpackInteract;
import io.dataease.share.dao.auto.mapper.XpackShareMapper;
import io.dataease.utils.*;
import io.dataease.share.dao.auto.entity.XpackShare;
import io.dataease.share.dao.ext.mapper.XpackShareExtMapper;
import io.dataease.share.dao.ext.po.XpackSharePO;
import io.dataease.share.util.LinkTokenUtil;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.stereotype.Component;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
@Component("xpackShareManage")
public class XpackShareManage {
@Resource(name = "xpackShareMapper")
private XpackShareMapper xpackShareMapper;
@Resource(name = "xpackShareExtMapper")
private XpackShareExtMapper xpackShareExtMapper;
public XpackShare queryByResource(Long resourceId) {
Long userId = AuthUtils.getUser().getUserId();
QueryWrapper<XpackShare> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("creator", userId);
queryWrapper.eq("resource_id", resourceId);
return xpackShareMapper.selectOne(queryWrapper);
}
public void switcher(Long resourceId) {
XpackShare originData = queryByResource(resourceId);
if (ObjectUtils.isNotEmpty(originData)) {
xpackShareMapper.deleteById(originData.getId());
return;
}
TokenUserBO user = AuthUtils.getUser();
Long userId = user.getUserId();
XpackShare xpackShare = new XpackShare();
xpackShare.setId(IDUtils.snowID());
xpackShare.setCreator(userId);
xpackShare.setTime(System.currentTimeMillis());
xpackShare.setResourceId(resourceId);
xpackShare.setUuid(RandomStringUtils.randomAlphanumeric(8));
xpackShare.setOid(user.getDefaultOid());
String dType = xpackShareExtMapper.visualizationType(resourceId);
xpackShare.setType(StringUtils.equalsIgnoreCase("dataV", dType) ? 2 : 1);
xpackShareMapper.insert(xpackShare);
}
public void editExp(Long resourceId, Long exp) {
XpackShare originData = queryByResource(resourceId);
if (ObjectUtils.isEmpty(originData)) {
DEException.throwException("share instance not exist");
}
originData.setExp(exp);
if (ObjectUtils.isEmpty(exp)) {
originData.setExp(0L);
}
xpackShareMapper.updateById(originData);
}
public void editPwd(Long resourceId, String pwd) {
XpackShare originData = queryByResource(resourceId);
if (ObjectUtils.isEmpty(originData)) {
DEException.throwException("share instance not exist");
}
originData.setPwd(pwd);
xpackShareMapper.updateById(originData);
}
public IPage<XpackSharePO> querySharePage(int goPage, int pageSize, VisualizationWorkbranchQueryRequest request) {
Long uid = AuthUtils.getUser().getUserId();
QueryWrapper<Object> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("s.creator", uid);
if (StringUtils.isNotBlank(request.getType())) {
BusiResourceEnum busiResourceEnum = BusiResourceEnum.valueOf(request.getType().toUpperCase());
if (ObjectUtils.isEmpty(busiResourceEnum)) {
DEException.throwException("type is invalid");
}
String resourceType = convertResourceType(request.getType());
if (StringUtils.isNotBlank(resourceType)) {
queryWrapper.eq("v.type", resourceType);
}
}
if (StringUtils.isNotBlank(request.getKeyword())) {
queryWrapper.like("v.name", request.getKeyword());
}
queryWrapper.orderBy(true, request.isAsc(), "s.time");
Page<XpackSharePO> page = new Page<>(goPage, pageSize);
return xpackShareExtMapper.query(page, queryWrapper);
}
private String convertResourceType(String busiFlag) {
return switch (busiFlag) {
case "panel" -> "dashboard";
case "screen" -> "dataV";
default -> null;
};
}
@XpackInteract(value = "perFilterShareManage", recursion = true)
public IPage<XpackShareGridVO> query(int pageNum, int pageSize, VisualizationWorkbranchQueryRequest request) {
IPage<XpackSharePO> poiPage = proxy().querySharePage(pageNum, pageSize, request);
List<XpackShareGridVO> vos = proxy().formatResult(poiPage.getRecords());
IPage<XpackShareGridVO> ipage = new Page<>();
ipage.setSize(poiPage.getSize());
ipage.setCurrent(poiPage.getCurrent());
ipage.setPages(poiPage.getPages());
ipage.setTotal(poiPage.getTotal());
ipage.setRecords(vos);
return ipage;
}
public List<XpackShareGridVO> formatResult(List<XpackSharePO> pos) {
if (CollectionUtils.isEmpty(pos)) return new ArrayList<>();
return pos.stream().map(po ->
new XpackShareGridVO(
po.getShareId(), po.getResourceId(), po.getName(), po.getCreator().toString(),
po.getTime(), po.getExp(), 9)).toList();
}
private XpackShareManage proxy() {
return CommonBeanFactory.getBean(this.getClass());
}
public XpackShareProxyVO proxyInfo(XpackShareProxyRequest request) {
QueryWrapper<XpackShare> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("uuid", request.getUuid());
XpackShare xpackShare = xpackShareMapper.selectOne(queryWrapper);
if (ObjectUtils.isEmpty(xpackShare))
return null;
String linkToken = LinkTokenUtil.generate(xpackShare.getCreator(), xpackShare.getResourceId(), xpackShare.getExp(), xpackShare.getPwd(), xpackShare.getOid());
HttpServletResponse response = ServletUtils.response();
response.addHeader(AuthConstant.LINK_TOKEN_KEY, linkToken);
Integer type = xpackShare.getType();
String typeText = (ObjectUtils.isNotEmpty(type) && type == 1) ? "dashboard" : "dataV";
return new XpackShareProxyVO(xpackShare.getResourceId(), xpackShare.getCreator(), linkExp(xpackShare), pwdValid(xpackShare, request.getCiphertext()), typeText);
}
private boolean linkExp(XpackShare xpackShare) {
if (ObjectUtils.isEmpty(xpackShare.getExp()) || xpackShare.getExp().equals(0L)) return false;
return System.currentTimeMillis() > xpackShare.getExp();
}
private boolean pwdValid(XpackShare xpackShare, String ciphertext) {
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;
String pwd = text.substring(splitIndex);
String uuid = text.substring(0, splitIndex);
return StringUtils.equals(xpackShare.getUuid(), uuid) && StringUtils.equals(xpackShare.getPwd(), pwd);
}
public boolean validatePwd(XpackSharePwdValidator validator) {
String ciphertext = RsaUtils.decryptStr(validator.getCiphertext());
int len = ciphertext.length();
int splitIndex = len - 4;
String pwd = ciphertext.substring(splitIndex);
String uuid = ciphertext.substring(0, splitIndex);
QueryWrapper<XpackShare> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("uuid", uuid);
XpackShare xpackShare = xpackShareMapper.selectOne(queryWrapper);
return StringUtils.equals(xpackShare.getUuid(), uuid) && StringUtils.equals(xpackShare.getPwd(), pwd);
}
public Map<String, String> queryRelationByUserId(Long uid) {
QueryWrapper<XpackShare> queryWrapper = new QueryWrapper<>();
queryWrapper.eq("creator", uid);
List<XpackShare> result = xpackShareMapper.selectList(queryWrapper);
if (CollectionUtils.isNotEmpty(result)) {
return result.stream()
.collect(Collectors.toMap(xpackShare -> String.valueOf(xpackShare.getResourceId()), XpackShare::getUuid));
}
return new HashMap<>();
}
}

View File

@ -0,0 +1,76 @@
package io.dataease.share.server;
import io.dataease.api.visualization.request.VisualizationWorkbranchQueryRequest;
import io.dataease.api.xpack.share.XpackShareApi;
import io.dataease.api.xpack.share.request.XpackShareExpRequest;
import io.dataease.api.xpack.share.request.XpackShareProxyRequest;
import io.dataease.api.xpack.share.request.XpackSharePwdRequest;
import io.dataease.api.xpack.share.request.XpackSharePwdValidator;
import io.dataease.api.xpack.share.vo.XpackShareGridVO;
import io.dataease.api.xpack.share.vo.XpackShareProxyVO;
import io.dataease.api.xpack.share.vo.XpackShareVO;
import io.dataease.utils.BeanUtils;
import io.dataease.share.dao.auto.entity.XpackShare;
import io.dataease.share.manage.XpackShareManage;
import jakarta.annotation.Resource;
import org.apache.commons.lang3.ObjectUtils;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
import java.util.Map;
@RequestMapping("/share")
@RestController
public class XpackShareServer implements XpackShareApi {
@Resource(name = "xpackShareManage")
private XpackShareManage xpackShareManage;
@Override
public boolean status(Long resourceId) {
return ObjectUtils.isNotEmpty(xpackShareManage.queryByResource(resourceId));
}
@Override
public void switcher(Long resourceId) {
xpackShareManage.switcher(resourceId);
}
@Override
public void editExp(XpackShareExpRequest request) {
xpackShareManage.editExp(request.getResourceId(), request.getExp());
}
@Override
public void editPwd(XpackSharePwdRequest request) {
xpackShareManage.editPwd(request.getResourceId(), request.getPwd());
}
@Override
public XpackShareVO detail(Long resourceId) {
XpackShare xpackShare = xpackShareManage.queryByResource(resourceId);
if (ObjectUtils.isEmpty(xpackShare)) return null;
return BeanUtils.copyBean(new XpackShareVO(), xpackShare);
}
@Override
public List<XpackShareGridVO> query(VisualizationWorkbranchQueryRequest request) {
return xpackShareManage.query(1, 20, request).getRecords();
}
@Override
public XpackShareProxyVO proxyInfo(XpackShareProxyRequest request) {
return xpackShareManage.proxyInfo(request);
}
@Override
public boolean validatePwd(XpackSharePwdValidator validator) {
return xpackShareManage.validatePwd(validator);
}
@Override
public Map<String, String> queryRelationByUserId(@PathVariable("uid") Long uid) {
return xpackShareManage.queryRelationByUserId(uid);
}
}

View File

@ -0,0 +1,23 @@
package io.dataease.share.util;
import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.commons.lang3.StringUtils;
import java.util.Date;
public class LinkTokenUtil {
private static final String defaultPwd = "link-pwd-fit2cloud";
public static String generate(Long uid, Long resourceId, Long exp, String pwd, Long oid) {
pwd = StringUtils.isBlank(pwd) ? defaultPwd : pwd;
Algorithm algorithm = Algorithm.HMAC256(pwd);
JWTCreator.Builder builder = JWT.create();
builder.withClaim("uid", uid).withClaim("resourceId", resourceId).withClaim("oid", oid);
if (ObjectUtils.isNotEmpty(exp) && !exp.equals(0L)) {
builder = builder.withExpiresAt(new Date(exp));
}
return builder.sign(algorithm);
}
}

View File

@ -3,7 +3,7 @@ import { Icon } from '@/components/icon-custom'
import { propTypes } from '@/utils/propTypes'
import type { Placement } from 'element-plus-secondary'
import { ref, PropType } from 'vue'
import { XpackComponent } from '@/components/plugin'
import ShareHandler from '@/views/share/share/ShareHandler.vue'
export interface Menu {
svgName?: string
label?: string
@ -44,7 +44,8 @@ const menus = ref([
])
const handleCommand = (command: string | number | object) => {
if (command === 'share') {
shareComponent.value.invokeMethod({ methodName: 'execute' })
// shareComponent.value.invokeMethod({ methodName: 'execute' })
shareComponent.value.execute()
return
}
emit('handleCommand', command)
@ -85,9 +86,8 @@ const emit = defineEmits(['handleCommand'])
</el-dropdown-menu>
</template>
</el-dropdown>
<XpackComponent
<ShareHandler
ref="shareComponent"
jsname="c2hhcmUtaGFuZGxlcg=="
:resource-id="props.node.id"
:resource-type="props.resourceType"
:weight="node.weight"

View File

@ -1,6 +1,6 @@
<template>
<XpackComponent jsname="bGluaw==" :error-tips="true" />
<link-index :error-tips="true" />
</template>
<script lang="ts" setup>
import { XpackComponent } from '@/components/plugin'
import LinkIndex from '@/views/share/link/index.vue'
</script>

View File

@ -6,7 +6,7 @@ import { useAppStoreWithOut } from '@/store/modules/app'
import DvDetailInfo from '@/views/common/DvDetailInfo.vue'
import { storeApi, storeStatusApi } from '@/api/visualization/dataVisualization'
import { ref, watch, computed } from 'vue'
import { XpackComponent } from '@/components/plugin'
import ShareVisualHead from '@/views/share/share/ShareVisualHead.vue'
const dvMainStore = dvMainStoreWithOut()
const appStore = useAppStoreWithOut()
const { dvInfo } = storeToRefs(dvMainStore)
@ -94,8 +94,7 @@ watch(
</template>
预览</el-button
>
<XpackComponent
jsname="L2NvbXBvbmVudC9zaGFyZS9TaGFyZVZpc3VhbEhlYWQ="
<ShareVisualHead
:resource-id="dvInfo.id"
:weight="dvInfo.weight"
:resource-type="dvInfo.type"

View File

@ -0,0 +1,42 @@
import request from '@/config/axios'
import { useCache } from '@/hooks/web/useCache'
const { wsCache } = useCache()
export interface ProxyInfo {
resourceId: string
uid: string
exp?: boolean
pwdValid?: boolean
type: string
}
class ShareProxy {
uuid: string
constructor() {
this.uuid = ''
}
setUuid() {
const curLocation = window.location.href
const uuidObj = curLocation.substring(
curLocation.lastIndexOf('de-link/') + 8,
curLocation.lastIndexOf('?') > 0 ? curLocation.lastIndexOf('?') : curLocation.length
)
this.uuid = uuidObj
}
async loadProxy() {
this.setUuid()
if (!this.uuid) {
return null
}
const uuid = this.uuid
const url = '/share/proxyInfo'
const param = { uuid, ciphertext: null }
const ciphertext = wsCache.get(`link-${uuid}`)
if (ciphertext) {
param['ciphertext'] = ciphertext
}
const res = await request.post({ url, data: param })
const proxyInfo: ProxyInfo = res.data as ProxyInfo
return proxyInfo
}
}
export const shareProxy = new ShareProxy()

View File

@ -0,0 +1,6 @@
<script lang="ts" setup>
import EmptyBackground from '@/components/empty-background/src/EmptyBackground.vue'
</script>
<template>
<EmptyBackground img-type="noneWhite" description="链接不存在" />
</template>

View File

@ -0,0 +1,6 @@
<script lang="ts" setup>
import EmptyBackground from '@/components/empty-background/src/EmptyBackground.vue'
</script>
<template>
<EmptyBackground img-type="noneWhite" description="链接已过期" />
</template>

View File

@ -0,0 +1,55 @@
<template>
<div class="link-container" v-loading="loading">
<LinkError v-if="!loading && !linkExist" />
<Exp v-else-if="!loading && linkExp" />
<PwdTips v-else-if="!loading && !pwdValid" />
<PreviewCanvas
v-else
:class="{ 'hidden-link': loading }"
ref="pcanvas"
public-link-status="true"
/>
</div>
</template>
<script lang="ts" setup>
import { onMounted, nextTick, ref } from 'vue'
import PreviewCanvas from '@/views/data-visualization/PreviewCanvas.vue'
import { ProxyInfo, shareProxy } from './ShareProxy'
import Exp from './exp.vue'
import LinkError from './error.vue'
import PwdTips from './pwd.vue'
const pcanvas = ref(null)
const linkExist = ref(false)
const loading = ref(true)
const linkExp = ref(false)
const pwdValid = ref(false)
onMounted(async () => {
const proxyInfo = (await shareProxy.loadProxy()) as ProxyInfo
if (!proxyInfo?.resourceId) {
loading.value = false
return
}
linkExist.value = true
linkExp.value = !!proxyInfo.exp
pwdValid.value = !!proxyInfo.pwdValid
nextTick(() => {
const method = pcanvas?.value?.loadCanvasDataAsync
if (method) {
method(proxyInfo.resourceId, proxyInfo.type, null)
}
loading.value = false
})
})
</script>
<style lang="less" scoped>
.link-container {
position: absolute !important;
top: 0;
bottom: 0;
left: 0;
right: 0;
}
.hidden-link {
display: none !important;
}
</style>

View File

@ -0,0 +1,218 @@
<template>
<div class="pwd-body" v-loading="loading">
<div class="pwd-wrapper">
<div class="pwd-content">
<div class="span-header">
<div class="bi-text">
<span style="text-align: center">{{ t('pblink.key_pwd') }} </span>
</div>
</div>
<div class="input-layout">
<div class="input-main">
<div class="div-input">
<el-form ref="pwdForm" :model="form" :rules="rule" size="small" @submit.stop.prevent>
<el-form-item label="" prop="password">
<CustomPassword
v-model="form.password"
maxlength="4"
show-password
class="real-input"
:placeholder="t('pblink.input_placeholder')"
/>
</el-form-item>
</el-form>
</div>
</div>
<div class="abs-input">
<div class="input-text">{{ msg }}</div>
</div>
</div>
<div class="auth-root-class">
<el-button size="small" type="primary" @click="refresh">{{
t('pblink.sure_bt')
}}</el-button>
</div>
</div>
</div>
</div>
</template>
<script lang="ts" setup>
import { ref, reactive, onMounted } from 'vue'
import { useI18n } from '@/hooks/web/useI18n'
import type { FormInstance, FormRules } from 'element-plus-secondary'
import request from '@/config/axios'
import { useAppStoreWithOut } from '@/store/modules/app'
import { rsaEncryp } from '@/utils/encryption'
import { useCache } from '@/hooks/web/useCache'
import { queryDekey } from '@/api/login'
import { CustomPassword } from '@/components/custom-password'
const { wsCache } = useCache()
const appStore = useAppStoreWithOut()
const { t } = useI18n()
const msg = ref('')
const loading = ref(true)
const pwdForm = ref<FormInstance>()
const form = ref({
password: ''
})
const rule = reactive<FormRules>({
password: [
{ required: true, message: t('pblink.key_pwd'), trigger: 'blur' },
{
required: true,
pattern: /^[a-zA-Z0-9]{4}$/,
message: t('pblink.pwd_format_error'),
trigger: 'blur'
}
]
})
const refresh = () => {
const curLocation = window.location.href
const paramIndex = curLocation.indexOf('?')
const uuidIndex = curLocation.indexOf('de-link/') + 8
const uuid = curLocation.substring(uuidIndex, paramIndex !== -1 ? paramIndex : curLocation.length)
const pwd = form.value.password
const text = uuid + pwd
const ciphertext = rsaEncryp(text)
request.post({ url: '/share/validate', data: { ciphertext } }).then(res => {
if (res.data) {
wsCache.set(`link-${uuid}`, ciphertext)
window.location.reload()
} else {
msg.value = '密码错误'
}
})
}
onMounted(() => {
if (!wsCache.get(appStore.getDekey)) {
queryDekey()
.then(res => {
wsCache.set(appStore.getDekey, res.data)
})
.finally(() => {
loading.value = false
})
}
loading.value = false
})
</script>
<style lang="less" scoped>
.pwd-body {
position: absolute;
width: 100%;
margin: 0;
padding: 0;
top: 0;
left: 0;
background-repeat: repeat;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
-o-user-select: none;
user-select: none;
color: #3d4d66;
// font: normal 12px Helvetica Neue,Arial,PingFang SC,Hiragino Sans GB,Microsoft YaHei,,Heiti,,sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-decoration: none;
-kthml-user-focus: normal;
-moz-user-focus: normal;
-moz-outline: 0 none;
outline: 0 none;
height: 100%;
display: block;
}
.pwd-wrapper {
background-color: #f7f8fa;
height: 100%;
justify-content: center !important;
align-items: center !important;
min-height: 25px;
display: flex;
-moz-flex-direction: row;
-o-flex-direction: row;
flex-direction: row;
-moz-justify-content: flex-start;
-ms-justify-content: flex-start;
-o-justify-content: flex-start;
justify-content: flex-start;
-moz-align-items: flex-start;
-ms-align-items: flex-start;
-o-align-items: flex-start;
align-items: flex-start;
-o-flex-wrap: nowrap;
flex-wrap: nowrap;
}
.pwd-content {
width: 450px;
height: 250px;
position: relative;
flex-shrink: 0;
background-color: #ffffff;
display: block;
}
.span-header {
position: relative;
margin: 57px auto 0px;
justify-content: center !important;
align-items: center !important;
}
.bi-text {
max-width: 100%;
text-align: center;
white-space: pre;
text-overflow: ellipsis;
position: relative;
flex-shrink: 0;
box-sizing: border-box;
overflow: hidden;
overflow-x: hidden;
overflow-y: hidden;
word-break: break-all;
display: block;
}
.input-layout {
width: 200px;
position: relative;
margin: 0px auto;
padding: 0;
display: block;
}
.input-main {
width: 192px;
height: 35px;
position: relative;
margin-top: 30px;
// border: 1px solid #e8eaed;
display: block;
}
.abs-input {
height: 20px;
position: relative;
margin-top: 5px;
display: block;
}
.input-text {
height: 20px;
line-height: 20px;
text-align: center;
white-space: pre;
text-overflow: ellipsis;
left: 0px;
top: 0px;
bottom: 0px;
position: absolute;
color: #e65251;
box-sizing: border-box;
}
.auth-root-class {
margin: 15px 0px 5px;
text-align: center;
}
</style>

View File

@ -0,0 +1,267 @@
<script lang="ts" setup>
import { useI18n } from '@/hooks/web/useI18n'
import { ref, reactive, watch, computed } from 'vue'
import GridTable from '@/components/grid-table/src/GridTable.vue'
import request from '@/config/axios'
import dayjs from 'dayjs'
import { propTypes } from '@/utils/propTypes'
import ShareHandler from './ShareHandler.vue'
import { interactiveStoreWithOut } from '@/store/modules/interactive'
const props = defineProps({
activeName: propTypes.string.def('')
})
const { t } = useI18n()
const interactiveStore = interactiveStoreWithOut()
const busiDataMap = computed(() => interactiveStore.getData)
const panelKeyword = ref()
const userAddPopper = ref(false)
const activeCommand = ref('all_types')
const state = reactive({
tableData: [],
curTypeList: ['all_types', 'panel', 'screen'],
tableColumn: [
{ field: 'creator', label: '分享人' },
{ field: 'time', label: '分享时间', type: 'time' },
{ field: 'exp', label: '有效期', type: 'time' }
]
})
const handleVisibleChange = (val: boolean) => {
userAddPopper.value = val
}
const handleCommand = (command: string) => {
activeCommand.value = command
loadTableData()
}
const triggerFilterPanel = () => {
loadTableData()
}
const preview = id => {
const routeUrl = `/#/preview?dvId=${id}`
window.open(routeUrl, '_blank')
}
const formatterTime = (_, _column, cellValue) => {
if (!cellValue) {
return '-'
}
return dayjs(new Date(cellValue)).format('YYYY-MM-DD HH:mm:ss')
}
const showLoading = () => {
emits('setLoading', true)
}
const closeLoading = () => {
emits('setLoading', false)
}
const emits = defineEmits(['setLoading'])
const loadTableData = () => {
showLoading()
const queryType = activeCommand.value === 'all_types' ? '' : activeCommand.value
request
.post({
url: '/share/query',
data: { type: queryType, keyword: panelKeyword.value, asc: !orderDesc.value }
})
.then(res => {
state.tableData = res.data
})
.finally(() => {
imgType.value = getEmptyImg()
emptyDesc.value = getEmptyDesc()
closeLoading()
})
}
const orderDesc = ref(true)
const sortChange = param => {
orderDesc.value = true
const type = param.order.substring(0, param.order.indexOf('ending'))
orderDesc.value = type === 'desc'
loadTableData()
}
const getBusiListWithPermission = () => {
const baseFlagList: string[] = ['panel', 'screen']
const busiFlagList: string[] = []
for (const key in busiDataMap.value) {
if (busiDataMap.value[key].menuAuth) {
busiFlagList.push(baseFlagList[parseInt(key)])
}
}
return busiFlagList
}
const busiAuthList: string[] = getBusiListWithPermission()
const imgType = ref()
const emptyDesc = ref('')
const getEmptyImg = (): string => {
if (panelKeyword.value) {
return 'tree'
}
return 'noneWhite'
}
const getEmptyDesc = (): string => {
if (panelKeyword.value) {
return '没有找到相关内容'
}
return ''
}
watch(
() => props.activeName,
() => {
if (props.activeName === 'share') {
loadTableData()
}
},
{
immediate: true
}
)
</script>
<template>
<el-row v-if="props.activeName === 'share'">
<el-col :span="12">
<el-dropdown
placement="bottom-start"
@visible-change="handleVisibleChange"
popper-class="menu-panel-select_popper"
@command="handleCommand"
trigger="click"
>
<el-button secondary>
{{ t(`auth.${activeCommand}`) }}
<el-icon style="margin-left: 4px">
<arrow-up v-if="userAddPopper" />
<arrow-down v-else />
</el-icon>
</el-button>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item
:class="activeCommand === ele && 'active'"
v-for="ele in state.curTypeList.filter(
busi => busi === 'all_types' || busiAuthList.includes(busi)
)"
:command="ele"
:key="ele"
>
{{ t(`auth.${ele}`) }}
<el-icon v-if="activeCommand === ele">
<Icon name="icon_done_outlined"></Icon>
</el-icon>
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
</el-col>
<el-col class="search" :span="12">
<el-input
v-model="panelKeyword"
clearable
@change="triggerFilterPanel"
placeholder="搜索关键词"
>
<template #prefix>
<el-icon>
<Icon name="icon_search-outline_outlined"></Icon>
</el-icon>
</template>
</el-input>
</el-col>
</el-row>
<div v-if="props.activeName === 'share'" class="panel-table">
<GridTable
:show-pagination="false"
:table-data="state.tableData"
@sort-change="sortChange"
:empty-desc="emptyDesc"
:empty-img="imgType"
class="workbranch-grid"
>
<el-table-column key="name" width="280" prop="name" :label="t('common.name')">
<template v-slot:default="scope">
<div class="name-content">
<el-icon class="main-color"> <Icon name="icon_dashboard_outlined" /> </el-icon>
<el-tooltip placement="top">
<template #content>{{ scope.row.name }}</template>
<span class="ellipsis" style="max-width: 250px">{{ scope.row.name }}</span>
</el-tooltip>
</div>
</template>
</el-table-column>
<el-table-column
v-for="item in state.tableColumn"
:key="item.label"
prop="name"
show-overflow-tooltip
:label="item.label"
:sortable="item.type === 'time' && item.field === 'time'"
>
<template #default="scope">
<span v-if="item.type && item.type === 'time'">{{
formatterTime(null, null, scope.row[item.field])
}}</span>
<span v-else>{{ scope.row[item.field] }}</span>
</template>
</el-table-column>
<el-table-column width="96" fixed="right" key="_operation" :label="t('common.operate')">
<template #default="scope">
<el-tooltip effect="dark" content="新页面预览" placement="top">
<el-icon class="hover-icon hover-icon-in-table" @click="preview(scope.row.resourceId)">
<Icon name="icon_pc_outlined"></Icon>
</el-icon>
</el-tooltip>
<ShareHandler
:in-grid="true"
:resource-id="scope.row.resourceId"
:weight="scope.row.weight"
/>
</template>
</el-table-column>
</GridTable>
</div>
</template>
<style lang="less" scoped>
.search {
text-align: right;
.ed-input {
width: 240px;
}
}
.panel-table {
margin-top: 16px;
height: calc(100% - 110px);
.name-content {
display: flex;
align-items: center;
}
.main-color {
font-size: 21.33px;
padding: 5.33px;
margin-right: 12px;
border-radius: 4px;
color: #fff;
background: #3370ff;
}
.name-star {
font-size: 15px;
padding-left: 5px;
}
}
.workbranch-grid :deep(.ed-empty) {
padding: 80px 0 !important;
.ed-empty__description {
margin-top: 0px;
line-height: 20px !important;
}
}
</style>

View File

@ -0,0 +1,370 @@
<template>
<el-tooltip
v-if="props.weight >= 7 && props.inGrid"
effect="dark"
:content="t('visualization.share')"
placement="top"
>
<el-icon class="hover-icon hover-icon-in-table share-button-icon" @click="share">
<Icon name="icon_share-label_outlined"></Icon>
</el-icon>
</el-tooltip>
<el-button v-if="props.weight >= 7 && props.isButton" @click="share" icon="Share">{{
t('visualization.share')
}}</el-button>
<el-dialog
v-if="dialogVisible && props.weight >= 7"
class="copy-link_dialog"
:class="{ 'hidden-footer': !shareEnable }"
v-model="dialogVisible"
:close-on-click-modal="true"
:append-to-body="true"
title="公共链接分享"
width="480px"
:show-close="false"
>
<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" />
{{ shareTips }}
</div>
<div v-if="shareEnable" class="text">{{ linkAddr }}</div>
<div v-if="shareEnable" class="exp-container">
<el-checkbox
:disabled="!shareEnable"
v-model="overTimeEnable"
@change="expEnableSwitcher"
:label="t('visualization.over_time')"
/>
<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"
type="datetime"
placeholder=""
:shortcuts="shortcuts"
@change="expChangeHandler"
:disabled-date="disabledDate"
value-format="x"
/>
<span v-if="expError" class="exp-error">必须大于当前时间</span>
</div>
</div>
<div v-if="shareEnable" class="pwd-container">
<el-checkbox
:disabled="!shareEnable"
v-model="passwdEnable"
@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">
<template #append>
<div @click="resetPwd" class="share-reset-container">
<span>{{ t('commons.reset') }}</span>
</div>
</template>
</el-input>
</div>
</div>
</div>
</div>
<template #footer>
<span class="dialog-footer">
<el-button :disabled="!shareEnable || expError" type="primary" @click="copyInfo">
{{ t('visualization.copy_link') }}
</el-button>
</span>
</template>
</el-dialog>
</template>
<script lang="ts" setup>
import { useI18n } from '@/hooks/web/useI18n'
import { ref, reactive, onMounted, computed } from 'vue'
import request from '@/config/axios'
import { propTypes } from '@/utils/propTypes'
import { ShareInfo, SHARE_BASE, shortcuts } from './option'
import { ElMessage, ElLoading } from 'element-plus-secondary'
import useClipboard from 'vue-clipboard3'
const { toClipboard } = useClipboard()
const { t } = useI18n()
const props = defineProps({
inGrid: propTypes.bool.def(false),
resourceId: propTypes.string.def(''),
resourceType: propTypes.string.def(''),
weight: propTypes.number.def(0),
isButton: propTypes.bool.def(false)
})
const loadingInstance = ref<any>(null)
const dialogVisible = ref(false)
const overTimeEnable = ref(false)
const passwdEnable = ref(false)
const shareEnable = ref(false)
const linkAddr = ref('')
const expError = ref(false)
const state = reactive({
detailInfo: {
id: '',
uuid: '',
pwd: '',
exp: 0
} as ShareInfo
})
const emits = defineEmits(['loaded'])
const shareTips = computed(
() =>
`开启后,用户可以通过该链接访问${props.resourceType === 'dashboard' ? '仪表板' : '数据大屏'}`
)
const copyInfo = async () => {
if (shareEnable.value) {
try {
await toClipboard(linkAddr.value)
ElMessage.success(t('common.copy_success'))
} catch (e) {
ElMessage.warning(t('common.copy_unsupported'))
}
} else {
ElMessage.warning(t('common.copy_unsupported'))
}
dialogVisible.value = false
}
const disabledDate = date => {
return date.getTime() < new Date().getTime()
}
const showLoading = () => {
loadingInstance.value = ElLoading.service({ target: '.share-dialog-container' })
}
const closeLoading = () => {
loadingInstance.value?.close()
}
const share = () => {
dialogVisible.value = true
loadShareInfo()
}
const loadShareInfo = () => {
showLoading()
const resourceId = props.resourceId
const url = `/share/detail/${resourceId}`
request
.get({ url })
.then(res => {
state.detailInfo = { ...res.data }
setPageInfo()
})
.finally(() => {
closeLoading()
})
}
const setPageInfo = () => {
if (state.detailInfo.id && state.detailInfo.uuid) {
shareEnable.value = true
formatLinkAddr()
}
passwdEnable.value = !!state.detailInfo.pwd
overTimeEnable.value = !!state.detailInfo.exp
}
const enableSwitcher = () => {
const resourceId = props.resourceId
const url = `/share/switcher/${resourceId}`
request.post({ url }).then(() => {
loadShareInfo()
})
}
const formatLinkAddr = () => {
const href = window.location.href
const prefix = href.substring(0, href.indexOf('#') + 1)
linkAddr.value = prefix + SHARE_BASE + state.detailInfo.uuid
}
const expEnableSwitcher = val => {
let exp = 0
if (val) {
const now = new Date()
now.setTime(now.getTime() + 3600 * 1000)
exp = now.getTime()
state.detailInfo.exp = exp
}
expChangeHandler(exp)
}
const expChangeHandler = exp => {
if (overTimeEnable.value && exp < new Date().getTime()) {
expError.value = true
return
}
expError.value = false
const resourceId = props.resourceId
const url = '/share/editExp'
const data = { resourceId, exp }
request.post({ url, data }).then(() => {
loadShareInfo()
})
}
const pwdEnableSwitcher = val => {
let pwd = ''
if (val) {
pwd = getUuid()
}
resetPwdHandler(pwd)
}
const resetPwd = () => {
const pwd = getUuid()
resetPwdHandler(pwd)
}
const resetPwdHandler = (pwd?: string) => {
const resourceId = props.resourceId
const url = '/share/editPwd'
const data = { resourceId, pwd }
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 execute = () => {
share()
}
defineExpose({
execute
})
onMounted(() => {
if (!props.inGrid && props.weight >= 7) {
const commandInfo = {
label: '分享',
command: 'share',
svgName: 'dv-share'
}
emits('loaded', commandInfo)
}
})
</script>
<style lang="less">
.copy-link_dialog {
.ed-dialog__header {
padding: 16px 16px 10px !important;
.ed-dialog__title {
font-size: 14px !important;
}
}
.ed-dialog__body {
padding: 16px !important;
}
.ed-dialog__footer {
border-top: 1px solid #1f232926;
padding: 12px 16px 16px;
}
}
.hidden-footer {
.ed-dialog__footer {
display: none !important;
}
}
</style>
<style lang="less" scoped>
.share-button-icon {
margin-left: 4px;
}
.copy-link_dialog {
.exp-container {
.ed-checkbox {
margin-right: 10px;
}
.inline-share-item-picker {
display: flex;
align-items: center;
:deep(.share-exp-picker) {
margin-left: 25px !important;
.ed-input__wrapper {
width: 200px !important;
}
}
.exp-error {
color: var(--ed-color-danger);
font-size: 12px;
}
}
}
.pwd-container {
.ed-checkbox {
margin-right: 10px;
}
.inline-share-item {
margin-left: 25px;
width: 220px;
:deep(.ed-input-group__append) {
width: 45px !important;
background: none;
color: #1f2329;
padding: 0px 0px !important;
.share-reset-container {
width: 100%;
display: flex;
justify-content: center;
}
&:hover {
cursor: pointer;
background-color: #f5f6f7;
}
&:active {
cursor: pointer;
background-color: #eff0f1;
}
}
}
}
.copy-link {
font-weight: 400;
font-family: PingFang SC;
.open-share {
margin: -18px 0 8px 0;
color: #646a73;
font-size: 12px;
font-style: normal;
line-height: 20px;
.ed-switch {
margin-right: 8px;
}
}
.text {
border-radius: 4px;
border: 1px solid #bbbfc4;
background: #eff0f1;
margin-bottom: 16px;
height: 32px;
padding: 5px 12px;
color: #8f959e;
font-size: 14px;
font-style: normal;
line-height: 22px;
}
}
}
</style>

View File

@ -0,0 +1,14 @@
<script lang="ts" setup>
import { useI18n } from '@/hooks/web/useI18n'
import { onMounted } from 'vue'
const { t } = useI18n()
const emits = defineEmits(['loaded'])
const panelInfo = {
title: t('visualization.share_out'),
name: 'share'
}
onMounted(() => {
emits('loaded', panelInfo)
})
</script>

View File

@ -0,0 +1,332 @@
<template>
<el-button 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"
title=""
virtual-triggering
width="480"
placement="bottom-start"
:show-arrow="false"
popper-class="share-popover"
@show="share"
>
<div class="share-container">
<div class="share-title share-padding">公共链接分享</div>
<div class="open-share flex-align-center share-padding">
<el-switch size="small" v-model="shareEnable" @change="enableSwitcher" />
{{ shareTips }}
</div>
<div v-if="shareEnable" class="text share-padding">
<el-input v-model="linkAddr" disabled />
</div>
<div v-if="shareEnable" class="exp-container share-padding">
<el-checkbox
:disabled="!shareEnable"
v-model="overTimeEnable"
@change="expEnableSwitcher"
:label="t('visualization.over_time')"
/>
<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"
type="datetime"
:teleported="false"
placeholder=""
:shortcuts="shortcuts"
@change="expChangeHandler"
:disabled-date="disabledDate"
value-format="x"
/>
<span v-if="expError" class="exp-error">必须大于当前时间</span>
</div>
</div>
<div v-if="shareEnable" class="pwd-container share-padding">
<el-checkbox
:disabled="!shareEnable"
v-model="passwdEnable"
@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">
<template #append>
<div @click="resetPwd" class="share-reset-container">
<span>{{ t('commons.reset') }}</span>
</div>
</template>
</el-input>
</div>
</div>
<el-divider v-if="shareEnable" class="share-divider" />
<div v-if="shareEnable" class="share-foot share-padding">
<el-button :disabled="!shareEnable || expError" type="primary" @click="copyInfo">
{{ t('visualization.copy_link') }}
</el-button>
</div>
</div>
</el-popover>
</template>
<script lang="ts" setup>
import { useI18n } from '@/hooks/web/useI18n'
import { ref, reactive, unref, computed } from 'vue'
import request from '@/config/axios'
import { propTypes } from '@/utils/propTypes'
import { ShareInfo, SHARE_BASE, shortcuts } from './option'
import { ElMessage, ElLoading } from 'element-plus-secondary'
import useClipboard from 'vue-clipboard3'
const { toClipboard } = useClipboard()
const { t } = useI18n()
const props = defineProps({
resourceId: propTypes.string.def(''),
resourceType: propTypes.string.def(''),
weight: propTypes.number.def(0)
})
const shareButtonRef = ref()
const sharePopoverRef = ref()
const loadingInstance = ref<any>(null)
const dialogVisible = ref(false)
const overTimeEnable = ref(false)
const passwdEnable = ref(false)
const shareEnable = ref(false)
const linkAddr = ref('')
const expError = ref(false)
const state = reactive({
detailInfo: {
id: '',
uuid: '',
pwd: '',
exp: 0
} as ShareInfo
})
const openShare = () => {
unref(sharePopoverRef).popperRef?.delayHide?.()
}
const shareTips = computed(
() =>
`开启后,用户可以通过该链接访问${props.resourceType === 'dashboard' ? '仪表板' : '数据大屏'}`
)
const copyInfo = async () => {
if (shareEnable.value) {
try {
await toClipboard(linkAddr.value)
ElMessage.success(t('common.copy_success'))
} catch (e) {
ElMessage.warning(t('common.copy_unsupported'))
}
} else {
ElMessage.warning(t('common.copy_unsupported'))
}
dialogVisible.value = false
openShare()
}
const disabledDate = date => {
return date.getTime() < new Date().getTime()
}
const showLoading = () => {
loadingInstance.value = ElLoading.service({ target: '.share-dialog-container' })
}
const closeLoading = () => {
loadingInstance.value?.close()
}
const share = () => {
dialogVisible.value = true
loadShareInfo()
}
const loadShareInfo = () => {
showLoading()
const resourceId = props.resourceId
const url = `/share/detail/${resourceId}`
request
.get({ url })
.then(res => {
state.detailInfo = { ...res.data }
setPageInfo()
})
.finally(() => {
closeLoading()
})
}
const setPageInfo = () => {
if (state.detailInfo.id && state.detailInfo.uuid) {
shareEnable.value = true
formatLinkAddr()
passwdEnable.value = !!state.detailInfo.pwd
overTimeEnable.value = !!state.detailInfo.exp
} else {
shareEnable.value = false
passwdEnable.value = false
overTimeEnable.value = false
}
}
const enableSwitcher = () => {
const resourceId = props.resourceId
const url = `/share/switcher/${resourceId}`
request.post({ url }).then(() => {
loadShareInfo()
})
}
const formatLinkAddr = () => {
const href = window.location.href
const prefix = href.substring(0, href.indexOf('#') + 1)
linkAddr.value = prefix + SHARE_BASE + state.detailInfo.uuid
}
const expEnableSwitcher = val => {
let exp = 0
if (val) {
const now = new Date()
now.setTime(now.getTime() + 3600 * 1000)
exp = now.getTime()
state.detailInfo.exp = exp
}
expChangeHandler(exp)
}
const expChangeHandler = exp => {
if (overTimeEnable.value && exp < new Date().getTime()) {
expError.value = true
return
}
expError.value = false
const resourceId = props.resourceId
const url = '/share/editExp'
const data = { resourceId, exp }
request.post({ url, data }).then(() => {
loadShareInfo()
})
}
const pwdEnableSwitcher = val => {
let pwd = ''
if (val) {
pwd = getUuid()
}
resetPwdHandler(pwd)
}
const resetPwd = () => {
const pwd = getUuid()
resetPwdHandler(pwd)
}
const resetPwdHandler = (pwd?: string) => {
const resourceId = props.resourceId
const url = '/share/editPwd'
const data = { resourceId, pwd }
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 execute = () => {
share()
}
defineExpose({
execute
})
</script>
<style lang="less">
.share-popover {
padding: 16px 0px !important;
}
</style>
<style lang="less" scoped>
.share-container {
.share-title {
font-weight: 500;
color: #1f2329;
padding-bottom: 5px !important;
}
.share-padding {
padding: 0px 16px;
}
.share-divider {
margin-bottom: 10px !important;
border-top: 1px #1f232926 solid;
}
.share-foot {
display: flex;
justify-content: flex-end;
}
.open-share {
font-size: 12px;
color: #646a73;
.ed-switch {
margin-right: 8px;
}
}
.text {
padding-bottom: 5px !important;
}
}
.inline-share-item-picker {
display: flex;
align-items: center;
:deep(.share-exp-picker) {
margin-left: 25px !important;
.ed-input__wrapper {
width: 200px !important;
}
}
.exp-error {
color: var(--ed-color-danger);
font-size: 12px;
}
}
.inline-share-item {
margin-left: 25px;
width: 220px;
:deep(.ed-input-group__append) {
width: 45px !important;
background: none;
color: #1f2329;
padding: 0px 0px !important;
.share-reset-container {
width: 100%;
display: flex;
justify-content: center;
}
&:hover {
cursor: pointer;
background-color: #f5f6f7;
}
&:active {
cursor: pointer;
background-color: #eff0f1;
}
}
}
</style>

View File

@ -0,0 +1,35 @@
export interface ShareInfo {
id: string
exp?: number
uuid: string
pwd?: string
}
export const SHARE_BASE = '/de-link/'
export const shortcuts = [
{
text: '一小时',
value: () => {
const date = new Date()
date.setTime(date.getTime() + 3600 * 1000)
return date
}
},
{
text: '一天',
value: () => {
const date = new Date()
date.setTime(date.getTime() + 3600 * 1000 * 24)
return date
}
},
{
text: '一周',
value: () => {
const date = new Date()
date.setTime(date.getTime() + 7 * 3600 * 1000 * 24)
return date
}
}
]

View File

@ -6,11 +6,13 @@ import GridTable from '@/components/grid-table/src/GridTable.vue'
import { useRouter } from 'vue-router'
import dayjs from 'dayjs'
import { shortcutOption } from './ShortcutOption'
import { XpackComponent } from '@/components/plugin'
/* import { XpackComponent } from '@/components/plugin' */
import { interactiveStoreWithOut } from '@/store/modules/interactive'
import { storeApi } from '@/api/visualization/dataVisualization'
import { useCache } from '@/hooks/web/useCache'
import { useUserStoreWithOut } from '@/store/modules/user'
import ShareGrid from '@/views/share/share/ShareGrid.vue'
import ShareHandler from '@/views/share/share/ShareHandler.vue'
const userStore = useUserStoreWithOut()
const { resolve } = useRouter()
const { t } = useI18n()
@ -111,17 +113,18 @@ const loadTableData = () => {
})
}
const panelLoad = paneInfo => {
/* const panelLoad = paneInfo => {
tablePaneList.value.push({
title: paneInfo.title,
name: paneInfo.name,
disabled: tablePaneList.value[1].disabled
})
}
} */
const tablePaneList = ref([
{ title: '最近使用', name: 'recent', disabled: false },
{ title: '我的收藏', name: 'store', disabled: false }
{ title: '我的收藏', name: 'store', disabled: false },
{ title: t('visualization.share_out'), name: 'share', disabled: false }
])
const busiAuthList = getBusiListWithPermission()
@ -206,8 +209,9 @@ const getEmptyDesc = (): string => {
</template>
</el-tab-pane>
</el-tabs>
<XpackComponent jsname="c2hhcmUtcGFuZWw=" @loaded="panelLoad" />
<XpackComponent :active-name="activeName" jsname="c2hhcmU=" @set-loading="setLoading" />
<!-- <XpackComponent jsname="c2hhcmUtcGFuZWw=" @loaded="panelLoad" /> -->
<!-- <XpackComponent :active-name="activeName" jsname="c2hhcmU=" @set-loading="setLoading" /> -->
<share-grid :active-name="activeName" @set-loading="setLoading" />
<el-row v-if="activeName === 'recent' || activeName === 'store'">
<el-col :span="12">
<el-select
@ -304,14 +308,19 @@ const getEmptyDesc = (): string => {
<Icon name="icon_pc_outlined"></Icon>
</el-icon>
</el-tooltip>
<XpackComponent
<ShareHandler
:in-grid="true"
:weight="scope.row.weight"
:resource-id="activeName === 'recent' ? scope.row.id : scope.row.resourceId"
:resource-type="scope.row.type"
/>
<!-- <XpackComponent
:in-grid="true"
jsname="c2hhcmUtaGFuZGxlcg=="
:weight="scope.row.weight"
:resource-id="activeName === 'recent' ? scope.row.id : scope.row.resourceId"
:resource-type="scope.row.type"
/>
/> -->
</template>
<template v-if="['dataset'].includes(scope.row.type)">

@ -1 +1 @@
Subproject commit c46269ec581913e102d21ded8690ed387509db87
Subproject commit 39beda8526d237673972ffc5addaf0e8ec569e80