fix(全局): bug修复&XPath支持 html格式
This commit is contained in:
parent
0b3f7b40b2
commit
6103b1abd2
|
@ -70,6 +70,7 @@
|
||||||
"pinia": "^2.1.6",
|
"pinia": "^2.1.6",
|
||||||
"pinia-plugin-persistedstate": "^3.2.0",
|
"pinia-plugin-persistedstate": "^3.2.0",
|
||||||
"pm": "link:@/tiptap/pm",
|
"pm": "link:@/tiptap/pm",
|
||||||
|
"pretty": "^2.0.0",
|
||||||
"query-string": "^8.1.0",
|
"query-string": "^8.1.0",
|
||||||
"resize-observer-polyfill": "^1.5.1",
|
"resize-observer-polyfill": "^1.5.1",
|
||||||
"sortablejs": "^1.15.0",
|
"sortablejs": "^1.15.0",
|
||||||
|
|
|
@ -149,7 +149,7 @@ export function batchOptionScenario(
|
||||||
data: {
|
data: {
|
||||||
moduleIds: string[];
|
moduleIds: string[];
|
||||||
selectAll: boolean;
|
selectAll: boolean;
|
||||||
condition: { keyword: string };
|
condition: { keyword: string; filter: Record<string, any> };
|
||||||
excludeIds: any[];
|
excludeIds: any[];
|
||||||
selectIds: any[];
|
selectIds: any[];
|
||||||
projectId: string;
|
projectId: string;
|
||||||
|
|
|
@ -594,6 +594,9 @@
|
||||||
projectId: innerProject.value,
|
projectId: innerProject.value,
|
||||||
sourceId: props.caseId,
|
sourceId: props.caseId,
|
||||||
totalCount: propsRes.value.msPagination?.total,
|
totalCount: propsRes.value.msPagination?.total,
|
||||||
|
condition: {
|
||||||
|
keyword: keyword.value,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
emit('save', params);
|
emit('save', params);
|
||||||
|
|
|
@ -164,6 +164,7 @@
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '',
|
title: '',
|
||||||
|
dataIndex: 'operation',
|
||||||
slotName: 'operation',
|
slotName: 'operation',
|
||||||
width: 50,
|
width: 50,
|
||||||
},
|
},
|
||||||
|
|
|
@ -7,7 +7,7 @@ export default {
|
||||||
'ms.personal.apiKey': 'APIKEY',
|
'ms.personal.apiKey': 'APIKEY',
|
||||||
'ms.personal.tripartite': '三方平台账号',
|
'ms.personal.tripartite': '三方平台账号',
|
||||||
'ms.personal.changeAvatar': '更换头像',
|
'ms.personal.changeAvatar': '更换头像',
|
||||||
'ms.personal.name': '用户名称',
|
'ms.personal.name': '姓名',
|
||||||
'ms.personal.namePlaceholder': '请输入用户名称',
|
'ms.personal.namePlaceholder': '请输入用户名称',
|
||||||
'ms.personal.nameRequired': '用户名称不能为空',
|
'ms.personal.nameRequired': '用户名称不能为空',
|
||||||
'ms.personal.email': '邮箱',
|
'ms.personal.email': '邮箱',
|
||||||
|
|
|
@ -13,12 +13,13 @@
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
|
|
||||||
import { XpathNode } from './types';
|
import { XpathNode } from './types';
|
||||||
|
import HtmlBeautify from 'pretty';
|
||||||
import XmlBeautify from 'xml-beautify';
|
import XmlBeautify from 'xml-beautify';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps<{
|
||||||
xmlString: string;
|
xmlString: string;
|
||||||
}>();
|
}>();
|
||||||
const emit = defineEmits(['pick']);
|
const emit = defineEmits(['pick', 'init']);
|
||||||
|
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
|
@ -74,12 +75,98 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将html扁平化
|
||||||
|
* @param node html节点
|
||||||
|
* @param currentPath 当前路径
|
||||||
|
*/
|
||||||
|
function flattenHtml(node: HTMLElement | Element, currentPath: string) {
|
||||||
|
const sameNameSiblings = getSameNameSiblings(node);
|
||||||
|
if (sameNameSiblings.length > 1) {
|
||||||
|
const sameNodesIndex = document.evaluate(
|
||||||
|
`count(ancestor-or-self::${node.nodeName.toLowerCase()}/preceding-sibling::${node.nodeName.toLowerCase()}) + 1`,
|
||||||
|
node,
|
||||||
|
null,
|
||||||
|
XPathResult.NUMBER_TYPE,
|
||||||
|
null
|
||||||
|
).numberValue;
|
||||||
|
const xpath = `${currentPath}/${node.nodeName.toLowerCase()}[${sameNodesIndex}]`;
|
||||||
|
tempXmls.value.push({ content: node.nodeName.toLowerCase(), xpath });
|
||||||
|
const children = Array.from(node.children);
|
||||||
|
children.forEach((child) => {
|
||||||
|
flattenHtml(child, xpath);
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const xpath = `${currentPath}/${node.nodeName.toLowerCase()}`;
|
||||||
|
tempXmls.value.push({ content: node.nodeName.toLowerCase(), xpath });
|
||||||
|
const children = Array.from(node.children);
|
||||||
|
children.forEach((child) => {
|
||||||
|
flattenHtml(child, xpath);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
function copyXPath(xpath: string) {
|
function copyXPath(xpath: string) {
|
||||||
if (xpath) {
|
if (xpath) {
|
||||||
emit('pick', xpath);
|
emit('pick', xpath);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 替换文档
|
||||||
|
* @param beautifyDoc 格式化后的文档
|
||||||
|
*/
|
||||||
|
function replaceDoc(beautifyDoc: string) {
|
||||||
|
// 先将 HTML 字符串格式化,然后解析转换并给每个开始标签加上复制 icon
|
||||||
|
flattenedXml.value = beautifyDoc
|
||||||
|
.replace(/</g, '<')
|
||||||
|
.replace(/>/g, '>')
|
||||||
|
.replace(/(<([^/][^&]*?)>)/g, '<span style="color: rgb(var(--primary-5));cursor: pointer">$1📋</span>')
|
||||||
|
.split(/\r?\n/)
|
||||||
|
.map((e) => ({ content: e, xpath: '' }));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 解析html
|
||||||
|
*/
|
||||||
|
function parseHtml() {
|
||||||
|
try {
|
||||||
|
const parser = new DOMParser();
|
||||||
|
const xmlDoc = parser.parseFromString(props.xmlString, 'text/html');
|
||||||
|
// 如果存在 parsererror 元素,说明 HTML 不合法
|
||||||
|
const htmlErrors = xmlDoc.getElementsByTagName('parsererror');
|
||||||
|
if (htmlErrors.length > 0) {
|
||||||
|
isValidXml.value = false;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
isValidXml.value = true;
|
||||||
|
parsedXml.value = xmlDoc;
|
||||||
|
const beautifyDoc = HtmlBeautify(props.xmlString, { ocd: true });
|
||||||
|
replaceDoc(beautifyDoc);
|
||||||
|
// 解析真实 HTML 并将其扁平化,得到每个节点的 xpath
|
||||||
|
flattenHtml(xmlDoc.documentElement, '');
|
||||||
|
// 将扁平化后的 XML/HTML 字符串中的每个节点的 xpath 替换为真实的 xpath
|
||||||
|
flattenedXml.value = flattenedXml.value
|
||||||
|
.map((e) => {
|
||||||
|
const targetNodeIndex = tempXmls.value.findIndex((txt) => e.content.includes(`<${txt.content}`));
|
||||||
|
if (targetNodeIndex >= 0) {
|
||||||
|
const { xpath } = tempXmls.value[targetNodeIndex];
|
||||||
|
tempXmls.value.splice(targetNodeIndex, 1); // 匹配成功后,将匹配到的节点从 tempXmls 中删除,避免重复匹配
|
||||||
|
return {
|
||||||
|
...e,
|
||||||
|
xpath,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
})
|
||||||
|
.filter(Boolean) as any[];
|
||||||
|
emit('init', 'html');
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error('Error parsing XML:', error);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 解析xml
|
* 解析xml
|
||||||
*/
|
*/
|
||||||
|
@ -88,21 +175,15 @@
|
||||||
const parser = new DOMParser();
|
const parser = new DOMParser();
|
||||||
const xmlDoc = parser.parseFromString(props.xmlString, 'application/xml');
|
const xmlDoc = parser.parseFromString(props.xmlString, 'application/xml');
|
||||||
// 如果存在 parsererror 元素,说明 XML 不合法
|
// 如果存在 parsererror 元素,说明 XML 不合法
|
||||||
const errors = xmlDoc.getElementsByTagName('parsererror');
|
const xmlErrors = xmlDoc.getElementsByTagName('parsererror');
|
||||||
if (errors.length > 0) {
|
if (xmlErrors.length > 0) {
|
||||||
isValidXml.value = false;
|
parseHtml();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
isValidXml.value = true;
|
isValidXml.value = true;
|
||||||
parsedXml.value = xmlDoc;
|
parsedXml.value = xmlDoc;
|
||||||
// 先将 XML 字符串格式化,然后解析转换并给每个开始标签加上复制 icon
|
const beautifyDoc = new XmlBeautify().beautify(props.xmlString);
|
||||||
flattenedXml.value = new XmlBeautify()
|
replaceDoc(beautifyDoc);
|
||||||
.beautify(props.xmlString)
|
|
||||||
.replace(/</g, '<')
|
|
||||||
.replace(/>/g, '>')
|
|
||||||
.replace(/(<([^/][^&]*?)>)/g, '<span style="color: rgb(var(--primary-5));cursor: pointer">$1📋</span>')
|
|
||||||
.split(/\r?\n/)
|
|
||||||
.map((e) => ({ content: e, xpath: '' }));
|
|
||||||
// 解析真实 XML 并将其扁平化,得到每个节点的 xpath
|
// 解析真实 XML 并将其扁平化,得到每个节点的 xpath
|
||||||
flattenXml(xmlDoc.documentElement, '');
|
flattenXml(xmlDoc.documentElement, '');
|
||||||
// 将扁平化后的 XML 字符串中的每个节点的 xpath 替换为真实的 xpath
|
// 将扁平化后的 XML 字符串中的每个节点的 xpath 替换为真实的 xpath
|
||||||
|
@ -118,6 +199,7 @@
|
||||||
}
|
}
|
||||||
return e;
|
return e;
|
||||||
});
|
});
|
||||||
|
emit('init', 'xml');
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.error('Error parsing XML:', error);
|
console.error('Error parsing XML:', error);
|
||||||
|
|
|
@ -71,10 +71,14 @@
|
||||||
<MsButton v-if="hasChange" @click="handleReset">{{ t('msTable.columnSetting.resetDefault') }}</MsButton>
|
<MsButton v-if="hasChange" @click="handleReset">{{ t('msTable.columnSetting.resetDefault') }}</MsButton>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex-col">
|
<div class="flex-col">
|
||||||
<div v-for="item in nonSortColumn" :key="item.dataIndex" class="column-item">
|
<div
|
||||||
<div v-show="item.dataIndex !== 'operation'">{{ t((item.title || item.columnTitle) as string) }}</div>
|
v-for="item in nonSortColumn"
|
||||||
|
v-show="item.dataIndex !== 'operation'"
|
||||||
|
:key="item.dataIndex"
|
||||||
|
class="column-item"
|
||||||
|
>
|
||||||
|
<div>{{ t((item.title || item.columnTitle) as string) }}</div>
|
||||||
<a-switch
|
<a-switch
|
||||||
v-show="item.dataIndex !== 'operation'"
|
|
||||||
v-model="item.showInTable"
|
v-model="item.showInTable"
|
||||||
size="small"
|
size="small"
|
||||||
:disabled="item.columnSelectorDisabled"
|
:disabled="item.columnSelectorDisabled"
|
||||||
|
@ -84,17 +88,17 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<a-divider orientation="center" class="non-sort"
|
<a-divider orientation="center" class="non-sort"
|
||||||
><span class="one-line-text text-xs text-[var(--color-text-4)]">{{
|
><span class="one-line-text text-xs text-[var(--color-text-4)]">
|
||||||
t('msTable.columnSetting.nonSort')
|
{{ t('msTable.columnSetting.nonSort') }}
|
||||||
}}</span></a-divider
|
</span></a-divider
|
||||||
>
|
>
|
||||||
<VueDraggable v-model="couldSortColumn" handle=".sort-handle" ghost-class="ghost" @change="handleSwitchChange">
|
<VueDraggable v-model="couldSortColumn" handle=".sort-handle" ghost-class="ghost" @change="handleSwitchChange">
|
||||||
<div v-for="element in couldSortColumn" :key="element.dataIndex" class="column-drag-item">
|
<div v-for="element in couldSortColumn" :key="element.dataIndex" class="column-drag-item">
|
||||||
<div class="flex w-[60%] items-center">
|
<div class="flex w-[60%] items-center">
|
||||||
<MsIcon type="icon-icon_drag" class="sort-handle cursor-move text-[16px] text-[var(--color-text-4)]" />
|
<MsIcon type="icon-icon_drag" class="sort-handle cursor-move text-[16px] text-[var(--color-text-4)]" />
|
||||||
<span class="one-line-text ml-[8px] max-w-[85%]">{{
|
<span class="one-line-text ml-[8px] max-w-[85%]">
|
||||||
t((element.title || element.columnTitle) as string)
|
{{ t((element.title || element.columnTitle) as string) }}
|
||||||
}}</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
<a-switch v-model="element.showInTable" size="small" type="line" @change="handleSwitchChange" />
|
<a-switch v-model="element.showInTable" size="small" type="line" @change="handleSwitchChange" />
|
||||||
</div>
|
</div>
|
||||||
|
|
|
@ -93,7 +93,7 @@
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
</li>
|
</li>
|
||||||
<li>
|
<li>
|
||||||
<a-dropdown trigger="click" position="br">
|
<a-dropdown trigger="click" position="br" @select="handleHelpSelect">
|
||||||
<a-tooltip :content="t('settings.navbar.help')">
|
<a-tooltip :content="t('settings.navbar.help')">
|
||||||
<a-button type="secondary">
|
<a-button type="secondary">
|
||||||
<template #icon>
|
<template #icon>
|
||||||
|
@ -102,7 +102,7 @@
|
||||||
</a-button>
|
</a-button>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
<template #content>
|
<template #content>
|
||||||
<a-doption value="doc">
|
<a-doption v-if="appStore.pageConfig.helpDoc" value="doc">
|
||||||
<component :is="IconQuestionCircle"></component>
|
<component :is="IconQuestionCircle"></component>
|
||||||
{{ t('settings.help.doc') }}
|
{{ t('settings.help.doc') }}
|
||||||
</a-doption>
|
</a-doption>
|
||||||
|
@ -184,7 +184,6 @@
|
||||||
const route = useRoute();
|
const route = useRoute();
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { t } = useI18n();
|
const { t } = useI18n();
|
||||||
|
|
||||||
const unReadCount = ref<number>(0);
|
const unReadCount = ref<number>(0);
|
||||||
|
|
||||||
async function checkMessageRead() {
|
async function checkMessageRead() {
|
||||||
|
@ -213,7 +212,8 @@
|
||||||
const showProjectSelect = computed(() => {
|
const showProjectSelect = computed(() => {
|
||||||
const { getRouteLevelByKey } = usePathMap();
|
const { getRouteLevelByKey } = usePathMap();
|
||||||
// 非项目级别页面不需要展示项目选择器
|
// 非项目级别页面不需要展示项目选择器
|
||||||
return getRouteLevelByKey(route.name as PathMapRoute) === MENU_LEVEL[2];
|
const level = getRouteLevelByKey(route.name as PathMapRoute);
|
||||||
|
return level === MENU_LEVEL[2] || level === null;
|
||||||
});
|
});
|
||||||
|
|
||||||
async function selectProject(
|
async function selectProject(
|
||||||
|
@ -266,6 +266,12 @@
|
||||||
messageCenterVisible.value = true;
|
messageCenterVisible.value = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function handleHelpSelect(val: string | number | Record<string, any> | undefined) {
|
||||||
|
if (val === 'doc') {
|
||||||
|
window.open(appStore.pageConfig.helpDoc, '_blank');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
if (route.query.task) {
|
if (route.query.task) {
|
||||||
goTaskCenter();
|
goTaskCenter();
|
||||||
|
|
|
@ -208,7 +208,7 @@ export const pathMap: PathMapItem[] = [
|
||||||
level: MENU_LEVEL[2],
|
level: MENU_LEVEL[2],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
key: 'CASE_MANAGEMENT_CASE_DETAIL', // 功能测试-功能用例-用例评审
|
key: 'CASE_MANAGEMENT_CASE_DETAIL', // 功能测试-功能用例-用例详情
|
||||||
locale: 'menu.caseManagement.featureCaseDetail',
|
locale: 'menu.caseManagement.featureCaseDetail',
|
||||||
route: RouteEnum.CASE_MANAGEMENT_CASE_DETAIL,
|
route: RouteEnum.CASE_MANAGEMENT_CASE_DETAIL,
|
||||||
permission: [],
|
permission: [],
|
||||||
|
@ -224,7 +224,7 @@ export const pathMap: PathMapItem[] = [
|
||||||
{
|
{
|
||||||
key: 'CASE_MANAGEMENT_REVIEW_DETAIL', // 功能测试-功能用例-用例评审
|
key: 'CASE_MANAGEMENT_REVIEW_DETAIL', // 功能测试-功能用例-用例评审
|
||||||
locale: 'menu.caseManagement.caseManagementReviewDetail',
|
locale: 'menu.caseManagement.caseManagementReviewDetail',
|
||||||
route: RouteEnum.CASE_MANAGEMENT_REVIEW_CREATE,
|
route: RouteEnum.CASE_MANAGEMENT_REVIEW_DETAIL,
|
||||||
permission: [],
|
permission: [],
|
||||||
level: MENU_LEVEL[2],
|
level: MENU_LEVEL[2],
|
||||||
},
|
},
|
||||||
|
|
|
@ -39,7 +39,7 @@ const defaultLoginConfig = {
|
||||||
const defaultPlatformConfig = {
|
const defaultPlatformConfig = {
|
||||||
logoPlatform: [],
|
logoPlatform: [],
|
||||||
platformName: 'MeterSphere',
|
platformName: 'MeterSphere',
|
||||||
helpDoc: '',
|
helpDoc: 'https://metersphere.io/docs/v3.x/',
|
||||||
};
|
};
|
||||||
|
|
||||||
const useAppStore = defineStore('app', {
|
const useAppStore = defineStore('app', {
|
||||||
|
@ -105,8 +105,10 @@ const useAppStore = defineStore('app', {
|
||||||
...state.defaultPlatformConfig,
|
...state.defaultPlatformConfig,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
getCurrentEnvId(state: AppState): string {
|
||||||
|
return state.currentEnvConfig?.id || '';
|
||||||
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
/**
|
/**
|
||||||
* 更新设置
|
* 更新设置
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
import { DOMParser } from '@xmldom/xmldom';
|
import { DOMParser as XmlDOMParser } from '@xmldom/xmldom';
|
||||||
import * as xpath from 'xpath';
|
import * as xpath from 'xpath';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -10,7 +10,7 @@ import * as xpath from 'xpath';
|
||||||
export function matchXMLWithXPath(xmlText: string, xpathQuery: string): xpath.SelectReturnType {
|
export function matchXMLWithXPath(xmlText: string, xpathQuery: string): xpath.SelectReturnType {
|
||||||
try {
|
try {
|
||||||
// 解析 XML 文本
|
// 解析 XML 文本
|
||||||
const xmlDoc = new DOMParser().parseFromString(xmlText, 'text/xml');
|
const xmlDoc = new XmlDOMParser().parseFromString(xmlText, 'text/xml');
|
||||||
|
|
||||||
// 创建一个命名空间解析器
|
// 创建一个命名空间解析器
|
||||||
const resolver = (prefix: string) => {
|
const resolver = (prefix: string) => {
|
||||||
|
@ -31,4 +31,33 @@ export function matchXMLWithXPath(xmlText: string, xpathQuery: string): xpath.Se
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export default { matchXMLWithXPath };
|
/**
|
||||||
|
* 通过 xpath 提取 html 文档对应节点内容
|
||||||
|
* @param htmlText html文本
|
||||||
|
* @param xpathQuery xpath
|
||||||
|
* @returns 匹配节点的文本内容
|
||||||
|
*/
|
||||||
|
export function extractTextFromHtmlWithXPath(htmlText: string, xpathQuery: string): Node[] {
|
||||||
|
try {
|
||||||
|
// 解析 HTML 文本
|
||||||
|
const parser = new DOMParser();
|
||||||
|
const htmlDoc = parser.parseFromString(htmlText, 'text/html');
|
||||||
|
|
||||||
|
// 使用 XPath 查询匹配的节点
|
||||||
|
const iterator = document.evaluate(xpathQuery, htmlDoc.documentElement, null, XPathResult.ANY_TYPE, null);
|
||||||
|
// 提取匹配节点的文本内容
|
||||||
|
let node = iterator.iterateNext();
|
||||||
|
const nodes: Node[] = [];
|
||||||
|
while (node) {
|
||||||
|
nodes.push(node);
|
||||||
|
node = iterator.iterateNext();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 返回匹配节点
|
||||||
|
return nodes;
|
||||||
|
} catch (error) {
|
||||||
|
// eslint-disable-next-line no-console
|
||||||
|
console.error('Error parsing HTML or executing XPath query:', error);
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -749,6 +749,7 @@ if (!result){
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '',
|
title: '',
|
||||||
|
dataIndex: 'operation',
|
||||||
slotName: 'operation',
|
slotName: 'operation',
|
||||||
width: 50,
|
width: 50,
|
||||||
},
|
},
|
||||||
|
@ -801,7 +802,7 @@ if (!result){
|
||||||
value: RequestExtractEnvType.TEMPORARY,
|
value: RequestExtractEnvType.TEMPORARY,
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
width: 130,
|
width: 150,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: 'apiTestDebug.mode',
|
title: 'apiTestDebug.mode',
|
||||||
|
@ -872,6 +873,7 @@ if (!result){
|
||||||
{
|
{
|
||||||
title: '',
|
title: '',
|
||||||
slotName: 'operation',
|
slotName: 'operation',
|
||||||
|
dataIndex: 'operation',
|
||||||
fixed: 'right',
|
fixed: 'right',
|
||||||
moreAction: [
|
moreAction: [
|
||||||
{
|
{
|
||||||
|
|
|
@ -24,7 +24,7 @@
|
||||||
<MsJsonPathPicker :data="props.response || ''" class="bg-white" @init="initJsonPath" @pick="handlePathPick" />
|
<MsJsonPathPicker :data="props.response || ''" class="bg-white" @init="initJsonPath" @pick="handlePathPick" />
|
||||||
</div>
|
</div>
|
||||||
<div v-else-if="expressionForm.extractType === RequestExtractExpressionEnum.X_PATH" class="code-container">
|
<div v-else-if="expressionForm.extractType === RequestExtractExpressionEnum.X_PATH" class="code-container">
|
||||||
<MsXPathPicker :xml-string="props.response || ''" class="bg-white" @pick="handlePathPick" />
|
<MsXPathPicker :xml-string="props.response || ''" class="bg-white" @init="initXpath" @pick="handlePathPick" />
|
||||||
</div>
|
</div>
|
||||||
<a-form ref="expressionFormRef" :model="expressionForm" layout="vertical" class="mt-[16px]">
|
<a-form ref="expressionFormRef" :model="expressionForm" layout="vertical" class="mt-[16px]">
|
||||||
<a-form-item
|
<a-form-item
|
||||||
|
@ -162,7 +162,7 @@
|
||||||
import moreSetting from './moreSetting.vue';
|
import moreSetting from './moreSetting.vue';
|
||||||
|
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
import { matchXMLWithXPath } from '@/utils/xpath';
|
import { extractTextFromHtmlWithXPath, matchXMLWithXPath } from '@/utils/xpath';
|
||||||
|
|
||||||
import type { JSONPathExtract, RegexExtract, XPathExtract } from '@/models/apiTest/common';
|
import type { JSONPathExtract, RegexExtract, XPathExtract } from '@/models/apiTest/common';
|
||||||
import { RequestExtractExpressionEnum, RequestExtractExpressionRuleType } from '@/enums/apiEnum';
|
import { RequestExtractExpressionEnum, RequestExtractExpressionRuleType } from '@/enums/apiEnum';
|
||||||
|
@ -193,6 +193,7 @@
|
||||||
const expressionForm = ref({ ...props.config });
|
const expressionForm = ref({ ...props.config });
|
||||||
const expressionFormRef = ref<FormInstance | null>(null);
|
const expressionFormRef = ref<FormInstance | null>(null);
|
||||||
const parseJson = ref<string | Record<string, any>>({});
|
const parseJson = ref<string | Record<string, any>>({});
|
||||||
|
const isHtml = ref(false);
|
||||||
const matchResult = ref<any[]>([]); // 当前匹配结果
|
const matchResult = ref<any[]>([]); // 当前匹配结果
|
||||||
const isMatched = ref(false); // 是否执行过匹配
|
const isMatched = ref(false); // 是否执行过匹配
|
||||||
|
|
||||||
|
@ -211,6 +212,10 @@
|
||||||
parseJson.value = _parseJson;
|
parseJson.value = _parseJson;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function initXpath(type: 'xml' | 'html') {
|
||||||
|
isHtml.value = type === 'html';
|
||||||
|
}
|
||||||
|
|
||||||
function handlePathPick(path: string, _parseJson: string | Record<string, any>) {
|
function handlePathPick(path: string, _parseJson: string | Record<string, any>) {
|
||||||
expressionForm.value.expression = path;
|
expressionForm.value.expression = path;
|
||||||
parseJson.value = _parseJson;
|
parseJson.value = _parseJson;
|
||||||
|
@ -223,7 +228,9 @@
|
||||||
function testExpression() {
|
function testExpression() {
|
||||||
switch (props.config.extractType) {
|
switch (props.config.extractType) {
|
||||||
case RequestExtractExpressionEnum.X_PATH:
|
case RequestExtractExpressionEnum.X_PATH:
|
||||||
const nodes = matchXMLWithXPath(props.response || '', expressionForm.value.expression);
|
const nodes = isHtml.value
|
||||||
|
? extractTextFromHtmlWithXPath(props.response || '', expressionForm.value.expression)
|
||||||
|
: matchXMLWithXPath(props.response || '', expressionForm.value.expression);
|
||||||
if (nodes) {
|
if (nodes) {
|
||||||
// 直接匹配到文本信息
|
// 直接匹配到文本信息
|
||||||
if (typeof nodes === 'boolean' || typeof nodes === 'string' || typeof nodes === 'number') {
|
if (typeof nodes === 'boolean' || typeof nodes === 'string' || typeof nodes === 'number') {
|
||||||
|
@ -248,7 +255,7 @@
|
||||||
JSONPath({
|
JSONPath({
|
||||||
json: parseJson.value,
|
json: parseJson.value,
|
||||||
path: expressionForm.value.expression,
|
path: expressionForm.value.expression,
|
||||||
})?.map((e) => JSON.stringify(e).replace(/Number\(([^)]+)\)/g, '$1')) || [];
|
})?.map((e) => JSON.stringify(e).replace(/"Number\(([^)]+)\)"|Number\(([^)]+)\)/g, '$1$2')) || [];
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
matchResult.value = JSONPath({ json: props.response || '', path: expressionForm.value.expression }) || [];
|
matchResult.value = JSONPath({ json: props.response || '', path: expressionForm.value.expression }) || [];
|
||||||
}
|
}
|
||||||
|
|
|
@ -244,6 +244,7 @@
|
||||||
: [
|
: [
|
||||||
{
|
{
|
||||||
title: '',
|
title: '',
|
||||||
|
dataIndex: 'operation',
|
||||||
slotName: 'operation',
|
slotName: 'operation',
|
||||||
fixed: 'right' as TableColumnData['fixed'],
|
fixed: 'right' as TableColumnData['fixed'],
|
||||||
format: innerParams.value.bodyType,
|
format: innerParams.value.bodyType,
|
||||||
|
|
|
@ -77,6 +77,7 @@
|
||||||
: [
|
: [
|
||||||
{
|
{
|
||||||
title: '',
|
title: '',
|
||||||
|
dataIndex: 'operation',
|
||||||
slotName: 'operation',
|
slotName: 'operation',
|
||||||
width: 50,
|
width: 50,
|
||||||
},
|
},
|
||||||
|
|
|
@ -112,6 +112,7 @@
|
||||||
: [
|
: [
|
||||||
{
|
{
|
||||||
title: '',
|
title: '',
|
||||||
|
dataIndex: 'operation',
|
||||||
slotName: 'operation',
|
slotName: 'operation',
|
||||||
fixed: 'right' as TableColumnData['fixed'],
|
fixed: 'right' as TableColumnData['fixed'],
|
||||||
width: 50,
|
width: 50,
|
||||||
|
|
|
@ -113,6 +113,7 @@
|
||||||
: [
|
: [
|
||||||
{
|
{
|
||||||
title: '',
|
title: '',
|
||||||
|
dataIndex: 'operation',
|
||||||
slotName: 'operation',
|
slotName: 'operation',
|
||||||
fixed: 'right' as TableColumnData['fixed'],
|
fixed: 'right' as TableColumnData['fixed'],
|
||||||
width: 50,
|
width: 50,
|
||||||
|
|
|
@ -508,6 +508,7 @@
|
||||||
showSelectAll: !props.readOnly,
|
showSelectAll: !props.readOnly,
|
||||||
draggable: hasAnyPermission(['PROJECT_API_DEFINITION:READ+UPDATE']) ? { type: 'handle', width: 32 } : undefined,
|
draggable: hasAnyPermission(['PROJECT_API_DEFINITION:READ+UPDATE']) ? { type: 'handle', width: 32 } : undefined,
|
||||||
heightUsed: 256,
|
heightUsed: 256,
|
||||||
|
paginationSize: 'mini',
|
||||||
showSubdirectory: true,
|
showSubdirectory: true,
|
||||||
},
|
},
|
||||||
(item) => ({
|
(item) => ({
|
||||||
|
@ -688,7 +689,14 @@
|
||||||
selectIds,
|
selectIds,
|
||||||
selectAll: !!params?.selectAll,
|
selectAll: !!params?.selectAll,
|
||||||
excludeIds: params?.excludeIds || [],
|
excludeIds: params?.excludeIds || [],
|
||||||
condition: { keyword: keyword.value },
|
condition: {
|
||||||
|
keyword: keyword.value,
|
||||||
|
filter: {
|
||||||
|
status: statusFilters.value,
|
||||||
|
method: methodFilters.value,
|
||||||
|
createUser: createUserFilters.value,
|
||||||
|
},
|
||||||
|
},
|
||||||
projectId: appStore.currentProjectId,
|
projectId: appStore.currentProjectId,
|
||||||
moduleIds: await getModuleIds(),
|
moduleIds: await getModuleIds(),
|
||||||
deleteAll: true,
|
deleteAll: true,
|
||||||
|
@ -813,6 +821,7 @@
|
||||||
filter: {
|
filter: {
|
||||||
status: statusFilters.value,
|
status: statusFilters.value,
|
||||||
method: methodFilters.value,
|
method: methodFilters.value,
|
||||||
|
createUser: createUserFilters.value,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
projectId: appStore.currentProjectId,
|
projectId: appStore.currentProjectId,
|
||||||
|
@ -857,6 +866,7 @@
|
||||||
filter: {
|
filter: {
|
||||||
status: statusFilters.value,
|
status: statusFilters.value,
|
||||||
method: methodFilters.value,
|
method: methodFilters.value,
|
||||||
|
createUser: createUserFilters.value,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
projectId: appStore.currentProjectId,
|
projectId: appStore.currentProjectId,
|
||||||
|
|
|
@ -404,7 +404,6 @@
|
||||||
updateCasePriority,
|
updateCasePriority,
|
||||||
updateCaseStatus,
|
updateCaseStatus,
|
||||||
} from '@/api/modules/api-test/management';
|
} from '@/api/modules/api-test/management';
|
||||||
import { getProjectOptions } from '@/api/modules/project-management/projectMember';
|
|
||||||
import { useI18n } from '@/hooks/useI18n';
|
import { useI18n } from '@/hooks/useI18n';
|
||||||
import useModal from '@/hooks/useModal';
|
import useModal from '@/hooks/useModal';
|
||||||
import useTableStore from '@/hooks/useTableStore';
|
import useTableStore from '@/hooks/useTableStore';
|
||||||
|
@ -774,6 +773,7 @@
|
||||||
status: statusFilters.value,
|
status: statusFilters.value,
|
||||||
priority: caseFilters.value,
|
priority: caseFilters.value,
|
||||||
lastReportStatus: lastReportStatusFilters.value,
|
lastReportStatus: lastReportStatusFilters.value,
|
||||||
|
createUser: createUserFilters.value,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
projectId: appStore.currentProjectId,
|
projectId: appStore.currentProjectId,
|
||||||
|
|
|
@ -50,7 +50,7 @@
|
||||||
</div>
|
</div>
|
||||||
<a-divider class="my-[8px]" />
|
<a-divider class="my-[8px]" />
|
||||||
|
|
||||||
<a-spin class="w-full" :style="{ height: `calc(100vh - 320px)` }" :loading="loading">
|
<a-spin class="w-full" :style="{ height: `calc(100vh - 300px)` }" :loading="loading">
|
||||||
<MsTree
|
<MsTree
|
||||||
v-model:focus-node-key="focusNodeKey"
|
v-model:focus-node-key="focusNodeKey"
|
||||||
v-model:selected-keys="selectedKeys"
|
v-model:selected-keys="selectedKeys"
|
||||||
|
@ -186,7 +186,7 @@
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
height: 'calc(100vh - 325px)',
|
height: 'calc(100vh - 300px)',
|
||||||
threshold: 200,
|
threshold: 200,
|
||||||
fixedSize: true,
|
fixedSize: true,
|
||||||
buffer: 15, // 缓冲区默认 10 的时候,虚拟滚动的底部 padding 计算有问题
|
buffer: 15, // 缓冲区默认 10 的时候,虚拟滚动的底部 padding 计算有问题
|
||||||
|
|
|
@ -1339,7 +1339,16 @@
|
||||||
selectIds: batchParams.value?.selectedIds || [],
|
selectIds: batchParams.value?.selectedIds || [],
|
||||||
selectAll: !!batchParams.value?.selectAll,
|
selectAll: !!batchParams.value?.selectAll,
|
||||||
excludeIds: batchParams.value?.excludeIds || [],
|
excludeIds: batchParams.value?.excludeIds || [],
|
||||||
condition: { keyword: keyword.value },
|
condition: {
|
||||||
|
keyword: keyword.value,
|
||||||
|
filter: {
|
||||||
|
lastReportStatus: lastReportStatusListFilters.value,
|
||||||
|
status: statusFilters.value,
|
||||||
|
priority: priorityFilters.value,
|
||||||
|
createUser: createUserFilters.value,
|
||||||
|
updateUser: updateUserFilters.value,
|
||||||
|
},
|
||||||
|
},
|
||||||
projectId: appStore.currentProjectId,
|
projectId: appStore.currentProjectId,
|
||||||
moduleIds: props.activeModule === 'all' ? [] : [props.activeModule],
|
moduleIds: props.activeModule === 'all' ? [] : [props.activeModule],
|
||||||
type: batchForm.value?.attr,
|
type: batchForm.value?.attr,
|
||||||
|
@ -1399,7 +1408,16 @@
|
||||||
selectIds: batchParams.value?.selectedIds || [],
|
selectIds: batchParams.value?.selectedIds || [],
|
||||||
selectAll: !!batchParams.value?.selectAll,
|
selectAll: !!batchParams.value?.selectAll,
|
||||||
excludeIds: batchParams.value?.excludeIds || [],
|
excludeIds: batchParams.value?.excludeIds || [],
|
||||||
condition: { keyword: keyword.value },
|
condition: {
|
||||||
|
keyword: keyword.value,
|
||||||
|
filter: {
|
||||||
|
lastReportStatus: lastReportStatusListFilters.value,
|
||||||
|
status: statusFilters.value,
|
||||||
|
priority: priorityFilters.value,
|
||||||
|
createUser: createUserFilters.value,
|
||||||
|
updateUser: updateUserFilters.value,
|
||||||
|
},
|
||||||
|
},
|
||||||
projectId: appStore.currentProjectId,
|
projectId: appStore.currentProjectId,
|
||||||
moduleIds: props.activeModule === 'all' ? [] : [props.activeModule],
|
moduleIds: props.activeModule === 'all' ? [] : [props.activeModule],
|
||||||
targetModuleId: selectedBatchOptModuleKey.value,
|
targetModuleId: selectedBatchOptModuleKey.value,
|
||||||
|
|
|
@ -251,7 +251,7 @@
|
||||||
res = await executeScenario({
|
res = await executeScenario({
|
||||||
id: activeScenarioTab.value.id,
|
id: activeScenarioTab.value.id,
|
||||||
grouped: false,
|
grouped: false,
|
||||||
environmentId: activeScenarioTab.value.environmentId || '',
|
environmentId: appStore.getCurrentEnvId || '',
|
||||||
projectId: appStore.currentProjectId,
|
projectId: appStore.currentProjectId,
|
||||||
scenarioConfig: activeScenarioTab.value.scenarioConfig,
|
scenarioConfig: activeScenarioTab.value.scenarioConfig,
|
||||||
...executeParams,
|
...executeParams,
|
||||||
|
@ -267,7 +267,7 @@
|
||||||
res = await debugScenario({
|
res = await debugScenario({
|
||||||
id: activeScenarioTab.value.id,
|
id: activeScenarioTab.value.id,
|
||||||
grouped: false,
|
grouped: false,
|
||||||
environmentId: activeScenarioTab.value.environmentId || '',
|
environmentId: appStore.getCurrentEnvId || '',
|
||||||
projectId: appStore.currentProjectId,
|
projectId: appStore.currentProjectId,
|
||||||
scenarioConfig: activeScenarioTab.value.scenarioConfig,
|
scenarioConfig: activeScenarioTab.value.scenarioConfig,
|
||||||
stepFileParam: activeScenarioTab.value.stepFileParam,
|
stepFileParam: activeScenarioTab.value.stepFileParam,
|
||||||
|
@ -437,7 +437,7 @@
|
||||||
scenarioTabs.value.push({
|
scenarioTabs.value.push({
|
||||||
...cloneDeep(defaultScenario),
|
...cloneDeep(defaultScenario),
|
||||||
id: getGenerateId(),
|
id: getGenerateId(),
|
||||||
environmentId: appStore.currentEnvConfig?.id || '',
|
environmentId: appStore.getCurrentEnvId || '',
|
||||||
label: `${t('apiScenario.createScenario')}${scenarioTabs.value.length}`,
|
label: `${t('apiScenario.createScenario')}${scenarioTabs.value.length}`,
|
||||||
moduleId: activeModule.value === 'all' ? 'root' : activeModule.value,
|
moduleId: activeModule.value === 'all' ? 'root' : activeModule.value,
|
||||||
projectId: appStore.currentProjectId,
|
projectId: appStore.currentProjectId,
|
||||||
|
@ -494,7 +494,7 @@
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
projectId: appStore.currentProjectId,
|
projectId: appStore.currentProjectId,
|
||||||
environmentId: activeScenarioTab.value.environmentId || '',
|
environmentId: appStore.getCurrentEnvId || '',
|
||||||
});
|
});
|
||||||
const scenarioDetail = await getScenarioDetail(res.id);
|
const scenarioDetail = await getScenarioDetail(res.id);
|
||||||
scenarioDetail.stepDetails = {};
|
scenarioDetail.stepDetails = {};
|
||||||
|
@ -534,7 +534,7 @@
|
||||||
} else {
|
} else {
|
||||||
await updateScenario({
|
await updateScenario({
|
||||||
...activeScenarioTab.value,
|
...activeScenarioTab.value,
|
||||||
environmentId: activeScenarioTab.value.environmentId || '',
|
environmentId: appStore.getCurrentEnvId || '',
|
||||||
steps: mapTree(activeScenarioTab.value.steps, (node) => {
|
steps: mapTree(activeScenarioTab.value.steps, (node) => {
|
||||||
return {
|
return {
|
||||||
...node,
|
...node,
|
||||||
|
|
|
@ -31,7 +31,12 @@
|
||||||
>
|
>
|
||||||
<!-- ID -->
|
<!-- ID -->
|
||||||
<template #num="{ record, rowIndex }">
|
<template #num="{ record, rowIndex }">
|
||||||
<a-button type="text" class="px-0" size="mini" @click="handleShowDetail(record.id, rowIndex)">
|
<a-button
|
||||||
|
type="text"
|
||||||
|
class="px-0 text-[14px] leading-[22px]"
|
||||||
|
size="mini"
|
||||||
|
@click="handleShowDetail(record.id, rowIndex)"
|
||||||
|
>
|
||||||
{{ record.num }}
|
{{ record.num }}
|
||||||
</a-button>
|
</a-button>
|
||||||
</template>
|
</template>
|
||||||
|
|
|
@ -51,7 +51,7 @@
|
||||||
<template #num="{ record, rowIndex }">
|
<template #num="{ record, rowIndex }">
|
||||||
<span
|
<span
|
||||||
type="text"
|
type="text"
|
||||||
class="one-line-text px-0 text-[rgb(var(--primary-5))]"
|
class="one-line-text cursor-pointer px-0 text-[rgb(var(--primary-5))]"
|
||||||
@click="showCaseDetail(record.id, rowIndex)"
|
@click="showCaseDetail(record.id, rowIndex)"
|
||||||
>{{ record.num }}</span
|
>{{ record.num }}</span
|
||||||
>
|
>
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<div class="px-[24px] py-[16px]">
|
<div class="px-[24px] py-[8px]">
|
||||||
<div class="mb-[16px] flex flex-wrap items-center justify-end">
|
<div class="mb-[8px] flex flex-wrap items-center justify-end">
|
||||||
<MsAdvanceFilter
|
<MsAdvanceFilter
|
||||||
v-model:keyword="keyword"
|
v-model:keyword="keyword"
|
||||||
:filter-config-list="filterConfigList"
|
:filter-config-list="filterConfigList"
|
||||||
|
@ -73,7 +73,7 @@
|
||||||
</template>
|
</template>
|
||||||
<template #num="{ record }">
|
<template #num="{ record }">
|
||||||
<a-tooltip :content="record.num">
|
<a-tooltip :content="record.num">
|
||||||
<a-button type="text" class="px-0" @click="review(record)">
|
<a-button type="text" class="px-0 !text-[14px] !leading-[22px]" size="mini" @click="review(record)">
|
||||||
<div class="one-line-text max-w-[168px]">{{ record.num }}</div>
|
<div class="one-line-text max-w-[168px]">{{ record.num }}</div>
|
||||||
</a-button>
|
</a-button>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
|
@ -434,10 +434,11 @@
|
||||||
{
|
{
|
||||||
scroll: { x: '100%' },
|
scroll: { x: '100%' },
|
||||||
tableKey: TableKeyEnum.CASE_MANAGEMENT_REVIEW_CASE,
|
tableKey: TableKeyEnum.CASE_MANAGEMENT_REVIEW_CASE,
|
||||||
heightUsed: 472,
|
heightUsed: 372,
|
||||||
showSetting: true,
|
showSetting: true,
|
||||||
selectable: true,
|
selectable: true,
|
||||||
showSelectAll: true,
|
showSelectAll: true,
|
||||||
|
paginationSize: 'mini',
|
||||||
draggable: { type: 'handle', width: 32 },
|
draggable: { type: 'handle', width: 32 },
|
||||||
},
|
},
|
||||||
(record) => {
|
(record) => {
|
||||||
|
@ -628,6 +629,7 @@
|
||||||
userId: props.onlyMine ? userStore.id || '' : '',
|
userId: props.onlyMine ? userStore.id || '' : '',
|
||||||
moduleIds: props.activeFolder === 'all' ? [] : [props.activeFolder, ...props.offspringIds],
|
moduleIds: props.activeFolder === 'all' ? [] : [props.activeFolder, ...props.offspringIds],
|
||||||
...batchParams.value,
|
...batchParams.value,
|
||||||
|
...tableParams.value,
|
||||||
});
|
});
|
||||||
Message.success(t('common.updateSuccess'));
|
Message.success(t('common.updateSuccess'));
|
||||||
resetSelector();
|
resetSelector();
|
||||||
|
@ -663,6 +665,7 @@
|
||||||
notifier: dialogForm.value.commentIds.join(';'),
|
notifier: dialogForm.value.commentIds.join(';'),
|
||||||
moduleIds: props.activeFolder === 'all' ? [] : [props.activeFolder, ...props.offspringIds],
|
moduleIds: props.activeFolder === 'all' ? [] : [props.activeFolder, ...props.offspringIds],
|
||||||
...batchParams.value,
|
...batchParams.value,
|
||||||
|
...tableParams.value,
|
||||||
});
|
});
|
||||||
Message.success(t('common.updateSuccess'));
|
Message.success(t('common.updateSuccess'));
|
||||||
dialogVisible.value = false;
|
dialogVisible.value = false;
|
||||||
|
@ -690,6 +693,7 @@
|
||||||
append: dialogForm.value.isAppend, // 是否追加
|
append: dialogForm.value.isAppend, // 是否追加
|
||||||
moduleIds: props.activeFolder === 'all' ? [] : [props.activeFolder, ...props.offspringIds],
|
moduleIds: props.activeFolder === 'all' ? [] : [props.activeFolder, ...props.offspringIds],
|
||||||
...batchParams.value,
|
...batchParams.value,
|
||||||
|
...tableParams.value,
|
||||||
});
|
});
|
||||||
Message.success(t('common.updateSuccess'));
|
Message.success(t('common.updateSuccess'));
|
||||||
dialogVisible.value = false;
|
dialogVisible.value = false;
|
||||||
|
@ -720,6 +724,7 @@
|
||||||
notifier: dialogForm.value.commentIds.join(';'),
|
notifier: dialogForm.value.commentIds.join(';'),
|
||||||
moduleIds: props.activeFolder === 'all' ? [] : [props.activeFolder, ...props.offspringIds],
|
moduleIds: props.activeFolder === 'all' ? [] : [props.activeFolder, ...props.offspringIds],
|
||||||
...batchParams.value,
|
...batchParams.value,
|
||||||
|
...tableParams.value,
|
||||||
});
|
});
|
||||||
Message.success(t('caseManagement.caseReview.reviewSuccess'));
|
Message.success(t('caseManagement.caseReview.reviewSuccess'));
|
||||||
dialogVisible.value = false;
|
dialogVisible.value = false;
|
||||||
|
@ -890,4 +895,5 @@
|
||||||
:deep(.arco-radio-label) {
|
:deep(.arco-radio-label) {
|
||||||
@apply inline-flex;
|
@apply inline-flex;
|
||||||
}
|
}
|
||||||
|
.ms-table--special-small();
|
||||||
</style>
|
</style>
|
||||||
|
|
|
@ -4,7 +4,7 @@
|
||||||
v-model:model-value="moduleKeyword"
|
v-model:model-value="moduleKeyword"
|
||||||
:placeholder="t('caseManagement.caseReview.folderSearchPlaceholder')"
|
:placeholder="t('caseManagement.caseReview.folderSearchPlaceholder')"
|
||||||
allow-clear
|
allow-clear
|
||||||
class="mb-[16px]"
|
class="mb-[8px]"
|
||||||
:max-length="255"
|
:max-length="255"
|
||||||
/>
|
/>
|
||||||
<div class="folder">
|
<div class="folder">
|
||||||
|
|
|
@ -67,8 +67,7 @@
|
||||||
<template v-if="!props.isModal" #extra="nodeData">
|
<template v-if="!props.isModal" #extra="nodeData">
|
||||||
<!-- 默认模块的 id 是root,默认模块不可编辑、不可添加子模块 -->
|
<!-- 默认模块的 id 是root,默认模块不可编辑、不可添加子模块 -->
|
||||||
<popConfirm
|
<popConfirm
|
||||||
v-if="nodeData.id !== 'root'"
|
v-if="nodeData.id !== 'root' && hasAnyPermission(['CASE_REVIEW:READ+DELETE'])"
|
||||||
v-permission="['CASE_REVIEW:READ+ADD']"
|
|
||||||
mode="add"
|
mode="add"
|
||||||
:all-names="(nodeData.children || []).map((e: ModuleTreeNode) => e.name || '')"
|
:all-names="(nodeData.children || []).map((e: ModuleTreeNode) => e.name || '')"
|
||||||
:parent-id="nodeData.id"
|
:parent-id="nodeData.id"
|
||||||
|
@ -80,8 +79,7 @@
|
||||||
</MsButton>
|
</MsButton>
|
||||||
</popConfirm>
|
</popConfirm>
|
||||||
<popConfirm
|
<popConfirm
|
||||||
v-if="nodeData.id !== 'root'"
|
v-if="nodeData.id !== 'root' && hasAnyPermission(['CASE_REVIEW:READ+UPDATE'])"
|
||||||
v-permission="['CASE_REVIEW:READ+UPDATE']"
|
|
||||||
mode="rename"
|
mode="rename"
|
||||||
:parent-id="nodeData.id"
|
:parent-id="nodeData.id"
|
||||||
:node-id="nodeData.id"
|
:node-id="nodeData.id"
|
||||||
|
|
|
@ -92,7 +92,7 @@
|
||||||
</template>
|
</template>
|
||||||
<template #num="{ record }">
|
<template #num="{ record }">
|
||||||
<a-tooltip :content="`${record.num}`">
|
<a-tooltip :content="`${record.num}`">
|
||||||
<a-button type="text" class="px-0" size="mini" @click="openDetail(record.id)">
|
<a-button type="text" class="px-0 !text-[14px] !leading-[22px]" size="mini" @click="openDetail(record.id)">
|
||||||
<div class="one-line-text max-w-[168px]">{{ record.num }}</div>
|
<div class="one-line-text max-w-[168px]">{{ record.num }}</div>
|
||||||
</a-button>
|
</a-button>
|
||||||
</a-tooltip>
|
</a-tooltip>
|
||||||
|
@ -675,14 +675,7 @@
|
||||||
try {
|
try {
|
||||||
batchMoveFileLoading.value = true;
|
batchMoveFileLoading.value = true;
|
||||||
await moveReview({
|
await moveReview({
|
||||||
selectIds: batchParams.value?.selectedIds || [],
|
...tableQueryParams.value,
|
||||||
selectAll: !!batchParams.value?.selectAll,
|
|
||||||
excludeIds: batchParams.value?.excludeIds || [],
|
|
||||||
currentSelectCount: batchParams.value?.currentSelectCount || 0,
|
|
||||||
condition: { keyword: keyword.value },
|
|
||||||
projectId: appStore.currentProjectId,
|
|
||||||
moduleIds: props.activeFolder === 'all' ? [] : [props.activeFolder],
|
|
||||||
moveModuleId: selectedModuleKeys.value[0],
|
|
||||||
});
|
});
|
||||||
Message.success(t('caseManagement.caseReview.batchMoveSuccess'));
|
Message.success(t('caseManagement.caseReview.batchMoveSuccess'));
|
||||||
loadList();
|
loadList();
|
||||||
|
|
|
@ -102,7 +102,7 @@
|
||||||
<MsCard class="mt-[16px]" :special-height="180" simple has-breadcrumb no-content-padding>
|
<MsCard class="mt-[16px]" :special-height="180" simple has-breadcrumb no-content-padding>
|
||||||
<MsSplitBox>
|
<MsSplitBox>
|
||||||
<template #first>
|
<template #first>
|
||||||
<div class="p-[24px]">
|
<div class="p-[16px]">
|
||||||
<CaseTree
|
<CaseTree
|
||||||
ref="folderTreeRef"
|
ref="folderTreeRef"
|
||||||
:modules-count="modulesCount"
|
:modules-count="modulesCount"
|
||||||
|
|
Loading…
Reference in New Issue