feat(全局组件): ms-case-associate关联用例业务组件&ms-prev-next-button快速切换上下数据按钮组件&ms-color-line彩色进度条组件&部分组件调整

This commit is contained in:
baiqi 2023-11-28 16:35:06 +08:00 committed by 刘瑞斌
parent f69d3eaec3
commit a33a0580ff
49 changed files with 1109 additions and 225 deletions

View File

@ -1,7 +1,7 @@
@font-face {
font-family: iconfont; /* Project id 3462279 */
src: url('iconfont.woff2?t=1700905969825') format('woff2'), url('iconfont.woff?t=1700905969825') format('woff'),
url('iconfont.ttf?t=1700905969825') format('truetype'), url('iconfont.svg?t=1700905969825#iconfont') format('svg');
src: url('iconfont.woff2?t=1700812414583') format('woff2'), url('iconfont.woff?t=1700812414583') format('woff'),
url('iconfont.ttf?t=1700812414583') format('truetype'), url('iconfont.svg?t=1700812414583#iconfont') format('svg');
}
.iconfont {
font-size: 16px;
@ -793,9 +793,6 @@
.icon-icon_new-item_outlined::before {
content: '\e695';
}
.icon-icon_logs_outlined-1::before {
content: '\e697';
}
.icon-icon_refresh_outlined::before {
content: '\e698';
}

View File

@ -521,6 +521,7 @@
}
.arco-popover-content {
font-size: 12px;
line-height: 16px;
}
/** 链接 **/
@ -670,6 +671,16 @@
align-items: center;
}
}
&-size-mini {
font-size: 12px;
line-height: 16px;
.ms-pagination-item {
height: 24px;
}
.ms-pagination-jumper-input {
padding: 0;
}
}
}
/** 折叠面板样式 **/

View File

