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 request from './request';
import { randomUuid } from './utils/string'; import { useApiFetch } from './useRequest';
/** /**
* 可用于各模块定义各自api请求 * 可用于各模块定义各自api请求
@@ -21,8 +21,6 @@ class Api {
*/ */
beforeHandler: Function; beforeHandler: Function;
static abortControllers: Map<string, AbortController> = new Map();
constructor(url: string, method: string) { constructor(url: string, method: string) {
this.url = url; this.url = url;
this.method = method; 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的参数 * @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) { if (this.beforeHandler) {
this.beforeHandler(param); this.beforeHandler(param);
} }
return request.request(this.method, this.url, param, options); return request.xhrReq(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);
} }
/** 静态方法 **/ /** 静态方法 **/
/**
* 取消请求
* @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属性 * 静态工厂返回Api对象并设置url与method属性
* @param url url * @param url url
@@ -151,3 +119,8 @@ class Api {
} }
export default 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 { templateResolve } from './utils/string';
import { ElMessage } from 'element-plus'; import { ElMessage } from 'element-plus';
import axios from 'axios'; import axios from 'axios';
import { useApiFetch } from './useRequest';
import Api from './Api';
export default { export default {
request, request,
fetchReq, xhrReq,
get, get,
post, post,
put, put,
@@ -30,7 +32,7 @@ export interface Result {
data?: any; data?: any;
} }
enum ResultEnum { export enum ResultEnum {
SUCCESS = 200, SUCCESS = 200,
ERROR = 400, ERROR = 400,
PARAM_ERROR = 405, PARAM_ERROR = 405,
@@ -38,7 +40,7 @@ enum ResultEnum {
NO_PERMISSION = 501, NO_PERMISSION = 501,
} }
const baseUrl: string = config.baseApiUrl; export const baseUrl: string = config.baseApiUrl;
// const baseUrl: string = 'http://localhost:18888/api'; // const baseUrl: string = 'http://localhost:18888/api';
// const baseWsUrl: string = config.baseWsUrl; // const baseWsUrl: string = config.baseWsUrl;
@@ -115,14 +117,15 @@ axiosInst.interceptors.response.use(
); );
/** /**
* 请求uri * xhr请求url
* 该方法已处理请求结果中code != 200的message提示,如需其他错误处理(取消加载状态,重置对象状态等等),可catch继续处理
* *
* @param {Object} method 请求方法(GET,POST,PUT,DELTE等) * @param method 请求方法
* @param {Object} uri uri * @param url url
* @param {Object} params 参数 * @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) { if (!url) {
throw new Error('请求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 * get请求uri
* 该方法已处理请求结果中code != 200的message提示,如需其他错误处理(取消加载状态,重置对象状态等等),可catch继续处理 * 该方法已处理请求结果中code != 200的message提示,如需其他错误处理(取消加载状态,重置对象状态等等),可catch继续处理
@@ -190,64 +208,6 @@ export function joinClientParams(): string {
return `token=${getToken()}&clientId=${getClientId()}`; 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) { function parseResult(result: Result) {
if (result.code === ResultEnum.SUCCESS) { if (result.code === ResultEnum.SUCCESS) {
return result.data; 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 class="query" ref="queryRef">
<div> <div>
<div v-if="props.query.length > 0"> <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 <el-row
v-for="i in Math.ceil((props.query.length + 1) / (defaultQueryCount + 1))" v-for="i in Math.ceil((props.query.length + 1) / (defaultQueryCount + 1))"
:key="i" :key="i"
@@ -104,9 +104,9 @@
v-bind="$attrs" v-bind="$attrs"
:max-height="tableMaxHeight" :max-height="tableMaxHeight"
@selection-change="handleSelectionChange" @selection-change="handleSelectionChange"
:data="props.data" :data="state.data"
highlight-current-row highlight-current-row
v-loading="loadingData" v-loading="state.loading"
:size="props.size" :size="props.size"
> >
<el-table-column v-if="props.showSelection" type="selection" width="40" /> <el-table-column v-if="props.showSelection" type="selection" width="40" />
@@ -171,9 +171,9 @@
@size-change="handleSizeChange" @size-change="handleSizeChange"
style="text-align: right" style="text-align: right"
layout="prev, pager, next, total, sizes, jumper" layout="prev, pager, next, total, sizes, jumper"
:total="props.total" :total="state.total"
v-model:current-page="state.pageNum" v-model:current-page="queryForm_.pageNum"
v-model:page-size="state.pageSize" v-model:page-size="queryForm_.pageSize"
:page-sizes="pageSizes" :page-sizes="pageSizes"
/> />
</el-row> </el-row>
@@ -182,11 +182,13 @@
</template> </template>
<script lang="ts" setup> <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 { TableColumn, TableQuery } from './index';
import EnumTag from '@/components/enumtag/EnumTag.vue'; import EnumTag from '@/components/enumtag/EnumTag.vue';
import { useThemeConfig } from '@/store/themeConfig'; import { useThemeConfig } from '@/store/themeConfig';
import { storeToRefs } from 'pinia'; 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']); const emit = defineEmits(['update:queryForm', 'update:pageNum', 'update:pageSize', 'update:selectionData', 'pageChange']);
@@ -216,22 +218,19 @@ const props = defineProps({
}, },
required: true, required: true,
}, },
// 表格数据 // 调用分页数据的api
data: { pageApi: {
type: Array, type: Api,
required: true, required: true,
}, },
total: { // 数据处理回调函数,用于将请求回来的数据二次加工处理等
type: [Number], dataHandlerFn: {
default: 0, type: Function,
}, },
pageNum: { // 懒加载即需要手动调用search方法才可调接口获取数据不会在mounted的时候调用。
type: Number, lazy: {
default: 1, type: Boolean,
}, default: false,
pageSize: {
type: [Number],
default: 10,
}, },
// 查询条件配置 // 查询条件配置
query: { query: {
@@ -244,7 +243,10 @@ const props = defineProps({
queryForm: { queryForm: {
type: Object, type: Object,
default: function () { default: function () {
return {}; return {
pageNum: 1,
pageSize: 10,
};
}, },
}, },
}); });
@@ -253,56 +255,36 @@ const { themeConfig } = storeToRefs(useThemeConfig());
const state = reactive({ const state = reactive({
pageSizes: [] as any, // 可选每页显示的数据量 pageSizes: [] as any, // 可选每页显示的数据量
pageSize: 10,
pageNum: 1,
isOpenMoreQuery: false, isOpenMoreQuery: false,
defaultQueryCount: 2, // 默认显示的查询参数个数展开后每行显示查询条件个数为该值加1。第一行用最后一列来占用按钮 defaultQueryCount: 2, // 默认显示的查询参数个数展开后每行显示查询条件个数为该值加1。第一行用最后一列来占用按钮
queryForm_: {} as any, loading: false,
loadingData: false, data: [],
total: 0,
// 输入框宽度 // 输入框宽度
inputWidth_: '200px' as any, inputWidth_: '200px' as any,
formatVal: '', // 格式化后的值 formatVal: '', // 格式化后的值
tableMaxHeight: window.innerHeight - 240 + 'px', 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( watch(
() => props.queryForm, () => state.data,
(newValue: any) => {
state.queryForm_ = newValue;
}
);
watch(
() => props.pageNum,
(newValue: any) => {
state.pageNum = newValue;
}
);
watch(
() => props.pageSize,
(newValue: any) => {
state.pageSize = newValue;
}
);
watch(
() => props.data,
(newValue: any) => { (newValue: any) => {
if (newValue && newValue.length > 0) { if (newValue && newValue.length > 0) {
props.columns.forEach((item) => { props.columns.forEach((item) => {
if (item.autoWidth && item.show) { if (item.autoWidth && item.show) {
item.autoCalculateMinWidth(props.data); item.autoCalculateMinWidth(state.data);
} }
}); });
} }
} }
); );
onMounted(() => { onMounted(async () => {
let pageSize = props.pageSize; let pageSize = queryForm_.value.pageSize;
// 如果pageSize设为0则使用系统全局配置的pageSize // 如果pageSize设为0则使用系统全局配置的pageSize
if (!pageSize) { if (!pageSize) {
@@ -311,12 +293,10 @@ onMounted(() => {
if (!pageSize) { if (!pageSize) {
pageSize = 10; pageSize = 10;
} }
emit('update:pageSize', pageSize);
} }
state.pageNum = props.pageNum; queryForm_.value.pageNum = 1;
state.pageSize = pageSize; queryForm_.value.pageSize = pageSize;
state.queryForm_ = props.queryForm;
state.pageSizes = [pageSize, pageSize * 2, pageSize * 3, pageSize * 4, pageSize * 5]; state.pageSizes = [pageSize, pageSize * 2, pageSize * 3, pageSize * 4, pageSize * 5];
// 如果没传输入框宽度则根据组件size设置默认宽度 // 如果没传输入框宽度则根据组件size设置默认宽度
@@ -329,6 +309,10 @@ onMounted(() => {
window.addEventListener('resize', () => { window.addEventListener('resize', () => {
calcuTableHeight(); calcuTableHeight();
}); });
if (!props.lazy) {
await reqPageApi();
}
}); });
const calcuTableHeight = () => { const calcuTableHeight = () => {
@@ -360,14 +344,28 @@ const handleSelectionChange = (val: any) => {
emit('update:selectionData', val); emit('update:selectionData', val);
}; };
const handlePageChange = () => { const reqPageApi = async () => {
emit('update:pageNum', state.pageNum); 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(); execQuery();
}; };
const handleSizeChange = () => { const handleSizeChange = () => {
changePageNum(1); changePageNum(1);
emit('update:pageSize', state.pageSize);
execQuery(); execQuery();
}; };
@@ -379,31 +377,24 @@ const queryData = () => {
const reset = () => { const reset = () => {
// 将查询参数绑定的值置空,并重新粗发查询接口 // 将查询参数绑定的值置空,并重新粗发查询接口
for (let qi of props.query) { for (let qi of props.query) {
state.queryForm_[qi.prop] = null; queryForm_.value[qi.prop] = null;
} }
changePageNum(1); changePageNum(1);
emit('update:queryForm', state.queryForm_);
execQuery(); execQuery();
}; };
const changePageNum = (pageNum: number) => { const changePageNum = (pageNum: number) => {
state.pageNum = pageNum; queryForm_.value.pageNum = pageNum;
emit('update:pageNum', state.pageNum);
}; };
const execQuery = () => { const execQuery = async () => {
emit('pageChange'); await reqPageApi();
}; };
/** defineExpose({
* 是否正在加载数据 search: execQuery,
*/ });
const loading = (loading: boolean) => {
state.loadingData = loading;
};
defineExpose({ loading });
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.page-table { .page-table {

View File

@@ -77,7 +77,7 @@
<template #footer> <template #footer>
<div class="dialog-footer"> <div class="dialog-footer">
<el-button @click="cancel()">取 消</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> </div>
</template> </template>
</el-dialog> </el-dialog>
@@ -155,11 +155,12 @@ const state = reactive({
remark: '', remark: '',
instanceId: null as any, instanceId: null as any,
}, },
btnLoading: false,
instances: [] as any, 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) => { watch(props, async (newValue: any) => {
state.dialogVisible = newValue.visible; state.dialogVisible = newValue.visible;
@@ -216,22 +217,15 @@ const open = async () => {
const btnOk = async () => { const btnOk = async () => {
dbForm.value.validate(async (valid: boolean) => { dbForm.value.validate(async (valid: boolean) => {
if (valid) { 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 {
ElMessage.error('请正确填写信息'); ElMessage.error('请正确填写信息');
return false; return false;
} }
await saveDbExec();
ElMessage.success('保存成功');
emit('val-change', state.form);
cancel();
}); });
}; };

View File

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

View File

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

View File

@@ -71,9 +71,9 @@
<template #footer> <template #footer>
<div class="dialog-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 @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> </div>
</template> </template>
</el-dialog> </el-dialog>
@@ -168,15 +168,17 @@ const state = reactive({
remark: '', remark: '',
sshTunnelMachineId: null as any, sshTunnelMachineId: null as any,
}, },
subimtForm: {},
// 原密码 // 原密码
pwd: '', pwd: '',
// 原用户名 // 原用户名
oldUserName: null, 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) => { watch(props, (newValue: any) => {
state.dialogVisible = newValue.visible; state.dialogVisible = newValue.visible;
@@ -214,18 +216,14 @@ const getReqForm = async () => {
const testConn = async () => { const testConn = async () => {
dbForm.value.validate(async (valid: boolean) => { dbForm.value.validate(async (valid: boolean) => {
if (valid) { if (!valid) {
state.testConnBtnLoading = true;
try {
await dbApi.testConn.request(await getReqForm());
ElMessage.success('连接成功');
} finally {
state.testConnBtnLoading = false;
}
} else {
ElMessage.error('请正确填写信息'); ElMessage.error('请正确填写信息');
return false; return false;
} }
state.subimtForm = await getReqForm();
await testConnExec();
ElMessage.success('连接成功');
}); });
}; };
@@ -237,21 +235,16 @@ const btnOk = async () => {
} }
dbForm.value.validate(async (valid: boolean) => { dbForm.value.validate(async (valid: boolean) => {
if (valid) { 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 {
ElMessage.error('请正确填写信息'); ElMessage.error('请正确填写信息');
return false; 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"> <div class="db-list">
<page-table <page-table
ref="pageTableRef" ref="pageTableRef"
:page-api="dbApi.instances"
:query="queryConfig" :query="queryConfig"
v-model:query-form="query" v-model:query-form="query"
:show-selection="true" :show-selection="true"
v-model:selection-data="state.selectionData" v-model:selection-data="state.selectionData"
:data="datas"
:columns="columns" :columns="columns"
:total="total"
v-model:page-size="query.pageSize"
v-model:page-num="query.pageNum"
@pageChange="search()"
> >
<template #queryRight> <template #queryRight>
<el-button v-auth="perms.saveInstance" type="primary" icon="plus" @click="editInstance(false)">添加</el-button> <el-button v-auth="perms.saveInstance" type="primary" icon="plus" @click="editInstance(false)">添加</el-button>
@@ -56,7 +52,7 @@
</el-dialog> </el-dialog>
<instance-edit <instance-edit
@val-change="valChange" @val-change="search"
:title="instanceEditDialog.title" :title="instanceEditDialog.title"
v-model:visible="instanceEditDialog.visible" v-model:visible="instanceEditDialog.visible"
v-model:data="instanceEditDialog.data" v-model:data="instanceEditDialog.data"
@@ -65,7 +61,7 @@
</template> </template>
<script lang="ts" setup> <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 { ElMessage, ElMessageBox } from 'element-plus';
import { dbApi } from './api'; import { dbApi } from './api';
import { dateFormat } from '@/common/utils/date'; import { dateFormat } from '@/common/utils/date';
@@ -96,8 +92,7 @@ const columns = ref([
// 该用户拥有的的操作列按钮权限 // 该用户拥有的的操作列按钮权限
const actionBtns = hasPerms([perms.saveInstance]); const actionBtns = hasPerms([perms.saveInstance]);
const actionColumn = TableColumn.new('action', '操作').isSlot().setMinWidth(110).fixedRight().alignCenter(); const actionColumn = TableColumn.new('action', '操作').isSlot().setMinWidth(110).fixedRight().alignCenter();
const pageTableRef: Ref<any> = ref(null);
const pageTableRef: any = ref(null);
const state = reactive({ const state = reactive({
row: {}, row: {},
@@ -115,8 +110,6 @@ const state = reactive({
pageNum: 1, pageNum: 1,
pageSize: 0, pageSize: 0,
}, },
datas: [],
total: 0,
infoDialog: { infoDialog: {
visible: false, visible: false,
data: null as any, 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 () => { onMounted(async () => {
if (Object.keys(actionBtns).length > 0) { if (Object.keys(actionBtns).length > 0) {
columns.value.push(actionColumn); columns.value.push(actionColumn);
} }
search();
}); });
const search = async () => { const search = () => {
try { pageTableRef.value.search();
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 showInfo = (info: any) => { const showInfo = (info: any) => {
@@ -164,10 +149,6 @@ const editInstance = async (data: any) => {
state.instanceEditDialog.visible = true; state.instanceEditDialog.visible = true;
}; };
const valChange = () => {
search();
};
const deleteInstance = async () => { const deleteInstance = async () => {
try { try {
await ElMessageBox.confirm(`确定删除数据库实例【${state.selectionData.map((x: any) => x.name).join(', ')}】?`, '提示', { await ElMessageBox.confirm(`确定删除数据库实例【${state.selectionData.map((x: any) => x.name).join(', ')}】?`, '提示', {

View File

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

View File

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

View File

@@ -196,16 +196,7 @@ export class DbInst {
* @param sql sql * @param sql sql
* @param remark 执行备注 * @param remark 执行备注
*/ */
async runSql(dbName: string, sql: string, remark: string = '', key: string = '') { async runSql(dbName: string, sql: string, remark: string = '') {
if (key) {
return await dbApi.sqlExec.allowCancelReq(key, {
id: this.id,
db: dbName,
sql: sql.trim(),
remark,
});
}
return await dbApi.sqlExec.request({ return await dbApi.sqlExec.request({
id: this.id, id: this.id,
db: dbName, 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 * 获取count sql
* @param table 表名 * @param table 表名

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -84,9 +84,9 @@
<template #footer> <template #footer>
<div class="dialog-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 @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> </div>
</template> </template>
</el-dialog> </el-dialog>
@@ -173,13 +173,15 @@ const state = reactive({
remark: '', remark: '',
sshTunnelMachineId: -1, sshTunnelMachineId: -1,
}, },
submitForm: {} as any,
dbList: [0], dbList: [0],
pwd: '', 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) => { watch(props, async (newValue: any) => {
state.dialogVisible = newValue.visible; state.dialogVisible = newValue.visible;
@@ -226,38 +228,28 @@ const getReqForm = async () => {
const testConn = async () => { const testConn = async () => {
redisForm.value.validate(async (valid: boolean) => { redisForm.value.validate(async (valid: boolean) => {
if (valid) { if (!valid) {
state.testConnBtnLoading = true;
try {
await redisApi.testConn.request(await getReqForm());
ElMessage.success('连接成功');
} finally {
state.testConnBtnLoading = false;
}
} else {
ElMessage.error('请正确填写信息'); ElMessage.error('请正确填写信息');
return false; return false;
} }
state.submitForm = await getReqForm();
await testConnExec();
ElMessage.success('连接成功');
}); });
}; };
const btnOk = async () => { const btnOk = async () => {
redisForm.value.validate(async (valid: boolean) => { redisForm.value.validate(async (valid: boolean) => {
if (valid) { 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 {
ElMessage.error('请正确填写信息'); ElMessage.error('请正确填写信息');
return false; return false;
} }
state.submitForm = await getReqForm();
await saveRedisExec();
ElMessage.success('保存成功');
emit('val-change', state.form);
cancel();
}); });
}; };

View File

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

View File

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

View File

@@ -22,7 +22,7 @@
<template #footer> <template #footer>
<div class="dialog-footer"> <div class="dialog-footer">
<el-button @click="cancel()"> </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> </div>
</template> </template>
</el-dialog> </el-dialog>
@@ -84,10 +84,11 @@ const state = reactive({
password: null, password: null,
repassword: 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) => { watch(props, (newValue: any) => {
if (newValue.account) { if (newValue.account) {
@@ -101,23 +102,18 @@ watch(props, (newValue: any) => {
}); });
const btnOk = async () => { const btnOk = async () => {
accountForm.value.validate((valid: boolean) => { accountForm.value.validate(async (valid: boolean) => {
if (valid) { 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 {
ElMessage.error('表单填写有误'); ElMessage.error('表单填写有误');
return false; 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> <div>
<page-table <page-table
ref="pageTableRef" ref="pageTableRef"
:page-api="accountApi.list"
:query="queryConfig" :query="queryConfig"
v-model:query-form="query" v-model:query-form="query"
:show-selection="true" :show-selection="true"
v-model:selection-data="selectionData" v-model:selection-data="selectionData"
:data="datas"
:columns="columns" :columns="columns"
:total="total"
v-model:page-size="query.pageSize"
v-model:page-num="query.pageNum"
@pageChange="search()"
> >
<template #queryRight> <template #queryRight>
<el-button v-auth="perms.addAccount" type="primary" icon="plus" @click="editAccount(false)">添加</el-button> <el-button v-auth="perms.addAccount" type="primary" icon="plus" @click="editAccount(false)">添加</el-button>
@@ -81,7 +77,7 @@
</template> </template>
<script lang="ts" setup> <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 RoleEdit from './RoleEdit.vue';
import AccountEdit from './AccountEdit.vue'; import AccountEdit from './AccountEdit.vue';
import { AccountStatusEnum, ResourceTypeEnum } from '../enums'; import { AccountStatusEnum, ResourceTypeEnum } from '../enums';
@@ -92,8 +88,6 @@ import PageTable from '@/components/pagetable/PageTable.vue';
import { TableColumn, TableQuery } from '@/components/pagetable'; import { TableColumn, TableQuery } from '@/components/pagetable';
import { hasPerms } from '@/components/auth/auth'; import { hasPerms } from '@/components/auth/auth';
const pageTableRef: any = ref(null);
const perms = { const perms = {
addAccount: 'account:add', addAccount: 'account:add',
delAccount: 'account:del', delAccount: 'account:del',
@@ -118,6 +112,7 @@ const columns = ref([
const actionBtns = hasPerms([perms.addAccount, perms.saveAccountRole, perms.changeAccountStatus]); const actionBtns = hasPerms([perms.addAccount, perms.saveAccountRole, perms.changeAccountStatus]);
const actionColumn = TableColumn.new('action', '操作').isSlot().fixedRight().setMinWidth(260).noShowOverflowTooltip().alignCenter(); const actionColumn = TableColumn.new('action', '操作').isSlot().fixedRight().setMinWidth(260).noShowOverflowTooltip().alignCenter();
const pageTableRef: Ref<any> = ref(null);
const state = reactive({ const state = reactive({
/** /**
* 选中的数据 * 选中的数据
@@ -131,8 +126,6 @@ const state = reactive({
pageNum: 1, pageNum: 1,
pageSize: 0, pageSize: 0,
}, },
datas: [],
total: 0,
showRoleDialog: { showRoleDialog: {
title: '', title: '',
visible: false, 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(() => { onMounted(() => {
if (Object.keys(actionBtns).length > 0) { if (Object.keys(actionBtns).length > 0) {
columns.value.push(actionColumn); columns.value.push(actionColumn);
} }
search();
}); });
const search = async () => { const search = async () => {
try { pageTableRef.value.search();
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);
}
}; };
const showResources = async (row: any) => { const showResources = async (row: any) => {

View File

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

View File

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

View File

@@ -132,7 +132,7 @@
<template #footer> <template #footer>
<div> <div>
<el-button @click="cancel()"> </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> </div>
</template> </template>
</el-dialog> </el-dialog>
@@ -222,10 +222,12 @@ const state = reactive({
link: '', 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) => { watch(props, (newValue: any) => {
state.dialogVisible = newValue.visible; state.dialogVisible = newValue.visible;
@@ -264,20 +266,15 @@ const btnOk = () => {
} else { } else {
submitForm.meta = null as any; 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(); menuForm.value.validate(async (valid: any) => {
}); if (valid) {
} else { state.submitForm = submitForm;
return false; await saveResouceExec();
emit('val-change', submitForm);
ElMessage.success('保存成功');
cancel();
} }
}); });
}; };

View File

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

View File

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

View File

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

View File

@@ -1,16 +1,6 @@
<template> <template>
<div> <div>
<page-table <page-table :query="queryConfig" v-model:query-form="query" :columns="columns" :page-api="logApi.list">
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()"
>
<template #selectAccount> <template #selectAccount>
<el-select <el-select
style="width: 200px" style="width: 200px"
@@ -29,13 +19,26 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { ref, toRefs, reactive, onMounted } from 'vue'; import { toRefs, reactive } from 'vue';
import { logApi, accountApi } from '../api'; import { logApi, accountApi } from '../api';
import PageTable from '@/components/pagetable/PageTable.vue'; import PageTable from '@/components/pagetable/PageTable.vue';
import { TableColumn, TableQuery } from '@/components/pagetable'; import { TableColumn, TableQuery } from '@/components/pagetable';
import { LogTypeEnum } from '../enums'; 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({ const state = reactive({
query: { query: {
@@ -45,40 +48,10 @@ const state = reactive({
pageNum: 1, pageNum: 1,
pageSize: 0, 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, accounts: [] as any,
}); });
const { query, total, logs, accounts } = toRefs(state); const { query, 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 getAccount = (username: any) => { const getAccount = (username: any) => {
accountApi.list.request({ username }).then((res) => { 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() instanceEntity.Network = instanceEntity.GetNetwork()
// 查找是否存在该库 // 查找是否存在该库
oldInstance := &entity.DbInstance{Host: instanceEntity.Host, Port: instanceEntity.Port, Username: instanceEntity.Username} oldInstance := &entity.DbInstance{
if instanceEntity.SshTunnelMachineId > 0 { Host: instanceEntity.Host,
oldInstance.SshTunnelMachineId = instanceEntity.SshTunnelMachineId Port: instanceEntity.Port,
Username: instanceEntity.Username,
SshTunnelMachineId: instanceEntity.SshTunnelMachineId,
} }
err := app.GetBy(oldInstance) 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 { 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} oldMachine := &entity.Machine{
if me.SshTunnelMachineId > 0 { Ip: me.Ip,
oldMachine.SshTunnelMachineId = me.SshTunnelMachineId Port: me.Port,
Username: me.Username,
SshTunnelMachineId: me.SshTunnelMachineId,
} }
err := m.GetBy(oldMachine) err := m.GetBy(oldMachine)
me.PwdEncrypt() 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 { func (r *redisAppImpl) Save(ctx context.Context, re *entity.Redis, tagIds ...uint64) error {
// 查找是否存在该库 // 查找是否存在该库
oldRedis := &entity.Redis{Host: re.Host} oldRedis := &entity.Redis{
if re.SshTunnelMachineId > 0 { Host: re.Host,
oldRedis.SshTunnelMachineId = re.SshTunnelMachineId SshTunnelMachineId: re.SshTunnelMachineId,
} }
err := r.GetBy(oldRedis) 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 }) 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 {
if len(addTagIds) > 0 { addTagResource := make([]*entity.TagResource, 0)
addTagResource := make([]*entity.TagResource, 0) for _, tagId := range addTagIds {
for _, tagId := range addTagIds { tag, err := p.GetById(new(entity.TagTree), tagId)
tag, err := p.GetById(new(entity.TagTree), tagId) if err != nil {
if err != nil { return errorx.NewBiz("存在错误标签id")
return errorx.NewBiz("存在错误标签id")
}
addTagResource = append(addTagResource, &entity.TagResource{
ResourceCode: resourceCode,
ResourceType: resourceType,
TagId: tagId,
TagPath: tag.CodePath,
})
} }
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 return err
} }
} }
}
if len(delTagIds) > 0 { return nil
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
})
} }
func (p *tagTreeAppImpl) ListTagPathByResource(resourceType int8, resourceCode string) []string { func (p *tagTreeAppImpl) ListTagPathByResource(resourceType int8, resourceCode string) []string {