Merge remote-tracking branch 'origin/dev-v2' into dev-v2

This commit is contained in:
wangjiahao
2026-06-16 13:44:41 +08:00
18 changed files with 159 additions and 31 deletions

View File

@@ -42,7 +42,7 @@ public class LinkInterceptor implements HandlerInterceptor {
String requestURI = ServletUtils.request().getRequestURI();
if (StringUtils.startsWith(requestURI, WhitelistUtils.getContextPath())) {
requestURI = requestURI.replaceFirst(WhitelistUtils.getContextPath(), "");
requestURI = StringUtils.replaceOnce(requestURI, WhitelistUtils.getContextPath(), "");
}
if (StringUtils.startsWith(requestURI, AuthConstant.DE_API_PREFIX)) {
requestURI = requestURI.replaceFirst(AuthConstant.DE_API_PREFIX, "");

View File

@@ -27,6 +27,12 @@ watch(formatData, () => {
watch([props.theme], () => {
format()
})
watch(
() => props.data,
() => {
format()
}
)
onMounted(() => {
format()
})

View File

@@ -3017,6 +3017,7 @@ export default {
to_top: 'Pin to Top',
publish_recover: 'Revert Publish',
publish_tips1: 'Visible after publication',
no_permission_tips: 'No permission',
publish_tips2: 'Available after publication {0}',
cancel_publish_tips: 'Successfully unpublished',
resource_not_published: 'Resource not published',
@@ -4894,6 +4895,8 @@ export default {
add: 'Add Webhook',
search_placeholder: 'Search by name',
content_type: 'Content Type',
msg_template: 'Message Template',
msg_template_tips: 'Available placeholders: {t0}, {t1}, {t2}',
del_confirm: 'Are you sure you want to delete this Webhook?',
batch_del_confirm: 'Are you sure you want to delete {0} Webhooks?'
},

View File

@@ -2934,6 +2934,7 @@ export default {
to_top: '置頂',
publish_recover: '恢復到發佈版本',
publish_tips1: '發佈後可查看',
no_permission_tips: '當前資源無權限',
publish_tips2: '發佈後可{0}',
cancel_publish_tips: '取消發佈成功',
resource_not_published: '该資源未發佈',
@@ -4744,6 +4745,8 @@ export default {
add: '添加 Webhook',
search_placeholder: '通過名稱搜索',
content_type: '內容類型',
msg_template: '消息模板',
msg_template_tips: '可用占位符:{t0}、{t1}、{t2}',
del_confirm: '確定刪除該 Webhook嗎',
batch_del_confirm: '確定刪除 {0} 個 Webhook嗎'
}

View File

@@ -2940,6 +2940,7 @@ export default {
to_top: '置顶',
publish_recover: '恢复到发布版本',
publish_tips1: '发布后可查看',
no_permission_tips: '当前资源无权限',
publish_tips2: '发布后可{0}',
cancel_publish_tips: '取消发布成功',
resource_not_published: '该资源未发布',
@@ -4754,6 +4755,8 @@ export default {
add: '添加 Webhook',
search_placeholder: '通过名称搜索',
content_type: '内容类型',
msg_template: '消息模板',
msg_template_tips: '可用占位符:{t0}、{t1}、{t2}',
del_confirm: '确定删除该 Webhook吗',
batch_del_confirm: '确定删除 {0} 个 Webhook吗'
},

View File

@@ -111,6 +111,10 @@ function retain(value, n) {
const tran = Math.round(value * Math.pow(10, n)) / Math.pow(10, n)
let tranV = tran.toString()
const newVal = tranV.indexOf('.')
// 遇到科学计数法时用 toFixed(n) 转成普通小数字符串
if (/e/i.test(tranV)) {
tranV = tran.toFixed(n)
}
if (newVal < 0) {
tranV += '.'
}

View File

@@ -81,7 +81,7 @@ const props = defineProps({
const defaultProps = {
children: 'children',
label: 'name',
disabled: (data: any) => data.extraFlag1 === 0
disabled: (data: any) => data.extraFlag1 === 0 || data.weight === 0
}
const mounted = ref(false)
const rootManage = ref(false)
@@ -785,7 +785,10 @@ defineExpose({
draggable
>
<template #default="{ node, data }">
<span class="custom-tree-node" :class="{ 'node-disabled-custom': data.extraFlag1 === 0 }">
<span
class="custom-tree-node"
:class="{ 'node-disabled-custom': data.extraFlag1 === 0 || data.weight === 0 }"
>
<el-icon style="font-size: 18px" v-if="!data.leaf">
<Icon name="dv-folder"><dvFolder class="svg-icon" /></Icon>
</el-icon>
@@ -815,8 +818,12 @@ defineExpose({
<el-tooltip
class="box-item"
effect="dark"
:content="t('visualization.publish_tips1')"
:disabled="data.extraFlag1"
:content="
data.weight === 0
? t('visualization.no_permission_tips')
: t('visualization.publish_tips1')
"
:disabled="data.extraFlag1 && data.weight > 0"
placement="top-start"
>
{{ node.label }}

View File

@@ -361,7 +361,7 @@ const tableData = shallowRef([])
const total = ref(null)
const handleNodeClick = (data: BusiTreeNode) => {
if (!data.leaf) {
if (!data.leaf || data.weight === 0) {
datasetListTree.value.setCurrentKey(null)
return
}
@@ -700,7 +700,8 @@ const datasetTypeList = computed(() => {
const defaultProps = {
children: 'children',
label: 'name'
label: 'name',
disabled: (data: any) => data.weight === 0
}
const defaultTab = [
@@ -947,14 +948,21 @@ const proxyAllowDrop = throttle((arg1, arg2) => {
@node-click="handleNodeClick"
>
<template #default="{ node, data }">
<span class="custom-tree-node">
<span class="custom-tree-node" :class="{ 'node-disabled-custom': data.weight === 0 }">
<el-icon v-if="!data.leaf" style="font-size: 18px">
<Icon name="dv-folder"><dvFolder class="svg-icon" /></Icon>
</el-icon>
<el-icon v-if="data.leaf" style="font-size: 18px">
<Icon name="icon_dataset"><icon_dataset class="svg-icon" /></Icon>
</el-icon>
<span :title="node.label" class="label-tooltip ellipsis">{{ node.label }}</span>
<el-tooltip
effect="dark"
:content="t('visualization.no_permission_tips')"
:disabled="data.weight > 0"
placement="top-start"
>
<span :title="node.label" class="label-tooltip ellipsis">{{ node.label }}</span>
</el-tooltip>
<div class="icon-more" v-if="data.weight >= 7">
<handle-more
icon-size="24px"
@@ -1470,4 +1478,9 @@ const proxyAllowDrop = throttle((arg1, arg2) => {
}
}
}
.node-disabled-custom {
color: rgba(187, 191, 196, 1);
cursor: not-allowed;
}
</style>

View File

@@ -3,7 +3,7 @@ import { propTypes } from '@/utils/propTypes'
import { onBeforeMount, watch, toRefs, PropType } from 'vue'
import { useI18n } from '@/hooks/web/useI18n'
import ApiVariable from './ApiVariable.vue'
import CodeEdit from './CodeEdit.vue'
import CodeEdit from '@/components/CodeEdit/CodeEdit.vue'
import Convert from './convert.js'
import { KeyValue, BODY_TYPE } from './ApiTestModel.js'
export interface ApiBodyItem {

View File

@@ -583,7 +583,7 @@ const sortTypeTip = computed(() => {
const tableData = shallowRef([])
const tabData = shallowRef([])
const handleNodeClick = data => {
if (!data.leaf) {
if (!data.leaf || data.weight === 0) {
dsListTree.value.setCurrentKey(null)
return
}
@@ -1054,7 +1054,8 @@ const uploadExcel = editType => {
const activeName = ref('table')
const defaultProps = {
children: 'children',
label: 'name'
label: 'name',
disabled: (data: any) => data.weight === 0
}
const loadInit = () => {
@@ -1231,7 +1232,11 @@ const getMenuList = (val: boolean) => {
@node-click="handleNodeClick"
>
<template #default="{ node, data }">
<span class="custom-tree-node" style="position: relative">
<span
class="custom-tree-node"
style="position: relative"
:class="{ 'node-disabled-custom': data.weight === 0 }"
>
<el-icon :class="data.leaf && 'icon-border'" style="font-size: 18px">
<Icon :static-content="getDsIcon(data)"
><component class="svg-icon" :is="getDsIconName(data)"></component
@@ -1243,18 +1248,30 @@ const getMenuList = (val: boolean) => {
>
<Icon><icon_warning_colorful_red class="svg-icon" /></Icon>
</el-icon>
<span
:title="node.label"
class="label-tooltip ellipsis"
:class="data.type === 'Excel' && 'excel'"
v-if="data.extraFlag > -1"
>{{ node.label }}</span
>
<el-tooltip
v-if="data.extraFlag > -1"
effect="dark"
:content="t('visualization.no_permission_tips')"
:disabled="data.weight > 0"
placement="top-start"
>
<span
:title="node.label"
class="label-tooltip ellipsis"
:class="data.type === 'Excel' && 'excel'"
v-if="data.extraFlag > -1"
>{{ node.label }}</span
>
</el-tooltip>
<el-tooltip
v-else
:content="`${t('data_set.invalid_data_source')}: ${node.label}`"
placement="top"
effect="dark"
:content="
data.weight === 0
? t('visualization.no_permission_tips')
: `${t('data_set.invalid_data_source')}: ${node.label}`
"
placement="top-start"
>
<span
:title="node.label"
@@ -2428,6 +2445,11 @@ const getMenuList = (val: boolean) => {
}
}
}
.node-disabled-custom {
color: rgba(187, 191, 196, 1);
cursor: not-allowed;
}
</style>
<style lang="less">
.record-drawer {

View File

@@ -5,6 +5,7 @@ import com.github.xiaoymin.knife4j.annotations.ApiSupport;
import io.dataease.api.webhook.request.WebhookSwitchRequest;
import io.dataease.api.webhook.vo.WebhookGridVO;
import io.dataease.api.webhook.vo.WebhookOption;
import io.dataease.api.webhook.vo.WebhookVO;
import io.dataease.model.KeywordRequest;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
@@ -33,7 +34,7 @@ public interface WebhookApi {
@Operation(summary = "保存")
@PostMapping("/save")
void save(@RequestBody WebhookGridVO creator);
void save(@RequestBody WebhookVO creator);
@Operation(summary = "切换SSL")
@PostMapping("/switchSsl")
@@ -43,6 +44,10 @@ public interface WebhookApi {
@PostMapping("/delete")
void delete(@RequestBody List<Long> ids);
@Operation(summary = "查询详情")
@GetMapping("/get/{id}")
WebhookVO get(@PathVariable("id") Long id);
@Operation(summary = "查询选项")
@GetMapping("/options")
List<WebhookOption> options();

View File

@@ -24,9 +24,4 @@ public class WebhookGridVO implements Serializable {
private String contentType;
private Boolean ssl;
@JsonSerialize(using= ToStringSerializer.class)
private Long oid;
private Long createTime;
}

View File

@@ -0,0 +1,34 @@
package io.dataease.api.webhook.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 WebhookVO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
@JsonSerialize(using = ToStringSerializer.class)
private Long id;
private String name;
private String url;
private String secret;
private String contentType;
private Boolean ssl;
private String msgTemplate;
@JsonSerialize(using = ToStringSerializer.class)
private Long oid;
private Long createTime;
}

View File

@@ -74,8 +74,8 @@ public class FileUtils {
return filename;
}
public static void validateExist(String path) {
File dir = new File(path);
public static void validateExist(String path) throws IOException {
File dir = new File(path).getCanonicalFile();
if (dir.exists()) return;
dir.mkdirs();
}

View File

@@ -717,6 +717,39 @@ public class HttpClientUtil {
}
}
public static String postRawBody(String url, String contentType, String body, boolean ssl, HttpClientConfig config) {
CloseableHttpClient httpClient = null;
try {
httpClient = buildHttpClient(ssl);
HttpPost httpPost = new HttpPost(url);
if (ObjectUtils.isEmpty(config)) {
config = new HttpClientConfig();
}
httpPost.setConfig(config.buildRequestConfig());
Map<String, String> header = config.getHeader();
for (String key : header.keySet()) {
httpPost.addHeader(key, header.get(key));
}
EntityBuilder entityBuilder = EntityBuilder.create();
entityBuilder.setText(body);
entityBuilder.setContentType(ContentType.create(contentType, java.nio.charset.StandardCharsets.UTF_8));
httpPost.setEntity(entityBuilder.build());
HttpResponse response = httpClient.execute(httpPost);
return getResponseStr(response, config);
} catch (Exception e) {
logger.error("HttpClient POST raw body failed", e);
throw new DEException(SYSTEM_INNER_ERROR.code(), "HttpClient POST raw body failed: " + e.getMessage());
} finally {
try {
if (httpClient != null) {
httpClient.close();
}
} catch (Exception e) {
logger.error("HttpClient关闭连接失败", e);
}
}
}
public static MultipartResponse postForScreenshot(
String url, Map<String,String> body, HttpClientConfig config) throws IOException {
CloseableHttpClient httpClient = null;

View File

@@ -55,7 +55,7 @@ public class WhitelistUtils {
public static boolean match(String requestURI) {
invalidUrl(requestURI);
if (StringUtils.startsWith(requestURI, getContextPath())) {
requestURI = requestURI.replaceFirst(getContextPath(), "");
requestURI = StringUtils.replaceOnce(requestURI, getContextPath(), "");
}
if (StringUtils.startsWith(requestURI, AuthConstant.DE_API_PREFIX)) {
requestURI = requestURI.replaceFirst(AuthConstant.DE_API_PREFIX, "");