refactor: 评论展开收起

This commit is contained in:
RubyLiu 2024-02-27 18:42:55 +08:00 committed by 刘瑞斌
parent 28c7f3abbb
commit ee591fc936
14 changed files with 230 additions and 107 deletions

View File

@ -79,8 +79,8 @@ export function groupDeleteEnv(data: EnvListItem) {
return MSR.post<EnvListItem>({ url: envURL.groupDeleteEnvUrl, data }); return MSR.post<EnvListItem>({ url: envURL.groupDeleteEnvUrl, data });
} }
// 获取项目组的项目 // 获取项目组的项目
export function groupProjectEnv() { export function groupProjectEnv(organizationId: string) {
return MSR.get<ProjectOptionItem[]>({ url: envURL.groupProjectEnvUrl }); return MSR.get<ProjectOptionItem[]>({ url: envURL.groupProjectEnvUrl + organizationId });
} }
/** 项目管理-环境-全局参数-更新or新增 */ /** 项目管理-环境-全局参数-更新or新增 */

View File

@ -17,7 +17,7 @@ export const groupDetailEnvUrl = '/project/environment/group/get/';
export const groupDeleteEnvUrl = '/project/environment/group/delete/'; export const groupDeleteEnvUrl = '/project/environment/group/delete/';
export const getEnvPluginUrl = '/project/environment/scripts/'; export const getEnvPluginUrl = '/project/environment/scripts/';
// 获取项目组的项目 // 获取项目组的项目
export const groupProjectEnvUrl = '/project/environment/group/get-project'; export const groupProjectEnvUrl = '/project/environment/group/get-project/';
// 全局参数 // 全局参数
export const updateGlobalParamUrl = '/project/global/params/update'; export const updateGlobalParamUrl = '/project/global/params/update';
export const addGlobalParamUrl = '/project/global/params/add'; export const addGlobalParamUrl = '/project/global/params/add';

View File

@ -95,3 +95,10 @@ body {
background: rgb(var(--primary-6)); background: rgb(var(--primary-6));
} }
} }
/* 评论组件的样式 */
.ms-comment-child-container {
padding: 16px;
border: 0.5px solid var(--color-text-input-border); /* 设置近似 0.5px 的边框 */
border-radius: 4px;
}

View File

