refactor: PageTable组件重构、使用useFetch封装接口请求

This commit is contained in:
meilin.huang
2023-12-11 01:00:09 +08:00
parent 6709135a0b
commit e444500835
40 changed files with 596 additions and 814 deletions

View File

@@ -1,5 +1,5 @@
import request from './request';
import { randomUuid } from './utils/string';
import { useApiFetch } from './useRequest';
/**
* 可用于各模块定义各自api请求
@@ -21,8 +21,6 @@ class Api {
*/
beforeHandler: Function;
static abortControllers: Map<string, AbortController> = new Map();
constructor(url: string, method: string) {
this.url = url;
this.method = method;
@@ -46,68 +44,38 @@ class Api {
}
/**
* 请求对应的该api
* 响应式使用该api
* @param params 响应式params
* @param reqOptions 其他可选值
* @returns
*/
useApi<T>(params: any = null, reqOptions: RequestInit = {}) {
return useApiFetch<T>(this, params, reqOptions);
}
/**
* fetch 请求对应的该api
* @param {Object} param 请求该api的参数
*/
request(param: any = null, options: any = {}): Promise<any> {
async request(param: any = null, options: any = {}): Promise<any> {
const { execute, data } = this.useApi(param, options);
await execute();
return data.value;
}
/**
* xhr 请求对应的该api
* @param {Object} param 请求该api的参数
*/
async xhrReq(param: any = null, options: any = {}): Promise<any> {
if (this.beforeHandler) {
this.beforeHandler(param);
}
return request.request(this.method, this.url, param, options);
}
/**
* 允许取消的请求, 使用Api.cancelReq(key) 取消请求
* @param key 用于取消该key关联的请求
* @param {Object} param 请求该api的参数
*/
allowCancelReq(key: string, param: any = null, options: RequestInit = {}): Promise<any> {
let controller = Api.abortControllers.get(key);
if (!controller) {
controller = new AbortController();
Api.abortControllers.set(key, controller);
}
options.signal = controller.signal;
return this.request(param, options);
return request.xhrReq(this.method, this.url, param, options);
}
/** 静态方法 **/
/**
* 取消请求
* @param key 请求key
*/
static cancelReq(key: string) {
let controller = Api.abortControllers.get(key);
if (controller) {
controller.abort();
Api.removeAbortKey(key);
}
}
static removeAbortKey(key: string) {
if (key) {
console.log('remove abort key: ', key);
Api.abortControllers.delete(key);
}
}
/**
* 根据旧key生成新的abort key可能旧key未取消造成多余无用对象
* @param oldKey 旧key
* @returns key
*/
static genAbortKey(oldKey: string) {
if (!oldKey) {
return randomUuid();
}
if (Api.abortControllers.get(oldKey)) {
return oldKey;
}
return randomUuid();
}
/**
* 静态工厂返回Api对象并设置url与method属性
* @param url url
@@ -151,3 +119,8 @@ class Api {
}
export default Api;
export class PageRes {
list: any[] = [];
total: number = 0;
}

View File

@@ -4,10 +4,12 @@ import { getClientId, getToken } from './utils/storage';
import { templateResolve } from './utils/string';
import { ElMessage } from 'element-plus';
import axios from 'axios';
import { useApiFetch } from './useRequest';
import Api from './Api';
export default {
request,
fetchReq,
xhrReq,
get,
post,
put,
@@ -30,7 +32,7 @@ export interface Result {
data?: any;
}
enum ResultEnum {
export enum ResultEnum {
SUCCESS = 200,
ERROR = 400,
PARAM_ERROR = 405,
@@ -38,7 +40,7 @@ enum ResultEnum {
NO_PERMISSION = 501,
}
const baseUrl: string = config.baseApiUrl;
export const baseUrl: string = config.baseApiUrl;
// const baseUrl: string = 'http://localhost:18888/api';
// const baseWsUrl: string = config.baseWsUrl;
@@ -115,14 +117,15 @@ axiosInst.interceptors.response.use(
);
/**
* 请求uri
* 该方法已处理请求结果中code != 200的message提示,如需其他错误处理(取消加载状态,重置对象状态等等),可catch继续处理
* xhr请求url
*
* @param {Object} method 请求方法(GET,POST,PUT,DELTE等)
* @param {Object} uri uri
* @param {Object} params 参数
* @param method 请求方法
* @param url url
* @param params 参数
* @param options 可选
* @returns
*/
function request(method: string, url: string, params: any = null, options: any = {}): Promise<any> {
export function xhrReq(method: string, url: string, params: any = null, options: any = {}) {
if (!url) {
throw new Error('请求url不能为空');
}
@@ -157,6 +160,21 @@ function request(method: string, url: string, params: any = null, options: any =
});
}
/**
* fetch请求url
*
* 该方法已处理请求结果中code != 200的message提示,如需其他错误处理(取消加载状态,重置对象状态等等),可catch继续处理
*
* @param {Object} method 请求方法(GET,POST,PUT,DELTE等)
* @param {Object} uri uri
* @param {Object} params 参数
*/
async function request(method: string, url: string, params: any = null, options: any = {}): Promise<any> {
const { execute, data } = useApiFetch(Api.create(url, method), params, options);
await execute();
return data.value;
}
/**
* get请求uri
* 该方法已处理请求结果中code != 200的message提示,如需其他错误处理(取消加载状态,重置对象状态等等),可catch继续处理
@@ -190,64 +208,6 @@ export function joinClientParams(): string {
return `token=${getToken()}&clientId=${getClientId()}`;
}
async function fetchReq(method: string, url: string, params: any = null, options: RequestInit = {}): Promise<any> {
options.method = method;
if (params) {
// post和put使用json格式传参
if (method === 'post' || method === 'put') {
options.body = JSON.stringify(params);
} else {
const searchParam = new URLSearchParams();
Object.keys(params).forEach((key) => {
const val = params[key];
if (val) {
searchParam.append(key, val);
}
});
url = `${url}?${searchParam.toString()}`;
}
}
// Part 1: Add headers and attach auth token
const headers = new Headers(options.headers || {});
const token = getToken();
if (token) {
headers.set('Authorization', token);
headers.set('ClientId', getClientId());
}
options.headers = headers;
try {
const res: Response = await fetch(`${baseUrl}${url}`, options);
if (!res.ok) {
throw new Error(`请求响应错误: 状态码=${res.status}`);
}
const jsonRes = await res.json();
// 获取请求返回结果
const result: Result = jsonRes;
return parseResult(result);
} catch (e: any) {
const rejectPromise = Promise.reject(e);
if (e?.name == 'AbortError') {
console.log('请求已取消');
return rejectPromise;
}
if (e.message) {
notifyErrorMsg(e.message);
return rejectPromise;
}
notifyErrorMsg('网络请求错误');
console.error(e);
return rejectPromise;
}
}
function parseResult(result: Result) {
if (result.code === ResultEnum.SUCCESS) {
return result.data;

View File

@@ -0,0 +1,134 @@
import router from '../router';
import { getClientId, getToken } from './utils/storage';
import { templateResolve } from './utils/string';
import { ElMessage } from 'element-plus';
import { createFetch } from '@vueuse/core';
import Api from './Api';
import { Result, ResultEnum } from './request';
import config from './config';
import { unref } from 'vue';
const baseUrl: string = config.baseApiUrl;
const useCustomFetch = createFetch({
baseUrl: baseUrl,
combination: 'chain',
options: {
immediate: false,
timeout: 60000,
// beforeFetch in pre-configured instance will only run when the newly spawned instance do not pass beforeFetch
async beforeFetch({ options }) {
const token = getToken();
const headers = new Headers(options.headers || {});
if (token) {
headers.set('Authorization', token);
headers.set('ClientId', getClientId());
}
options.headers = headers;
return { options };
},
async afterFetch(ctx) {
const result: Result = await ctx.response.json();
ctx.data = result;
return ctx;
},
},
});
export function useApiFetch<T>(api: Api, params: any = null, reqOptions: RequestInit = {}) {
const uaf = useCustomFetch<T>(api.url, {
beforeFetch({ url, options }) {
options.method = api.method;
if (!params) {
return;
}
let paramsValue = unref(params);
if (api.beforeHandler) {
paramsValue = api.beforeHandler(paramsValue);
}
let apiUrl = url;
// 简单判断该url是否是restful风格
if (apiUrl.indexOf('{') != -1) {
apiUrl = templateResolve(apiUrl, paramsValue);
}
if (paramsValue) {
const method = options.method?.toLowerCase();
// post和put使用json格式传参
if (method === 'post' || method === 'put') {
options.body = JSON.stringify(paramsValue);
} else {
const searchParam = new URLSearchParams();
Object.keys(paramsValue).forEach((key) => {
const val = paramsValue[key];
if (val) {
searchParam.append(key, val);
}
});
apiUrl = `${apiUrl}?${searchParam.toString()}`;
}
}
return {
url: apiUrl,
options: {
...options,
...reqOptions,
},
};
},
});
return {
execute: async function () {
try {
await uaf.execute(true);
} catch (e: any) {
const rejectPromise = Promise.reject(e);
if (e?.name == 'AbortError') {
console.log('请求已取消');
return rejectPromise;
}
console.error(e);
ElMessage.error('网络请求错误');
return rejectPromise;
}
const result: Result = uaf.data.value as any;
if (!result) {
ElMessage.error('网络请求失败');
return Promise.reject(result);
}
// 如果返回为成功结果则将结果的data赋值给响应式data
if (result.code === ResultEnum.SUCCESS) {
uaf.data.value = result.data;
return;
}
// 如果提示没有权限则移除token使其重新登录
if (result.code === ResultEnum.NO_PERMISSION) {
router.push({
path: '/401',
});
}
// 如果返回的code不为成功则会返回对应的错误msg则直接统一通知即可。忽略登录超时或没有权限的提示直接跳转至401页面
if (result.msg && result?.code != ResultEnum.NO_PERMISSION) {
ElMessage.error(result.msg);
uaf.error.value = new Error(result.msg);
}
return Promise.reject(result);
},
isFetching: uaf.isFetching,
data: uaf.data,
abort: uaf.abort,
};
}

View File

@@ -11,7 +11,7 @@
<div class="query" ref="queryRef">
<div>
<div v-if="props.query.length > 0">
<el-form :model="props.queryForm" label-width="auto" :size="props.size">
<el-form :model="queryForm_" label-width="auto" :size="props.size">
<el-row
v-for="i in Math.ceil((props.query.length + 1) / (defaultQueryCount + 1))"
:key="i"
@@ -104,9 +104,9 @@
v-bind="$attrs"
:max-height="tableMaxHeight"
@selection-change="handleSelectionChange"
:data="props.data"
:data="state.data"
highlight-current-row
v-loading="loadingData"
v-loading="state.loading"
:size="props.size"
>
<el-table-column v-if="props.showSelection" type="selection" width="40" />
@@ -171,9 +171,9 @@
@size-change="handleSizeChange"
style="text-align: right"
layout="prev, pager, next, total, sizes, jumper"
:total="props.total"
v-model:current-page="state.pageNum"
v-model:page-size="state.pageSize"
:total="state.total"
v-model:current-page="queryForm_.pageNum"
v-model:page-size="queryForm_.pageSize"
:page-sizes="pageSizes"
/>
</el-row>
@@ -182,11 +182,13 @@
</template>
<script lang="ts" setup>
import { toRefs, watch, reactive, onMounted } from 'vue';
import { toRefs, watch, reactive, onMounted, Ref } from 'vue';
import { TableColumn, TableQuery } from './index';
import EnumTag from '@/components/enumtag/EnumTag.vue';
import { useThemeConfig } from '@/store/themeConfig';
import { storeToRefs } from 'pinia';
import { useVModel } from '@vueuse/core';
import Api from '@/common/Api';
const emit = defineEmits(['update:queryForm', 'update:pageNum', 'update:pageSize', 'update:selectionData', 'pageChange']);
@@ -216,22 +218,19 @@ const props = defineProps({
},
required: true,
},
// 表格数据
data: {
type: Array,
// 调用分页数据的api
pageApi: {
type: Api,
required: true,
},
total: {
type: [Number],
default: 0,
// 数据处理回调函数,用于将请求回来的数据二次加工处理等
dataHandlerFn: {
type: Function,
},
pageNum: {
type: Number,
default: 1,
},
pageSize: {
type: [Number],
default: 10,
// 懒加载即需要手动调用search方法才可调接口获取数据不会在mounted的时候调用。
lazy: {
type: Boolean,
default: false,
},
// 查询条件配置
query: {
@@ -244,7 +243,10 @@ const props = defineProps({
queryForm: {
type: Object,
default: function () {
return {};
return {
pageNum: 1,
pageSize: 10,
};
},
},
});
@@ -253,56 +255,36 @@ const { themeConfig } = storeToRefs(useThemeConfig());
const state = reactive({
pageSizes: [] as any, // 可选每页显示的数据量
pageSize: 10,
pageNum: 1,
isOpenMoreQuery: false,
defaultQueryCount: 2, // 默认显示的查询参数个数展开后每行显示查询条件个数为该值加1。第一行用最后一列来占用按钮
queryForm_: {} as any,
loadingData: false,
loading: false,
data: [],
total: 0,
// 输入框宽度
inputWidth_: '200px' as any,
formatVal: '', // 格式化后的值
tableMaxHeight: window.innerHeight - 240 + 'px',
});
const { pageSizes, isOpenMoreQuery, defaultQueryCount, queryForm_, inputWidth_, formatVal, loadingData, tableMaxHeight } = toRefs(state);
const { pageSizes, isOpenMoreQuery, defaultQueryCount, inputWidth_, formatVal, tableMaxHeight } = toRefs(state);
const queryForm_: Ref<any> = useVModel(props, 'queryForm', emit);
watch(
() => props.queryForm,
(newValue: any) => {
state.queryForm_ = newValue;
}
);
watch(
() => props.pageNum,
(newValue: any) => {
state.pageNum = newValue;
}
);
watch(
() => props.pageSize,
(newValue: any) => {
state.pageSize = newValue;
}
);
watch(
() => props.data,
() => state.data,
(newValue: any) => {
if (newValue && newValue.length > 0) {
props.columns.forEach((item) => {
if (item.autoWidth && item.show) {
item.autoCalculateMinWidth(props.data);
item.autoCalculateMinWidth(state.data);
}
});
}
}
);
onMounted(() => {
let pageSize = props.pageSize;
onMounted(async () => {
let pageSize = queryForm_.value.pageSize;
// 如果pageSize设为0则使用系统全局配置的pageSize
if (!pageSize) {
@@ -311,12 +293,10 @@ onMounted(() => {
if (!pageSize) {
pageSize = 10;
}
emit('update:pageSize', pageSize);
}
state.pageNum = props.pageNum;
state.pageSize = pageSize;
state.queryForm_ = props.queryForm;
queryForm_.value.pageNum = 1;
queryForm_.value.pageSize = pageSize;
state.pageSizes = [pageSize, pageSize * 2, pageSize * 3, pageSize * 4, pageSize * 5];
// 如果没传输入框宽度则根据组件size设置默认宽度
@@ -329,6 +309,10 @@ onMounted(() => {
window.addEventListener('resize', () => {
calcuTableHeight();
});
if (!props.lazy) {
await reqPageApi();
}
});
const calcuTableHeight = () => {
@@ -360,14 +344,28 @@ const handleSelectionChange = (val: any) => {
emit('update:selectionData', val);
};
const handlePageChange = () => {
emit('update:pageNum', state.pageNum);
const reqPageApi = async () => {
try {
state.loading = true;
const res = await props.pageApi?.request(queryForm_.value);
if (props.dataHandlerFn) {
state.data = await props.dataHandlerFn(res.list);
} else {
state.data = res.list;
}
state.total = res.total;
} finally {
state.loading = false;
}
};
const handlePageChange = (val: number) => {
queryForm_.value.pageNum = val;
execQuery();
};
const handleSizeChange = () => {
changePageNum(1);
emit('update:pageSize', state.pageSize);
execQuery();
};
@@ -379,31 +377,24 @@ const queryData = () => {
const reset = () => {
// 将查询参数绑定的值置空,并重新粗发查询接口
for (let qi of props.query) {
state.queryForm_[qi.prop] = null;
queryForm_.value[qi.prop] = null;
}
changePageNum(1);
emit('update:queryForm', state.queryForm_);
execQuery();
};
const changePageNum = (pageNum: number) => {
state.pageNum = pageNum;
emit('update:pageNum', state.pageNum);
queryForm_.value.pageNum = pageNum;
};
const execQuery = () => {
emit('pageChange');
const execQuery = async () => {
await reqPageApi();
};
/**
* 是否正在加载数据
*/
const loading = (loading: boolean) => {
state.loadingData = loading;
};
defineExpose({ loading });
defineExpose({
search: execQuery,
});
</script>
<style scoped lang="scss">
.page-table {

View File

@@ -77,7 +77,7 @@
<template #footer>
<div class="dialog-footer">
<el-button @click="cancel()">取 消</el-button>
<el-button type="primary" :loading="btnLoading" @click="btnOk">确 定</el-button>
<el-button type="primary" :loading="saveBtnLoading" @click="btnOk">确 定</el-button>
</div>
</template>
</el-dialog>
@@ -155,11 +155,12 @@ const state = reactive({
remark: '',
instanceId: null as any,
},
btnLoading: false,
instances: [] as any,
});
const { dialogVisible, allDatabases, databaseList, form, btnLoading } = toRefs(state);
const { dialogVisible, allDatabases, form, databaseList } = toRefs(state);
const { isFetching: saveBtnLoading, execute: saveDbExec } = dbApi.saveDb.useApi(form);
watch(props, async (newValue: any) => {
state.dialogVisible = newValue.visible;
@@ -216,22 +217,15 @@ const open = async () => {
const btnOk = async () => {
dbForm.value.validate(async (valid: boolean) => {
if (valid) {
const reqForm = { ...state.form };
dbApi.saveDb.request(reqForm).then(() => {
ElMessage.success('保存成功');
emit('val-change', state.form);
state.btnLoading = true;
setTimeout(() => {
state.btnLoading = false;
}, 1000);
cancel();
});
} else {
if (!valid) {
ElMessage.error('请正确填写信息');
return false;
}
await saveDbExec();
ElMessage.success('保存成功');
emit('val-change', state.form);
cancel();
});
};

View File

@@ -2,16 +2,12 @@
<div class="db-list">
<page-table
ref="pageTableRef"
:page-api="dbApi.dbs"
:query="queryConfig"
v-model:query-form="query"
:show-selection="true"
v-model:selection-data="state.selectionData"
:data="datas"
:columns="columns"
:total="total"
v-model:page-size="query.pageSize"
v-model:page-num="query.pageNum"
@pageChange="search()"
>
<template #tagPathSelect>
<el-select @focus="getTags" v-model="query.tagPath" placeholder="请选择标签" filterable clearable style="width: 200px">
@@ -140,7 +136,7 @@
<el-dialog v-model="infoDialog.visible" :before-close="onBeforeCloseInfoDialog" :close-on-click-modal="false">
<el-descriptions title="详情" :column="3" border>
<el-descriptions-item :span="3" label="标签路径">{{ infoDialog.data?.tagPath }}</el-descriptions-item>
<!-- <el-descriptions-item :span="3" label="标签路径">{{ infoDialog.data?.tagPath }}</el-descriptions-item> -->
<el-descriptions-item :span="2" label="名称">{{ infoDialog.data?.name }}</el-descriptions-item>
<el-descriptions-item :span="1" label="id">{{ infoDialog.data?.id }}</el-descriptions-item>
<el-descriptions-item :span="3" label="数据库">{{ infoDialog.data?.database }}</el-descriptions-item>
@@ -158,12 +154,12 @@
</el-descriptions>
</el-dialog>
<db-edit @val-change="valChange" :title="dbEditDialog.title" v-model:visible="dbEditDialog.visible" v-model:db="dbEditDialog.data"></db-edit>
<db-edit @val-change="search" :title="dbEditDialog.title" v-model:visible="dbEditDialog.visible" v-model:db="dbEditDialog.data"></db-edit>
</div>
</template>
<script lang="ts" setup>
import { ref, toRefs, reactive, onMounted, defineAsyncComponent } from 'vue';
import { ref, toRefs, reactive, onMounted, defineAsyncComponent, Ref } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import { dbApi } from './api';
import config from '@/common/config';
@@ -206,8 +202,7 @@ const actionBtns = hasPerms([perms.base, perms.saveDb]);
const actionColumn = TableColumn.new('action', '操作').isSlot().setMinWidth(220).fixedRight().alignCenter();
const route = useRoute();
const pageTableRef: any = ref(null);
const pageTableRef: Ref<any> = ref(null);
const state = reactive({
row: {} as any,
dbId: 0,
@@ -227,8 +222,6 @@ const state = reactive({
pageNum: 1,
pageSize: 0,
},
datas: [],
total: 0,
infoDialog: {
visible: false,
data: null as any,
@@ -265,33 +258,19 @@ const state = reactive({
},
});
const { db, tags, selectionData, query, datas, total, infoDialog, sqlExecLogDialog, exportDialog, dbEditDialog } = toRefs(state);
const { db, tags, selectionData, query, infoDialog, sqlExecLogDialog, exportDialog, dbEditDialog } = toRefs(state);
onMounted(async () => {
if (Object.keys(actionBtns).length > 0) {
columns.value.push(actionColumn);
}
search();
if (route.query.tagPath) {
state.query.tagPath = route.query.tagPath as string;
}
});
const search = async () => {
try {
pageTableRef.value.loading(true);
if (route.query.tagPath) {
state.query.tagPath = route.query.tagPath as string;
}
let res: any = await dbApi.dbs.request(state.query);
// 切割数据库
res.list?.forEach((e: any) => {
e.popoverSelectDbVisible = false;
e.dbs = e.database.split(' ');
});
state.datas = res.list;
state.total = res.total;
} finally {
pageTableRef.value.loading(false);
}
pageTableRef.value.search();
};
const showInfo = async (info: any) => {
@@ -352,10 +331,6 @@ const editDb = async (data: any) => {
state.dbEditDialog.visible = true;
};
const valChange = () => {
search();
};
const deleteDb = async () => {
try {
await ElMessageBox.confirm(`确定删除【${state.selectionData.map((x: any) => x.name).join(', ')}】库?`, '提示', {

View File

@@ -1,17 +1,6 @@
<template>
<div class="db-sql-exec-log">
<page-table
height="100%"
ref="sqlExecDialogPageTableRef"
:query="queryConfig"
v-model:query-form="query"
:data="data"
:columns="columns"
:total="total"
v-model:page-size="query.pageSize"
v-model:page-num="query.pageNum"
@pageChange="searchSqlExecLog()"
>
<page-table ref="pageTableRef" :page-api="dbApi.getSqlExecs" height="100%" :query="queryConfig" v-model:query-form="query" :columns="columns">
<template #dbSelect>
<el-select v-model="query.db" placeholder="请选择数据库" style="width: 200px" filterable clearable>
<el-option v-for="item in dbs" :key="item" :label="`${item}`" :value="item"> </el-option>
@@ -39,7 +28,7 @@
</template>
<script lang="ts" setup>
import { toRefs, watch, reactive, onMounted } from 'vue';
import { toRefs, watch, reactive, onMounted, Ref, ref } from 'vue';
import { dbApi } from './api';
import { DbSqlExecTypeEnum } from './enums';
import PageTable from '@/components/pagetable/PageTable.vue';
@@ -74,9 +63,9 @@ const columns = [
TableColumn.new('action', '操作').isSlot().setMinWidth(90).fixedRight().alignCenter(),
];
const pageTableRef: Ref<any> = ref(null);
const state = reactive({
data: [],
total: 0,
dbs: [],
query: {
dbId: 0,
@@ -97,21 +86,18 @@ const state = reactive({
},
});
const { data, query, total, rollbackSqlDialog } = toRefs(state);
const { query, rollbackSqlDialog } = toRefs(state);
onMounted(async () => {
searchSqlExecLog();
});
onMounted(async () => {});
watch(props, async () => {
state.query.dbId = props.dbId;
state.query.pageNum = 1;
await searchSqlExecLog();
});
const searchSqlExecLog = async () => {
state.query.dbId = props.dbId;
const res = await dbApi.getSqlExecs.request(state.query);
state.data = res.list;
state.total = res.total;
pageTableRef.value.search();
};
const onShowRollbackSql = async (sqlExecLog: any) => {

View File

@@ -71,9 +71,9 @@
<template #footer>
<div class="dialog-footer">
<el-button @click="testConn" :loading="state.testConnBtnLoading" type="success">测试连接</el-button>
<el-button @click="testConn" :loading="testConnBtnLoading" type="success">测试连接</el-button>
<el-button @click="cancel()"> </el-button>
<el-button type="primary" :loading="btnLoading" @click="btnOk"> </el-button>
<el-button type="primary" :loading="saveBtnLoading" @click="btnOk"> </el-button>
</div>
</template>
</el-dialog>
@@ -168,15 +168,17 @@ const state = reactive({
remark: '',
sshTunnelMachineId: null as any,
},
subimtForm: {},
// 原密码
pwd: '',
// 原用户名
oldUserName: null,
btnLoading: false,
testConnBtnLoading: false,
});
const { dialogVisible, tabActiveName, form, pwd, btnLoading } = toRefs(state);
const { dialogVisible, tabActiveName, form, subimtForm, pwd } = toRefs(state);
const { isFetching: saveBtnLoading, execute: saveInstanceExec } = dbApi.saveInstance.useApi(subimtForm);
const { isFetching: testConnBtnLoading, execute: testConnExec } = dbApi.testConn.useApi(subimtForm);
watch(props, (newValue: any) => {
state.dialogVisible = newValue.visible;
@@ -214,18 +216,14 @@ const getReqForm = async () => {
const testConn = async () => {
dbForm.value.validate(async (valid: boolean) => {
if (valid) {
state.testConnBtnLoading = true;
try {
await dbApi.testConn.request(await getReqForm());
ElMessage.success('连接成功');
} finally {
state.testConnBtnLoading = false;
}
} else {
if (!valid) {
ElMessage.error('请正确填写信息');
return false;
}
state.subimtForm = await getReqForm();
await testConnExec();
ElMessage.success('连接成功');
});
};
@@ -237,21 +235,16 @@ const btnOk = async () => {
}
dbForm.value.validate(async (valid: boolean) => {
if (valid) {
dbApi.saveInstance.request(await getReqForm()).then(() => {
ElMessage.success('保存成功');
emit('val-change', state.form);
state.btnLoading = true;
setTimeout(() => {
state.btnLoading = false;
}, 1000);
cancel();
});
} else {
if (!valid) {
ElMessage.error('请正确填写信息');
return false;
}
state.subimtForm = await getReqForm();
await saveInstanceExec();
ElMessage.success('保存成功');
emit('val-change', state.form);
cancel();
});
};

View File

@@ -2,16 +2,12 @@
<div class="db-list">
<page-table
ref="pageTableRef"
:page-api="dbApi.instances"
:query="queryConfig"
v-model:query-form="query"
:show-selection="true"
v-model:selection-data="state.selectionData"
:data="datas"
:columns="columns"
:total="total"
v-model:page-size="query.pageSize"
v-model:page-num="query.pageNum"
@pageChange="search()"
>
<template #queryRight>
<el-button v-auth="perms.saveInstance" type="primary" icon="plus" @click="editInstance(false)">添加</el-button>
@@ -56,7 +52,7 @@
</el-dialog>
<instance-edit
@val-change="valChange"
@val-change="search"
:title="instanceEditDialog.title"
v-model:visible="instanceEditDialog.visible"
v-model:data="instanceEditDialog.data"
@@ -65,7 +61,7 @@
</template>
<script lang="ts" setup>
import { ref, toRefs, reactive, onMounted, defineAsyncComponent } from 'vue';
import { ref, toRefs, reactive, onMounted, defineAsyncComponent, Ref } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import { dbApi } from './api';
import { dateFormat } from '@/common/utils/date';
@@ -96,8 +92,7 @@ const columns = ref([
// 该用户拥有的的操作列按钮权限
const actionBtns = hasPerms([perms.saveInstance]);
const actionColumn = TableColumn.new('action', '操作').isSlot().setMinWidth(110).fixedRight().alignCenter();
const pageTableRef: any = ref(null);
const pageTableRef: Ref<any> = ref(null);
const state = reactive({
row: {},
@@ -115,8 +110,6 @@ const state = reactive({
pageNum: 1,
pageSize: 0,
},
datas: [],
total: 0,
infoDialog: {
visible: false,
data: null as any,
@@ -128,24 +121,16 @@ const state = reactive({
},
});
const { selectionData, query, datas, total, infoDialog, instanceEditDialog } = toRefs(state);
const { selectionData, query, infoDialog, instanceEditDialog } = toRefs(state);
onMounted(async () => {
if (Object.keys(actionBtns).length > 0) {
columns.value.push(actionColumn);
}
search();
});
const search = async () => {
try {
pageTableRef.value.loading(true);
let res: any = await dbApi.instances.request(state.query);
state.datas = res.list;
state.total = res.total;
} finally {
pageTableRef.value.loading(false);
}
const search = () => {
pageTableRef.value.search();
};
const showInfo = (info: any) => {
@@ -164,10 +149,6 @@ const editInstance = async (data: any) => {
state.instanceEditDialog.visible = true;
};
const valChange = () => {
search();
};
const deleteInstance = async () => {
try {
await ElMessageBox.confirm(`确定删除数据库实例【${state.selectionData.map((x: any) => x.name).join(', ')}】?`, '提示', {

View File

@@ -20,6 +20,7 @@ export const dbApi = {
if (param.sql) {
param.sql = Base64.encode(param.sql);
}
return param;
}),
// 保存sql
saveSql: Api.newPost('/dbs/{id}/sql'),

View File

@@ -111,7 +111,7 @@
:table="dt.table"
:columns="dt.tableColumn"
:loading="dt.loading"
:loading-key="dt.loadingKey"
:abort-fn="dt.abortFn"
:height="tableDataHeight"
empty-text="tips: select *开头的单表查询或点击表名默认查询的数据,可双击数据在线修改"
@change-updated-field="changeUpdatedField($event, dt)"
@@ -128,7 +128,7 @@
</template>
<script lang="ts" setup>
import { h, nextTick, onMounted, reactive, toRefs, ref, onBeforeUnmount } from 'vue';
import { h, nextTick, onMounted, reactive, toRefs, ref } from 'vue';
import { getToken } from '@/common/utils/storage';
import { notBlank } from '@/common/assert';
import { format as sqlFormatter } from 'sql-formatter';
@@ -151,7 +151,6 @@ import syssocket from '@/common/syssocket';
import SvgIcon from '@/components/svgIcon/index.vue';
import { getDbDialect } from '../../dialect';
import { Splitpanes, Pane } from 'splitpanes';
import Api from '@/common/Api';
const emits = defineEmits(['saveSqlSuccess']);
@@ -178,12 +177,15 @@ class ExecResTab {
*/
sql: string;
loading: boolean;
loadingKey: string;
/**
* 响应式loading
*/
loading: any;
dbTableRef: any;
abortFn: Function;
tableColumn: any[] = [];
data: any[] = [];
@@ -252,12 +254,6 @@ onMounted(async () => {
await getNowDbInst().loadDbHints(props.dbName);
});
onBeforeUnmount(() => {
state.execResTabs.forEach((x: ExecResTab) => {
Api.removeAbortKey(x.loadingKey);
});
});
const onRemoveTab = (targetId: number) => {
let activeTab = state.activeTab;
const tabs = [...state.execResTabs];
@@ -339,7 +335,7 @@ const onRunSql = async (newTab = false) => {
// 不是新建tab执行则在当前激活的tab上执行sql
i = state.execResTabs.findIndex((x) => x.id == state.activeTab);
execRes = state.execResTabs[i];
if (execRes.loading) {
if (execRes.loading?.value) {
ElMessage.error('当前结果集tab正在执行, 请使用新标签执行');
return;
}
@@ -349,18 +345,19 @@ const onRunSql = async (newTab = false) => {
state.activeTab = id;
const startTime = new Date().getTime();
try {
execRes.loading = true;
execRes.errorMsg = '';
execRes.sql = '';
// 用于取消执行
const loadingKey = Api.genAbortKey(execRes.loadingKey);
execRes.loadingKey = loadingKey;
const { data, execute, isFetching, abort } = getNowDbInst().execSql(props.dbName, sql, execRemark);
execRes.loading = isFetching;
execRes.abortFn = abort;
const colAndData: any = await getNowDbInst().runSql(props.dbName, sql, execRemark, loadingKey);
await execute();
const colAndData: any = data.value;
if (!colAndData.res || colAndData.res.length === 0) {
ElMessage.warning('未查询到结果集');
}
// 要实时响应,故需要用索引改变数据才生效
state.execResTabs[i].data = colAndData.res;
// 兼容表格字段配置
@@ -378,7 +375,6 @@ const onRunSql = async (newTab = false) => {
execRes.errorMsg = e.msg;
return;
} finally {
state.execResTabs[i].loading = false;
execRes.sql = sql;
execRes.execTime = new Date().getTime() - startTime;
}

View File

@@ -96,7 +96,7 @@
<SvgIcon class="is-loading" name="loading" color="var(--el-color-primary)" :size="28" />
<el-text class="ml5" tag="b">执行时间 - {{ state.execTime.toFixed(1) }}s</el-text>
</div>
<div v-if="loadingKey" class="mt10">
<div v-if="loading && abortFn" class="mt10">
<el-button @click="cancelLoading" type="info" size="small" plain> </el-button>
</div>
</div>
@@ -133,7 +133,6 @@ import { ContextmenuItem, Contextmenu } from '@/components/contextmenu';
import SvgIcon from '@/components/svgIcon/index.vue';
import { exportCsv, exportFile } from '@/common/utils/export';
import { dateStrFormat } from '@/common/utils/date';
import Api from '@/common/Api';
import { useIntervalFn } from '@vueuse/core';
const emits = defineEmits(['dataDelete', 'sortChange', 'deleteData', 'selectionChange', 'changeUpdatedField']);
@@ -165,8 +164,8 @@ const props = defineProps({
type: Boolean,
default: false,
},
loadingKey: {
type: String,
abortFn: {
type: Function,
},
emptyText: {
type: String,
@@ -441,10 +440,8 @@ const endLoading = () => {
};
const cancelLoading = async () => {
if (props.loadingKey) {
Api.cancelReq(props.loadingKey);
endLoading();
}
props.abortFn && props.abortFn();
endLoading();
};
/**

View File

@@ -196,16 +196,7 @@ export class DbInst {
* @param sql sql
* @param remark 执行备注
*/
async runSql(dbName: string, sql: string, remark: string = '', key: string = '') {
if (key) {
return await dbApi.sqlExec.allowCancelReq(key, {
id: this.id,
db: dbName,
sql: sql.trim(),
remark,
});
}
async runSql(dbName: string, sql: string, remark: string = '') {
return await dbApi.sqlExec.request({
id: this.id,
db: dbName,
@@ -214,6 +205,22 @@ export class DbInst {
});
}
/**
* 执行sql(可取消的)
*
* @param sql sql
* @param remark 执行备注
*/
execSql(dbName: string, sql: string, remark: string = '') {
let dbId = this.id;
return dbApi.sqlExec.useApi({
id: dbId,
db: dbName,
sql: sql.trim(),
remark,
});
}
/**
* 获取count sql
* @param table 表名

View File

@@ -118,6 +118,9 @@ let postgresDialect = new PostgresqlDialect();
let dmDialect = new DMDialect();
export const getDbDialect = (dbType: string | undefined): DbDialect => {
if (!dbType) {
return mysqlDialect;
}
if (dbType === DbType.mysql) {
return mysqlDialect;
}

View File

@@ -71,7 +71,7 @@
<div>
<el-button @click="testConn" :loading="testConnBtnLoading" type="success">测试连接</el-button>
<el-button @click="cancel()">取 消</el-button>
<el-button type="primary" :loading="btnLoading" @click="btnOk">确 定</el-button>
<el-button type="primary" :loading="saveBtnLoading" @click="btnOk">确 定</el-button>
</div>
</template>
</el-dialog>
@@ -164,12 +164,14 @@ const state = reactive({
sshTunnelMachineId: null as any,
enableRecorder: -1,
},
submitForm: {},
pwd: '',
testConnBtnLoading: false,
btnLoading: false,
});
const { dialogVisible, tabActiveName, form, testConnBtnLoading, btnLoading } = toRefs(state);
const { dialogVisible, tabActiveName, form, submitForm } = toRefs(state);
const { isFetching: testConnBtnLoading, execute: testConnExec } = machineApi.testConn.useApi(submitForm);
const { isFetching: saveBtnLoading, execute: saveMachineExec } = machineApi.saveMachine.useApi(submitForm);
watch(props, async (newValue: any) => {
state.dialogVisible = newValue.visible;
@@ -205,45 +207,29 @@ const changeAuthMethod = (val: any) => {
const testConn = async () => {
machineForm.value.validate(async (valid: boolean) => {
if (valid) {
const form = getReqForm();
if (!form) {
return;
}
state.testConnBtnLoading = true;
try {
await machineApi.testConn.request(form);
ElMessage.success('连接成功');
} finally {
state.testConnBtnLoading = false;
}
} else {
if (!valid) {
ElMessage.error('请正确填写信息');
return false;
}
state.submitForm = getReqForm();
await testConnExec();
ElMessage.success('连接成功');
});
};
const btnOk = async () => {
machineForm.value.validate(async (valid: boolean) => {
if (valid) {
const form = getReqForm();
if (!form) {
return;
}
state.btnLoading = true;
try {
await machineApi.saveMachine.request(form);
ElMessage.success('保存成功');
emit('val-change', form);
cancel();
} finally {
state.btnLoading = false;
}
} else {
if (!valid) {
ElMessage.error('请正确填写信息');
return false;
}
state.submitForm = getReqForm();
await saveMachineExec();
ElMessage.success('保存成功');
emit('val-change', submitForm);
cancel();
});
};

View File

@@ -2,16 +2,12 @@
<div>
<page-table
ref="pageTableRef"
:page-api="machineApi.list"
:query="queryConfig"
v-model:query-form="params"
:show-selection="true"
v-model:selection-data="state.selectionData"
:data="data.list"
:columns="columns"
:total="data.total"
v-model:page-size="params.pageSize"
v-model:page-num="params.pageNum"
@pageChange="search()"
>
<template #tagPathSelect>
<el-select @focus="getTags" v-model="params.tagPath" placeholder="请选择标签" @clear="search" filterable clearable style="width: 200px">
@@ -186,7 +182,7 @@
</template>
<script lang="ts" setup>
import { ref, toRefs, reactive, onMounted, defineAsyncComponent } from 'vue';
import { ref, toRefs, reactive, onMounted, defineAsyncComponent, Ref } from 'vue';
import { useRouter, useRoute } from 'vue-router';
import { ElMessage, ElMessageBox } from 'element-plus';
import { machineApi, getMachineTerminalSocketUrl } from './api';
@@ -210,8 +206,8 @@ const ProcessList = defineAsyncComponent(() => import('./ProcessList.vue'));
const router = useRouter();
const route = useRoute();
const pageTableRef: any = ref(null);
const terminalDialogRef: any = ref(null);
const pageTableRef: Ref<any> = ref(null);
const perms = {
addMachine: 'machine:add',
@@ -247,11 +243,6 @@ const state = reactive({
name: null,
tagPath: '',
},
// 列表数据
data: {
list: [],
total: 10,
},
infoDialog: {
visible: false,
data: null as any,
@@ -290,11 +281,13 @@ const state = reactive({
},
});
const { tags, params, data, infoDialog, selectionData, serviceDialog, processDialog, fileDialog, machineStatsDialog, machineEditDialog, machineRecDialog } =
const { tags, params, infoDialog, selectionData, serviceDialog, processDialog, fileDialog, machineStatsDialog, machineEditDialog, machineRecDialog } =
toRefs(state);
onMounted(async () => {
search();
if (route.query.tagPath) {
state.params.tagPath = route.query.tagPath as string;
}
});
const handleCommand = (commond: any) => {
@@ -421,6 +414,10 @@ const showMachineStats = async (machine: any) => {
state.machineStatsDialog.visible = true;
};
const search = async () => {
pageTableRef.value.search();
};
const submitSuccess = () => {
search();
};
@@ -431,19 +428,6 @@ const showFileManage = (selectionData: any) => {
state.fileDialog.title = `${selectionData.name} => ${selectionData.ip}`;
};
const search = async () => {
try {
pageTableRef.value.loading(true);
if (route.query.tagPath) {
state.params.tagPath = route.query.tagPath as string;
}
const res = await machineApi.list.request(state.params);
state.data = res;
} finally {
pageTableRef.value.loading(false);
}
};
const getStatsFontClass = (availavle: number, total: number) => {
const p = availavle / total;
if (p < 0.1) {

View File

@@ -11,14 +11,11 @@
>
<page-table
ref="pageTableRef"
:page-api="machineApi.scripts"
:lazy="true"
:query="queryConfig"
v-model:query-form="query"
:data="scriptTable"
:columns="columns"
:total="total"
v-model:page-size="query.pageSize"
v-model:page-num="query.pageNum"
@pageChange="getScripts()"
:show-selection="true"
v-model:selection-data="selectionData"
>
@@ -88,7 +85,7 @@
</template>
<script lang="ts" setup>
import { ref, toRefs, reactive, watch } from 'vue';
import { ref, toRefs, reactive, watch, Ref } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import TerminalBody from '@/components/terminal/TerminalBody.vue';
import { getMachineTerminalSocketUrl, machineApi } from './api';
@@ -107,7 +104,7 @@ const props = defineProps({
const emit = defineEmits(['update:visible', 'cancel', 'update:machineId']);
const paramsForm: any = ref(null);
const pageTableRef: any = ref(null);
const pageTableRef: Ref<any> = ref(null);
const state = reactive({
dialogVisible: false,
@@ -150,24 +147,15 @@ const state = reactive({
},
});
const { dialogVisible, queryConfig, columns, selectionData, query, editDialog, total, scriptTable, scriptParamsDialog, resultDialog, terminalDialog } =
toRefs(state);
const { dialogVisible, queryConfig, columns, selectionData, query, editDialog, scriptParamsDialog, resultDialog, terminalDialog } = toRefs(state);
watch(props, async (newValue) => {
state.dialogVisible = newValue.visible;
});
const getScripts = async () => {
try {
// 通过open事件才开获取到pageTableRef值
pageTableRef.value.loading(true);
state.query.machineId = state.query.type == ScriptTypeEnum.Private.value ? props.machineId : 9999999;
const res = await machineApi.scripts.request(state.query);
state.scriptTable = res.list;
state.total = res.total;
} finally {
pageTableRef.value.loading(false);
}
state.query.machineId = state.query.type == ScriptTypeEnum.Private.value ? props.machineId : 9999999;
pageTableRef.value.search();
};
const runScript = async (script: any) => {

View File

@@ -1,16 +1,13 @@
<template>
<div>
<page-table
ref="pageTableRef"
:page-api="authCertApi.list"
:query="state.queryConfig"
v-model:query-form="query"
:show-selection="true"
v-model:selection-data="selectionData"
:data="authcerts"
:columns="state.columns"
:total="total"
v-model:page-size="query.pageSize"
v-model:page-num="query.pageNum"
@pageChange="search()"
>
<template #queryRight>
<el-button type="primary" icon="plus" @click="edit(false)">添加</el-button>
@@ -27,7 +24,7 @@
</template>
<script lang="ts" setup>
import { toRefs, reactive, onMounted } from 'vue';
import { toRefs, reactive, onMounted, ref, Ref } from 'vue';
import AuthCertEdit from './AuthCertEdit.vue';
import { authCertApi } from '../api';
import { ElMessage, ElMessageBox } from 'element-plus';
@@ -35,6 +32,7 @@ import PageTable from '@/components/pagetable/PageTable.vue';
import { TableColumn, TableQuery } from '@/components/pagetable';
import { AuthMethodEnum } from '../enums';
const pageTableRef: Ref<any> = ref(null);
const state = reactive({
query: {
pageNum: 1,
@@ -68,16 +66,12 @@ const state = reactive({
},
});
const { query, total, authcerts, selectionData, editor } = toRefs(state);
const { query, selectionData, editor } = toRefs(state);
onMounted(() => {
search();
});
onMounted(() => {});
const search = async () => {
let res = await authCertApi.list.request(state.query);
state.authcerts = res.list;
state.total = res.total;
pageTableRef.value.search();
};
const editChange = () => {

View File

@@ -1,6 +1,7 @@
<template>
<div>
<el-dialog
@open="search()"
:title="title"
v-model="dialogVisible"
:close-on-click-modal="false"
@@ -11,14 +12,13 @@
>
<page-table
ref="pageTableRef"
:page-api="cronJobApi.execList"
:lazy="true"
:data-handler-fn="parseData"
:query="queryConfig"
v-model:query-form="params"
:data="state.data.list"
:columns="columns"
:total="state.data.total"
v-model:page-size="params.pageSize"
v-model:page-num="params.pageNum"
@pageChange="search()"
>
<template #machineSelect>
<el-select v-model="params.machineId" filterable placeholder="选择机器查询" style="width: 200px" clearable>
@@ -35,7 +35,7 @@
</template>
<script lang="ts" setup>
import { watch, ref, toRefs, reactive } from 'vue';
import { watch, ref, toRefs, reactive, Ref } from 'vue';
import { cronJobApi, machineApi } from '../api';
import PageTable from '@/components/pagetable/PageTable.vue';
import { TableColumn, TableQuery } from '@/components/pagetable';
@@ -68,6 +68,8 @@ const columns = ref([
TableColumn.new('execTime', '执行时间').isTime().setMinWidth(150),
]);
const pageTableRef: Ref<any> = ref(null);
const state = reactive({
dialogVisible: false,
tags: [] as any,
@@ -108,17 +110,15 @@ watch(props, async (newValue: any) => {
});
state.params.cronJobId = props.data?.id;
search();
});
const search = async () => {
const res = await cronJobApi.execList.request(state.params);
if (!res.list) {
return;
}
pageTableRef.value.search();
};
const parseData = async (dataList: any) => {
// 填充机器信息
for (let x of res.list) {
for (let x of dataList) {
const machineId = x.machineId;
let machine = machineMap.get(machineId);
// 如果未找到,则可能被移除,则调接口查询机器信息
@@ -139,8 +139,7 @@ const search = async () => {
x.machineIp = machine?.ip;
x.machineName = machine?.name;
}
state.data = res;
return dataList;
};
const cancel = () => {

View File

@@ -2,16 +2,12 @@
<div>
<page-table
ref="pageTableRef"
:page-api="cronJobApi.list"
:query="queryConfig"
v-model:query-form="params"
:show-selection="true"
v-model:selection-data="state.selectionData"
:data="data.list"
:columns="columns"
:total="data.total"
v-model:page-size="params.pageSize"
v-model:page-num="params.pageNum"
@pageChange="search()"
>
<template #queryRight>
<el-button v-auth="perms.saveCronJob" type="primary" icon="plus" @click="openFormDialog(false)" plain>添加 </el-button>
@@ -38,7 +34,7 @@
</template>
<script lang="ts" setup>
import { ref, toRefs, reactive, onMounted, defineAsyncComponent } from 'vue';
import { ref, toRefs, reactive, onMounted, defineAsyncComponent, Ref } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import { cronJobApi } from '../api';
import PageTable from '@/components/pagetable/PageTable.vue';
@@ -48,8 +44,6 @@ import { CronJobStatusEnum, CronJobSaveExecResTypeEnum } from '../enums';
const CronJobEdit = defineAsyncComponent(() => import('./CronJobEdit.vue'));
const CronJobExecList = defineAsyncComponent(() => import('./CronJobExecList.vue'));
const pageTableRef: any = ref(null);
const perms = {
saveCronJob: 'machine:cronjob:save',
delCronJob: 'machine:cronjob:del',
@@ -69,6 +63,8 @@ const columns = ref([
TableColumn.new('action', '操作').isSlot().setMinWidth(180).fixedRight().alignCenter(),
]);
const pageTableRef: Ref<any> = ref(null);
const state = reactive({
params: {
pageNum: 1,
@@ -76,11 +72,6 @@ const state = reactive({
ip: null,
name: null,
},
// 列表数据
data: {
list: [],
total: 10,
},
selectionData: [],
execDialog: {
visible: false,
@@ -94,11 +85,9 @@ const state = reactive({
},
});
const { selectionData, params, data, execDialog, cronJobEdit } = toRefs(state);
const { selectionData, params, execDialog, cronJobEdit } = toRefs(state);
onMounted(async () => {
search();
});
onMounted(async () => {});
const openFormDialog = async (data: any) => {
let dialogTitle;
@@ -143,13 +132,7 @@ const showExec = async (data: any) => {
};
const search = async () => {
try {
pageTableRef.value.loading(true);
const res = await cronJobApi.list.request(state.params);
state.data = res;
} finally {
pageTableRef.value.loading(false);
}
pageTableRef.value.search();
};
</script>

View File

@@ -639,7 +639,7 @@ function uploadFolder(e: any) {
// 上传操作
machineApi.uploadFile
.request(form, {
.xhrReq(form, {
url: `${config.baseApiUrl}/machines/${props.machineId}/files/${props.fileId}/upload-folder?${joinClientParams()}`,
headers: { 'Content-Type': 'multipart/form-data; boundary=----WebKitFormBoundaryF1uyUD0tWdqmJqpl' },
onUploadProgress: onUploadProgress,
@@ -680,7 +680,7 @@ const uploadFile = (content: any) => {
params.append('fileId', props.fileId as any);
params.append('token', token);
machineApi.uploadFile
.request(params, {
.xhrReq(params, {
url: `${config.baseApiUrl}/machines/${props.machineId}/files/${props.fileId}/upload?${joinClientParams()}`,
headers: { 'Content-Type': 'multipart/form-data; boundary=----WebKitFormBoundaryF1uyUD0tWdqmJqpl' },
onUploadProgress: onUploadProgress,

View File

@@ -110,6 +110,9 @@ const getFileType = (path: string) => {
if (path.endsWith('xml') || path.endsWith('html')) {
return 'html';
}
if (path.endsWith('py')) {
return 'python';
}
return 'text';
};
</script>

View File

@@ -43,9 +43,9 @@
<template #footer>
<div class="dialog-footer">
<el-button @click="testConn" :loading="state.testConnBtnLoading" type="success">测试连接</el-button>
<el-button @click="testConn" :loading="testConnBtnLoading" type="success">测试连接</el-button>
<el-button @click="cancel()"> </el-button>
<el-button type="primary" :loading="btnLoading" @click="btnOk"> </el-button>
<el-button type="primary" :loading="saveBtnLoading" @click="btnOk"> </el-button>
</div>
</template>
</el-dialog>
@@ -113,11 +113,13 @@ const state = reactive({
sshTunnelMachineId: null as any,
tagId: [],
},
btnLoading: false,
testConnBtnLoading: false,
submitForm: {},
});
const { dialogVisible, tabActiveName, form, btnLoading } = toRefs(state);
const { dialogVisible, tabActiveName, form, submitForm } = toRefs(state);
const { isFetching: testConnBtnLoading, execute: testConnExec } = mongoApi.testConn.useApi(submitForm);
const { isFetching: saveBtnLoading, execute: saveMongoExec } = mongoApi.saveMongo.useApi(submitForm);
watch(props, async (newValue: any) => {
state.dialogVisible = newValue.visible;
@@ -142,38 +144,28 @@ const getReqForm = () => {
const testConn = async () => {
mongoForm.value.validate(async (valid: boolean) => {
if (valid) {
state.testConnBtnLoading = true;
try {
await mongoApi.testConn.request(getReqForm());
ElMessage.success('连接成功');
} finally {
state.testConnBtnLoading = false;
}
} else {
if (!valid) {
ElMessage.error('请正确填写信息');
return false;
}
state.submitForm = getReqForm();
await testConnExec();
ElMessage.success('连接成功');
});
};
const btnOk = async () => {
mongoForm.value.validate(async (valid: boolean) => {
if (valid) {
mongoApi.saveMongo.request(getReqForm()).then(() => {
ElMessage.success('保存成功');
emit('val-change', state.form);
state.btnLoading = true;
setTimeout(() => {
state.btnLoading = false;
}, 1000);
cancel();
});
} else {
if (!valid) {
ElMessage.error('请正确填写信息');
return false;
}
state.submitForm = getReqForm();
await saveMongoExec();
ElMessage.success('保存成功');
emit('val-change', state.form);
cancel();
});
};

View File

@@ -2,16 +2,12 @@
<div>
<page-table
ref="pageTableRef"
:page-api="mongoApi.mongoList"
:query="queryConfig"
v-model:query-form="query"
:show-selection="true"
v-model:selection-data="selectionData"
:data="list"
:columns="columns"
:total="total"
v-model:page-size="query.pageSize"
v-model:page-num="query.pageNum"
@pageChange="search()"
>
<template #tagPathSelect>
<el-select @focus="getTags" v-model="query.tagPath" placeholder="请选择标签" @clear="search" filterable clearable style="width: 200px">
@@ -42,7 +38,7 @@
<mongo-run-command v-model:visible="usersVisible" :id="state.dbOps.dbId" />
<mongo-edit
@val-change="valChange"
@val-change="search"
:title="mongoEditDialog.title"
v-model:visible="mongoEditDialog.visible"
v-model:mongo="mongoEditDialog.data"
@@ -52,7 +48,7 @@
<script lang="ts" setup>
import { mongoApi } from './api';
import { defineAsyncComponent, ref, toRefs, reactive, onMounted } from 'vue';
import { defineAsyncComponent, ref, toRefs, reactive, onMounted, Ref } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import ResourceTag from '../component/ResourceTag.vue';
import PageTable from '@/components/pagetable/PageTable.vue';
@@ -65,8 +61,8 @@ const MongoEdit = defineAsyncComponent(() => import('./MongoEdit.vue'));
const MongoDbs = defineAsyncComponent(() => import('./MongoDbs.vue'));
const MongoRunCommand = defineAsyncComponent(() => import('./MongoRunCommand.vue'));
const pageTableRef: any = ref(null);
const route = useRoute();
const pageTableRef: Ref<any> = ref(null);
const queryConfig = [TableQuery.slot('tagPath', '标签', 'tagPathSelect')];
const columns = ref([
@@ -84,8 +80,6 @@ const state = reactive({
dbId: 0,
db: '',
},
list: [],
total: 0,
selectionData: [],
query: {
pageNum: 1,
@@ -101,10 +95,12 @@ const state = reactive({
usersVisible: false,
});
const { tags, list, total, selectionData, query, mongoEditDialog, dbsVisible, usersVisible } = toRefs(state);
const { tags, selectionData, query, mongoEditDialog, dbsVisible, usersVisible } = toRefs(state);
onMounted(async () => {
search();
if (route.query.tagPath) {
state.query.tagPath = route.query.tagPath as string;
}
});
const showDatabases = async (id: number) => {
@@ -133,19 +129,7 @@ const deleteMongo = async () => {
};
const search = async () => {
try {
pageTableRef.value.loading(true);
if (route.query.tagPath) {
state.query.tagPath = route.query.tagPath as string;
}
const res = await mongoApi.mongoList.request(state.query);
state.list = res.list;
state.total = res.total;
} finally {
pageTableRef.value.loading(false);
}
pageTableRef.value.search();
};
const getTags = async () => {
@@ -162,10 +146,6 @@ const editMongo = async (data: any) => {
}
state.mongoEditDialog.visible = true;
};
const valChange = () => {
search();
};
</script>
<style></style>

View File

@@ -84,9 +84,9 @@
<template #footer>
<div class="dialog-footer">
<el-button @click="testConn" :loading="state.testConnBtnLoading" type="success">测试连接</el-button>
<el-button @click="testConn" :loading="testConnBtnLoading" type="success">测试连接</el-button>
<el-button @click="cancel()">取 消</el-button>
<el-button type="primary" :loading="btnLoading" @click="btnOk">确 定</el-button>
<el-button type="primary" :loading="saveBtnLoading" @click="btnOk">确 定</el-button>
</div>
</template>
</el-dialog>
@@ -173,13 +173,15 @@ const state = reactive({
remark: '',
sshTunnelMachineId: -1,
},
submitForm: {} as any,
dbList: [0],
pwd: '',
btnLoading: false,
testConnBtnLoading: false,
});
const { dialogVisible, tabActiveName, form, dbList, pwd, btnLoading } = toRefs(state);
const { dialogVisible, tabActiveName, form, submitForm, dbList, pwd } = toRefs(state);
const { isFetching: testConnBtnLoading, execute: testConnExec } = redisApi.testConn.useApi(submitForm);
const { isFetching: saveBtnLoading, execute: saveRedisExec } = redisApi.saveRedis.useApi(submitForm);
watch(props, async (newValue: any) => {
state.dialogVisible = newValue.visible;
@@ -226,38 +228,28 @@ const getReqForm = async () => {
const testConn = async () => {
redisForm.value.validate(async (valid: boolean) => {
if (valid) {
state.testConnBtnLoading = true;
try {
await redisApi.testConn.request(await getReqForm());
ElMessage.success('连接成功');
} finally {
state.testConnBtnLoading = false;
}
} else {
if (!valid) {
ElMessage.error('请正确填写信息');
return false;
}
state.submitForm = await getReqForm();
await testConnExec();
ElMessage.success('连接成功');
});
};
const btnOk = async () => {
redisForm.value.validate(async (valid: boolean) => {
if (valid) {
redisApi.saveRedis.request(await getReqForm()).then(() => {
ElMessage.success('保存成功');
emit('val-change', state.form);
state.btnLoading = true;
setTimeout(() => {
state.btnLoading = false;
}, 1000);
cancel();
});
} else {
if (!valid) {
ElMessage.error('请正确填写信息');
return false;
}
state.submitForm = await getReqForm();
await saveRedisExec();
ElMessage.success('保存成功');
emit('val-change', state.form);
cancel();
});
};

View File

@@ -2,16 +2,12 @@
<div>
<page-table
ref="pageTableRef"
:page-api="redisApi.redisList"
:query="queryConfig"
v-model:query-form="query"
:show-selection="true"
v-model:selection-data="selectionData"
:data="redisTable"
:columns="columns"
:total="total"
v-model:page-size="query.pageSize"
v-model:page-num="query.pageNum"
@pageChange="search()"
>
<template #tagPathSelect>
<el-select @focus="getTags" v-model="query.tagPath" placeholder="请选择标签" @clear="search" filterable clearable style="width: 200px">
@@ -148,7 +144,7 @@
</el-dialog>
<redis-edit
@val-change="valChange"
@val-change="search"
:tags="tags"
:title="redisEditDialog.title"
v-model:visible="redisEditDialog.visible"
@@ -160,7 +156,7 @@
<script lang="ts" setup>
import Info from './Info.vue';
import { redisApi } from './api';
import { ref, toRefs, reactive, onMounted } from 'vue';
import { ref, toRefs, reactive, onMounted, Ref } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import RedisEdit from './RedisEdit.vue';
import { dateFormat } from '@/common/utils/date';
@@ -171,8 +167,8 @@ import { tagApi } from '../tag/api';
import { TagResourceTypeEnum } from '@/common/commonEnum';
import { useRoute } from 'vue-router';
const pageTableRef: any = ref(null);
const route = useRoute();
const pageTableRef: Ref<any> = ref(null);
const queryConfig = [TableQuery.slot('tagPath', '标签', 'tagPathSelect')];
const columns = ref([
@@ -186,8 +182,6 @@ const columns = ref([
const state = reactive({
tags: [],
redisTable: [],
total: 0,
selectionData: [],
query: {
tagPath: '',
@@ -222,10 +216,12 @@ const state = reactive({
},
});
const { tags, redisTable, total, selectionData, query, detailDialog, clusterInfoDialog, infoDialog, redisEditDialog } = toRefs(state);
const { tags, selectionData, query, detailDialog, clusterInfoDialog, infoDialog, redisEditDialog } = toRefs(state);
onMounted(async () => {
search();
if (route.query.tagPath) {
state.query.tagPath = route.query.tagPath as string;
}
});
const showDetail = (detail: any) => {
@@ -267,20 +263,8 @@ const onShowClusterInfo = async (redis: any) => {
state.clusterInfoDialog.visible = true;
};
const search = async () => {
try {
pageTableRef.value.loading(true);
if (route.query.tagPath) {
state.query.tagPath = route.query.tagPath as string;
}
const res = await redisApi.redisList.request(state.query);
state.redisTable = res.list;
state.total = res.total;
} finally {
pageTableRef.value.loading(false);
}
const search = () => {
pageTableRef.value.search();
};
const getTags = async () => {
@@ -297,10 +281,6 @@ const editRedis = async (data: any) => {
}
state.redisEditDialog.visible = true;
};
const valChange = () => {
search();
};
</script>
<style></style>

View File

@@ -1,16 +1,13 @@
<template>
<div>
<page-table
ref="pageTableRef"
:page-api="tagApi.getTeams"
:query="state.queryConfig"
v-model:query-form="query"
:show-selection="true"
v-model:selection-data="selectionData"
:data="data"
:columns="state.columns"
:total="total"
v-model:page-size="query.pageSize"
v-model:page-num="query.pageNum"
@pageChange="search()"
>
<template #queryRight>
<el-button v-auth="'team:save'" type="primary" icon="plus" @click="showSaveTeamDialog(false)">添加</el-button>
@@ -89,16 +86,14 @@
</template>
</el-dialog>
<el-dialog width="50%" :title="showMemDialog.title" v-model="showMemDialog.visible">
<el-dialog @open="setMemebers" width="50%" :title="showMemDialog.title" v-model="showMemDialog.visible">
<page-table
ref="showMemPageTableRef"
:page-api="tagApi.getTeamMem"
:lazy="true"
:query="showMemDialog.queryConfig"
v-model:query-form="showMemDialog.query"
:data="showMemDialog.members.list"
:columns="showMemDialog.columns"
:total="showMemDialog.members.total"
v-model:page-size="showMemDialog.query.pageSize"
v-model:page-num="showMemDialog.query.pageNum"
@pageChange="setMemebers()"
>
<template #queryRight>
<el-button v-auth="'team:member:save'" @click="showAddMemberDialog()" type="primary" icon="plus">添加</el-button>
@@ -138,7 +133,7 @@
</template>
<script lang="ts" setup>
import { ref, toRefs, reactive, onMounted } from 'vue';
import { ref, toRefs, reactive, onMounted, Ref } from 'vue';
import { tagApi } from './api';
import { accountApi } from '../../system/api';
import { ElMessage, ElMessageBox } from 'element-plus';
@@ -148,6 +143,9 @@ import { TableColumn, TableQuery } from '@/components/pagetable';
const teamForm: any = ref(null);
const tagTreeRef: any = ref(null);
const pageTableRef: Ref<any> = ref(null);
const showMemPageTableRef: Ref<any> = ref(null);
const state = reactive({
currentEditPermissions: false,
addTeamDialog: {
@@ -168,8 +166,6 @@ const state = reactive({
TableColumn.new('creator', '创建人'),
TableColumn.new('action', '操作').isSlot().setMinWidth(120).fixedRight().alignCenter(),
],
total: 0,
data: [],
selectionData: [],
showMemDialog: {
queryConfig: [TableQuery.text('username', '用户名')],
@@ -213,16 +209,12 @@ const state = reactive({
},
});
const { query, addTeamDialog, total, data, selectionData, showMemDialog, showTagDialog } = toRefs(state);
const { query, addTeamDialog, selectionData, showMemDialog, showTagDialog } = toRefs(state);
onMounted(() => {
search();
});
onMounted(() => {});
const search = async () => {
let res = await tagApi.getTeams.request(state.query);
state.data = res.list;
state.total = res.total;
pageTableRef.value.search();
};
const showSaveTeamDialog = (data: any) => {
@@ -270,7 +262,6 @@ const deleteTeam = () => {
const showMembers = async (team: any) => {
state.showMemDialog.query.teamId = team.id;
state.showMemDialog.visible = true;
await setMemebers();
state.showMemDialog.title = `[${team.name}] 成员信息`;
};
@@ -293,9 +284,7 @@ const deleteMember = async (data: any) => {
* 设置成员列表信息
*/
const setMemebers = async () => {
const res = await tagApi.getTeamMem.request(state.showMemDialog.query);
state.showMemDialog.members.list = res.list;
state.showMemDialog.members.total = res.total;
showMemPageTableRef.value.search();
};
const showAddMemberDialog = () => {

View File

@@ -22,7 +22,7 @@
<template #footer>
<div class="dialog-footer">
<el-button @click="cancel()"> </el-button>
<el-button type="primary" :loading="btnLoading" @click="btnOk"> </el-button>
<el-button type="primary" :loading="saveBtnLoading" @click="btnOk"> </el-button>
</div>
</template>
</el-dialog>
@@ -84,10 +84,11 @@ const state = reactive({
password: null,
repassword: null,
},
btnLoading: false,
});
const { dialogVisible, edit, form, btnLoading } = toRefs(state);
const { dialogVisible, edit, form } = toRefs(state);
const { isFetching: saveBtnLoading, execute: saveAccountExec } = accountApi.save.useApi(form);
watch(props, (newValue: any) => {
if (newValue.account) {
@@ -101,23 +102,18 @@ watch(props, (newValue: any) => {
});
const btnOk = async () => {
accountForm.value.validate((valid: boolean) => {
if (valid) {
accountApi.save.request(state.form).then(() => {
ElMessage.success('操作成功');
emit('val-change', state.form);
state.btnLoading = true;
setTimeout(() => {
state.btnLoading = false;
}, 1000);
//重置表单域
accountForm.value.resetFields();
state.form = {} as any;
});
} else {
accountForm.value.validate(async (valid: boolean) => {
if (!valid) {
ElMessage.error('表单填写有误');
return false;
}
await saveAccountExec();
ElMessage.success('操作成功');
emit('val-change', state.form);
//重置表单域
accountForm.value.resetFields();
state.form = {} as any;
});
};

View File

@@ -2,16 +2,12 @@
<div>
<page-table
ref="pageTableRef"
:page-api="accountApi.list"
:query="queryConfig"
v-model:query-form="query"
:show-selection="true"
v-model:selection-data="selectionData"
:data="datas"
:columns="columns"
:total="total"
v-model:page-size="query.pageSize"
v-model:page-num="query.pageNum"
@pageChange="search()"
>
<template #queryRight>
<el-button v-auth="perms.addAccount" type="primary" icon="plus" @click="editAccount(false)">添加</el-button>
@@ -81,7 +77,7 @@
</template>
<script lang="ts" setup>
import { ref, toRefs, reactive, onMounted } from 'vue';
import { ref, toRefs, reactive, onMounted, Ref } from 'vue';
import RoleEdit from './RoleEdit.vue';
import AccountEdit from './AccountEdit.vue';
import { AccountStatusEnum, ResourceTypeEnum } from '../enums';
@@ -92,8 +88,6 @@ import PageTable from '@/components/pagetable/PageTable.vue';
import { TableColumn, TableQuery } from '@/components/pagetable';
import { hasPerms } from '@/components/auth/auth';
const pageTableRef: any = ref(null);
const perms = {
addAccount: 'account:add',
delAccount: 'account:del',
@@ -118,6 +112,7 @@ const columns = ref([
const actionBtns = hasPerms([perms.addAccount, perms.saveAccountRole, perms.changeAccountStatus]);
const actionColumn = TableColumn.new('action', '操作').isSlot().fixedRight().setMinWidth(260).noShowOverflowTooltip().alignCenter();
const pageTableRef: Ref<any> = ref(null);
const state = reactive({
/**
* 选中的数据
@@ -131,8 +126,6 @@ const state = reactive({
pageNum: 1,
pageSize: 0,
},
datas: [],
total: 0,
showRoleDialog: {
title: '',
visible: false,
@@ -158,24 +151,16 @@ const state = reactive({
},
});
const { selectionData, query, datas, total, showRoleDialog, showResourceDialog, roleDialog, accountDialog } = toRefs(state);
const { selectionData, query, showRoleDialog, showResourceDialog, roleDialog, accountDialog } = toRefs(state);
onMounted(() => {
if (Object.keys(actionBtns).length > 0) {
columns.value.push(actionColumn);
}
search();
});
const search = async () => {
try {
pageTableRef.value.loading(true);
let res: any = await accountApi.list.request(state.query);
state.datas = res.list;
state.total = res.total;
} finally {
pageTableRef.value.loading(false);
}
pageTableRef.value.search();
};
const showResources = async (row: any) => {

View File

@@ -33,7 +33,7 @@
<template #footer>
<div class="dialog-footer">
<el-button @click="cancel()"> </el-button>
<el-button type="primary" :loading="btnLoading" @click="btnOk"> </el-button>
<el-button type="primary" :loading="saveBtnLoading" @click="btnOk"> </el-button>
</div>
</template>
</el-dialog>
@@ -76,10 +76,11 @@ const state = reactive({
remark: '',
permission: '',
},
btnLoading: false,
});
const { dvisible, params, form, btnLoading } = toRefs(state);
const { dvisible, params, form } = toRefs(state);
const { isFetching: saveBtnLoading, execute: saveConfigExec } = configApi.save.useApi(form);
watch(props, (newValue: any) => {
state.dvisible = newValue.visible;
@@ -134,13 +135,10 @@ const btnOk = async () => {
} else {
state.form.permission = 'all';
}
await configApi.save.request(state.form);
await saveConfigExec();
emit('val-change', state.form);
cancel();
state.btnLoading = true;
setTimeout(() => {
state.btnLoading = false;
}, 1000);
}
});
};

View File

@@ -1,15 +1,6 @@
<template>
<div>
<page-table
:show-selection="true"
v-model:selection-data="selectionData"
:data="configs"
:columns="columns"
:total="total"
v-model:page-size="query.pageSize"
v-model:page-num="query.pageNum"
@pageChange="search()"
>
<page-table ref="pageTableRef" :page-api="configApi.list" v-model:selection-data="selectionData" :columns="columns">
<template #queryRight>
<el-button v-auth="perms.saveConfig" type="primary" icon="plus" @click="editConfig(false)">添加</el-button>
</template>
@@ -52,7 +43,7 @@
</template>
<script lang="ts" setup>
import { ref, toRefs, reactive, onMounted } from 'vue';
import { ref, toRefs, reactive, onMounted, Ref } from 'vue';
import ConfigEdit from './ConfigEdit.vue';
import { configApi } from '../api';
import { ElMessage } from 'element-plus';
@@ -75,15 +66,15 @@ const columns = ref([
const actionColumn = TableColumn.new('action', '操作').isSlot().fixedRight().setMinWidth(130).noShowOverflowTooltip().alignCenter();
const actionBtns = hasPerms([perms.saveConfig]);
const pageTableRef: Ref<any> = ref(null);
const paramsFormRef: any = ref(null);
const state = reactive({
query: {
pageNum: 1,
pageSize: 0,
name: null,
},
total: 0,
configs: [],
selectionData: [],
paramsDialog: {
visible: false,
@@ -98,19 +89,16 @@ const state = reactive({
},
});
const { query, total, configs, selectionData, paramsDialog, configEdit } = toRefs(state);
const { selectionData, paramsDialog, configEdit } = toRefs(state);
onMounted(() => {
if (Object.keys(actionBtns).length > 0) {
columns.value.push(actionColumn);
}
search();
});
const search = async () => {
let res = await configApi.list.request(state.query);
state.configs = res.list;
state.total = res.total;
pageTableRef.value.search();
};
const showSetConfigDialog = (row: any) => {

View File

@@ -132,7 +132,7 @@
<template #footer>
<div>
<el-button @click="cancel()"> </el-button>
<el-button type="primary" :loading="btnLoading" @click="btnOk"> </el-button>
<el-button type="primary" :loading="saveBtnLoading" @click="btnOk"> </el-button>
</div>
</template>
</el-dialog>
@@ -222,10 +222,12 @@ const state = reactive({
link: '',
},
},
btnLoading: false,
submitForm: {},
});
const { dialogVisible, form, btnLoading } = toRefs(state);
const { dialogVisible, form, submitForm } = toRefs(state);
const { isFetching: saveBtnLoading, execute: saveResouceExec } = resourceApi.save.useApi(submitForm);
watch(props, (newValue: any) => {
state.dialogVisible = newValue.visible;
@@ -264,20 +266,15 @@ const btnOk = () => {
} else {
submitForm.meta = null as any;
}
menuForm.value.validate((valid: any) => {
if (valid) {
resourceApi.save.request(submitForm).then(() => {
emit('val-change', submitForm);
state.btnLoading = true;
ElMessage.success('保存成功');
setTimeout(() => {
state.btnLoading = false;
}, 1000);
cancel();
});
} else {
return false;
menuForm.value.validate(async (valid: any) => {
if (valid) {
state.submitForm = submitForm;
await saveResouceExec();
emit('val-change', submitForm);
ElMessage.success('保存成功');
cancel();
}
});
};

View File

@@ -223,8 +223,7 @@ const deleteMenu = (data: any) => {
.request({
id: data.id,
})
.then((res) => {
console.log(res);
.then(() => {
ElMessage.success('删除成功!');
search();
});

View File

@@ -20,7 +20,7 @@
<template #footer>
<div class="dialog-footer">
<el-button @click="cancel()"> </el-button>
<el-button type="primary" :loading="btnLoading" @click="btnOk"> </el-button>
<el-button type="primary" :loading="saveBtnLoading" @click="btnOk"> </el-button>
</div>
</template>
</el-dialog>
@@ -57,10 +57,11 @@ const state = reactive({
status: 1,
remark: '',
},
btnLoading: false,
});
const { dvisible, form, btnLoading } = toRefs(state);
const { dvisible, form } = toRefs(state);
const { isFetching: saveBtnLoading, execute: saveRoleExec } = roleApi.save.useApi(form);
watch(props, (newValue: any) => {
state.dvisible = newValue.visible;
@@ -81,13 +82,9 @@ const cancel = () => {
const btnOk = async () => {
roleForm.value.validate(async (valid: boolean) => {
if (valid) {
await roleApi.save.request(state.form);
await saveRoleExec();
emit('val-change', state.form);
cancel();
state.btnLoading = true;
setTimeout(() => {
state.btnLoading = false;
}, 1000);
}
});
};

View File

@@ -1,17 +1,13 @@
<template>
<div>
<page-table
ref="pageTableRef"
:query="queryConfig"
v-model:query-form="query"
:show-selection="true"
v-model:selection-data="selectionData"
:data="roles"
:columns="columns"
:total="total"
v-model:page-size="query.pageSize"
v-model:page-num="query.pageNum"
@pageChange="search()"
:page-api="roleApi.list"
ref="pageTableRef"
>
<template #queryRight>
<el-button v-auth="perms.addRole" type="primary" icon="plus" @click="editRole(false)">添加</el-button>
@@ -40,7 +36,7 @@
</template>
<script lang="ts" setup>
import { ref, toRefs, reactive, onMounted } from 'vue';
import { ref, toRefs, reactive, onMounted, Ref } from 'vue';
import RoleEdit from './RoleEdit.vue';
import ResourceEdit from './ResourceEdit.vue';
import ShowResource from './ShowResource.vue';
@@ -51,8 +47,6 @@ import { TableColumn, TableQuery } from '@/components/pagetable';
import { hasPerms } from '@/components/auth/auth';
import { RoleStatusEnum } from '../enums';
const pageTableRef: any = ref(null);
const perms = {
addRole: 'role:add',
delRole: 'role:del',
@@ -75,14 +69,13 @@ const columns = ref([
const actionBtns = hasPerms([perms.updateRole, perms.saveRoleResource]);
const actionColumn = TableColumn.new('action', '操作').isSlot().setMinWidth(260).fixedRight().alignCenter();
const pageTableRef: Ref<any> = ref(null);
const state = reactive({
query: {
pageNum: 1,
pageSize: 0,
name: null,
},
total: 0,
roles: [],
selectionData: [],
resourceDialog: {
visible: false,
@@ -102,24 +95,16 @@ const state = reactive({
},
});
const { query, total, roles, selectionData, resourceDialog, roleEditDialog, showResourceDialog } = toRefs(state);
const { query, selectionData, resourceDialog, roleEditDialog, showResourceDialog } = toRefs(state);
onMounted(() => {
if (Object.keys(actionBtns).length > 0) {
columns.value.push(actionColumn);
}
search();
});
const search = async () => {
try {
pageTableRef.value.loading(true);
let res = await roleApi.list.request(state.query);
state.roles = res.list;
state.total = res.total;
} finally {
pageTableRef.value.loading(false);
}
const search = () => {
pageTableRef.value.search();
};
const roleEditChange = () => {

View File

@@ -1,16 +1,6 @@
<template>
<div>
<page-table
ref="pageTableRef"
:query="state.queryConfig"
v-model:query-form="query"
:data="logs"
:columns="state.columns"
:total="total"
v-model:page-size="query.pageSize"
v-model:page-num="query.pageNum"
@pageChange="search()"
>
<page-table :query="queryConfig" v-model:query-form="query" :columns="columns" :page-api="logApi.list">
<template #selectAccount>
<el-select
style="width: 200px"
@@ -29,13 +19,26 @@
</template>
<script lang="ts" setup>
import { ref, toRefs, reactive, onMounted } from 'vue';
import { toRefs, reactive } from 'vue';
import { logApi, accountApi } from '../api';
import PageTable from '@/components/pagetable/PageTable.vue';
import { TableColumn, TableQuery } from '@/components/pagetable';
import { LogTypeEnum } from '../enums';
const pageTableRef: any = ref(null);
const queryConfig = [
TableQuery.slot('creatorId', '操作人', 'selectAccount'),
TableQuery.select('type', '操作结果').setOptions(Object.values(LogTypeEnum)),
TableQuery.text('description', '描述'),
];
const columns = [
TableColumn.new('creator', '操作人'),
TableColumn.new('createTime', '操作时间').isTime(),
TableColumn.new('type', '结果').typeTag(LogTypeEnum),
TableColumn.new('description', '描述'),
TableColumn.new('reqParam', '操作信息').canBeautify(),
TableColumn.new('resp', '响应信息'),
];
const state = reactive({
query: {
@@ -45,40 +48,10 @@ const state = reactive({
pageNum: 1,
pageSize: 0,
},
queryConfig: [
TableQuery.slot('creatorId', '操作人', 'selectAccount'),
TableQuery.select('type', '操作结果').setOptions(Object.values(LogTypeEnum)),
TableQuery.text('description', '描述'),
],
columns: [
TableColumn.new('creator', '操作人'),
TableColumn.new('createTime', '操作时间').isTime(),
TableColumn.new('type', '结果').typeTag(LogTypeEnum),
TableColumn.new('description', '描述'),
TableColumn.new('reqParam', '操作信息').canBeautify(),
TableColumn.new('resp', '响应信息'),
],
total: 0,
logs: [],
accounts: [] as any,
});
const { query, total, logs, accounts } = toRefs(state);
onMounted(() => {
search();
});
const search = async () => {
try {
pageTableRef.value.loading(true);
let res = await logApi.list.request(state.query);
state.logs = res.list;
state.total = res.total;
} finally {
pageTableRef.value.loading(false);
}
};
const { query, accounts } = toRefs(state);
const getAccount = (username: any) => {
accountApi.list.request({ username }).then((res) => {

View File

@@ -62,9 +62,11 @@ func (app *instanceAppImpl) Save(ctx context.Context, instanceEntity *entity.DbI
instanceEntity.Network = instanceEntity.GetNetwork()
// 查找是否存在该库
oldInstance := &entity.DbInstance{Host: instanceEntity.Host, Port: instanceEntity.Port, Username: instanceEntity.Username}
if instanceEntity.SshTunnelMachineId > 0 {
oldInstance.SshTunnelMachineId = instanceEntity.SshTunnelMachineId
oldInstance := &entity.DbInstance{
Host: instanceEntity.Host,
Port: instanceEntity.Port,
Username: instanceEntity.Username,
SshTunnelMachineId: instanceEntity.SshTunnelMachineId,
}
err := app.GetBy(oldInstance)

View File

@@ -74,10 +74,13 @@ func (m *machineAppImpl) GetMachineList(condition *entity.MachineQuery, pagePara
}
func (m *machineAppImpl) Save(ctx context.Context, me *entity.Machine, tagIds ...uint64) error {
oldMachine := &entity.Machine{Ip: me.Ip, Port: me.Port, Username: me.Username}
if me.SshTunnelMachineId > 0 {
oldMachine.SshTunnelMachineId = me.SshTunnelMachineId
oldMachine := &entity.Machine{
Ip: me.Ip,
Port: me.Port,
Username: me.Username,
SshTunnelMachineId: me.SshTunnelMachineId,
}
err := m.GetBy(oldMachine)
me.PwdEncrypt()

View File

@@ -70,9 +70,9 @@ func (r *redisAppImpl) TestConn(re *entity.Redis) error {
func (r *redisAppImpl) Save(ctx context.Context, re *entity.Redis, tagIds ...uint64) error {
// 查找是否存在该库
oldRedis := &entity.Redis{Host: re.Host}
if re.SshTunnelMachineId > 0 {
oldRedis.SshTunnelMachineId = re.SshTunnelMachineId
oldRedis := &entity.Redis{
Host: re.Host,
SshTunnelMachineId: re.SshTunnelMachineId,
}
err := r.GetBy(oldRedis)

View File

@@ -160,37 +160,35 @@ func (p *tagTreeAppImpl) RelateResource(ctx context.Context, resourceCode string
addTagIds, delTagIds, _ = collx.ArrayCompare[uint64](tagIds, oldTagIds, func(u1, u2 uint64) bool { return u1 == u2 })
}
return p.Tx(ctx, func(ctx context.Context) error {
if len(addTagIds) > 0 {
addTagResource := make([]*entity.TagResource, 0)
for _, tagId := range addTagIds {
tag, err := p.GetById(new(entity.TagTree), tagId)
if err != nil {
return errorx.NewBiz("存在错误标签id")
}
addTagResource = append(addTagResource, &entity.TagResource{
ResourceCode: resourceCode,
ResourceType: resourceType,
TagId: tagId,
TagPath: tag.CodePath,
})
if len(addTagIds) > 0 {
addTagResource := make([]*entity.TagResource, 0)
for _, tagId := range addTagIds {
tag, err := p.GetById(new(entity.TagTree), tagId)
if err != nil {
return errorx.NewBiz("存在错误标签id")
}
if err := p.tagResourceApp.BatchInsert(ctx, addTagResource); err != nil {
addTagResource = append(addTagResource, &entity.TagResource{
ResourceCode: resourceCode,
ResourceType: resourceType,
TagId: tagId,
TagPath: tag.CodePath,
})
}
if err := p.tagResourceApp.BatchInsert(ctx, addTagResource); err != nil {
return err
}
}
if len(delTagIds) > 0 {
for _, tagId := range delTagIds {
cond := &entity.TagResource{ResourceCode: resourceCode, ResourceType: resourceType, TagId: tagId}
if err := p.tagResourceApp.DeleteByCond(ctx, cond); err != nil {
return err
}
}
}
if len(delTagIds) > 0 {
for _, tagId := range delTagIds {
cond := &entity.TagResource{ResourceCode: resourceCode, ResourceType: resourceType, TagId: tagId}
if err := p.tagResourceApp.DeleteByCond(ctx, cond); err != nil {
return err
}
}
}
return nil
})
return nil
}
func (p *tagTreeAppImpl) ListTagPathByResource(resourceType int8, resourceCode string) []string {