forked from github/dataease
Merge branch 'dev-v2' into pr@dev-v2_export_data
This commit is contained in:
commit
892c395e67
@ -14,18 +14,18 @@ public class MybatisPlusGenerator {
|
||||
* 第一 我嫌麻烦
|
||||
* 第二 后面配置会放到nacos读起来更麻烦了
|
||||
*/
|
||||
private static final String url = "jdbc:mysql://localhost:3306/dataease?autoReconnect=false&useUnicode=true&characterEncoding=UTF-8&characterSetResults=UTF-8&zeroDateTimeBehavior=convertToNull&useSSL=false";
|
||||
private static final String url = "jdbc:mysql://localhost:3306/de_standalone?autoReconnect=false&useUnicode=true&characterEncoding=UTF-8&characterSetResults=UTF-8&zeroDateTimeBehavior=convertToNull&useSSL=false";
|
||||
private static final String username = "root";
|
||||
private static final String password = "123456";
|
||||
private static final String password = "Password123@mysql";
|
||||
|
||||
/**
|
||||
* 业务模块例如datasource,dataset,panel等
|
||||
*/
|
||||
private static final String busi = "visualization";
|
||||
private static final String busi = "share";
|
||||
/**
|
||||
* 这是要生成代码的表名称
|
||||
*/
|
||||
private static final String TABLE_NAME = "data_visualization_info";
|
||||
private static final String TABLE_NAME = "xpack_share";
|
||||
|
||||
/**
|
||||
* 下面两个配置基本上不用动
|
||||
|
@ -1,7 +1,28 @@
|
||||
package io.dataease.datasource.provider;
|
||||
|
||||
import io.dataease.api.dataset.dto.DatasetTableDTO;
|
||||
import io.dataease.api.ds.vo.TableField;
|
||||
import io.dataease.datasource.dao.auto.entity.CoreDatasource;
|
||||
import io.dataease.datasource.request.DatasourceRequest;
|
||||
import io.dataease.exception.DEException;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* @Author Junjun
|
||||
*/
|
||||
public abstract class Provider {
|
||||
public abstract List<String> getSchema(DatasourceRequest datasourceRequest);
|
||||
|
||||
public abstract List<DatasetTableDTO> getTables(DatasourceRequest datasourceRequest);
|
||||
|
||||
public abstract Connection getConnection(CoreDatasource coreDatasource) throws DEException;
|
||||
|
||||
public abstract String checkStatus(DatasourceRequest datasourceRequest) throws Exception;
|
||||
|
||||
public abstract Map<String, Object> fetchResultField(DatasourceRequest datasourceRequest) throws DEException;
|
||||
|
||||
public abstract List<TableField> fetchTableField(DatasourceRequest datasourceRequest) throws DEException;
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package io.dataease.home;
|
||||
|
||||
import io.dataease.license.utils.LicenseUtil;
|
||||
import io.dataease.utils.ModelUtils;
|
||||
import io.dataease.utils.RsaUtils;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
@ -31,7 +32,7 @@ public class RestIndexController {
|
||||
@GetMapping("/xpackModel")
|
||||
@ResponseBody
|
||||
public boolean xpackModel() {
|
||||
return xpackFrontDistributed;
|
||||
return xpackFrontDistributed && LicenseUtil.licenseValid();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,108 @@
|
||||
package io.dataease.share.dao.auto.entity;
|
||||
|
||||
import com.baomidou.mybatisplus.annotation.TableName;
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
*
|
||||
* </p>
|
||||
*
|
||||
* @author fit2cloud
|
||||
* @since 2024-06-21
|
||||
*/
|
||||
@TableName("core_share_ticket")
|
||||
public class CoreShareTicket implements Serializable {
|
||||
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
/**
|
||||
* ID
|
||||
*/
|
||||
private Long id;
|
||||
|
||||
/**
|
||||
* 分享uuid
|
||||
*/
|
||||
private String uuid;
|
||||
|
||||
/**
|
||||
* ticket
|
||||
*/
|
||||
private String ticket;
|
||||
|
||||
/**
|
||||
* ticket有效期
|
||||
*/
|
||||
private Long exp;
|
||||
|
||||
/**
|
||||
* ticket参数
|
||||
*/
|
||||
private String args;
|
||||
|
||||
/**
|
||||
* 首次访问时间
|
||||
*/
|
||||
private Long accessTime;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public void setId(Long id) {
|
||||
this.id = id;
|
||||
}
|
||||
|
||||
public String getUuid() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
public void setUuid(String uuid) {
|
||||
this.uuid = uuid;
|
||||
}
|
||||
|
||||
public String getTicket() {
|
||||
return ticket;
|
||||
}
|
||||
|
||||
public void setTicket(String ticket) {
|
||||
this.ticket = ticket;
|
||||
}
|
||||
|
||||
public Long getExp() {
|
||||
return exp;
|
||||
}
|
||||
|
||||
public void setExp(Long exp) {
|
||||
this.exp = exp;
|
||||
}
|
||||
|
||||
public String getArgs() {
|
||||
return args;
|
||||
}
|
||||
|
||||
public void setArgs(String args) {
|
||||
this.args = args;
|
||||
}
|
||||
|
||||
public Long getAccessTime() {
|
||||
return accessTime;
|
||||
}
|
||||
|
||||
public void setAccessTime(Long accessTime) {
|
||||
this.accessTime = accessTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "CoreShareTicket{" +
|
||||
"id = " + id +
|
||||
", uuid = " + uuid +
|
||||
", ticket = " + ticket +
|
||||
", exp = " + exp +
|
||||
", args = " + args +
|
||||
", accessTime = " + accessTime +
|
||||
"}";
|
||||
}
|
||||
}
|
@ -9,7 +9,7 @@ import java.io.Serializable;
|
||||
* </p>
|
||||
*
|
||||
* @author fit2cloud
|
||||
* @since 2024-04-07
|
||||
* @since 2024-06-21
|
||||
*/
|
||||
@TableName("xpack_share")
|
||||
public class XpackShare implements Serializable {
|
||||
@ -66,6 +66,11 @@ public class XpackShare implements Serializable {
|
||||
*/
|
||||
private Boolean autoPwd;
|
||||
|
||||
/**
|
||||
* ticket必须
|
||||
*/
|
||||
private Boolean ticketRequire;
|
||||
|
||||
public Long getId() {
|
||||
return id;
|
||||
}
|
||||
@ -146,6 +151,14 @@ public class XpackShare implements Serializable {
|
||||
this.autoPwd = autoPwd;
|
||||
}
|
||||
|
||||
public Boolean getTicketRequire() {
|
||||
return ticketRequire;
|
||||
}
|
||||
|
||||
public void setTicketRequire(Boolean ticketRequire) {
|
||||
this.ticketRequire = ticketRequire;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "XpackShare{" +
|
||||
@ -159,6 +172,7 @@ public class XpackShare implements Serializable {
|
||||
", oid = " + oid +
|
||||
", type = " + type +
|
||||
", autoPwd = " + autoPwd +
|
||||
", ticketRequire = " + ticketRequire +
|
||||
"}";
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,18 @@
|
||||
package io.dataease.share.dao.auto.mapper;
|
||||
|
||||
import io.dataease.share.dao.auto.entity.CoreShareTicket;
|
||||
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
* Mapper 接口
|
||||
* </p>
|
||||
*
|
||||
* @author fit2cloud
|
||||
* @since 2024-06-21
|
||||
*/
|
||||
@Mapper
|
||||
public interface CoreShareTicketMapper extends BaseMapper<CoreShareTicket> {
|
||||
|
||||
}
|
@ -10,7 +10,7 @@ import org.apache.ibatis.annotations.Mapper;
|
||||
* </p>
|
||||
*
|
||||
* @author fit2cloud
|
||||
* @since 2024-04-07
|
||||
* @since 2024-06-21
|
||||
*/
|
||||
@Mapper
|
||||
public interface XpackShareMapper extends BaseMapper<XpackShare> {
|
||||
|
@ -6,6 +6,7 @@ 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;
|
||||
import org.apache.ibatis.annotations.Update;
|
||||
|
||||
@Mapper
|
||||
public interface XpackShareExtMapper {
|
||||
@ -28,4 +29,7 @@ public interface XpackShareExtMapper {
|
||||
|
||||
@Select("select type from data_visualization_info where id = #{id}")
|
||||
String visualizationType(@Param("id") Long id);
|
||||
|
||||
@Update("update core_share_ticket set uuid = #{ticketUuid} where uuid = #{originUuid}")
|
||||
void updateTicketUuid(@Param("originUuid") String originUuid, @Param("ticketUuid") String ticketUuid);
|
||||
}
|
||||
|
@ -0,0 +1,153 @@
|
||||
package io.dataease.share.manage;
|
||||
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
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.api.xpack.share.vo.TicketValidVO;
|
||||
import io.dataease.commons.utils.CodingUtil;
|
||||
import io.dataease.exception.DEException;
|
||||
import io.dataease.share.dao.auto.entity.CoreShareTicket;
|
||||
import io.dataease.share.dao.auto.entity.XpackShare;
|
||||
import io.dataease.share.dao.auto.mapper.CoreShareTicketMapper;
|
||||
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.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;
|
||||
|
||||
@Component
|
||||
public class ShareTicketManage {
|
||||
|
||||
@Resource
|
||||
private CoreShareTicketMapper coreShareTicketMapper;
|
||||
|
||||
@Resource
|
||||
private XpackShareMapper xpackShareMapper;
|
||||
|
||||
@Resource
|
||||
private XpackShareExtMapper xpackShareExtMapper;
|
||||
|
||||
public CoreShareTicket getByTicket(String ticket) {
|
||||
QueryWrapper<CoreShareTicket> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq("ticket", ticket);
|
||||
return coreShareTicketMapper.selectOne(queryWrapper);
|
||||
}
|
||||
|
||||
public String saveTicket(TicketCreator creator) {
|
||||
String ticket = creator.getTicket();
|
||||
if (StringUtils.isNotBlank(ticket)) {
|
||||
CoreShareTicket ticketEntity = getByTicket(ticket);
|
||||
if (ObjectUtils.isNotEmpty(ticketEntity)) {
|
||||
if (creator.isGenerateNew()) {
|
||||
ticketEntity.setAccessTime(null);
|
||||
ticketEntity.setTicket(CodingUtil.shortUuid());
|
||||
}
|
||||
ticketEntity.setArgs(creator.getArgs());
|
||||
ticketEntity.setExp(creator.getExp());
|
||||
ticketEntity.setUuid(creator.getUuid());
|
||||
coreShareTicketMapper.updateById(ticketEntity);
|
||||
return ticketEntity.getTicket();
|
||||
}
|
||||
}
|
||||
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);
|
||||
return ticket;
|
||||
}
|
||||
|
||||
public void deleteTicket(TicketDelRequest request) {
|
||||
String ticket = request.getTicket();
|
||||
if (StringUtils.isBlank(ticket)) {
|
||||
DEException.throwException("ticket为必填参数");
|
||||
}
|
||||
QueryWrapper<CoreShareTicket> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq("ticket", ticket);
|
||||
coreShareTicketMapper.delete(queryWrapper);
|
||||
}
|
||||
|
||||
public void switchRequire(TicketSwitchRequest request) {
|
||||
String resourceId = request.getResourceId();
|
||||
Boolean require = request.getRequire();
|
||||
QueryWrapper<XpackShare> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq("resource_id", resourceId);
|
||||
queryWrapper.eq("creator", AuthUtils.getUser().getUserId());
|
||||
XpackShare xpackShare = xpackShareMapper.selectOne(queryWrapper);
|
||||
xpackShare.setTicketRequire(require);
|
||||
xpackShareMapper.updateById(xpackShare);
|
||||
}
|
||||
|
||||
public List<TicketVO> query(Long resourceId) {
|
||||
QueryWrapper<XpackShare> queryWrapper = new QueryWrapper<>();
|
||||
queryWrapper.eq("resource_id", resourceId);
|
||||
queryWrapper.eq("creator", AuthUtils.getUser().getUserId());
|
||||
XpackShare xpackShare = xpackShareMapper.selectOne(queryWrapper);
|
||||
if (ObjectUtils.isEmpty(xpackShare)) return null;
|
||||
String uuid = xpackShare.getUuid();
|
||||
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();
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void updateByUuidChange(String originalUuid, String newUuid) {
|
||||
xpackShareExtMapper.updateTicketUuid(originalUuid, newUuid);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void deleteByShare(String uuid) {
|
||||
QueryWrapper<CoreShareTicket> ticketQueryWrapper = new QueryWrapper<>();
|
||||
ticketQueryWrapper.eq("uuid", uuid);
|
||||
coreShareTicketMapper.delete(ticketQueryWrapper);
|
||||
}
|
||||
|
||||
public TicketValidVO validateTicket(String ticket, XpackShare share) {
|
||||
TicketValidVO vo = new TicketValidVO();
|
||||
if (StringUtils.isBlank(ticket)) {
|
||||
vo.setTicketValid(!share.getTicketRequire());
|
||||
return vo;
|
||||
}
|
||||
CoreShareTicket linkTicket = getByTicket(ticket);
|
||||
if (ObjectUtils.isEmpty(linkTicket)) {
|
||||
vo.setTicketValid(false);
|
||||
return vo;
|
||||
}
|
||||
vo.setTicketValid(true);
|
||||
vo.setArgs(linkTicket.getArgs());
|
||||
Long accessTime = linkTicket.getAccessTime();
|
||||
long now = System.currentTimeMillis();
|
||||
if (ObjectUtils.isEmpty(accessTime)) {
|
||||
accessTime = now;
|
||||
vo.setTicketExp(false);
|
||||
linkTicket.setAccessTime(accessTime);
|
||||
coreShareTicketMapper.updateById(linkTicket);
|
||||
return vo;
|
||||
}
|
||||
Long exp = linkTicket.getExp();
|
||||
if (ObjectUtils.isEmpty(exp) || exp.equals(0L)) {
|
||||
vo.setTicketExp(false);
|
||||
return vo;
|
||||
}
|
||||
long expTime = exp * 60L * 1000L;
|
||||
long time = now - accessTime;
|
||||
vo.setTicketExp(time > expTime);
|
||||
return vo;
|
||||
}
|
||||
}
|
@ -7,6 +7,7 @@ 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.request.XpackShareUuidEditor;
|
||||
import io.dataease.api.xpack.share.vo.TicketValidVO;
|
||||
import io.dataease.api.xpack.share.vo.XpackShareGridVO;
|
||||
import io.dataease.api.xpack.share.vo.XpackShareProxyVO;
|
||||
import io.dataease.auth.bo.TokenUserBO;
|
||||
@ -28,6 +29,7 @@ import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.apache.commons.lang3.RandomStringUtils;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
@ -46,6 +48,9 @@ public class XpackShareManage {
|
||||
@Resource(name = "xpackShareExtMapper")
|
||||
private XpackShareExtMapper xpackShareExtMapper;
|
||||
|
||||
@Resource
|
||||
private ShareTicketManage shareTicketManage;
|
||||
|
||||
public XpackShare queryByResource(Long resourceId) {
|
||||
Long userId = AuthUtils.getUser().getUserId();
|
||||
QueryWrapper<XpackShare> queryWrapper = new QueryWrapper<>();
|
||||
@ -54,10 +59,12 @@ public class XpackShareManage {
|
||||
return xpackShareMapper.selectOne(queryWrapper);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void switcher(Long resourceId) {
|
||||
XpackShare originData = queryByResource(resourceId);
|
||||
if (ObjectUtils.isNotEmpty(originData)) {
|
||||
xpackShareMapper.deleteById(originData.getId());
|
||||
shareTicketManage.deleteByShare(originData.getUuid());
|
||||
return;
|
||||
}
|
||||
TokenUserBO user = AuthUtils.getUser();
|
||||
@ -74,6 +81,7 @@ public class XpackShareManage {
|
||||
xpackShareMapper.insert(xpackShare);
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public String editUuid(XpackShareUuidEditor editor) {
|
||||
Long resourceId = editor.getResourceId();
|
||||
String uuid = editor.getUuid();
|
||||
@ -98,6 +106,7 @@ public class XpackShareManage {
|
||||
if (!matcher.matches()) {
|
||||
return "仅支持8-16位(字母数字),请重新输入!";
|
||||
}
|
||||
shareTicketManage.updateByUuidChange(originData.getUuid(), uuid);
|
||||
originData.setUuid(uuid);
|
||||
xpackShareMapper.updateById(originData);
|
||||
return "";
|
||||
@ -196,7 +205,8 @@ public class XpackShareManage {
|
||||
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, inIframeError);
|
||||
TicketValidVO validVO = shareTicketManage.validateTicket(request.getTicket(), xpackShare);
|
||||
return new XpackShareProxyVO(xpackShare.getResourceId(), xpackShare.getCreator(), linkExp(xpackShare), pwdValid(xpackShare, request.getCiphertext()), typeText, inIframeError, validVO);
|
||||
}
|
||||
|
||||
private boolean linkExp(XpackShare xpackShare) {
|
||||
|
@ -0,0 +1,40 @@
|
||||
package io.dataease.share.server;
|
||||
|
||||
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.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")
|
||||
public class ShareTicketServer implements ShareTicketApi {
|
||||
|
||||
@Resource
|
||||
private ShareTicketManage shareTicketManage;
|
||||
|
||||
@Override
|
||||
public String saveTicket(TicketCreator creator) {
|
||||
return shareTicketManage.saveTicket(creator);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void deleteTicket(TicketDelRequest request) {
|
||||
shareTicketManage.deleteTicket(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void switchRequire(TicketSwitchRequest request) {
|
||||
shareTicketManage.switchRequire(request);
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<TicketVO> query(Long resourceId) {
|
||||
return shareTicketManage.query(resourceId);
|
||||
}
|
||||
}
|
@ -76,7 +76,7 @@ public class StaticResourceServer implements StaticResourceApi {
|
||||
return true;
|
||||
}
|
||||
// 判断其他图片
|
||||
if (image == null || image.getWidth() <= 0 || image.getHeight() <= 0 || !isValidSVG(file)) {
|
||||
if (image == null || image.getWidth() <= 0 || image.getHeight() <= 0) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
@ -1 +1,36 @@
|
||||
ALTER TABLE `core_export_task` ADD COLUMN `msg` LONGTEXT NULL COMMENT '错误信息' AFTER `params`;
|
||||
ALTER TABLE `core_export_task`
|
||||
ADD COLUMN `msg` LONGTEXT NULL COMMENT '错误信息' AFTER `params`;
|
||||
|
||||
|
||||
DROP TABLE IF EXISTS `xpack_plugin`;
|
||||
CREATE TABLE `xpack_plugin`
|
||||
(
|
||||
`id` bigint NOT NULL COMMENT 'ID',
|
||||
`name` varchar(255) NOT NULL COMMENT '插件名称',
|
||||
`icon` longtext NOT NULL COMMENT '图标',
|
||||
`version` varchar(255) NOT NULL COMMENT '版本',
|
||||
`install_time` bigint NOT NULL COMMENT '安装时间',
|
||||
`flag` varchar(255) NOT NULL COMMENT '类型',
|
||||
`developer` varchar(255) NOT NULL COMMENT '开发者',
|
||||
`config` longtext NOT NULL COMMENT '插件配置',
|
||||
`require_version` varchar(255) NOT NULL COMMENT 'DE最低版本',
|
||||
`module_name` varchar(255) NOT NULL COMMENT '模块名称',
|
||||
`jar_name` varchar(255) NOT NULL COMMENT 'Jar包名称',
|
||||
PRIMARY KEY (`id`)
|
||||
) COMMENT ='插件表';
|
||||
|
||||
ALTER TABLE `xpack_share`
|
||||
ADD COLUMN `ticket_require` tinyint(1) NOT NULL DEFAULT 0 COMMENT 'ticket必须' AFTER `auto_pwd`;
|
||||
|
||||
|
||||
DROP TABLE IF EXISTS `core_share_ticket`;
|
||||
CREATE TABLE `core_share_ticket`
|
||||
(
|
||||
`id` bigint NOT NULL COMMENT 'ID',
|
||||
`uuid` varchar(255) NOT NULL COMMENT '分享uuid',
|
||||
`ticket` varchar(255) NOT NULL COMMENT 'ticket',
|
||||
`exp` bigint DEFAULT NULL COMMENT 'ticket有效期',
|
||||
`args` longtext COMMENT 'ticket参数',
|
||||
`access_time` bigint DEFAULT NULL COMMENT '首次访问时间',
|
||||
PRIMARY KEY (`id`)
|
||||
) COMMENT ='分享Ticket表';
|
@ -26,6 +26,6 @@ export default {
|
||||
}
|
||||
}
|
||||
},
|
||||
sourcemap: false
|
||||
sourcemap: true
|
||||
}
|
||||
}
|
||||
|
@ -14,7 +14,7 @@ export default {
|
||||
],
|
||||
build: {
|
||||
rollupOptions: {
|
||||
external: id => /de-xpack/.test(id) || /extensions-view-3dpie/.test(id),
|
||||
external: id => /de-xpack/.test(id) || /extensions/.test(id),
|
||||
output: {
|
||||
// 用于命名代码拆分时创建的共享块的输出命名
|
||||
chunkFileNames: `assets/chunk/[name]-${pkg.version}-${pkg.name}.js`,
|
||||
|
@ -1,6 +1,7 @@
|
||||
import request from '@/config/axios'
|
||||
|
||||
export const queryUserApi = data => request.post({ url: '/user/byCurOrg', data })
|
||||
export const queryUserOptionsApi = () => request.get({ url: '/user/org/option' })
|
||||
export const queryRoleApi = data => request.post({ url: '/role/byCurOrg', data })
|
||||
|
||||
export const resourceTreeApi = (flag: string) => request.get({ url: '/auth/busiResource/' + flag })
|
||||
|
1
core/core-frontend/src/assets/svg/dv-ruler.svg
Normal file
1
core/core-frontend/src/assets/svg/dv-ruler.svg
Normal file
@ -0,0 +1 @@
|
||||
<?xml version="1.0" standalone="no"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg t="1719155593625" class="icon" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg" p-id="32183" xmlns:xlink="http://www.w3.org/1999/xlink" width="200" height="200"><path d="M658.52 0v658.52H0V1024h1024V0H658.52z m23.1 991.3V835.18h-32.7V991.3H465.31V790.06h-32.7V991.3H249.02V835.18h-32.7V991.3H32.7V691.22h658.52V32.7H991.3v183.61H835.18v32.7H991.3v183.61H790.06v32.7H991.3v183.59H835.18v32.7H991.3v309.68H681.62z" p-id="32184"></path></svg>
|
After Width: | Height: | Size: 604 B |
3
core/core-frontend/src/assets/svg/edit-done.svg
Normal file
3
core/core-frontend/src/assets/svg/edit-done.svg
Normal file
@ -0,0 +1,3 @@
|
||||
<svg width="24" height="24" viewBox="0 0 24 24" fill="" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M12 21C16.9706 21 21 16.9706 21 12C21 7.02944 16.9706 3 12 3C7.02944 3 3 7.02944 3 12C3 16.9706 7.02944 21 12 21ZM12 23C5.92487 23 1 18.0751 1 12C1 5.92487 5.92487 1 12 1C18.0751 1 23 5.92487 23 12C23 18.0751 18.0751 23 12 23ZM10.84 14.2799L16.1433 8.97665C16.3386 8.78138 16.6551 8.78138 16.8504 8.97665L17.5575 9.68375C17.7528 9.87901 17.7528 10.1956 17.5575 10.3909L11.1936 16.7548C10.9983 16.9501 10.6817 16.9501 10.4864 16.7548L6.82603 13.0944C6.63077 12.8991 6.63077 12.5826 6.82603 12.3873L7.53314 11.6802C7.7284 11.4849 8.04498 11.4849 8.24024 11.6802L10.84 14.2799Z" fill=""/>
|
||||
</svg>
|
After Width: | Height: | Size: 694 B |
@ -157,6 +157,7 @@ onUnmounted(() => {
|
||||
background-color: @side-area-background;
|
||||
border-top: 1px solid @side-outline-border-color;
|
||||
color: #fff;
|
||||
z-index: 2;
|
||||
transition: 0.5s;
|
||||
.scale-area {
|
||||
display: flex;
|
||||
|
@ -160,6 +160,7 @@ const dragOnEnd = ({ oldIndex, newIndex }) => {
|
||||
componentData.value.splice(comLength - 1 - oldIndex, 1)
|
||||
componentData.value.splice(comLength - 1 - newIndex, 0, target)
|
||||
dvMainStore.setCurComponent({ component: target, index: transformIndex(comLength - oldIndex) })
|
||||
snapshotStore.recordSnapshotCache()
|
||||
}
|
||||
|
||||
const getIconName = item => {
|
||||
|
@ -1490,25 +1490,6 @@ defineExpose({
|
||||
@linkJumpSetOpen="linkJumpSetOpen(item)"
|
||||
@linkageSetOpen="linkageSetOpen(item)"
|
||||
>
|
||||
<!--如果是图表 则动态获取预存的chart-view数据-->
|
||||
<!-- <PluginComponent
|
||||
v-if="item['isPlugin']"
|
||||
:jsname="item['pluginFlag'] || 'L2NvbXBvbmVudC9pbmRleA=='"
|
||||
class="component"
|
||||
:id="'component' + item.id"
|
||||
:active="item.id === curComponentId"
|
||||
:dv-type="dvInfo.type"
|
||||
:scale="curBaseScale"
|
||||
:style="getComponentStyle(item.style)"
|
||||
:prop-value="item.propValue"
|
||||
:is-edit="true"
|
||||
:view="canvasViewInfo[item.id]"
|
||||
:element="item"
|
||||
:request="item.request"
|
||||
@input="handleInput"
|
||||
:dv-info="dvInfo"
|
||||
:canvas-active="canvasActive"
|
||||
/> -->
|
||||
<component
|
||||
:is="findComponent(item.component)"
|
||||
v-if="item.component === 'UserView' || item['isPlugin']"
|
||||
|
@ -2,7 +2,7 @@
|
||||
import noLic from './nolic.vue'
|
||||
import { ref, useAttrs, onMounted } from 'vue'
|
||||
import { execute, randomKey, formatArray } from './convert'
|
||||
import { loadPluginApi, loadDistributed, xpackModelApi } from '@/api/plugin'
|
||||
import { loadPluginApi, xpackModelApi } from '@/api/plugin'
|
||||
import { useCache } from '@/hooks/web/useCache'
|
||||
import { i18n } from '@/plugins/vue-i18n'
|
||||
import * as Vue from 'vue'
|
||||
@ -10,7 +10,7 @@ import axios from 'axios'
|
||||
import * as Pinia from 'pinia'
|
||||
import * as vueRouter from 'vue-router'
|
||||
import { useEmitt } from '@/hooks/web/useEmitt'
|
||||
|
||||
import request from '@/config/axios'
|
||||
const { wsCache } = useCache()
|
||||
|
||||
const plugin = ref()
|
||||
@ -33,15 +33,12 @@ const generateRamStr = (len: number) => {
|
||||
}
|
||||
|
||||
const importProxy = (bytesArray: any[]) => {
|
||||
/* const promise = import(
|
||||
`../../../../../../../${formatArray(bytesArray[7])}/${formatArray(bytesArray[8])}/${formatArray(
|
||||
bytesArray[9]
|
||||
)}/${formatArray(bytesArray[10])}/${formatArray(bytesArray[11])}.vue`
|
||||
) */
|
||||
const promise = import(
|
||||
`../../../../../../../extensions-view-3dpie/${formatArray(bytesArray[8])}/${formatArray(
|
||||
`../../../../../../../extensions/${formatArray(bytesArray[8])}/${formatArray(
|
||||
bytesArray[9]
|
||||
)}/${formatArray(bytesArray[10])}/${formatArray(bytesArray[11])}.vue`
|
||||
)}/${formatArray(bytesArray[10])}/${formatArray(bytesArray[11])}/${formatArray(
|
||||
bytesArray[12]
|
||||
)}.vue`
|
||||
)
|
||||
promise
|
||||
.then((res: any) => {
|
||||
@ -53,16 +50,23 @@ const importProxy = (bytesArray: any[]) => {
|
||||
})
|
||||
}
|
||||
|
||||
const getModuleName = () => {
|
||||
const jsPath = window.atob(attrs.jsname.toString())
|
||||
return jsPath.split('/')[0]
|
||||
}
|
||||
const loadComponent = () => {
|
||||
const moduleName = getModuleName()
|
||||
loading.value = true
|
||||
const byteArray = wsCache.get(`de-plugin-proxy-plugin`)
|
||||
const byteArray = wsCache.get(`de-plugin-proxy-${moduleName}`)
|
||||
if (byteArray) {
|
||||
importProxy(JSON.parse(byteArray))
|
||||
loading.value = false
|
||||
return
|
||||
}
|
||||
const key = generateRamStr(randomKey())
|
||||
loadPluginApi(key)
|
||||
const moduleNameKey = window.btoa(moduleName)
|
||||
const saltKey = `${key},${moduleNameKey}`
|
||||
loadPluginApi(saltKey)
|
||||
.then(response => {
|
||||
let code = response.data
|
||||
const byteArray = execute(code, key)
|
||||
@ -82,7 +86,8 @@ const storeCacheProxy = byteArray => {
|
||||
byteArray.forEach(item => {
|
||||
result.push([...item])
|
||||
})
|
||||
wsCache.set(`de-plugin-proxy-plugin`, JSON.stringify(result))
|
||||
const moduleName = getModuleName()
|
||||
wsCache.set(`de-plugin-proxy-${moduleName}`, JSON.stringify(result))
|
||||
}
|
||||
const pluginProxy = ref(null)
|
||||
const invokeMethod = param => {
|
||||
@ -104,8 +109,9 @@ onMounted(async () => {
|
||||
distributed = wsCache.get(key)
|
||||
}
|
||||
if (distributed) {
|
||||
if (window['DEXPack']) {
|
||||
const xpack = await window['DEXPack'].mapping[attrs.jsname]
|
||||
const moduleName = getModuleName()
|
||||
if (window[moduleName]) {
|
||||
const xpack = await window[moduleName].mapping[attrs.jsname]
|
||||
plugin.value = xpack.default
|
||||
} else {
|
||||
window['Vue'] = Vue
|
||||
@ -114,9 +120,10 @@ onMounted(async () => {
|
||||
window['vueRouter'] = vueRouter
|
||||
window['MittAll'] = useEmitt().emitter.all
|
||||
window['I18n'] = i18n
|
||||
loadDistributed().then(async res => {
|
||||
new Function(res.data)()
|
||||
const xpack = await window['DEXPack'].mapping[attrs.jsname]
|
||||
const url = `/xpackComponent/pluginStaticInfo/${moduleName}`
|
||||
request.get({ url }).then(async res => {
|
||||
new Function(res.data || res)()
|
||||
const xpack = await window[moduleName].mapping[attrs.jsname]
|
||||
plugin.value = xpack.default
|
||||
})
|
||||
}
|
||||
|
@ -618,7 +618,7 @@ defineExpose({
|
||||
}
|
||||
|
||||
:deep(.ed-tree--highlight-current .ed-tree-node.is-current > .ed-tree-node__content) {
|
||||
background-color: #8dbbef !important;
|
||||
background-color: rgba(51, 112, 255, 0.1) !important;
|
||||
}
|
||||
|
||||
.tree-content ::deep(.ed-input__inner) {
|
||||
|
@ -20,7 +20,7 @@
|
||||
id="input"
|
||||
ref="files"
|
||||
type="file"
|
||||
accept=".jpeg,.jpg,.png,.gif"
|
||||
accept=".jpeg,.jpg,.png,.gif,.svg"
|
||||
hidden
|
||||
@click="
|
||||
e => {
|
||||
|
@ -4,7 +4,7 @@
|
||||
id="input"
|
||||
ref="files"
|
||||
type="file"
|
||||
accept=".jpeg,.jpg,.png,.gif"
|
||||
accept=".jpeg,.jpg,.png,.gif,.svg"
|
||||
hidden
|
||||
@click="
|
||||
e => {
|
||||
|
@ -4,7 +4,7 @@
|
||||
id="input"
|
||||
ref="files"
|
||||
type="file"
|
||||
accept=".jpeg,.jpg,.png,.gif"
|
||||
accept=".jpeg,.jpg,.png,.gif,.svg"
|
||||
hidden
|
||||
@click="
|
||||
e => {
|
||||
|
@ -173,6 +173,8 @@ service.interceptors.response.use(
|
||||
return response
|
||||
} else if (response.config.url.includes('DEXPack.umd.js')) {
|
||||
return response
|
||||
} else if (response.config.url.startsWith('/xpackComponent/pluginStaticInfo/extensions-')) {
|
||||
return response
|
||||
} else {
|
||||
if (
|
||||
!response?.config?.url.startsWith('/xpackComponent/content') &&
|
||||
@ -269,18 +271,9 @@ const executeVersionHandler = (response: AxiosResponse) => {
|
||||
return
|
||||
}
|
||||
if (executeVersion && executeVersion !== cacheVal) {
|
||||
wsCache.clear()
|
||||
wsCache.set(key, executeVersion)
|
||||
showMsg('系统有升级,请点击刷新页面', '-sys-upgrade-')
|
||||
/* ElMessageBox.confirm('系统有升级,请点击刷新页面', {
|
||||
confirmButtonType: 'primary',
|
||||
type: 'warning',
|
||||
confirmButtonText: '刷新',
|
||||
cancelButtonText: '取消',
|
||||
autofocus: false,
|
||||
showClose: false
|
||||
}).then(() => {
|
||||
window.location.reload()
|
||||
}) */
|
||||
}
|
||||
}
|
||||
export { service, cancelMap }
|
||||
|
219
core/core-frontend/src/custom-component/common/DeRuler.vue
Normal file
219
core/core-frontend/src/custom-component/common/DeRuler.vue
Normal file
@ -0,0 +1,219 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain'
|
||||
import { storeToRefs } from 'pinia'
|
||||
const dvMainStore = dvMainStoreWithOut()
|
||||
const wRuleRef = ref(null)
|
||||
const props = defineProps({
|
||||
tickLabelFormatter: {
|
||||
type: Function,
|
||||
default: value => value.toString() // 刻度标签格式化函数,默认直接转为字符串
|
||||
},
|
||||
size: {
|
||||
type: Number,
|
||||
default: 300 // 尺子方向
|
||||
},
|
||||
direction: {
|
||||
type: String,
|
||||
default: 'horizontal' // 尺子方向
|
||||
}
|
||||
})
|
||||
|
||||
const labelInterval = 5
|
||||
|
||||
const { canvasStyleData, curComponent } = storeToRefs(dvMainStore)
|
||||
|
||||
const rulerSize = computed(() =>
|
||||
props.direction === 'horizontal' ? canvasStyleData.value.width : canvasStyleData.value.height
|
||||
)
|
||||
|
||||
const curComponentSize = computed(() => {
|
||||
if (curComponent.value) {
|
||||
return (
|
||||
((props.direction === 'horizontal'
|
||||
? curComponent.value.style.width
|
||||
: curComponent.value.style.height) *
|
||||
canvasStyleData.value.scale) /
|
||||
100
|
||||
)
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
})
|
||||
|
||||
const curComponentShadow = computed(() => {
|
||||
if (curComponent.value) {
|
||||
return {
|
||||
left:
|
||||
(props.direction === 'horizontal'
|
||||
? curComponent.value.style.left
|
||||
: curComponent.value.style.top) + 'px',
|
||||
width:
|
||||
(props.direction === 'horizontal'
|
||||
? curComponent.value.style.width
|
||||
: curComponent.value.style.height) + 'px'
|
||||
}
|
||||
} else {
|
||||
return {}
|
||||
}
|
||||
})
|
||||
|
||||
const ticks = computed(() => {
|
||||
const result = []
|
||||
let currentValue = 0
|
||||
while (currentValue <= rulerSize.value) {
|
||||
const isLong = currentValue % (labelInterval * tickSize.value) === 0
|
||||
const label = isLong ? props.tickLabelFormatter(currentValue) : ''
|
||||
result.push({ position: (currentValue * canvasStyleData.value.scale) / 100, label, isLong })
|
||||
currentValue += tickSize.value
|
||||
}
|
||||
return result
|
||||
})
|
||||
|
||||
const wStyle = computed(() => {
|
||||
return {
|
||||
width: rulerSize.value * 1.5 + 'px'
|
||||
}
|
||||
})
|
||||
|
||||
const radio = computed(() => rulerSize.value / canvasStyleData.value.width)
|
||||
const tickSize = computed(
|
||||
() =>
|
||||
10 *
|
||||
Math.max(
|
||||
Math.floor((200000 * radio.value) / (rulerSize.value * canvasStyleData.value.scale)),
|
||||
1
|
||||
)
|
||||
)
|
||||
|
||||
const scaleWidth = computed(() => (rulerSize.value * canvasStyleData.value.scale) / 100)
|
||||
|
||||
const rulerScroll = e => {
|
||||
const left = props.direction === 'vertical' ? e.scrollTop : e.scrollLeft
|
||||
wRuleRef.value.scrollTo(left, 0)
|
||||
}
|
||||
|
||||
const outerStyle = computed(() => {
|
||||
return {
|
||||
width: props.direction === 'vertical' ? props.size - 30 + 'px' : '100%'
|
||||
}
|
||||
})
|
||||
|
||||
defineExpose({
|
||||
rulerScroll
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="ruler-outer"
|
||||
:style="outerStyle"
|
||||
:class="{ 'ruler-vertical': direction === 'vertical' }"
|
||||
ref="wRuleRef"
|
||||
>
|
||||
<!--覆盖着尺子上方防止鼠标移到尺子位置滑动-->
|
||||
<div class="ruler-shadow" :style="outerStyle"></div>
|
||||
<div :style="wStyle" class="ruler-outer-scroll">
|
||||
<div class="ruler" :style="{ width: `${scaleWidth}px` }">
|
||||
<div v-if="curComponent" :style="curComponentShadow" class="cur-shadow"></div>
|
||||
<div class="ruler-line" :style="{ width: `${scaleWidth}px` }"></div>
|
||||
<div
|
||||
v-for="(tick, index) in ticks"
|
||||
:key="index"
|
||||
class="ruler-tick"
|
||||
:class="{ 'long-tick': tick.isLong }"
|
||||
:style="{ left: `${tick.position}px` }"
|
||||
>
|
||||
<span v-if="tick.isLong" class="tick-label">{{ tick.label }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="less">
|
||||
::-webkit-scrollbar {
|
||||
width: 0px !important;
|
||||
height: 0px !important;
|
||||
}
|
||||
.ruler-vertical {
|
||||
position: absolute;
|
||||
left: 30px;
|
||||
top: 30px;
|
||||
transform-origin: top left;
|
||||
transform: rotate(90deg);
|
||||
overflow-y: auto;
|
||||
overflow-x: hidden;
|
||||
z-index: 1;
|
||||
.ruler {
|
||||
.ruler-line {
|
||||
top: 0;
|
||||
}
|
||||
.ruler-tick {
|
||||
top: 0;
|
||||
.tick-label {
|
||||
transform: rotate(180deg);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.ruler-shadow {
|
||||
position: absolute;
|
||||
height: 30px;
|
||||
z-index: 10;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.ruler-outer {
|
||||
overflow-x: auto;
|
||||
background-color: #2c2c2c;
|
||||
}
|
||||
|
||||
.ruler-outer-scroll {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
.ruler {
|
||||
position: relative;
|
||||
height: 30px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
background-color: #2c2c2c;
|
||||
}
|
||||
|
||||
.ruler-line {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
height: 1px;
|
||||
background-color: #ac2a2a;
|
||||
}
|
||||
|
||||
.ruler-tick {
|
||||
position: absolute;
|
||||
bottom: 1px;
|
||||
height: 3px;
|
||||
width: 1px;
|
||||
background-color: #e38a8a;
|
||||
}
|
||||
|
||||
.long-tick {
|
||||
width: 1px;
|
||||
height: 15px;
|
||||
}
|
||||
|
||||
.tick-label {
|
||||
position: absolute;
|
||||
bottom: 2px;
|
||||
font-size: 8px;
|
||||
left: 50%;
|
||||
transform: translateX(2%);
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.cur-shadow {
|
||||
background: rgba(10, 123, 224, 0.3);
|
||||
height: 30px;
|
||||
position: absolute;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,144 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain'
|
||||
import { storeToRefs } from 'pinia'
|
||||
const dvMainStore = dvMainStoreWithOut()
|
||||
const wRuleRef = ref(null)
|
||||
const props = defineProps({
|
||||
tickLabelFormatter: {
|
||||
type: Function,
|
||||
default: value => value.toString() // 刻度标签格式化函数,默认直接转为字符串
|
||||
},
|
||||
direction: {
|
||||
type: String,
|
||||
default: 'horizontal' // 尺子方向
|
||||
}
|
||||
})
|
||||
|
||||
const labelInterval = 5
|
||||
|
||||
const { canvasStyleData } = storeToRefs(dvMainStore)
|
||||
|
||||
const ticks = computed(() => {
|
||||
const result = []
|
||||
let currentValue = 0
|
||||
while (currentValue <= canvasStyleData.value.height) {
|
||||
const isLong = currentValue % (labelInterval * tickSize.value) === 0
|
||||
const label = isLong ? props.tickLabelFormatter(currentValue) : ''
|
||||
result.push({ position: (currentValue * canvasStyleData.value.scale) / 100, label, isLong })
|
||||
currentValue += tickSize.value
|
||||
}
|
||||
return result
|
||||
})
|
||||
|
||||
const hStyle = computed(() => {
|
||||
return {
|
||||
height: canvasStyleData.value.height * 1.5 + 'px'
|
||||
}
|
||||
})
|
||||
const tickSize = computed(
|
||||
() =>
|
||||
10 *
|
||||
Math.max(Math.floor(200000 / (canvasStyleData.value.height * canvasStyleData.value.scale)), 1)
|
||||
)
|
||||
|
||||
const scaleHeight = computed(
|
||||
() => (canvasStyleData.value.height * canvasStyleData.value.scale) / 100
|
||||
)
|
||||
|
||||
const rulerScroll = e => {
|
||||
wRuleRef.value.scrollTo(0, e.scrollHeight)
|
||||
}
|
||||
|
||||
defineExpose({
|
||||
rulerScroll
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="ruler-outer-vertical" ref="wRuleRef">
|
||||
testtest
|
||||
<!--覆盖着尺子上方防止鼠标移到尺子位置滑动-->
|
||||
<div class="ruler-shadow-vertical"></div>
|
||||
<div :style="hStyle" class="ruler-outer-vertical-scroll">
|
||||
<div class="ruler" :style="{ height: `${scaleHeight}px` }">
|
||||
<div class="ruler-line" :style="{ height: `${scaleHeight}px` }"></div>
|
||||
<div
|
||||
v-for="(tick, index) in ticks"
|
||||
:key="index"
|
||||
class="ruler-tick"
|
||||
:class="{ 'long-tick': tick.isLong }"
|
||||
:style="{ left: `${tick.position}px` }"
|
||||
>
|
||||
<span v-if="tick.isLong" class="tick-label">{{ tick.label }}</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped lang="less">
|
||||
::-webkit-scrollbar {
|
||||
width: 0px !important;
|
||||
height: 0px !important;
|
||||
}
|
||||
.ruler-shadow-vertical {
|
||||
position: absolute;
|
||||
width: 30px;
|
||||
height: 100%;
|
||||
z-index: 10;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.ruler-outer-vertical {
|
||||
position: absolute;
|
||||
width: 30px;
|
||||
height: 100%;
|
||||
overflow-y: auto;
|
||||
background-color: #2c2c2c;
|
||||
}
|
||||
|
||||
.ruler-outer-vertical-scroll {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.ruler {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 50%;
|
||||
transform: translateX(-50%);
|
||||
height: 100%;
|
||||
border-left: 1px solid #974e4e;
|
||||
background-color: #2c2c2c;
|
||||
}
|
||||
|
||||
.ruler-line {
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
height: 1px;
|
||||
background-color: #ac2a2a;
|
||||
}
|
||||
|
||||
.ruler-tick {
|
||||
position: absolute;
|
||||
bottom: 1px;
|
||||
height: 3px;
|
||||
width: 1px;
|
||||
background-color: #e38a8a;
|
||||
}
|
||||
|
||||
.long-tick {
|
||||
width: 1px;
|
||||
height: 15px;
|
||||
}
|
||||
|
||||
.tick-label {
|
||||
position: absolute;
|
||||
bottom: 2px;
|
||||
font-size: 8px;
|
||||
left: 50%;
|
||||
transform: translateX(2%);
|
||||
white-space: nowrap;
|
||||
}
|
||||
</style>
|
@ -48,8 +48,8 @@ const anchorPosition = anchor => {
|
||||
scrollTo(element.offsetTop)
|
||||
}
|
||||
|
||||
const newComponent = (innerType, isPlugin) => {
|
||||
eventBus.emit('handleNew', { componentName: 'UserView', innerType: innerType, isPlugin })
|
||||
const newComponent = (innerType, staticMap) => {
|
||||
eventBus.emit('handleNew', { componentName: 'UserView', innerType: innerType, staticMap })
|
||||
}
|
||||
|
||||
const handleDragStart = e => {
|
||||
@ -66,14 +66,15 @@ const groupActiveChange = category => {
|
||||
}
|
||||
const loadPluginCategory = data => {
|
||||
data.forEach(item => {
|
||||
const { category, title, render, chartValue, chartTitle, icon } = item
|
||||
const { category, title, render, chartValue, chartTitle, icon, staticMap } = item
|
||||
const node = {
|
||||
render,
|
||||
category,
|
||||
icon,
|
||||
value: chartValue,
|
||||
title: chartTitle,
|
||||
isPlugin: true
|
||||
isPlugin: true,
|
||||
staticMap
|
||||
}
|
||||
const stack = [...state.chartGroupList]
|
||||
let findParent = false
|
||||
@ -128,7 +129,7 @@ const loadPluginCategory = data => {
|
||||
:key="chartInfo.title"
|
||||
>
|
||||
<div
|
||||
v-on:click="newComponent(chartInfo.value, chartInfo['isPlugin'])"
|
||||
v-on:click="newComponent(chartInfo.value, chartInfo['staticMap'])"
|
||||
class="item-top"
|
||||
draggable="true"
|
||||
:data-id="'UserView&' + chartInfo.value"
|
||||
|
@ -518,8 +518,9 @@ export function findNewComponentFromList(
|
||||
componentName,
|
||||
innerType,
|
||||
curOriginThemes,
|
||||
isPlugin?: boolean
|
||||
staticMap?: object
|
||||
) {
|
||||
const isPlugin = !!staticMap
|
||||
let newComponent
|
||||
list.forEach(comp => {
|
||||
if (comp.component === componentName) {
|
||||
@ -540,6 +541,9 @@ export function findNewComponentFromList(
|
||||
newComponent.label = viewConfig?.title
|
||||
newComponent.render = viewConfig?.render
|
||||
newComponent.isPlugin = !!isPlugin
|
||||
if (isPlugin) {
|
||||
newComponent.staticMap = staticMap
|
||||
}
|
||||
}
|
||||
return newComponent
|
||||
}
|
||||
|
@ -99,7 +99,7 @@ onBeforeUnmount(() => {
|
||||
id="input"
|
||||
ref="files"
|
||||
type="file"
|
||||
accept=".jpeg,.jpg,.png,.gif"
|
||||
accept=".jpeg,.jpg,.png,.gif,.svg"
|
||||
hidden
|
||||
@click="
|
||||
e => {
|
||||
|
@ -19,6 +19,8 @@ import 'tinymce/plugins/table' // 插入表格插件
|
||||
import 'tinymce/plugins/lists' // 列表插件
|
||||
import 'tinymce/plugins/wordcount' // 字数统计插件
|
||||
import 'tinymce/plugins/code' // 源码
|
||||
import './plugins' //自定义插件
|
||||
import '@npkg/tinymce-plugins/letterspacing'
|
||||
|
||||
//接下来定义编辑器所需要的插件数据
|
||||
import { reactive, ref } from 'vue'
|
||||
@ -49,7 +51,7 @@ const props = defineProps({
|
||||
toolbar: {
|
||||
type: [String, Array],
|
||||
default:
|
||||
'codesample bold italic underline alignleft aligncenter alignright alignjustify | undo redo | formatselect | fontselect | fontsizeselect | forecolor backcolor | bullist numlist outdent indent | lists link table code | removeformat '
|
||||
'codesample bold italic underline alignleft aligncenter alignright alignjustify | undo redo | formatselect | fontselect | fontsizeselect | forecolor backcolor | bullist numlist outdent indent | lists link table code | removeformat letterspacing '
|
||||
} //必填
|
||||
})
|
||||
//用于接收外部传递进来的富文本
|
||||
|
@ -59,6 +59,8 @@ import { dvMainStoreWithOut } from '@/store/modules/data-visualization/dvMain'
|
||||
import ChartError from '@/views/chart/components/views/components/ChartError.vue'
|
||||
import { useEmitt } from '@/hooks/web/useEmitt'
|
||||
import { valueFormatter } from '@/views/chart/components/js/formatter'
|
||||
import { parseJson } from '@/views/chart/components/js/util'
|
||||
import { mappingColor } from '@/views/chart/components/js/panel/common/common_table'
|
||||
const snapshotStore = snapshotStoreWithOut()
|
||||
const errMsg = ref('')
|
||||
const dvMainStore = dvMainStoreWithOut()
|
||||
@ -103,6 +105,7 @@ const { element, editMode, active, disabled, showPosition } = toRefs(props)
|
||||
|
||||
const state = reactive({
|
||||
data: null,
|
||||
viewDataInfo: null,
|
||||
totalItems: 0
|
||||
})
|
||||
const dataRowSelect = ref({})
|
||||
@ -150,7 +153,7 @@ watch(
|
||||
() => active.value,
|
||||
val => {
|
||||
if (!val) {
|
||||
const ed = tinymce.editors[tinymceId]
|
||||
const ed = window.tinymce.editors[tinymceId]
|
||||
if (canEdit.value) {
|
||||
element.value.propValue.textValue = ed.getContent()
|
||||
}
|
||||
@ -158,6 +161,7 @@ watch(
|
||||
canEdit.value = false
|
||||
reShow()
|
||||
myValue.value = assignment(element.value.propValue.textValue)
|
||||
ed.setContent(myValue.value)
|
||||
}
|
||||
}
|
||||
)
|
||||
@ -166,7 +170,7 @@ watch(
|
||||
() => myValue.value,
|
||||
() => {
|
||||
if (canEdit.value) {
|
||||
const ed = tinymce.editors[tinymceId]
|
||||
const ed = window.tinymce.editors[tinymceId]
|
||||
element.value.propValue.textValue = ed.getContent()
|
||||
}
|
||||
if (initReady.value && canEdit.value) {
|
||||
@ -216,23 +220,19 @@ const initCurFieldsChange = () => {
|
||||
const assignment = content => {
|
||||
const on = content.match(/\[(.+?)\]/g)
|
||||
if (on) {
|
||||
const thresholdStyleInfo = conditionAdaptor(state.viewDataInfo)
|
||||
on.forEach(itm => {
|
||||
if (dataRowFiledName.value.includes(itm)) {
|
||||
const ele = itm.slice(1, -1)
|
||||
let value = dataRowNameSelect.value[ele] !== undefined ? dataRowNameSelect.value[ele] : null
|
||||
if (value && thresholdStyleInfo && thresholdStyleInfo[ele]) {
|
||||
const thresholdStyle = thresholdStyleInfo[ele]
|
||||
value = `<span style="color:${thresholdStyle.color};background-color: ${thresholdStyle.backgroundColor}">${value}</span>`
|
||||
}
|
||||
if (initReady.value) {
|
||||
content = content.replace(
|
||||
itm,
|
||||
dataRowNameSelect.value[ele] !== undefined
|
||||
? dataRowNameSelect.value[ele]
|
||||
: '[未获取字段值]'
|
||||
)
|
||||
content = content.replace(itm, !!value ? value : '[未获取字段值]')
|
||||
} else {
|
||||
content = content.replace(
|
||||
itm,
|
||||
dataRowNameSelect.value[ele] !== undefined
|
||||
? dataRowNameSelect.value[ele]
|
||||
: '[获取中...]'
|
||||
)
|
||||
content = content.replace(itm, !!value ? value : '[获取中...]')
|
||||
}
|
||||
}
|
||||
})
|
||||
@ -245,7 +245,7 @@ const assignment = content => {
|
||||
return content
|
||||
}
|
||||
const fieldSelect = field => {
|
||||
const ed = tinymce.editors[tinymceId]
|
||||
const ed = window.tinymce.editors[tinymceId]
|
||||
const fieldId = 'changeText-' + guid()
|
||||
const value =
|
||||
'<span id="' +
|
||||
@ -262,12 +262,12 @@ const fieldSelect = field => {
|
||||
}
|
||||
const onClick = () => {
|
||||
if (canEdit.value) {
|
||||
const node = tinymce.activeEditor.selection.getNode()
|
||||
const node = window.tinymce.activeEditor.selection.getNode()
|
||||
resetSelect(node)
|
||||
}
|
||||
}
|
||||
const resetSelect = (node?) => {
|
||||
const edInner = tinymce.get(tinymceId)
|
||||
const edInner = window.tinymce.get(tinymceId)
|
||||
if (edInner?.dom) {
|
||||
const nodeArray = edInner.dom.select('.base-selected')
|
||||
if (nodeArray) {
|
||||
@ -316,7 +316,7 @@ const setEdit = () => {
|
||||
canEdit.value = true
|
||||
element.value['editing'] = true
|
||||
myValue.value = element.value.propValue.textValue
|
||||
const ed = tinymce.editors[tinymceId]
|
||||
const ed = window.tinymce.editors[tinymceId]
|
||||
ed.setContent(myValue.value)
|
||||
reShow()
|
||||
}
|
||||
@ -359,6 +359,7 @@ const calcData = (view: Chart, callback) => {
|
||||
errMsg.value = res.msg
|
||||
} else {
|
||||
state.data = res?.data
|
||||
state.viewDataInfo = res
|
||||
state.totalItems = res?.totalItems
|
||||
const curViewInfo = canvasViewInfo.value[element.value.id]
|
||||
curViewInfo['curFields'] = res.data.fields
|
||||
@ -449,6 +450,40 @@ const renderChart = () => {
|
||||
initCurFieldsChange()
|
||||
}
|
||||
|
||||
const conditionAdaptor = (chart: Chart) => {
|
||||
if (!chart) {
|
||||
return
|
||||
}
|
||||
const { threshold } = parseJson(chart.senior)
|
||||
if (!threshold.enable) {
|
||||
return
|
||||
}
|
||||
const res = {}
|
||||
const conditions = threshold.tableThreshold ?? []
|
||||
if (conditions?.length > 0) {
|
||||
for (let i = 0; i < conditions.length; i++) {
|
||||
const field = conditions[i]
|
||||
let defaultValueColor = 'none'
|
||||
let defaultBgColor = 'none'
|
||||
res[field.field.name] = {
|
||||
color: mappingColor(
|
||||
dataRowNameSelect.value[field.field.name],
|
||||
defaultValueColor,
|
||||
field,
|
||||
'color'
|
||||
),
|
||||
backgroundColor: mappingColor(
|
||||
dataRowNameSelect.value[field.field.name],
|
||||
defaultBgColor,
|
||||
field,
|
||||
'backgroundColor'
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
viewInit()
|
||||
})
|
||||
|
@ -660,6 +660,7 @@ export default {
|
||||
horizontal: '水平',
|
||||
vertical: '垂直',
|
||||
legend: '图例',
|
||||
legend_num: '图例数',
|
||||
shape: '形状',
|
||||
polygon: '多边形',
|
||||
circle: '圆形',
|
||||
@ -2268,7 +2269,10 @@ export default {
|
||||
you_can_type_here: '可以在这里输入其他内容'
|
||||
},
|
||||
link_ticket: {
|
||||
require: 'Ticket 必填'
|
||||
require: '必选',
|
||||
back: '返回公共链接设置页面',
|
||||
refresh: '刷新',
|
||||
time_tips: '单位: 分钟,范围: [0-1440],0代表无期限,自首次使用ticket访问开始'
|
||||
},
|
||||
pblink: {
|
||||
key_pwd: '请输入密码打开链接',
|
||||
|
@ -612,6 +612,22 @@ declare interface ChartMiscAttr {
|
||||
* 词云图文字间距
|
||||
*/
|
||||
wordSpacing: number
|
||||
/**
|
||||
* 自动图例
|
||||
*/
|
||||
mapAutoLegend: boolean
|
||||
/**
|
||||
* 图例最大值
|
||||
*/
|
||||
mapLegendMax: number
|
||||
/**
|
||||
* 图例最小值
|
||||
*/
|
||||
mapLegendMin: number
|
||||
/**
|
||||
* 显示图例个数
|
||||
*/
|
||||
mapLegendNumber: number
|
||||
}
|
||||
/**
|
||||
* 动态极值配置
|
||||
|
@ -1,4 +1,4 @@
|
||||
declare interface ChartPlugin {
|
||||
isPlugin: boolean
|
||||
pluginResourceId?: string
|
||||
staticMap?: object
|
||||
}
|
||||
|
@ -391,7 +391,7 @@ export const dvMainStore = defineStore('dataVisualization', {
|
||||
render: component.render,
|
||||
plugin: {
|
||||
isPlugin: component.isPlugin,
|
||||
pluginResourceId: component.pluginResourceId
|
||||
staticMap: component.staticMap
|
||||
}
|
||||
} as unknown as ChartObj
|
||||
// 处理配置项默认值,不同图表的同一配置项默认值不同
|
||||
|
@ -94,6 +94,7 @@ export const selectKey = ['textAlign', 'borderStyle', 'verticalAlign']
|
||||
export const horizontalPosition = ['headHorizontalPosition']
|
||||
|
||||
export const fieldType = ['text', 'time', 'value', 'value', 'value', 'location']
|
||||
export const fieldTypeText = ['文本', '时间', '数值', '数值(小数)', '数值', '地理位置']
|
||||
|
||||
export const optionMap = {
|
||||
textAlign: textAlignOptions,
|
||||
|
@ -73,10 +73,9 @@ const editStyle = computed(() => {
|
||||
|
||||
// 通过实时监听的方式直接添加组件
|
||||
const handleNewFromCanvasMain = newComponentInfo => {
|
||||
const { componentName, innerType, isPlugin } = newComponentInfo
|
||||
const { componentName, innerType, staticMap } = newComponentInfo
|
||||
if (componentName) {
|
||||
const component = findNewComponentFromList(componentName, innerType, curOriginThemes, isPlugin)
|
||||
component.isPlugin = !!isPlugin
|
||||
const component = findNewComponentFromList(componentName, innerType, curOriginThemes, staticMap)
|
||||
syncShapeItemStyle(component, baseWidth.value, baseHeight.value)
|
||||
component.id = guid()
|
||||
component.y = 200
|
||||
|
@ -64,7 +64,7 @@ const showValueFormatter = computed<boolean>(() => {
|
||||
})
|
||||
|
||||
watch(
|
||||
[() => props.dimensionData, () => props.item],
|
||||
[() => props.dimensionData, () => props.item, () => props.chart.type],
|
||||
() => {
|
||||
getItemTagType()
|
||||
},
|
||||
@ -159,6 +159,10 @@ const removeItem = () => {
|
||||
}
|
||||
|
||||
const getItemTagType = () => {
|
||||
if (props.chart.type !== 'table-info' && props.item.desensitized) {
|
||||
tagType.value = '#F54A45'
|
||||
return
|
||||
}
|
||||
tagType.value = getItemType(props.dimensionData, props.quotaData, props.item)
|
||||
}
|
||||
|
||||
@ -204,7 +208,10 @@ onMounted(() => {
|
||||
:content="item.chartShowName ? item.chartShowName : item.name"
|
||||
>
|
||||
<span class="item-span-style">
|
||||
<span class="item-name">{{ item.chartShowName ? item.chartShowName : item.name }}</span>
|
||||
<span class="item-name"
|
||||
>{{ item.chartShowName ? item.chartShowName : item.name
|
||||
}}{{ item.desensitized ? '(已脱敏)' : '' }}</span
|
||||
>
|
||||
</span>
|
||||
</el-tooltip>
|
||||
|
||||
|
@ -18,6 +18,10 @@ const props = defineProps({
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
chart: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
index: {
|
||||
type: Number,
|
||||
required: true
|
||||
@ -41,7 +45,7 @@ const emit = defineEmits(['onDimensionItemRemove'])
|
||||
const { item } = toRefs(props)
|
||||
|
||||
watch(
|
||||
[() => props.dimensionData, () => props.item],
|
||||
[() => props.dimensionData, () => props.item, () => props.chart.type],
|
||||
() => {
|
||||
getItemTagType()
|
||||
},
|
||||
@ -70,6 +74,10 @@ const removeItem = () => {
|
||||
emit('onDimensionItemRemove', item.value)
|
||||
}
|
||||
const getItemTagType = () => {
|
||||
if (props.chart.type !== 'table-info' && props.item.desensitized) {
|
||||
tagType.value = '#F54A45'
|
||||
return
|
||||
}
|
||||
tagType.value = getItemType(props.dimensionData, props.quotaData, props.item)
|
||||
}
|
||||
onMounted(() => {
|
||||
@ -93,7 +101,9 @@ onMounted(() => {
|
||||
></Icon>
|
||||
</el-icon>
|
||||
</span>
|
||||
<span class="item-span-style" :title="item.name">{{ item.name }}</span>
|
||||
<span class="item-span-style" :title="item.name"
|
||||
>{{ item.name }}{{ item.desensitized ? '(已脱敏)' : '' }}</span
|
||||
>
|
||||
<el-icon class="child remove-icon" size="14px">
|
||||
<Icon name="icon_delete-trash_outlined" class-name="inner-class" @click="removeItem" />
|
||||
</el-icon>
|
||||
|
@ -68,7 +68,7 @@ const toolTip = computed(() => {
|
||||
return props.themes === 'dark' ? 'ndark' : 'dark'
|
||||
})
|
||||
watch(
|
||||
[() => props.quotaData, () => props.item],
|
||||
[() => props.quotaData, () => props.item, () => props.chart.type],
|
||||
() => {
|
||||
getItemTagType()
|
||||
},
|
||||
@ -214,6 +214,10 @@ const removeItem = () => {
|
||||
}
|
||||
|
||||
const getItemTagType = () => {
|
||||
if (props.chart.type !== 'table-info' && props.item.desensitized) {
|
||||
tagType.value = '#F54A45'
|
||||
return
|
||||
}
|
||||
tagType.value = getItemType(props.dimensionData, props.quotaData, props.item)
|
||||
}
|
||||
|
||||
@ -304,7 +308,10 @@ onMounted(() => {
|
||||
:content="item.chartShowName ? item.chartShowName : item.name"
|
||||
>
|
||||
<span class="item-span-style">
|
||||
<span class="item-name">{{ item.chartShowName ? item.chartShowName : item.name }}</span>
|
||||
<span class="item-name"
|
||||
>{{ item.chartShowName ? item.chartShowName : item.name
|
||||
}}{{ item.desensitized ? '(已脱敏)' : '' }}</span
|
||||
>
|
||||
<span v-if="item.summary !== ''" class="item-right-summary">
|
||||
({{ t('chart.' + item.summary) }})
|
||||
</span>
|
||||
|
@ -260,6 +260,7 @@ watch(
|
||||
:themes="themes"
|
||||
:chart="chart"
|
||||
@onLegendChange="onLegendChange"
|
||||
@onMiscChange="onMiscChange"
|
||||
/>
|
||||
</collapse-switch-item>
|
||||
<el-collapse-item
|
||||
|
@ -1,8 +1,14 @@
|
||||
<script lang="tsx" setup>
|
||||
import { computed, onMounted, reactive, watch } from 'vue'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { COLOR_PANEL, DEFAULT_LEGEND_STYLE } from '@/views/chart/components/editor/util/chart'
|
||||
import { ElSpace } from 'element-plus-secondary'
|
||||
import {
|
||||
COLOR_PANEL,
|
||||
DEFAULT_LEGEND_STYLE,
|
||||
DEFAULT_MISC
|
||||
} from '@/views/chart/components/editor/util/chart'
|
||||
import { ElCol, ElRow, ElSpace } from 'element-plus-secondary'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import { useEmitt } from '@/hooks/web/useEmitt'
|
||||
|
||||
const { t } = useI18n()
|
||||
|
||||
@ -14,8 +20,11 @@ const props = withDefaults(
|
||||
}>(),
|
||||
{ themes: 'dark' }
|
||||
)
|
||||
|
||||
const emit = defineEmits(['onLegendChange'])
|
||||
useEmitt({
|
||||
name: 'map-default-range',
|
||||
callback: args => mapDefaultRange(args)
|
||||
})
|
||||
const emit = defineEmits(['onLegendChange', 'onMiscChange'])
|
||||
const toolTip = computed(() => {
|
||||
return props.themes === 'dark' ? 'ndark' : 'dark'
|
||||
})
|
||||
@ -36,7 +45,15 @@ const iconSymbolOptions = [
|
||||
]
|
||||
|
||||
const state = reactive({
|
||||
legendForm: JSON.parse(JSON.stringify(DEFAULT_LEGEND_STYLE))
|
||||
legendForm: {
|
||||
...JSON.parse(JSON.stringify(DEFAULT_LEGEND_STYLE)),
|
||||
miscForm: JSON.parse(JSON.stringify(DEFAULT_MISC)) as ChartMiscAttr
|
||||
}
|
||||
})
|
||||
|
||||
const chartType = computed(() => {
|
||||
const chart = JSON.parse(JSON.stringify(props.chart))
|
||||
return chart?.type
|
||||
})
|
||||
|
||||
const fontSizeList = computed(() => {
|
||||
@ -54,6 +71,10 @@ const changeLegendStyle = prop => {
|
||||
emit('onLegendChange', state.legendForm, prop)
|
||||
}
|
||||
|
||||
const changeMisc = prop => {
|
||||
emit('onMiscChange', { data: state.legendForm.miscForm, requestData: true }, prop)
|
||||
}
|
||||
|
||||
const init = () => {
|
||||
const chart = JSON.parse(JSON.stringify(props.chart))
|
||||
if (chart.customStyle) {
|
||||
@ -63,13 +84,21 @@ const init = () => {
|
||||
} else {
|
||||
customStyle = JSON.parse(chart.customStyle)
|
||||
}
|
||||
const miscStyle = cloneDeep(props.chart.customAttr.misc)
|
||||
if (customStyle.legend) {
|
||||
state.legendForm = customStyle.legend
|
||||
state.legendForm.miscForm = miscStyle
|
||||
}
|
||||
}
|
||||
}
|
||||
const showProperty = prop => props.propertyInner?.includes(prop)
|
||||
|
||||
const mapDefaultRange = args => {
|
||||
if (args.from === 'map') {
|
||||
state.legendForm.miscForm.mapLegendMax = args.data.max
|
||||
state.legendForm.miscForm.mapLegendMin = args.data.min
|
||||
state.legendForm.miscForm.mapLegendNumber = args.data.legendNumber
|
||||
}
|
||||
}
|
||||
onMounted(() => {
|
||||
init()
|
||||
})
|
||||
@ -145,6 +174,82 @@ onMounted(() => {
|
||||
</el-tooltip>
|
||||
</el-form-item>
|
||||
</el-space>
|
||||
<el-space>
|
||||
<div v-if="chartType === 'map'">
|
||||
<el-row>
|
||||
<el-col>
|
||||
<el-form-item
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
:label="t('chart.legend')"
|
||||
>
|
||||
<el-checkbox
|
||||
size="small"
|
||||
:effect="themes"
|
||||
v-model="state.legendForm.miscForm.mapAutoLegend"
|
||||
@change="changeMisc('mapAutoLegend')"
|
||||
>
|
||||
{{ t('chart.margin_model_auto') }}
|
||||
</el-checkbox>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<div v-if="!state.legendForm.miscForm.mapAutoLegend">
|
||||
<el-row :gutter="8">
|
||||
<el-col :span="12">
|
||||
<el-form-item
|
||||
:label="t('chart.max')"
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
>
|
||||
<el-input-number
|
||||
:effect="themes"
|
||||
v-model="state.legendForm.miscForm.mapLegendMax"
|
||||
size="small"
|
||||
controls-position="right"
|
||||
@change="changeMisc('mapLegendMax')"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
<el-col :span="12">
|
||||
<el-form-item
|
||||
:label="t('chart.min')"
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
>
|
||||
<el-input-number
|
||||
:effect="themes"
|
||||
v-model="state.legendForm.miscForm.mapLegendMin"
|
||||
size="small"
|
||||
controls-position="right"
|
||||
@change="changeMisc('mapLegendMin')"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
<el-row>
|
||||
<el-col>
|
||||
<el-form-item
|
||||
class="form-item"
|
||||
:class="'form-item-' + themes"
|
||||
:label="t('chart.legend_num')"
|
||||
>
|
||||
<el-input-number
|
||||
:effect="themes"
|
||||
v-model="state.legendForm.miscForm.mapLegendNumber"
|
||||
size="small"
|
||||
:min="1"
|
||||
:max="9"
|
||||
controls-position="right"
|
||||
@change="changeMisc('mapLegendNumber')"
|
||||
/>
|
||||
</el-form-item>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</div>
|
||||
</div>
|
||||
</el-space>
|
||||
|
||||
<el-form-item
|
||||
:label="t('chart.orient')"
|
||||
class="form-item"
|
||||
|
@ -1519,19 +1519,30 @@ const dragOver = (ev: MouseEvent) => {
|
||||
}
|
||||
|
||||
const drop = (ev: MouseEvent, type = 'xAxis') => {
|
||||
let hasSesensitized = false
|
||||
ev.preventDefault()
|
||||
const arr = activeDimension.value.length ? activeDimension.value : activeQuota.value
|
||||
for (let i = 0; i < arr.length; i++) {
|
||||
const obj = cloneDeep(arr[i])
|
||||
if (obj.desensitized && view.value.type !== 'table-info') {
|
||||
hasSesensitized = true
|
||||
continue
|
||||
}
|
||||
|
||||
state.moveId = obj.id as unknown as number
|
||||
view.value[type].push(obj)
|
||||
const e = { newDraggableIndex: view.value[type].length - 1 }
|
||||
|
||||
if ('drillFields' === type) {
|
||||
addDrill(e)
|
||||
} else {
|
||||
addAxis(e, type as AxisType)
|
||||
}
|
||||
}
|
||||
|
||||
if (hasSesensitized) {
|
||||
ElMessage.error('脱敏字段不能用于制作该图表!')
|
||||
}
|
||||
}
|
||||
|
||||
const fieldLoading = ref(false)
|
||||
@ -1605,7 +1616,7 @@ const deleteChartFieldItem = id => {
|
||||
</div>
|
||||
<plugin-component
|
||||
v-else-if="view.plugin?.isPlugin"
|
||||
jsname="L2NvbXBvbmVudC9lZGl0b3IvaW5kZXg="
|
||||
:jsname="view.plugin.staticMap['editor']"
|
||||
:view="view"
|
||||
:dimension="state.dimension"
|
||||
:quota="state.quota"
|
||||
@ -2277,6 +2288,7 @@ const deleteChartFieldItem = id => {
|
||||
<drill-item
|
||||
:key="element.id"
|
||||
:index="index"
|
||||
:chart="view"
|
||||
:item="element"
|
||||
:dimension-data="state.dimension"
|
||||
:quota-data="state.quota"
|
||||
|
@ -262,7 +262,11 @@ export const DEFAULT_MISC: ChartMiscAttr = {
|
||||
mapLineSourceColor: '#146C94',
|
||||
mapLineTargetColor: '#576CBC',
|
||||
wordSizeRange: [8, 32],
|
||||
wordSpacing: 6
|
||||
wordSpacing: 6,
|
||||
mapAutoLegend: true,
|
||||
mapLegendMax: 0,
|
||||
mapLegendMin: 0,
|
||||
mapLegendNumber: 9
|
||||
}
|
||||
|
||||
export const DEFAULT_MARK = {
|
||||
|
@ -3,7 +3,15 @@ import {
|
||||
L7PlotDrawOptions
|
||||
} from '@/views/chart/components/js/panel/types/impl/l7plot'
|
||||
import { Choropleth, ChoroplethOptions } from '@antv/l7plot/dist/esm/plots/choropleth'
|
||||
import { flow, getGeoJsonFile, hexColorToRGBA, parseJson } from '@/views/chart/components/js/util'
|
||||
import {
|
||||
filterChartDataByRange,
|
||||
flow,
|
||||
getDynamicColorScale,
|
||||
getGeoJsonFile,
|
||||
setMapChartDefaultMaxAndMinValueByData,
|
||||
hexColorToRGBA,
|
||||
parseJson
|
||||
} from '@/views/chart/components/js/util'
|
||||
import { handleGeoJson } from '@/views/chart/components/js/panel/common/common_antv'
|
||||
import { FeatureCollection } from '@antv/l7plot/dist/esm/plots/choropleth/types'
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
@ -50,6 +58,30 @@ export class Map extends L7PlotChartView<ChoroplethOptions, Choropleth> {
|
||||
if (!areaId) {
|
||||
return
|
||||
}
|
||||
const sourceData = JSON.parse(JSON.stringify(chart.data?.data || []))
|
||||
let data = []
|
||||
const { misc } = parseJson(chart.customAttr)
|
||||
const { legend } = parseJson(chart.customStyle)
|
||||
// 自定义图例
|
||||
if (!misc.mapAutoLegend && legend.show) {
|
||||
let minValue = misc.mapLegendMin
|
||||
let maxValue = misc.mapLegendMax
|
||||
setMapChartDefaultMaxAndMinValueByData(sourceData, maxValue, minValue, (max, min) => {
|
||||
maxValue = max
|
||||
minValue = min
|
||||
action({
|
||||
from: 'map',
|
||||
data: {
|
||||
max: maxValue,
|
||||
min: minValue,
|
||||
legendNumber: 9
|
||||
}
|
||||
})
|
||||
})
|
||||
data = filterChartDataByRange(sourceData, maxValue, minValue)
|
||||
} else {
|
||||
data = sourceData
|
||||
}
|
||||
const geoJson = cloneDeep(await getGeoJsonFile(areaId))
|
||||
let options: ChoroplethOptions = {
|
||||
preserveDrawingBuffer: true,
|
||||
@ -61,7 +93,7 @@ export class Map extends L7PlotChartView<ChoroplethOptions, Choropleth> {
|
||||
type: 'geojson'
|
||||
},
|
||||
source: {
|
||||
data: chart.data?.data || [],
|
||||
data: data,
|
||||
joinBy: {
|
||||
sourceField: 'name',
|
||||
geoField: 'name',
|
||||
@ -125,7 +157,7 @@ export class Map extends L7PlotChartView<ChoroplethOptions, Choropleth> {
|
||||
): ChoroplethOptions {
|
||||
const { areaId }: L7PlotDrawOptions<any> = context.drawOption
|
||||
const geoJson: FeatureCollection = context.geoJson
|
||||
const { basicStyle, label } = parseJson(chart.customAttr)
|
||||
const { basicStyle, label, misc } = parseJson(chart.customAttr)
|
||||
const senior = parseJson(chart.senior)
|
||||
const curAreaNameMapping = senior.areaMapping?.[areaId]
|
||||
handleGeoJson(geoJson, curAreaNameMapping)
|
||||
@ -141,7 +173,32 @@ export class Map extends L7PlotChartView<ChoroplethOptions, Choropleth> {
|
||||
options.label && (options.label.field = 'name')
|
||||
return options
|
||||
}
|
||||
const data = chart.data.data
|
||||
const sourceData = JSON.parse(JSON.stringify(chart.data.data))
|
||||
const colors = basicStyle.colors.map(item => hexColorToRGBA(item, basicStyle.alpha))
|
||||
const { legend } = parseJson(chart.customStyle)
|
||||
let data = []
|
||||
let colorScale = []
|
||||
if (legend.show) {
|
||||
let minValue = misc.mapLegendMin
|
||||
let maxValue = misc.mapLegendMax
|
||||
let mapLegendNumber = misc.mapLegendNumber
|
||||
setMapChartDefaultMaxAndMinValueByData(sourceData, maxValue, minValue, (max, min) => {
|
||||
maxValue = max
|
||||
minValue = min
|
||||
mapLegendNumber = 9
|
||||
})
|
||||
// 非自动,过滤数据
|
||||
if (!misc.mapAutoLegend) {
|
||||
data = filterChartDataByRange(sourceData, maxValue, minValue)
|
||||
} else {
|
||||
mapLegendNumber = 9
|
||||
}
|
||||
// 定义最大值、最小值、区间数量和对应的颜色
|
||||
colorScale = getDynamicColorScale(minValue, maxValue, mapLegendNumber, colors)
|
||||
} else {
|
||||
data = sourceData
|
||||
colorScale = colors
|
||||
}
|
||||
const areaMap = data.reduce((obj, value) => {
|
||||
obj[value['field']] = value.value
|
||||
return obj
|
||||
@ -164,12 +221,11 @@ export class Map extends L7PlotChartView<ChoroplethOptions, Choropleth> {
|
||||
item.properties['_DE_LABEL_'] = content.join('\n\n')
|
||||
}
|
||||
})
|
||||
let colors = basicStyle.colors.map(item => hexColorToRGBA(item, basicStyle.alpha))
|
||||
if (validArea < colors.length) {
|
||||
colors = colors.slice(0, validArea)
|
||||
if (validArea < colorScale.length && !misc.mapAutoLegend) {
|
||||
colorScale = colorScale.map(item => (item.color ? item.color : item)).slice(0, validArea)
|
||||
}
|
||||
if (colors.length) {
|
||||
options.color['value'] = colors
|
||||
if (colorScale.length) {
|
||||
options.color['value'] = colorScale.map(item => (item.color ? item.color : item))
|
||||
}
|
||||
return options
|
||||
}
|
||||
|
@ -6,9 +6,10 @@ const { t } = useI18n()
|
||||
* 富文本图表
|
||||
*/
|
||||
export class RichTextChartView extends AbstractChartView {
|
||||
properties: EditorProperty[] = ['background-overall-component']
|
||||
properties: EditorProperty[] = ['background-overall-component', 'threshold']
|
||||
propertyInner: EditorPropertyInner = {
|
||||
'background-overall-component': ['all']
|
||||
'background-overall-component': ['all'],
|
||||
threshold: ['tableThreshold']
|
||||
}
|
||||
axis: AxisType[] = ['xAxis', 'yAxis', 'filter']
|
||||
axisConfig: AxisConfig = {
|
||||
|
@ -458,7 +458,7 @@ export function getConditions(chart: Chart) {
|
||||
return res
|
||||
}
|
||||
|
||||
function mappingColor(value, defaultColor, field, type) {
|
||||
export function mappingColor(value, defaultColor, field, type) {
|
||||
let color
|
||||
for (let i = 0; i < field.conditions.length; i++) {
|
||||
let flag = false
|
||||
|
@ -527,3 +527,68 @@ export const copyString = (content: string, notify = false) => {
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 计算动态区间和颜色
|
||||
* @param minValue
|
||||
* @param maxValue
|
||||
* @param intervals
|
||||
* @param colors
|
||||
*/
|
||||
export const getDynamicColorScale = (
|
||||
minValue: number,
|
||||
maxValue: number,
|
||||
intervals: number,
|
||||
colors: string[]
|
||||
) => {
|
||||
const step = (maxValue - minValue) / intervals
|
||||
|
||||
const colorScale = []
|
||||
for (let i = 0; i < intervals; i++) {
|
||||
colorScale.push({
|
||||
value: [minValue + i * step, minValue + (i + 1) * step],
|
||||
color: colors[i],
|
||||
label: `${(minValue + i * step).toFixed(2)} - ${(minValue + (i + 1) * step).toFixed(2)}`
|
||||
})
|
||||
}
|
||||
|
||||
return colorScale
|
||||
}
|
||||
/**
|
||||
* 过滤掉不在区间的数据
|
||||
* @param data
|
||||
* @param maxValue
|
||||
* @param minValue
|
||||
*/
|
||||
export const filterChartDataByRange = (data: any[], maxValue: number, minValue: number) => {
|
||||
return data.filter(
|
||||
item =>
|
||||
item.value === null ||
|
||||
item.value === undefined ||
|
||||
(item.value >= minValue && item.value <= maxValue)
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取地图默认最大最小值根据数据
|
||||
* @param data
|
||||
* @param maxValue
|
||||
* @param minValue
|
||||
* @param callback
|
||||
*/
|
||||
export const setMapChartDefaultMaxAndMinValueByData = (
|
||||
data: any[],
|
||||
maxValue: number,
|
||||
minValue: number,
|
||||
callback: (max: number, min: number) => void
|
||||
) => {
|
||||
if (minValue === 0 && maxValue === 0) {
|
||||
const maxResult = data.reduce((max, current) => {
|
||||
return current.value > max ? current.value : max
|
||||
}, Number.MIN_SAFE_INTEGER)
|
||||
const minResult = data.reduce((min, current) => {
|
||||
return current.value < min ? current.value : min
|
||||
}, Number.MAX_SAFE_INTEGER)
|
||||
callback(maxResult, minResult)
|
||||
}
|
||||
}
|
||||
|
@ -287,6 +287,10 @@ const pointClickTrans = () => {
|
||||
}
|
||||
|
||||
const action = param => {
|
||||
if (param.from === 'map') {
|
||||
emitter.emit('map-default-range', param)
|
||||
return
|
||||
}
|
||||
state.pointParam = param.data
|
||||
// 点击
|
||||
pointClickTrans()
|
||||
|
@ -762,7 +762,7 @@ const showActionIcons = computed(() => {
|
||||
<div v-if="chartAreaShow" style="flex: 1; overflow: hidden">
|
||||
<plugin-component
|
||||
v-if="view.plugin?.isPlugin"
|
||||
jsname="L2NvbXBvbmVudC9pbmRleA=="
|
||||
:jsname="view.plugin.staticMap['index']"
|
||||
:scale="scale"
|
||||
:dynamic-area-id="dynamicAreaId"
|
||||
:view="view"
|
||||
|
@ -4,7 +4,7 @@
|
||||
id="input"
|
||||
ref="files"
|
||||
type="file"
|
||||
accept=".jpeg,.jpg,.png,.gif"
|
||||
accept=".jpeg,.jpg,.png,.gif,.svg"
|
||||
hidden
|
||||
@click="
|
||||
e => {
|
||||
|
@ -11,7 +11,7 @@ import { ElMessage } from 'element-plus-secondary'
|
||||
import { useEmbedded } from '@/store/modules/embedded'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { XpackComponent } from '@/components/plugin'
|
||||
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
const dvMainStore = dvMainStoreWithOut()
|
||||
const { t } = useI18n()
|
||||
const embeddedStore = useEmbedded()
|
||||
@ -32,7 +32,8 @@ const props = defineProps({
|
||||
isSelector: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
ticketArgs: propTypes.string.def(null)
|
||||
})
|
||||
|
||||
const loadCanvasDataAsync = async (dvId, dvType) => {
|
||||
@ -57,6 +58,14 @@ const loadCanvasDataAsync = async (dvId, dvType) => {
|
||||
}
|
||||
}
|
||||
|
||||
let argsObject = null
|
||||
try {
|
||||
argsObject = JSON.parse(props.ticketArgs)
|
||||
} catch (error) {
|
||||
console.error(error)
|
||||
}
|
||||
const hasTicketArgs = argsObject && Object.keys(argsObject)
|
||||
|
||||
// 添加外部参数
|
||||
let attachParam
|
||||
await getOuterParamsInfo(dvId).then(rsp => {
|
||||
@ -65,9 +74,14 @@ const loadCanvasDataAsync = async (dvId, dvType) => {
|
||||
|
||||
// 外部参数(iframe 或者 iframe嵌入)
|
||||
const attachParamsEncode = router.currentRoute.value.query.attachParams
|
||||
if (attachParamsEncode) {
|
||||
if (attachParamsEncode || hasTicketArgs) {
|
||||
try {
|
||||
attachParam = JSON.parse(Base64.decode(decodeURIComponent(attachParamsEncode)))
|
||||
if (attachParam) {
|
||||
attachParam = JSON.parse(Base64.decode(decodeURIComponent(attachParamsEncode)))
|
||||
}
|
||||
if (hasTicketArgs) {
|
||||
attachParam = Object.assign({}, attachParam, argsObject)
|
||||
}
|
||||
} catch (e) {
|
||||
console.error(e)
|
||||
ElMessage.error(t('visualization.outer_param_decode_error'))
|
||||
|
@ -39,6 +39,7 @@ import { Base64 } from 'js-base64'
|
||||
import CanvasCacheDialog from '@/components/visualization/CanvasCacheDialog.vue'
|
||||
import { deepCopy } from '@/utils/utils'
|
||||
import DvPreview from '@/views/data-visualization/DvPreview.vue'
|
||||
import DeRuler from '@/custom-component/common/DeRuler.vue'
|
||||
const interactiveStore = interactiveStoreWithOut()
|
||||
const embeddedStore = useEmbedded()
|
||||
const { wsCache } = useCache()
|
||||
@ -63,6 +64,8 @@ const snapshotStore = snapshotStoreWithOut()
|
||||
const contextmenuStore = contextmenuStoreWithOut()
|
||||
const composeStore = composeStoreWithOut()
|
||||
const canvasCacheOutRef = ref(null)
|
||||
const deWRulerRef = ref(null)
|
||||
const deHRulerRef = ref(null)
|
||||
|
||||
const {
|
||||
fullscreenFlag,
|
||||
@ -80,6 +83,7 @@ const canvasInner = ref(null)
|
||||
const leftSidebarRef = ref(null)
|
||||
const dvLayout = ref(null)
|
||||
const canvasCenterRef = ref(null)
|
||||
const mainHeight = ref(300)
|
||||
const state = reactive({
|
||||
datasetTree: [],
|
||||
scaleHistory: null,
|
||||
@ -100,9 +104,6 @@ const contentStyle = computed(() => {
|
||||
}
|
||||
} else {
|
||||
return {
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
width: width * 1.5 + 'px',
|
||||
height: height * 1.5 + 'px'
|
||||
}
|
||||
@ -180,9 +181,9 @@ const initScroll = () => {
|
||||
nextTick(() => {
|
||||
const { width, height } = canvasStyleData.value
|
||||
const mainWidth = canvasCenterRef.value.clientWidth
|
||||
const mainHeight = canvasCenterRef.value.clientHeight
|
||||
mainHeight.value = canvasCenterRef.value.clientHeight
|
||||
const scrollX = (1.5 * width - mainWidth) / 2
|
||||
const scrollY = (1.5 * height - mainHeight) / 2 + 20
|
||||
const scrollY = (1.5 * height - mainHeight.value) / 2 + 20
|
||||
// 设置画布初始滚动条位置
|
||||
canvasOut.value.scrollTo(scrollX, scrollY)
|
||||
})
|
||||
@ -359,6 +360,12 @@ const canvasPropertiesShow = computed(
|
||||
const viewsPropertiesShow = computed(
|
||||
() => !!(curComponent.value && ['UserView', 'VQuery'].includes(curComponent.value.component))
|
||||
)
|
||||
|
||||
const scrollCanvas = e => {
|
||||
deWRulerRef.value.rulerScroll(e)
|
||||
deHRulerRef.value.rulerScroll(e)
|
||||
}
|
||||
|
||||
eventBus.on('handleNew', handleNew)
|
||||
</script>
|
||||
|
||||
@ -390,7 +397,19 @@ eventBus.on('handleNew', handleNew)
|
||||
</dv-sidebar>
|
||||
<!-- 中间画布 -->
|
||||
<main id="dv-main-center" class="center" ref="canvasCenterRef">
|
||||
<el-scrollbar ref="canvasOut" class="content" :class="{ 'preview-content': previewStatus }">
|
||||
<div class="de-ruler-icon-outer">
|
||||
<el-icon class="de-ruler-icon">
|
||||
<Icon name="dv-ruler" />
|
||||
</el-icon>
|
||||
</div>
|
||||
<de-ruler ref="deWRulerRef"></de-ruler>
|
||||
<de-ruler direction="vertical" :size="mainHeight" ref="deHRulerRef"></de-ruler>
|
||||
<el-scrollbar
|
||||
ref="canvasOut"
|
||||
@scroll="scrollCanvas"
|
||||
class="content"
|
||||
:class="{ 'preview-content': previewStatus }"
|
||||
>
|
||||
<div
|
||||
id="canvas-dv-outer"
|
||||
ref="canvasInner"
|
||||
@ -400,15 +419,17 @@ eventBus.on('handleNew', handleNew)
|
||||
@mousedown="handleMouseDown"
|
||||
@mouseup="deselectCurComponent"
|
||||
>
|
||||
<canvas-core
|
||||
class="canvas-area-shadow editor-main"
|
||||
v-if="state.canvasInitStatus"
|
||||
ref="mainCanvasCoreRef"
|
||||
:component-data="componentData"
|
||||
:canvas-style-data="canvasStyleData"
|
||||
:canvas-view-info="canvasViewInfo"
|
||||
:canvas-id="state.canvasId"
|
||||
></canvas-core>
|
||||
<div class="canvas-dv-inner">
|
||||
<canvas-core
|
||||
class="canvas-area-shadow editor-main"
|
||||
v-if="state.canvasInitStatus"
|
||||
ref="mainCanvasCoreRef"
|
||||
:component-data="componentData"
|
||||
:canvas-style-data="canvasStyleData"
|
||||
:canvas-view-info="canvasViewInfo"
|
||||
:canvas-id="state.canvasId"
|
||||
></canvas-core>
|
||||
</div>
|
||||
</div>
|
||||
</el-scrollbar>
|
||||
<ComponentToolBar :class="{ 'preview-aside-x': previewStatus }"></ComponentToolBar>
|
||||
@ -535,4 +556,27 @@ eventBus.on('handleNew', handleNew)
|
||||
height: 1px;
|
||||
background: #000;
|
||||
}
|
||||
|
||||
.canvas-dv-inner {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.de-ruler-icon-outer {
|
||||
background: #2c2c2c;
|
||||
position: absolute;
|
||||
width: 30px;
|
||||
height: 30px;
|
||||
z-index: 3;
|
||||
color: #ebebeb;
|
||||
.de-ruler-icon {
|
||||
margin-left: 6px;
|
||||
margin-top: 6px;
|
||||
font-size: 24px;
|
||||
color: #ebebeb;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -2,6 +2,12 @@ import request from '@/config/axios'
|
||||
import { useCache } from '@/hooks/web/useCache'
|
||||
import { isInIframe } from '@/utils/utils'
|
||||
const { wsCache } = useCache()
|
||||
|
||||
export interface TicketValidVO {
|
||||
ticketValid: boolean
|
||||
ticketExp: boolean
|
||||
args: string
|
||||
}
|
||||
export interface ProxyInfo {
|
||||
resourceId: string
|
||||
uid: string
|
||||
@ -9,17 +15,36 @@ export interface ProxyInfo {
|
||||
pwdValid?: boolean
|
||||
type: string
|
||||
inIframeError: boolean
|
||||
ticketValidVO: TicketValidVO
|
||||
}
|
||||
class ShareProxy {
|
||||
uuid: string
|
||||
constructor() {
|
||||
this.uuid = ''
|
||||
}
|
||||
getTicket() {
|
||||
const curLocation = window.location.href
|
||||
const pmIndex = curLocation.lastIndexOf('?')
|
||||
if (pmIndex == -1) {
|
||||
return null
|
||||
}
|
||||
const searchText = curLocation.substring(pmIndex + 1)
|
||||
const regex = /([^&=]+)=([^&]*)/g
|
||||
let m
|
||||
while ((m = regex.exec(searchText)) !== null) {
|
||||
const key = decodeURIComponent(m[1])
|
||||
if (key === 'ticket') {
|
||||
return decodeURIComponent(m[2])
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
setUuid() {
|
||||
const curLocation = window.location.href
|
||||
const pmIndex = curLocation.lastIndexOf('?')
|
||||
const uuidObj = curLocation.substring(
|
||||
curLocation.lastIndexOf('de-link/') + 8,
|
||||
curLocation.lastIndexOf('?') > 0 ? curLocation.lastIndexOf('?') : curLocation.length
|
||||
pmIndex > 0 ? pmIndex : curLocation.length
|
||||
)
|
||||
this.uuid = uuidObj
|
||||
}
|
||||
@ -31,7 +56,8 @@ class ShareProxy {
|
||||
const uuid = this.uuid
|
||||
const url = '/share/proxyInfo'
|
||||
const inIframe = isInIframe()
|
||||
const param = { uuid, ciphertext: null, inIframe }
|
||||
const ticket = this.getTicket()
|
||||
const param = { uuid, ciphertext: null, inIframe, ticket }
|
||||
const ciphertext = wsCache.get(`link-${uuid}`)
|
||||
if (ciphertext) {
|
||||
param['ciphertext'] = ciphertext
|
||||
|
6
core/core-frontend/src/views/share/link/TicketError.vue
Normal file
6
core/core-frontend/src/views/share/link/TicketError.vue
Normal 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="Ticket参数错误" />
|
||||
</template>
|
@ -4,28 +4,40 @@
|
||||
<LinkError v-else-if="!loading && !linkExist" />
|
||||
<Exp v-else-if="!loading && linkExp" />
|
||||
<PwdTips v-else-if="!loading && !pwdValid" />
|
||||
<TicketError
|
||||
v-else-if="!loading && (!state.ticketValidVO.ticketValid || state.ticketValidVO.ticketExp)"
|
||||
/>
|
||||
<PreviewCanvas
|
||||
v-else
|
||||
:class="{ 'hidden-link': loading }"
|
||||
ref="pcanvas"
|
||||
public-link-status="true"
|
||||
public-link-status
|
||||
:ticket-args="state.ticketValidVO.args"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { onMounted, nextTick, ref } from 'vue'
|
||||
import { onMounted, nextTick, ref, reactive } 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'
|
||||
import IframeError from './IframeError.vue'
|
||||
import TicketError from './TicketError.vue'
|
||||
const pcanvas = ref(null)
|
||||
const iframeError = ref(true)
|
||||
const linkExist = ref(false)
|
||||
const loading = ref(true)
|
||||
const linkExp = ref(false)
|
||||
const pwdValid = ref(false)
|
||||
const state = reactive({
|
||||
ticketValidVO: {
|
||||
ticketValid: false,
|
||||
ticketExp: false,
|
||||
args: ''
|
||||
}
|
||||
})
|
||||
onMounted(async () => {
|
||||
const proxyInfo = (await shareProxy.loadProxy()) as ProxyInfo
|
||||
if (proxyInfo?.inIframeError) {
|
||||
@ -41,6 +53,7 @@ onMounted(async () => {
|
||||
linkExist.value = true
|
||||
linkExp.value = !!proxyInfo.exp
|
||||
pwdValid.value = !!proxyInfo.pwdValid
|
||||
state.ticketValidVO = proxyInfo.ticketValidVO
|
||||
nextTick(() => {
|
||||
const method = pcanvas?.value?.loadCanvasDataAsync
|
||||
if (method) {
|
||||
|
@ -16,7 +16,10 @@
|
||||
<el-dialog
|
||||
v-if="dialogVisible && props.weight >= 7"
|
||||
class="copy-link_dialog"
|
||||
:class="{ 'hidden-footer': !shareEnable || showTicket }"
|
||||
:class="{
|
||||
'hidden-footer': !shareEnable || showTicket,
|
||||
'is-ticket-dialog': shareEnable && showTicket
|
||||
}"
|
||||
v-model="dialogVisible"
|
||||
:close-on-click-modal="true"
|
||||
:append-to-body="true"
|
||||
@ -118,11 +121,18 @@
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="shareEnable && showTicket" class="share-ticket-container">
|
||||
<share-ticket :link-url="linkAddr" @close="closeTicket" />
|
||||
<share-ticket
|
||||
:link-url="linkAddr"
|
||||
: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">Ticket 设置</el-button> -->
|
||||
<el-button secondary @click="openTicket">Ticket 设置</el-button>
|
||||
<el-button :disabled="!shareEnable || expError" type="primary" @click.stop="copyInfo">
|
||||
{{ t('visualization.copy_link') }}
|
||||
</el-button>
|
||||
@ -167,7 +177,8 @@ const state = reactive({
|
||||
uuid: '',
|
||||
pwd: '',
|
||||
exp: 0,
|
||||
autoPwd: true
|
||||
autoPwd: true,
|
||||
ticketRequire: false
|
||||
} as ShareInfo
|
||||
})
|
||||
const emits = defineEmits(['loaded'])
|
||||
@ -335,12 +346,14 @@ const expChangeHandler = exp => {
|
||||
}
|
||||
const beforeClose = async done => {
|
||||
if (!shareEnable.value) {
|
||||
showTicket.value = false
|
||||
done()
|
||||
return
|
||||
}
|
||||
const pwdValid = validatePwdFormat()
|
||||
const uuidValid = await validateUuid()
|
||||
if (pwdValid && uuidValid) {
|
||||
showTicket.value = false
|
||||
done()
|
||||
}
|
||||
}
|
||||
@ -460,6 +473,9 @@ const openTicket = () => {
|
||||
const closeTicket = () => {
|
||||
showTicket.value = false
|
||||
}
|
||||
const updateRequireTicket = val => {
|
||||
state.detailInfo.ticketRequire = val
|
||||
}
|
||||
|
||||
const execute = () => {
|
||||
share()
|
||||
@ -480,6 +496,11 @@ onMounted(() => {
|
||||
})
|
||||
</script>
|
||||
<style lang="less">
|
||||
.is-ticket-dialog {
|
||||
.ed-dialog__header {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
.copy-link_dialog {
|
||||
.ed-dialog__header {
|
||||
padding: 16px 16px 10px !important;
|
||||
|
@ -4,7 +4,7 @@
|
||||
<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="close">
|
||||
<el-icon class="custom-el-icon back-icon" @click.stop="close">
|
||||
<Icon class="toolbar-icon" name="icon_left_outlined" />
|
||||
</el-icon>
|
||||
</span>
|
||||
@ -12,31 +12,33 @@
|
||||
<span class="ticket-title">{{ 'Ticket ' + $t('commons.setting') }}</span>
|
||||
</div>
|
||||
<div class="ticket-model-end">
|
||||
<el-checkbox v-model="requireTicket" @change="requireTicketChange" />
|
||||
<span>{{ $t('link_ticket.require') }}</span>
|
||||
<el-checkbox
|
||||
v-model="ticketRequire"
|
||||
@change="requireTicketChange"
|
||||
:label="t('link_ticket.require')"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div class="ticket-add">
|
||||
<el-button @click.stop="addRow" text>
|
||||
<template #icon>
|
||||
<icon name="icon_add_outlined"></icon>
|
||||
</template>
|
||||
{{ t('commons.create') }}
|
||||
</el-button>
|
||||
</div>
|
||||
<div class="ticket-table">
|
||||
<!-- <div class="text-add-ticket">
|
||||
<el-button class="de-text-btn mr2" type="text" icon="el-icon-plus" @click="addRow">{{
|
||||
$t('commons.create')
|
||||
}}</el-button>
|
||||
</div>
|
||||
|
||||
<el-table :data="tableData" style="width: 100%" size="mini">
|
||||
<el-table-column prop="ticket" label="ticket" width="120">
|
||||
<el-table :data="state.tableData" style="width: 100%" size="small">
|
||||
<el-table-column prop="ticket" label="Ticket" width="130">
|
||||
<template v-slot="scope">
|
||||
<div class="ticket-row">
|
||||
<span>{{ scope.row.ticket }}</span>
|
||||
<span :title="scope.row.ticket">{{ scope.row.ticket }}</span>
|
||||
<el-tooltip class="item" effect="dark" :content="$t('commons.copy')" placement="top">
|
||||
<span
|
||||
v-clipboard:copy="`${props.linkUrl}?ticket=${scope.row.ticket}`"
|
||||
v-clipboard:success="onCopy"
|
||||
v-clipboard:error="onError"
|
||||
class="copy-i"
|
||||
>
|
||||
<svg-icon icon-class="de-icon-copy" />
|
||||
</span>
|
||||
<el-button text @click.stop="copyTicket(scope.row.ticket)">
|
||||
<template #icon>
|
||||
<Icon name="de-copy"></Icon>
|
||||
</template>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip
|
||||
class="item"
|
||||
@ -44,31 +46,34 @@
|
||||
:content="`${$t('link_ticket.refresh')} ticket`"
|
||||
placement="top"
|
||||
>
|
||||
<span class="refresh-i">
|
||||
<i class="el-icon-refresh-right" @click="refreshTicket(scope.row)" />
|
||||
</span>
|
||||
<el-button text @click.stop="refreshTicket(scope.row)">
|
||||
<template #icon>
|
||||
<Icon name="icon_refresh_outlined"></Icon>
|
||||
</template>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column prop="exp" :label="$t('panel.over_time')" width="100">
|
||||
<el-table-column prop="exp" :label="$t('visualization.over_time')" width="100">
|
||||
<template v-slot:header>
|
||||
<span>{{ $t('panel.over_time') }}</span>
|
||||
<el-tooltip
|
||||
class="item"
|
||||
effect="dark"
|
||||
:content="$t('link_ticket.time_tips')"
|
||||
placement="top"
|
||||
>
|
||||
<span class="check-tips">
|
||||
<svg-icon icon-class="de-icon-info" @click="closeTicket" />
|
||||
</span>
|
||||
</el-tooltip>
|
||||
<div class="ticket-exp-head">
|
||||
<span>{{ $t('visualization.over_time') }}</span>
|
||||
<el-tooltip
|
||||
class="item"
|
||||
effect="dark"
|
||||
:content="$t('link_ticket.time_tips')"
|
||||
placement="top"
|
||||
>
|
||||
<Icon name="dv-info"></Icon>
|
||||
</el-tooltip>
|
||||
</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')"
|
||||
@ -86,6 +91,7 @@
|
||||
<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')"
|
||||
@ -100,16 +106,18 @@
|
||||
</el-table-column>
|
||||
<el-table-column :label="$t('commons.operating')" width="80">
|
||||
<template v-slot="scope">
|
||||
<div class="ticket-op">
|
||||
<div class="ticket-row">
|
||||
<el-tooltip
|
||||
class="item"
|
||||
effect="dark"
|
||||
:content="$t('commons.delete')"
|
||||
placement="top"
|
||||
>
|
||||
<span>
|
||||
<i class="el-icon-delete" @click="deleteTicket(scope.row, scope.$idnex)" />
|
||||
</span>
|
||||
<el-button text @click.stop="deleteTicket(scope.row, scope.$index)">
|
||||
<template #icon>
|
||||
<Icon name="icon_delete-trash_outlined"></Icon>
|
||||
</template>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
<el-tooltip
|
||||
class="item"
|
||||
@ -117,93 +125,233 @@
|
||||
:content="scope.row.isEdit ? $t('commons.save') : $t('commons.edit')"
|
||||
placement="top"
|
||||
>
|
||||
<span>
|
||||
<i v-if="!scope.row.isEdit" class="el-icon-edit" @click="editRow(scope.row)" />
|
||||
<i
|
||||
v-else
|
||||
class="el-icon-circle-check"
|
||||
@click="saveRow(scope.row, scope.$index)"
|
||||
/>
|
||||
</span>
|
||||
<el-button v-if="!scope.row.isEdit" text @click.stop="editRow(scope.row)">
|
||||
<template #icon>
|
||||
<Icon name="icon_edit_outlined"></Icon>
|
||||
</template>
|
||||
</el-button>
|
||||
<el-button v-else text @click.stop="saveRow(scope.row, scope.$index)">
|
||||
<template #icon>
|
||||
<Icon name="edit-done"></Icon>
|
||||
</template>
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table> -->
|
||||
</el-table>
|
||||
</div>
|
||||
|
||||
<div class="ticket-btn">
|
||||
<el-button type="primary" @click.stop="finish"> 完成 </el-button>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { ref, reactive } from 'vue'
|
||||
import { ref, reactive, onMounted, toRefs } from 'vue'
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import request from '@/config/axios'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus-secondary'
|
||||
import useClipboard from 'vue-clipboard3'
|
||||
const { toClipboard } = useClipboard()
|
||||
const { t } = useI18n()
|
||||
const props = defineProps({
|
||||
linkUrl: propTypes.string.def(null)
|
||||
linkUrl: propTypes.string.def(null),
|
||||
uuid: propTypes.string.def(null),
|
||||
resourceId: propTypes.string.def(null),
|
||||
ticketRequire: propTypes.bool
|
||||
})
|
||||
|
||||
const requireTicket = ref(false)
|
||||
const tableData = reactive([])
|
||||
const emits = defineEmits(['close'])
|
||||
const { ticketRequire } = toRefs(props)
|
||||
const expRefs = ref({})
|
||||
const argRefs = ref({})
|
||||
|
||||
const state = reactive({
|
||||
tableData: []
|
||||
})
|
||||
const emits = defineEmits(['close', 'requireChange'])
|
||||
|
||||
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 => {
|
||||
console.log(val)
|
||||
const url = '/ticket/enableTicket'
|
||||
const data = {
|
||||
resourceId: props.resourceId,
|
||||
require: val
|
||||
}
|
||||
request.post({ url, data }).then(() => {
|
||||
emits('requireChange', val)
|
||||
})
|
||||
}
|
||||
const createLimit = (count?: number) => {
|
||||
const realCount = count ? count : state.tableData.length || 0
|
||||
if (realCount > 4) {
|
||||
ElMessageBox.confirm('提示', {
|
||||
confirmButtonType: 'primary',
|
||||
type: 'warning',
|
||||
confirmButtonText: t('common.roger_that'),
|
||||
cancelButtonText: t('dataset.cancel'),
|
||||
autofocus: false,
|
||||
showClose: false,
|
||||
showCancelButton: false,
|
||||
tip: '最多支持创建5个Ticket'
|
||||
})
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
const addRow = () => {
|
||||
console.log(11)
|
||||
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)
|
||||
})
|
||||
}
|
||||
|
||||
const refreshTicket = row => {
|
||||
console.log(row)
|
||||
const copyTicket = async ticket => {
|
||||
const url = `${props.linkUrl}?ticket=${ticket}`
|
||||
try {
|
||||
await toClipboard(url)
|
||||
ElMessage.success(t('common.copy_success'))
|
||||
} catch (e) {
|
||||
ElMessage.warning(t('common.copy_unsupported'), e)
|
||||
}
|
||||
}
|
||||
const closeTicket = () => {
|
||||
console.log(11)
|
||||
const refreshTicket = row => {
|
||||
const url = '/ticket/saveTicket'
|
||||
const param = JSON.parse(JSON.stringify(row))
|
||||
param['generateNew'] = true
|
||||
request.post({ url, data: param }).then(res => {
|
||||
row.ticket = res.data
|
||||
})
|
||||
}
|
||||
|
||||
const validateExp = (val, index) => {
|
||||
console.log(val)
|
||||
console.log(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) => {
|
||||
console.log(val)
|
||||
console.log(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 = (val, index) => {
|
||||
console.log(val)
|
||||
console.log(index)
|
||||
const deleteTicket = (row, index) => {
|
||||
const param = { ticket: row.ticket }
|
||||
const url = '/ticket/delTicket'
|
||||
request.post({ url, data: param }).then(() => {
|
||||
state.tableData.splice(index, 1)
|
||||
})
|
||||
}
|
||||
|
||||
const saveRow = (val, index) => {
|
||||
console.log(val)
|
||||
console.log(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 => {
|
||||
console.log(row)
|
||||
row.isEdit = true
|
||||
}
|
||||
const onCopy = e => {
|
||||
console.log(e)
|
||||
|
||||
const finish = () => {
|
||||
close()
|
||||
}
|
||||
const onError = e => {
|
||||
console.log(e)
|
||||
|
||||
const loadTicketData = () => {
|
||||
const resourceId = props.resourceId
|
||||
const url = `/ticket/query/${resourceId}`
|
||||
request.get({ url }).then(res => {
|
||||
state.tableData = res.data || []
|
||||
})
|
||||
}
|
||||
onMounted(() => {
|
||||
loadTicketData()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.ticket {
|
||||
height: 261px;
|
||||
min-height: 280px;
|
||||
.ticket-model {
|
||||
display: flex;
|
||||
height: 22px;
|
||||
justify-content: space-between;
|
||||
padding: 16px 0;
|
||||
padding: 0;
|
||||
.ticket-model-start {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #1f2329;
|
||||
font-family: PingFang SC;
|
||||
font-weight: 500;
|
||||
@ -212,6 +360,11 @@ const onError = e => {
|
||||
font-size: 14px;
|
||||
}
|
||||
.back-tips {
|
||||
i {
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
width: 22px;
|
||||
margin-right: 4px;
|
||||
display: flex;
|
||||
@ -224,32 +377,109 @@ const onError = e => {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
label {
|
||||
margin-right: 8px;
|
||||
.ticket-model-end {
|
||||
display: flex;
|
||||
label {
|
||||
height: 22px;
|
||||
margin-right: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
.ticket-add {
|
||||
margin: 16px 0;
|
||||
height: 22px;
|
||||
button {
|
||||
height: 22px;
|
||||
line-height: 22px;
|
||||
}
|
||||
}
|
||||
.ticket-table {
|
||||
border-top: 1px solid #d5d7d8;
|
||||
min-height: 156px;
|
||||
padding: 0 0;
|
||||
// height: 260px;
|
||||
height: 50px;
|
||||
overflow-y: overlay;
|
||||
position: relative;
|
||||
::v-deep .error-msg {
|
||||
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;
|
||||
}
|
||||
::v-deep .check-tips {
|
||||
margin-left: 4px;
|
||||
}
|
||||
.text-add-ticket {
|
||||
width: 48px;
|
||||
:deep(.ticket-exp-head) {
|
||||
display: flex;
|
||||
line-height: 22px;
|
||||
height: 22px;
|
||||
gap: 4px;
|
||||
align-items: center;
|
||||
svg {
|
||||
margin-left: 4px;
|
||||
width: 16px;
|
||||
height: 16px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
:deep(.ticket-row) {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 22px;
|
||||
span {
|
||||
width: 66px;
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
:deep(.ed-table__header) {
|
||||
background-color: #f5f6f7;
|
||||
thead {
|
||||
tr {
|
||||
th {
|
||||
background-color: #f5f6f7 !important;
|
||||
.cell {
|
||||
line-height: 22px;
|
||||
padding: 4px 8px;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
height: 38px;
|
||||
}
|
||||
:deep(.ed-table__row) {
|
||||
height: 39px;
|
||||
td {
|
||||
.cell {
|
||||
line-height: 22px;
|
||||
height: 22px;
|
||||
font-size: 14px;
|
||||
font-weight: 400;
|
||||
}
|
||||
}
|
||||
}
|
||||
: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-start"
|
||||
:show-arrow="false"
|
||||
popper-class="share-popover"
|
||||
:popper-class="`share-popover ${showTicket ? 'share-ticket-popover' : ''}`"
|
||||
@show="share"
|
||||
>
|
||||
<template #reference>
|
||||
@ -21,15 +21,12 @@
|
||||
{{ t('visualization.share') }}
|
||||
</el-button>
|
||||
</template>
|
||||
<div class="share-container">
|
||||
<div class="share-container" :class="{ 'hidden-link-container': showTicket }">
|
||||
<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="custom-link-line share-padding">
|
||||
<el-input
|
||||
ref="linkUuidRef"
|
||||
@ -117,11 +114,22 @@
|
||||
|
||||
<el-divider v-if="shareEnable" class="share-divider" />
|
||||
<div v-if="shareEnable" class="share-foot share-padding">
|
||||
<el-button secondary @click="openTicket">Ticket 设置</el-button>
|
||||
<el-button :disabled="!shareEnable || expError" type="primary" @click="copyInfo">
|
||||
{{ t('visualization.copy_link') }}
|
||||
</el-button>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="shareEnable && showTicket" class="share-ticket-container">
|
||||
<share-ticket
|
||||
:link-url="linkAddr"
|
||||
:uuid="state.detailInfo.uuid"
|
||||
:resource-id="props.resourceId"
|
||||
:ticket-require="state.detailInfo.ticketRequire"
|
||||
@require-change="updateRequireTicket"
|
||||
@close="closeTicket"
|
||||
/>
|
||||
</div>
|
||||
</el-popover>
|
||||
</template>
|
||||
|
||||
@ -133,6 +141,8 @@ import { propTypes } from '@/utils/propTypes'
|
||||
import { ShareInfo, SHARE_BASE, shortcuts } from './option'
|
||||
import { ElMessage, ElLoading } from 'element-plus-secondary'
|
||||
import useClipboard from 'vue-clipboard3'
|
||||
import ShareTicket from './ShareTicket.vue'
|
||||
|
||||
const { toClipboard } = useClipboard()
|
||||
const { t } = useI18n()
|
||||
const props = defineProps({
|
||||
@ -150,6 +160,7 @@ const linkAddr = ref('')
|
||||
const expError = ref(false)
|
||||
const linkCustom = ref(false)
|
||||
const linkUuidRef = ref(null)
|
||||
const showTicket = ref(false)
|
||||
const state = reactive({
|
||||
detailInfo: {
|
||||
id: '',
|
||||
@ -186,6 +197,7 @@ const clickOutPopover = e => {
|
||||
}
|
||||
const openPopover = () => {
|
||||
if (!popoverVisible.value) {
|
||||
showTicket.value = false
|
||||
popoverVisible.value = true
|
||||
}
|
||||
}
|
||||
@ -468,6 +480,16 @@ const finishEditUuid = async () => {
|
||||
linkCustom.value = !uuidValid
|
||||
}
|
||||
|
||||
const openTicket = () => {
|
||||
showTicket.value = true
|
||||
}
|
||||
const closeTicket = () => {
|
||||
showTicket.value = false
|
||||
}
|
||||
const updateRequireTicket = val => {
|
||||
state.detailInfo.ticketRequire = val
|
||||
}
|
||||
|
||||
const execute = () => {
|
||||
share()
|
||||
}
|
||||
@ -477,12 +499,21 @@ defineExpose({
|
||||
</script>
|
||||
|
||||
<style lang="less">
|
||||
.share-popover {
|
||||
.share-popover:not(.share-ticket-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;
|
||||
|
@ -4,6 +4,7 @@ export interface ShareInfo {
|
||||
uuid: string
|
||||
pwd?: string
|
||||
autoPwd: boolean
|
||||
ticketRequire?: boolean
|
||||
}
|
||||
|
||||
export const SHARE_BASE = '/de-link/'
|
||||
|
@ -1,5 +1,5 @@
|
||||
<script lang="tsx" setup>
|
||||
import { nextTick, reactive, ref, shallowRef } from 'vue'
|
||||
import { nextTick, reactive, ref, shallowRef, provide } from 'vue'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import type { FormInstance, FormRules } from 'element-plus-secondary'
|
||||
import { ElIcon, ElMessage } from 'element-plus-secondary'
|
||||
@ -150,8 +150,10 @@ const rule = reactive<FormRules>({
|
||||
}
|
||||
]
|
||||
})
|
||||
|
||||
const initApiItem = (val: ApiItem, apiList) => {
|
||||
const activeName = ref('third')
|
||||
provide('api-active-name', activeName)
|
||||
const initApiItem = (val: ApiItem, apiList, name) => {
|
||||
activeName.value = name
|
||||
apiItemList = apiList
|
||||
Object.assign(apiItem, val)
|
||||
edit_api_item.value = true
|
||||
@ -406,7 +408,9 @@ defineExpose({
|
||||
<span class="icon">
|
||||
{{ active <= 1 ? '2' : '' }}
|
||||
</span>
|
||||
<span class="title">{{ t('datasource.api_step_2') }}</span>
|
||||
<span class="title">{{
|
||||
activeName === 'third' ? t('datasource.api_step_2') : '提取参数'
|
||||
}}</span>
|
||||
</div>
|
||||
</template>
|
||||
</el-step>
|
||||
|
@ -1,6 +1,6 @@
|
||||
<script lang="ts" setup>
|
||||
import { propTypes } from '@/utils/propTypes'
|
||||
import { computed, onBeforeMount, PropType, toRefs } from 'vue'
|
||||
import { computed, onBeforeMount, PropType, toRefs, inject, ref } from 'vue'
|
||||
import { useI18n } from '@/hooks/web/useI18n'
|
||||
import { KeyValue } from './ApiTestModel.js'
|
||||
import draggable from 'vuedraggable'
|
||||
@ -44,6 +44,8 @@ onBeforeMount(() => {
|
||||
}
|
||||
})
|
||||
|
||||
const activeName = inject('api-active-name')
|
||||
|
||||
const remove = (index: number) => {
|
||||
if (isDisable()) return
|
||||
// 移除整行输入控件及内容
|
||||
@ -66,6 +68,18 @@ const createFilter = (queryString: string) => {
|
||||
return restaurant.value.toLowerCase().indexOf(queryString.toLowerCase()) === 0
|
||||
}
|
||||
}
|
||||
const options = [
|
||||
{
|
||||
label: '参数',
|
||||
value: 'params'
|
||||
},
|
||||
{
|
||||
label: '固定值',
|
||||
value: 'fixed'
|
||||
}
|
||||
]
|
||||
|
||||
const value = ref('')
|
||||
</script>
|
||||
|
||||
<template>
|
||||
@ -77,7 +91,7 @@ const createFilter = (queryString: string) => {
|
||||
<el-icon class="drag handle">
|
||||
<Icon name="icon_drag_outlined"></Icon>
|
||||
</el-icon>
|
||||
<el-col :span="8" v-if="!unShowSelect">
|
||||
<el-col :span="activeName === 'third' ? 8 : 6" v-if="!unShowSelect">
|
||||
<el-input
|
||||
v-if="!suggestions"
|
||||
v-model="element.name"
|
||||
@ -96,7 +110,16 @@ const createFilter = (queryString: string) => {
|
||||
show-word-limit
|
||||
/>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="3" v-if="activeName === 'fourth'">
|
||||
<el-select v-model="value">
|
||||
<el-option
|
||||
v-for="item in options"
|
||||
:key="item.value"
|
||||
:label="item.label"
|
||||
:value="item.value"
|
||||
/>
|
||||
</el-select>
|
||||
</el-col>
|
||||
<el-col :span="8" v-if="unShowSelect">
|
||||
<el-input
|
||||
v-if="!!suggestions.length"
|
||||
@ -108,14 +131,21 @@ const createFilter = (queryString: string) => {
|
||||
/>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="7">
|
||||
<el-col :span="activeName === 'third' ? 7 : 6">
|
||||
<el-input
|
||||
v-if="!needMock"
|
||||
v-if="!needMock && activeName === 'third'"
|
||||
v-model="element.value"
|
||||
:disabled="isReadOnly"
|
||||
:placeholder="unShowSelect ? t('common.description') : valueText"
|
||||
show-word-limit
|
||||
/>
|
||||
<el-input
|
||||
v-if="!needMock && activeName === 'fourth'"
|
||||
v-model="element.value"
|
||||
:disabled="isReadOnly"
|
||||
:placeholder="value === 'params' ? '参数名称' : '值'"
|
||||
show-word-limit
|
||||
/>
|
||||
</el-col>
|
||||
|
||||
<el-col :span="7" v-if="showDesc">
|
||||
|
@ -6,11 +6,12 @@ import EmptyBackground from '@/components/empty-background/src/EmptyBackground.v
|
||||
import { cloneDeep } from 'lodash-es'
|
||||
import ApiHttpRequestDraw from './ApiHttpRequestDraw.vue'
|
||||
import type { Configuration, ApiConfiguration, SyncSetting } from './option'
|
||||
import { fieldType, fieldTypeText } from '@/utils/attr'
|
||||
import { Icon } from '@/components/icon-custom'
|
||||
import { getSchema } from '@/api/datasource'
|
||||
import { Base64 } from 'js-base64'
|
||||
import { CustomPassword } from '@/components/custom-password'
|
||||
import { ElForm, ElMessage } from 'element-plus-secondary'
|
||||
import { ElForm, ElMessage, ElMessageBox } from 'element-plus-secondary'
|
||||
import Cron from '@/components/cron/src/Cron.vue'
|
||||
import { ComponentPublicInstance } from 'vue'
|
||||
const { t } = useI18n()
|
||||
@ -324,9 +325,11 @@ const addApiItem = item => {
|
||||
: 0
|
||||
}
|
||||
nextTick(() => {
|
||||
editApiItem.value.initApiItem(apiItem, form.value.apiConfiguration)
|
||||
editApiItem.value.initApiItem(apiItem, form.value.apiConfiguration, activeName.value)
|
||||
})
|
||||
}
|
||||
|
||||
const activeName = ref('third')
|
||||
const showPriority = ref(false)
|
||||
|
||||
const deleteItem = (item, idx) => {
|
||||
@ -470,7 +473,149 @@ const apiRule = {
|
||||
}
|
||||
]
|
||||
}
|
||||
const dialogEditParams = ref(false)
|
||||
const dialogRenameApi = ref(false)
|
||||
const activeParamsName = ref('')
|
||||
const apiParams = ref([
|
||||
{
|
||||
id: 1,
|
||||
name: '接口1'
|
||||
}
|
||||
])
|
||||
const paramsObj = ref({
|
||||
name: '',
|
||||
id: 1,
|
||||
deType: 0
|
||||
})
|
||||
|
||||
const apiObj = ref({
|
||||
name: '',
|
||||
id: 1
|
||||
})
|
||||
const paramsObjRules = {
|
||||
name: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入参数名称',
|
||||
trigger: 'change'
|
||||
},
|
||||
{
|
||||
min: 2,
|
||||
max: 64,
|
||||
message: '参数名称限制2~64字符',
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
const apiObjRules = {
|
||||
name: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入接口名称',
|
||||
trigger: 'change'
|
||||
},
|
||||
{
|
||||
min: 2,
|
||||
max: 64,
|
||||
message: '接口名称限制2~64字符',
|
||||
trigger: 'blur'
|
||||
}
|
||||
]
|
||||
}
|
||||
const setActiveName = val => {
|
||||
activeParamsName.value = val.name
|
||||
}
|
||||
|
||||
const paramsObjRef = ref()
|
||||
const apiObjRef = ref()
|
||||
|
||||
const saveParamsObj = () => {
|
||||
paramsObjRef.value.validate(result => {
|
||||
if (result) {
|
||||
gridData.value.forEach(ele => {
|
||||
if (ele.id === paramsObj.value.id) {
|
||||
ele.name = paramsObj.value.name
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const saveApiObj = () => {
|
||||
apiObjRef.value.validate(result => {
|
||||
if (result) {
|
||||
apiParams.value.forEach(ele => {
|
||||
if (ele.id === apiObj.value.id) {
|
||||
ele.name = apiObj.value.name
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
const paramsResetForm = () => {
|
||||
dialogEditParams.value = false
|
||||
}
|
||||
|
||||
const apiResetForm = () => {
|
||||
dialogRenameApi.value = false
|
||||
}
|
||||
|
||||
const gridData = ref([
|
||||
{
|
||||
name: 'name',
|
||||
deType: 0,
|
||||
id: 0
|
||||
}
|
||||
])
|
||||
const handleApiParams = (cmd: string, data) => {
|
||||
if (cmd === 'rename') {
|
||||
dialogRenameApi.value = true
|
||||
paramsObj.value.name = data.name
|
||||
}
|
||||
if (cmd === 'delete') {
|
||||
ElMessageBox.confirm('确定删除吗?', {
|
||||
confirmButtonType: 'danger',
|
||||
type: 'warning',
|
||||
autofocus: false,
|
||||
showClose: false
|
||||
}).then(() => {
|
||||
apiParams.value.splice(0, 1)
|
||||
})
|
||||
}
|
||||
|
||||
if (cmd === 'edit') {
|
||||
addApiItem(data)
|
||||
}
|
||||
}
|
||||
|
||||
const editParams = data => {
|
||||
dialogEditParams.value = true
|
||||
}
|
||||
|
||||
const delParams = data => {
|
||||
ElMessageBox.confirm('确定删除吗?', {
|
||||
confirmButtonType: 'danger',
|
||||
type: 'warning',
|
||||
autofocus: false,
|
||||
showClose: false
|
||||
}).then(() => {
|
||||
apiParams.value.splice(0, 1)
|
||||
})
|
||||
}
|
||||
const datasetTypeList = [
|
||||
{
|
||||
label: '重命名',
|
||||
svgName: 'icon_rename_outlined',
|
||||
command: 'rename'
|
||||
},
|
||||
{
|
||||
label: '删除',
|
||||
svgName: 'icon_delete-trash_outlined',
|
||||
command: 'delete'
|
||||
}
|
||||
]
|
||||
defineExpose({
|
||||
submitForm,
|
||||
resetForm,
|
||||
@ -522,7 +667,10 @@ defineExpose({
|
||||
</el-form-item>
|
||||
<template v-if="form.type === 'API'">
|
||||
<div class="title-form_primary flex-space table-info-mr" v-show="activeStep !== 2">
|
||||
<span>{{ t('datasource.data_table') }}</span>
|
||||
<el-tabs v-model="activeName" class="api-tabs">
|
||||
<el-tab-pane :label="t('datasource.data_table')" name="third"></el-tab-pane>
|
||||
<el-tab-pane label="接口参数" name="fourth"></el-tab-pane>
|
||||
</el-tabs>
|
||||
<el-button type="primary" style="margin-left: auto" @click="() => addApiItem(null)">
|
||||
<template #icon>
|
||||
<Icon name="icon_add_outlined"></Icon>
|
||||
@ -536,7 +684,7 @@ defineExpose({
|
||||
:description="t('datasource.no_data_table')"
|
||||
img-type="noneWhite"
|
||||
/>
|
||||
<template v-if="form.type === 'API' && activeStep === 1">
|
||||
<template v-if="form.type === 'API' && activeStep === 1 && activeName === 'third'">
|
||||
<div class="api-card-content">
|
||||
<div
|
||||
v-for="(api, idx) in form.apiConfiguration"
|
||||
@ -609,6 +757,77 @@ defineExpose({
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<div
|
||||
style="display: flex"
|
||||
v-if="form.type === 'API' && activeStep === 1 && activeName === 'fourth'"
|
||||
>
|
||||
<div class="left-api_params">
|
||||
<div
|
||||
v-for="ele in apiParams"
|
||||
:class="[{ active: activeParamsName === ele.name }]"
|
||||
class="list-item_primary"
|
||||
:title="ele.name"
|
||||
:key="ele.id"
|
||||
@click="setActiveName(ele)"
|
||||
>
|
||||
<span class="label">{{ ele.name }}</span>
|
||||
<span class="name-copy">
|
||||
<el-icon class="hover-icon" @click.stop="handleApiParams('edit', ele)">
|
||||
<icon name="icon_edit_outlined"></icon>
|
||||
</el-icon>
|
||||
<handle-more
|
||||
icon-size="24px"
|
||||
@handle-command="cmd => handleApiParams(cmd, ele)"
|
||||
:menu-list="datasetTypeList"
|
||||
placement="bottom-start"
|
||||
></handle-more>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="right-api_params">
|
||||
<el-table
|
||||
height="300"
|
||||
style="width: 100%"
|
||||
header-cell-class-name="header-cell"
|
||||
:data="gridData"
|
||||
>
|
||||
<el-table-column :label="t('visualization.param_name')">
|
||||
<template #default="scope">
|
||||
{{ scope.row.name || '-' }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column :label="t('deDataset.parameter_type')">
|
||||
<template #default="scope">
|
||||
<div class="flex-align-center icon">
|
||||
<el-icon>
|
||||
<Icon
|
||||
:className="`field-icon-${fieldType[scope.row.deType]}`"
|
||||
:name="`field_${fieldType[scope.row.deType]}`"
|
||||
></Icon>
|
||||
</el-icon>
|
||||
{{ fieldTypeText[scope.row.deType] }}
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
|
||||
<el-table-column :label="t('common.operate')">
|
||||
<template #default="scope">
|
||||
<el-button text @click.stop="editParams(scope.row)">
|
||||
<template #icon>
|
||||
<Icon name="icon_edit_outlined"></Icon>
|
||||
</template>
|
||||
</el-button>
|
||||
|
||||
<el-button text @click.stop="delParams(scope.row)">
|
||||
<template #icon>
|
||||
<Icon name="icon_delete-trash_outlined"></Icon>
|
||||
</template>
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<template v-if="notapiexcelconfig">
|
||||
<el-form-item label="连接方式" prop="type">
|
||||
@ -957,6 +1176,61 @@ defineExpose({
|
||||
</el-form-item>
|
||||
</div>
|
||||
</el-form>
|
||||
<el-dialog
|
||||
title="编辑参数"
|
||||
v-model="dialogEditParams"
|
||||
width="420px"
|
||||
class="create-dialog"
|
||||
:before-close="paramsResetForm"
|
||||
>
|
||||
<el-form
|
||||
label-position="top"
|
||||
require-asterisk-position="right"
|
||||
ref="paramsObjRef"
|
||||
@keydown.stop.prevent.enter
|
||||
:model="paramsObj"
|
||||
:rules="paramsObjRules"
|
||||
>
|
||||
<el-form-item :label="t('visualization.param_name')" prop="name">
|
||||
<el-input placeholder="请输入参数名称" v-model="paramsObj.name" />
|
||||
</el-form-item>
|
||||
<el-form-item :label="t('deDataset.parameter_type')" prop="deType">
|
||||
<el-radio-group v-model="paramsObj.deType">
|
||||
<el-radio :value="0" label="文本"></el-radio>
|
||||
<el-radio :value="2" label="数值"></el-radio>
|
||||
<el-radio :value="3" label="数值(小数)"></el-radio>
|
||||
</el-radio-group>
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button secondary @click="paramsResetForm">{{ t('dataset.cancel') }} </el-button>
|
||||
<el-button type="primary" @click="saveParamsObj">{{ t('dataset.confirm') }} </el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
<el-dialog
|
||||
title="重命名"
|
||||
v-model="dialogRenameApi"
|
||||
width="420px"
|
||||
class="create-dialog"
|
||||
:before-close="apiResetForm"
|
||||
>
|
||||
<el-form
|
||||
label-position="top"
|
||||
require-asterisk-position="right"
|
||||
ref="paramsObjRef"
|
||||
@keydown.stop.prevent.enter
|
||||
:model="apiObj"
|
||||
:rules="apiObjRules"
|
||||
>
|
||||
<el-form-item label="接口名称" prop="name">
|
||||
<el-input placeholder="请输入接口名称" v-model="apiObj.name" />
|
||||
</el-form-item>
|
||||
</el-form>
|
||||
<template #footer>
|
||||
<el-button secondary @click="apiResetForm">{{ t('dataset.cancel') }} </el-button>
|
||||
<el-button type="primary" @click="saveApiObj">{{ t('dataset.confirm') }} </el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<api-http-request-draw @return-item="returnItem" ref="editApiItem"></api-http-request-draw>
|
||||
</div>
|
||||
@ -1037,8 +1311,44 @@ defineExpose({
|
||||
margin: 24px 0 16px 0;
|
||||
}
|
||||
|
||||
.left-api_params {
|
||||
border-top-left-radius: 4px;
|
||||
border-bottom-left-radius: 4px;
|
||||
border: 1px solid #bbbfc4;
|
||||
width: 300px;
|
||||
padding: 16px;
|
||||
.name-copy {
|
||||
display: none;
|
||||
line-height: 24px;
|
||||
margin-left: 4px;
|
||||
}
|
||||
|
||||
.list-item_primary:hover {
|
||||
.name-copy {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
.label {
|
||||
width: 74% !important;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.right-api_params {
|
||||
border-top-right-radius: 4px;
|
||||
border-bottom-right-radius: 4px;
|
||||
border: 1px solid #bbbfc4;
|
||||
border-left: none;
|
||||
width: calc(100% - 200px);
|
||||
}
|
||||
|
||||
.table-info-mr {
|
||||
margin: 28px 0 12px 0;
|
||||
.api-tabs {
|
||||
:deep(.ed-tabs__nav-wrap::after) {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.info-update {
|
||||
|
2
de-xpack
2
de-xpack
@ -1 +1 @@
|
||||
Subproject commit 97d33ae8253dd163a257c5025f21a7facb85004b
|
||||
Subproject commit 178f8f52209c1ffb81b58c8055733d411aef054a
|
2
pom.xml
2
pom.xml
@ -19,7 +19,7 @@
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
<dataease.version>2.7.0</dataease.version>
|
||||
<dataease.version>2.8.0</dataease.version>
|
||||
<java.version>21</java.version>
|
||||
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||
<spring-cloud-alibaba.version>2023.0.1.0</spring-cloud-alibaba.version>
|
||||
|
@ -20,4 +20,7 @@ public interface XpackComponentApi {
|
||||
|
||||
@GetMapping("/viewPlugins")
|
||||
List<XpackPluginsViewVO> viewPlugins();
|
||||
|
||||
@GetMapping("/pluginStaticInfo/{moduleName}")
|
||||
void pluginStaticInfo(@PathVariable("moduleName") String moduleName);
|
||||
}
|
||||
|
@ -0,0 +1,29 @@
|
||||
package io.dataease.api.xpack.share;
|
||||
|
||||
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.tags.Tag;
|
||||
import org.springframework.web.bind.annotation.GetMapping;
|
||||
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 {
|
||||
|
||||
@PostMapping("/saveTicket")
|
||||
String saveTicket(@RequestBody TicketCreator creator);
|
||||
|
||||
@PostMapping("/delTicket")
|
||||
void deleteTicket(@RequestBody TicketDelRequest request);
|
||||
|
||||
@PostMapping("/enableTicket")
|
||||
void switchRequire(@RequestBody TicketSwitchRequest request);
|
||||
|
||||
@GetMapping("/query/{resourceId}")
|
||||
List<TicketVO> query(@PathVariable("resourceId") Long resourceId);
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package io.dataease.api.xpack.share.request;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
@Data
|
||||
public class TicketCreator implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 8661378104009097296L;
|
||||
|
||||
private String ticket;
|
||||
|
||||
private Long exp;
|
||||
|
||||
private String args;
|
||||
|
||||
private String uuid;
|
||||
|
||||
private boolean generateNew;
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package io.dataease.api.xpack.share.request;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
@Data
|
||||
public class TicketDelRequest implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = -3978489349675065507L;
|
||||
|
||||
private String ticket;
|
||||
}
|
@ -0,0 +1,16 @@
|
||||
package io.dataease.api.xpack.share.request;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
@Data
|
||||
public class TicketSwitchRequest implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 7670768142874123370L;
|
||||
|
||||
private String resourceId;
|
||||
|
||||
private Boolean require = false;
|
||||
}
|
@ -20,4 +20,6 @@ public class XpackShareProxyRequest implements Serializable {
|
||||
@Schema(description = "密钥", requiredMode = Schema.RequiredMode.REQUIRED)
|
||||
private String ciphertext;
|
||||
private boolean inIframe;
|
||||
|
||||
private String ticket;
|
||||
}
|
||||
|
@ -0,0 +1,27 @@
|
||||
package io.dataease.api.xpack.share.vo;
|
||||
|
||||
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
|
||||
import com.fasterxml.jackson.databind.ser.std.ToStringSerializer;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
@Data
|
||||
public class TicketVO implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = -599110079356725271L;
|
||||
|
||||
@JsonSerialize(using = ToStringSerializer.class)
|
||||
private Long id;
|
||||
|
||||
private String uuid;
|
||||
|
||||
private String ticket;
|
||||
|
||||
private Long exp;
|
||||
|
||||
private String args;
|
||||
|
||||
private Long accessTime;
|
||||
}
|
@ -0,0 +1,18 @@
|
||||
package io.dataease.api.xpack.share.vo;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
|
||||
@Data
|
||||
public class TicketValidVO implements Serializable {
|
||||
@Serial
|
||||
private static final long serialVersionUID = 2452043685969885580L;
|
||||
|
||||
private boolean ticketValid;
|
||||
|
||||
private boolean ticketExp;
|
||||
|
||||
private String args;
|
||||
}
|
@ -32,4 +32,6 @@ public class XpackShareProxyVO implements Serializable {
|
||||
@Schema(description = "类型")
|
||||
private String type;
|
||||
private boolean inIframeError = true;
|
||||
|
||||
private TicketValidVO ticketValidVO;
|
||||
}
|
||||
|
@ -29,4 +29,6 @@ public class XpackShareVO implements Serializable {
|
||||
private String pwd;
|
||||
@Schema(description = "自动生成密码")
|
||||
private Boolean autoPwd = true;
|
||||
@Schema(description = "ticket必须")
|
||||
private Boolean ticketRequire = false;
|
||||
}
|
||||
|
@ -63,6 +63,7 @@ public class WhitelistUtils {
|
||||
|| StringUtils.startsWithAny(requestURI, "/appearance/image/")
|
||||
|| StringUtils.startsWithAny(requestURI, "/share/proxyInfo")
|
||||
|| StringUtils.startsWithAny(requestURI, "/xpackComponent/content")
|
||||
|| StringUtils.startsWithAny(requestURI, "/xpackComponent/pluginStaticInfo")
|
||||
|| StringUtils.startsWithAny(requestURI, "/geo/")
|
||||
|| StringUtils.startsWithAny(requestURI, "/websocket")
|
||||
|| StringUtils.startsWithAny(requestURI, "/map/")
|
||||
|
@ -1,7 +1,11 @@
|
||||
package io.dataease.extensions.view.factory;
|
||||
|
||||
import io.dataease.exception.DEException;
|
||||
import io.dataease.extensions.view.template.PluginsChartTemplate;
|
||||
import io.dataease.extensions.view.vo.XpackPluginsViewVO;
|
||||
import io.dataease.license.utils.LicenseUtil;
|
||||
import io.dataease.license.utils.LogUtil;
|
||||
import io.dataease.plugins.factory.DataEasePluginFactory;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@ -11,22 +15,29 @@ public class PluginsChartFactory {
|
||||
|
||||
private static final Map<String, PluginsChartTemplate> templateMap = new ConcurrentHashMap<>();
|
||||
|
||||
|
||||
public static PluginsChartTemplate getInstance(String render, String type) {
|
||||
if (!LicenseUtil.licenseValid()) DEException.throwException("插件功能只对企业版本可用!");
|
||||
String key = render + "_" + type;
|
||||
return templateMap.get(key);
|
||||
}
|
||||
|
||||
public static void loadTemplate(String render, String type, PluginsChartTemplate template) {
|
||||
if (!LicenseUtil.licenseValid()) DEException.throwException("插件功能只对企业版本可用!");
|
||||
String key = render + "_" + type;
|
||||
if (templateMap.containsKey(key)) return;
|
||||
templateMap.put(key, template);
|
||||
try {
|
||||
String moduleName = template.getPluginInfo().getModuleName();
|
||||
DataEasePluginFactory.loadTemplate(moduleName, template);
|
||||
} catch (Exception e) {
|
||||
LogUtil.error(e.getMessage(), new Throwable(e));
|
||||
DEException.throwException(e);
|
||||
}
|
||||
}
|
||||
|
||||
public static List<XpackPluginsViewVO> getViewConfigList() {
|
||||
if (!LicenseUtil.licenseValid()) DEException.throwException("插件功能只对企业版本可用!");
|
||||
return templateMap.values().stream().map(PluginsChartTemplate::getConfig).toList();
|
||||
}
|
||||
|
||||
public static List<String> getAllPlugins() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import lombok.Data;
|
||||
|
||||
import java.io.Serial;
|
||||
import java.io.Serializable;
|
||||
import java.util.Map;
|
||||
|
||||
@Data
|
||||
public class XpackPluginsViewVO implements Serializable {
|
||||
@ -26,4 +27,6 @@ public class XpackPluginsViewVO implements Serializable {
|
||||
|
||||
private String render;
|
||||
|
||||
private Map<String, String> staticMap;
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user