fix(全局): 项目管理/测试计划/脑图部分 bug 修复

This commit is contained in:
baiqi 2024-05-27 17:49:23 +08:00 committed by 刘瑞斌
parent c1a3f918e4
commit 0ad5eb7e64
17 changed files with 122 additions and 44 deletions

View File

@ -1,5 +1,4 @@
import MSR from '@/api/http/index'; import MSR from '@/api/http/index';
import * as bugURL from '@/api/requrls/bug-management';
import { CommonList, TableQueryParams } from '@/models/common'; import { CommonList, TableQueryParams } from '@/models/common';
@ -91,5 +90,5 @@ export function getMessageReadAll(resourceType?: string) {
} }
export function getMessageUnReadCount(projectId: string) { export function getMessageUnReadCount(projectId: string) {
return MSR.get<number>({ url: '/notification/un-read', params: projectId }); return MSR.get<number>({ url: '/notification/un-read', params: projectId }, { ignoreCancelToken: true });
} }

View File

@ -36,7 +36,7 @@
> >
{{ t('common.save') }} {{ t('common.save') }}
</a-button> </a-button>
<a-button type="secondary" :disabled="saveLoading">{{ t('common.cancel') }}</a-button> <a-button type="secondary" :disabled="saveLoading" @click="handleCancel">{{ t('common.cancel') }}</a-button>
</div> </div>
</div> </div>
</template> </template>
@ -47,6 +47,7 @@
import MsFormCreate from '@/components/pure/ms-form-create/ms-form-create.vue'; import MsFormCreate from '@/components/pure/ms-form-create/ms-form-create.vue';
import { FormItem, FormRuleItem } from '@/components/pure/ms-form-create/types'; import { FormItem, FormRuleItem } from '@/components/pure/ms-form-create/types';
import { MinderJsonNode } from '@/components/pure/ms-minder-editor/props'; import { MinderJsonNode } from '@/components/pure/ms-minder-editor/props';
import MsTagsInput from '@/components/pure/ms-tags-input/index.vue';
import { getCaseDefaultFields, updateCaseRequest } from '@/api/modules/case-management/featureCase'; import { getCaseDefaultFields, updateCaseRequest } from '@/api/modules/case-management/featureCase';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
@ -61,6 +62,9 @@
activeCase: Record<string, any>; activeCase: Record<string, any>;
loading: boolean; loading: boolean;
}>(); }>();
const emit = defineEmits<{
(e: 'cancel'): void;
}>();
const appStore = useAppStore(); const appStore = useAppStore();
const userStore = useUserStore(); const userStore = useUserStore();
@ -149,7 +153,7 @@
fileList: [], fileList: [],
}); });
const selectedNode: MinderJsonNode = window.minder.getSelectedNode(); const selectedNode: MinderJsonNode = window.minder.getSelectedNode();
if (selectedNode.data) { if (selectedNode?.data) {
selectedNode.data.text = baseInfoForm.value.name; selectedNode.data.text = baseInfoForm.value.name;
} }
Message.success(t('common.saveSuccess')); Message.success(t('common.saveSuccess'));
@ -164,6 +168,9 @@
} }
}); });
} }
function handleCancel() {
emit('cancel');
}
watch( watch(
() => props.activeCase.id, () => props.activeCase.id,

View File

@ -17,7 +17,12 @@
@save="handleMinderSave" @save="handleMinderSave"
> >
<template #extractTabContent> <template #extractTabContent>
<baseInfo v-if="activeExtraKey === 'baseInfo'" :loading="baseInfoLoading" :active-case="activeCase" /> <baseInfo
v-if="activeExtraKey === 'baseInfo'"
:loading="baseInfoLoading"
:active-case="activeCase"
@cancel="handleBaseInfoCancel"
/>
<attachment <attachment
v-else-if="activeExtraKey === 'attachment'" v-else-if="activeExtraKey === 'attachment'"
v-model:model-value="fileList" v-model:model-value="fileList"
@ -69,7 +74,7 @@
const topTags = [moduleTag, caseTag]; const topTags = [moduleTag, caseTag];
const descTags = [t('ms.minders.stepDesc'), t('ms.minders.textDesc')]; const descTags = [t('ms.minders.stepDesc'), t('ms.minders.textDesc')];
const importJson = ref<MinderJson>({ const importJson = ref<MinderJson>({
root: {}, root: {} as MinderJsonNode,
template: 'default', template: 'default',
treePath: [], treePath: [],
}); });
@ -552,6 +557,11 @@
fileList.value = []; fileList.value = [];
} }
function handleBaseInfoCancel() {
extraVisible.value = false;
resetExtractInfo();
}
/** /**
* 处理脑图节点激活/点击 * 处理脑图节点激活/点击
* @param node 被激活/点击的节点 * @param node 被激活/点击的节点

View File

@ -42,6 +42,7 @@
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import useUserStore from '@/store/modules/user'; import useUserStore from '@/store/modules/user';
import { encrypted } from '@/utils'; import { encrypted } from '@/utils';
import { clearToken } from '@/utils/auth';
import { validatePasswordLength, validateWordPassword } from '@/utils/validate'; import { validatePasswordLength, validateWordPassword } from '@/utils/validate';
const router = useRouter(); const router = useRouter();
@ -111,6 +112,7 @@
}, 1000); }, 1000);
setTimeout(() => { setTimeout(() => {
clearInterval(timer); clearInterval(timer);
clearToken();
router.push({ router.push({
name: 'login', name: 'login',
query: { query: {

View File

@ -92,6 +92,8 @@
import useMinderStore from '@/store/modules/components/minder-editor'; import useMinderStore from '@/store/modules/components/minder-editor';
import { findNodePathByKey } from '@/utils'; import { findNodePathByKey } from '@/utils';
import { MinderEventName } from '@/enums/minderEnum';
import { editMenuProps, insertProps, mainEditorProps, MinderJsonNode, priorityProps, tagProps } from '../props'; import { editMenuProps, insertProps, mainEditorProps, MinderJsonNode, priorityProps, tagProps } from '../props';
import Editor from '../script/editor'; import Editor from '../script/editor';
import { markChangeNode, markDeleteNode } from '../script/tool/utils'; import { markChangeNode, markDeleteNode } from '../script/tool/utils';
@ -222,8 +224,12 @@
const menuVisible = ref(false); const menuVisible = ref(false);
const menuPopupOffset = ref([0, 0]); const menuPopupOffset = ref([0, 0]);
function switchNode(node: any) { /**
innerImportJson.value = cloneDeep(findNodePathByKey([props.importJson.root], node.id, 'data', 'id')); * 切换脑图展示的节点层级
* @param node 切换的节点
*/
function switchNode(node: MinderJsonNode) {
innerImportJson.value = cloneDeep(findNodePathByKey([props.importJson.root], node.data?.id, 'data', 'id'));
innerImportJson.value.data.expandState = 'expand'; innerImportJson.value.data.expandState = 'expand';
window.minder.importJson(innerImportJson.value); window.minder.importJson(innerImportJson.value);
window.minder.execCommand('template', Object.keys(window.kityminder.Minder.getTemplateList())[minderStore.mold]); window.minder.execCommand('template', Object.keys(window.kityminder.Minder.getTemplateList())[minderStore.mold]);
@ -232,7 +238,7 @@
watch( watch(
() => minderStore.event.timestamp, () => minderStore.event.timestamp,
() => { () => {
if (minderStore.event.name === 'hotbox') { if (minderStore.event.name === MinderEventName.HOTBOX && minderStore.event.nodePosition) {
const nodeDomWidth = minderStore.event.nodeDom?.getBoundingClientRect().width || 0; const nodeDomWidth = minderStore.event.nodeDom?.getBoundingClientRect().width || 0;
menuPopupOffset.value = [ menuPopupOffset.value = [
minderStore.event.nodePosition.x + nodeDomWidth / 2, minderStore.event.nodePosition.x + nodeDomWidth / 2,
@ -240,12 +246,16 @@
]; ];
menuVisible.value = true; menuVisible.value = true;
} }
if (minderStore.event.name === 'enterNode') { if (minderStore.event.name === MinderEventName.ENTER_NODE && minderStore.event.node) {
switchNode(minderStore.event.nodeData); switchNode(minderStore.event.node);
} }
} }
); );
/**
* 执行插入
* @param command 插入命令
*/
function execInsertCommand(command: string) { function execInsertCommand(command: string) {
const node: MinderJsonNode = window.minder.getSelectedNode(); const node: MinderJsonNode = window.minder.getSelectedNode();
if (props.insertNode) { if (props.insertNode) {
@ -257,6 +267,10 @@
} }
} }
/**
* 处理快捷菜单选择
* @param val 选择的菜单项
*/
function handleMinderMenuSelect(val: string | number | Record<string, any> | undefined) { function handleMinderMenuSelect(val: string | number | Record<string, any> | undefined) {
const selectedNode = window.minder.getSelectedNode(); const selectedNode = window.minder.getSelectedNode();
switch (val) { switch (val) {

View File

@ -19,10 +19,15 @@
import { nextTick, onMounted, reactive, ref } from 'vue'; import { nextTick, onMounted, reactive, ref } from 'vue';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import useMinderStore from '@/store/modules/components/minder-editor';
import { MinderNodePosition } from '@/store/modules/components/minder-editor/types';
import { MinderEventName } from '@/enums/minderEnum';
import { delProps } from '../../props'; import { delProps } from '../../props';
import { isDeleteDisableNode } from '../../script/tool/utils'; import { isDeleteDisableNode } from '../../script/tool/utils';
const minderStore = useMinderStore();
const { t } = useI18n(); const { t } = useI18n();
const props = defineProps(delProps); const props = defineProps(delProps);
@ -54,9 +59,19 @@
if (removeNodeDisabled.value || !minder.queryCommandState || !minder.execCommand) { if (removeNodeDisabled.value || !minder.queryCommandState || !minder.execCommand) {
return; return;
} }
if (props.delConfirm) { const node = minder.getSelectedNode();
props.delConfirm(); let position: MinderNodePosition | undefined;
return; if (node) {
if (props.delConfirm) {
props.delConfirm(node);
return;
}
const box = node.getRenderBox();
position = {
x: box.cx,
y: box.cy,
};
minderStore.dispatchEvent(MinderEventName.DELETE_NODE, position, node.rc.node, node.data);
} }
minder.forceRemoveNode(); minder.forceRemoveNode();
} }

View File

@ -19,8 +19,8 @@ export interface MinderJsonNodeData {
[key: string]: any; [key: string]: any;
} }
export interface MinderJsonNode { export interface MinderJsonNode {
data: MinderJsonNodeData;
parent?: MinderJsonNode; parent?: MinderJsonNode;
data?: MinderJsonNodeData;
children?: MinderJsonNode[]; children?: MinderJsonNode[];
[key: string]: any; // minder 内置字段 [key: string]: any; // minder 内置字段
} }
@ -148,8 +148,9 @@ export const moleProps = {
}; };
export const delProps = { export const delProps = {
// 节点删除确认
delConfirm: { delConfirm: {
type: Function, type: Function as PropType<(node: MinderJsonNode) => void>,
default: null, default: null,
}, },
}; };

View File

@ -1,9 +1,7 @@
import useMinderStore from '@/store/modules/components/minder-editor'; import useMinderStore from '@/store/modules/components/minder-editor';
import type { MinderNodePosition } from '@/store/modules/components/minder-editor/types';
interface Position { import { MinderEventName } from '@/enums/minderEnum';
x: number;
y: number;
}
function HotboxRuntime(this: any) { function HotboxRuntime(this: any) {
const { fsm } = this; const { fsm } = this;
@ -18,14 +16,14 @@ function HotboxRuntime(this: any) {
function handleHotBoxShow() { function handleHotBoxShow() {
const node = minder.getSelectedNode(); const node = minder.getSelectedNode();
let position: Position | undefined; let position: MinderNodePosition | undefined;
if (node) { if (node) {
const box = node.getRenderBox(); const box = node.getRenderBox();
position = { position = {
x: box.cx, x: box.cx,
y: box.cy, y: box.cy,
}; };
minderStore.dispatchEvent('hotbox', position, node.rc.node); minderStore.dispatchEvent(MinderEventName.HOTBOX, position, node.rc.node);
} }
} }
@ -48,14 +46,14 @@ function HotboxRuntime(this: any) {
e.preventDefault(); // 阻止默认行为 e.preventDefault(); // 阻止默认行为
// 执行进入模块方法 // 执行进入模块方法
const node = minder.getSelectedNode(); const node = minder.getSelectedNode();
let position: Position | undefined; let position: MinderNodePosition | undefined;
if (node) { if (node) {
const box = node.getRenderBox(); const box = node.getRenderBox();
position = { position = {
x: box.cx, x: box.cx,
y: box.cy, y: box.cy,
}; };
minderStore.dispatchEvent('enterNode', position, node.rc.node, node.data); minderStore.dispatchEvent(MinderEventName.ENTER_NODE, position, node.rc.node, node.data);
return; return;
} }
} }

View File

@ -68,8 +68,8 @@
modelValue.value = result; modelValue.value = result;
nextTick(() => { nextTick(() => {
initNumberAndType(); initNumberAndType();
emit('change', modelValue.value);
}); });
emit('change', modelValue.value);
} }
const option = [ const option = [

View File

@ -0,0 +1,7 @@
export enum MinderEventName {
'DELETE_NODE' = 'DELETE_NODE', // 删除节点
'HOTBOX' = 'HOTBOX', // 热键菜单
'ENTER_NODE' = 'ENTER_NODE', // 进入节点
}
export default {};

View File

@ -1,30 +1,41 @@
import { defineStore } from 'pinia'; import { defineStore } from 'pinia';
import type { MinderJsonNode } from '@/components/pure/ms-minder-editor/props';
import type { MinderEventName } from '@/enums/minderEnum';
import { MinderNodePosition, MinderState } from './types'; import { MinderNodePosition, MinderState } from './types';
// 脑图组件的 store // 脑图组件的 store
const useMinderStore = defineStore('minder', { const useMinderStore = defineStore('minder', {
state: (): MinderState => ({ state: (): MinderState => ({
event: { event: {
name: '', name: '' as MinderEventName,
timestamp: 0, timestamp: 0,
nodePosition: { nodePosition: {
x: 0, x: 0,
y: 0, y: 0,
}, },
nodeDom: undefined, nodeDom: undefined,
nodeData: undefined, node: undefined,
}, },
mold: 0, mold: 0,
}), }),
actions: { actions: {
dispatchEvent(name: string, position: MinderNodePosition, nodeDom?: HTMLElement, nodeData?: Record<string, any>) { /**
*
* @param name
* @param position /
* @param nodeDom DOM
* @param node
*/
dispatchEvent(name: MinderEventName, position?: MinderNodePosition, nodeDom?: HTMLElement, node?: MinderJsonNode) {
this.event = { this.event = {
name, name,
timestamp: Date.now(), timestamp: Date.now(),
nodePosition: position, nodePosition: position,
nodeDom, nodeDom,
nodeData, node,
}; };
}, },
setMold(val: number) { setMold(val: number) {

View File

@ -1,14 +1,18 @@
import type { MinderJsonNode } from '@/components/pure/ms-minder-editor/props';
import type { MinderEventName } from '@/enums/minderEnum';
export interface MinderNodePosition { export interface MinderNodePosition {
x: number; x: number;
y: number; y: number;
} }
export interface MinderEvent { export interface MinderEvent {
name: string; name: MinderEventName;
timestamp: number; timestamp: number;
nodePosition: MinderNodePosition; nodePosition?: MinderNodePosition;
nodeDom?: HTMLElement; nodeDom?: HTMLElement;
nodeData?: Record<string, any>; node?: MinderJsonNode;
} }
export interface MinderState { export interface MinderState {

View File

@ -142,11 +142,13 @@ const useUserStore = defineStore('user', {
appStore.resetSystemPackageType(); appStore.resetSystemPackageType();
}, },
// 登出 // 登出
async logout() { async logout(silence = false) {
try { try {
const { t } = useI18n(); const { t } = useI18n();
const appStore = useAppStore(); if (!silence) {
appStore.showLoading(t('message.logouting')); const appStore = useAppStore();
appStore.showLoading(t('message.logouting'));
}
await userLogout(); await userLogout();
} finally { } finally {
this.logoutCallBack(); this.logoutCallBack();

View File

@ -162,7 +162,7 @@
setLoading(true); setLoading(true);
try { try {
try { try {
await userStore.logout(); // await userStore.logout(true); //
} catch (error) { } catch (error) {
// eslint-disable-next-line no-console // eslint-disable-next-line no-console
console.log('logout error', error); console.log('logout error', error);

View File

@ -1,5 +1,5 @@
export default { export default {
'login.form.title': 'One-stop open source continuous testing platform', 'login.form.title': 'Modern, open-source test management and interface testing tools',
'login.form.userName.errMsg': 'Username cannot be empty', 'login.form.userName.errMsg': 'Username cannot be empty',
'login.form.password.errMsg': 'Password cannot be empty', 'login.form.password.errMsg': 'Password cannot be empty',
'login.form.login.errMsg': 'Login error, refresh and try again', 'login.form.login.errMsg': 'Login error, refresh and try again',

View File

@ -1,5 +1,5 @@
export default { export default {
'login.form.title': '一站式开源持续测试平台', 'login.form.title': '现代化、开源的测试管理和接口测试工具',
'login.form.userName.errMsg': '邮箱不能为空', 'login.form.userName.errMsg': '邮箱不能为空',
'login.form.password.errMsg': '密码不能为空', 'login.form.password.errMsg': '密码不能为空',
'login.form.login.errMsg': '登录出错,请刷新重试', 'login.form.login.errMsg': '登录出错,请刷新重试',

View File

@ -61,7 +61,7 @@
value-format="timestamp" value-format="timestamp"
:separator="t('common.to')" :separator="t('common.to')"
:time-picker-props="{ :time-picker-props="{
defaultValue: ['00:00:00', '00:00:00'], defaultValue: tempRange,
}" }"
:disabled-time="disabledTime" :disabled-time="disabledTime"
@select="handleTimeSelect" @select="handleTimeSelect"
@ -196,14 +196,14 @@
return (nodeData as ModuleTreeNode).name.toLowerCase().indexOf(searchValue.toLowerCase()) > -1; return (nodeData as ModuleTreeNode).name.toLowerCase().indexOf(searchValue.toLowerCase()) > -1;
} }
const tempRange = ref<(Date | string | number | undefined)[]>([]); const tempRange = ref<(Date | string | number)[]>(['00:00:00', '00:00:00']);
function makeLessNumbers(value: number) { function makeLessNumbers(value: number, isSecond = false) {
const res = []; const res = [];
for (let i = 0; i < value; i++) { for (let i = 0; i < value; i++) {
res.push(i); res.push(i);
} }
return res; return isSecond && res.length === 0 ? [0] : res; // 1
} }
function disabledTime(current: Date, type: 'start' | 'end'): DisabledTimeProps { function disabledTime(current: Date, type: 'start' | 'end'): DisabledTimeProps {
@ -230,7 +230,7 @@
currentDate.isSame(startDate, 'h') && currentDate.isSame(startDate, 'h') &&
currentDate.isSame(startDate, 'm') currentDate.isSame(startDate, 'm')
) { ) {
return makeLessNumbers(startDate.get('s')); return makeLessNumbers(startDate.get('s'), true);
} }
return []; return [];
}, },
@ -240,7 +240,15 @@
} }
function handleTimeSelect(value: (Date | string | number | undefined)[]) { function handleTimeSelect(value: (Date | string | number | undefined)[]) {
tempRange.value = value; if (value) {
// const start = dayjs(value[0]);
// const end = dayjs(value[1]);
// if (start.isSame(end, 'D') && end.hour() === 0 && end.minute() === 0 && end.second() === 0) {
// const newEnd = end.hour(23).minute(59).second(59);
// value[1] = newEnd.valueOf();
// }
tempRange.value = value as number[];
}
} }
const switchList: SwitchListModel[] = [ const switchList: SwitchListModel[] = [