refactor: PageTable优化

This commit is contained in:
meilin.huang
2023-12-16 17:41:15 +08:00
parent 06bce33c48
commit 68f8603c75
21 changed files with 372 additions and 216 deletions

View File

@@ -11,13 +11,13 @@
"dependencies": {
"@element-plus/icons-vue": "^2.1.0",
"@vueuse/core": "^10.7.0",
"asciinema-player": "^3.6.2",
"asciinema-player": "^3.6.3",
"axios": "^1.6.2",
"clipboard": "^2.0.11",
"countup.js": "^2.7.0",
"cropperjs": "^1.5.11",
"echarts": "^5.4.3",
"element-plus": "^2.4.3",
"element-plus": "^2.4.4",
"js-base64": "^3.7.5",
"jsencrypt": "^3.3.2",
"lodash": "^4.17.21",
@@ -47,15 +47,15 @@
"@types/sortablejs": "^1.15.3",
"@typescript-eslint/eslint-plugin": "^6.7.4",
"@typescript-eslint/parser": "^6.7.4",
"@vitejs/plugin-vue": "^4.5.1",
"@vue/compiler-sfc": "^3.3.10",
"@vitejs/plugin-vue": "^4.5.2",
"@vue/compiler-sfc": "^3.3.11",
"dotenv": "^16.3.1",
"eslint": "^8.35.0",
"eslint-plugin-vue": "^9.19.2",
"prettier": "^3.1.0",
"sass": "^1.69.0",
"typescript": "^5.3.2",
"vite": "^5.0.7",
"vite": "^5.0.10",
"vue-eslint-parser": "^9.3.2"
},
"browserslist": [

View File

@@ -2,6 +2,7 @@
<component
:is="item?.render ?? `el-${item.type}`"
v-bind="{ ...handleSearchProps, ...placeholder, clearable: true }"
v-on="{ ...handleEvents }"
v-model.trim="itemValue"
:data="item.type === 'tree-select' ? item.options : []"
:options="['cascader', 'select-v2'].includes(item.type!) ? item.options : []"
@@ -48,19 +49,6 @@ const fieldNames = computed(() => {
};
});
// 接收 enumMap (el 为 select-v2 需单独处理 enumData)
// const enumMap = inject('enumMap', ref(new Map()));
// const columnEnum = computed(() => {
// let enumData = enumMap.value.get(props.item.prop);
// if (!enumData) return [];
// if (props.item?.type === 'select-v2' && props.item.fieldNames) {
// enumData = enumData.map((item: { [key: string]: any }) => {
// return { ...item, label: item[fieldNames.value.label], value: item[fieldNames.value.value] };
// });
// }
// return enumData;
// });
// 处理透传的 searchProps (type 为 tree-select、cascader 的时候需要给下默认 label && value && children)
const handleSearchProps = computed(() => {
const label = fieldNames.value.label;
@@ -77,6 +65,12 @@ const handleSearchProps = computed(() => {
return searchProps;
});
// 处理透传的 事件
const handleEvents = computed(() => {
let itemEvents = props.item?.props ?? {};
return itemEvents;
});
// 处理默认 placeholder
const placeholder = computed(() => {
const search = props.item;

View File

@@ -1,4 +1,5 @@
import { VNode } from 'vue';
import Api from '@/common/Api';
import { VNode, ref, toValue } from 'vue';
export type FieldNamesProps = {
label: string;
@@ -19,6 +20,78 @@ export type SearchItemType =
| 'switch'
| 'slider';
/**
* 表单组件可选项的api信息
*/
export class OptionsApi {
/**
* 请求获取options的api
*/
api: Api;
/**
* 请求参数
*/
params: any;
/**
* 是否立即执行否则在组件focus事件中获取
*/
immediate: boolean = false;
/**
* 是否只获取一次即若以获取则不继续调用该api
*/
once: boolean = true;
/**
* 转换函数主要用于将响应的api结果转换为满足组件options的结构
*/
convertFn: (apiResp: any) => any;
withConvertFn(fn: (apiResp: any) => any) {
this.convertFn = fn;
return this;
}
/**
* 立即获取该可选值
* @returns
*/
withImmediate() {
this.immediate = true;
return this;
}
/**
* 设为非一次性api即每次组件focus获取的时候都允许重新获取options
* @returns this
*/
withNoOnce() {
this.once = false;
return this;
}
/**
* 调用api获取组件可选项
* @returns 组件可选项信息
*/
async getOptions() {
let res = await this.api.request(toValue(this.params));
if (this.convertFn) {
res = this.convertFn(res);
}
return res;
}
static new(api: Api, params: any): OptionsApi {
const oa = new OptionsApi();
oa.api = api;
oa.params = params;
return oa;
}
}
/**
* 搜索项
*/
@@ -43,22 +116,50 @@ export class SearchItem {
*/
options: any;
/**
* 获取可选项的api信息
*/
optionsApi: OptionsApi;
/**
* 插槽名
*/
slot: string;
props?: any; // 搜索项参数,根据 element plus 官方文档来传递,该属性所有值会透传到组件
/**
* 搜索项参数,根据 element plus 官方文档来传递,该属性所有值会透传到组件
*/
props?: any;
tooltip?: string; // 搜索提示
/**
* 搜索项事件,根据 element plus 官方文档来传递,该属性所有值会透传到组件
*/
events?: any;
span?: number; // 搜索项所占用的列数,默认为 1 列
/**
* 搜索提示
*/
tooltip?: string;
offset?: number; // 搜索字段左侧偏移列数
/**
* 搜索项所占用的列数,默认为 1 列
*/
span?: number;
fieldNames: FieldNamesProps; // 指定 label && value && children 的 key 值用于select等类型组件
/**
* 搜索字段左侧偏移列数
*/
offset?: number;
render?: (scope: any) => VNode; // 自定义搜索内容渲染tsx语法
/**
* 指定 label && value && children 的 key 值用于select等类型组件
*/
fieldNames: FieldNamesProps;
/**
* 自定义搜索内容渲染tsx语法
*/
render?: (scope: any) => VNode;
constructor(prop: string, label: string) {
this.prop = prop;
@@ -69,7 +170,7 @@ export class SearchItem {
return new SearchItem(prop, label);
}
static text(prop: string, label: string): SearchItem {
static input(prop: string, label: string): SearchItem {
const tq = new SearchItem(prop, label);
tq.type = 'input';
return tq;
@@ -78,10 +179,11 @@ export class SearchItem {
static select(prop: string, label: string): SearchItem {
const tq = new SearchItem(prop, label);
tq.type = 'select';
tq.withOneProps('filterable', true);
return tq;
}
static date(prop: string, label: string): SearchItem {
static datePicker(prop: string, label: string): SearchItem {
const tq = new SearchItem(prop, label);
tq.type = 'date-picker';
return tq;
@@ -93,8 +195,40 @@ export class SearchItem {
return tq;
}
withSpan(span: number): SearchItem {
this.span = span;
/**
* 为组件设置一个props属性
* @param propsKey 属性key
* @param propsValue 属性value
* @returns
*/
withOneProps(propsKey: string, propsValue: any): SearchItem {
if (!this.props) {
this.props = {};
}
this.props[propsKey] = propsValue;
return this;
}
/**
* 为组件传递组件自身的props属性 (根据 element plus 官方文档来传递,该属性所有值会透传到组件)
* @returns this
*/
withProps(props: any = {}): SearchItem {
this.props = props;
return this;
}
/**
* 为组件传递组件自身事件函数
* @param event 事件名称
* @param fn 事件处理函数
* @returns
*/
bindEvent(event: string, eventFn: any): SearchItem {
if (!this.events) {
this.events = {};
}
this.props[event] = eventFn;
return this;
}
@@ -108,7 +242,38 @@ export class SearchItem {
return this;
}
setOptions(options: any): SearchItem {
/**
* 设置获取组件options可选项值的api配置
* @param optionsApi 可选项api配置
* @returns this
*/
withOptionsApi(optionsApi: OptionsApi): SearchItem {
this.optionsApi = optionsApi;
// 使用api获取组件可选项需要将options转为响应式否则组件无法响应式获取组件可选项
this.options = ref(null);
// 立即执行则直接调用api获取并赋值options
if (this.optionsApi.immediate) {
this.optionsApi.getOptions().then((res) => {
this.options.value = res;
});
} else {
// 注册focus事件在触发focus时赋值options
this.bindEvent('focus', async () => {
if (!toValue(this.options) || !optionsApi.once) {
this.options.value = await this.optionsApi.getOptions();
}
});
}
return this;
}
withSpan(span: number): SearchItem {
this.span = span;
return this;
}
withOptions(options: any): SearchItem {
this.options = options;
return this;
}

View File

@@ -2,7 +2,7 @@
<div>
<transition name="el-zoom-in-top">
<!-- 查询表单 -->
<SearchForm v-if="isShowSearch" :items="searchItems" v-model="queryForm_" :search="queryData" :reset="reset" :search-col="searchCol">
<SearchForm v-if="isShowSearch" :items="tableSearchItems" v-model="queryForm_" :search="queryData" :reset="reset" :search-col="searchCol">
<!-- 遍历父组件传入的 solts 透传给子组件 -->
<template v-for="(_, key) in useSlots()" v-slot:[key]>
<slot :name="key"></slot>
@@ -20,24 +20,63 @@
<div v-if="toolButton" class="header-button-ri">
<slot name="toolButton">
<el-button v-if="showToolButton('refresh')" icon="Refresh" circle @click="execQuery()" />
<div class="tool-button">
<!-- 简易单个搜索项 -->
<div v-if="nowSearchItem" class="simple-search-form">
<el-dropdown v-if="searchItems?.length > 1">
<SvgIcon :size="16" name="ArrowDown" class="mr5 mt7" />
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item
v-for="searchItem in searchItems"
:key="searchItem.prop"
@click="changeSimpleFormItem(searchItem)"
>
{{ searchItem.label }}
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
<el-button v-if="showToolButton('search') && searchItems?.length" icon="Search" circle @click="isShowSearch = !isShowSearch" />
<div class="simple-search-form-label mt5">
<el-text truncated tag="b">{{ `${nowSearchItem?.label} : ` }}</el-text>
</div>
<el-popover
placement="bottom"
title="表格配置"
popper-style="max-height: 550px; overflow: auto; max-width: 450px"
width="auto"
trigger="click"
>
<div v-for="(item, index) in tableColumns" :key="index">
<el-checkbox v-model="item.show" :label="item.label" :true-label="true" :false-label="false" />
<el-form-item style="width: 200px" :key="nowSearchItem.prop">
<SearchFormItem v-if="!nowSearchItem.slot" :item="nowSearchItem" v-model="queryForm_[nowSearchItem.prop]" />
<slot v-else :name="nowSearchItem.slot"></slot>
</el-form-item>
</div>
<template #reference>
<el-button icon="Operation" circle :size="props.size"></el-button>
</template>
</el-popover>
<div>
<el-button v-if="showToolButton('search') && searchItems?.length" icon="Search" circle @click="queryData" />
<!-- <el-button v-if="showToolButton('refresh')" icon="Refresh" circle @click="execQuery()" /> -->
<el-button
v-if="showToolButton('search') && searchItems?.length > 1"
:icon="isShowSearch ? 'ArrowDown' : 'ArrowUp'"
circle
@click="isShowSearch = !isShowSearch"
/>
<el-popover
placement="bottom"
title="表格配置"
popper-style="max-height: 550px; overflow: auto; max-width: 450px"
width="auto"
trigger="click"
>
<div v-for="(item, index) in tableColumns" :key="index">
<el-checkbox v-model="item.show" :label="item.label" :true-label="true" :false-label="false" />
</div>
<template #reference>
<el-button icon="Operation" circle :size="props.size"></el-button>
</template>
</el-popover>
</div>
</div>
</slot>
</div>
</div>
@@ -135,6 +174,8 @@ import { useVModel, useEventListener } from '@vueuse/core';
import Api from '@/common/Api';
import SearchForm from '@/components/SearchForm/index.vue';
import { SearchItem } from '../SearchForm/index';
import SearchFormItem from '../SearchForm/components/SearchFormItem.vue';
import SvgIcon from '@/components/svgIcon/index.vue';
const emit = defineEmits(['update:queryForm', 'update:selectionData', 'pageChange']);
@@ -151,7 +192,7 @@ export interface PageTableProps {
searchItems?: SearchItem[];
queryForm?: any; // 查询表单参数 ==> 非必传(默认为{pageNum:1, pageSize: 10}
border?: boolean; // 是否带有纵向边框 ==> 非必传默认为false
toolButton?: ('refresh' | 'setting' | 'search')[] | boolean; // 是否显示表格功能按钮 ==> 非必传默认为true
toolButton?: ('setting' | 'search')[] | boolean; // 是否显示表格功能按钮 ==> 非必传默认为true
searchCol?: any; // 表格搜索项 每列占比配置 ==> 非必传 { xs: 1, sm: 2, md: 2, lg: 3, xl: 4 }
}
@@ -160,7 +201,6 @@ const props = withDefaults(defineProps<PageTableProps>(), {
columns: () => [],
showSelection: false,
lazy: false,
initParam: {},
queryForm: {
pageNum: 1,
pageSize: 0,
@@ -175,31 +215,43 @@ const props = withDefaults(defineProps<PageTableProps>(), {
// 接收 columns 并设置为响应式
const tableColumns = reactive<TableColumn[]>(props.columns);
const { themeConfig } = storeToRefs(useThemeConfig());
// 接收 searchItems 并设置为响应式
const tableSearchItems = reactive<SearchItem[]>(props.searchItems);
const state = reactive({
pageSizes: [] as any, // 可选每页显示的数据量
isOpenMoreQuery: false,
defaultQueryCount: 2, // 默认显示的查询参数个数展开后每行显示查询条件个数为该值加1。第一行用最后一列来占用按钮
loading: false,
data: [],
total: 0,
// 输入框宽度
inputWidth_: '200px' as any,
formatVal: '', // 格式化后的值
tableMaxHeight: '500px',
});
const { themeConfig } = storeToRefs(useThemeConfig());
// 是否显示搜索模块
const isShowSearch = ref(props.showSearch);
// 控制 ToolButton 显示
const showToolButton = (key: 'refresh' | 'setting' | 'search') => {
const showToolButton = (key: 'setting' | 'search') => {
return Array.isArray(props.toolButton) ? props.toolButton.includes(key) : props.toolButton;
};
const state = reactive({
pageSizes: [] as any, // 可选每页显示的数据量
loading: false,
data: [],
total: 0,
// 输入框宽度
formatVal: '', // 格式化后的值
tableMaxHeight: '500px',
});
const { pageSizes, formatVal, tableMaxHeight } = toRefs(state);
const nowSearchItem: Ref<SearchItem> = ref(null) as any;
/**
* 改变当前的搜索项
* @param searchItem 当前点击的搜索项
*/
const changeSimpleFormItem = (searchItem: SearchItem) => {
// 将之前的值置为空,避免因为只显示一个搜索项却搜索多个条件
queryForm_.value[nowSearchItem.value.prop] = null;
nowSearchItem.value = searchItem;
};
const queryForm_: Ref<any> = useVModel(props, 'queryForm', emit);
watch(
@@ -223,6 +275,10 @@ onMounted(async () => {
calcuTableHeight();
useEventListener(window, 'resize', calcuTableHeight);
if (props.searchItems.length > 0) {
nowSearchItem.value = props.searchItems[0];
}
let pageSize = queryForm_.value.pageSize;
// 如果pageSize设为0则使用系统全局配置的pageSize
if (!pageSize) {
@@ -243,7 +299,7 @@ onMounted(async () => {
});
const calcuTableHeight = () => {
const headerHeight = isShowSearch.value ? 322 : 242;
const headerHeight = isShowSearch.value ? 325 : 245;
state.tableMaxHeight = window.innerHeight - headerHeight + 'px';
};
@@ -340,6 +396,26 @@ defineExpose({
.header-button-ri {
float: right;
.tool-button {
display: flex;
justify-content: space-between;
}
.simple-search-form {
margin-right: 10px;
display: flex;
justify-content: space-between;
::v-deep(.el-form-item__content > *) {
width: 100% !important;
}
.simple-search-form-label {
text-align: right;
margin-right: 5px;
}
}
}
.el-button {
@@ -402,9 +478,9 @@ defineExpose({
border-radius: 50%;
}
}
}
::v-deep(.el-form-item__label) {
font-weight: bold;
::v-deep(.el-form-item__label) {
font-weight: bold;
}
}
</style>

View File

@@ -40,8 +40,8 @@ body,
-webkit-tap-highlight-color: transparent;
background-color: var(--bg-main-color);
font-size: 14px;
// overflow: hidden;
// position: relative;
overflow: hidden;
position: relative;
}
/* 主布局样式

View File

@@ -1,4 +1,6 @@
import { OptionsApi, SearchItem } from '@/components/SearchForm';
import { ContextmenuItem } from '@/components/contextmenu';
import { tagApi } from '../tag/api';
export class TagTreeNode {
/**
@@ -115,3 +117,21 @@ export class NodeType {
return this;
}
}
/**
* 获取标签搜索项配置
* @param resourceType 资源类型
* @returns
*/
export function getTagPathSearchItem(resourceType: number) {
return SearchItem.select('tagPath', '标签').withOptionsApi(
OptionsApi.new(tagApi.getResourceTagPaths, { resourceType }).withConvertFn((res: any) => {
return res.map((x: any) => {
return {
label: x,
value: x,
};
});
})
);
}

View File

@@ -10,12 +10,6 @@
v-model:selection-data="state.selectionData"
:columns="columns"
>
<template #tagPathSelect>
<el-select @focus="getTags" v-model="query.tagPath" placeholder="请选择标签" filterable clearable>
<el-option v-for="item in tags" :key="item" :label="item" :value="item"> </el-option>
</el-select>
</template>
<template #instanceSelect>
<el-select remote :remote-method="getInstances" v-model="query.instanceId" placeholder="输入并选择实例" filterable clearable>
<el-option v-for="item in state.instances" :key="item.id" :label="`${item.name}`" :value="item.id">
@@ -166,10 +160,10 @@ import { TableColumn } from '@/components/pagetable';
import { hasPerms } from '@/components/auth/auth';
import DbSqlExecLog from './DbSqlExecLog.vue';
import { DbType } from './dialect';
import { tagApi } from '../tag/api';
import { TagResourceTypeEnum } from '@/common/commonEnum';
import { useRoute } from 'vue-router';
import { getDbDialect } from './dialect/index';
import { getTagPathSearchItem } from '../component/tag';
import { SearchItem } from '@/components/SearchForm';
const DbEdit = defineAsyncComponent(() => import('./DbEdit.vue'));
@@ -180,7 +174,7 @@ const perms = {
delDb: 'db:del',
};
const searchItems = [SearchItem.slot('tagPath', '标签', 'tagPathSelect'), SearchItem.slot('instanceId', '实例', 'instanceSelect')];
const searchItems = [getTagPathSearchItem(TagResourceTypeEnum.Db.value), SearchItem.slot('instanceId', '实例', 'instanceSelect')];
const columns = ref([
TableColumn.new('instanceName', '实例名'),
@@ -202,7 +196,6 @@ const state = reactive({
row: {} as any,
dbId: 0,
db: '',
tags: [],
instances: [] as any,
/**
* 选中的数据
@@ -253,7 +246,7 @@ const state = reactive({
},
});
const { db, tags, selectionData, query, infoDialog, sqlExecLogDialog, exportDialog, dbEditDialog } = toRefs(state);
const { db, selectionData, query, infoDialog, sqlExecLogDialog, exportDialog, dbEditDialog } = toRefs(state);
onMounted(async () => {
if (Object.keys(actionBtns).length > 0) {
@@ -286,10 +279,6 @@ const onBeforeCloseInfoDialog = () => {
state.infoDialog.instance = null;
};
const getTags = async () => {
state.tags = await tagApi.getResourceTagPaths.request({ resourceType: TagResourceTypeEnum.Db.value });
};
const getInstances = async (instanceName = '') => {
if (!instanceName) {
state.instances = [];

View File

@@ -56,7 +56,7 @@ const props = defineProps({
const searchItems = [
SearchItem.slot('db', '数据库', 'dbSelect'),
SearchItem.text('table', '表名'),
SearchItem.input('table', '表名'),
SearchItem.select('type', '操作类型').withEnum(DbSqlExecTypeEnum),
];

View File

@@ -79,7 +79,7 @@ const perms = {
delInstance: 'db:instance:del',
};
const searchItems = [SearchItem.text('name', '名称')];
const searchItems = [SearchItem.input('name', '名称')];
const columns = ref([
TableColumn.new('name', '名称'),

View File

@@ -91,6 +91,7 @@
<el-date-picker
v-if="nowUpdateCell.dataType == DataType.Date"
:ref="(el: any) => el?.focus()"
@change="onExitEditMode(rowData, column, rowIndex)"
@blur="onExitEditMode(rowData, column, rowIndex)"
class="edit-time-picker mb4"
size="small"
@@ -103,18 +104,22 @@
<el-date-picker
v-if="nowUpdateCell.dataType == DataType.DateTime"
:ref="(el: any) => el?.focus()"
@change="onExitEditMode(rowData, column, rowIndex)"
@blur="onExitEditMode(rowData, column, rowIndex)"
class="edit-time-picker mb4"
size="small"
:key="rowIndex"
v-model="rowData[column.dataKey!]"
:clearable="false"
type="datetime"
value-format="YYYY-MM-DD HH:mm:ss"
placeholder="选择日期时间"
:teleported="false"
/>
<el-time-picker
v-if="nowUpdateCell.dataType == DataType.Time"
:ref="(el: any) => el?.focus()"
@change="onExitEditMode(rowData, column, rowIndex)"
@blur="onExitEditMode(rowData, column, rowIndex)"
class="edit-time-picker mb4"
size="small"

View File

@@ -10,12 +10,6 @@
v-model:selection-data="state.selectionData"
:columns="columns"
>
<template #tagPathSelect>
<el-select @focus="getTags" v-model="params.tagPath" placeholder="请选择标签" @clear="search" filterable clearable>
<el-option v-for="item in tags" :key="item" :label="item" :value="item"> </el-option>
</el-select>
</template>
<template #tableHeader>
<el-button v-auth="perms.addMachine" type="primary" icon="plus" @click="openFormDialog(false)" plain>添加 </el-button>
<el-button v-auth="perms.delMachine" :disabled="selectionData.length < 1" @click="deleteMachine()" type="danger" icon="delete">删除</el-button>
@@ -193,9 +187,9 @@ import PageTable from '@/components/pagetable/PageTable.vue';
import { TableColumn } from '@/components/pagetable';
import { hasPerms } from '@/components/auth/auth';
import { formatByteSize } from '@/common/utils/format';
import { tagApi } from '../tag/api';
import { TagResourceTypeEnum } from '@/common/commonEnum';
import { SearchItem } from '@/components/SearchForm';
import { getTagPathSearchItem } from '../component/tag';
// 组件
const TerminalDialog = defineAsyncComponent(() => import('@/components/terminal/TerminalDialog.vue'));
@@ -219,7 +213,7 @@ const perms = {
closeCli: 'machine:close-cli',
};
const searchItems = [SearchItem.slot('tagPath', '标签', 'tagPathSelect'), SearchItem.text('ip', 'IP'), SearchItem.text('name', '名称')];
const searchItems = [getTagPathSearchItem(TagResourceTypeEnum.Machine.value), SearchItem.input('ip', 'IP'), SearchItem.input('name', '名称')];
const columns = [
TableColumn.new('name', '名称'),
@@ -237,7 +231,6 @@ const columns = [
const actionBtns = hasPerms([perms.updateMachine, perms.closeCli]);
const state = reactive({
tags: [] as any,
params: {
pageNum: 1,
pageSize: 0,
@@ -283,8 +276,7 @@ const state = reactive({
},
});
const { tags, params, infoDialog, selectionData, serviceDialog, processDialog, fileDialog, machineStatsDialog, machineEditDialog, machineRecDialog } =
toRefs(state);
const { params, infoDialog, selectionData, serviceDialog, processDialog, fileDialog, machineStatsDialog, machineEditDialog, machineRecDialog } = toRefs(state);
onMounted(async () => {});
@@ -357,10 +349,6 @@ const closeCli = async (row: any) => {
search();
};
const getTags = async () => {
state.tags = await tagApi.getResourceTagPaths.request({ resourceType: TagResourceTypeEnum.Machine.value });
};
const openFormDialog = async (machine: any) => {
let dialogTitle;
if (machine) {

View File

@@ -40,7 +40,7 @@ const state = reactive({
pageSize: 0,
name: null,
},
searchItems: [SearchItem.text('name', '凭证名称')],
searchItems: [SearchItem.input('name', '凭证名称')],
columns: [
TableColumn.new('name', '名称'),
TableColumn.new('authMethod', '认证方式').typeTag(AuthMethodEnum),

View File

@@ -56,10 +56,7 @@ const props = defineProps({
const emit = defineEmits(['update:visible', 'update:data', 'cancel']);
const searchItems = [
SearchItem.slot('machineId', '机器', 'machineSelect'),
SearchItem.select('status', '状态').setOptions(Object.values(CronJobExecStatusEnum)),
];
const searchItems = [SearchItem.slot('machineId', '机器', 'machineSelect'), SearchItem.select('status', '状态').withEnum(CronJobExecStatusEnum)];
const columns = ref([
TableColumn.new('machineIp', '机器IP').setMinWidth(120),

View File

@@ -50,7 +50,7 @@ const perms = {
delCronJob: 'machine:cronjob:del',
};
const searchItems = [SearchItem.text('name', '名称'), SearchItem.select('status', '状态').withEnum(CronJobStatusEnum)];
const searchItems = [SearchItem.input('name', '名称'), SearchItem.select('status', '状态').withEnum(CronJobStatusEnum)];
const columns = ref([
TableColumn.new('key', 'key'),

View File

@@ -10,12 +10,6 @@
v-model:selection-data="selectionData"
:columns="columns"
>
<template #tagPathSelect>
<el-select @focus="getTags" v-model="query.tagPath" placeholder="请选择标签" @clear="search" filterable clearable>
<el-option v-for="item in tags" :key="item" :label="item" :value="item"> </el-option>
</el-select>
</template>
<template #tableHeader>
<el-button type="primary" icon="plus" @click="editMongo(true)" plain>添加</el-button>
<el-button type="danger" icon="delete" :disabled="selectionData.length < 1" @click="deleteMongo" plain>删除 </el-button>
@@ -55,9 +49,8 @@ import ResourceTag from '../component/ResourceTag.vue';
import PageTable from '@/components/pagetable/PageTable.vue';
import { TableColumn } from '@/components/pagetable';
import { TagResourceTypeEnum } from '@/common/commonEnum';
import { tagApi } from '../tag/api';
import { useRoute } from 'vue-router';
import { SearchItem } from '@/components/SearchForm';
import { getTagPathSearchItem } from '../component/tag';
const MongoEdit = defineAsyncComponent(() => import('./MongoEdit.vue'));
const MongoDbs = defineAsyncComponent(() => import('./MongoDbs.vue'));
@@ -66,7 +59,8 @@ const MongoRunCommand = defineAsyncComponent(() => import('./MongoRunCommand.vue
const route = useRoute();
const pageTableRef: Ref<any> = ref(null);
const searchItems = [SearchItem.slot('tagPath', '标签', 'tagPathSelect')];
const searchItems = [getTagPathSearchItem(TagResourceTypeEnum.Mongo.value)];
const columns = [
TableColumn.new('name', '名称'),
TableColumn.new('uri', '连接uri'),
@@ -77,7 +71,6 @@ const columns = [
];
const state = reactive({
tags: [],
dbOps: {
dbId: 0,
db: '',
@@ -97,7 +90,7 @@ const state = reactive({
usersVisible: false,
});
const { tags, selectionData, query, mongoEditDialog, dbsVisible, usersVisible } = toRefs(state);
const { selectionData, query, mongoEditDialog, dbsVisible, usersVisible } = toRefs(state);
onMounted(async () => {});
@@ -137,10 +130,6 @@ const search = async () => {
pageTableRef.value.search();
};
const getTags = async () => {
state.tags = await tagApi.getResourceTagPaths.request({ resourceType: TagResourceTypeEnum.Mongo.value });
};
const editMongo = async (data: any) => {
if (!data) {
state.mongoEditDialog.data = null;

View File

@@ -10,12 +10,6 @@
v-model:selection-data="selectionData"
:columns="columns"
>
<template #tagPathSelect>
<el-select @focus="getTags" v-model="query.tagPath" placeholder="请选择标签" @clear="search" filterable clearable>
<el-option v-for="item in tags" :key="item" :label="item" :value="item"> </el-option>
</el-select>
</template>
<template #tableHeader>
<el-button type="primary" icon="plus" @click="editRedis(false)" plain>添加</el-button>
<el-button type="danger" icon="delete" :disabled="selectionData.length < 1" @click="deleteRedis" plain>删除 </el-button>
@@ -146,7 +140,6 @@
<redis-edit
@val-change="search"
:tags="tags"
:title="redisEditDialog.title"
v-model:visible="redisEditDialog.visible"
v-model:redis="redisEditDialog.data"
@@ -164,15 +157,15 @@ import { dateFormat } from '@/common/utils/date';
import ResourceTag from '../component/ResourceTag.vue';
import PageTable from '@/components/pagetable/PageTable.vue';
import { TableColumn } from '@/components/pagetable';
import { tagApi } from '../tag/api';
import { TagResourceTypeEnum } from '@/common/commonEnum';
import { useRoute } from 'vue-router';
import { SearchItem } from '@/components/SearchForm';
import { getTagPathSearchItem } from '../component/tag';
const route = useRoute();
const pageTableRef: Ref<any> = ref(null);
const searchItems = [SearchItem.slot('tagPath', '标签', 'tagPathSelect')];
const searchItems = [getTagPathSearchItem(TagResourceTypeEnum.Redis.value)];
const columns = ref([
TableColumn.new('name', '名称'),
TableColumn.new('host', 'host:port'),
@@ -183,7 +176,6 @@ const columns = ref([
]);
const state = reactive({
tags: [],
selectionData: [],
query: {
tagPath: '',
@@ -218,7 +210,7 @@ const state = reactive({
},
});
const { tags, selectionData, query, detailDialog, clusterInfoDialog, infoDialog, redisEditDialog } = toRefs(state);
const { selectionData, query, detailDialog, clusterInfoDialog, infoDialog, redisEditDialog } = toRefs(state);
onMounted(async () => {});
@@ -272,10 +264,6 @@ const search = () => {
pageTableRef.value.search();
};
const getTags = async () => {
state.tags = await tagApi.getResourceTagPaths.request({ resourceType: TagResourceTypeEnum.Redis.value });
};
const editRedis = async (data: any) => {
if (!data) {
state.redisEditDialog.data = null;

View File

@@ -147,7 +147,7 @@ const tagTreeRef: any = ref(null);
const pageTableRef: Ref<any> = ref(null);
const showMemPageTableRef: Ref<any> = ref(null);
const searchItems = [SearchItem.text('name', '团队名称')];
const searchItems = [SearchItem.input('name', '团队名称')];
const columns = [
TableColumn.new('name', '团队名称'),
TableColumn.new('remark', '备注'),
@@ -170,7 +170,7 @@ const state = reactive({
},
selectionData: [],
showMemDialog: {
searchItems: [SearchItem.text('username', '用户名').withSpan(2)],
searchItems: [SearchItem.input('username', '用户名').withSpan(2)],
columns: [
TableColumn.new('name', '姓名'),
TableColumn.new('username', '账号'),

View File

@@ -96,7 +96,7 @@ const perms = {
changeAccountStatus: 'account:changeStatus',
};
const searchItems = [SearchItem.text('username', '用户名')];
const searchItems = [SearchItem.input('username', '用户名')];
const columns = [
TableColumn.new('name', '姓名'),
TableColumn.new('username', '用户名'),

View File

@@ -55,7 +55,7 @@ const perms = {
saveRoleResource: 'role:saveResources',
};
const searchItems = [SearchItem.text('name', '角色名')];
const searchItems = [SearchItem.input('name', '角色名')];
const columns = ref([
TableColumn.new('name', '角色名称'),
TableColumn.new('code', '角色code'),

View File

@@ -21,7 +21,7 @@ import { SearchItem } from '@/components/SearchForm';
const searchItems = [
SearchItem.slot('creatorId', '操作人', 'selectAccount'),
SearchItem.select('type', '操作结果').withEnum(LogTypeEnum),
SearchItem.text('description', '描述'),
SearchItem.input('description', '描述'),
];
const columns = [
@@ -47,6 +47,9 @@ const state = reactive({
const { query, accounts } = toRefs(state);
const getAccount = (username: any) => {
if (!username) {
return;
}
accountApi.list.request({ username }).then((res) => {
state.accounts = res.list;
});

View File

@@ -435,20 +435,10 @@
"@typescript-eslint/types" "6.7.4"
eslint-visitor-keys "^3.4.1"
"@vitejs/plugin-vue@^4.5.1":
version "4.5.1"
resolved "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-4.5.1.tgz#84815bfeb46928c03a9ed765e4a8425c22345e15"
integrity sha512-DaUzYFr+2UGDG7VSSdShKa9sIWYBa1LL8KC0MNOf2H5LjcTPjob0x8LbkqXWmAtbANJCkpiQTj66UVcQkN2s3g==
"@vue/compiler-core@3.3.10":
version "3.3.10"
resolved "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.3.10.tgz#9ca4123a1458df43db641aaa8b7d1e636aa22545"
integrity sha512-doe0hODR1+i1menPkRzJ5MNR6G+9uiZHIknK3Zn5OcIztu6GGw7u0XUzf3AgB8h/dfsZC9eouzoLo3c3+N/cVA==
dependencies:
"@babel/parser" "^7.23.5"
"@vue/shared" "3.3.10"
estree-walker "^2.0.2"
source-map-js "^1.0.2"
"@vitejs/plugin-vue@^4.5.2":
version "4.5.2"
resolved "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-4.5.2.tgz#1212d81bc83680e14448fefe55abd9fe1ed49ed1"
integrity sha512-UGR3DlzLi/SaVBPX0cnSyE37vqxU3O6chn8l0HJNzQzDia6/Au2A4xKv+iIJW8w2daf80G7TYHhi1pAUjdZ0bQ==
"@vue/compiler-core@3.3.11":
version "3.3.11"
@@ -460,14 +450,6 @@
estree-walker "^2.0.2"
source-map-js "^1.0.2"
"@vue/compiler-dom@3.3.10":
version "3.3.10"
resolved "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.3.10.tgz#183811252be6aff4ac923f783124bb1590301907"
integrity sha512-NCrqF5fm10GXZIK0GrEAauBqdy+F2LZRt3yNHzrYjpYBuRssQbuPLtSnSNjyR9luHKkWSH8we5LMB3g+4z2HvA==
dependencies:
"@vue/compiler-core" "3.3.10"
"@vue/shared" "3.3.10"
"@vue/compiler-dom@3.3.11":
version "3.3.11"
resolved "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.3.11.tgz#36a76ea3a296d41bad133a6912cb0a847d969e4f"
@@ -476,7 +458,7 @@
"@vue/compiler-core" "3.3.11"
"@vue/shared" "3.3.11"
"@vue/compiler-sfc@3.3.11":
"@vue/compiler-sfc@3.3.11", "@vue/compiler-sfc@^3.3.11":
version "3.3.11"
resolved "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.3.11.tgz#acfae240c875d067e0e2c9a4e2d910074408c73b"
integrity sha512-U4iqPlHO0KQeK1mrsxCN0vZzw43/lL8POxgpzcJweopmqtoYy9nljJzWDIQS3EfjiYhfdtdk9Gtgz7MRXnz3GA==
@@ -492,30 +474,6 @@
postcss "^8.4.32"
source-map-js "^1.0.2"
"@vue/compiler-sfc@^3.3.10":
version "3.3.10"
resolved "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.3.10.tgz#8eb97d42f276089ec58fd0565ef3a813bceeaa87"
integrity sha512-xpcTe7Rw7QefOTRFFTlcfzozccvjM40dT45JtrE3onGm/jBLZ0JhpKu3jkV7rbDFLeeagR/5RlJ2Y9SvyS0lAg==
dependencies:
"@babel/parser" "^7.23.5"
"@vue/compiler-core" "3.3.10"
"@vue/compiler-dom" "3.3.10"
"@vue/compiler-ssr" "3.3.10"
"@vue/reactivity-transform" "3.3.10"
"@vue/shared" "3.3.10"
estree-walker "^2.0.2"
magic-string "^0.30.5"
postcss "^8.4.32"
source-map-js "^1.0.2"
"@vue/compiler-ssr@3.3.10":
version "3.3.10"
resolved "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.3.10.tgz#5a1b14a358cb3960a4edbce0ade90548e452fcaa"
integrity sha512-12iM4jA4GEbskwXMmPcskK5wImc2ohKm408+o9iox3tfN9qua8xL0THIZtoe9OJHnXP4eOWZpgCAAThEveNlqQ==
dependencies:
"@vue/compiler-dom" "3.3.10"
"@vue/shared" "3.3.10"
"@vue/compiler-ssr@3.3.11":
version "3.3.11"
resolved "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.3.11.tgz#598942a73b64f2bd3f95908b104a7fbb55fc41a2"
@@ -529,17 +487,6 @@
resolved "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-6.5.0.tgz#98b99425edee70b4c992692628fa1ea2c1e57d07"
integrity sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q==
"@vue/reactivity-transform@3.3.10":
version "3.3.10"
resolved "https://registry.npmmirror.com/@vue/reactivity-transform/-/reactivity-transform-3.3.10.tgz#b045776cc954bb57883fd305db7a200d42993768"
integrity sha512-0xBdk+CKHWT+Gev8oZ63Tc0qFfj935YZx+UAynlutnrDZ4diFCVFMWixn65HzjE3S1iJppWOo6Tt1OzASH7VEg==
dependencies:
"@babel/parser" "^7.23.5"
"@vue/compiler-core" "3.3.10"
"@vue/shared" "3.3.10"
estree-walker "^2.0.2"
magic-string "^0.30.5"
"@vue/reactivity-transform@3.3.11":
version "3.3.11"
resolved "https://registry.npmmirror.com/@vue/reactivity-transform/-/reactivity-transform-3.3.11.tgz#2bd486f4eff60c8724309925618891e722fcfadc"
@@ -583,11 +530,6 @@
"@vue/compiler-ssr" "3.3.11"
"@vue/shared" "3.3.11"
"@vue/shared@3.3.10":
version "3.3.10"
resolved "https://registry.npmmirror.com/@vue/shared/-/shared-3.3.10.tgz#1583a8d85a957d8b819078c465d2a11db7914b2f"
integrity sha512-2y3Y2J1a3RhFa0WisHvACJR2ncvWiVHcP8t0Inxo+NKz+8RKO4ZV8eZgCxRgQoA6ITfV12L4E6POOL9HOU5nqw==
"@vue/shared@3.3.11":
version "3.3.11"
resolved "https://registry.npmmirror.com/@vue/shared/-/shared-3.3.11.tgz#f6a038e15237edefcc90dbfe7edb806dd355c7bd"
@@ -692,10 +634,10 @@ array-union@^2.1.0:
resolved "https://registry.npm.taobao.org/array-union/download/array-union-2.1.0.tgz?cache=0&sync_timestamp=1614624262896&other_urls=https%3A%2F%2Fregistry.npm.taobao.org%2Farray-union%2Fdownload%2Farray-union-2.1.0.tgz"
integrity sha1-t5hCCtvrHego2ErNii4j0+/oXo0=
asciinema-player@^3.6.2:
version "3.6.2"
resolved "https://registry.npmmirror.com/asciinema-player/-/asciinema-player-3.6.2.tgz#f62133f8d38875839881cd15ded713c6022021bd"
integrity sha512-698O3/Vm2+V6uFlc6oYma67IZByQsiNpduhEGhuqrxBmKpIYpgouLNNJ3R8DrRPTNNMISHfnLgvAp1x8ChgrTw==
asciinema-player@^3.6.3:
version "3.6.3"
resolved "https://registry.npmmirror.com/asciinema-player/-/asciinema-player-3.6.3.tgz#b029290df39805180c986656ce39b8517b351422"
integrity sha512-62aDgLpbuduhmpFfNgPOzf6fOluACLsftVnjpWJjUXX6dqhqTckFqWoJ+ayA0XjSlZ9l9wXTcJqRqvAAIpMblg==
dependencies:
"@babel/runtime" "^7.21.0"
solid-js "^1.3.0"
@@ -923,10 +865,10 @@ echarts@^5.4.3:
tslib "2.3.0"
zrender "5.4.4"
element-plus@^2.4.3:
version "2.4.3"
resolved "https://registry.npmmirror.com/element-plus/-/element-plus-2.4.3.tgz#ff21d0207d71752eb6a47a46609bc667f222841f"
integrity sha512-b3q26j+lM4SBqiyzw8HybybGnP2pk4MWgrnzzzYW5qKQUgV6EG1Zg7nMCfgCVccI8tNvZoTiUHb2mFaiB9qT8w==
element-plus@^2.4.4:
version "2.4.4"
resolved "https://registry.npmmirror.com/element-plus/-/element-plus-2.4.4.tgz#962be40b4843381af04b8f799bfc142072184b8b"
integrity sha512-TlKubXJgxwhER0dw+8ULn9hr9kZjraV4R6Q/eidwWUwCKxwXYPBGmMKsZ/85tlxlhMYbcLZd/YZh6G3QkHX4fg==
dependencies:
"@ctrl/tinycolor" "^3.4.1"
"@element-plus/icons-vue" "^2.3.1"
@@ -1944,10 +1886,10 @@ uuid@^9.0.1:
resolved "https://registry.npmmirror.com/uuid/-/uuid-9.0.1.tgz#e188d4c8853cc722220392c424cd637f32293f30"
integrity sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA==
vite@^5.0.7:
version "5.0.7"
resolved "https://registry.npmmirror.com/vite/-/vite-5.0.7.tgz#ad081d735f6769f76b556818500bdafb72c3fe93"
integrity sha512-B4T4rJCDPihrQo2B+h1MbeGL/k/GMAHzhQ8S0LjQ142s6/+l3hHTT095ORvsshj4QCkoWu3Xtmob5mazvakaOw==
vite@^5.0.10:
version "5.0.10"
resolved "https://registry.npmmirror.com/vite/-/vite-5.0.10.tgz#1e13ef5c3cf5aa4eed81f5df6d107b3c3f1f6356"
integrity sha512-2P8J7WWgmc355HUMlFrwofacvr98DAjoE52BfdbwQtyLH06XKwaL/FMnmKM2crF0iX4MpmMKoDlNCB1ok7zHCw==
dependencies:
esbuild "^0.19.3"
postcss "^8.4.32"