feat(测试计划): 测试计划详情-接口场景-点id新开场景详情页面&点击执行结果显示报告抽屉

This commit is contained in:
teukkk 2024-06-04 14:15:47 +08:00 committed by Craftsman
parent 3ee5bd00a8
commit 353b95ffad
11 changed files with 280 additions and 103 deletions

View File

@ -57,6 +57,8 @@ import type {
ExecuteHistoryType,
FollowPlanParams,
PassRateCountDetail,
PlanDetailApiCaseItem,
PlanDetailApiScenarioItem,
PlanDetailBugItem,
PlanDetailFeatureCaseItem,
PlanDetailFeatureCaseListQueryParams,
@ -238,3 +240,11 @@ export function testPlanCancelBug(id: string) {
export function executeHistory(data: ExecuteHistoryType) {
return MSR.post<ExecuteHistoryItem[]>({ url: ExecuteHistoryUrl, data });
}
// 计划详情-接口用例列表 TODO 联调
export function getPlanDetailApiCaseList(data: PlanDetailFeatureCaseListQueryParams) {
return MSR.post<CommonList<PlanDetailApiCaseItem>>({ url: GetPlanDetailFeatureCaseListUrl, data });
}
// 计划详情-接口场景列表 TODO 联调
export function getPlanDetailApiScenarioList(data: PlanDetailFeatureCaseListQueryParams) {
return MSR.post<CommonList<PlanDetailApiScenarioItem>>({ url: GetPlanDetailFeatureCaseListUrl, data });
}

View File

@ -98,6 +98,7 @@
specialHeight: number; // autoHeight
hideBack: boolean; //
autoHeight: boolean; //
autoWidth: boolean; //
otherWidth: number; //
headerMinWidth: number; //
minWidth: number; //
@ -121,6 +122,7 @@
specialHeight: 0,
hideBack: false,
autoHeight: false,
autoWidth: false,
hasBreadcrumb: false,
noContentPadding: false,
noBottomRadius: false,
@ -180,11 +182,12 @@
height: props.autoHeight ? 'auto' : `calc(100vh - ${cardOverHeight.value}px)`,
};
}
const width = props.otherWidth
? `calc(100vw - ${menuWidth.value}px - ${props.otherWidth}px)`
: `calc(100vw - ${menuWidth.value}px - 58px)`;
return {
overflow: 'auto',
width: props.otherWidth
? `calc(100vw - ${menuWidth.value}px - ${props.otherWidth}px)`
: `calc(100vw - ${menuWidth.value}px - 58px)`,
width: props.autoWidth ? 'auto' : width,
height: props.autoHeight ? 'auto' : `calc(100vh - ${cardOverHeight.value}px)`,
};
});

View File

@ -22,7 +22,7 @@
<div class="flex flex-1 items-center justify-between">
<div class="flex items-center">
<a-tooltip :disabled="!props.title" :content="props.title">
<span> {{ characterLimit(props.title) }}</span>
<span class="one-line-text max-w-[300px]"> {{ props.title }}</span>
</a-tooltip>
<slot name="headerLeft"></slot>

View File

@ -236,4 +236,43 @@ export interface ExecuteHistoryItem {
deleted: boolean;
}
// TODO: 联调
export interface PlanDetailApiCaseItem {
id: string;
num: string;
name: string;
moduleId: string;
versionName: string;
createUser: string;
createUserName: string;
lastExecResult: LastExecuteResults;
lastExecTime: number;
executeUser: string;
executeUserName: string;
bugCount: number;
customFields: customFieldsItem[]; // 自定义字段集合
caseId: string;
testPlanId: string;
lastExecResultReportId: string;
}
// TODO: 联调
export interface PlanDetailApiScenarioItem {
id: string;
num: string;
name: string;
moduleId: string;
versionName: string;
createUser: string;
createUserName: string;
lastExecResult: LastExecuteResults;
lastExecTime: number;
executeUser: string;
executeUserName: string;
bugCount: number;
customFields: customFieldsItem[]; // 自定义字段集合
caseId: string;
testPlanId: string;
lastExecResultReportId: string;
}
export default {};

View File