@ -51,18 +51,18 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import {defineModel, ref} from 'vue'; import { defineModel, ref } from 'vue';
import dayjs from 'dayjs'; import dayjs from 'dayjs';
import MsAvatar from '@/components/pure/ms-avatar/index.vue'; import MsAvatar from '@/components/pure/ms-avatar/index.vue';
import MsIconfont from '@/components/pure/ms-icon-font/index.vue'; import MsIconfont from '@/components/pure/ms-icon-font/index.vue';
import {useI18n} from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import useUserStore from '@/store/modules/user/index'; import useUserStore from '@/store/modules/user/index';
import {CommentItem} from './types'; import { CommentItem } from './types';
const userStore = useUserStore(); const userStore = useUserStore();
const { t } = useI18n(); const { t } = useI18n();
defineOptions({ name: 'MsCommentItem' }); defineOptions({ name: 'MsCommentItem' });
@ -86,12 +86,14 @@ const userStore = useUserStore();
(event: 'reply'): void; (event: 'reply'): void;
(event: 'edit'): void; (event: 'edit'): void;
(event: 'delete'): void; (event: 'delete'): void;
(event: 'expend', value: boolean): void;
}>(); }>();
const expendComment = ref(false); const expendComment = ref(false);
const expendChange = () => { const expendChange = () => {
expendComment.value = !expendComment.value; expendComment.value = !expendComment.value;
emit('expend', expendComment.value);
}; };
const replyClick = () => { const replyClick = () => {
emit('reply'); emit('reply');

View File

@ -3,9 +3,9 @@
import Item from './comment-item.vue'; import Item from './comment-item.vue';
import CommentInput from './input.vue'; import CommentInput from './input.vue';
import {useI18n} from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import {CommentEvent, CommentItem, CommentParams, CommentType} from './types'; import { CommentEvent, CommentItem, CommentParams, CommentType } from './types';
import message from '@arco-design/web-vue/es/message'; import message from '@arco-design/web-vue/es/message';
export default defineComponent({ export default defineComponent({
@ -33,6 +33,7 @@ export default defineComponent({
// 控制回复编辑删除按钮的状态 // 控制回复编辑删除按钮的状态
commentStatus: 'normal', commentStatus: 'normal',
}); });
const expendedIds = ref<string[]>([]); // 展开的评论id
// 被@的用户id // 被@的用户id
const noticeUserIds = ref<string[]>([]); const noticeUserIds = ref<string[]>([]);
const { t } = useI18n(); const { t } = useI18n();
@ -85,6 +86,16 @@ export default defineComponent({
emit('delete', item.id); emit('delete', item.id);
}; };
const handleExpend = (val: boolean, id: string) => {
if (val) {
// 展开
expendedIds.value = [...expendedIds.value, id];
} else {
// 收起
expendedIds.value = expendedIds.value.filter((item) => item !== id);
}
};
const handleReply = (item: CommentItem) => { const handleReply = (item: CommentItem) => {
if (item.childComments && Array.isArray(item.childComments)) { if (item.childComments && Array.isArray(item.childComments)) {
// 点击的是父级评论的回复 // 点击的是父级评论的回复
@ -122,49 +133,46 @@ export default defineComponent({
if (!list || list.length === 0) { if (!list || list.length === 0) {
return null; return null;
} }
return list.map((item) => {
return ( return list.map((item) => (
<div class="flex flex-col"> <div class="flex flex-col">
<Item <Item
mode={'child'} mode={'child'}
onReply={() => handleReply(item)} onReply={() => handleReply(item)}
onEdit={() => handelEdit(item)} onEdit={() => handelEdit(item)}
onDelete={() => handleDelete(item)} onDelete={() => handleDelete(item)}
status={item.id === currentItem.id ? currentItem.commentStatus : 'normal'} status={item.id === currentItem.id ? currentItem.commentStatus : 'normal'}
onUpdate:status={(v: string) => { onUpdate:status={(v: string) => {
currentItem.commentStatus = v; currentItem.commentStatus = v;
}} }}
element={item} element={item}
/> />
{item.id === currentItem.id && renderInput(item)} {item.id === currentItem.id && renderInput(item)}
</div> </div>
); ));
});
}; };
const renderParentList = (list: CommentItem[]) => { const renderParentList = (list: CommentItem[]) => {
return list.map((item) => { return list.map((item) => (
return ( <>
<> <Item
<Item mode={'parent'}
mode={'parent'} onReply={() => handleReply(item)}
onReply={() => handleReply(item)} onEdit={() => handelEdit(item)}
onEdit={() => handelEdit(item)} onDelete={() => handleDelete(item)}
onDelete={() => handleDelete(item)} onExpend={(val: boolean) => handleExpend(val, item.id)}
status={item.id === currentItem.id ? currentItem.commentStatus : 'normal'} status={item.id === currentItem.id ? currentItem.commentStatus : 'normal'}
onUpdate:status={(v: string) => { onUpdate:status={(v: string) => {
currentItem.commentStatus = v; currentItem.commentStatus = v;
}} }}
element={item} element={item}
> />
<div class="rounded border border-[var(--color-text-7)] p-[16px]"> {expendedIds.value.includes(item.id) && (
{renderChildrenList(item.childComments)} <div class="ms-comment-child-container">{renderChildrenList(item.childComments)}</div>
</div> )}
</Item> {item.id === currentItem.id && renderInput(item)}
{item.id === currentItem.id && renderInput(item)} </>
</> ));
);
});
}; };
return () => <div class="ms-comment gap[24px] flex flex-col">{renderParentList(commentList.value)}</div>; return () => <div class="ms-comment gap[24px] flex flex-col">{renderParentList(commentList.value)}</div>;

View File

@ -372,6 +372,10 @@ export default defineComponent(
} }
emit('update:modelValue', value); emit('update:modelValue', value);
emit('change', value); emit('change', value);
emit(
'changeObject',
remoteOriginOptions.value.filter((e) => value === e[props.valueKey || 'value'])
);
} }
/** /**
@ -544,6 +548,14 @@ export default defineComponent(
'disabled', 'disabled',
'size', 'size',
], ],
emits: ['update:modelValue', 'remoteSearch', 'popupVisibleChange', 'update:loading', 'remove', 'change'], emits: [
'update:modelValue',
'remoteSearch',
'popupVisibleChange',
'update:loading',
'remove',
'change',
'changeObject',
],
} }
); );

View File

@ -209,7 +209,11 @@
const handleMouseMove = (_event: MouseEvent) => { const handleMouseMove = (_event: MouseEvent) => {
if (resizing.value) { if (resizing.value) {
const newWidth = initialWidth + (startX - _event.clientX); // + const newWidth = initialWidth + (startX - _event.clientX); // +
if (newWidth >= (props.width || 480) && newWidth <= window.innerWidth * 0.9) { if (
typeof props.width === 'number' &&
newWidth >= (props.width || 480) &&
newWidth <= window.innerWidth * 0.9
) {
// width48090% // width48090%
drawerWidth.value = newWidth; drawerWidth.value = newWidth;
} }

View File

@ -291,20 +291,62 @@
</a-tooltip> </a-tooltip>
<a-input v-model="record.matchValue" size="mini" class="param-input" /> <a-input v-model="record.matchValue" size="mini" class="param-input" />
</template> </template>
<template #project="{ record, columnConfig, rowIndex }"> <template #project="{ record, rowIndex }">
<a-select <a-select
v-model="record.projectId" v-model:model-value="record.projectId"
class="param-input" class="param-input w-max-[200px] focus-within:!bg-[var(--color-text-n8)] hover:!bg-[var(--color-text-n8)]"
size="mini" :bordered="false"
@change="(val) => handelProjectChange(val as string, record.projectId, rowIndex)" allow-search
@change="(val) => handleProjectChange(val as string,record.projectId, rowIndex)"
> >
<a-option v-for="item in columnConfig.options" :key="item.id">{{ item.name }}</a-option> <template #arrow-icon>
<icon-caret-down />
</template>
<a-tooltip
v-for="project of appStore.projectList"
:key="project.id"
:mouse-enter-delay="500"
:content="project.name"
>
<a-option
:value="project.id"
:class="project.id === appStore.currentProjectId ? 'arco-select-option-selected' : ''"
>
{{ project.name }}
</a-option>
</a-tooltip>
</a-select> </a-select>
</template> </template>
<template #environment="{ record, columnConfig }"> <template #environment="{ record }">
<a-select v-model="record.environmentId" size="mini" class="param-input"> <MsSelect
<a-option v-for="item in columnConfig.options" :key="item.id">{{ item.name }}</a-option> v-if="record.projectId"
</a-select> v-model:model-value="record.environmentId"
v-model:input-value="record.environmentInput"
:disabled="!record.projectId"
:options="[]"
mode="remote"
value-key="id"
label-key="name"
:search-keys="['name']"
size="mini"
allow-search
class="param-input"
:remote-func="initEnvOptions"
:remote-extra-params="{ projectId: record.projectId, keyword: record.environmentInput }"
@change-object="(val) => handleEnvironment(val, record)"
/>
<span v-else></span>
</template>
<template #host="{ record }">
<span v-if="record.host.length === 1" class="text-[var(--color-text-4)]">{{ record.host }}</span>
<span
v-if="record.host.length > 1"
class="cursor-pointer text-[var(--color-text-4)]"
@click="showHostModal(record)"
>
{{ t('common.more') }}
</span>
<span v-else></span>
</template> </template>
<template #operation="{ record, rowIndex, columnConfig }"> <template #operation="{ record, rowIndex, columnConfig }">
<div class="flex flex-row items-center" :class="{ 'justify-end': columnConfig.align === 'right' }"> <div class="flex flex-row items-center" :class="{ 'justify-end': columnConfig.align === 'right' }">
@ -399,6 +441,9 @@
:max-length="1000" :max-length="1000"
></a-textarea> ></a-textarea>
</a-modal> </a-modal>
<a-modal v-model:visible="hostVisible" :title="t('apiTestDebug.host')" @close="hostModalClose">
<a-table :columns="hostColumn" :data="hostData"> </a-table>
</a-modal>
</template> </template>
<script async setup lang="ts"> <script async setup lang="ts">
@ -415,12 +460,15 @@
import MsTagsGroup from '@/components/pure/ms-tag/ms-tag-group.vue'; import MsTagsGroup from '@/components/pure/ms-tag/ms-tag-group.vue';
import MsTagsInput from '@/components/pure/ms-tags-input/index.vue'; import MsTagsInput from '@/components/pure/ms-tags-input/index.vue';
import { MsFileItem } from '@/components/pure/ms-upload/types'; import { MsFileItem } from '@/components/pure/ms-upload/types';
import MsSelect from '@/components/business/ms-select/index';
import paramDescInput from './paramDescInput.vue'; import paramDescInput from './paramDescInput.vue';
import { groupProjectEnv, listEnv } from '@/api/modules/project-management/envManagement';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import useTableStore from '@/hooks/useTableStore'; import useTableStore from '@/hooks/useTableStore';
import useAppStore from '@/store/modules/app'; import useAppStore from '@/store/modules/app';
import { ProjectOptionItem } from '@/models/projectManagement/environmental';
import { RequestBodyFormat, RequestContentTypeEnum, RequestParamsType } from '@/enums/apiEnum'; import { RequestBodyFormat, RequestContentTypeEnum, RequestParamsType } from '@/enums/apiEnum';
import { SelectAllEnum, TableKeyEnum } from '@/enums/tableEnum'; import { SelectAllEnum, TableKeyEnum } from '@/enums/tableEnum';
@ -632,6 +680,70 @@
}; };
/** 断言-文档-end */ /** 断言-文档-end */
/** 环境管理-环境组 start */
const sourceProjectOptions = ref<ProjectOptionItem[]>([]);
// options
const initProjectOptions = async () => {
const res = await groupProjectEnv(appStore.currentOrgId);
sourceProjectOptions.value = res;
};
// options
const initEnvOptions = async (params: Record<string, any>) => {
const { projectId, keyword } = params;
const res = await listEnv({ projectId, keyword });
return res;
};
const handleEnvironment = (obj: Record<string, any>, record: Record<string, any>) => {
record.host = obj.host;
emit('change', propsRes.value.data);
};
const hostVisible = ref(false);
const hostData = ref<any[]>([]);
const hostColumn = [
{
title: 'project.environmental.http.host',
dataIndex: 'host',
},
{
title: 'project.environmental.http.desc',
dataIndex: 'desc',
},
{
title: 'project.environmental.http.applyScope',
dataIndex: 'applyScope',
},
{
title: 'project.environmental.http.enableScope',
dataIndex: 'enableScope',
},
{
title: 'project.environmental.http.value',
dataIndex: 'value',
},
];
const showHostModal = (record: Record<string, any>) => {
hostVisible.value = true;
hostData.value = record.hostList || [];
};
const hostModalClose = () => {
hostVisible.value = false;
hostData.value = [];
};
watchEffect(() => {
if (props.columns.some((e) => e.dataIndex === 'projectId')) {
initProjectOptions();
}
});
/** 环境管理-环境组 end */
/** /**
* 当表格输入框变化时给参数表格添加一行数据行 * 当表格输入框变化时给参数表格添加一行数据行
* @param val 输入值 * @param val 输入值
@ -813,7 +925,7 @@
emit('moreActionSelect', event, record); emit('moreActionSelect', event, record);
} }
function handelProjectChange(val: string, projectId: string, rowIndex: number) { function handleProjectChange(val: string, projectId: string, rowIndex: number) {
emit('projectChange', projectId); emit('projectChange', projectId);
addTableLine(rowIndex); addTableLine(rowIndex);
} }
@ -851,6 +963,9 @@
.arco-select-view-value { .arco-select-view-value {
color: var(--color-text-brand); color: var(--color-text-brand);
} }
.arco-select {
border-color: transparent !important;
}
} }
} }
:deep(.param-input-number) { :deep(.param-input-number) {

View File

@ -3,16 +3,16 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import MsComment from '@/components/business/ms-comment'; import MsComment from '@/components/business/ms-comment';
import {CommentItem, CommentParams} from '@/components/business/ms-comment/types'; import { CommentItem, CommentParams } from '@/components/business/ms-comment/types';
import {createOrUpdateComment, deleteComment, getCommentList} from '@/api/modules/bug-management/index'; import { createOrUpdateComment, deleteComment, getCommentList } from '@/api/modules/bug-management/index';
import {useI18n} from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import useModal from '@/hooks/useModal'; import useModal from '@/hooks/useModal';
import message from '@arco-design/web-vue/es/message'; import message from '@arco-design/web-vue/es/message';
const { openModal } = useModal(); const { openModal } = useModal();
const props = defineProps<{ const props = defineProps<{
bugId: string; bugId: string;

View File

@ -52,16 +52,12 @@
</div> </div>
</template> </template>
<script lang="ts" setup> <script lang="ts" setup async>
import paramsTable, { type ParamTableColumn } from '@/views/api-test/components/paramTable.vue'; import paramsTable, { type ParamTableColumn } from '@/views/api-test/components/paramTable.vue';
import { groupProjectEnv, listEnv } from '@/api/modules/project-management/envManagement';
import { useI18n } from '@/hooks/useI18n'; import { useI18n } from '@/hooks/useI18n';
import { useAppStore } from '@/store';
import useProjectEnvStore from '@/store/modules/setting/useProjectEnvStore'; import useProjectEnvStore from '@/store/modules/setting/useProjectEnvStore';
import { EnvListItem, ProjectOptionItem } from '@/models/projectManagement/environmental';
const { t } = useI18n(); const { t } = useI18n();
const envGroupForm = ref(); const envGroupForm = ref();
@ -70,31 +66,25 @@
description: '', description: '',
}); });
const store = useProjectEnvStore(); const store = useProjectEnvStore();
const appStore = useAppStore();
const sourceProjectOptions = ref<ProjectOptionItem[]>([]);
const projectOptions = computed(() => {
return sourceProjectOptions.value;
});
const environmentOptions = ref<EnvListItem[]>([]);
const columns = computed<ParamTableColumn[]>(() => [ const columns = computed<ParamTableColumn[]>(() => [
{ {
title: 'project.environmental.project', title: 'project.environmental.project',
dataIndex: 'projectId', dataIndex: 'projectId',
slotName: 'project', slotName: 'project',
options: projectOptions.value, width: 200,
}, },
{ {
title: 'project.environmental.env', title: 'project.environmental.env',
dataIndex: 'environmentId', dataIndex: 'environmentId',
slotName: 'environment', slotName: 'environment',
options: environmentOptions.value, width: 200,
}, },
{ {
title: 'project.environmental.host', title: 'project.environmental.host',
dataIndex: 'host', dataIndex: 'host',
slotName: 'host', slotName: 'host',
width: 200,
}, },
{ {
title: 'project.environmental.desc', title: 'project.environmental.desc',
@ -123,24 +113,9 @@
} }
}); });
}; };
// options
const initProjectOptions = async () => {
const res = await groupProjectEnv();
sourceProjectOptions.value = res;
};
// options
const initEnvOptions = async () => {
const res = await listEnv({ projectId: appStore.currentProjectId, keyword: '' });
environmentOptions.value = res;
};
function handleParamTableChange(resultArr: any[]) { function handleParamTableChange(resultArr: any[]) {
innerParams.value = [...resultArr]; innerParams.value = [...resultArr];
} }
onMounted(() => {
initProjectOptions();
initEnvOptions();
});
</script> </script>
<style lang="less" scoped> <style lang="less" scoped>

View File

@ -78,7 +78,7 @@
> >
<template #prefix> <template #prefix>
<div class="input-prefix"> <div class="input-prefix">
<a-select> <a-select default-value="like">
<a-option v-for="item in OPERATOR_MAP.string" :key="item.value" :value="item.value">{{ <a-option v-for="item in OPERATOR_MAP.string" :key="item.value" :value="item.value">{{
t(item.label) t(item.label)
}}</a-option> }}</a-option>
@ -192,9 +192,7 @@
background-color: var(--color-text-n10); background-color: var(--color-text-n10);
} }
:deep(.arco-select) { :deep(.arco-select) {
border-top: 1px solid var(--color-text-n7); border: 1px solid var(--color-text-n7);
border-right: 1px solid var(--color-text-n7);
border-bottom: 1px solid var(--color-text-n7);
border-radius: 0 4px 4px 0; border-radius: 0 4px 4px 0;
background: var(--color-text-n8); background: var(--color-text-n8);
} }

View File

@ -217,7 +217,7 @@
const envList = ref<EnvListItem[]>([]); // const envList = ref<EnvListItem[]>([]); //
const evnGroupList = ref<EnvListItem[]>([]); // const evnGroupList = ref<EnvListItem[]>([]); //
const showType = ref<EnvAuthScopeEnum>(EnvAuthScopeEnum.PROJECT); // const showType = ref<EnvAuthScopeEnum>(EnvAuthScopeEnum.PROJECT_GROUP); //
const activeKey = computed(() => store.currentId); // id const activeKey = computed(() => store.currentId); // id

View File

@ -52,6 +52,7 @@ export default {
'project.environmental.http.apiModuleSelect': 'Select API Test Module', 'project.environmental.http.apiModuleSelect': 'Select API Test Module',
'project.environmental.http.uiModuleSelect': 'Select UI Test Module', 'project.environmental.http.uiModuleSelect': 'Select UI Test Module',
'project.environmental.http.pathRequired': 'Path is required', 'project.environmental.http.pathRequired': 'Path is required',
'project.environmental.http.pathPlaceholder': 'Please enter the path',
'project.environmental.database.addDatabase': 'Add Database', 'project.environmental.database.addDatabase': 'Add Database',
'project.environmental.database.updateDatabase': 'Update Database {name}', 'project.environmental.database.updateDatabase': 'Update Database {name}',
'project.environmental.database.name': 'Database Name', 'project.environmental.database.name': 'Database Name',

View File

@ -61,6 +61,7 @@ export default {
'project.environmental.http.apiModuleSelect': '接口模块选择', 'project.environmental.http.apiModuleSelect': '接口模块选择',
'project.environmental.http.uiModuleSelect': '选择UI测试模块', 'project.environmental.http.uiModuleSelect': '选择UI测试模块',
'project.environmental.http.pathRequired': '路径必填', 'project.environmental.http.pathRequired': '路径必填',
'project.environmental.http.pathPlaceholder': '请输入路径',
'project.environmental.database.addDatabase': '添加数据源', 'project.environmental.database.addDatabase': '添加数据源',
'project.environmental.database.updateDatabase': '更新数据源{name}', 'project.environmental.database.updateDatabase': '更新数据源{name}',
'project.environmental.database.name': '数据源名称', 'project.environmental.database.name': '数据源名称',