feat(全局组件): ms-case-associate关联用例业务组件&ms-prev-next-button快速切换上下数据按钮组件&ms-color-line彩色进度条组件&部分组件调整
This commit is contained in:
parent
f69d3eaec3
commit
a33a0580ff
|
@ -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';
|
||||
}
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** 折叠面板样式 **/
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
||||
/* 单航文本缩略 */
|
||||
|
|
|
@ -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>
|
||||
|
||||
|
|
|
@ -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[]; // 路由地址参数,面包屑组件会根据此参数拼接路由地址
|
||||
}
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
|
@ -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',
|
||||
};
|
|
@ -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': '关联成功',
|
||||
};
|
|
@ -0,0 +1 @@
|
|||
export type CaseLevel = 0 | 1 | 2 | 3;
|
|
@ -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;
|
||||
}
|
||||
);
|
||||
|
||||
// 当前查看的是否是总数据的第一条数据,用当前查看数据的下标是否等于0,且当前页码是否等于1
|
||||
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();
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
|
|
|
@ -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 {};
|
||||
|
|
|
@ -1,6 +1 @@
|
|||
export default {
|
||||
'ms.detail.drawer.prev': '上一个',
|
||||
'ms.detail.drawer.noPrev': '当前已是第一个',
|
||||
'ms.detail.drawer.next': '下一个',
|
||||
'ms.detail.drawer.noNext': '当前已是最后一个',
|
||||
};
|
||||
export default {};
|
||||
|
|
|
@ -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]">
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
// 当前查看的是否是总数据的第一条数据,用当前查看数据的下标是否等于0,且当前页码是否等于1
|
||||
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>
|
|
@ -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',
|
||||
};
|
|
@ -0,0 +1,6 @@
|
|||
export default {
|
||||
'ms.prevNextButton.prev': '上一个',
|
||||
'ms.prevNextButton.noPrev': '当前已是第一个',
|
||||
'ms.prevNextButton.next': '下一个',
|
||||
'ms.prevNextButton.noNext': '当前已是最后一个',
|
||||
};
|
|
@ -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>
|
|
@ -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,
|
||||
|
|
|
@ -113,6 +113,7 @@
|
|||
bottom: 0;
|
||||
padding: 2px 4px;
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
font-weight: 500;
|
||||
color: #ffffff;
|
||||
background-color: #00000050;
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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') }}
|
||||
|
|
|
@ -111,6 +111,7 @@
|
|||
.time-text {
|
||||
font-size: 12px;
|
||||
color: rgb(var(--gray-6));
|
||||
line-height: 16px;
|
||||
}
|
||||
.arco-empty {
|
||||
@apply hidden;
|
||||
|
|
|
@ -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)"
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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: '(';
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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>
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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';
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -215,6 +215,6 @@
|
|||
}
|
||||
.non-sort {
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
}
|
||||
</style>
|
||||
@/store/modules/components/ms-table/types
|
||||
|
|
|
@ -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));
|
||||
|
|
|
@ -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 模式下打印属性
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
export default {
|
||||
'ms.tagsInput.tagsDuplicateText': 'Same label already exists',
|
||||
'ms.tagsInput.tagsInputPlaceholder': 'Add tag and press Enter to end',
|
||||
};
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
export default {
|
||||
'ms.tagsInput.tagsDuplicateText': '已存在相同的标签',
|
||||
'ms.tagsInput.tagsInputPlaceholder': '添加标签,回车结束',
|
||||
};
|
||||
|
|
|
@ -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'
|
||||
|
|
|
@ -248,6 +248,7 @@
|
|||
margin-bottom: 6px;
|
||||
font-size: 12px;
|
||||
color: var(--color-text-4);
|
||||
line-height: 16px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -226,7 +226,7 @@
|
|||
fileId: string;
|
||||
activeFileIndex: number;
|
||||
tableData: any[];
|
||||
pagination?: MsPaginationI;
|
||||
pagination: MsPaginationI;
|
||||
pageChange: (page: number) => Promise<void>;
|
||||
}>();
|
||||
|
||||
|
|
|
@ -279,7 +279,7 @@
|
|||
:active-file-index="activeFileIndex"
|
||||
:table-data="propsRes.data"
|
||||
:page-change="propsEvent.pageChange"
|
||||
:pagination="propsRes.msPagination"
|
||||
:pagination="propsRes.msPagination!"
|
||||
/>
|
||||
</template>
|
||||
|
||||
|
|
Loading…
Reference in New Issue