@ -1,5 +1,5 @@
<template>
<MsCard class="mb-[16px]" hide-back hide-footer auto-height no-content-padding hide-divider>
<MsCard v-if="!props.isDrawer" class="mb-[16px]" hide-back hide-footer auto-height no-content-padding hide-divider>
<template #headerLeft>
<div class="flex items-center font-medium">
<a-tooltip :content="detail.name" :mouse-enter-delay="300"
@ -8,33 +8,7 @@
</div>
</template>
<template #headerRight>
<a-popover position="bottom" content-class="response-popover-content">
<div>
<span class="text-[var(--color-text-4)]">{{ t('report.detail.api.executionTime') }}</span>
{{ detail.executeTime ? dayjs(detail.executeTime).format('YYYY-MM-DD HH:mm:ss') : '-' }}
<span class="text-[var(--color-text-4)]">{{ t('report.detail.api.executionTimeTo') }}</span>
{{ detail.endTime ? dayjs(detail.endTime).format('YYYY-MM-DD HH:mm:ss') : '-' }}
</div>
<template #content>
<div class="max-w-[400px] items-center gap-[8px] text-[14px]">
<div class="flex-shrink-0 text-[var(--color-text-4)]">{{ t('report.detail.api.executionTime') }}</div>
<div class="mt-2">
{{ dayjs(detail.executeTime).format('YYYY-MM-DD HH:mm:ss') }}
</div>
</div>
</template>
</a-popover>
<MsButton
v-if="hasAnyPermission(['PROJECT_TEST_PLAN_REPORT:READ+SHARE']) && !shareId"
type="icon"
status="secondary"
class="ml-4 !rounded-[var(--border-radius-small)]"
:loading="shareLoading"
@click="shareHandler"
>
<MsIcon type="icon-icon_share1" class="mr-2 font-[16px]" />
{{ t('common.share') }}
</MsButton>
<PlanDetailHeaderRight :share-id="shareId" :detail="detail" />
</template>
</MsCard>
<div class="analysis-wrapper">
@ -123,7 +97,7 @@
</div>
</div>
</div>
<MsCard class="mb-[16px]" simple auto-height>
<MsCard class="mb-[16px]" simple auto-height auto-width>
<div class="font-medium">{{ t('report.detail.reportSummary') }}</div>
<div
:class="`${hasAnyPermission(['PROJECT_TEST_PLAN_REPORT:READ+UPDATE']) && !shareId ? '' : 'cursor-not-allowed'}`"
@ -145,7 +119,7 @@
<a-button type="secondary" @click="handleCancel">{{ t('common.cancel') }}</a-button>
</div>
</MsCard>
<MsCard simple auto-height>
<MsCard simple auto-height auto-width>
<MsTab
v-model:active-key="activeTab"
:show-badge="false"
@ -164,38 +138,35 @@
import { useEventListener } from '@vueuse/core';
import { Message } from '@arco-design/web-vue';
import { cloneDeep } from 'lodash-es';
import dayjs from 'dayjs';
import MsChart from '@/components/pure/chart/index.vue';
import MsButton from '@/components/pure/ms-button/index.vue';
import MsCard from '@/components/pure/ms-card/index.vue';
import MsRichText from '@/components/pure/ms-rich-text/MsRichText.vue';
import MsTab from '@/components/pure/ms-tab/index.vue';
import PlanDetailHeaderRight from './planDetailHeaderRight.vue';
import SetReportChart from '@/views/api-test/report/component/case/setReportChart.vue';
import SingleStatusProgress from '@/views/test-plan/report/component/singleStatusProgress.vue';
import BugTable from '@/views/test-plan/report/detail/component/bugTable.vue';
import FeatureCaseTable from '@/views/test-plan/report/detail/component/featureCaseTable.vue';
import { editorUploadFile, planReportShare, updateReportDetail } from '@/api/modules/test-plan/report';
import { editorUploadFile, updateReportDetail } from '@/api/modules/test-plan/report';
import { PreviewEditorImageUrl } from '@/api/requrls/case-management/featureCase';
import { defaultReportDetail, statusConfig } from '@/config/testPlan';
import { useI18n } from '@/hooks/useI18n';
import useAppStore from '@/store/modules/app';
import { addCommasToNumber } from '@/utils';
import { hasAnyPermission } from '@/utils/permission';
import type { LegendData } from '@/models/apiTest/report';
import type { PlanReportDetail, StatusListType } from '@/models/testPlan/testPlanReport';
import { RouteEnum } from '@/enums/routeEnum';
import { getIndicators } from '@/views/api-test/report/utils';
const { t } = useI18n();
const route = useRoute();
const appStore = useAppStore();
const props = defineProps<{
detailInfo: PlanReportDetail;
isDrawer?: boolean;
}>();
const emit = defineEmits<{
@ -211,39 +182,7 @@
/**
* 分享share
*/
const shareLink = ref<string>('');
const shareId = ref<string>(route.query.shareId as string);
const shareLoading = ref<boolean>(false);
async function shareHandler() {
try {
const res = await planReportShare({
reportId: detail.value.id,
projectId: appStore.currentProjectId,
});
const { origin } = window.location;
shareLink.value = `${origin}/#/${RouteEnum.SHARE}/${RouteEnum.SHARE_REPORT_TEST_PLAN}${res.shareUrl}`;
if (navigator.clipboard) {
navigator.clipboard.writeText(shareLink.value).then(
() => {
Message.info(t('bugManagement.detail.shareTip'));
},
(e) => {
Message.error(e);
}
);
} else {
const input = document.createElement('input');
input.value = shareLink.value;
document.body.appendChild(input);
input.select();
document.execCommand('copy');
document.body.removeChild(input);
Message.info(t('bugManagement.detail.shareTip'));
}
} catch (error) {
console.log(error);
}
}
const legendData = ref<LegendData[]>([]);

View File

@ -0,0 +1,88 @@
<template>
<div class="flex items-center">
<a-popover position="bottom" content-class="response-popover-content">
<div>
<span class="text-[var(--color-text-4)]">{{ t('report.detail.api.executionTime') }}</span>
{{ props.detail.executeTime ? dayjs(props.detail.executeTime).format('YYYY-MM-DD HH:mm:ss') : '-' }}
<span class="text-[var(--color-text-4)]">{{ t('report.detail.api.executionTimeTo') }}</span>
{{ props.detail.endTime ? dayjs(props.detail.endTime).format('YYYY-MM-DD HH:mm:ss') : '-' }}
</div>
<template #content>
<div class="max-w-[400px] items-center gap-[8px] text-[14px]">
<div class="flex-shrink-0 text-[var(--color-text-4)]">{{ t('report.detail.api.executionTime') }}</div>
<div class="mt-2">
{{ dayjs(props.detail.executeTime).format('YYYY-MM-DD HH:mm:ss') }}
</div>
</div>
</template>
</a-popover>
<MsButton
v-if="hasAnyPermission(['PROJECT_TEST_PLAN_REPORT:READ+SHARE']) && !props.shareId"
type="icon"
status="secondary"
class="ml-4 !rounded-[var(--border-radius-small)]"
:loading="shareLoading"
@click="shareHandler"
>
<MsIcon type="icon-icon_share1" class="mr-2 font-[16px]" />
{{ t('common.share') }}
</MsButton>
</div>
</template>
<script setup lang="ts">
import { Message } from '@arco-design/web-vue';
import dayjs from 'dayjs';
import MsButton from '@/components/pure/ms-button/index.vue';
import { planReportShare } from '@/api/modules/test-plan/report';
import { useI18n } from '@/hooks/useI18n';
import useAppStore from '@/store/modules/app';
import { hasAnyPermission } from '@/utils/permission';
import type { PlanReportDetail } from '@/models/testPlan/testPlanReport';
import { RouteEnum } from '@/enums/routeEnum';
const props = defineProps<{
detail: PlanReportDetail;
shareId?: string;
}>();
const appStore = useAppStore();
const { t } = useI18n();
const shareLink = ref<string>('');
const shareLoading = ref<boolean>(false);
async function shareHandler() {
try {
const res = await planReportShare({
reportId: props.detail.id,
projectId: appStore.currentProjectId,
});
const { origin } = window.location;
shareLink.value = `${origin}/#/${RouteEnum.SHARE}/${RouteEnum.SHARE_REPORT_TEST_PLAN}${res.shareUrl}`;
if (navigator.clipboard) {
navigator.clipboard.writeText(shareLink.value).then(
() => {
Message.info(t('bugManagement.detail.shareTip'));
},
(e) => {
Message.error(e);
}
);
} else {
const input = document.createElement('input');
input.value = shareLink.value;
document.body.appendChild(input);
input.select();
document.execCommand('copy');
document.body.removeChild(input);
Message.info(t('bugManagement.detail.shareTip'));
}
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
}
</script>

View File

@ -39,6 +39,7 @@
type="icon-icon_take-action_outlined"
class="ml-[8px] cursor-pointer text-[rgb(var(--primary-5))]"
size="16"
@click="showReport(record)"
/>
</template>
<template #status="{ record }">
@ -78,6 +79,7 @@
</MsButton>
</template>
</MsBaseTable>
<ReportDrawer v-model:visible="reportVisible" :report-id="reportId" />
</div>
</template>
@ -99,12 +101,13 @@
import CaseLevel from '@/components/business/ms-case-associate/caseLevel.vue';
import ExecuteResult from '@/components/business/ms-case-associate/executeResult.vue';
import apiStatus from '@/views/api-test/components/apiStatus.vue';
import ReportDrawer from '@/views/test-plan/testPlan/detail/reportDrawer.vue';
import {
associationCaseToPlan,
batchDisassociateCase,
disassociateCase,
getPlanDetailFeatureCaseList,
getPlanDetailApiCaseList,
sortFeatureCase,
} from '@/api/modules/test-plan/testPlan';
import { useI18n } from '@/hooks/useI18n';
@ -115,7 +118,7 @@
import { hasAnyPermission } from '@/utils/permission';
import { DragSortParams, ModuleTreeNode } from '@/models/common';
import type { PlanDetailFeatureCaseItem, PlanDetailFeatureCaseListQueryParams } from '@/models/testPlan/testPlan';
import type { PlanDetailApiCaseItem, PlanDetailFeatureCaseListQueryParams } from '@/models/testPlan/testPlan';
import { LastExecuteResults } from '@/enums/caseEnum';
import { TableKeyEnum } from '@/enums/tableEnum';
import { FilterSlotNameEnum } from '@/enums/tableFilterEnum';
@ -266,7 +269,7 @@
},
]);
const tableProps = ref<Partial<MsTableProps<PlanDetailFeatureCaseItem>>>({
const tableProps = ref<Partial<MsTableProps<PlanDetailApiCaseItem>>>({
scroll: { x: '100%' },
tableKey: TableKeyEnum.TEST_PLAN_DETAIL_API_CASE,
showSetting: true,
@ -278,8 +281,7 @@
});
const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector } = useTable(
// TODO
getPlanDetailFeatureCaseList,
getPlanDetailApiCaseList,
tableProps.value,
(record) => {
return {
@ -393,6 +395,14 @@
});
}
//
const reportVisible = ref(false);
const reportId = ref('');
function showReport(record: PlanDetailApiCaseItem) {
reportVisible.value = true;
reportId.value = record.lastExecResultReportId; // TODO
}
const tableSelected = ref<(string | number)[]>([]); //
const batchParams = ref<BatchActionQueryParams>({
selectIds: [],
@ -425,7 +435,7 @@
}
//
async function handleCopyCase(record: PlanDetailFeatureCaseItem) {
async function handleCopyCase(record: PlanDetailApiCaseItem) {
try {
// TODO
await associationCaseToPlan({
@ -443,7 +453,7 @@
//
const disassociateLoading = ref(false);
async function handleDisassociateCase(record: PlanDetailFeatureCaseItem, done?: () => void) {
async function handleDisassociateCase(record: PlanDetailApiCaseItem, done?: () => void) {
try {
disassociateLoading.value = true;
// TODO

View File

@ -23,6 +23,9 @@
@selected-change="handleTableSelect"
@filter-change="getModuleCount"
>
<template #num="{ record }">
<MsButton type="text" @click="toDetail(record)">{{ record.num }}</MsButton>
</template>
<template #[FilterSlotNameEnum.CASE_MANAGEMENT_CASE_LEVEL]="{ filterContent }">
<CaseLevel :case-level="filterContent.value" />
</template>
@ -39,6 +42,7 @@
type="icon-icon_take-action_outlined"
class="ml-[8px] cursor-pointer text-[rgb(var(--primary-5))]"
size="16"
@click="showReport(record)"
/>
</template>
<template #status="{ record }">
@ -78,6 +82,7 @@
</MsButton>
</template>
</MsBaseTable>
<ReportDrawer v-model:visible="reportVisible" :report-id="reportId" />
</div>
</template>
@ -99,24 +104,27 @@
import CaseLevel from '@/components/business/ms-case-associate/caseLevel.vue';
import ExecuteResult from '@/components/business/ms-case-associate/executeResult.vue';
import apiStatus from '@/views/api-test/components/apiStatus.vue';
import ReportDrawer from '@/views/test-plan/testPlan/detail/reportDrawer.vue';
import {
associationCaseToPlan,
batchDisassociateCase,
disassociateCase,
getPlanDetailFeatureCaseList,
getPlanDetailApiScenarioList,
sortFeatureCase,
} from '@/api/modules/test-plan/testPlan';
import { useI18n } from '@/hooks/useI18n';
import useModal from '@/hooks/useModal';
import useOpenNewPage from '@/hooks/useOpenNewPage';
import useTableStore from '@/hooks/useTableStore';
import useAppStore from '@/store/modules/app';
import { characterLimit } from '@/utils';
import { hasAnyPermission } from '@/utils/permission';
import { DragSortParams, ModuleTreeNode } from '@/models/common';
import type { PlanDetailFeatureCaseItem, PlanDetailFeatureCaseListQueryParams } from '@/models/testPlan/testPlan';
import type { PlanDetailApiScenarioItem, PlanDetailFeatureCaseListQueryParams } from '@/models/testPlan/testPlan';
import { LastExecuteResults } from '@/enums/caseEnum';
import { ApiTestRouteEnum } from '@/enums/routeEnum';
import { TableKeyEnum } from '@/enums/tableEnum';
import { FilterSlotNameEnum } from '@/enums/tableFilterEnum';
@ -148,6 +156,7 @@
const appStore = useAppStore();
const tableStore = useTableStore();
const { openModal } = useModal();
const { openNewPage } = useOpenNewPage();
const keyword = ref('');
const moduleNamePath = computed(() => {
@ -160,6 +169,7 @@
const columns = computed<MsTableColumn>(() => [
{
title: 'ID',
slotName: 'num',
dataIndex: 'num',
sortIndex: 1,
sortable: {
@ -259,7 +269,7 @@
},
]);
const tableProps = ref<Partial<MsTableProps<PlanDetailFeatureCaseItem>>>({
const tableProps = ref<Partial<MsTableProps<PlanDetailApiScenarioItem>>>({
scroll: { x: '100%' },
tableKey: TableKeyEnum.TEST_PLAN_DETAIL_API_CASE,
showSetting: true,
@ -271,8 +281,7 @@
});
const { propsRes, propsEvent, loadList, setLoadListParams, resetSelector } = useTable(
// TODO
getPlanDetailFeatureCaseList,
getPlanDetailApiScenarioList,
tableProps.value,
(record) => {
return {
@ -386,6 +395,14 @@
});
}
//
const reportVisible = ref(false);
const reportId = ref('');
function showReport(record: PlanDetailApiScenarioItem) {
reportVisible.value = true;
reportId.value = record.lastExecResultReportId; // TODO
}
const tableSelected = ref<(string | number)[]>([]); //
const batchParams = ref<BatchActionQueryParams>({
selectIds: [],
@ -418,7 +435,7 @@
}
//
async function handleCopyCase(record: PlanDetailFeatureCaseItem) {
async function handleCopyCase(record: PlanDetailApiScenarioItem) {
try {
// TODO
await associationCaseToPlan({
@ -436,7 +453,7 @@
//
const disassociateLoading = ref(false);
async function handleDisassociateCase(record: PlanDetailFeatureCaseItem, done?: () => void) {
async function handleDisassociateCase(record: PlanDetailApiScenarioItem, done?: () => void) {
try {
disassociateLoading.value = true;
// TODO
@ -508,6 +525,13 @@
}
}
//
function toDetail(record: PlanDetailApiScenarioItem) {
openNewPage(ApiTestRouteEnum.API_TEST_SCENARIO, {
id: record.id,
});
}
onBeforeMount(() => {
loadCaseList();
});

View File

@ -16,14 +16,12 @@
</template>
<script setup lang="ts">
import { useRouter } from 'vue-router';
import MsButton from '@/components/pure/ms-button/index.vue';
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
import type { MsTableColumn } from '@/components/pure/ms-table/type';
import useTable from '@/components/pure/ms-table/useTable';
import useAppStore from '@/store/modules/app';
import useOpenNewPage from '@/hooks/useOpenNewPage';
import type { PlanDetailBugItem } from '@/models/testPlan/testPlan';
import { CaseManagementRouteEnum } from '@/enums/routeEnum';
@ -33,8 +31,7 @@
bugItem: PlanDetailBugItem;
}>();
const router = useRouter();
const appStore = useAppStore();
const { openNewPage } = useOpenNewPage();
const columns: MsTableColumn = [
{
@ -68,11 +65,9 @@
}
function goCaseDetail(id: string) {
window.open(
`${window.location.origin}#${
router.resolve({ name: CaseManagementRouteEnum.CASE_MANAGEMENT_CASE }).fullPath
}?id=${id}&orgId=${appStore.currentOrgId}&pId=${appStore.currentProjectId}`
);
openNewPage(CaseManagementRouteEnum.CASE_MANAGEMENT_CASE, {
id,
});
}
</script>

View File

@ -221,7 +221,7 @@
</template>
<script setup lang="ts">
import { useRoute, useRouter } from 'vue-router';
import { useRoute } from 'vue-router';
import { Message } from '@arco-design/web-vue';
import dayjs from 'dayjs';
@ -251,6 +251,7 @@
} from '@/api/modules/test-plan/testPlan';
import { testPlanDefaultDetail } from '@/config/testPlan';
import { useI18n } from '@/hooks/useI18n';
import useOpenNewPage from '@/hooks/useOpenNewPage';
import useAppStore from '@/store/modules/app';
import { hasAnyPermission } from '@/utils/permission';
@ -267,8 +268,8 @@
const { t } = useI18n();
const route = useRoute();
const router = useRouter();
const appStore = useAppStore();
const { openNewPage } = useOpenNewPage();
const planDetail = ref<TestPlanDetail>({
...testPlanDefaultDetail,
@ -334,11 +335,9 @@
}
function goCaseDetail() {
window.open(
`${window.location.origin}#${
router.resolve({ name: CaseManagementRouteEnum.CASE_MANAGEMENT_CASE }).fullPath
}?id=${activeCaseId.value}&orgId=${appStore.currentOrgId}&pId=${appStore.currentProjectId}`
);
openNewPage(CaseManagementRouteEnum.CASE_MANAGEMENT_CASE, {
id: activeCaseId.value,
});
}
const caseDetail = ref<any>({});

View File

@ -0,0 +1,70 @@
<template>
<MsDrawer
v-model:visible="innerVisible"
:title="detail.name"
:width="1200"
:footer="false"
class="report-drawer"
unmount-on-close
no-content-padding
show-full-screen
>
<template #tbutton>
<PlanDetailHeaderRight share-id="" :detail="detail" />
</template>
<PlanDetail is-drawer :detail-info="detail" @update-success="getDetail()" />
</MsDrawer>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import { cloneDeep } from 'lodash-es';
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
import PlanDetail from '@/views/test-plan/report/detail/component/planDetail.vue';
import PlanDetailHeaderRight from '@/views/test-plan/report/detail/component/planDetailHeaderRight.vue';
import { getReportDetail } from '@/api/modules/test-plan/report';
import { defaultReportDetail } from '@/config/testPlan';
import type { PlanReportDetail } from '@/models/testPlan/testPlanReport';
const props = defineProps<{
reportId: string;
}>();
const innerVisible = defineModel<boolean>('visible', {
required: true,
});
const detail = ref<PlanReportDetail>(cloneDeep(defaultReportDetail));
async function getDetail() {
try {
detail.value = await getReportDetail(props.reportId);
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
}
watch(
() => innerVisible.value,
async (val) => {
if (val) {
await getDetail();
}
}
);
</script>
<style lang="less">
.report-drawer {
.ms-drawer-body {
padding: 16px;
background: var(--color-text-n9);
}
.right-operation-button-icon {
margin-left: 4px;
}
}
</style>