feat: 系统_插件管理_列表滚动_上传调整

This commit is contained in:
xinxin.wu 2023-07-27 17:02:58 +08:00 committed by fit2-zhao
parent a045f0406d
commit df20aec81a
7 changed files with 374 additions and 216 deletions

View File

@ -37,7 +37,7 @@
"@7polo/kity": "2.0.8",
"@7polo/kityminder-core": "1.4.53",
"@arco-design/web-vue": "^2.48.0",
"@arco-themes/vue-ms-theme-default": "^0.0.19",
"@arco-themes/vue-ms-theme-default": "^0.0.21",
"@form-create/arco-design": "^3.1.21",
"@vueuse/core": "^9.13.0",
"ace-builds": "^1.22.0",

View File

@ -165,7 +165,7 @@
};
function getRowClass(record: TableData) {
if (!record.raw.enable && !props.noDisable) {
if (!record.enable && !props.noDisable) {
return 'ms-table-row-disabled';
}
}

View File

@ -0,0 +1,65 @@
import { nextTick, reactive } from 'vue';
// 自定义展开行:如果当前行下边无内容,隐藏表格折叠按钮
const useButtonStyle = (wrapperName?: string, className?: string[]) => {
const cssHeight = reactive({
height: '460px',
});
// 隐藏按钮以及图标
const hiddenButton = () => {
nextTick(() => {
const emptyBtns = document.querySelectorAll('.empty-button');
emptyBtns.forEach((node) => {
(node.parentNode as HTMLElement).style.display = 'none';
});
});
};
// 设置折叠展开后图标的颜色
const expandOrcollapseStyle = () => {
nextTick(() => {
const expandBtns = document.querySelectorAll('.expand');
const collapseBtns = document.querySelectorAll('.collapsebtn');
expandBtns.forEach((node) => {
(node.parentNode as HTMLElement).style.borderColor = 'rgb(var(--primary-6))';
});
collapseBtns.forEach((node) => {
(node.parentNode as HTMLElement).style.borderColor = 'var(--color-border-4)';
});
});
};
// 获取盒子的总高度
const getPageContentHeight = (boxElement: string) => {
const pageContent = document.querySelector(boxElement);
return pageContent ? pageContent.getBoundingClientRect().height : 0;
};
// 计算每一个元素的高度
const getDomHeightWithMargin = (selector: string) => {
const dom = document.querySelector(selector) as HTMLElement;
const computedStyle = getComputedStyle(dom);
const marginTop = parseFloat(computedStyle.marginTop);
const marginBottom = parseFloat(computedStyle.marginBottom);
return dom ? dom.getBoundingClientRect().height + marginTop + marginBottom : 460;
};
// 计算最后的高度
const countHeight = () => {
const contentHeight = getPageContentHeight(wrapperName as string);
const excludeTotalHeight =
className?.reduce((prev, item) => {
return prev + getDomHeightWithMargin(item);
}, 0) || 0;
return `${contentHeight - excludeTotalHeight - 70}px`;
};
window.onresize = () => {
cssHeight.height = countHeight();
};
return {
hiddenButton,
expandOrcollapseStyle,
countHeight,
cssHeight,
};
};
export default useButtonStyle;

View File

@ -1,119 +1,126 @@
<!-- eslint-disable vue/valid-v-for -->
<template>
<div>
<div class="mb-4">
<a-row class="grid-demo" :gutter="24">
<a-col :span="5">
<a-button class="mr-3" type="primary" @click="uploadPlugin">{{ t('system.plugin.uploadPlugin') }}</a-button>
</a-col>
<a-col :span="5" :offset="9">
<a-select v-model="searchKeys.scene" allow-clear>
<a-option v-for="item of sceneList" :key-="item.value" :value="item.value">{{ item.label }}</a-option>
</a-select>
</a-col>
<a-col :span="5">
<a-input-search
v-model="searchKeys.name"
:placeholder="t('system.plugin.searchPlugin')"
@search="searchHanlder"
></a-input-search>
</a-col>
</a-row>
<a-row class="grid-demo mb-4" :gutter="10">
<a-col :span="5">
<a-button class="mr-3" type="primary" @click="uploadPlugin">{{ t('system.plugin.uploadPlugin') }}</a-button>
</a-col>
<a-col :span="5" :offset="9">
<a-select v-model="searchKeys.scene">
<a-option v-for="item of sceneList" :key-="item.value" :value="item.value">{{ item.label }}</a-option>
</a-select>
</a-col>
<a-col :span="5">
<a-input-search
v-model="searchKeys.name"
:placeholder="t('system.plugin.searchPlugin')"
@search="searchHanlder"
></a-input-search>
</a-col>
</a-row>
<div class="mb-6" style="overflow: auto">
<a-table
:data="filterData"
:pagination="false"
:scroll="{ y: 'auto', x: 2400 }"
:expandable="expandable"
:loading="loading"
row-key="id"
:expanded-row-keys="expandedRowKeys"
@expand="handleExpand"
>
<template #columns>
<a-table-column fixed="left" :title="t('system.plugin.tableColunmName')">
<template #cell="{ record }">
{{ record.name }} <span class="text-[--color-text-4]">({{ record.pluginForms.length }})</span>
</template>
</a-table-column>
<a-table-column :title="t('system.plugin.tableColunmDescription')" data-index="description" />
<a-table-column :title="t('system.plugin.tableColunmStatus')">
<template #cell="{ record }">
<div v-if="record.enable" class="flex items-center">
<icon-check-circle-fill class="mr-[2px] text-[rgb(var(--success-6))]" />
{{ t('system.user.tableEnable') }}
</div>
<div v-else class="flex items-center text-[var(--color-text-4)]">
<icon-stop class="mr-[2px]" />
{{ t('system.user.tableDisable') }}
</div>
</template>
</a-table-column>
<a-table-column :title="t('system.plugin.tableColunmApplicationScene')" data-index="scenario">
<template #cell="{ record }">{{
record.scenario === 'API' ? t('system.plugin.secneApi') : t('system.plugin.secneProManger')
}}</template>
</a-table-column>
<a-table-column :title="t('system.user.tableColunmOrg')">
<template #cell="{ record }">
<a-tag
v-for="org of record.organizations"
:key="org.id"
class="mr-[4px] border-[rgb(var(--primary-5))] bg-transparent !text-[rgb(var(--primary-5))]"
bordered
>
{{ org.name }}
</a-tag>
<a-tag
v-show="record.organizations.length > 2"
class="mr-[4px] border-[rgb(var(--primary-5))] bg-transparent !text-[rgb(var(--primary-5))]"
bordered
>
+{{ record.organizations.length - 2 }}
</a-tag>
</template>
</a-table-column>
<a-table-column :title="t('system.plugin.tableColunmDescription')" data-index="fileName" />
<a-table-column :title="t('system.plugin.tableColunmVersion')" data-index="pluginId" />
<a-table-column :title="t('system.plugin.tableColunmAuthorization')">
<template #cell="{ record }">
<span>{{
record.xpack ? t('system.plugin.uploadOpenSource') : t('system.plugin.uploadCompSource')
}}</span>
</template>
</a-table-column>
<a-table-column :title="t('system.plugin.tableColunmCreatedBy')" data-index="createUser" />
<a-table-column :title="t('system.plugin.tableColunmUpdateTime')">
<template #cell="{ record }">
<span>{{ getTime(record.updateTime) }}</span>
</template>
</a-table-column>
<a-table-column :width="200" fixed="right" align="center" :bordered="false">
<template #title>
{{ t('system.plugin.tableColunmActions') }}
</template>
<template #cell="{ record }">
<MsButton @click="update(record)">{{ t('system.plugin.edit') }}</MsButton>
<MsButton v-if="record.enable" @click="disableHandler(record)">{{
t('system.plugin.tableDisable')
}}</MsButton>
<MsButton v-else>{{ t('system.plugin.tableEnable') }}</MsButton>
<MsTableMoreAction :list="tableActions" @select="handleSelect($event, record)"></MsTableMoreAction>
</template>
</a-table-column>
</template>
<template #expand-icon="{ record, expanded }">
<span v-if="record.pluginForms.length && !expanded" class="collapsebtn"
><icon-plus :style="{ 'font-size': '12px' }"
/></span>
<span v-else-if="record.pluginForms.length && expanded" class="expand"
><icon-minus class="text-[rgb(var(--primary-6))]" :style="{ 'font-size': '12px' }"
/></span>
<span v-else class="empty-button"></span>
</template>
<template #expand-row="{ record }">
<div v-for="(item, index) in record.pluginForms" :key="item.id" class="ms-self"
><span class="circle"> {{ index + 1 }} </span
><span class="cursor-pointer text-[rgb(var(--primary-6))]" @click="detailScript(record, item)">{{
item.name
}}</span></div
>
</template>
</a-table>
</div>
<a-table
:data="data"
:pagination="false"
:scroll="{ y: 'auto', x: 2400 }"
:expandable="expandable"
:loading="loading"
row-key="id"
:expanded-row-keys="expandedRowKeys"
@expand="handleExpand"
>
<template #columns>
<a-table-column fixed="left" :title="t('system.plugin.tableColunmName')">
<template #cell="{ record }">
{{ record.name }} <span class="text-[--color-text-4]">({{ record.pluginForms.length }})</span>
</template>
</a-table-column>
<a-table-column :title="t('system.plugin.tableColunmDescription')" data-index="description" />
<a-table-column :title="t('system.plugin.tableColunmStatus')">
<template #cell="{ record }">
<div v-if="record.enable" class="flex items-center">
<icon-check-circle-fill class="mr-[2px] text-[rgb(var(--success-6))]" />
{{ t('system.user.tableEnable') }}
</div>
<div v-else class="flex items-center text-[var(--color-text-4)]">
<icon-stop class="mr-[2px]" />
{{ t('system.user.tableDisable') }}
</div>
</template>
</a-table-column>
<a-table-column :title="t('system.plugin.tableColunmApplicationScene')" data-index="scenario">
<template #cell="{ record }">{{
record.scenario === 'API' ? t('system.plugin.secneApi') : t('system.plugin.secneProManger')
}}</template>
</a-table-column>
<a-table-column :width="300" :title="t('system.user.tableColunmOrg')">
<template #cell="{ record }">
<a-tag
v-for="org of record.organizations"
:key="org.id"
class="mr-[4px] border-[rgb(var(--primary-5))] bg-transparent !text-[rgb(var(--primary-5))]"
bordered
>
{{ org.name }}
</a-tag>
<a-tag
v-show="record.organizations.length > 2"
class="mr-[4px] border-[rgb(var(--primary-5))] bg-transparent !text-[rgb(var(--primary-5))]"
bordered
>
+{{ record.organizations.length - 2 }}
</a-tag>
</template>
</a-table-column>
<a-table-column :title="t('system.plugin.tableColunmDescription')" data-index="fileName" />
<a-table-column :width="350" :title="t('system.plugin.tableColunmVersion')" data-index="pluginId" />
<a-table-column :title="t('system.plugin.tableColunmAuthorization')">
<template #cell="{ record }">
<span>{{ record.xpack ? t('system.plugin.uploadOpenSource') : t('system.plugin.uploadCompSource') }}</span>
</template>
</a-table-column>
<a-table-column :title="t('system.plugin.tableColunmCreatedBy')" data-index="createUser" />
<a-table-column :title="t('system.plugin.tableColunmUpdateTime')">
<template #cell="{ record }">
<span>{{ getTime(record.updateTime) }}</span>
</template>
</a-table-column>
<a-table-column :width="200" fixed="right" align="center" :bordered="false">
<template #title>
{{ t('system.plugin.tableColunmActions') }}
</template>
<template #cell="{ record }">
<MsButton @click="update(record)">{{ t('system.plugin.edit') }}</MsButton>
<MsButton v-if="record.enable" @click="disableHandler(record)">{{
t('system.plugin.tableDisable')
}}</MsButton>
<MsButton v-else>{{ t('system.plugin.tableEnable') }}</MsButton>
<MsTableMoreAction :list="tableActions" @select="handleSelect($event, record)"></MsTableMoreAction>
</template>
</a-table-column>
</template>
<!-- <template #expand-icon="{ expanded }">
<span v-if="!expanded"><icon-plus /></span>
<span v-else><icon-minus /></span>
</template> -->
<template #expand-row="{ record }">
<div v-for="(item, index) in record.pluginForms" :key="item.id" class="ms-self"
><span class="circle"> {{ index + 1 }} </span
><span class="cursor-pointer text-[rgb(var(--primary-6))]" @click="detailScript(record, item)">{{
item.name
}}</span></div
>
</template>
</a-table>
<div class="mt-4 text-sm text-slate-500"
<div class="ms-footerNum"
>{{ t('system.plugin.totalNum') }}<span class="mx-2">{{ totalNum }}</span
>{{ t('system.plugin.dataList') }}</div
>
@ -135,16 +142,21 @@
import uploadSuccessModal from './uploadSuccessModal.vue';
import scriptDetailDrawer from './scriptDetailDrawer.vue';
import { useCommandComponent } from '@/hooks/useCommandComponent';
import useButtonStyle from '@/hooks/useHiddenButton';
import useModal from '@/hooks/useModal';
import { Message } from '@arco-design/web-vue';
import useVisit from '@/hooks/useVisit';
import type { PluginForms, PluginList, PluginItem, Options, DrawerConfig } from '@/models/setting/plugin';
import dayjs from 'dayjs';
const { t } = useI18n();
const visitedKey = 'doNotShowAgain';
const { getIsVisited } = useVisit(visitedKey);
const { hiddenButton, expandOrcollapseStyle, countHeight, cssHeight } = useButtonStyle('.ms-card', [
'.arco-alert',
'.arco-row',
'.ms-footerNum',
]);
const data = ref<PluginList>([]);
const loading = ref<boolean>(false);
@ -165,6 +177,7 @@
danger: true,
},
];
const filterData = ref<PluginList>([]);
const searchKeys = reactive({
scene: '',
name: '',
@ -206,68 +219,11 @@
loading.value = false;
}
};
const handleExpand = (rowKey: string | number) => {
Object.assign(expandedRowKeys, [rowKey]);
const searchHanlder = () => {
filterData.value = data.value.filter(
(item) => item.name?.includes(searchKeys.name) && item.scenario?.indexOf(searchKeys.scene) !== -1
);
};
const searchHanlder = () => {};
onMounted(() => {
data.value = [
{
id: 'string1',
name: '插件一',
pluginId: 'string',
fileName: 'string',
createTime: 0,
updateTime: 3084234,
createUser: '创建人',
enable: true,
global: true,
xpack: true,
description: 'string',
scenario: 'string',
pluginForms: [
{
id: '111',
name: '步骤一',
},
{
id: '222',
name: '步骤二',
},
],
organizations: [
{
id: 'string',
num: 0,
name: 'string',
},
],
},
{
id: 'string2',
name: '插件二',
pluginId: 'string',
fileName: 'string',
createTime: 0,
updateTime: 3084234,
createUser: '创建人',
enable: true,
global: true,
xpack: true,
description: 'string',
scenario: 'string',
pluginForms: [],
organizations: [
{
id: 'string',
num: 0,
name: 'string',
},
],
},
];
// loadData();
});
function deletePlugin(record: any) {
openModal({
type: 'warning',
@ -321,7 +277,6 @@
options.visible = true;
myUploadSuccessDialog(uploadSuccessOptions);
};
const okHandler = () => {
const isOpen = getIsVisited();
if (!isOpen) {
@ -365,6 +320,151 @@
console.log(error);
}
};
const handleExpand = (rowKey: string | number) => {
Object.assign(expandedRowKeys, [rowKey]);
expandOrcollapseStyle();
};
onMounted(() => {
data.value = [
{
id: 'string1',
name: '插件一',
pluginId: 'string',
fileName: 'string',
createTime: 0,
updateTime: 3084234,
createUser: '创建人',
enable: true,
global: true,
xpack: true,
description: 'string',
scenario: 'API',
pluginForms: [
{
id: '111',
name: '步骤一',
},
{
id: '222',
name: '步骤二',
},
],
organizations: [
{
id: 'string',
num: 0,
name: 'string',
},
],
},
{
id: 'string2',
name: '插件二',
pluginId: 'string',
fileName: 'string',
createTime: 0,
updateTime: 3084234,
createUser: '创建人',
enable: true,
global: true,
xpack: true,
description: 'string',
scenario: 'PLATFORM',
pluginForms: [],
organizations: [
{
id: 'string',
num: 0,
name: 'string',
},
],
},
{
id: 'string3',
name: '插件3',
pluginId: 'string',
fileName: 'string',
createTime: 0,
updateTime: 3084234,
createUser: '创建人',
enable: true,
global: true,
xpack: true,
description: 'string',
scenario: 'PLATFORM',
pluginForms: [
{
id: '111',
name: '步骤一',
},
],
organizations: [
{
id: 'string',
num: 0,
name: 'string',
},
],
},
{
id: 'string4',
name: '插件4',
pluginId: 'string',
fileName: 'string',
createTime: 0,
updateTime: 3084234,
createUser: '创建人',
enable: true,
global: true,
xpack: true,
description: 'string',
scenario: 'API',
pluginForms: [
{
id: '111',
name: '步骤一',
},
{
id: '222',
name: '步骤二',
},
],
organizations: [
{
id: 'string',
num: 0,
name: 'string',
},
],
},
{
id: 'string5',
name: '插件5',
pluginId: 'string',
fileName: 'string',
createTime: 0,
updateTime: 3084234,
createUser: '创建人',
enable: true,
global: true,
xpack: true,
description: 'string',
scenario: 'PLATFORM',
pluginForms: [],
organizations: [
{
id: 'string',
num: 0,
name: 'string',
},
],
},
];
loadData();
filterData.value = [...data.value];
hiddenButton();
cssHeight.height = countHeight();
});
</script>
<style scoped lang="less">
@ -379,10 +479,17 @@
:deep(.arco-table-tr-expand .arco-table-cell) {
padding: 0 !important;
}
:deep(.arco-table) {
overflow: hidden !important;
height: v-bind('cssHeight.height') !important;
}
.ms-self {
height: 40px;
line-height: 40px;
border-bottom: 1px solid var(--color-text-n8);
@apply flex items-center align-middle leading-6;
}
.ms-footerNum {
@apply mt-4 text-sm text-slate-500;
}
</style>

View File

@ -3,13 +3,14 @@
v-model:visible="showScriptDrawer"
width="680px"
:mask="false"
:footer="false"
:title="t('system.plugin.showScriptTitle', { name: props.config.title })"
>
<MsCodeEditor
v-model:model-value="jobDefinition"
title="YAML"
width="100%"
height="calc(100vh - 205px)"
height="calc(100vh - 155px)"
theme="MS-text"
/>
</MsDrawer>

View File

@ -46,7 +46,7 @@
:placeholder="t('system.plugin.selectOriginize')"
allow-clear
>
<a-option v-for="item of originizeList" :key="item.value" :value="item.value">{{ item.label }}</a-option>
<a-option v-for="item of originizeList" :key="item.id" :value="item.id">{{ item.name }}</a-option>
</a-select>
</a-form-item>
<a-form-item field="describe" :label="t('system.plugin.description')" asterisk-position="end">
@ -60,13 +60,13 @@
</a-form>
</a-row>
<MsUpload
v-model:file-list="fileList"
accept="jar"
:on-before-upload="beforeUpload"
main-text="system.user.importModalDragtext"
:sub-text="t('system.plugin.supportFormat')"
:show-file-list="true"
:file-list="fileList"
:on-before-remove="removeHandler"
:show-file-list="false"
:auto-upload="false"
:disabled="confirmLoading"
></MsUpload>
</div>
<template #footer>
@ -98,12 +98,12 @@
</template>
<script setup lang="ts">
import { ref, watchEffect } from 'vue';
import { ref, watchEffect, onMounted } from 'vue';
import MsUpload from '@/components/pure/ms-upload/index.vue';
import type { FormInstance, ValidatedError } from '@arco-design/web-vue';
import type { FormInstance, ValidatedError, SelectOptionData, FileItem } from '@arco-design/web-vue';
import { addPlugin } from '@/api/modules/setting/pluginManger';
import { Message } from '@arco-design/web-vue';
import { formatFileSize } from '@/utils';
import { getAllOrgList } from '@/api/modules/setting/orgnization';
import { useI18n } from '@/hooks/useI18n';
const { t } = useI18n();
@ -118,7 +118,7 @@
}>();
const pluginVisible = ref(false);
const fileName = ref<string>('');
const fileList = ref<File[]>([]);
const fileList = ref<FileItem[]>([]);
const saveLoading = ref<boolean>(false);
const confirmLoading = ref<boolean>(false);
const pluginFormRef = ref<FormInstance | null>(null);
@ -130,41 +130,19 @@
global: true,
};
const form = ref({ ...initForm });
const originizeList = ref([
{
label: '组织一',
value: '1',
},
{
label: '组织二',
value: '2',
},
]);
const originizeList = ref<SelectOptionData>([]);
watchEffect(() => {
pluginVisible.value = props.visible;
});
const beforeUpload = (file: File) => {
const size = formatFileSize(file.size);
if (size.includes('MB') && Number(size.replace('MB', '')) > 50) {
Message.warning(t('system.plugin.sizeExceedTip'));
} else {
fileName.value = file.name;
fileList.value = [...fileList.value, file];
fileList.value = fileList.value.slice(-1);
}
};
const removeHandler = () => {
const resetForm = () => {
form.value = { ...initForm };
fileList.value = [];
};
const handleCancel = () => {
pluginFormRef.value?.resetFields();
resetForm();
emits('cancel');
};
const resetForm = () => {
form.value = { ...initForm };
fileList.value = [];
};
const confirmHandler = async (flag: string) => {
try {
if (fileList.value.length < 1) {
@ -176,7 +154,7 @@
...form.value,
name: form.value.name || fileName.value,
},
fileList: fileList.value,
fileList: [fileList.value[0].file],
};
await addPlugin(params);
Message.success(t('system.plugin.uploadSuccessTip'));
@ -214,6 +192,12 @@
}
});
};
watchEffect(() => {
fileName.value = fileList.value[0]?.name as string;
});
onMounted(async () => {
originizeList.value = await getAllOrgList();
});
</script>
<style scoped lang="less">

View File

@ -1,6 +1,6 @@
/* stylelint-disable order/properties-order */
<template>
<div>
<MsCard simple>
<a-alert :closable="true">
<div>
{{ t('system.plugin.alertDescribe') }}
@ -12,11 +12,12 @@
<div class="mt-4">
<pluginTable />
</div>
</div>
</MsCard>
</template>
<script setup lang="ts">
import pluginTable from './components/pluginTable.vue';
import MsCard from '@/components/pure/ms-card/index.vue';
import { useI18n } from '@/hooks/useI18n';
const { t } = useI18n();