@ -14,6 +14,7 @@ body {
@apply m-0 h-full w-full p-0;
font-size: 14px;
line-height: 22px;
background-color: var(--color-bg-1);
-moz-osx-font-smoothing: grayscale;
-webkit-font-smoothing: antialiased;
@ -80,6 +81,7 @@ body {
.sub-text {
font-size: 12px;
color: rgb(var(--color-text-4));
line-height: 16px;
}
/* 单航文本缩略 */

View File

@ -1,6 +1,6 @@
<template>
<a-breadcrumb v-if="appStore.breadcrumbList.length > 0" class="z-10 mb-[-8px] mt-[8px]">
<a-breadcrumb-item v-for="crumb of appStore.breadcrumbList" :key="crumb.name" @click="jumpTo(crumb.name)">
<a-breadcrumb-item v-for="crumb of appStore.breadcrumbList" :key="crumb.name" @click="jumpTo(crumb)">
{{ isEdit ? t(crumb.editLocale || crumb.locale) : t(crumb.locale) }}
</a-breadcrumb-item>
</a-breadcrumb>
@ -8,12 +8,14 @@
<script setup lang="ts">
import { ref } from 'vue';
import { RouteRecordName, useRoute, useRouter } from 'vue-router';
import { useRoute, useRouter } from 'vue-router';
import { useI18n } from '@/hooks/useI18n';
import { useAppStore } from '@/store';
import { listenerRouteChange } from '@/utils/route-listener';
import { BreadcrumbItem } from './types';
const appStore = useAppStore();
const { t } = useI18n();
const router = useRouter();
@ -25,7 +27,7 @@
*/
listenerRouteChange((newRoute) => {
const { name, meta } = newRoute;
isEdit.value = false;
//
if (name === appStore.currentTopMenu.name) {
appStore.setBreadcrumbList(appStore.currentTopMenu?.meta?.breadcrumbs);
@ -35,7 +37,7 @@
const currentRoute = children.length > 0 ? children.find((e) => e.name === name) : null;
const currentBreads = currentRoute?.meta?.breadcrumbs || meta.breadcrumbs;
appStore.setBreadcrumbList(currentBreads);
//
//
const editTag = currentBreads && currentBreads[currentBreads.length - 1].editTag;
setTimeout(() => {
// 使nextTick使
@ -46,8 +48,18 @@
}
}, true);
function jumpTo(name: RouteRecordName) {
router.push({ name, query: route.query });
function jumpTo(crumb: BreadcrumbItem) {
if (crumb.isBack && window.history.state.back) {
router.back();
} else {
const query: Record<string, any> = {};
if (crumb.query) {
crumb.query.forEach((key) => {
query[key] = route.query[key];
});
}
router.replace({ name: crumb.name, query });
}
}
</script>

View File

@ -3,6 +3,8 @@ import type { RouteRecordName } from 'vue-router';
export interface BreadcrumbItem {
name: RouteRecordName; // 路由名称
locale: string; // 国际化语言单词
isBack?: boolean; // 是否为返回上一个历史记录(当遇到父级页面也是携带 ID 参数打开的详情页面包屑时,设置 true 可以使面包屑跳转变成回退)
editTag?: string; // 编辑标识,指路由地址参数,面包屑组件会根据此参数判断是否为编辑页面
editLocale?: string; // 编辑国际化语言单词
query?: string[]; // 路由地址参数,面包屑组件会根据此参数拼接路由地址
}

View File

@ -0,0 +1,43 @@
<template>
<div
class="mr-[4px] h-[8px] w-[8px] rounded-full"
:style="{
backgroundColor: caseLevelMap[props.caseLevel].bgColor,
border: `1px solid ${caseLevelMap[props.caseLevel].borderColor}`,
}"
></div>
{{ caseLevelMap[props.caseLevel].label }}
</template>
<script setup lang="ts">
import { CaseLevel } from './types';
const props = defineProps<{
caseLevel: CaseLevel;
}>();
const caseLevelMap = {
0: {
label: 'P0',
bgColor: 'rgb(var(--danger-2))',
borderColor: 'rgb(var(--danger-6))',
},
1: {
label: 'P1',
bgColor: 'rgb(var(--warning-2))',
borderColor: 'rgb(var(--warning-6))',
},
2: {
label: 'P2',
bgColor: 'rgb(var(--link-2))',
borderColor: 'rgb(var(--link-5))',
},
3: {
label: 'P3',
bgColor: 'var(--color-text-n8)',
borderColor: 'var(--color-text-brand)',
},
} as const;
</script>
<style lang="less" scoped></style>

View File

@ -0,0 +1,502 @@
<template>
<MsDrawer
v-model:visible="innerVisible"
:title="t('ms.case.associate.title')"
:width="1200"
:footer="false"
no-content-padding
>
<div class="flex h-full">
<div class="w-[292px] border-r border-[var(--color-text-n8)] p-[16px]">
<MsProjectSelect v-model:project="innerProject" class="mb-[16px]" />
<a-input
v-model:model-value="moduleKeyword"
:placeholder="t('caseManagement.caseReview.folderSearchPlaceholder')"
allow-clear
class="mb-[16px]"
/>
<div class="folder">
<div :class="getFolderClass('all')" @click="setActiveFolder('all')">
<MsIcon type="icon-icon_folder_filled1" class="folder-icon" />
<div class="folder-name">{{ t('caseManagement.caseReview.allReviews') }}</div>
<div class="folder-count">({{ allFileCount }})</div>
</div>
</div>
<a-divider class="my-[8px]" />
<a-spin class="w-full" :loading="moduleLoading">
<MsTree
v-model:selected-keys="selectedModuleKeys"
:data="folderTree"
:keyword="moduleKeyword"
:empty-text="t('caseManagement.caseReview.noReviews')"
:virtual-list-props="virtualListProps"
:field-names="{
title: 'name',
key: 'id',
children: 'children',
count: 'count',
}"
block-node
title-tooltip-position="left"
@select="folderNodeSelect"
>
<template #title="nodeData">
<div class="inline-flex w-full">
<div class="one-line-text w-[calc(100%-32px)] text-[var(--color-text-1)]">{{ nodeData.name }}</div>
<div class="ml-[4px] text-[var(--color-text-4)]">({{ nodeData.count || 0 }})</div>
</div>
</template>
</MsTree>
</a-spin>
</div>
<div class="flex w-[calc(100%-293px)] flex-col p-[16px]">
<div class="flex items-center justify-between">
<div class="flex items-center">
<div class="mr-[4px] text-[var(--color-text-1)]">{{ activeFolderName }}</div>
<div class="text-[var(--color-text-4)]">({{ activeFolderName }})</div>
</div>
<div class="flex items-center gap-[8px]">
<a-select
v-model:model-value="version"
:options="versionOptions"
:placeholder="t('ms.case.associate.versionPlaceholder')"
class="w-[200px]"
allow-clear
/>
<a-input-search
v-model="keyword"
:placeholder="t('ms.case.associate.searchPlaceholder')"
allow-clear
class="w-[200px]"
@press-enter="searchCase"
@search="searchCase"
/>
<a-button type="outline" class="arco-btn-outline--secondary px-[8px]">
<MsIcon type="icon-icon-filter" class="mr-[4px] text-[var(--color-text-4)]" />
<div class="text-[var(--color-text-4)]">{{ t('common.filter') }}</div>
</a-button>
</div>
</div>
<ms-base-table v-bind="propsRes" no-disable v-on="propsEvent">
<template #caseLevel="{ record }">
<caseLevel :case-level="record.caseLevel" />
</template>
</ms-base-table>
<div class="footer">
<div class="flex flex-1 items-center">
<slot name="footerLeft"></slot>
</div>
<div class="flex items-center">
<slot name="footerRight">
<a-button type="secondary" :disabled="loading" class="mr-[12px]" @click="cancel">{{
t('common.cancel')
}}</a-button>
<a-button
type="primary"
:loading="loading"
:disabled="propsRes.selectedKeys.size === 0 || props.okButtonDisabled"
@click="handleConfirm"
>
{{ t('ms.case.associate.associate') }}
</a-button>
</slot>
</div>
</div>
</div>
</div>
</MsDrawer>
</template>
<script setup lang="ts">
import { computed, onBeforeMount, ref, watch } from 'vue';
import { Message } from '@arco-design/web-vue';
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
import MsBaseTable from '@/components/pure/ms-table/base-table.vue';
import { MsTableColumn } from '@/components/pure/ms-table/type';
import useTable from '@/components/pure/ms-table/useTable';
import MsProjectSelect from '@/components/business/ms-project-select/index.vue';
import MsTree from '@/components/business/ms-tree/index.vue';
import type { MsTreeNodeData } from '@/components/business/ms-tree/types';
import caseLevel from './caseLevel.vue';
import { getModules } from '@/api/modules/project-management/fileManagement';
import { useI18n } from '@/hooks/useI18n';
import useAppStore from '@/store/modules/app';
import { mapTree } from '@/utils';
import { ModuleTreeNode } from '@/models/projectManagement/file';
const props = defineProps<{
visible: boolean;
project: string;
modulesCount?: Record<string, number>; //
okButtonDisabled?: boolean; //
}>();
const emit = defineEmits<{
(e: 'update:visible', val: boolean): void;
(e: 'update:project', val: string): void;
(e: 'init', val: string[]): void;
(e: 'folderNodeSelect', ids: (string | number)[], springIds: string[]): void;
(e: 'success', val: string[]): void;
(e: 'close'): void;
}>();
const appStore = useAppStore();
const { t } = useI18n();
const virtualListProps = computed(() => {
return {
height: 'calc(100vh - 251px)',
};
});
const innerVisible = ref(props.visible);
const innerProject = ref(props.project);
watch(
() => props.visible,
(val) => {
innerVisible.value = val;
}
);
watch(
() => innerVisible.value,
(val) => {
if (!val) {
emit('update:visible', false);
}
}
);
watch(
() => props.project,
(val) => {
innerProject.value = val;
}
);
watch(
() => innerProject.value,
(val) => {
emit('update:project', val);
}
);
const activeFolder = ref('all');
const activeFolderName = ref(t('ms.case.associate.allCase'));
const allFileCount = ref(0);
function getFolderClass(id: string) {
return activeFolder.value === id ? 'folder-text folder-text--active' : 'folder-text';
}
const moduleKeyword = ref('');
const folderTree = ref<ModuleTreeNode[]>([]);
const moduleLoading = ref(false);
const selectedModuleKeys = ref<string[]>([]);
function setActiveFolder(id: string) {
activeFolder.value = id;
activeFolderName.value = t('ms.case.associate.allCase');
selectedModuleKeys.value = [];
emit('folderNodeSelect', [id], []);
}
/**
* 初始化模块树
* @param isSetDefaultKey 是否设置第一个节点为选中节点
*/
async function initModules(isSetDefaultKey = false) {
try {
moduleLoading.value = true;
const res = await getModules(appStore.currentProjectId);
folderTree.value = res;
if (isSetDefaultKey) {
selectedModuleKeys.value = [folderTree.value[0].id];
activeFolderName.value = folderTree.value[0].name;
const offspringIds: string[] = [];
mapTree(folderTree.value[0].children || [], (e) => {
offspringIds.push(e.id);
return e;
});
emit('folderNodeSelect', selectedModuleKeys.value, offspringIds);
}
emit(
'init',
folderTree.value.map((e) => e.name)
);
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
} finally {
moduleLoading.value = false;
}
}
/**
* 处理文件夹树节点选中事件
*/
function folderNodeSelect(_selectedKeys: (string | number)[], node: MsTreeNodeData) {
selectedModuleKeys.value = _selectedKeys as string[];
activeFolder.value = node.id;
activeFolderName.value = node.name;
const offspringIds: string[] = [];
mapTree(node.children || [], (e) => {
offspringIds.push(e.id);
return e;
});
emit('folderNodeSelect', _selectedKeys, offspringIds);
}
onBeforeMount(() => {
initModules();
});
/**
* 初始化模块文件数量
*/
watch(
() => props.modulesCount,
(obj) => {
folderTree.value = mapTree<ModuleTreeNode>(folderTree.value, (node) => {
return {
...node,
count: obj?.[node.id] || 0,
};
});
}
);
const keyword = ref('');
const version = ref('');
const versionOptions = ref([
{
label: '全部',
value: 'all',
},
{
label: '版本1',
value: '1',
},
{
label: '版本2',
value: '2',
},
]);
const columns: MsTableColumn = [
{
title: 'ID',
dataIndex: 'id',
sortIndex: 1,
showTooltip: true,
sortable: {
sortDirections: ['ascend', 'descend'],
},
width: 90,
},
{
title: 'ms.case.associate.caseName',
dataIndex: 'name',
sortable: {
sortDirections: ['ascend', 'descend'],
},
showTooltip: true,
width: 200,
},
{
title: 'ms.case.associate.caseLevel',
dataIndex: 'caseLevel',
slotName: 'caseLevel',
width: 90,
},
{
title: 'ms.case.associate.version',
slotName: 'version',
width: 80,
},
{
title: 'ms.case.associate.tags',
dataIndex: 'tags',
isTag: true,
},
];
const { propsRes, propsEvent, loadList, setLoadListParams } = useTable(
() =>
Promise.resolve({
list: [
{
id: 'ded3d43',
name: '测试评审1',
creator: '张三',
reviewer: '李四',
module: '模块1',
caseLevel: 0, //
caseCount: 100,
passCount: 0,
failCount: 10,
reviewCount: 20,
reviewingCount: 25,
tags: ['标签1', '标签2'],
type: 'single',
desc: 'douifd9304',
cycle: [1700200794229, 1700200994229],
},
{
id: 'g545hj4',
name: '测试评审2',
creator: '张三',
reviewer: '李四',
module: '模块1',
caseLevel: 1, //
caseCount: 105,
passCount: 50,
failCount: 10,
reviewCount: 20,
reviewingCount: 25,
tags: ['标签1', '标签2'],
type: 'single',
desc: 'douifd9304',
cycle: [1700200794229, 1700200994229],
},
{
id: 'hj65b54',
name: '测试评审3',
creator: '张三',
reviewer: '李四',
module: '模块1',
caseLevel: 2, //
caseCount: 125,
passCount: 70,
failCount: 10,
reviewCount: 20,
reviewingCount: 25,
passRate: '80%',
tags: ['标签1', '标签2'],
type: 'single',
desc: 'douifd9304',
cycle: [1700200794229, 1700200994229],
},
{
id: 'wefwefw',
name: '测试评审4',
creator: '张三',
reviewer: '李四',
module: '模块1',
caseLevel: 3, //
caseCount: 130,
passCount: 70,
failCount: 10,
reviewCount: 0,
reviewingCount: 50,
passRate: '80%',
tags: ['标签1', '标签2'],
type: 'single',
desc: 'douifd9304',
cycle: [1700200794229, 1700200994229],
},
],
current: 1,
pageSize: 10,
total: 2,
}),
{
columns,
scroll: {
x: '100%',
},
showSetting: false,
selectable: true,
showSelectAll: true,
},
(item) => {
return {
...item,
tags: item.tags?.map((e: string) => ({ id: e, name: e })) || [],
};
}
);
function searchCase() {
setLoadListParams({
version: version.value,
keyword: keyword.value,
});
loadList();
}
onBeforeMount(() => {
searchCase();
});
const loading = ref(false);
async function handleConfirm() {
try {
loading.value = true;
Message.success(t('ms.case.associate.associateSuccess'));
innerVisible.value = false;
emit('success', Array.from(propsRes.value.selectedKeys));
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
} finally {
loading.value = false;
}
}
function cancel() {
innerVisible.value = false;
emit('close');
}
defineExpose({
initModules,
});
</script>
<style lang="less" scoped>
.folder {
@apply flex cursor-pointer items-center justify-between;
padding: 8px 4px;
border-radius: var(--border-radius-small);
&:hover {
background-color: rgb(var(--primary-1));
}
.folder-text {
@apply flex cursor-pointer items-center;
.folder-icon {
margin-right: 4px;
color: var(--color-text-4);
}
.folder-name {
color: var(--color-text-1);
}
.folder-count {
margin-left: 4px;
color: var(--color-text-4);
}
}
.folder-text--active {
.folder-icon,
.folder-name,
.folder-count {
color: rgb(var(--primary-5));
}
}
}
.footer {
@apply flex items-center justify-between;
margin: auto -16px -16px;
padding: 12px 16px;
box-shadow: 0 -1px 4px 0 rgb(31 35 41 / 10%);
}
</style>

View File

@ -0,0 +1,12 @@
export default {
'ms.case.associate.title': 'Associated use cases',
'ms.case.associate.associate': 'associate',
'ms.case.associate.allCase': 'All use cases',
'ms.case.associate.caseName': 'Use case name',
'ms.case.associate.caseLevel': 'Use case levels',
'ms.case.associate.version': 'Version',
'ms.case.associate.versionPlaceholder': 'Default latest version',
'ms.case.associate.tags': 'Tag',
'ms.case.associate.searchPlaceholder': 'Search by ID or name',
'ms.case.associate.associateSuccess': 'Association successful',
};

View File

@ -0,0 +1,12 @@
export default {
'ms.case.associate.title': '关联用例',
'ms.case.associate.associate': '关联',
'ms.case.associate.allCase': '全部用例',
'ms.case.associate.caseName': '用例名称',
'ms.case.associate.caseLevel': '用例等级',
'ms.case.associate.version': '版本',
'ms.case.associate.versionPlaceholder': '默认最新版本',
'ms.case.associate.tags': '标签',
'ms.case.associate.searchPlaceholder': '通过 ID 或名称搜索',
'ms.case.associate.associateSuccess': '关联成功',
};

View File

@ -0,0 +1 @@
export type CaseLevel = 0 | 1 | 2 | 3;

View File

@ -10,40 +10,18 @@
<template #title>
<div class="flex w-full items-center">
{{ props.title }}
<a-tooltip
:content="activeDetailIsFirst ? t('ms.detail.drawer.noPrev') : t('ms.detail.drawer.prev')"
:mouse-enter-delay="300"
mini
>
<a-button
type="outline"
size="mini"
class="arco-btn-outline--secondary ml-[16px] mr-[4px]"
:disabled="activeDetailIsFirst || loading"
@click="openPrevDetail"
>
<template #icon>
<icon-left />
</template>
</a-button>
</a-tooltip>
<a-tooltip
:content="activeDetailIsLast ? t('ms.detail.drawer.noNext') : t('ms.detail.drawer.next')"
:mouse-enter-delay="300"
mini
>
<a-button
type="outline"
size="mini"
class="arco-btn-outline--secondary"
:disabled="activeDetailIsLast || loading"
@click="openNextDetail"
>
<template #icon>
<icon-right />
</template>
</a-button>
</a-tooltip>
<MsPrevNextButton
ref="prevNextButtonRef"
v-model:loading="loading"
class="ml-[16px]"
:page-change="props.pageChange"
:pagination="props.pagination"
:get-detail-func="props.getDetailFunc"
:detail-id="props.detailId"
:detail-index="props.detailIndex"
:table-data="props.tableData"
@loaded="(e) => emit('loaded', e)"
/>
<div class="ml-auto flex items-center">
<slot name="titleRight" :loading="loading" :detail="detail"></slot>
</div>
@ -54,12 +32,9 @@
</template>
<script setup lang="ts">
import { computed, ref, watch } from 'vue';
import MsDrawer from '@/components/pure/ms-drawer/index.vue';
import type { MsPaginationI } from '@/components/pure/ms-table/type';
import { useI18n } from '@/hooks/useI18n';
import MsPrevNextButton from '@/components/business/ms-prev-next-button/index.vue';
const props = defineProps<{
visible: boolean;
@ -68,13 +43,14 @@
detailId: string; // id
detailIndex: number; //
tableData: any[]; //
pagination?: MsPaginationI; //
pagination: MsPaginationI; //
pageChange: (page: number) => Promise<void>; //
getDetailFunc: (id: string) => Promise<any>; //
}>();
const emit = defineEmits(['update:visible', 'loaded']);
const { t } = useI18n();
const prevNextButtonRef = ref<InstanceType<typeof MsPrevNextButton>>();
const innerVisible = ref(false);
@ -95,102 +71,26 @@
const loading = ref(false);
const detail = ref<any>({});
const activeDetailId = ref<string>(props.detailId);
async function initDetail() {
try {
loading.value = true;
detail.value = await props.getDetailFunc(activeDetailId.value);
emit('loaded', detail.value);
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
} finally {
loading.value = false;
}
function initDetail() {
prevNextButtonRef.value?.initDetail();
}
watch(
() => props.detailId,
(val) => {
activeDetailId.value = val;
}
);
const activeDetailIndex = ref(props.detailIndex);
watch(
() => props.detailIndex,
(val) => {
activeDetailIndex.value = val;
}
);
// 01
const activeDetailIsFirst = computed(() => activeDetailIndex.value === 0 && props.pagination?.current === 1);
const activeDetailIsLast = computed(
// (-1)*+
() =>
activeDetailIndex.value === props.tableData.length - 1 &&
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
(props.pagination!.current - 1) * props.pagination!.pageSize + (activeDetailIndex.value + 1) >=
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
props.pagination!.total
);
async function openPrevDetail() {
if (!activeDetailIsFirst.value) {
//
if (activeDetailIndex.value === 0 && props.pagination) {
try {
//
loading.value = true;
await props.pageChange(props.pagination.current - 1);
activeDetailId.value = props.tableData[props.tableData.length - 1].id;
activeDetailIndex.value = props.tableData.length - 1;
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
loading.value = false;
}
} else {
//
activeDetailId.value = props.tableData[activeDetailIndex.value - 1].id;
activeDetailIndex.value -= 1;
}
initDetail();
}
function openPrevDetail() {
prevNextButtonRef.value?.openPrevDetail();
}
async function openNextDetail() {
if (!activeDetailIsLast.value) {
//
if (activeDetailIndex.value === props.tableData.length - 1 && props.pagination) {
try {
//
loading.value = true;
await props.pageChange(props.pagination.current + 1);
activeDetailId.value = props.tableData[0].id;
activeDetailIndex.value = 0;
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
loading.value = false;
}
} else {
//
activeDetailId.value = props.tableData[activeDetailIndex.value + 1].id;
activeDetailIndex.value += 1;
}
initDetail();
}
function openNextDetail() {
prevNextButtonRef.value?.openNextDetail();
}
watch(
() => innerVisible.value,
(val) => {
if (val) {
initDetail();
nextTick(() => {
// prevNextButtonRef
initDetail();
});
}
}
);

View File

@ -1,6 +1 @@
export default {
'ms.detail.drawer.prev': 'Prev',
'ms.detail.drawer.noPrev': 'Currently the first',
'ms.detail.drawer.next': 'Next',
'ms.detail.drawer.noNext': 'Currently the last one',
};
export default {};

View File

@ -1,6 +1 @@
export default {
'ms.detail.drawer.prev': '上一个',
'ms.detail.drawer.noPrev': '当前已是第一个',
'ms.detail.drawer.next': '下一个',
'ms.detail.drawer.noNext': '当前已是最后一个',
};
export default {};

View File

@ -1,15 +1,15 @@
<template>
<div class="filter-panel">
<div class="mb-4 flex items-center justify-between">
<div class="condition-text">{{ t('featureTest.featureCase.setFilterCondition') }}</div>
<div class="condition-text">{{ t('caseManagement.featureCase.setFilterCondition') }}</div>
<div>
<span class="condition-text">{{ t('featureTest.featureCase.followingCondition') }}</span>
<span class="condition-text">{{ t('caseManagement.featureCase.followingCondition') }}</span>
<a-select v-model="filterConditions.unit" class="mx-4 w-[68px]" size="small">
<a-option v-for="version of conditionOptions" :key="version.id" :value="version.value">{{
version.name
}}</a-option>
</a-select>
<span class="condition-text">{{ t('featureTest.featureCase.condition') }}</span>
<span class="condition-text">{{ t('caseManagement.featureCase.condition') }}</span>
</div>
</div>
<div v-for="(formItem, index) in formModels" :key="index" class="mb-[8px]">

View File

@ -1,5 +1,5 @@
<template>
<div class="mt-[4px] flex w-full items-center text-[12px] text-[var(--color-text-4)]">
<div class="mt-[4px] flex w-full items-center text-[12px] leading-[16px] text-[var(--color-text-4)]">
{{ props.text }}
<MsIcon
v-if="props.showFillIcon"

View File

@ -0,0 +1,173 @@
<template>
<div>
<a-tooltip
:content="activeDetailIsFirst ? t('ms.prevNextButton.noPrev') : t('ms.prevNextButton.prev')"
:mouse-enter-delay="300"
mini
>
<a-button
type="outline"
size="mini"
class="arco-btn-outline--secondary mr-[4px]"
:disabled="activeDetailIsFirst || innerLoading"
@click="openPrevDetail"
>
<template #icon>
<icon-left />
</template>
</a-button>
</a-tooltip>
<a-tooltip
:content="activeDetailIsLast ? t('ms.prevNextButton.noNext') : t('ms.prevNextButton.next')"
:mouse-enter-delay="300"
mini
>
<a-button
type="outline"
size="mini"
class="arco-btn-outline--secondary"
:disabled="activeDetailIsLast || innerLoading"
@click="openNextDetail"
>
<template #icon>
<icon-right />
</template>
</a-button>
</a-tooltip>
</div>
</template>
<script setup lang="ts">
import { MsPaginationI } from '@/components/pure/ms-table/type';
import { useI18n } from '@/hooks/useI18n';
const props = defineProps<{
loading: boolean;
detailId: string; // id
detailIndex: number; //
tableData: any[]; //
pagination: MsPaginationI; //
pageChange: (page: number) => Promise<void>; //
getDetailFunc: (id: string) => Promise<any>; //
}>();
const emit = defineEmits(['update:loading', 'loaded']);
const { t } = useI18n();
const innerLoading = ref(false);
watch(
() => props.loading,
(val) => {
innerLoading.value = val;
}
);
watch(
() => innerLoading.value,
(val) => {
emit('update:loading', val);
}
);
const activeDetailId = ref<string>(props.detailId);
watch(
() => props.detailId,
(val) => {
activeDetailId.value = val;
}
);
const activeDetailIndex = ref(props.detailIndex);
watch(
() => props.detailIndex,
(val) => {
activeDetailIndex.value = val;
}
);
async function initDetail() {
try {
innerLoading.value = true;
const res = await props.getDetailFunc(activeDetailId.value);
emit('loaded', res);
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
} finally {
innerLoading.value = false;
}
}
// 01
const activeDetailIsFirst = computed(() => activeDetailIndex.value === 0 && props.pagination.current === 1);
const activeDetailIsLast = computed(
// (-1)*+
() =>
activeDetailIndex.value === props.tableData.length - 1 &&
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
(props.pagination.current - 1) * props.pagination.pageSize + (activeDetailIndex.value + 1) >=
// eslint-disable-next-line @typescript-eslint/no-non-null-assertion
props.pagination.total
);
async function openPrevDetail() {
if (!activeDetailIsFirst.value) {
//
if (activeDetailIndex.value === 0 && props.pagination) {
try {
//
innerLoading.value = true;
await props.pageChange(props.pagination.current - 1);
activeDetailId.value = props.tableData[props.tableData.length - 1].id;
activeDetailIndex.value = props.tableData.length - 1;
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
innerLoading.value = false;
}
} else {
//
activeDetailId.value = props.tableData[activeDetailIndex.value - 1].id;
activeDetailIndex.value -= 1;
}
initDetail();
}
}
async function openNextDetail() {
if (!activeDetailIsLast.value) {
//
if (activeDetailIndex.value === props.tableData.length - 1 && props.pagination) {
try {
//
innerLoading.value = true;
await props.pageChange(props.pagination.current + 1);
activeDetailId.value = props.tableData[0].id;
activeDetailIndex.value = 0;
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
innerLoading.value = false;
}
} else {
//
activeDetailId.value = props.tableData[activeDetailIndex.value + 1].id;
activeDetailIndex.value += 1;
}
initDetail();
}
}
defineExpose({
initDetail,
openPrevDetail,
openNextDetail,
});
</script>
<style lang="less" scoped></style>

View File

@ -0,0 +1,6 @@
export default {
'ms.prevNextButton.prev': 'Prev',
'ms.prevNextButton.noPrev': 'Currently the first',
'ms.prevNextButton.next': 'Next',
'ms.prevNextButton.noNext': 'Currently the last one',
};

View File

@ -0,0 +1,6 @@
export default {
'ms.prevNextButton.prev': '上一个',
'ms.prevNextButton.noPrev': '当前已是第一个',
'ms.prevNextButton.next': '下一个',
'ms.prevNextButton.noNext': '当前已是最后一个',
};

View File

@ -0,0 +1,62 @@
<template>
<a-select class="w-[260px]" :default-value="innerProject" allow-search @change="selectProject">
<template #arrow-icon>
<icon-caret-down />
</template>
<a-tooltip v-for="item of projectList" :key="item.id" :mouse-enter-delay="500" :content="item.name">
<a-option :value="item.id" :class="item.id === innerProject ? 'arco-select-option-selected' : ''">
{{ item.name }}
</a-option>
</a-tooltip>
</a-select>
</template>
<script setup lang="ts">
import { getProjectList } from '@/api/modules/project-management/project';
import useAppStore from '@/store/modules/app';
import type { ProjectListItem } from '@/models/setting/project';
const props = defineProps<{
project: string;
}>();
const emit = defineEmits<{
(e: 'update:project', val: string): void;
}>();
const appStore = useAppStore();
const projectList = ref<ProjectListItem[]>([]);
const innerProject = ref(props.project || appStore.currentProjectId);
watch(
() => props.project,
(val) => {
innerProject.value = val;
}
);
watch(
() => innerProject.value,
(val) => {
emit('update:project', val);
}
);
onBeforeMount(async () => {
try {
const res = await getProjectList(appStore.getCurrentOrgId);
projectList.value = res;
} catch (error) {
// eslint-disable-next-line no-console
console.log(error);
}
});
function selectProject(
value: string | number | boolean | Record<string, any> | (string | number | boolean | Record<string, any>)[]
) {
emit('update:project', value as string);
}
</script>
<style lang="less" scoped></style>

View File

@ -95,11 +95,17 @@ export default defineComponent(
}
);
/**
*
* @param val
* @param manual
*/
async function handleSearch(val: string, manual = false) {
if (isArcoFirstSearch.value && !manual) {
isArcoFirstSearch.value = false;
return;
}
isArcoFirstSearch.value = false;
try {
loading.value = true;
// 如果是远程模式,则请求接口数据
@ -225,6 +231,16 @@ export default defineComponent(
}
);
function getOptionItemDisabled(item: SelectOptionData) {
return (
!!props.multiple &&
!!props.atLeastOne &&
Array.isArray(innerValue.value) &&
!!innerValue.value.find((e) => e[props.valueKey || 'value'] === item[props.valueKey || 'value']) &&
innerValue.value.length === 1
);
}
const selectSlots = () => {
const _slots: MsSearchSelectSlots = {
default: () =>
@ -238,13 +254,7 @@ export default defineComponent(
? { closable: Array.isArray(innerValue.value) && innerValue.value.length > 1 }
: {}
}
disabled={
props.multiple &&
props.atLeastOne &&
Array.isArray(innerValue.value) &&
innerValue.value.find((e) => e[props.valueKey || 'value'] === item[props.valueKey || 'value']) &&
innerValue.value.length === 1
}
disabled={getOptionItemDisabled(item)}
>
<div class="one-line-text" style={getOptionComputedStyle.value}>
{optionItemLabelRender(item)}
@ -333,10 +343,18 @@ export default defineComponent(
popup-container={props.popupContainer || document.body}
trigger-props={props.triggerProps}
fallback-option={props.fallbackOption}
onUpdate:model-value={(value: ModelType) => emit('update:modelValue', value)}
onChange={(value: ModelType) => emit('update:modelValue', value)}
onSearch={handleSearch}
onPopupVisibleChange={(val: boolean) => emit('popupVisibleChange', val)}
onRemove={(val: string | number | boolean | Record<string, any> | undefined) => emit('remove', val)}
onKeyup={(e: KeyboardEvent) => {
// 阻止组件在回车时自动触发的事件
if (e.code === 'Enter') {
e.preventDefault();
e.stopPropagation();
handleSearch('', true);
}
}}
>
{{
prefix: props.prefix ? () => t(props.prefix || '') : null,

View File

@ -113,6 +113,7 @@
bottom: 0;
padding: 2px 4px;
font-size: 12px;
line-height: 16px;
font-weight: 500;
color: #ffffff;
background-color: #00000050;

View File

@ -56,7 +56,7 @@
<slot name="empty">
<div
v-show="treeData.length === 0 && props.emptyText"
class="rounded-[var(--border-radius-small)] bg-[var(--color-fill-1)] p-[8px] text-[12px] text-[var(--color-text-4)]"
class="rounded-[var(--border-radius-small)] bg-[var(--color-fill-1)] p-[8px] text-[12px] leading-[16px] text-[var(--color-text-4)]"
>
{{ props.emptyText }}
</div>

View File

@ -16,7 +16,7 @@
{{ t('common.confirm') }}
</a-button>
<div
class="ml-[8px] flex h-[24px] w-[48px] cursor-pointer items-center justify-center rounded-[4px] border border-[var(--color-text-input-border)] px-[11px] text-[12px] text-[var(--color-text-1)] hover:bg-[rgb(var(--primary-1))]"
class="ml-[8px] flex h-[24px] w-[48px] cursor-pointer items-center justify-center rounded-[4px] border border-[var(--color-text-input-border)] px-[11px] text-[12px] leading-[16px] text-[var(--color-text-1)] hover:bg-[rgb(var(--primary-1))]"
@click="handleCancel"
>
{{ t('common.cancel') }}

View File

@ -111,6 +111,7 @@
.time-text {
font-size: 12px;
color: rgb(var(--gray-6));
line-height: 16px;
}
.arco-empty {
@apply hidden;

View File

@ -15,7 +15,7 @@
v-if="pIndex != 0"
:key="item"
:disabled="commandDisabled"
class="priority-btn mr-[4px] h-[22px] w-[22px] rounded-[8px] pr-[5px] text-[12px]"
class="priority-btn mr-[4px] h-[22px] w-[22px] rounded-[8px] pr-[5px] text-[12px] leading-[16px]"
:class="'priority-btn_' + pIndex"
size="small"
@click="execCommand(pIndex)"

View File

@ -4,11 +4,13 @@
.selection-dropdown-list,
.expand-dropdown-list {
font-size: 12px;
line-height: 16px;
}
.mold-dropdown-list {
width: 126px;
height: 170px;
font-size: 12px;
line-height: 16px;
.dropdown-item {
@apply inline-block p-0;
@ -28,6 +30,7 @@
.theme-dropdown-list {
width: 120px;
font-size: 12px;
line-height: 16px;
.mold-icons {
@apply bg-no-repeat;
}

View File

@ -27,11 +27,12 @@
margin-top: 17px;
font-size: 16px;
line-height: 1em;
line-height: 24px;
}
.key {
font-size: 12px;
color: #999999;
line-height: 16px;
}
}
.ring-shape {
@ -62,8 +63,8 @@
margin-left: 3px;
font-size: 12px;
line-height: 12px;
color: #999999;
line-height: 16px;
&::before {
content: '(';
}

View File

@ -14,7 +14,7 @@
const props = withDefaults(
defineProps<{
type?: 'text' | 'icon' | 'button';
status?: 'primary' | 'danger' | 'secondary';
status?: 'primary' | 'danger' | 'secondary' | 'default';
disabled?: boolean;
loading?: boolean;
}>(),
@ -43,10 +43,9 @@
padding: 0 4px;
font-size: 14px;
border-radius: var(--border-radius-mini);
line-height: 22px;
}
.ms-button--primary--disabled {
color: var(--color-text-4) !important;
color: rgb(var(--primary-3)) !important;
}
.ms-button--danger--disabled {
color: rgb(var(--danger-2)) !important;
@ -68,6 +67,12 @@
}
}
}
.ms-button--default {
color: var(--color-text-1);
&:not(.ms-button-text, .ms-button--disabled):hover {
background-color: var(--color-text-n8);
}
}
.ms-button--secondary {
color: var(--color-text-2);
&:not(.ms-button-text, .ms-button--disabled):hover {

View File

@ -11,15 +11,22 @@
props.noBottomRadius ? 'ms-card--noBottomRadius' : '',
]"
>
<div v-if="!props.simple" class="card-header">
<div v-if="!props.hideBack" class="back-btn" @click="back"><icon-arrow-left /></div>
<div class="font-medium text-[var(--color-text-000)]">{{ props.title }}</div>
<div class="text-[var(--color-text-4)]">{{ props.subTitle }}</div>
<div class="ml-auto">
<slot name="headerRight"></slot>
<a-scrollbar v-if="!props.simple" :style="{ overflow: 'auto' }">
<div class="card-header" :style="props.minWidth ? { minWidth: `${props.minWidth}px` } : {}">
<div v-if="!props.hideBack" class="back-btn" @click="back"><icon-arrow-left /></div>
<slot name="headerLeft">
<div class="font-medium text-[var(--color-text-000)]">{{ props.title }}</div>
<div class="text-[var(--color-text-4)]">{{ props.subTitle }}</div>
</slot>
<div class="ml-auto flex items-center">
<slot name="headerRight"></slot>
</div>
<div v-if="$slots.subHeader" class="basis-full">
<slot name="subHeader"></slot>
</div>
</div>
</div>
<a-divider v-if="!props.simple" class="mb-[16px]" />
</a-scrollbar>
<a-divider v-if="!props.simple && !props.hideDivider" class="mb-[16px] mt-0" />
<div class="ms-card-container">
<a-scrollbar :class="props.noContentPadding ? '' : 'pr-[5px]'" :style="getComputedContentStyle">
<div class="relative h-full w-full" :style="{ minWidth: `${props.minWidth || 1000}px` }">
@ -77,6 +84,7 @@
noContentPadding: boolean; // padding
noBottomRadius?: boolean; //
isFullscreen?: boolean; //
hideDivider?: boolean; // 线
handleBack: () => void; //
}>
>(),
@ -110,11 +118,11 @@
const cardOverHeight = computed(() => {
if (props.simple) {
//
return props.noContentPadding ? 88 : 136 + _specialHeight;
return props.noContentPadding ? 88 + _specialHeight : 136 + _specialHeight;
}
if (props.hideFooter) {
//
return 192 + _specialHeight;
return props.noContentPadding ? 152 + _specialHeight : 192 + _specialHeight;
}
return 246 + _specialHeight;
});
@ -161,7 +169,7 @@
&--noContentPadding {
border-radius: var(--border-radius-large);
.card-header {
padding: 24px 24px 0;
padding: 24px 24px 16px;
}
.arco-divider {
@apply mb-0;
@ -171,7 +179,9 @@
border-radius: var(--border-radius-large) var(--border-radius-large) 0 0;
}
.card-header {
@apply flex items-center;
@apply flex flex-wrap items-center;
padding-bottom: 16px;
.back-btn {
@apply flex cursor-pointer items-center rounded-full;

View File

@ -0,0 +1,46 @@
<template>
<a-popover position="bottom" content-class="p-[16px]">
<div class="color-bar" :style="{ borderRadius: props.radius }">
<template v-for="(item, index) in colorData">
<div v-if="item.percentage > 0" :key="index" :style="getStyle(item, index)"></div>
</template>
</div>
<template #content>
<slot name="popoverContent"></slot>
</template>
</a-popover>
</template>
<script setup lang="ts">
const props = withDefaults(
defineProps<{
colorData: Array<{ percentage: number; color: string }>;
height: string;
radius?: string;
}>(),
{
radius: 'var(--border-radius-large)',
}
);
const getStyle = (item: { percentage: number; color: string }, index: number) => {
return {
width: `${item.percentage}%`,
backgroundColor: item.color,
height: props.height,
display: 'inline-block',
borderRight: index === props.colorData.length - 1 ? 'none' : '1px solid white', // 1px
};
};
</script>
<style scoped>
.color-bar {
@apply flex w-full overflow-hidden;
background-color: var(--color-text-n8);
}
.color-bar div {
box-sizing: border-box;
}
</style>

View File

@ -47,7 +47,7 @@
<icon-loading v-if="tagInputLoading" class="text-[rgb(var(--primary-5))]" />
</template>
</a-input>
<span v-if="tagInputError" class="text-[12px] text-[rgb(var(--danger-6))]">
<span v-if="tagInputError" class="text-[12px] leading-[16px] text-[rgb(var(--danger-6))]">
{{ t('ms.description.addTagRepeat') }}
</span>
</template>

View File

@ -26,7 +26,7 @@
<div class="handle" @mousedown="startResize">
<icon-drag-dot-vertical class="absolute left-[-3px] top-[50%] w-[14px]" size="14" />
</div>
<a-scrollbar class="overflow-y-auto" :style="{ height: `calc(100vh - ${contentExtraHeight}px)` }">
<a-scrollbar class="h-full overflow-y-auto">
<div class="ms-drawer-body">
<slot>
<MsDescription
@ -119,7 +119,7 @@
const contentExtraHeight = computed(() => {
// 146 30 60
return 146 - (props.noContentPadding ? 30 : 0) - (props.footer ? 0 : 60);
return 146 - (props.noContentPadding ? 24 : 0) - (props.footer ? 0 : 60);
});
const handleContinue = () => {
@ -181,11 +181,15 @@
<style lang="less">
.arco-drawer {
@apply bg-white;
max-width: 100vw;
.arco-drawer-header {
height: 56px;
border-bottom: 1px solid var(--color-text-n8);
.arco-drawer-title {
@apply w-full;
line-height: 24px;
}
.arco-drawer-close-btn {
@apply flex items-center;
@ -225,4 +229,7 @@
background-color: var(--color-neutral-3);
cursor: col-resize;
}
.arco-scrollbar {
@apply h-full;
}
</style>

View File

@ -5,7 +5,7 @@
v-bind="attrs"
@change="(v: string) => emit('update:modelValue', v)"
/>
<div class="flex flex-row items-center gap-[10px] text-[12px] leading-[20px]">
<div class="flex flex-row items-center gap-[10px] text-[12px] leading-[16px]">
<span class="text-[var(--color-text-4)]">{{ t('project.menu.howGetJiraKey') }}</span>
<a-popover position="rt">
<template #title>

View File

@ -49,7 +49,7 @@
<template v-if="$slots['empty'] || props.emptyText" #empty>
<slot name="empty">
<div
class="rounded-[var(--border-radius-small)] bg-[var(--color-fill-1)] p-[8px] text-[12px] text-[var(--color-text-4)]"
class="rounded-[var(--border-radius-small)] bg-[var(--color-fill-1)] p-[8px] text-[12px] leading-[16px] text-[var(--color-text-4)]"
>
{{ props.emptyText }}
</div>

View File

@ -1,28 +1,57 @@
<template>
<a-split
v-model:size="innerWidth"
v-model:size="innerSize"
:min="props.min"
:max="props.max"
:class="['h-full', isExpandedLeft ? '' : 'expanded-panel', isExpandAnimating ? 'animating' : '']"
:class="['h-full', isExpanded ? '' : 'expanded-panel', isExpandAnimating ? 'animating' : '']"
:direction="props.direction"
>
<template #first>
<div class="ms-split-box ms-split-box--left">
<div v-if="props.direction === 'horizontal'" class="ms-split-box ms-split-box--left">
<div v-if="props.expandDirection === 'right'" class="absolute right-0 flex h-full w-[16px] items-center">
<div class="expand-icon expand-icon--left" @click="changeLeftExpand">
<MsIcon
:type="isExpanded ? 'icon-icon_up-left_outlined' : 'icon-icon_down-right_outlined'"
class="text-[var(--color-text-brand)]"
size="12"
/>
</div>
</div>
<slot name="left"></slot>
</div>
<div v-else class="ms-split-box ms-split-box--top">
<slot name="top"></slot>
</div>
</template>
<template #second>
<div class="absolute flex h-full w-[16px] items-center">
<div class="expand-icon" @click="changeLeftExpand">
<MsIcon
:type="isExpandedLeft ? 'icon-icon_up-left_outlined' : 'icon-icon_down-right_outlined'"
class="text-[var(--color-text-brand)]"
size="12"
/>
<template v-if="props.direction === 'horizontal'">
<div v-if="props.expandDirection === 'left'" class="absolute flex h-full w-[16px] items-center">
<div class="expand-icon" @click="changeLeftExpand">
<MsIcon
:type="isExpanded ? 'icon-icon_up-left_outlined' : 'icon-icon_down-right_outlined'"
class="text-[var(--color-text-brand)]"
size="12"
/>
</div>
</div>
</div>
<div class="ms-split-box ms-split-box--right">
<slot name="right"></slot>
</div>
<div class="ms-split-box ms-split-box--right">
<slot name="right"></slot>
</div>
</template>
<template v-else>
<div class="absolute top-0 flex h-[16px] w-full items-center justify-center">
<div class="expand-icon expand-icon--vertical" @click="changeLeftExpand">
<MsIcon
:type="isExpanded ? 'icon-icon_up-left_outlined' : 'icon-icon_down-right_outlined'"
class="text-[var(--color-text-brand)]"
size="12"
/>
</div>
</div>
<div class="ms-split-box ms-split-box--bottom">
<slot name="bottom"></slot>
</div>
</template>
</template>
</a-split>
</template>
@ -34,48 +63,52 @@
const props = withDefaults(
defineProps<{
width?: number | string;
size?: number | string; // /expandDirection right size size
min?: number | string;
max?: number | string;
direction?: 'horizontal' | 'vertical';
expandDirection?: 'left' | 'right' | 'top'; // TODO: bottom left top
}>(),
{
width: '300px',
size: '300px',
min: '250px',
max: 0.5,
direction: 'horizontal',
expandDirection: 'left',
}
);
const emit = defineEmits(['update:width', 'expandChange']);
const emit = defineEmits(['update:size', 'expandChange']);
const innerWidth = ref(props.width || '300px');
const innerSize = ref(props.size || '300px');
watch(
() => props.width,
() => props.size,
(val) => {
if (val !== undefined) {
innerWidth.value = val;
innerSize.value = val;
}
}
);
watch(
() => innerWidth.value,
() => innerSize.value,
(val) => {
emit('update:width', val);
emit('update:size', val);
}
);
const isExpandedLeft = ref(true);
const isExpanded = ref(true);
const isExpandAnimating = ref(false); //
function changeLeftExpand() {
isExpandAnimating.value = true;
isExpandedLeft.value = !isExpandedLeft.value;
if (isExpandedLeft.value) {
innerWidth.value = props.width || '300px';
isExpanded.value = !isExpanded.value;
if (isExpanded.value) {
innerSize.value = props.size || '300px'; // size /
emit('expandChange', true);
} else {
innerWidth.value = '0px';
innerSize.value = props.expandDirection === 'right' ? 1 : '0px'; // expandDirection right 100%
emit('expandChange', false);
}
//
@ -110,12 +143,12 @@
.ms-scroll-bar();
}
.ms-split-box--left {
width: calc(v-bind(innerWidth) - 4px);
width: calc(v-bind(innerSize) - 4px);
}
.expand-icon {
@apply z-10 cursor-pointer;
@apply z-10 flex cursor-pointer justify-center;
padding: 8px 2px;
padding: 12px 2px;
border-radius: 0 var(--border-radius-small) var(--border-radius-small) 0;
background-color: var(--color-text-n8);
}
@ -125,4 +158,18 @@
:deep(.arco-split-trigger-icon) {
font-size: 14px;
}
.ms-split-box--top {
height: calc(v-bind(innerSize) - 4px);
}
.ms-split-box--bottom {
@apply h-full;
}
.expand-icon--vertical {
@apply rotate-90;
}
.expand-icon--left {
@apply rotate-180;
border-radius: 0 var(--border-radius-small) var(--border-radius-small) 0;
}
</style>

View File

@ -6,9 +6,10 @@
<template #content>
<template v-for="item of props.list">
<a-divider v-if="item.isDivider" :key="`${item.label}-divider`" margin="4px" />
<a-doption v-else :key="item.label" :class="item.danger ? 'error-6' : ''" :disabled="item.disabled">{{
t(item.label || '')
}}</a-doption>
<a-doption v-else :key="item.label" :class="item.danger ? 'error-6' : ''" :disabled="item.disabled">
<MsIcon v-if="item.icon" :type="item.icon" />
{{ t(item.label || '') }}
</a-doption>
</template>
</template>
</a-dropdown>
@ -16,6 +17,7 @@
<script setup lang="ts">
import MsButton from '@/components/pure/ms-button/index.vue';
import MsIcon from '@/components/pure/ms-icon-font/index.vue';
import { useI18n } from '@/hooks/useI18n';

View File

@ -4,6 +4,7 @@ export interface ActionsItem {
isDivider?: boolean; // 是否分割线true 的话只展示分割线,没有其他内容
danger?: boolean; // 是否危险操作true 的话会显示红色按钮
disabled?: boolean; // 是否禁用
icon?: string; // 按钮图标
}
export type SelectedValue = string | number | Record<string, any> | undefined;

View File

@ -215,6 +215,6 @@
}
.non-sort {
font-size: 12px;
line-height: 16px;
}
</style>
@/store/modules/components/ms-table/types

View File

@ -81,6 +81,7 @@
font-size: 12px;
border-radius: 50%;
color: var(--color-text-4);
line-height: 16px;
}
.dropdown-icon:hover {
color: rgb(var(--primary-5));

View File

@ -214,6 +214,8 @@ export default function useTableProps<T>(
} catch (err) {
setTableErrorStatus('error');
propsRes.value.data = [];
// eslint-disable-next-line no-console
console.log(err);
} finally {
setLoading(false);
// debug 模式下打印属性

View File

@ -45,6 +45,7 @@
const getTagWidth = (tag: { [x: string]: any }) => {
const tagStr = props.isStringTag ? tag : tag[props.nameKey];
const tagWidth = tagStr.length;
// 16
return tagWidth < 16 ? tagWidth : 16;

View File

@ -2,7 +2,7 @@
<a-input-tag
v-model:model-value="innerModelValue"
v-model:input-value="innerInputValue"
:placeholder="t(props.placeholder || '')"
:placeholder="t(props.placeholder || 'ms.tagsInput.tagsInputPlaceholder')"
:allow-clear="props.allowClear"
:retain-input-value="props.retainInputValue"
:unique-value="props.uniqueValue"

View File

@ -1,3 +1,4 @@
export default {
'ms.tagsInput.tagsDuplicateText': 'Same label already exists',
'ms.tagsInput.tagsInputPlaceholder': 'Add tag and press Enter to end',
};

View File

@ -1,3 +1,4 @@
export default {
'ms.tagsInput.tagsDuplicateText': '已存在相同的标签',
'ms.tagsInput.tagsInputPlaceholder': '添加标签,回车结束',
};

View File

@ -29,10 +29,13 @@
<div class="font-normal">{{ item.file.name }}</div>
</template>
<template #description>
<div v-if="item.status === UploadStatus.init" class="text-[12px] text-[var(--color-text-4)]">
<div v-if="item.status === UploadStatus.init" class="text-[12px] leading-[16px] text-[var(--color-text-4)]">
{{ t('ms.upload.waiting') }}
</div>
<div v-else-if="item.status === UploadStatus.done" class="text-[12px] text-[var(--color-text-4)]">
<div
v-else-if="item.status === UploadStatus.done"
class="text-[12px] leading-[16px] text-[var(--color-text-4)]"
>
{{
`${formatFileSize(item.file.size)} ${t('ms.upload.uploadAt')} ${dayjs(item.uploadedTime).format(
'YYYY-MM-DD HH:mm:ss'

View File

@ -248,6 +248,7 @@
margin-bottom: 6px;
font-size: 12px;
color: var(--color-text-4);
line-height: 16px;
}
}
</style>

View File

@ -336,6 +336,7 @@
font-size: 16px;
border-color: rgb(var(--gray-2));
color: rgb(var(--gray-8));
line-height: 24px;
}
.trigger-btn,
.ref-btn {

View File

@ -226,7 +226,7 @@
fileId: string;
activeFileIndex: number;
tableData: any[];
pagination?: MsPaginationI;
pagination: MsPaginationI;
pageChange: (page: number) => Promise<void>;
}>();

View File

@ -279,7 +279,7 @@
:active-file-index="activeFileIndex"
:table-data="propsRes.data"
:page-change="propsEvent.pageChange"
:pagination="propsRes.msPagination"
:pagination="propsRes.msPagination!"
/>
</template>