feat: 小功能优化&前端基于setup语法糖重构

This commit is contained in:
meilin.huang
2022-10-29 20:08:15 +08:00
committed by 刘宗洋
parent a6d9a4b5ae
commit 03291594b1
147 changed files with 9089 additions and 9951 deletions

View File

@@ -4,15 +4,12 @@
</div>
</template>
<script lang="ts">
import { ref, toRefs, reactive, nextTick, watch, onMounted, onUnmounted, defineComponent } from 'vue';
<script lang="ts" setup>
import { ref, toRefs, reactive, nextTick, watch, onMounted, onUnmounted } from 'vue';
import JSONEditor from 'jsoneditor';
import 'jsoneditor/dist/jsoneditor.min.css';
export default defineComponent({
name: 'JsonEdit',
components: {},
props: {
const props = defineProps({
modelValue: {
type: [String, Object],
},
@@ -38,37 +35,40 @@ export default defineComponent({
return ['tree', 'code', 'form', 'text', 'view'];
},
},
},
setup(props: any, { emit }) {
let { modelValue, options, modeList, currentMode } = toRefs(props);
})
const jsoneditorVue = ref(null)
// 编辑器实例
let editor = null as any;
// 值类型
let valueType = 'string';
// 是否内部改变(即onChange事件双向绑定)内部改变则不需要重新赋值给editor
let internalChange = false;
//定义事件
const emit = defineEmits(['update:visible', 'update:modelValue', 'onChange'])
const state = reactive({
let { modelValue, options, modeList, currentMode } = toRefs(props);
const jsoneditorVue = ref(null)
// 编辑器实例
let editor = null as any;
// 值类型
let valueType = 'string';
// 是否内部改变(即onChange事件双向绑定)内部改变则不需要重新赋值给editor
let internalChange = false;
const state = reactive({
height: '500px',
width: 'auto',
});
});
onMounted(() => {
onMounted(() => {
state.width = props.width;
state.height = props.height;
init();
setJson(modelValue.value);
});
setJson(modelValue!.value);
});
onUnmounted(() => {
onUnmounted(() => {
editor?.destroy();
editor = null;
});
});
watch(
watch(
() => props.modelValue,
(newValue) => {
if (!editor) {
@@ -76,9 +76,9 @@ export default defineComponent({
}
setJson(newValue);
}
);
);
const setJson = (value: any) => {
const setJson = (value: any) => {
if (internalChange) {
return;
}
@@ -89,9 +89,9 @@ export default defineComponent({
valueType = 'object';
editor.set(value);
}
};
};
const onChange = () => {
const onChange = () => {
try {
const json = editor.get();
if (valueType == 'string') {
@@ -104,10 +104,10 @@ export default defineComponent({
nextTick(() => {
internalChange = false;
});
} catch (error) {}
};
} catch (error) { }
};
const init = () => {
const init = () => {
console.log('init json editor');
const finalOptions = {
...options.value,
@@ -116,14 +116,7 @@ export default defineComponent({
onChange,
};
editor = new JSONEditor(jsoneditorVue.value, finalOptions);
};
return {
...toRefs(state),
jsoneditorVue,
};
},
});
};
</script>
<style lang="scss">

View File

@@ -3,7 +3,7 @@ import RouterParent from '@/views/layout/routerView/parent.vue';
export const imports = {
'RouterParent': RouterParent,
"Home": () => import('@/views/home/index.vue'),
"Home": () => import('@/views/home/Home.vue'),
'Personal': () => import('@/views/personal/index.vue'),
// machine
"MachineList": () => import('@/views/ops/machine'),

View File

@@ -7,13 +7,14 @@
<img :src="getUserInfos.photo" />
<div class="home-card-first-right ml15">
<div class="flex-margin">
<div class="home-card-first-right-title">{{ `${currentTime}, ${getUserInfos.username}` }}</div>
<div class="home-card-first-right-title">{{ `${currentTime}, ${getUserInfos.username}`
}}</div>
</div>
</div>
</div>
</div>
</el-col>
<el-col :sm="3" class="mb15" v-for="(v, k) in topCardItemList" :key="k">
<el-col :sm="3" class="mb15" v-for="(v, k) in topCardItemList as any" :key="k">
<div @click="toPage(v)" class="home-card-item home-card-item-box" :style="{ background: v.color }">
<div class="home-card-item-flex">
<div class="home-card-item-title pb3">{{ v.title }}</div>
@@ -26,7 +27,7 @@
</div>
</template>
<script lang="ts">
<script lang="ts" setup>
import { toRefs, reactive, onMounted, nextTick, computed } from 'vue';
import { useStore } from '@/store/index.ts';
// import * as echarts from 'echarts';
@@ -34,13 +35,10 @@ import { CountUp } from 'countup.js';
import { formatAxis } from '@/common/utils/formatTime.ts';
import { indexApi } from './api';
import { useRouter } from 'vue-router';
export default {
name: 'HomePage',
setup() {
// const { proxy } = getCurrentInstance() as any;
const router = useRouter();
const store = useStore();
const state = reactive({
const router = useRouter();
const store = useStore();
const state = reactive({
topCardItemList: [
{
title: 'Linux机器',
@@ -63,15 +61,19 @@ export default {
color: '#FEBB50',
},
],
});
});
//
const currentTime = computed(() => {
const {
topCardItemList,
} = toRefs(state)
//
const currentTime = computed(() => {
return formatAxis(new Date());
});
});
//
const initNumCountUp = async () => {
//
const initNumCountUp = async () => {
const res: any = await indexApi.getIndexCount.request();
nextTick(() => {
new CountUp('mongoNum', res.mongoNum).start();
@@ -79,9 +81,9 @@ export default {
new CountUp('dbNum', res.dbNum).start();
new CountUp('redisNum', res.redisNum).start();
});
};
};
const toPage = (item: any) => {
const toPage = (item: any) => {
switch (item.id) {
case 'personal': {
router.push('/personal');
@@ -104,33 +106,25 @@ export default {
break;
}
}
};
};
//
onMounted(() => {
//
onMounted(() => {
initNumCountUp();
// initHomeLaboratory();
// initHomeOvertime();
});
});
// vuex
const getUserInfos = computed(() => {
// vuex
const getUserInfos = computed(() => {
return store.state.userInfos.userInfos;
});
return {
getUserInfos,
currentTime,
toPage,
...toRefs(state),
};
},
};
});
</script>
<style scoped lang="scss">
.home-container {
overflow-x: hidden;
.home-card-item {
width: 100%;
height: 103px;
@@ -138,16 +132,19 @@ export default {
border-radius: 4px;
transition: all ease 0.3s;
cursor: pointer;
&:hover {
box-shadow: 0 2px 12px 0 rgb(0 0 0 / 10%);
transition: all ease 0.3s;
}
}
.home-card-item-box {
display: flex;
align-items: center;
position: relative;
overflow: hidden;
&:hover {
i {
right: 0px !important;
@@ -155,6 +152,7 @@ export default {
transition: all ease 0.3s;
}
}
i {
position: absolute;
right: -10px;
@@ -163,48 +161,59 @@ export default {
transform: rotate(-30deg);
transition: all ease 0.3s;
}
.home-card-item-flex {
padding: 0 20px;
color: white;
.home-card-item-title,
.home-card-item-tip {
font-size: 13px;
}
.home-card-item-title-num {
font-size: 18px;
}
.home-card-item-tip-num {
font-size: 13px;
}
}
}
.home-card-first {
background: white;
border: 1px solid #ebeef5;
display: flex;
align-items: center;
img {
width: 60px;
height: 60px;
border-radius: 100%;
border: 2px solid var(--color-primary-light-5);
}
.home-card-first-right {
flex: 1;
display: flex;
flex-direction: column;
.home-card-first-right-msg {
font-size: 13px;
color: gray;
}
}
}
.home-monitor {
height: 200px;
.flex-warp-item {
width: 50%;
height: 100px;
display: flex;
.flex-warp-item-box {
margin: auto;
height: auto;
@@ -212,19 +221,24 @@ export default {
}
}
}
.home-warning-card {
height: 292px;
::v-deep(.el-card) {
height: 100%;
}
}
.home-dynamic {
height: 200px;
.home-dynamic-item {
display: flex;
width: 100%;
height: 60px;
overflow: hidden;
&:first-of-type {
.home-dynamic-item-line {
i {
@@ -232,20 +246,24 @@ export default {
}
}
}
.home-dynamic-item-left {
text-align: right;
.home-dynamic-item-left-time1 {
}
.home-dynamic-item-left-time1 {}
.home-dynamic-item-left-time2 {
font-size: 13px;
color: gray;
}
}
.home-dynamic-item-line {
height: 60px;
border-right: 2px dashed #dfdfdf;
margin: 0 20px;
position: relative;
i {
color: var(--color-primary);
font-size: 12px;
@@ -256,8 +274,10 @@ export default {
background: white;
}
}
.home-dynamic-item-right {
flex: 1;
.home-dynamic-item-right-title {
i {
margin-right: 5px;
@@ -270,6 +290,7 @@ export default {
color: var(--color-primary);
}
}
.home-dynamic-item-right-label {
font-size: 13px;
color: gray;

View File

@@ -2,37 +2,25 @@
<div>
<el-form ref="loginFormRef" :model="loginForm" :rules="rules" class="login-content-form" size="large">
<el-form-item prop="username">
<el-input type="text" placeholder="请输入用户名" prefix-icon="user" v-model="loginForm.username" clearable autocomplete="off">
<el-input type="text" placeholder="请输入用户名" prefix-icon="user" v-model="loginForm.username" clearable
autocomplete="off">
</el-input>
</el-form-item>
<el-form-item prop="password">
<el-input type="password" placeholder="请输入密码" prefix-icon="lock" v-model="loginForm.password" autocomplete="off" show-password>
<el-input type="password" placeholder="请输入密码" prefix-icon="lock" v-model="loginForm.password"
autocomplete="off" show-password>
</el-input>
</el-form-item>
<el-form-item v-if="useLoginCaptcha" prop="captcha">
<el-form-item v-if="isUseLoginCaptcha" prop="captcha">
<el-row :gutter="15">
<el-col :span="16">
<el-input
type="text"
maxlength="6"
placeholder="请输入验证码"
prefix-icon="position"
v-model="loginForm.captcha"
clearable
autocomplete="off"
@keyup.enter="login"
></el-input>
<el-input type="text" maxlength="6" placeholder="请输入验证码" prefix-icon="position"
v-model="loginForm.captcha" clearable autocomplete="off" @keyup.enter="login"></el-input>
</el-col>
<el-col :span="8">
<div class="login-content-code">
<img
class="login-content-code-img"
@click="getCaptcha"
width="130px"
height="40px"
:src="captchaImage"
style="cursor: pointer"
/>
<img class="login-content-code-img" @click="getCaptcha" width="130px" height="40px"
:src="captchaImage" style="cursor: pointer" />
</div>
</el-col>
</el-row>
@@ -44,21 +32,20 @@
</el-form-item>
</el-form>
<el-dialog title="修改密码" v-model="changePwdDialog.visible" :close-on-click-modal="false" width="450px" :destroy-on-close="true">
<el-form :model="changePwdDialog.form" :rules="changePwdDialog.rules" ref="changePwdFormRef" label-width="65px">
<el-dialog title="修改密码" v-model="changePwdDialog.visible" :close-on-click-modal="false" width="450px"
:destroy-on-close="true">
<el-form :model="changePwdDialog.form" :rules="changePwdDialog.rules" ref="changePwdFormRef"
label-width="65px">
<el-form-item prop="username" label="用户名" required>
<el-input v-model.trim="changePwdDialog.form.username" disabled></el-input>
</el-form-item>
<el-form-item prop="oldPassword" label="旧密码" required>
<el-input v-model.trim="changePwdDialog.form.oldPassword" autocomplete="new-password" type="password"></el-input>
<el-input v-model.trim="changePwdDialog.form.oldPassword" autocomplete="new-password"
type="password"></el-input>
</el-form-item>
<el-form-item prop="newPassword" label="新密码" required>
<el-input
v-model.trim="changePwdDialog.form.newPassword"
placeholder="须为8位以上且包含字⺟⼤⼩写+数字+特殊符号"
type="password"
autocomplete="new-password"
></el-input>
<el-input v-model.trim="changePwdDialog.form.newPassword" placeholder="须为8位以上且包含字⺟⼤⼩写+数字+特殊符号"
type="password" autocomplete="new-password"></el-input>
</el-form-item>
</el-form>
@@ -72,8 +59,8 @@
</div>
</template>
<script lang="ts">
import { nextTick, onMounted, ref, toRefs, reactive, defineComponent, computed } from 'vue';
<script lang="ts" setup>
import { nextTick, onMounted, ref, toRefs, reactive, computed } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { ElMessage } from 'element-plus';
import { initBackEndControlRoutesFun } from '@/router/index.ts';
@@ -85,17 +72,20 @@ import { RsaEncrypt } from '@/common/rsa';
import { useLoginCaptcha, useWartermark } from '@/common/sysconfig';
import { letterAvatar } from '@/common/utils/string';
export default defineComponent({
name: 'AccountLogin',
setup() {
const store = useStore();
const route = useRoute();
const router = useRouter();
const loginFormRef: any = ref(null);
const changePwdFormRef: any = ref(null);
const rules = {
username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
captcha: [{ required: true, message: '请输入验证码', trigger: 'blur' }],
}
const state = reactive({
useLoginCaptcha: false,
const store = useStore();
const route = useRoute();
const router = useRouter();
const loginFormRef: any = ref(null);
const changePwdFormRef: any = ref(null);
const state = reactive({
isUseLoginCaptcha: false,
captchaImage: '',
loginForm: {
username: '',
@@ -121,42 +111,45 @@ export default defineComponent({
],
},
},
rules: {
username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
captcha: [{ required: true, message: '请输入验证码', trigger: 'blur' }],
},
loading: {
signIn: false,
changePwd: false,
},
});
});
onMounted(async () => {
const {
isUseLoginCaptcha,
captchaImage,
loginForm,
changePwdDialog,
loading,
} = toRefs(state)
onMounted(async () => {
nextTick(async () => {
state.useLoginCaptcha = await useLoginCaptcha();
state.isUseLoginCaptcha = await useLoginCaptcha();
getCaptcha();
});
// 移除公钥, 方便后续重新获取
sessionStorage.removeItem('RsaPublicKey');
});
});
const getCaptcha = async () => {
if (!state.useLoginCaptcha) {
const getCaptcha = async () => {
if (!state.isUseLoginCaptcha) {
return;
}
let res: any = await openApi.captcha();
state.captchaImage = res.base64Captcha;
state.loginForm.cid = res.cid;
};
};
// 时间获取
const currentTime = computed(() => {
// 时间获取
const currentTime = computed(() => {
return formatAxis(new Date());
});
});
// 校验登录表单并登录
const login = () => {
// 校验登录表单并登录
const login = () => {
loginFormRef.value.validate((valid: boolean) => {
if (valid) {
onSignIn();
@@ -164,10 +157,10 @@ export default defineComponent({
return false;
}
});
};
};
// 登录
const onSignIn = async () => {
// 登录
const onSignIn = async () => {
state.loading.signIn = true;
let loginRes;
const originPwd = state.loginForm.password;
@@ -221,10 +214,10 @@ export default defineComponent({
// 执行完 initBackEndControlRoutesFun再执行 signInSuccess
signInSuccess();
}
};
};
// 登录成功后的跳转
const signInSuccess = () => {
// 登录成功后的跳转
const signInSuccess = () => {
// 初始化登录成功时间问候语
let currentTimeInfo = currentTime.value;
// 登录成功,跳到转首页
@@ -240,9 +233,9 @@ export default defineComponent({
setUseWatermark2Session(true);
}
}, 300);
};
};
const changePwd = () => {
const changePwd = () => {
changePwdFormRef.value.validate(async (valid: boolean) => {
if (!valid) {
return false;
@@ -262,37 +255,26 @@ export default defineComponent({
state.loading.changePwd = false;
}
});
};
};
const cancelChangePwd = () => {
const cancelChangePwd = () => {
state.changePwdDialog.visible = false;
state.changePwdDialog.form.newPassword = '';
state.changePwdDialog.form.oldPassword = '';
state.changePwdDialog.form.username = '';
getCaptcha();
};
return {
getCaptcha,
currentTime,
loginFormRef,
changePwdFormRef,
login,
changePwd,
cancelChangePwd,
...toRefs(state),
};
},
});
};
</script>
<style scoped lang="scss">
.login-content-form {
margin-top: 20px;
.login-content-code {
display: flex;
align-items: center;
justify-content: space-around;
.login-content-code-img {
width: 100%;
height: 40px;
@@ -309,12 +291,14 @@ export default defineComponent({
transition: all ease 0.2s;
border-radius: 4px;
user-select: none;
&:hover {
border-color: #c0c4cc;
transition: all ease 0.2s;
}
}
}
.login-content-submit {
width: 100%;
letter-spacing: 2px;

View File

@@ -31,33 +31,30 @@
</div>
</template>
<script lang="ts">
<script lang="ts" setup>
import { toRefs, reactive, computed } from 'vue';
import Account from '@/views/login/component/AccountLogin.vue';
import { useStore } from '@/store/index.ts';
export default {
name: 'LoginPage',
components: { Account },
setup() {
const store = useStore();
const state = reactive({
const store = useStore();
const state = reactive({
tabsActiveName: 'account',
isTabPaneShow: true,
});
// 获取布局配置信息
const getThemeConfig = computed(() => {
});
const {
isTabPaneShow,
tabsActiveName,
} = toRefs(state)
// 获取布局配置信息
const getThemeConfig = computed(() => {
return store.state.themeConfig.themeConfig;
});
// 切换密码、手机登录
const onTabsClick = () => {
});
// 切换密码、手机登录
const onTabsClick = () => {
state.isTabPaneShow = !state.isTabPaneShow;
};
return {
onTabsClick,
getThemeConfig,
...toRefs(state),
};
},
};
</script>
@@ -67,6 +64,7 @@ export default {
height: 100%;
background: url('@/assets/image/bg-login.png') no-repeat;
background-size: 100% 100%;
.login-logo {
position: absolute;
top: 30px;
@@ -80,6 +78,7 @@ export default {
width: 90%;
transform: translateX(-50%);
}
.login-content {
width: 500px;
padding: 20px;
@@ -94,9 +93,11 @@ export default {
height: 480px;
overflow: hidden;
z-index: 1;
.login-content-main {
margin: 0 auto;
width: 80%;
.login-content-title {
color: #333;
font-weight: 500;
@@ -108,9 +109,11 @@ export default {
}
}
}
.login-content-mobile {
height: 418px;
}
.login-copyright {
position: absolute;
left: 50%;
@@ -120,9 +123,11 @@ export default {
color: white;
font-size: 12px;
opacity: 0.8;
.login-copyright-company {
white-space: nowrap;
}
.login-copyright-msg {
@extend .login-copyright-company;
}

View File

@@ -1,100 +0,0 @@
<template>
<div>
<el-form class="search-form" label-position="right" :inline="true">
<el-form-item prop="project" label="项目" label-width="40px">
<el-select v-model="projectId" placeholder="请选择项目" @change="changeProject" filterable>
<el-option v-for="item in projects" :key="item.id" :label="`${item.name} [${item.remark}]`" :value="item.id"></el-option>
</el-select>
</el-form-item>
<el-form-item prop="env" label="env" label-width="33px">
<el-select style="width: 85px" v-model="envId" placeholder="环境" @change="changeEnv" filterable>
<el-option v-for="item in envs" :key="item.id" :label="item.name" :value="item.id">
<span style="float: left">{{ item.name }}</span>
<span style="float: right; color: #8492a6; font-size: 13px">{{ item.remark }}</span>
</el-option>
</el-select>
</el-form-item>
<slot></slot>
</el-form>
</div>
</template>
<script lang="ts">
import {toRefs, reactive, defineComponent, onMounted, watch} from 'vue';
import { projectApi } from '../project/api';
export default defineComponent({
name: 'ProjectEnvSelect',
props: {
visible: {
type: Boolean,
},
data: {
type: Object,
},
title: {
type: String,
},
machineId: {
type: Number,
},
isCommon: {
type: Boolean,
},
},
setup(props: any, { emit }) {
const state = reactive({
projects: [] as any,
envs: [] as any,
projectId: null,
envId: null,
});
// 动态选中项目和环境
const setData = async (projectId: null, envId: null) => {
if (projectId) {
state.projectId = projectId;
if (envId) {
state.envs = await projectApi.projectEnvs.request({projectId});
state.envId = envId;
}
}
}
watch(() => props.data, (newValue)=>{
setData(newValue.projectId, newValue.envId)
})
onMounted(async () => {
state.projects = await projectApi.accountProjects.request(null);
// 初始化容器时可能会选中项目和环境
if(props.data?.projectId && props.data?.envId){
await setData(props.data.projectId, props.data.envId)
}
});
const changeProject = async (projectId: any) => {
emit('update:projectId', projectId);
emit('changeProjectEnv', state.projectId, null);
state.envId = null;
state.envs = await projectApi.projectEnvs.request({ projectId });
};
const changeEnv = (envId: any) => {
emit('update:envId', envId);
emit('changeProjectEnv', state.projectId, envId);
};
return {
...toRefs(state),
changeProject,
changeEnv,
setData,
};
},
});
</script>
<style lang="scss">
</style>

View File

@@ -1,21 +1,12 @@
<template>
<div>
<el-tree-select
@check="changeTag"
style="width: 100%"
v-model="selectTags"
:data="tags"
:render-after-expand="true"
:default-expanded-keys="[selectTags]"
show-checkbox
check-strictly
node-key="id"
<el-tree-select @check="changeTag" style="width: 100%" v-model="selectTags" :data="tags"
:render-after-expand="true" :default-expanded-keys="[selectTags]" show-checkbox check-strictly node-key="id"
:props="{
value: 'id',
label: 'codePath',
children: 'children',
}"
>
}">
<template #default="{ data }">
<span class="custom-tree-node">
<span style="font-size: 13px">
@@ -31,35 +22,41 @@
</div>
</template>
<script lang="ts">
import { toRefs, reactive, defineComponent, onMounted } from 'vue';
<script lang="ts" setup>
import { toRefs, reactive, onMounted } from 'vue';
import { tagApi } from '../tag/api';
export default defineComponent({
name: 'TagSelect',
props: {
const props = defineProps({
tagId: {
type: Number,
},
tagPath: {
type: String,
},
},
setup(props: any, { emit }) {
const state = reactive({
})
//定义事件
const emit = defineEmits(['changeTag', 'update:tagId', 'update:tagPath'])
const state = reactive({
tags: [],
// 单选则为id多选为id数组
selectTags: null as any,
});
});
onMounted(async () => {
const {
tags,
selectTags,
} = toRefs(state)
onMounted(async () => {
if (props.tagId) {
state.selectTags = props.tagId;
}
state.tags = await tagApi.getTagTrees.request(null);
});
});
const changeTag = (tag: any, checkInfo: any) => {
const changeTag = (tag: any, checkInfo: any) => {
if (checkInfo.checkedNodes.length > 0) {
emit('update:tagId', tag.id);
emit('update:tagPath', tag.codePath);
@@ -68,14 +65,8 @@ export default defineComponent({
emit('update:tagId', null);
emit('update:tagPath', null);
}
};
return {
...toRefs(state),
changeTag,
};
},
});
};
</script>
<style lang="scss">
</style>

View File

@@ -16,14 +16,18 @@
<el-col :span="12">
<el-form-item prop="characterSet" label="charset">
<el-select filterable style="width: 80%" v-model="tableData.characterSet" size="small">
<el-option v-for="item in characterSetNameList" :key="item" :label="item" :value="item"> </el-option>
<el-option v-for="item in characterSetNameList" :key="item" :label="item" :value="item">
</el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item prop="characterSet" label="collation">
<el-select filterable style="width: 80%" v-model="tableData.collation" size="small">
<el-option v-for="item in collationNameList" :key="item" :label="tableData.characterSet+'_'+item" :value="tableData.characterSet+'_'+item"> </el-option>
<el-option v-for="item in collationNameList" :key="item"
:label="tableData.characterSet + '_' + item"
:value="tableData.characterSet + '_' + item">
</el-option>
</el-select>
</el-form-item>
</el-col>
@@ -32,27 +36,38 @@
<el-tabs v-model="activeName">
<el-tab-pane label="字段" name="1">
<el-table :data="tableData.fields.res" :max-height="tableData.height">
<el-table-column :prop="item.prop" :label="item.label" v-for="item in tableData.fields.colNames" :key="item.prop">
<el-table-column :prop="item.prop" :label="item.label"
v-for="item in tableData.fields.colNames" :key="item.prop">
<template #default="scope">
<el-input v-if="item.prop === 'name'" size="small" v-model="scope.row.name"></el-input>
<el-input v-if="item.prop === 'name'" size="small" v-model="scope.row.name">
</el-input>
<el-select v-if="item.prop === 'type'" filterable size="small" v-model="scope.row.type">
<el-option v-for="typeValue in columnTypeList" :key="typeValue" :value="typeValue">{{ typeValue }}</el-option>
<el-select v-if="item.prop === 'type'" filterable size="small"
v-model="scope.row.type">
<el-option v-for="typeValue in columnTypeList" :key="typeValue"
:value="typeValue">{{ typeValue }}</el-option>
</el-select>
<el-input v-if="item.prop === 'value'" size="small" v-model="scope.row.value"> </el-input>
<el-input v-if="item.prop === 'value'" size="small" v-model="scope.row.value">
</el-input>
<el-input v-if="item.prop === 'length'" size="small" v-model="scope.row.length"> </el-input>
<el-input v-if="item.prop === 'length'" size="small" v-model="scope.row.length">
</el-input>
<el-checkbox v-if="item.prop === 'notNull'" size="small" v-model="scope.row.notNull"> </el-checkbox>
<el-checkbox v-if="item.prop === 'notNull'" size="small"
v-model="scope.row.notNull"> </el-checkbox>
<el-checkbox v-if="item.prop === 'pri'" size="small" v-model="scope.row.pri"> </el-checkbox>
<el-checkbox v-if="item.prop === 'pri'" size="small" v-model="scope.row.pri">
</el-checkbox>
<el-checkbox v-if="item.prop === 'auto_increment'" size="small" v-model="scope.row.auto_increment"> </el-checkbox>
<el-checkbox v-if="item.prop === 'auto_increment'" size="small"
v-model="scope.row.auto_increment"> </el-checkbox>
<el-input v-if="item.prop === 'remark'" size="small" v-model="scope.row.remark"> </el-input>
<el-input v-if="item.prop === 'remark'" size="small" v-model="scope.row.remark">
</el-input>
<el-link v-if="item.prop === 'action'" type="danger" plain size="small" :underline="false" @click.prevent="deleteRow(scope.$index)">删除</el-link>
<el-link v-if="item.prop === 'action'" type="danger" plain size="small"
:underline="false" @click.prevent="deleteRow(scope.$index)">删除</el-link>
</template>
</el-table-column>
</el-table>
@@ -63,35 +78,36 @@
</el-tab-pane>
<el-tab-pane label="索引" name="2">
<el-table :data="tableData.indexs.res" :max-height="tableData.height">
<el-table-column :prop="item.prop" :label="item.label" v-for="item in tableData.indexs.colNames" :key="item.prop">
<el-table-column :prop="item.prop" :label="item.label"
v-for="item in tableData.indexs.colNames" :key="item.prop">
<template #default="scope">
<el-input v-if="item.prop === 'indexName'" size="small" v-model="scope.row.indexName"></el-input>
<el-input v-if="item.prop === 'indexName'" size="small"
v-model="scope.row.indexName"></el-input>
<el-select
v-if="item.prop === 'columnNames'"
v-model="scope.row.columnNames"
multiple
collapse-tags
collapse-tags-tooltip
filterable
placeholder="请选择字段"
style="width: 100%"
>
<el-option v-for="cl in tableData.indexs.columns" :key="cl.name" :label="cl.name" :value="cl.name" >
<el-select v-if="item.prop === 'columnNames'" v-model="scope.row.columnNames"
multiple collapse-tags collapse-tags-tooltip filterable placeholder="请选择字段"
style="width: 100%">
<el-option v-for="cl in tableData.indexs.columns" :key="cl.name"
:label="cl.name" :value="cl.name">
{{ cl.name + ' - ' + (cl.remark || '') }}
</el-option>
</el-select>
<el-checkbox v-if="item.prop === 'unique'" size="small" v-model="scope.row.unique"> </el-checkbox>
<el-checkbox v-if="item.prop === 'unique'" size="small" v-model="scope.row.unique">
</el-checkbox>
<el-select v-if="item.prop === 'indexType'" filterable size="small" v-model="scope.row.indexType">
<el-option v-for="typeValue in indexTypeList" :key="typeValue" :value="typeValue">{{ typeValue }}</el-option>
<el-select v-if="item.prop === 'indexType'" filterable size="small"
v-model="scope.row.indexType">
<el-option v-for="typeValue in indexTypeList" :key="typeValue"
:value="typeValue">{{ typeValue }}</el-option>
</el-select>
<el-input v-if="item.prop === 'indexComment'" size="small" v-model="scope.row.indexComment"> </el-input>
<el-input v-if="item.prop === 'indexComment'" size="small"
v-model="scope.row.indexComment"> </el-input>
<el-link v-if="item.prop === 'action'" type="danger" plain size="small" :underline="false" @click.prevent="deleteIndex(scope.$index)">删除</el-link>
<el-link v-if="item.prop === 'action'" type="danger" plain size="small"
:underline="false" @click.prevent="deleteIndex(scope.$index)">删除</el-link>
</template>
</el-table-column>
</el-table>
@@ -111,15 +127,13 @@
</template>
<script lang="ts">
import { watch, toRefs, reactive, defineComponent, ref, getCurrentInstance } from 'vue';
import {TYPE_LIST, CHARACTER_SET_NAME_LIST, COLLATION_SUFFIX_LIST} from './service.ts';
<script lang="ts" setup>
import { watch, toRefs, reactive, ref, getCurrentInstance } from 'vue';
import { TYPE_LIST, CHARACTER_SET_NAME_LIST, COLLATION_SUFFIX_LIST } from './service.ts';
import { ElMessage } from 'element-plus';
import SqlExecBox from './component/SqlExecBox.ts';
export default defineComponent({
name: 'createTable',
props: {
const props = defineProps({
visible: {
type: Boolean,
},
@@ -135,12 +149,15 @@ export default defineComponent({
db: {
type: String,
}
},
setup(props: any, { emit }) {
const formRef: any = ref();
const { proxy } = getCurrentInstance() as any;
})
const state = reactive({
//定义事件
const emit = defineEmits(['update:visible', 'cancel', 'val-change'])
const formRef: any = ref();
const { proxy } = getCurrentInstance() as any;
const state = reactive({
dialogVisible: false,
btnloading: false,
activeName: '1',
@@ -229,7 +246,7 @@ export default defineComponent({
label: '操作',
},
],
columns: [{name: '', remark:''}],
columns: [{ name: '', remark: '' }],
res: [
{
indexName: '',
@@ -246,16 +263,29 @@ export default defineComponent({
tableComment: '',
height: 550
},
});
});
watch(props, async (newValue) => {
const {
dialogVisible,
btnloading,
activeName,
columnTypeList,
indexTypeList,
characterSetNameList,
collationNameList,
tableData,
} = toRefs(state)
watch(props, async (newValue) => {
state.dialogVisible = newValue.visible;
});
const cancel = () => {
});
const cancel = () => {
emit('update:visible', false);
reset();
};
const addRow = () => {
};
const addRow = () => {
state.tableData.fields.res.push({
name: '',
type: '',
@@ -266,8 +296,9 @@ export default defineComponent({
auto_increment: false,
remark: '',
});
};
const addIndex = () => {
};
const addIndex = () => {
state.tableData.indexs.res.push({
indexName: '',
columnNames: [],
@@ -275,27 +306,31 @@ export default defineComponent({
indexType: 'BTREE',
indexComment: '',
});
};
const addDefaultRows = () => {
};
const addDefaultRows = () => {
state.tableData.fields.res.push(
{name: 'id', type: 'bigint', length: '20', value: '', notNull: true, pri: true, auto_increment: true, remark: '主键ID'},
{name: 'creator_id', type: 'bigint', length: '20', value: '', notNull: true, pri: false, auto_increment: false, remark: '创建人id'},
{name: 'creator', type: 'varchar', length: '100', value: '', notNull: true, pri: false, auto_increment: false, remark: '创建人姓名'},
{name: 'creat_time', type: 'datetime', length: '', value: '', notNull: true, pri: false, auto_increment: false, remark: '创建时间'},
{name: 'updater_id', type: 'bigint', length: '20', value: '', notNull: true, pri: false, auto_increment: false, remark: '修改人id'},
{name: 'updater', type: 'varchar', length: '100', value: '', notNull: true, pri: false, auto_increment: false, remark: '修改人姓名'},
{name: 'update_time', type: 'datetime', length: '', value: '', notNull: true, pri: false, auto_increment: false, remark: '修改时间'},
{ name: 'id', type: 'bigint', length: '20', value: '', notNull: true, pri: true, auto_increment: true, remark: '主键ID' },
{ name: 'creator_id', type: 'bigint', length: '20', value: '', notNull: true, pri: false, auto_increment: false, remark: '创建人id' },
{ name: 'creator', type: 'varchar', length: '100', value: '', notNull: true, pri: false, auto_increment: false, remark: '创建人姓名' },
{ name: 'creat_time', type: 'datetime', length: '', value: '', notNull: true, pri: false, auto_increment: false, remark: '创建时间' },
{ name: 'updater_id', type: 'bigint', length: '20', value: '', notNull: true, pri: false, auto_increment: false, remark: '修改人id' },
{ name: 'updater', type: 'varchar', length: '100', value: '', notNull: true, pri: false, auto_increment: false, remark: '修改人姓名' },
{ name: 'update_time', type: 'datetime', length: '', value: '', notNull: true, pri: false, auto_increment: false, remark: '修改时间' },
);
};
const deleteRow = (index: any) => {
};
const deleteRow = (index: any) => {
state.tableData.fields.res.splice(index, 1);
};
const deleteIndex = (index: any) => {
};
const deleteIndex = (index: any) => {
state.tableData.indexs.res.splice(index, 1);
};
const submit = async () => {
};
const submit = async () => {
let sql = genSql();
if(!sql){
if (!sql) {
ElMessage.warning('没有更改');
return;
}
@@ -309,56 +344,56 @@ export default defineComponent({
// cancel();
},
});
};
};
/**
/**
* 对比两个数组,取出被修改过的对象数组
* @param oldArr 原对象数组
* @param nowArr 修改后的对象数组
* @param key 标志对象唯一属性
*/
const filterChangedData = (oldArr: object[], nowArr: object[], key: string) : { del: any[], add: any[], upd: any[] } => {
const filterChangedData = (oldArr: object[], nowArr: object[], key: string): { del: any[], add: any[], upd: any[] } => {
let data = {
del: [] as object[], // 删除的数据
add: [] as object[], // 新增的数据
upd:[] as object[] // 修改的数据
upd: [] as object[] // 修改的数据
}
// 旧数据为空
if(oldArr && Array.isArray(oldArr) && oldArr.length===0
&& nowArr && Array.isArray(nowArr) && nowArr.length>0 ){
if (oldArr && Array.isArray(oldArr) && oldArr.length === 0
&& nowArr && Array.isArray(nowArr) && nowArr.length > 0) {
data.add = nowArr;
return data;
}
// 新数据为空
if(nowArr && Array.isArray(nowArr) && nowArr.length===0
&& oldArr && Array.isArray(oldArr) && oldArr.length>0 ){
if (nowArr && Array.isArray(nowArr) && nowArr.length === 0
&& oldArr && Array.isArray(oldArr) && oldArr.length > 0) {
data.del = oldArr;
return data;
}
let oldMap= {}, newMap = {};
let oldMap = {}, newMap = {};
oldArr.forEach(a => oldMap[a[key]] = a)
nowArr.forEach(a => {
let k = a[key]
newMap[k] = a;
if(!oldMap.hasOwnProperty(k)){// 新增
if (!oldMap.hasOwnProperty(k)) {// 新增
data.add.push(a)
}
})
oldArr.forEach(a=>{
oldArr.forEach(a => {
let k = a[key];
let newData = newMap[k];
if(!newData){ // 删除
if (!newData) { // 删除
data.del.push(a)
}else{ // 判断每个字段是否相等,否则为修改
for(let f in a){
} else { // 判断每个字段是否相等,否则为修改
for (let f in a) {
let oldV = a[f]
let newV = newData[f]
if(oldV.toString() !== newV.toString()){
if (oldV.toString() !== newV.toString()) {
data.upd.push(newData)
break;
}
@@ -366,21 +401,21 @@ export default defineComponent({
}
})
return data;
}
}
const genSql = () => {
const genSql = () => {
const genColumnBasicSql = (cl: any) => {
let val = cl.value ? (cl.value === 'CURRENT_TIMESTAMP' ? cl.value : '\'' + cl.value + '\'') : '';
let defVal = `${val ? ('DEFAULT ' + val) :''}`;
let length = cl.length?`(${cl.length})`:'';
return ` ${cl.name} ${cl.type}${length} ${cl.notNull?'NOT NULL':'NULL'} ${cl.auto_increment?'AUTO_INCREMENT':''} ${defVal} comment '${cl.remark||''}' `
let defVal = `${val ? ('DEFAULT ' + val) : ''}`;
let length = cl.length ? `(${cl.length})` : '';
return ` ${cl.name} ${cl.type}${length} ${cl.notNull ? 'NOT NULL' : 'NULL'} ${cl.auto_increment ? 'AUTO_INCREMENT' : ''} ${defVal} comment '${cl.remark || ''}' `
}
let data = state.tableData;
// 创建表
if(!props.data.edit){
if(state.activeName === '1'){// 创建表结构
if (!props.data?.edit) {
if (state.activeName === '1') {// 创建表结构
let primary_key = '';
let fields: string[] = [];
data.fields.res.forEach((item) => {
@@ -395,43 +430,43 @@ export default defineComponent({
${primary_key ? `, PRIMARY KEY (${primary_key.slice(0, -1)})` : ''}
) ENGINE=InnoDB DEFAULT CHARSET=${data.characterSet} COLLATE =${data.collation} COMMENT='${data.tableComment}';`;
} else if (state.activeName === '2' && data.indexs.res.length > 0){ // 创建索引
} else if (state.activeName === '2' && data.indexs.res.length > 0) { // 创建索引
let sql = `ALTER TABLE ${data.tableName}`;
state.tableData.indexs.res.forEach(a=>{
sql += ` ADD ${a.unique?'UNIQUE':''} INDEX ${a.indexName}(${a.columnNames.join(',')}) USING ${a.indexType} COMMENT '${a.indexComment}',`;
state.tableData.indexs.res.forEach(a => {
sql += ` ADD ${a.unique ? 'UNIQUE' : ''} INDEX ${a.indexName}(${a.columnNames.join(',')}) USING ${a.indexType} COMMENT '${a.indexComment}',`;
})
return sql.substring(0, sql.length - 1) + ';'
}
} else { // 修改
let addSql = '', updSql = '', delSql = '';
if(state.activeName === '1'){// 修改列
if (state.activeName === '1') {// 修改列
let changeData = filterChangedData(oldData.fields, state.tableData.fields.res, 'name')
if(changeData.add.length > 0){
if (changeData.add.length > 0) {
addSql = `ALTER TABLE ${data.tableName}`
changeData.add.forEach(a=>{
changeData.add.forEach(a => {
addSql += ` ADD ${genColumnBasicSql(a)},`
})
addSql = addSql.substring(0, addSql.length - 1)
addSql +=';'
addSql += ';'
}
if(changeData.upd.length > 0){
if (changeData.upd.length > 0) {
updSql = `ALTER TABLE ${data.tableName}`;
changeData.upd.forEach(a=>{
changeData.upd.forEach(a => {
updSql += ` MODIFY ${genColumnBasicSql(a)},`
})
updSql = updSql.substring(0, updSql.length - 1)
updSql +=';'
updSql += ';'
}
if(changeData.del.length > 0){
changeData.del.forEach(a=>{
if (changeData.del.length > 0) {
changeData.del.forEach(a => {
delSql += ` ALTER TABLE ${data.tableName} DROP COLUMN ${a.name}; `
})
}
return addSql + updSql + delSql;
} else if (state.activeName === '2'){ // 修改索引
} else if (state.activeName === '2') { // 修改索引
let changeData = filterChangedData(oldData.indexs, state.tableData.indexs.res, 'indexName')
// 搜集修改和删除的索引添加到drop index xx
// 收集新增和修改的索引添加到ADD xx
@@ -444,29 +479,29 @@ export default defineComponent({
let dropIndexNames: string[] = [];
let addIndexs: any[] = [];
if(changeData.upd.length > 0){
changeData.upd.forEach(a=>{
if (changeData.upd.length > 0) {
changeData.upd.forEach(a => {
dropIndexNames.push(a.indexName)
addIndexs.push(a)
})
}
if(changeData.del.length > 0){
changeData.del.forEach(a=>{
if (changeData.del.length > 0) {
changeData.del.forEach(a => {
dropIndexNames.push(a.indexName)
})
}
if(changeData.add.length > 0){
changeData.add.forEach(a=>{
if (changeData.add.length > 0) {
changeData.add.forEach(a => {
addIndexs.push(a)
})
}
if(dropIndexNames.length > 0 || addIndexs.length > 0){
if (dropIndexNames.length > 0 || addIndexs.length > 0) {
let sql = `ALTER TABLE ${data.tableName} `;
if(dropIndexNames.length > 0){
dropIndexNames.forEach(a=>{
if (dropIndexNames.length > 0) {
dropIndexNames.forEach(a => {
sql += `DROP INDEX ${a},`
})
sql = sql.substring(0, sql.length - 1)
@@ -485,9 +520,9 @@ export default defineComponent({
}
}
}
};
};
const reset = () => {
const reset = () => {
state.activeName = '1'
formRef.value.resetFields()
state.tableData.tableName = ''
@@ -511,24 +546,24 @@ export default defineComponent({
indexType: 'BTREE',
indexComment: '',
},]
};
};
const oldData = {indexs: [] as any[], fields: [] as any[]}
watch(()=>props.data, (newValue)=>{
const {row, indexs, columns} = newValue;
const oldData = { indexs: [] as any[], fields: [] as any[] }
watch(() => props.data, (newValue: any) => {
const { row, indexs, columns } = newValue;
// 回显表名表注释
state.tableData.tableName = row.tableName
state.tableData.tableComment = row.tableComment
// 回显列
if(columns && Array.isArray(columns) && columns.length > 0){
if (columns && Array.isArray(columns) && columns.length > 0) {
oldData.fields = [];
state.tableData.fields.res = [];
// 索引列下拉选
state.tableData.indexs.columns = [];
columns.forEach(a=>{
let typeObj = a.columnType.replace(')','').split('(')
columns.forEach(a => {
let typeObj = a.columnType.replace(')', '').split('(')
let type = typeObj[0];
let length = typeObj.length > 1&& typeObj[1] || '';
let length = typeObj.length > 1 && typeObj[1] || '';
let data = {
name: a.columnName,
type,
@@ -542,15 +577,15 @@ export default defineComponent({
state.tableData.fields.res.push(data)
oldData.fields.push(JSON.parse(JSON.stringify(data)))
// 索引字段下拉选项
state.tableData.indexs.columns.push({name: a.columnName, remark: a.columnComment})
state.tableData.indexs.columns.push({ name: a.columnName, remark: a.columnComment })
})
}
// 回显索引
if(indexs && Array.isArray(indexs) && indexs.length > 0){
if (indexs && Array.isArray(indexs) && indexs.length > 0) {
oldData.indexs = [];
state.tableData.indexs.res = [];
// 索引过滤掉主键
indexs.filter(a=>a.indexName!=="PRIMARY").forEach(a=>{
indexs.filter(a => a.indexName !== "PRIMARY").forEach(a => {
let data = {
indexName: a.indexName,
columnNames: a.columnName?.split(','),
@@ -562,21 +597,6 @@ export default defineComponent({
oldData.indexs.push(JSON.parse(JSON.stringify(data)))
})
}
})
return {
...toRefs(state),
formRef,
cancel,
reset,
addDefaultRows,
addRow,
deleteRow,
addIndex,
deleteIndex,
submit,
};
},
});
})
</script>

View File

@@ -1,6 +1,7 @@
<template>
<div>
<el-dialog :title="title" v-model="dialogVisible" :before-close="cancel" :close-on-click-modal="false" :destroy-on-close="true" width="38%">
<el-dialog :title="title" v-model="dialogVisible" :before-close="cancel" :close-on-click-modal="false"
:destroy-on-close="true" width="38%">
<el-form :model="form" ref="dbForm" :rules="rules" label-width="95px">
<el-form-item prop="tagId" label="标签:" required>
<tag-select v-model:tag-id="form.tagId" v-model:tag-path="form.tagPath" style="width: 100%" />
@@ -17,7 +18,8 @@
</el-form-item>
<el-form-item prop="host" label="host:" required>
<el-col :span="18">
<el-input :disabled="form.id!==undefined" v-model.trim="form.host" placeholder="请输入主机ip" auto-complete="off"></el-input>
<el-input :disabled="form.id !== undefined" v-model.trim="form.host" placeholder="请输入主机ip"
auto-complete="off"></el-input>
</el-col>
<el-col style="text-align: center" :span="1">:</el-col>
<el-col :span="5">
@@ -28,17 +30,14 @@
<el-input v-model.trim="form.username" placeholder="请输入用户名"></el-input>
</el-form-item>
<el-form-item prop="password" label="密码:">
<el-input
type="password"
show-password
v-model.trim="form.password"
placeholder="请输入密码,修改操作可不填"
autocomplete="new-password"
>
<el-input type="password" show-password v-model.trim="form.password" placeholder="请输入密码,修改操作可不填"
autocomplete="new-password">
<template v-if="form.id && form.id != 0" #suffix>
<el-popover @hide="pwd = ''" placement="right" title="原密码" :width="200" trigger="click" :content="pwd">
<el-popover @hide="pwd = ''" placement="right" title="原密码" :width="200" trigger="click"
:content="pwd">
<template #reference>
<el-link @click="getDbPwd" :underline="false" type="primary" class="mr5">原密码</el-link>
<el-link @click="getDbPwd" :underline="false" type="primary" class="mr5">原密码
</el-link>
</template>
</el-popover>
</template>
@@ -47,28 +46,22 @@
<el-form-item prop="params" label="连接参数:">
<el-input v-model.trim="form.params" placeholder="其他连接参数,形如: key1=value1&key2=value2">
<template v-if="form.id && form.id != 0" #suffix>
<el-link target="_blank" href="https://github.com/go-sql-driver/mysql#dsn-data-source-name" :underline="false" type="primary" class="mr5">参数参考</el-link>
<el-link target="_blank" href="https://github.com/go-sql-driver/mysql#dsn-data-source-name"
:underline="false" type="primary" class="mr5">参数参考</el-link>
</template>
</el-input>
</el-form-item>
<el-form-item prop="database" label="数据库名:" required>
<el-col :span="19">
<el-select
@change="changeDatabase"
v-model="databaseList"
multiple
clearable
collapse-tags
collapse-tags-tooltip
filterable
allow-create
placeholder="请确保数据库实例信息填写完整后获取库名"
style="width: 100%"
>
<el-select @change="changeDatabase" v-model="databaseList" multiple clearable collapse-tags
collapse-tags-tooltip filterable allow-create placeholder="请确保数据库实例信息填写完整后获取库名"
style="width: 100%">
<el-option v-for="db in allDatabases" :key="db" :label="db" :value="db" />
</el-select>
</el-col>
<el-col style="text-align: center" :span="1"><el-divider direction="vertical" border-style="dashed" /></el-col>
<el-col style="text-align: center" :span="1">
<el-divider direction="vertical" border-style="dashed" />
</el-col>
<el-col :span="4">
<el-link @click="getAllDatabase" :underline="false" type="success">获取库名</el-link>
</el-col>
@@ -80,17 +73,14 @@
<el-form-item prop="enableSshTunnel" label="SSH隧道:">
<el-col :span="3">
<el-checkbox @change="getSshTunnelMachines" v-model="form.enableSshTunnel" :true-label="1" :false-label="-1"></el-checkbox>
<el-checkbox @change="getSshTunnelMachines" v-model="form.enableSshTunnel" :true-label="1"
:false-label="-1"></el-checkbox>
</el-col>
<el-col :span="5" v-if="form.enableSshTunnel == 1"> 机器: </el-col>
<el-col :span="16" v-if="form.enableSshTunnel == 1">
<el-select style="width: 100%" v-model="form.sshTunnelMachineId" placeholder="请选择SSH隧道机器">
<el-option
v-for="item in sshTunnelMachineList"
:key="item.id"
:label="`${item.ip}:${item.port} [${item.name}]`"
:value="item.id"
>
<el-option v-for="item in sshTunnelMachineList" :key="item.id"
:label="`${item.ip}:${item.port} [${item.name}]`" :value="item.id">
</el-option>
</el-select>
</el-col>
@@ -107,8 +97,8 @@
</div>
</template>
<script lang="ts">
import { toRefs, reactive, watch, defineComponent, ref } from 'vue';
<script lang="ts" setup>
import { toRefs, reactive, watch, ref } from 'vue';
import { dbApi } from './api';
import { machineApi } from '../machine/api.ts';
import { ElMessage } from 'element-plus';
@@ -116,70 +106,26 @@ import { notBlank } from '@/common/assert';
import { RsaEncrypt } from '@/common/rsa';
import TagSelect from '../component/TagSelect.vue';
export default defineComponent({
name: 'DbEdit',
components: {
TagSelect,
},
props: {
const props = defineProps({
visible: {
type: Boolean,
},
projects: {
type: Array,
},
db: {
type: [Boolean, Object],
},
title: {
type: String,
},
},
setup(props: any, { emit }) {
const dbForm: any = ref(null);
})
const state = reactive({
dialogVisible: false,
projects: [],
envs: [],
allDatabases: [] as any,
databaseList: [] as any,
sshTunnelMachineList: [] as any,
form: {
id: null,
tagId: null as any,
tagPath: null as any,
type: null,
name: null,
host: '',
port: 3306,
username: null,
password: null,
params: null,
database: '',
project: null,
projectId: null,
envId: null,
env: null,
remark: '',
enableSshTunnel: null,
sshTunnelMachineId: null,
},
// 原密码
pwd: '',
btnLoading: false,
rules: {
projectId: [
//定义事件
const emit = defineEmits(['update:visible', 'cancel', 'val-change'])
const rules = {
tagId: [
{
required: true,
message: '请选择项目',
trigger: ['change', 'blur'],
},
],
envId: [
{
required: true,
message: '请选择环境',
message: '请选择标签',
trigger: ['change', 'blur'],
},
],
@@ -218,61 +164,92 @@ export default defineComponent({
trigger: ['change', 'blur'],
},
],
},
});
}
watch(props, (newValue) => {
const dbForm: any = ref(null);
const state = reactive({
dialogVisible: false,
allDatabases: [] as any,
databaseList: [] as any,
sshTunnelMachineList: [] as any,
form: {
id: null,
tagId: null as any,
tagPath: null as any,
type: null,
name: null,
host: '',
port: 3306,
username: null,
password: null,
params: null,
database: '',
project: null,
projectId: null,
envId: null,
env: null,
remark: '',
enableSshTunnel: null,
sshTunnelMachineId: null,
},
// 原密码
pwd: '',
btnLoading: false,
});
const {
dialogVisible,
allDatabases,
databaseList,
sshTunnelMachineList,
form,
pwd,
btnLoading,
} = toRefs(state)
watch(props, (newValue: any) => {
state.dialogVisible = newValue.visible;
if (!state.dialogVisible) {
return;
}
state.projects = newValue.projects;
if (newValue.db) {
state.form = { ...newValue.db };
// 将数据库名使用空格切割,获取所有数据库列表
state.databaseList = newValue.db.database.split(' ');
} else {
state.envs = [];
state.form = { port: 3306, enableSshTunnel: -1 } as any;
state.databaseList = [];
}
getSshTunnelMachines();
});
});
/**
/**
* 改变表单中的数据库字段,方便表单错误提示。如全部删光,可提示请添加数据库
*/
const changeDatabase = () => {
const changeDatabase = () => {
state.form.database = state.databaseList.length == 0 ? '' : state.databaseList.join(' ');
};
};
const getSshTunnelMachines = async () => {
const getSshTunnelMachines = async () => {
if (state.form.enableSshTunnel == 1 && state.sshTunnelMachineList.length == 0) {
const res = await machineApi.list.request({ pageNum: 1, pageSize: 100 });
state.sshTunnelMachineList = res.list;
}
};
};
const changeEnv = (envId: number) => {
for (let p of state.envs as any) {
if (p.id == envId) {
state.form.env = p.name;
}
}
};
const getAllDatabase = async () => {
const getAllDatabase = async () => {
const reqForm = { ...state.form };
reqForm.password = await RsaEncrypt(reqForm.password);
state.allDatabases = await dbApi.getAllDatabase.request(reqForm);
ElMessage.success('获取成功, 请选择需要管理操作的数据库');
};
};
const getDbPwd = async () => {
const getDbPwd = async () => {
state.pwd = await dbApi.getDbPwd.request({ id: state.form.id });
};
};
const btnOk = async () => {
const btnOk = async () => {
if (!state.form.id) {
notBlank(state.form.password, '新增操作,密码不可为空');
}
@@ -295,34 +272,21 @@ export default defineComponent({
return false;
}
});
};
};
const resetInputDb = () => {
const resetInputDb = () => {
state.databaseList = [];
state.allDatabases = [];
};
};
const cancel = () => {
const cancel = () => {
emit('update:visible', false);
emit('cancel');
setTimeout(() => {
resetInputDb();
}, 500);
};
return {
...toRefs(state),
dbForm,
getAllDatabase,
getDbPwd,
changeDatabase,
getSshTunnelMachines,
changeEnv,
btnOk,
cancel,
};
},
});
};
</script>
<style lang="scss">
</style>

View File

@@ -2,8 +2,10 @@
<div class="db-list">
<el-card>
<el-button v-auth="permissions.saveDb" type="primary" icon="plus" @click="editDb(true)">添加</el-button>
<el-button v-auth="permissions.saveDb" :disabled="chooseId == null" @click="editDb(false)" type="primary" icon="edit">编辑</el-button>
<el-button v-auth="permissions.delDb" :disabled="chooseId == null" @click="deleteDb(chooseId)" type="danger" icon="delete">删除</el-button>
<el-button v-auth="permissions.saveDb" :disabled="chooseId == null" @click="editDb(false)" type="primary"
icon="edit">编辑</el-button>
<el-button v-auth="permissions.delDb" :disabled="chooseId == null" @click="deleteDb(chooseId)" type="danger"
icon="delete">删除</el-button>
<div style="float: right">
<el-select @focus="getTags" v-model="query.tagPath" placeholder="请选择标签" filterable clearable>
<el-option v-for="item in tags" :key="item" :label="item" :value="item"> </el-option>
@@ -30,19 +32,24 @@
<template #default="scope">
<el-popover placement="right" trigger="click" :width="300">
<template #reference>
<el-link type="primary" :underline="false" plain @click="selectDb(scope.row.dbs)">查看</el-link>
<el-link type="primary" :underline="false" plain @click="selectDb(scope.row.dbs)">查看
</el-link>
</template>
<el-input v-model="filterDb.param" @keyup="filterSchema" class="w-50 m-2" placeholder="搜索" size="small" >
<el-input v-model="filterDb.param" @keyup="filterSchema" class="w-50 m-2" placeholder="搜索"
size="small">
<template #prefix>
<el-icon class="el-input__icon"><search-icon /></el-icon>
<el-icon class="el-input__icon">
<search-icon />
</el-icon>
</template>
</el-input>
<div class="el-tag--plain el-tag--success"
v-for="db in filterDb.list" :key="db"
style="border:1px var(--color-success-light-3) solid; margin-top: 3px;border-radius: 5px; padding: 2px;position: relative"
>
<el-link type="success" plain size="small" :underline="false" @click="showTableInfo(scope.row, db)">{{ db }}</el-link>
<el-link type="primary" plain size="small" :underline="false" @click="openSqlExec(scope.row, db)" style="position: absolute; right: 4px">数据操作</el-link>
<div class="el-tag--plain el-tag--success" v-for="db in filterDb.list" :key="db"
style="border:1px var(--color-success-light-3) solid; margin-top: 3px;border-radius: 5px; padding: 2px;position: relative">
<el-link type="success" plain size="small" :underline="false"
@click="showTableInfo(scope.row, db)">{{ db }}</el-link>
<el-link type="primary" plain size="small" :underline="false"
@click="openSqlExec(scope.row, db)" style="position: absolute; right: 4px">数据操作
</el-link>
</div>
</el-popover>
</template>
@@ -53,25 +60,21 @@
<el-table-column min-width="115" prop="creator" label="创建账号"></el-table-column>
<el-table-column min-width="160" prop="createTime" label="创建时间" show-overflow-tooltip>
<template #default="scope">
{{ $filters.dateFormat(scope.row.createTime) }}
{{ dateFormat(scope.row.createTime) }}
</template>
</el-table-column>
<el-table-column label="操作" min-width="120" fixed="right">
<template #default="scope">
<el-link type="primary" plain size="small" :underline="false" @click="onShowSqlExec(scope.row)">SQL执行记录</el-link>
<el-link type="primary" plain size="small" :underline="false" @click="onShowSqlExec(scope.row)">
SQL执行记录</el-link>
</template>
</el-table-column>
</el-table>
<el-row style="margin-top: 20px" type="flex" justify="end">
<el-pagination
style="text-align: right"
@current-change="handlePageChange"
:total="total"
layout="prev, pager, next, total, jumper"
v-model:current-page="query.pageNum"
:page-size="query.pageSize"
></el-pagination>
<el-pagination style="text-align: right" @current-change="handlePageChange" :total="total"
layout="prev, pager, next, total, jumper" v-model:current-page="query.pageNum"
:page-size="query.pageSize"></el-pagination>
</el-row>
</el-card>
@@ -79,7 +82,7 @@
<el-row class="mb10">
<el-popover v-model:visible="showDumpInfo" :width="470" placement="right" trigger="click">
<template #reference>
<el-button class="ml5" type="success" size="small" >导出</el-button>
<el-button class="ml5" type="success" size="small">导出</el-button>
</template>
<el-form-item label="导出内容: ">
<el-radio-group v-model="dumpInfo.type">
@@ -90,10 +93,13 @@
</el-form-item>
<el-form-item label="导出表: ">
<el-table @selection-change="handleDumpTableSelectionChange" max-height="300" size="small" :data="tableInfoDialog.infos">
<el-table @selection-change="handleDumpTableSelectionChange" max-height="300" size="small"
:data="tableInfoDialog.infos">
<el-table-column type="selection" width="45" />
<el-table-column property="tableName" label="表名" min-width="150" show-overflow-tooltip> </el-table-column>
<el-table-column property="tableComment" label="备注" min-width="150" show-overflow-tooltip></el-table-column>
<el-table-column property="tableName" label="表名" min-width="150" show-overflow-tooltip>
</el-table-column>
<el-table-column property="tableComment" label="备注" min-width="150" show-overflow-tooltip>
</el-table-column>
</el-table>
</el-form-item>
@@ -105,40 +111,30 @@
<el-button type="primary" size="small" @click="openEditTable(false)">创建表</el-button>
</el-row>
<el-table v-loading="tableInfoDialog.loading" border stripe :data="filterTableInfos" size="small" max-height="680">
<el-table v-loading="tableInfoDialog.loading" border stripe :data="filterTableInfos" size="small"
max-height="680">
<el-table-column property="tableName" label="表名" min-width="150" show-overflow-tooltip>
<template #header>
<el-input v-model="tableInfoDialog.tableNameSearch" size="small" placeholder="表名: 输入可过滤" clearable />
<el-input v-model="tableInfoDialog.tableNameSearch" size="small" placeholder="表名: 输入可过滤"
clearable />
</template>
</el-table-column>
<el-table-column property="tableComment" label="备注" min-width="150" show-overflow-tooltip>
<template #header>
<el-input v-model="tableInfoDialog.tableCommentSearch" size="small" placeholder="备注: 输入可过滤" clearable />
<el-input v-model="tableInfoDialog.tableCommentSearch" size="small" placeholder="备注: 输入可过滤"
clearable />
</template>
</el-table-column>
<el-table-column
prop="tableRows"
label="Rows"
min-width="70"
sortable
:sort-method="(a, b) => parseInt(a.tableRows) - parseInt(b.tableRows)"
></el-table-column>
<el-table-column
property="dataLength"
label="数据大小"
sortable
:sort-method="(a, b) => parseInt(a.dataLength) - parseInt(b.dataLength)"
>
<el-table-column prop="tableRows" label="Rows" min-width="70" sortable
:sort-method="(a: any, b: any) => parseInt(a.tableRows) - parseInt(b.tableRows)"></el-table-column>
<el-table-column property="dataLength" label="数据大小" sortable
:sort-method="(a: any, b: any) => parseInt(a.dataLength) - parseInt(b.dataLength)">
<template #default="scope">
{{ formatByteSize(scope.row.dataLength) }}
</template>
</el-table-column>
<el-table-column
property="indexLength"
label="索引大小"
sortable
:sort-method="(a, b) => parseInt(a.indexLength) - parseInt(b.indexLength)"
>
<el-table-column property="indexLength" label="索引大小" sortable
:sort-method="(a: any, b: any) => parseInt(a.indexLength) - parseInt(b.indexLength)">
<template #default="scope">
{{ formatByteSize(scope.row.indexLength) }}
</template>
@@ -160,19 +156,18 @@
</el-table>
</el-dialog>
<el-dialog
width="90%"
:title="`${sqlExecLogDialog.title} - SQL执行记录`"
:before-close="onBeforeCloseSqlExecDialog"
v-model="sqlExecLogDialog.visible"
>
<el-dialog width="90%" :title="`${sqlExecLogDialog.title} - SQL执行记录`" :before-close="onBeforeCloseSqlExecDialog"
v-model="sqlExecLogDialog.visible">
<div class="toolbar">
<el-select v-model="sqlExecLogDialog.query.db" placeholder="请选择数据库" filterable clearable>
<el-option v-for="item in sqlExecLogDialog.dbs" :key="item" :label="`${item}`" :value="item"> </el-option>
<el-option v-for="item in sqlExecLogDialog.dbs" :key="item" :label="`${item}`" :value="item">
</el-option>
</el-select>
<el-input v-model="sqlExecLogDialog.query.table" placeholder="请输入表名" clearable class="ml5" style="width: 180px" />
<el-input v-model="sqlExecLogDialog.query.table" placeholder="请输入表名" clearable class="ml5"
style="width: 180px" />
<el-select v-model="sqlExecLogDialog.query.type" placeholder="请选择操作类型" clearable class="ml5">
<el-option v-for="item in enums.DbSqlExecTypeEnum" :key="item.value" :label="item.label" :value="item.value"> </el-option>
<el-option v-for="item in enums.DbSqlExecTypeEnum as any" :key="item.value" :label="item.label"
:value="item.value"> </el-option>
</el-select>
<el-button class="ml5" @click="searchSqlExecLog" type="success" icon="search"></el-button>
</div>
@@ -181,9 +176,12 @@
<el-table-column prop="table" label="" min-width="60" show-overflow-tooltip> </el-table-column>
<el-table-column prop="type" label="类型" width="85" show-overflow-tooltip>
<template #default="scope">
<el-tag v-if="scope.row.type == enums.DbSqlExecTypeEnum.UPDATE.value" color="#E4F5EB" size="small">UPDATE</el-tag>
<el-tag v-if="scope.row.type == enums.DbSqlExecTypeEnum.DELETE.value" color="#F9E2AE" size="small">DELETE</el-tag>
<el-tag v-if="scope.row.type == enums.DbSqlExecTypeEnum.INSERT.value" color="#A8DEE0" size="small">INSERT</el-tag>
<el-tag v-if="scope.row.type == enums.DbSqlExecTypeEnum['UPDATE'].value" color="#E4F5EB"
size="small">UPDATE</el-tag>
<el-tag v-if="scope.row.type == enums.DbSqlExecTypeEnum['DELETE'].value" color="#F9E2AE"
size="small">DELETE</el-tag>
<el-tag v-if="scope.row.type == enums.DbSqlExecTypeEnum['INSERT'].value" color="#A8DEE0"
size="small">INSERT</el-tag>
</template>
</el-table-column>
<el-table-column prop="sql" label="SQL" min-width="230" show-overflow-tooltip> </el-table-column>
@@ -198,31 +196,23 @@
<el-table-column label="操作" min-width="50" fixed="right">
<template #default="scope">
<el-link
v-if="scope.row.type == enums.DbSqlExecTypeEnum.UPDATE.value || scope.row.type == enums.DbSqlExecTypeEnum.DELETE.value"
type="primary"
plain
size="small"
:underline="false"
@click="onShowRollbackSql(scope.row)"
>还原SQL</el-link
>
v-if="scope.row.type == enums.DbSqlExecTypeEnum['UPDATE'].value || scope.row.type == enums.DbSqlExecTypeEnum['DELETE'].value"
type="primary" plain size="small" :underline="false" @click="onShowRollbackSql(scope.row)">
还原SQL</el-link>
</template>
</el-table-column>
</el-table>
<el-row style="margin-top: 20px" type="flex" justify="end">
<el-pagination
style="text-align: right"
@current-change="handleSqlExecPageChange"
:total="sqlExecLogDialog.total"
layout="prev, pager, next, total, jumper"
v-model:current-page="sqlExecLogDialog.query.pageNum"
:page-size="sqlExecLogDialog.query.pageSize"
></el-pagination>
<el-pagination style="text-align: right" @current-change="handleSqlExecPageChange"
:total="sqlExecLogDialog.total" layout="prev, pager, next, total, jumper"
v-model:current-page="sqlExecLogDialog.query.pageNum" :page-size="sqlExecLogDialog.query.pageSize">
</el-pagination>
</el-row>
</el-dialog>
<el-dialog width="55%" :title="`还原SQL`" v-model="rollbackSqlDialog.visible">
<el-input type="textarea" :autosize="{ minRows: 15, maxRows: 30 }" v-model="rollbackSqlDialog.sql" size="small"> </el-input>
<el-input type="textarea" :autosize="{ minRows: 15, maxRows: 30 }" v-model="rollbackSqlDialog.sql"
size="small"> </el-input>
</el-dialog>
<el-dialog width="40%" :title="`${chooseTableName} 字段信息`" v-model="columnDialog.visible">
@@ -236,30 +226,29 @@
<el-dialog width="40%" :title="`${chooseTableName} 索引信息`" v-model="indexDialog.visible">
<el-table border stripe :data="indexDialog.indexs" size="small">
<el-table-column prop="indexName" label="索引名" show-overflow-tooltip> </el-table-column>
<el-table-column prop="columnName" label="列名" show-overflow-tooltip> </el-table-column>
<el-table-column prop="indexName" label="索引名" min-width="120" show-overflow-tooltip> </el-table-column>
<el-table-column prop="columnName" label="列名" min-width="120" show-overflow-tooltip> </el-table-column>
<el-table-column prop="seqInIndex" label="列序列号" show-overflow-tooltip> </el-table-column>
<el-table-column prop="indexType" label="类型"> </el-table-column>
<el-table-column prop="indexComment" label="备注" min-width="230" show-overflow-tooltip> </el-table-column>
<el-table-column prop="indexComment" label="备注" min-width="130" show-overflow-tooltip>
</el-table-column>
</el-table>
</el-dialog>
<el-dialog width="55%" :title="`${chooseTableName} Create-DDL`" v-model="ddlDialog.visible">
<el-input disabled type="textarea" :autosize="{ minRows: 15, maxRows: 30 }" v-model="ddlDialog.ddl" size="small"> </el-input>
<el-input disabled type="textarea" :autosize="{ minRows: 15, maxRows: 30 }" v-model="ddlDialog.ddl"
size="small"> </el-input>
</el-dialog>
<db-edit
@val-change="valChange"
:title="dbEditDialog.title"
v-model:visible="dbEditDialog.visible"
v-model:db="dbEditDialog.data"
></db-edit>
<create-table :title="tableCreateDialog.title" :active-name="tableCreateDialog.activeName" :dbId="dbId" :db="db" :data="tableCreateDialog.data" v-model:visible="tableCreateDialog.visible"></create-table>
<db-edit @val-change="valChange" :title="dbEditDialog.title" v-model:visible="dbEditDialog.visible"
v-model:db="dbEditDialog.data"></db-edit>
<create-table :title="tableCreateDialog.title" :active-name="tableCreateDialog.activeName" :dbId="dbId" :db="db"
:data="tableCreateDialog.data" v-model:visible="tableCreateDialog.visible"></create-table>
</div>
</template>
<script lang='ts'>
import { toRefs, reactive, computed, onMounted, defineComponent } from 'vue';
<script lang='ts' setup>
import { toRefs, reactive, computed, onMounted } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import { formatByteSize } from '@/common/utils/format';
import DbEdit from './DbEdit.vue';
@@ -272,28 +261,21 @@ import { getSession } from '@/common/utils/storage';
import { isTrue } from '@/common/assert';
import { Search as SearchIcon } from '@element-plus/icons-vue'
import router from '@/router';
import {store} from '@/store';
import { store } from '@/store';
import { tagApi } from '../tag/api.ts';
import { dateFormat } from '@/common/utils/date';
export default defineComponent({
name: 'DbList',
components: {
DbEdit,
CreateTable,
SearchIcon,
},
setup() {
const state = reactive({
const permissions = {
saveDb: 'db:save',
delDb: 'db:del',
}
const state = reactive({
row: {},
dbId: 0,
db: '',
permissions: {
saveDb: 'db:save',
delDb: 'db:del',
},
tags: [],
chooseId: null,
chooseId: null as any,
/**
* 选中的数据
*/
@@ -303,7 +285,7 @@ export default defineComponent({
*/
query: {
tagPath: null,
projectId:null,
projectId: null,
pageNum: 1,
pageSize: 10,
},
@@ -358,11 +340,11 @@ export default defineComponent({
},
dbEditDialog: {
visible: false,
data: null,
data: null as any,
title: '新增数据库',
},
tableCreateDialog: {
title:'创建表',
title: '创建表',
visible: false,
activeName: '1',
data: { // 修改表时,传递修改数据
@@ -372,18 +354,41 @@ export default defineComponent({
columns: []
},
},
filterDb:{
param:'',
cache:[],
list:[],
filterDb: {
param: '',
cache: [],
list: [],
}
});
});
onMounted(async () => {
const {
dbId,
db,
tags,
chooseId,
query,
datas,
total,
showDumpInfo,
dumpInfo,
sqlExecLogDialog,
rollbackSqlDialog,
chooseTableName,
tableInfoDialog,
columnDialog,
indexDialog,
ddlDialog,
dbEditDialog,
tableCreateDialog,
filterDb,
} = toRefs(state)
onMounted(async () => {
search();
});
});
const filterTableInfos = computed(() => {
const filterTableInfos = computed(() => {
const infos = state.tableInfoDialog.infos;
const tableNameSearch = state.tableInfoDialog.tableNameSearch;
const tableCommentSearch = state.tableInfoDialog.tableCommentSearch;
@@ -401,17 +406,17 @@ export default defineComponent({
}
return tnMatch && tcMatch;
});
});
});
const choose = (item: any) => {
const choose = (item: any) => {
if (!item) {
return;
}
state.chooseId = item.id;
state.chooseData = item;
};
};
const search = async () => {
const search = async () => {
let res: any = await dbApi.dbs.request(state.query);
// 切割数据库
res.list.forEach((e: any) => {
@@ -420,18 +425,18 @@ export default defineComponent({
});
state.datas = res.list;
state.total = res.total;
};
};
const handlePageChange = (curPage: number) => {
const handlePageChange = (curPage: number) => {
state.query.pageNum = curPage;
search();
};
};
const getTags = async () => {
const getTags = async () => {
state.tags = await tagApi.getAccountTags.request(null);
};
};
const editDb = async (isAdd = false) => {
const editDb = async (isAdd = false) => {
if (isAdd) {
state.dbEditDialog.data = null;
state.dbEditDialog.title = '新增数据库资源';
@@ -440,15 +445,15 @@ export default defineComponent({
state.dbEditDialog.title = '修改数据库资源';
}
state.dbEditDialog.visible = true;
};
};
const valChange = () => {
const valChange = () => {
state.chooseData = null;
state.chooseId = null;
search();
};
};
const deleteDb = async (id: number) => {
const deleteDb = async (id: number) => {
try {
await ElMessageBox.confirm(`确定删除该库?`, '提示', {
confirmButtonText: '确定',
@@ -460,18 +465,18 @@ export default defineComponent({
state.chooseData = null;
state.chooseId = null;
search();
} catch (err) {}
};
} catch (err) { }
};
const onShowSqlExec = async (row: any) => {
const onShowSqlExec = async (row: any) => {
state.sqlExecLogDialog.title = `${row.name}[${row.host}:${row.port}]`;
state.sqlExecLogDialog.query.dbId = row.id;
state.sqlExecLogDialog.dbs = row.database.split(' ');
searchSqlExecLog();
state.sqlExecLogDialog.visible = true;
};
};
const onBeforeCloseSqlExecDialog = () => {
const onBeforeCloseSqlExecDialog = () => {
state.sqlExecLogDialog.visible = false;
state.sqlExecLogDialog.data = [];
state.sqlExecLogDialog.dbs = [];
@@ -481,30 +486,30 @@ export default defineComponent({
state.sqlExecLogDialog.query.table = '';
state.sqlExecLogDialog.query.db = '';
state.sqlExecLogDialog.query.type = null;
};
};
const searchSqlExecLog = async () => {
const searchSqlExecLog = async () => {
const res = await dbApi.getSqlExecs.request(state.sqlExecLogDialog.query);
state.sqlExecLogDialog.data = res.list;
state.sqlExecLogDialog.total = res.total;
};
};
const handleSqlExecPageChange = (curPage: number) => {
const handleSqlExecPageChange = (curPage: number) => {
state.sqlExecLogDialog.query.pageNum = curPage;
searchSqlExecLog();
};
};
/**
/**
* 选择导出数据库表
*/
const handleDumpTableSelectionChange = (vals: any) => {
const handleDumpTableSelectionChange = (vals: any) => {
state.dumpInfo.tables = vals.map((x: any) => x.tableName);
};
};
/**
/**
* 数据库信息导出
*/
const dump = (db: string) => {
const dump = (db: string) => {
isTrue(state.dumpInfo.tables.length > 0, '请选择要导出的表');
const a = document.createElement('a');
a.setAttribute(
@@ -515,9 +520,9 @@ export default defineComponent({
);
a.click();
state.showDumpInfo = false;
};
};
const onShowRollbackSql = async (sqlExecLog: any) => {
const onShowRollbackSql = async (sqlExecLog: any) => {
const columns = await dbApi.columnMetadata.request({ id: sqlExecLog.dbId, db: sqlExecLog.db, tableName: sqlExecLog.table });
const primaryKey = columns[0].columnName;
const oldValue = JSON.parse(sqlExecLog.oldValue);
@@ -547,19 +552,19 @@ export default defineComponent({
state.rollbackSqlDialog.sql = rollbackSqls.join('\n');
state.rollbackSqlDialog.visible = true;
};
};
/**
/**
* 包装值如果值类型为number则直接返回其他则需要使用''包装
*/
const wrapValue = (val: any) => {
const wrapValue = (val: any) => {
if (typeof val == 'number') {
return val;
}
return `'${val}'`;
};
};
const showTableInfo = async (row: any, db: string) => {
const showTableInfo = async (row: any, db: string) => {
state.tableInfoDialog.loading = true;
state.tableInfoDialog.visible = true;
try {
@@ -572,19 +577,19 @@ export default defineComponent({
} finally {
state.tableInfoDialog.loading = false;
}
};
};
const refreshTableInfo = async () => {
state.tableInfoDialog.infos = await dbApi.tableInfos.request({ id: state.dbId, db: state.db });
}
const closeTableInfo = () => {
const closeTableInfo = () => {
state.showDumpInfo = false;
state.tableInfoDialog.visible = false;
state.tableInfoDialog.infos = [];
};
};
const showColumns = async (row: any) => {
const showColumns = async (row: any) => {
state.chooseTableName = row.tableName;
state.columnDialog.columns = await dbApi.columnMetadata.request({
id: state.chooseId,
@@ -593,9 +598,9 @@ export default defineComponent({
});
state.columnDialog.visible = true;
};
};
const showTableIndex = async (row: any) => {
const showTableIndex = async (row: any) => {
state.chooseTableName = row.tableName;
state.indexDialog.indexs = await dbApi.tableIndex.request({
id: state.chooseId,
@@ -604,9 +609,9 @@ export default defineComponent({
});
state.indexDialog.visible = true;
};
};
const showCreateDdl = async (row: any) => {
const showCreateDdl = async (row: any) => {
state.chooseTableName = row.tableName;
const res = await dbApi.tableDdl.request({
id: state.chooseId,
@@ -615,12 +620,12 @@ export default defineComponent({
});
state.ddlDialog.ddl = res[0]['Create Table'];
state.ddlDialog.visible = true;
};
};
/**
/**
* 删除表
*/
const dropTable = async (row: any) => {
const dropTable = async (row: any) => {
try {
const tableName = row.tableName;
await ElMessageBox.confirm(`确定删除'${tableName}'表?`, '提示', {
@@ -636,13 +641,13 @@ export default defineComponent({
state.tableInfoDialog.infos = await dbApi.tableInfos.request({ id: state.chooseId, db: state.db });
},
});
} catch (err) {}
};
const openSqlExec = (row: any, db: any) => {
} catch (err) { }
};
const openSqlExec = (row: any, db: any) => {
// 判断db是否发生改变
let oldDb = store.state.sqlExecInfo.dbOptInfo.db;
if(db && oldDb !== db){
const {tagPath, id} = row;
if (db && oldDb !== db) {
const { tagPath, id } = row;
let params = {
tagPath,
dbId: id,
@@ -650,33 +655,33 @@ export default defineComponent({
}
store.dispatch('sqlExecInfo/setSqlExecInfo', params);
}
router.push({name: 'SqlExec'});
}
router.push({ name: 'SqlExec' });
}
// 点击查看时初始化数据
const selectDb = (row: any) => {
// 点击查看时初始化数据
const selectDb = (row: any) => {
state.filterDb.param = ''
state.filterDb.cache = row;
state.filterDb.list = row;
}
}
// 输入字符过滤schema
const filterSchema = () => {
if(state.filterDb.param){
state.filterDb.list = state.filterDb.cache.filter((a)=>{return String(a).toLowerCase().indexOf(state.filterDb.param) > -1 })
}else{
// 输入字符过滤schema
const filterSchema = () => {
if (state.filterDb.param) {
state.filterDb.list = state.filterDb.cache.filter((a) => { return String(a).toLowerCase().indexOf(state.filterDb.param) > -1 })
} else {
state.filterDb.list = state.filterDb.cache;
}
}
}
// 打开编辑表
const openEditTable = async (row: any) => {
// 打开编辑表
const openEditTable = async (row: any) => {
state.tableCreateDialog.visible = true
state.tableCreateDialog.activeName = '1'
if(row === false){
state.tableCreateDialog.data = {edit: false, row: {}, indexs: [], columns: []}
if (row === false) {
state.tableCreateDialog.data = { edit: false, row: {}, indexs: [], columns: [] }
state.tableCreateDialog.title = '创建表'
}
@@ -692,44 +697,10 @@ export default defineComponent({
db: state.db,
tableName: row.tableName,
});
state.tableCreateDialog.data = {edit: true, row, indexs, columns}
state.tableCreateDialog.data = { edit: true, row, indexs, columns }
}
}
return {
...toRefs(state),
dateFormat,
getTags,
filterTableInfos,
enums,
search,
choose,
handlePageChange,
editDb,
valChange,
deleteDb,
onShowSqlExec,
handleDumpTableSelectionChange,
dump,
onBeforeCloseSqlExecDialog,
handleSqlExecPageChange,
searchSqlExecLog,
onShowRollbackSql,
showTableInfo,
refreshTableInfo,
closeTableInfo,
showColumns,
showTableIndex,
showCreateDdl,
dropTable,
formatByteSize,
openSqlExec,
selectDb,
filterSchema,
openEditTable,
};
},
});
}
</script>
<style lang="scss">
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -2,19 +2,18 @@
<div>
<el-dialog :title="`${title} 详情`" v-model="dialogVisible" :before-close="cancel" width="90%">
<el-table @cell-click="cellClick" :data="data.res">
<el-table-column :width="200" :prop="item" :label="item" v-for="item in data.colNames" :key="item"> </el-table-column>
<el-table-column :width="200" :prop="item" :label="item" v-for="item in data.colNames" :key="item">
</el-table-column>
</el-table>
</el-dialog>
</div>
</template>
<script lang="ts">
import { watch, toRefs, reactive, defineComponent } from 'vue';
<script lang="ts" setup>
import { watch, toRefs, reactive } from 'vue';
export default defineComponent({
name: 'tableEdit',
props: {
const props = defineProps({
visible: {
type: Boolean,
},
@@ -24,22 +23,31 @@ export default defineComponent({
data: {
type: Object,
},
},
setup(props: any, { emit }) {
const state = reactive({
})
//定义事件
const emit = defineEmits(['update:visible'])
const state = reactive({
dialogVisible: false,
data: {
res: [],
colNames: [],
},
});
});
watch(props, async (newValue) => {
const {
dialogVisible,
data,
} = toRefs(state)
watch(props, async (newValue: any) => {
state.dialogVisible = newValue.visible;
state.data.res = newValue.data.res;
state.data.colNames = newValue.data.colNames;
});
const cellClick = (row: any, column: any, cell: any, event: any) => {
});
const cellClick = (row: any, column: any, cell: any) => {
let isDiv = cell.children[0].tagName === 'DIV';
let text = cell.children[0].innerText;
let div = cell.children[0];
@@ -53,16 +61,11 @@ export default defineComponent({
cell.replaceChildren(div);
});
}
};
const cancel = () => {
};
const cancel = () => {
emit('update:visible', false);
};
return {
...toRefs(state),
cancel,
cellClick,
};
},
});
};
</script>

View File

@@ -2,7 +2,8 @@
<div>
<el-dialog title="待执行SQL" v-model="dialogVisible" :show-close="false" width="600px">
如需执行多条sql需要在数据库管理配置连接参数multiStatements=true
<codemirror height="350px" class="codesql" ref="cmEditor" language="sql" v-model="sqlValue" :options="cmOptions" />
<codemirror height="350px" class="codesql" ref="cmEditor" language="sql" v-model="sqlValue"
:options="cmOptions" />
<el-input ref="remarkInputRef" v-model="remark" placeholder="请输入执行备注" class="mt5" />
<template #footer>
<span class="dialog-footer">
@@ -14,8 +15,8 @@
</div>
</template>
<script lang="ts">
import { toRefs, ref, nextTick, reactive, defineComponent } from 'vue';
<script lang="ts" setup>
import { toRefs, ref, nextTick, reactive } from 'vue';
import { dbApi } from '../api';
import { ElDialog, ElButton, ElInput, ElMessage, InputInstance } from 'element-plus';
// import base style
@@ -28,15 +29,7 @@ import { format as sqlFormatter } from 'sql-formatter';
import { SqlExecProps } from './SqlExecBox';
export default defineComponent({
name: 'SqlExecDialog',
components: {
codemirror,
ElButton,
ElDialog,
ElInput,
},
props: {
const props = defineProps({
visible: {
type: Boolean,
},
@@ -49,17 +42,9 @@ export default defineComponent({
sql: {
type: String,
},
},
setup(props: any) {
const remarkInputRef = ref<InputInstance>();
const state = reactive({
dialogVisible: false,
sqlValue: '',
dbId: 0,
db: '',
remark: '',
btnLoading: false,
cmOptions: {
})
const cmOptions = {
tabSize: 4,
mode: 'text/x-sql',
lineNumbers: true,
@@ -70,18 +55,34 @@ export default defineComponent({
theme: 'base16-light',
autofocus: true,
extraKeys: { Tab: 'autocomplete' }, // 自定义快捷键
},
});
state.sqlValue = props.sql;
}
let runSuccessCallback: any;
let cancelCallback: any;
let runSuccess: boolean = false;
const remarkInputRef = ref<InputInstance>();
const state = reactive({
dialogVisible: false,
sqlValue: '',
dbId: 0,
db: '',
remark: '',
btnLoading: false,
});
/**
const {
dialogVisible,
sqlValue,
remark,
btnLoading
} = toRefs(state)
state.sqlValue = props.sql as any;
let runSuccessCallback: any;
let cancelCallback: any;
let runSuccess: boolean = false;
/**
* 执行sql
*/
const runSql = async () => {
const runSql = async () => {
if (!state.remark) {
ElMessage.error('请输入执行的备注信息');
return;
@@ -107,9 +108,9 @@ export default defineComponent({
cancel();
}
state.btnLoading = false;
};
};
const cancel = () => {
const cancel = () => {
state.dialogVisible = false;
// 没有执行成功,并且取消回调函数存在,则执行
if (!runSuccess && cancelCallback) {
@@ -123,9 +124,9 @@ export default defineComponent({
cancelCallback = null;
runSuccess = false;
}, 200);
};
};
const open = (props: SqlExecProps) => {
const open = (props: SqlExecProps) => {
runSuccessCallback = props.runSuccessCallback;
cancelCallback = props.cancelCallback;
state.sqlValue = sqlFormatter(props.sql);
@@ -137,17 +138,7 @@ export default defineComponent({
remarkInputRef.value?.focus();
});
});
};
return {
...toRefs(state),
remarkInputRef,
open,
runSql,
cancel,
};
},
});
};
</script>
<style lang="scss">
.codesql {

View File

@@ -3,79 +3,56 @@
<el-dialog :title="title" v-model="dialogVisible" :show-close="true" :before-close="handleClose" width="800px">
<div class="toolbar">
<div style="float: right">
<el-button v-auth="'machine:file:add'" type="primary" @click="add" icon="plus" size="small" plain>添加</el-button>
<el-button v-auth="'machine:file:add'" type="primary" @click="add" icon="plus" size="small" plain>添加
</el-button>
</div>
</div>
<el-table :data="fileTable" stripe style="width: 100%">
<el-table-column prop="name" label="名称" width>
<template #default="scope">
<el-input v-model="scope.row.name" size="small" :disabled="scope.row.id != null" clearable></el-input>
<el-input v-model="scope.row.name" size="small" :disabled="scope.row.id != null" clearable>
</el-input>
</template>
</el-table-column>
<el-table-column prop="name" label="类型" min-width="50px">
<template #default="scope">
<el-select :disabled="scope.row.id != null" size="small" v-model="scope.row.type" style="width: 100px" placeholder="请选择">
<el-option v-for="item in enums.FileTypeEnum" :key="item.value" :label="item.label" :value="item.value"></el-option>
<el-select :disabled="scope.row.id != null" size="small" v-model="scope.row.type"
style="width: 100px" placeholder="请选择">
<el-option v-for="item in enums.FileTypeEnum as any" :key="item.value" :label="item.label"
:value="item.value"></el-option>
</el-select>
</template>
</el-table-column>
<el-table-column prop="path" label="路径" width>
<template #default="scope">
<el-input v-model="scope.row.path" :disabled="scope.row.id != null" size="small" clearable></el-input>
<el-input v-model="scope.row.path" :disabled="scope.row.id != null" size="small" clearable>
</el-input>
</template>
</el-table-column>
<el-table-column label="操作" width>
<template #default="scope">
<el-button v-if="scope.row.id == null" @click="addFiles(scope.row)" type="success" icon="success-filled" size="small" plain
>确定</el-button
>
<el-button v-if="scope.row.id != null" @click="getConf(scope.row)" type="primary" icon="tickets" size="small" plain
>查看</el-button
>
<el-button
v-auth="'machine:file:del'"
type="danger"
@click="deleteRow(scope.$index, scope.row)"
icon="delete"
size="small"
plain
>删除</el-button
>
<el-button v-if="scope.row.id == null" @click="addFiles(scope.row)" type="success"
icon="success-filled" size="small" plain>确定</el-button>
<el-button v-if="scope.row.id != null" @click="getConf(scope.row)" type="primary" icon="tickets"
size="small" plain>查看</el-button>
<el-button v-auth="'machine:file:del'" type="danger" @click="deleteRow(scope.$index, scope.row)"
icon="delete" size="small" plain>删除</el-button>
</template>
</el-table-column>
</el-table>
<el-row style="margin-top: 10px" type="flex" justify="end">
<el-pagination
small
style="text-align: center"
:total="total"
layout="prev, pager, next, total, jumper"
v-model:current-page="query.pageNum"
:page-size="query.pageSize"
@current-change="handlePageChange"
></el-pagination>
<el-pagination small style="text-align: center" :total="total" layout="prev, pager, next, total, jumper"
v-model:current-page="query.pageNum" :page-size="query.pageSize" @current-change="handlePageChange">
</el-pagination>
</el-row>
</el-dialog>
<el-dialog :title="tree.title" v-model="tree.visible" :close-on-click-modal="false" width="70%">
<el-progress
v-if="uploadProgressShow"
style="width: 90%; margin-left: 20px"
:text-inside="true"
:stroke-width="20"
:percentage="progressNum"
/>
<el-progress v-if="uploadProgressShow" style="width: 90%; margin-left: 20px" :text-inside="true"
:stroke-width="20" :percentage="progressNum" />
<div style="height: 45vh; overflow: auto">
<el-tree
v-if="tree.visible"
ref="fileTree"
:highlight-current="true"
:load="loadNode"
:props="props"
lazy
node-key="id"
:expand-on-click-node="true"
>
<el-tree v-if="tree.visible" ref="fileTree" :highlight-current="true" :load="loadNode"
:props="treeProps" lazy node-key="id" :expand-on-click-node="true">
<template #default="{ node, data }">
<span class="custom-tree-node">
<el-dropdown size="small" @visible-change="getFilePath(data, $event)" trigger="contextmenu">
@@ -97,31 +74,25 @@
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item
@click="getFileContent(tree.folder.id, data.path)"
v-if="data.type == '-' && data.size < 1 * 1024 * 1024"
>
<el-dropdown-item @click="getFileContent(tree.folder.id, data.path)"
v-if="data.type == '-' && data.size < 1 * 1024 * 1024">
<el-link type="info" icon="view" :underline="false">查看</el-link>
</el-dropdown-item>
<span v-auth="'machine:file:write'">
<el-dropdown-item @click="showCreateFileDialog(node, data)" v-if="data.type == 'd'">
<el-link type="primary" icon="document" :underline="false" style="margin-left: 2px">新建</el-link>
<el-dropdown-item @click="showCreateFileDialog(node)"
v-if="data.type == 'd'">
<el-link type="primary" icon="document" :underline="false"
style="margin-left: 2px">新建</el-link>
</el-dropdown-item>
</span>
<span v-auth="'machine:file:upload'">
<el-dropdown-item v-if="data.type == 'd'">
<el-upload
:before-upload="beforeUpload"
:on-success="uploadSuccess"
action=""
:http-request="getUploadFile"
:headers="{ token }"
:show-file-list="false"
name="file"
style="display: inline-block; margin-left: 2px"
>
<el-upload :before-upload="beforeUpload" :on-success="uploadSuccess"
action="" :http-request="getUploadFile" :headers="{ token }"
:show-file-list="false" name="file"
style="display: inline-block; margin-left: 2px">
<el-link icon="upload" :underline="false">上传</el-link>
</el-upload>
</el-dropdown-item>
@@ -129,21 +100,25 @@
<span v-auth="'machine:file:write'">
<el-dropdown-item @click="downloadFile(node, data)" v-if="data.type == '-'">
<el-link type="primary" icon="download" :underline="false" style="margin-left: 2px">下载</el-link>
<el-link type="primary" icon="download" :underline="false"
style="margin-left: 2px">下载</el-link>
</el-dropdown-item>
</span>
<span v-auth="'machine:file:rm'">
<el-dropdown-item @click="deleteFile(node, data)" v-if="!dontOperate(data)">
<el-link type="danger" icon="delete" :underline="false" style="margin-left: 2px">删除</el-link>
<el-link type="danger" icon="delete" :underline="false"
style="margin-left: 2px">删除</el-link>
</el-dropdown-item>
</span>
</el-dropdown-menu>
</template>
</el-dropdown>
<span style="display: inline-block" class="ml15">
<span style="color: #67c23a" v-if="data.type == '-'">[{{ formatFileSize(data.size) }}]</span>
<span v-if="data.mode" style="color: #67c23a">&nbsp;[{{ data.mode }} {{ data.modTime }}]</span>
<span style="color: #67c23a" v-if="data.type == '-'">[{{ formatFileSize(data.size)
}}]</span>
<span v-if="data.mode" style="color: #67c23a">&nbsp;[{{ data.mode }} {{ data.modTime
}}]</span>
</span>
</span>
</template>
@@ -151,15 +126,8 @@
</div>
</el-dialog>
<el-dialog
:destroy-on-close="true"
title="新建文件"
v-model="createFileDialog.visible"
:before-close="closeCreateFileDialog"
:close-on-click-modal="false"
top="5vh"
width="400px"
>
<el-dialog :destroy-on-close="true" title="新建文件" v-model="createFileDialog.visible"
:before-close="closeCreateFileDialog" :close-on-click-modal="false" top="5vh" width="400px">
<div>
<el-form-item prop="name" label="名称:">
<el-input v-model.trim="createFileDialog.name" placeholder="请输入名称" auto-complete="off"></el-input>
@@ -180,16 +148,11 @@
</template>
</el-dialog>
<el-dialog
:destroy-on-close="true"
:title="fileContent.dialogTitle"
v-model="fileContent.contentVisible"
:close-on-click-modal="false"
top="5vh"
width="70%"
>
<el-dialog :destroy-on-close="true" :title="fileContent.dialogTitle" v-model="fileContent.contentVisible"
:close-on-click-modal="false" top="5vh" width="70%">
<div>
<codemirror :can-change-mode="true" ref="cmEditor" v-model="fileContent.content" :language="fileContent.type" />
<codemirror :can-change-mode="true" ref="cmEditor" v-model="fileContent.content"
:language="fileContent.type" />
</div>
<template #footer>
@@ -202,8 +165,8 @@
</div>
</template>
<script lang="ts">
import { ref, toRefs, reactive, watch, defineComponent } from 'vue';
<script lang="ts" setup>
import { ref, toRefs, reactive, watch } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import { machineApi } from './api';
@@ -213,29 +176,31 @@ import enums from './enums';
import config from '@/common/config';
import { isTrue } from '@/common/assert';
export default defineComponent({
name: 'FileManage',
components: {
codemirror,
},
props: {
const props = defineProps({
visible: { type: Boolean },
machineId: { type: Number },
title: { type: String },
},
})
setup(props: any, { emit }) {
const addFile = machineApi.addConf;
const delFile = machineApi.delConf;
const updateFileContent = machineApi.updateFileContent;
const files = machineApi.files;
const fileTree: any = ref(null);
const token = getSession('token');
const emit = defineEmits(['update:visible', 'cancel', 'update:machineId'])
const folderType = 'd';
const fileType = '-';
const treeProps = {
label: 'name',
children: 'zones',
isLeaf: 'leaf',
}
const state = reactive({
const addFile = machineApi.addConf;
const delFile = machineApi.delConf;
const updateFileContent = machineApi.updateFileContent;
const files = machineApi.files;
const fileTree: any = ref(null);
const token = getSession('token');
const folderType = 'd';
const fileType = '-';
const state = reactive({
dialogVisible: false,
query: {
id: 0,
@@ -268,18 +233,13 @@ export default defineComponent({
},
resolve: {},
},
props: {
label: 'name',
children: 'zones',
isLeaf: 'leaf',
},
progressNum: 0,
uploadProgressShow: false,
dataObj: {
name: '',
path: '',
type: '',
},
progressNum: 0,
uploadProgressShow: false,
createFileDialog: {
visible: false,
name: '',
@@ -287,40 +247,52 @@ export default defineComponent({
node: null as any,
},
file: null as any,
});
});
watch(props, async (newValue) => {
const {
dialogVisible,
query,
total,
fileTable,
fileContent,
tree,
progressNum,
uploadProgressShow,
createFileDialog,
} = toRefs(state)
watch(props, async (newValue) => {
if (newValue.machineId && newValue.visible) {
await getFiles();
}
state.dialogVisible = newValue.visible;
});
});
const getFiles = async () => {
state.query.id = props.machineId;
const getFiles = async () => {
state.query.id = props.machineId as any;
const res = await files.request(state.query);
state.fileTable = res.list;
state.total = res.total;
};
};
const handlePageChange = (curPage: number) => {
const handlePageChange = (curPage: number) => {
state.query.pageNum = curPage;
getFiles();
};
};
const add = () => {
const add = () => {
// 往数组头部添加元素
state.fileTable = [{}].concat(state.fileTable);
};
};
const addFiles = async (row: any) => {
const addFiles = async (row: any) => {
row.machineId = props.machineId;
await addFile.request(row);
ElMessage.success('添加成功');
getFiles();
};
};
const deleteRow = (idx: any, row: any) => {
const deleteRow = (idx: any, row: any) => {
if (row.id) {
ElMessageBox.confirm(`此操作将删除 [${row.name}], 是否继续?`, '提示', {
confirmButtonText: '确定',
@@ -340,9 +312,9 @@ export default defineComponent({
} else {
state.fileTable.splice(idx, 1);
}
};
};
const getConf = (row: any) => {
const getConf = (row: any) => {
if (row.type == 1) {
state.tree.folder = row;
state.tree.title = row.name;
@@ -351,9 +323,9 @@ export default defineComponent({
return;
}
getFileContent(row.id, row.path);
};
};
const getFileContent = async (fileId: number, path: string) => {
const getFileContent = async (fileId: number, path: string) => {
const res = await machineApi.fileContent.request({
fileId,
path,
@@ -365,9 +337,9 @@ export default defineComponent({
state.fileContent.path = path;
state.fileContent.type = getFileType(path);
state.fileContent.contentVisible = true;
};
};
const getFileType = (path: string) => {
const getFileType = (path: string) => {
if (path.endsWith('.sh')) {
return 'shell';
}
@@ -390,9 +362,9 @@ export default defineComponent({
return 'html';
}
return 'text';
};
};
const updateContent = async () => {
const updateContent = async () => {
await updateFileContent.request({
content: state.fileContent.content,
id: state.fileContent.fileId,
@@ -402,25 +374,25 @@ export default defineComponent({
ElMessage.success('修改成功');
state.fileContent.contentVisible = false;
state.fileContent.content = '';
};
};
/**
/**
* 关闭取消按钮触发的事件
*/
const handleClose = () => {
const handleClose = () => {
emit('update:visible', false);
emit('update:machineId', null);
emit('cancel');
state.fileTable = [];
state.tree.folder = { id: 0 };
};
};
/**
/**
* 加载文件树节点
* @param {Object} node
* @param {Object} resolve
*/
const loadNode = async (node: any, resolve: any) => {
const loadNode = async (node: any, resolve: any) => {
if (typeof resolve !== 'function') {
return;
}
@@ -462,15 +434,15 @@ export default defineComponent({
}
}
return resolve(res);
};
};
const showCreateFileDialog = (node: any) => {
const showCreateFileDialog = (node: any) => {
isTrue(node.expanded, '请先点击展开该节点后再创建');
state.createFileDialog.node = node;
state.createFileDialog.visible = true;
};
};
const createFile = async () => {
const createFile = async () => {
const node = state.createFileDialog.node;
console.log(node.data);
const name = state.createFileDialog.name;
@@ -484,16 +456,16 @@ export default defineComponent({
});
fileTree.value.append({ name: name, path: path, type: type, leaf: type === fileType, size: 0 }, node);
closeCreateFileDialog();
};
};
const closeCreateFileDialog = () => {
const closeCreateFileDialog = () => {
state.createFileDialog.visible = false;
state.createFileDialog.node = null;
state.createFileDialog.name = '';
state.createFileDialog.type = folderType;
};
};
const deleteFile = (node: any, data: any) => {
const deleteFile = (node: any, data: any) => {
const file = data.path;
ElMessageBox.confirm(`此操作将删除 [${file}], 是否继续?`, '提示', {
confirmButtonText: '确定',
@@ -515,28 +487,28 @@ export default defineComponent({
.catch(() => {
// skip
});
};
};
const downloadFile = (node: any, data: any) => {
const downloadFile = (node: any, data: any) => {
const a = document.createElement('a');
a.setAttribute(
'href',
`${config.baseApiUrl}/machines/${props.machineId}/files/${state.tree.folder.id}/read?type=1&path=${data.path}&token=${token}`
);
a.click();
};
};
const onUploadProgress = (progressEvent: any) => {
const onUploadProgress = (progressEvent: any) => {
state.uploadProgressShow = true;
let complete = ((progressEvent.loaded / progressEvent.total) * 100) | 0;
state.progressNum = complete;
};
};
const getUploadFile = (content: any) => {
const getUploadFile = (content: any) => {
const params = new FormData();
params.append('file', content.file);
params.append('path', state.dataObj.path);
params.append('machineId', props.machineId);
params.append('machineId', props.machineId as any);
params.append('fileId', state.tree.folder.id as any);
params.append('token', token);
machineApi.uploadFile
@@ -556,23 +528,23 @@ export default defineComponent({
.catch(() => {
state.uploadProgressShow = false;
});
};
};
const uploadSuccess = (res: any) => {
const uploadSuccess = (res: any) => {
if (res.code !== 200) {
ElMessage.error(res.msg);
}
};
};
const beforeUpload = (file: File) => {
const beforeUpload = (file: File) => {
state.file = file;
};
const getFilePath = (data: object, visible: boolean) => {
};
const getFilePath = (data: object, visible: boolean) => {
if (visible) {
state.dataObj = data as any;
}
};
const dontOperate = (data: any) => {
};
const dontOperate = (data: any) => {
const path = data.path;
const ls = [
'/',
@@ -593,13 +565,13 @@ export default defineComponent({
'/root',
];
return ls.indexOf(path) != -1;
};
};
/**
/**
* 格式化文件大小
* @param {*} value
*/
const formatFileSize = (size: any) => {
const formatFileSize = (size: any) => {
const value = Number(size);
if (size && !isNaN(value)) {
const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB', 'BB'];
@@ -614,37 +586,8 @@ export default defineComponent({
return `${k.toFixed(2)}${units[index]}`;
}
return '-';
};
return {
...toRefs(state),
fileTree,
enums,
token,
add,
getFiles,
handlePageChange,
addFiles,
deleteRow,
getConf,
getFileContent,
updateContent,
handleClose,
loadNode,
showCreateFileDialog,
closeCreateFileDialog,
createFile,
deleteFile,
downloadFile,
getUploadFile,
beforeUpload,
getFilePath,
uploadSuccess,
dontOperate,
formatFileSize,
};
},
});
};
</script>
<style lang="scss">
</style>

View File

@@ -1,6 +1,7 @@
<template>
<div>
<el-dialog :title="title" v-model="dialogVisible" :close-on-click-modal="false" :destroy-on-close="true" :before-close="cancel" width="38%">
<el-dialog :title="title" v-model="dialogVisible" :close-on-click-modal="false" :destroy-on-close="true"
:before-close="cancel" width="38%">
<el-form :model="form" ref="machineForm" :rules="rules" label-width="85px">
<el-form-item prop="tagId" label="标签:" required>
<tag-select v-model:tag-id="form.tagId" v-model:tag-path="form.tagPath" style="width: 100%" />
@@ -10,7 +11,8 @@
</el-form-item>
<el-form-item prop="ip" label="ip:" required>
<el-col :span="18">
<el-input :disabled="form.id" v-model.trim="form.ip" placeholder="主机ip" auto-complete="off"></el-input>
<el-input :disabled="form.id" v-model.trim="form.ip" placeholder="主机ip" auto-complete="off">
</el-input>
</el-col>
<el-col style="text-align: center" :span="1">:</el-col>
<el-col :span="5">
@@ -27,15 +29,11 @@
</el-select>
</el-form-item>
<el-form-item v-if="form.authMethod == 1" prop="password" label="密码:">
<el-input
type="password"
show-password
v-model.trim="form.password"
placeholder="请输入密码,修改操作可不填"
autocomplete="new-password"
>
<el-input type="password" show-password v-model.trim="form.password" placeholder="请输入密码,修改操作可不填"
autocomplete="new-password">
<template v-if="form.id && form.id != 0" #suffix>
<el-popover @hide="pwd = ''" placement="right" title="原密码" :width="200" trigger="click" :content="pwd">
<el-popover @hide="pwd = ''" placement="right" title="原密码" :width="200" trigger="click"
:content="pwd">
<template #reference>
<el-link @click="getPwd" :underline="false" type="primary" class="mr5">原密码</el-link>
</template>
@@ -44,7 +42,8 @@
</el-input>
</el-form-item>
<el-form-item v-if="form.authMethod == 2" prop="password" label="秘钥:">
<el-input type="textarea" :rows="3" v-model="form.password" placeholder="请将私钥文件内容拷贝至此,修改操作可不填"></el-input>
<el-input type="textarea" :rows="3" v-model="form.password" placeholder="请将私钥文件内容拷贝至此,修改操作可不填">
</el-input>
</el-form-item>
<el-form-item prop="remark" label="备注:">
<el-input type="textarea" v-model="form.remark"></el-input>
@@ -56,17 +55,14 @@
<el-form-item prop="enableSshTunnel" label="SSH隧道:">
<el-col :span="3">
<el-checkbox @change="getSshTunnelMachines" v-model="form.enableSshTunnel" :true-label="1" :false-label="-1"></el-checkbox>
<el-checkbox @change="getSshTunnelMachines" v-model="form.enableSshTunnel" :true-label="1"
:false-label="-1"></el-checkbox>
</el-col>
<el-col :span="2" v-if="form.enableSshTunnel == 1"> 机器: </el-col>
<el-col :span="19" v-if="form.enableSshTunnel == 1">
<el-select style="width: 100%" v-model="form.sshTunnelMachineId" placeholder="请选择SSH隧道机器">
<el-option
v-for="item in sshTunnelMachineList"
:key="item.id"
:label="`${item.ip}:${item.port} [${item.name}]`"
:value="item.id"
>
<el-option v-for="item in sshTunnelMachineList" :key="item.id"
:label="`${item.ip}:${item.port} [${item.name}]`" :value="item.id">
</el-option>
</el-select>
</el-col>
@@ -83,20 +79,15 @@
</div>
</template>
<script lang="ts">
import { toRefs, reactive, watch, defineComponent, ref } from 'vue';
<script lang="ts" setup>
import { toRefs, reactive, watch, ref } from 'vue';
import { machineApi } from './api';
import { ElMessage } from 'element-plus';
import { notBlank } from '@/common/assert';
import { RsaEncrypt } from '@/common/rsa';
import TagSelect from '../component/TagSelect.vue';
export default defineComponent({
name: 'MachineEdit',
components: {
TagSelect,
},
props: {
const props = defineProps({
visible: {
type: Boolean,
},
@@ -109,33 +100,12 @@ export default defineComponent({
title: {
type: String,
},
},
setup(props: any, { emit }) {
const machineForm: any = ref(null);
const state = reactive({
dialogVisible: false,
projects: [] as any,
sshTunnelMachineList: [] as any,
tags: [],
selectTags: [],
form: {
id: null,
tagId: null as any,
tagPath: '',
ip: null,
name: null,
authMethod: 1,
port: 22,
username: '',
password: '',
remark: '',
enableSshTunnel: null,
sshTunnelMachineId: null,
enableRecorder: -1,
},
pwd: '',
btnLoading: false,
rules: {
})
//定义事件
const emit = defineEmits(['update:visible', 'cancel', 'val-change'])
const rules = {
tagId: [
{
required: true,
@@ -171,40 +141,69 @@ export default defineComponent({
trigger: ['change', 'blur'],
},
],
},
});
}
watch(props, async (newValue) => {
const machineForm: any = ref(null);
const state = reactive({
dialogVisible: false,
sshTunnelMachineList: [] as any,
form: {
id: null,
tagId: null as any,
tagPath: '',
ip: null,
name: null,
authMethod: 1,
port: 22,
username: '',
password: '',
remark: '',
enableSshTunnel: null,
sshTunnelMachineId: null,
enableRecorder: -1,
},
pwd: '',
btnLoading: false,
});
const {
dialogVisible,
sshTunnelMachineList,
form,
pwd,
btnLoading,
} = toRefs(state)
watch(props, async (newValue: any) => {
state.dialogVisible = newValue.visible;
if (!state.dialogVisible) {
return;
}
state.projects = newValue.projects;
if (newValue.machine) {
state.form = { ...newValue.machine };
} else {
state.form = { port: 22, authMethod: 1 } as any;
}
getSshTunnelMachines();
});
});
const getSshTunnelMachines = async () => {
const getSshTunnelMachines = async () => {
if (state.form.enableSshTunnel == 1 && state.sshTunnelMachineList.length == 0) {
const res = await machineApi.list.request({ pageNum: 1, pageSize: 100 });
state.sshTunnelMachineList = res.list;
}
};
};
const getSshTunnelMachine = (machineId: any) => {
const getSshTunnelMachine = (machineId: any) => {
notBlank(machineId, '请选择或先创建一台隧道机器');
return state.sshTunnelMachineList.find((x: any) => x.id == machineId);
};
};
const getPwd = async () => {
const getPwd = async () => {
state.pwd = await machineApi.getMachinePwd.request({ id: state.form.id });
};
};
const btnOk = async () => {
const btnOk = async () => {
if (!state.form.id) {
notBlank(state.form.password, '新增操作,密码不可为空');
}
@@ -236,23 +235,13 @@ export default defineComponent({
return false;
}
});
};
};
const cancel = () => {
const cancel = () => {
emit('update:visible', false);
emit('cancel');
};
return {
...toRefs(state),
machineForm,
getSshTunnelMachines,
getPwd,
btnOk,
cancel,
};
},
});
};
</script>
<style lang="scss">
</style>

View File

@@ -2,27 +2,21 @@
<div>
<el-card>
<div>
<el-button v-auth="'machine:add'" type="primary" icon="plus" @click="openFormDialog(false)" plain>添加</el-button>
<el-button v-auth="'machine:update'" type="primary" icon="edit" :disabled="!currentId" @click="openFormDialog(currentData)" plain
>编辑</el-button
>
<el-button v-auth="'machine:del'" :disabled="!currentId" @click="deleteMachine(currentId)" type="danger" icon="delete"
>删除</el-button
>
<el-button v-auth="'machine:add'" type="primary" icon="plus" @click="openFormDialog(false)" plain>添加
</el-button>
<el-button v-auth="'machine:update'" type="primary" icon="edit" :disabled="!currentId"
@click="openFormDialog(currentData)" plain>编辑</el-button>
<el-button v-auth="'machine:del'" :disabled="!currentId" @click="deleteMachine(currentId)" type="danger"
icon="delete">删除</el-button>
<div style="float: right">
<el-select @focus="getTags" v-model="params.tagPath" placeholder="请选择标签" @clear="search" filterable clearable>
<el-select @focus="getTags" v-model="params.tagPath" placeholder="请选择标签" @clear="search" filterable
clearable>
<el-option v-for="item in tags" :key="item" :label="item" :value="item"> </el-option>
</el-select>
<el-input
class="ml5"
placeholder="请输入名称"
style="width: 150px"
v-model="params.name"
@clear="search"
plain
clearable
></el-input>
<el-input class="ml5" placeholder="请输入ip" style="width: 150px" v-model="params.ip" @clear="search" plain clearable></el-input>
<el-input class="ml5" placeholder="请输入名称" style="width: 150px" v-model="params.name" @clear="search"
plain clearable></el-input>
<el-input class="ml5" placeholder="请输入ip" style="width: 150px" v-model="params.ip" @clear="search"
plain clearable></el-input>
<el-button class="ml5" @click="search" type="success" icon="search"></el-button>
</div>
</div>
@@ -35,32 +29,25 @@
</el-radio>
</template>
</el-table-column>
<el-table-column prop="tagPath" label="标签路径" min-width="150" show-overflow-tooltip></el-table-column>
<el-table-column prop="name" label="名称" min-width="140" show-overflow-tooltip></el-table-column>
<el-table-column prop="ip" label="ip:port" min-width="150">
<template #default="scope">
<el-link :disabled="scope.row.status == -1" @click="showMachineStats(scope.row)" type="primary" :underline="false">{{
<el-link :disabled="scope.row.status == -1" @click="showMachineStats(scope.row)" type="primary"
:underline="false">{{
`${scope.row.ip}:${scope.row.port}`
}}</el-link>
</template>
</el-table-column>
<el-table-column prop="status" label="状态" min-width="75">
<template #default="scope">
<el-switch
v-auth:disabled="'machine:update'"
:width="47"
v-model="scope.row.status"
:active-value="1"
:inactive-value="-1"
inline-prompt
active-text="启用"
inactive-text="停用"
<el-switch v-auth:disabled="'machine:update'" :width="47" v-model="scope.row.status"
:active-value="1" :inactive-value="-1" inline-prompt active-text="启用" inactive-text="停用"
style="--el-switch-on-color: #13ce66; --el-switch-off-color: #ff4949"
@change="changeStatus(scope.row)"
></el-switch>
@change="changeStatus(scope.row)"></el-switch>
</template>
</el-table-column>
<el-table-column prop="username" label="用户名" min-width="90"></el-table-column>
<el-table-column prop="tagPath" label="标签路径" min-width="150" show-overflow-tooltip></el-table-column>
<el-table-column prop="remark" label="备注" min-width="250" show-overflow-tooltip></el-table-column>
<el-table-column prop="createTime" label="创建时间" min-width="165">
<template #default="scope">
@@ -71,40 +58,19 @@
<el-table-column label="操作" min-width="235" fixed="right">
<template #default="scope">
<span v-auth="'machine:terminal'">
<el-link
:disabled="scope.row.status == -1"
type="primary"
@click="showTerminal(scope.row)"
plain
size="small"
:underline="false"
>终端</el-link
>
<el-link :disabled="scope.row.status == -1" type="primary" @click="showTerminal(scope.row)"
plain size="small" :underline="false">终端</el-link>
<el-divider direction="vertical" border-style="dashed" />
</span>
<span v-auth="'machine:file'">
<el-link
type="success"
:disabled="scope.row.status == -1"
@click="fileManage(scope.row)"
plain
size="small"
:underline="false"
>文件</el-link
>
<el-link type="success" :disabled="scope.row.status == -1"
@click="showFileManage(scope.row)" plain size="small" :underline="false">文件</el-link>
<el-divider direction="vertical" border-style="dashed" />
</span>
<el-link
:disabled="scope.row.status == -1"
type="warning"
@click="serviceManager(scope.row)"
plain
size="small"
:underline="false"
>脚本</el-link
>
<el-link :disabled="scope.row.status == -1" type="warning" @click="serviceManager(scope.row)"
plain size="small" :underline="false">脚本</el-link>
<el-divider direction="vertical" border-style="dashed" />
<el-dropdown>
@@ -116,34 +82,21 @@
</span>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item
><el-link
@click="showProcess(scope.row)"
:disabled="scope.row.status == -1"
plain
:underline="false"
size="small"
>进程</el-link
></el-dropdown-item
>
<el-dropdown-item>
<el-link @click="showProcess(scope.row)" :disabled="scope.row.status == -1"
plain :underline="false" size="small">进程</el-link>
</el-dropdown-item>
<el-dropdown-item v-if="scope.row.enableRecorder == 1"
><el-link v-auth="'machine:update'" @click="showRec(scope.row)" plain :underline="false" size="small"
>终端回放</el-link
></el-dropdown-item
>
<el-dropdown-item v-if="scope.row.enableRecorder == 1">
<el-link v-auth="'machine:update'" @click="showRec(scope.row)" plain
:underline="false" size="small">终端回放</el-link>
</el-dropdown-item>
<el-dropdown-item
><el-link
:disabled="!scope.row.hasCli || scope.row.status == -1"
type="danger"
@click="closeCli(scope.row)"
plain
size="small"
:underline="false"
>关闭连接</el-link
></el-dropdown-item
>
<el-dropdown-item>
<el-link :disabled="!scope.row.hasCli || scope.row.status == -1" type="danger"
@click="closeCli(scope.row)" plain size="small" :underline="false">关闭连接
</el-link>
</el-dropdown-item>
</el-dropdown-menu>
</template>
</el-dropdown>
@@ -151,42 +104,33 @@
</el-table-column>
</el-table>
<el-row style="margin-top: 20px" type="flex" justify="end">
<el-pagination
style="text-align: right"
:total="data.total"
layout="prev, pager, next, total, jumper"
v-model:current-page="params.pageNum"
:page-size="params.pageSize"
@current-change="handlePageChange"
></el-pagination>
<el-pagination style="text-align: right" :total="data.total" layout="prev, pager, next, total, jumper"
v-model:current-page="params.pageNum" :page-size="params.pageSize"
@current-change="handlePageChange"></el-pagination>
</el-row>
</el-card>
<machine-edit
:title="machineEditDialog.title"
v-model:visible="machineEditDialog.visible"
v-model:machine="machineEditDialog.data"
@valChange="submitSuccess"
></machine-edit>
<machine-edit :title="machineEditDialog.title" v-model:visible="machineEditDialog.visible"
v-model:machine="machineEditDialog.data" @valChange="submitSuccess"></machine-edit>
<process-list v-model:visible="processDialog.visible" v-model:machineId="processDialog.machineId" />
<service-manage :title="serviceDialog.title" v-model:visible="serviceDialog.visible" v-model:machineId="serviceDialog.machineId" />
<service-manage :title="serviceDialog.title" v-model:visible="serviceDialog.visible"
v-model:machineId="serviceDialog.machineId" />
<file-manage :title="fileDialog.title" v-model:visible="fileDialog.visible" v-model:machineId="fileDialog.machineId" />
<file-manage :title="fileDialog.title" v-model:visible="fileDialog.visible"
v-model:machineId="fileDialog.machineId" />
<machine-stats
v-model:visible="machineStatsDialog.visible"
:machineId="machineStatsDialog.machineId"
:title="machineStatsDialog.title"
></machine-stats>
<machine-stats v-model:visible="machineStatsDialog.visible" :machineId="machineStatsDialog.machineId"
:title="machineStatsDialog.title"></machine-stats>
<machine-rec v-model:visible="machineRecDialog.visible" :machineId="machineRecDialog.machineId" :title="machineRecDialog.title"></machine-rec>
<machine-rec v-model:visible="machineRecDialog.visible" :machineId="machineRecDialog.machineId"
:title="machineRecDialog.title"></machine-rec>
</div>
</template>
<script lang="ts">
import { toRefs, reactive, onMounted, defineComponent } from 'vue';
<script lang="ts" setup>
import { toRefs, reactive, onMounted } from 'vue';
import { useRouter } from 'vue-router';
import { ElMessage, ElMessageBox } from 'element-plus';
import { machineApi } from './api';
@@ -199,21 +143,9 @@ import MachineStats from './MachineStats.vue';
import MachineRec from './MachineRec.vue';
import { dateFormat } from '@/common/utils/date';
export default defineComponent({
name: 'MachineList',
components: {
ServiceManage,
ProcessList,
FileManage,
MachineEdit,
MachineStats,
MachineRec,
},
setup() {
const router = useRouter();
const state = reactive({
const router = useRouter();
const state = reactive({
tags: [] as any,
stats: '',
params: {
pageNum: 1,
pageSize: 10,
@@ -259,21 +191,35 @@ export default defineComponent({
machineId: 0,
title: '',
},
});
});
onMounted(async () => {
const {
tags,
params,
data,
currentId,
currentData,
serviceDialog,
processDialog,
fileDialog,
machineStatsDialog,
machineEditDialog,
machineRecDialog,
} = toRefs(state)
onMounted(async () => {
search();
});
});
const choose = (item: any) => {
const choose = (item: any) => {
if (!item) {
return;
}
state.currentId = item.id;
state.currentData = item;
};
};
const showTerminal = (row: any) => {
const showTerminal = (row: any) => {
const { href } = router.resolve({
path: `/machine/terminal`,
query: {
@@ -282,9 +228,9 @@ export default defineComponent({
},
});
window.open(href, '_blank');
};
};
const closeCli = async (row: any) => {
const closeCli = async (row: any) => {
await ElMessageBox.confirm(`确定关闭该机器客户端连接?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
@@ -293,13 +239,13 @@ export default defineComponent({
await machineApi.closeCli.request({ id: row.id });
ElMessage.success('关闭成功');
search();
};
};
const getTags = async () => {
const getTags = async () => {
state.tags = await tagApi.getAccountTags.request(null);
};
};
const openFormDialog = async (machine: any) => {
const openFormDialog = async (machine: any) => {
let dialogTitle;
if (machine) {
state.machineEditDialog.data = state.currentData as any;
@@ -311,9 +257,9 @@ export default defineComponent({
state.machineEditDialog.title = dialogTitle;
state.machineEditDialog.visible = true;
};
};
const deleteMachine = async (id: number) => {
const deleteMachine = async (id: number) => {
try {
await ElMessageBox.confirm(`确定删除该机器信息? 该操作将同时删除脚本及文件配置信息`, '提示', {
confirmButtonText: '确定',
@@ -325,91 +271,70 @@ export default defineComponent({
state.currentId = 0;
state.currentData = null;
search();
} catch (err) {}
};
} catch (err) { }
};
const serviceManager = (row: any) => {
const serviceManager = (row: any) => {
state.serviceDialog.machineId = row.id;
state.serviceDialog.visible = true;
state.serviceDialog.title = `${row.name} => ${row.ip}`;
};
};
/**
/**
* 调整机器状态
*/
const changeStatus = async (row: any) => {
const changeStatus = async (row: any) => {
await machineApi.changeStatus.request({ id: row.id, status: row.status });
};
};
/**
/**
* 显示机器状态统计信息
*/
const showMachineStats = async (machine: any) => {
const showMachineStats = async (machine: any) => {
state.machineStatsDialog.machineId = machine.id;
state.machineStatsDialog.title = `机器状态: ${machine.name} => ${machine.ip}`;
state.machineStatsDialog.visible = true;
};
};
const submitSuccess = () => {
const submitSuccess = () => {
state.currentId = 0;
state.currentData = null;
search();
};
};
const fileManage = (currentData: any) => {
const showFileManage = (currentData: any) => {
state.fileDialog.visible = true;
state.fileDialog.machineId = currentData.id;
state.fileDialog.title = `${currentData.name} => ${currentData.ip}`;
};
};
const search = async () => {
const search = async () => {
const res = await machineApi.list.request(state.params);
state.data = res;
};
};
const handlePageChange = (curPage: number) => {
const handlePageChange = (curPage: number) => {
state.params.pageNum = curPage;
search();
};
};
const showProcess = (row: any) => {
const showProcess = (row: any) => {
state.processDialog.machineId = row.id;
state.processDialog.visible = true;
};
};
const showRec = (row: any) => {
const showRec = (row: any) => {
state.machineRecDialog.title = `${row.name}[${row.ip}]-终端回放记录`;
state.machineRecDialog.machineId = row.id;
state.machineRecDialog.visible = true;
};
return {
...toRefs(state),
dateFormat,
choose,
getTags,
showTerminal,
openFormDialog,
deleteMachine,
closeCli,
serviceManager,
showMachineStats,
showProcess,
changeStatus,
submitSuccess,
fileManage,
search,
showRec,
handlePageChange,
};
},
});
};
</script>
<style>
.el-dialog__body {
padding: 2px 2px;
}
.el-dropdown-link-machine-list {
cursor: pointer;
color: var(--el-color-primary);

View File

@@ -1,13 +1,7 @@
<template>
<div id="terminalRecDialog">
<el-dialog
:title="title"
v-model="dialogVisible"
:before-close="handleClose"
:close-on-click-modal="false"
:destroy-on-close="true"
width="70%"
>
<el-dialog :title="title" v-model="dialogVisible" :before-close="handleClose" :close-on-click-modal="false"
:destroy-on-close="true" width="70%">
<div class="toolbar">
<el-select @change="getUsers" v-model="operateDate" placeholder="操作日期" filterable>
<el-option v-for="item in operateDates" :key="item" :label="item" :value="item"> </el-option>
@@ -26,23 +20,22 @@
</div>
</template>
<script lang="ts">
import { toRefs, watch, ref, reactive, defineComponent } from 'vue';
<script lang="ts" setup>
import { toRefs, watch, ref, reactive } from 'vue';
import { machineApi } from './api';
import * as AsciinemaPlayer from 'asciinema-player';
import 'asciinema-player/dist/bundle/asciinema-player.css';
export default defineComponent({
name: 'MachineRec',
components: {},
props: {
const props = defineProps({
visible: { type: Boolean },
machineId: { type: Number },
title: { type: String },
},
setup(props: any, context) {
const playerRef = ref(null);
const state = reactive({
})
const emit = defineEmits(['update:visible', 'cancel', 'update:machineId'])
const playerRef = ref(null);
const state = reactive({
dialogVisible: false,
title: '',
machineId: 0,
@@ -52,9 +45,20 @@ export default defineComponent({
operateDate: '',
user: '',
rec: '',
});
});
watch(props, async (newValue) => {
const {
dialogVisible,
title,
operateDates,
operateDate,
users,
recs,
user,
rec,
} = toRefs(state)
watch(props, async (newValue: any) => {
const visible = newValue.visible;
if (visible) {
state.machineId = newValue.machineId;
@@ -62,32 +66,32 @@ export default defineComponent({
await getOperateDate();
}
state.dialogVisible = visible;
});
});
const getOperateDate = async () => {
const getOperateDate = async () => {
const res = await machineApi.recDirNames.request({ path: state.machineId });
state.operateDates = res as any;
};
};
const getUsers = async (operateDate: string) => {
const getUsers = async (operateDate: string) => {
state.users = [];
state.user = '';
state.recs = [];
state.rec = '';
const res = await machineApi.recDirNames.request({ path: `${state.machineId}/${operateDate}` });
state.users = res as any;
};
};
const getRecs = async (user: string) => {
const getRecs = async (user: string) => {
state.recs = [];
state.rec = '';
const res = await machineApi.recDirNames.request({ path: `${state.machineId}/${state.operateDate}/${user}` });
state.recs = res as any;
};
};
let player: any = null;
let player: any = null;
const playRec = async (rec: string) => {
const playRec = async (rec: string) => {
if (player) {
player.dispose();
}
@@ -100,33 +104,22 @@ export default defineComponent({
speed: 1.0,
idleTimeLimit: 2,
});
};
};
/**
/**
* 关闭取消按钮触发的事件
*/
const handleClose = () => {
context.emit('update:visible', false);
context.emit('update:machineId', null);
context.emit('cancel');
const handleClose = () => {
emit('update:visible', false);
emit('update:machineId', null);
emit('cancel');
state.operateDates = [];
state.users = [];
state.recs = [];
state.operateDate = '';
state.user = '';
state.rec = '';
};
return {
...toRefs(state),
playerRef,
getUsers,
getRecs,
playRec,
handleClose,
};
},
});
};
</script>
<style lang="scss">
#terminalRecDialog {

View File

@@ -1,6 +1,7 @@
<template>
<div>
<el-dialog :title="title" v-model="dialogVisible" :close-on-click-modal="true" :destroy-on-close="true" :before-close="cancel" width="1050px">
<el-dialog :title="title" v-model="dialogVisible" :close-on-click-modal="true" :destroy-on-close="true"
:before-close="cancel" width="1050px">
<el-row :gutter="20">
<el-col :lg="12" :md="12">
<el-descriptions size="small" title="基础信息" :column="2" border>
@@ -19,7 +20,8 @@
<el-descriptions-item label="运行中任务">
{{ stats.RunningProcs }}
</el-descriptions-item>
<el-descriptions-item label="负载"> {{ stats.Load1 }} {{ stats.Load5 }} {{ stats.Load10 }} </el-descriptions-item>
<el-descriptions-item label="负载"> {{ stats.Load1 }} {{ stats.Load5 }} {{ stats.Load10 }}
</el-descriptions-item>
</el-descriptions>
</el-col>
@@ -36,7 +38,8 @@
<el-col :lg="8" :md="8">
<span style="font-size: 16px; font-weight: 700">磁盘</span>
<el-table :data="stats.FSInfos" stripe max-height="250" style="width: 100%" border>
<el-table-column prop="MountPoint" label="挂载点" min-width="100" show-overflow-tooltip></el-table-column>
<el-table-column prop="MountPoint" label="挂载点" min-width="100" show-overflow-tooltip>
</el-table-column>
<el-table-column prop="Used" label="可使用" min-width="70" show-overflow-tooltip>
<template #default="scope">
{{ formatByteSize(scope.row.Free) }}
@@ -54,8 +57,10 @@
<span style="font-size: 16px; font-weight: 700">网卡</span>
<el-table :data="netInter" stripe max-height="250" style="width: 100%" border>
<el-table-column prop="name" label="网卡" min-width="120" show-overflow-tooltip></el-table-column>
<el-table-column prop="IPv4" label="IPv4" min-width="130" show-overflow-tooltip></el-table-column>
<el-table-column prop="IPv6" label="IPv6" min-width="130" show-overflow-tooltip></el-table-column>
<el-table-column prop="IPv4" label="IPv4" min-width="130" show-overflow-tooltip>
</el-table-column>
<el-table-column prop="IPv6" label="IPv6" min-width="130" show-overflow-tooltip>
</el-table-column>
<el-table-column prop="Rx" label="接收(rx)" min-width="110" show-overflow-tooltip>
<template #default="scope">
{{ formatByteSize(scope.row.Rx) }}
@@ -73,17 +78,14 @@
</div>
</template>
<script lang="ts">
import { toRefs, reactive, watch, defineComponent, ref, nextTick } from 'vue';
<script lang="ts" setup>
import { toRefs, reactive, watch, ref, nextTick } from 'vue';
import useEcharts from '@/common/echarts/useEcharts.ts';
import tdTheme from '@/common/echarts/theme.json';
import { formatByteSize } from '@/common/utils/format';
import { machineApi } from './api';
export default defineComponent({
name: 'MachineStats',
components: {},
props: {
const props = defineProps({
visible: {
type: Boolean,
},
@@ -96,22 +98,31 @@ export default defineComponent({
title: {
type: String,
},
},
setup(props: any, { emit }) {
const cpuRef: any = ref();
const memRef: any = ref();
})
let cpuChart: any = null;
let memChart: any = null;
const emit = defineEmits(['update:visible', 'cancel', 'update:machineId'])
const state = reactive({
const cpuRef: any = ref();
const memRef: any = ref();
let cpuChart: any = null;
let memChart: any = null;
const state = reactive({
dialogVisible: false,
charts: [] as any,
stats: {} as any,
netInter: [] as any,
});
});
watch(props, async (newValue) => {
const {
dialogVisible,
stats,
netInter,
} = toRefs(state)
let charts = [] as any
watch(props, async (newValue: any) => {
const visible = newValue.visible;
if (visible) {
await setStats();
@@ -120,18 +131,18 @@ export default defineComponent({
if (visible) {
initCharts();
}
});
});
const setStats = async () => {
const setStats = async () => {
state.stats = await machineApi.stats.request({ id: props.machineId });
};
};
const onRefresh = async () => {
const onRefresh = async () => {
await setStats();
initCharts();
};
};
const initMemStats = () => {
const initMemStats = () => {
const data = [
{ name: '可用内存', value: state.stats.MemAvailable },
{
@@ -186,10 +197,10 @@ export default defineComponent({
}
const chart: any = useEcharts(memRef.value, tdTheme, option);
memChart = chart;
state.charts.push(chart);
};
charts.push(chart);
};
const initCpuStats = () => {
const initCpuStats = () => {
const cpu = state.stats.CPU;
const data = [
{ name: 'Idle', value: cpu.Idle },
@@ -253,33 +264,33 @@ export default defineComponent({
}
const chart: any = useEcharts(cpuRef.value, tdTheme, option);
cpuChart = chart;
state.charts.push(chart);
};
charts.push(chart);
};
const initCharts = () => {
const initCharts = () => {
nextTick(() => {
initMemStats();
initCpuStats();
});
parseNetInter();
initEchartsResize();
};
};
const initEchartResizeFun = () => {
const initEchartResizeFun = () => {
nextTick(() => {
for (let i = 0; i < state.charts.length; i++) {
for (let i = 0; i < charts.length; i++) {
setTimeout(() => {
state.charts[i].resize();
charts[i].resize();
}, i * 1000);
}
});
};
};
const initEchartsResize = () => {
const initEchartsResize = () => {
window.addEventListener('resize', initEchartResizeFun);
};
};
const parseNetInter = () => {
const parseNetInter = () => {
state.netInter = [];
const netInter = state.stats.NetIntf;
const keys = Object.keys(netInter);
@@ -290,9 +301,9 @@ export default defineComponent({
value.name = keys[i];
state.netInter.push(value);
}
};
};
const cancel = () => {
const cancel = () => {
emit('update:visible', false);
emit('cancel');
@@ -300,18 +311,7 @@ export default defineComponent({
cpuChart = null;
memChart = null;
}, 200);
};
return {
...toRefs(state),
cpuRef,
memRef,
cancel,
formatByteSize,
onRefresh,
};
},
});
};
</script>
<style lang="scss">
.card-item-chart {

View File

@@ -1,6 +1,7 @@
<template>
<div class="file-manage">
<el-dialog title="进程信息" v-model="dialogVisible" :destroy-on-close="true" :show-close="true" :before-close="handleClose" width="65%">
<el-dialog title="进程信息" v-model="dialogVisible" :destroy-on-close="true" :show-close="true"
:before-close="handleClose" width="65%">
<div class="toolbar">
<el-row>
<el-col :span="4">
@@ -21,7 +22,8 @@
</el-select>
</el-col>
<el-col :span="6">
<el-button class="ml5" @click="getProcess" type="primary" icon="tickets" size="small" plain>刷新</el-button>
<el-button class="ml5" @click="getProcess" type="primary" icon="tickets" size="small" plain>刷新
</el-button>
</el-col>
</el-row>
</div>
@@ -35,7 +37,9 @@
<template #header>
VSZ
<el-tooltip class="box-item" effect="dark" content="虚拟内存" placement="top">
<el-icon><question-filled /></el-icon>
<el-icon>
<question-filled />
</el-icon>
</el-tooltip>
</template>
</el-table-column>
@@ -43,7 +47,9 @@
<template #header>
RSS
<el-tooltip class="box-item" effect="dark" content="固定内存" placement="top">
<el-icon><question-filled /></el-icon>
<el-icon>
<question-filled />
</el-icon>
</el-tooltip>
</template>
</el-table-column>
@@ -51,7 +57,9 @@
<template #header>
STAT
<el-tooltip class="box-item" effect="dark" content="进程状态" placement="top">
<el-icon><question-filled /></el-icon>
<el-icon>
<question-filled />
</el-icon>
</el-tooltip>
</template>
</el-table-column>
@@ -59,7 +67,9 @@
<template #header>
START
<el-tooltip class="box-item" effect="dark" content="启动时间" placement="top">
<el-icon><question-filled /></el-icon>
<el-icon>
<question-filled />
</el-icon>
</el-tooltip>
</template>
</el-table-column>
@@ -67,17 +77,21 @@
<template #header>
TIME
<el-tooltip class="box-item" effect="dark" content="该进程实际使用CPU运作的时间" placement="top">
<el-icon><question-filled /></el-icon>
<el-icon>
<question-filled />
</el-icon>
</el-tooltip>
</template>
</el-table-column>
<el-table-column prop="command" label="command" :min-width="120" show-overflow-tooltip> </el-table-column>
<el-table-column prop="command" label="command" :min-width="120" show-overflow-tooltip>
</el-table-column>
<el-table-column label="操作">
<template #default="scope">
<el-popconfirm title="确定终止该进程?" @confirm="confirmKillProcess(scope.row.pid)">
<template #reference>
<el-button v-auth="'machine:killprocess'" type="danger" icon="delete" size="small" plain>终止</el-button>
<el-button v-auth="'machine:killprocess'" type="danger" icon="delete" size="small"
plain>终止</el-button>
</template>
</el-popconfirm>
<!-- <el-button @click="addFiles(scope.row)" type="danger" icon="delete" size="small" plain>终止</el-button> -->
@@ -88,21 +102,20 @@
</div>
</template>
<script lang="ts">
import { toRefs, reactive, watch, defineComponent } from 'vue';
<script lang="ts" setup>
import { toRefs, reactive, watch } from 'vue';
import { ElMessage } from 'element-plus';
import { machineApi } from './api';
import enums from './enums';
export default defineComponent({
name: 'ProcessList',
components: {},
props: {
const props = defineProps({
visible: { type: Boolean },
machineId: { type: Number },
title: { type: String },
},
setup(props: any, context) {
const state = reactive({
})
const emit = defineEmits(['update:visible', 'cancel', 'update:machineId'])
const state = reactive({
dialogVisible: false,
params: {
name: '',
@@ -111,17 +124,24 @@ export default defineComponent({
id: 0,
},
processList: [],
});
});
watch(props, (newValue) => {
const {
dialogVisible,
params,
processList,
} = toRefs(state)
watch(props, (newValue) => {
if (props.machineId) {
state.params.id = props.machineId;
getProcess();
}
state.dialogVisible = newValue.visible;
});
});
const getProcess = async () => {
const getProcess = async () => {
const res = await machineApi.process.request(state.params);
// 解析字符串
// USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
@@ -161,9 +181,9 @@ export default defineComponent({
});
}
state.processList = ps as any;
};
};
const confirmKillProcess = async (pid: any) => {
const confirmKillProcess = async (pid: any) => {
await machineApi.killProcess.request({
pid,
id: state.params.id,
@@ -171,19 +191,19 @@ export default defineComponent({
ElMessage.success('kill success');
state.params.name = '';
getProcess();
};
};
const kb2Mb = (kb: string) => {
const kb2Mb = (kb: string) => {
return (parseInt(kb) / 1024).toFixed(2) + 'M';
};
};
/**
/**
* 关闭取消按钮触发的事件
*/
const handleClose = () => {
context.emit('update:visible', false);
context.emit('update:machineId', null);
context.emit('cancel');
const handleClose = () => {
emit('update:visible', false);
emit('update:machineId', null);
emit('cancel');
state.params = {
name: '',
sortType: '1',
@@ -191,15 +211,5 @@ export default defineComponent({
id: 0,
};
state.processList = [];
};
return {
...toRefs(state),
getProcess,
confirmKillProcess,
enums,
handleClose,
};
},
});
};
</script>

View File

@@ -1,14 +1,7 @@
<template>
<div class="mock-data-dialog">
<el-dialog
:title="title"
v-model="dialogVisible"
:close-on-click-modal="false"
:before-close="cancel"
:show-close="true"
:destroy-on-close="true"
width="900px"
>
<el-dialog :title="title" v-model="dialogVisible" :close-on-click-modal="false" :before-close="cancel"
:show-close="true" :destroy-on-close="true" width="900px">
<el-form :model="form" ref="scriptForm" label-width="50px" size="small">
<el-form-item prop="method" label="名称">
<el-input v-model.trim="form.name" placeholder="请输入名称"></el-input>
@@ -20,7 +13,8 @@
<el-form-item prop="type" label="类型">
<el-select v-model="form.type" default-first-option style="width: 100%" placeholder="请选择类型">
<el-option v-for="item in enums.scriptTypeEnum" :key="item.value" :label="item.label" :value="item.value"></el-option>
<el-option v-for="item in enums.scriptTypeEnum as any" :key="item.value" :label="item.label"
:value="item.value"></el-option>
</el-select>
</el-form-item>
@@ -29,17 +23,25 @@
</el-row>
<el-form-item :key="param" v-for="(param, index) in params" prop="params" :label="`参数${index + 1}`">
<el-row>
<el-col :span="5"><el-input v-model="param.model" placeholder="内容中用{{.model}}替换"></el-input></el-col>
<el-col :span="5">
<el-input v-model="param.model" placeholder="内容中用{{.model}}替换"></el-input>
</el-col>
<el-divider :span="1" direction="vertical" border-style="dashed" />
<el-col :span="4"><el-input v-model="param.name" placeholder="字段名"></el-input></el-col>
<el-col :span="4">
<el-input v-model="param.name" placeholder="字段名"></el-input>
</el-col>
<el-divider :span="1" direction="vertical" border-style="dashed" />
<el-col :span="4"><el-input v-model="param.placeholder" placeholder="字段说明"></el-input></el-col>
<el-col :span="4">
<el-input v-model="param.placeholder" placeholder="字段说明"></el-input>
</el-col>
<el-divider :span="1" direction="vertical" border-style="dashed" />
<el-col :span="4">
<el-input v-model="param.options" placeholder="可选值 ,分割"></el-input>
</el-col>
<el-divider :span="1" direction="vertical" border-style="dashed" />
<el-col :span="2"><el-button @click="onDeleteParam(index)" size="small" type="danger">删除</el-button></el-col>
<el-col :span="2">
<el-button @click="onDeleteParam(index)" size="small" type="danger">删除</el-button>
</el-col>
</el-row>
</el-form-item>
@@ -51,22 +53,16 @@
<template #footer>
<div class="dialog-footer">
<el-button @click="cancel()" :disabled="submitDisabled"> </el-button>
<el-button
v-auth="'machine:script:save'"
type="primary"
:loading="btnLoading"
@click="btnOk"
:disabled="submitDisabled"
> </el-button
>
<el-button v-auth="'machine:script:save'" type="primary" :loading="btnLoading" @click="btnOk"
:disabled="submitDisabled"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script lang="ts">
import { ref, toRefs, reactive, watch, defineComponent } from 'vue';
<script lang="ts" setup>
import { ref, toRefs, reactive, watch } from 'vue';
import { ElMessage } from 'element-plus';
import { machineApi } from './api';
import enums from './enums';
@@ -74,12 +70,7 @@ import { notEmpty } from '@/common/assert';
import { codemirror } from '@/components/codemirror';
export default defineComponent({
name: 'ScriptEdit',
components: {
codemirror,
},
props: {
const props = defineProps({
visible: {
type: Boolean,
},
@@ -95,12 +86,14 @@ export default defineComponent({
isCommon: {
type: Boolean,
},
},
setup(props: any, { emit }) {
const { isCommon, machineId } = toRefs(props);
const scriptForm: any = ref(null);
})
const state = reactive({
const emit = defineEmits(['update:visible', 'cancel', 'submitSuccess'])
const { isCommon, machineId } = toRefs(props);
const scriptForm: any = ref(null);
const state = reactive({
dialogVisible: false,
submitDisabled: false,
params: [] as any,
@@ -114,9 +107,17 @@ export default defineComponent({
type: null,
},
btnLoading: false,
});
});
watch(props, (newValue) => {
const {
dialogVisible,
submitDisabled,
params,
form,
btnLoading,
} = toRefs(state)
watch(props, (newValue: any) => {
state.dialogVisible = newValue.visible;
if (!newValue.visible) {
return;
@@ -130,18 +131,18 @@ export default defineComponent({
state.form = {} as any;
state.form.script = '';
}
});
});
const onAddParam = () => {
const onAddParam = () => {
state.params.push({ name: '', model: '', placeholder: '' });
};
};
const onDeleteParam = (idx: number) => {
const onDeleteParam = (idx: number) => {
state.params.splice(idx, 1);
};
};
const btnOk = () => {
state.form.machineId = isCommon.value ? 9999999 : (machineId.value as any);
const btnOk = () => {
state.form.machineId = isCommon.value ? 9999999 : (machineId?.value as any);
console.log('machineid:', machineId);
scriptForm.value.validate((valid: any) => {
if (valid) {
@@ -166,25 +167,13 @@ export default defineComponent({
return false;
}
});
};
};
const cancel = () => {
const cancel = () => {
emit('update:visible', false);
emit('cancel');
state.params = [];
};
return {
...toRefs(state),
enums,
onAddParam,
onDeleteParam,
scriptForm,
btnOk,
cancel,
};
},
});
};
</script>
<style lang="scss">
#content {

View File

@@ -1,6 +1,7 @@
<template>
<div class="file-manage">
<el-dialog :title="title" v-model="dialogVisible" :destroy-on-close="true" :show-close="true" :before-close="handleClose" width="60%">
<el-dialog :title="title" v-model="dialogVisible" :destroy-on-close="true" :show-close="true"
:before-close="handleClose" width="60%">
<div class="toolbar">
<div style="float: left">
<el-select v-model="type" @change="getScripts" size="small" placeholder="请选择">
@@ -9,20 +10,12 @@
</el-select>
</div>
<div style="float: right">
<el-button @click="editScript(currentData)" :disabled="currentId == null" type="primary" icon="tickets" size="small" plain
>查看</el-button
>
<el-button v-auth="'machine:script:save'" type="primary" @click="editScript(null)" icon="plus" size="small" plain>添加</el-button>
<el-button
v-auth="'machine:script:del'"
:disabled="currentId == null"
type="danger"
@click="deleteRow(currentData)"
icon="delete"
size="small"
plain
>删除</el-button
>
<el-button @click="editScript(currentData)" :disabled="currentId == null" type="primary"
icon="tickets" size="small" plain>查看</el-button>
<el-button v-auth="'machine:script:save'" type="primary" @click="editScript(null)" icon="plus"
size="small" plain>添加</el-button>
<el-button v-auth="'machine:script:del'" :disabled="currentId == null" type="danger"
@click="deleteRow(currentData)" icon="delete" size="small" plain>删除</el-button>
</div>
</div>
@@ -43,56 +36,32 @@
</el-table-column>
<el-table-column label="操作">
<template #default="scope">
<el-button v-if="scope.row.id == null" @click="addFiles(scope.row)" type="success" icon="el-icon-success" size="small" plain
>确定</el-button
>
<el-button v-if="scope.row.id == null" type="success" icon="el-icon-success" size="small" plain>
确定</el-button>
<el-button
v-auth="'machine:script:run'"
v-if="scope.row.id != null"
@click="runScript(scope.row)"
type="primary"
icon="video-play"
size="small"
plain
>执行</el-button
>
<el-button v-auth="'machine:script:run'" v-if="scope.row.id != null"
@click="runScript(scope.row)" type="primary" icon="video-play" size="small" plain>执行
</el-button>
</template>
</el-table-column>
</el-table>
<el-row style="margin-top: 10px" type="flex" justify="end">
<el-pagination
small
style="text-align: center"
:total="total"
layout="prev, pager, next, total, jumper"
v-model:current-page="query.pageNum"
:page-size="query.pageSize"
@current-change="handlePageChange"
></el-pagination>
<el-pagination small style="text-align: center" :total="total" layout="prev, pager, next, total, jumper"
v-model:current-page="query.pageNum" :page-size="query.pageSize" @current-change="handlePageChange">
</el-pagination>
</el-row>
</el-dialog>
<el-dialog title="脚本参数" v-model="scriptParamsDialog.visible" width="400px">
<el-form ref="paramsForm" :model="scriptParamsDialog.params" label-width="70px" size="small">
<el-form-item v-for="item in scriptParamsDialog.paramsFormItem" :key="item.name" :prop="item.model" :label="item.name" required>
<el-input
v-if="!item.options"
v-model="scriptParamsDialog.params[item.model]"
:placeholder="item.placeholder"
autocomplete="off"
clearable
></el-input>
<el-select
v-else
v-model="scriptParamsDialog.params[item.model]"
:placeholder="item.placeholder"
filterable
autocomplete="off"
clearable
style="width: 100%"
>
<el-option v-for="option in item.options.split(',')" :key="option" :label="option" :value="option" />
<el-form-item v-for="item in scriptParamsDialog.paramsFormItem as any" :key="item.name"
:prop="item.model" :label="item.name" required>
<el-input v-if="!item.options" v-model="scriptParamsDialog.params[item.model]"
:placeholder="item.placeholder" autocomplete="off" clearable></el-input>
<el-select v-else v-model="scriptParamsDialog.params[item.model]" :placeholder="item.placeholder"
filterable autocomplete="off" clearable style="width: 100%">
<el-option v-for="option in item.options.split(',')" :key="option" :label="option"
:value="option" />
</el-select>
</el-form-item>
</el-form>
@@ -109,63 +78,47 @@
</div>
</el-dialog>
<el-dialog
v-if="terminalDialog.visible"
title="终端"
v-model="terminalDialog.visible"
width="80%"
:close-on-click-modal="false"
:modal="false"
@close="closeTermnial"
>
<ssh-terminal ref="terminal" :cmd="terminalDialog.cmd" :machineId="terminalDialog.machineId" height="560px" />
<el-dialog v-if="terminalDialog.visible" title="终端" v-model="terminalDialog.visible" width="80%"
:close-on-click-modal="false" :modal="false" @close="closeTermnial">
<ssh-terminal ref="terminal" :cmd="terminalDialog.cmd" :machineId="terminalDialog.machineId"
height="560px" />
</el-dialog>
<script-edit
v-model:visible="editDialog.visible"
v-model:data="editDialog.data"
:title="editDialog.title"
v-model:machineId="editDialog.machineId"
:isCommon="type == 1"
@submitSuccess="submitSuccess"
/>
<script-edit v-model:visible="editDialog.visible" v-model:data="editDialog.data" :title="editDialog.title"
v-model:machineId="editDialog.machineId" :isCommon="type == 1" @submitSuccess="submitSuccess" />
</div>
</template>
<script lang="ts">
import { ref, toRefs, reactive, watch, defineComponent } from 'vue';
<script lang="ts" setup>
import { ref, toRefs, reactive, watch } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import SshTerminal from './SshTerminal.vue';
import { machineApi } from './api';
import enums from './enums';
import ScriptEdit from './ScriptEdit.vue';
export default defineComponent({
name: 'ServiceManage',
components: {
ScriptEdit,
SshTerminal,
},
props: {
const props = defineProps({
visible: { type: Boolean },
machineId: { type: Number },
title: { type: String },
},
setup(props: any, context) {
const paramsForm: any = ref(null);
const state = reactive({
})
const emit = defineEmits(['update:visible', 'cancel', 'update:machineId'])
const paramsForm: any = ref(null);
const state = reactive({
dialogVisible: false,
type: 0,
currentId: null,
currentData: null,
query: {
machineId: 0,
machineId: 0 as any,
pageNum: 1,
pageSize: 8,
},
editDialog: {
visible: false,
data: null,
data: null as any,
title: '',
machineId: 9999999,
},
@@ -185,30 +138,44 @@ export default defineComponent({
cmd: '',
machineId: 0,
},
});
});
watch(props, async (newValue) => {
const {
dialogVisible,
type,
currentId,
currentData,
query,
editDialog,
total,
scriptTable,
scriptParamsDialog,
resultDialog,
terminalDialog,
} = toRefs(state)
watch(props, async (newValue) => {
if (props.machineId && newValue.visible) {
await getScripts();
}
state.dialogVisible = newValue.visible;
});
});
const getScripts = async () => {
const getScripts = async () => {
state.currentId = null;
state.currentData = null;
state.query.machineId = state.type == 0 ? props.machineId : 9999999;
const res = await machineApi.scripts.request(state.query);
state.scriptTable = res.list;
state.total = res.total;
};
};
const handlePageChange = (curPage: number) => {
const handlePageChange = (curPage: number) => {
state.query.pageNum = curPage;
getScripts();
};
};
const runScript = async (script: any) => {
const runScript = async (script: any) => {
// 如果存在参数,则弹窗输入参数后执行
if (script.params) {
state.scriptParamsDialog.paramsFormItem = JSON.parse(script.params);
@@ -219,10 +186,10 @@ export default defineComponent({
}
run(script);
};
};
// 有参数的脚本执行函数
const hasParamsRun = async (script: any) => {
// 有参数的脚本执行函数
const hasParamsRun = async (script: any) => {
// 如果脚本参数弹窗显示,则校验参数表单数据通过后执行
if (state.scriptParamsDialog.visible) {
paramsForm.value.validate((valid: any) => {
@@ -236,9 +203,9 @@ export default defineComponent({
}
});
}
};
};
const run = async (script: any) => {
const run = async (script: any) => {
const noResult = script.type == enums.scriptTypeEnum['NO_RESULT'].value;
// 如果脚本类型为有结果类型,则显示结果信息
if (script.type == enums.scriptTypeEnum['RESULT'].value || noResult) {
@@ -264,15 +231,15 @@ export default defineComponent({
}
state.terminalDialog.cmd = script;
state.terminalDialog.visible = true;
state.terminalDialog.machineId = props.machineId;
state.terminalDialog.machineId = props.machineId as any;
return;
}
};
};
/**
/**
* 解析 {{.param}} 形式模板字符串
*/
function templateResolve(template: string, param: any) {
function templateResolve(template: string, param: any) {
return template.replace(/\{{.\w+\}}/g, (word) => {
const key = word.substring(3, word.length - 2);
const value = param[key];
@@ -281,26 +248,26 @@ export default defineComponent({
}
return '';
});
}
}
const closeTermnial = () => {
const closeTermnial = () => {
state.terminalDialog.visible = false;
state.terminalDialog.machineId = 0;
};
};
/**
/**
* 选择数据
*/
const choose = (item: any) => {
const choose = (item: any) => {
if (!item) {
return;
}
state.currentId = item.id;
state.currentData = item;
};
};
const editScript = (data: any) => {
state.editDialog.machineId = props.machineId;
const editScript = (data: any) => {
state.editDialog.machineId = props.machineId as any;
state.editDialog.data = data;
if (data) {
state.editDialog.title = '查看编辑脚本';
@@ -308,13 +275,13 @@ export default defineComponent({
state.editDialog.title = '新增脚本';
}
state.editDialog.visible = true;
};
};
const submitSuccess = () => {
const submitSuccess = () => {
getScripts();
};
};
const deleteRow = (row: any) => {
const deleteRow = (row: any) => {
ElMessageBox.confirm(`此操作将删除 [${row.name}], 是否继续?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
@@ -330,36 +297,18 @@ export default defineComponent({
});
// 删除配置文件
});
};
};
/**
/**
* 关闭取消按钮触发的事件
*/
const handleClose = () => {
context.emit('update:visible', false);
context.emit('update:machineId', null);
context.emit('cancel');
const handleClose = () => {
emit('update:visible', false);
emit('update:machineId', null);
emit('cancel');
state.scriptTable = [];
state.scriptParamsDialog.paramsFormItem = [];
};
return {
...toRefs(state),
paramsForm,
enums,
getScripts,
handlePageChange,
runScript,
hasParamsRun,
closeTermnial,
choose,
editScript,
submitSuccess,
deleteRow,
handleClose,
};
},
});
};
</script>
<style lang="sass">
</style>

View File

@@ -2,64 +2,66 @@
<div :style="{ height: height }" id="xterm" class="xterm" />
</template>
<script lang="ts">
<script lang="ts" setup>
import 'xterm/css/xterm.css';
import { Terminal } from 'xterm';
import { FitAddon } from 'xterm-addon-fit';
import { getSession } from '@/common/utils/storage.ts';
import config from '@/common/config';
import { useStore } from '@/store/index.ts';
import { nextTick, toRefs, watch, computed, reactive, defineComponent, onMounted, onBeforeUnmount } from 'vue';
import { nextTick, toRefs, watch, computed, reactive, onMounted, onBeforeUnmount } from 'vue';
export default defineComponent({
name: 'SshTerminal',
props: {
const props = defineProps({
machineId: { type: Number },
cmd: { type: String },
height: { type: String },
},
setup(props: any) {
const state = reactive({
})
const state = reactive({
machineId: 0,
cmd: '',
height: '',
term: null as any,
socket: null as any,
});
});
const resize = 1;
const data = 2;
const ping = 3;
const {
height,
} = toRefs(state)
watch(props, (newValue) => {
const resize = 1;
const data = 2;
const ping = 3;
watch(props, (newValue: any) => {
state.machineId = newValue.machineId;
state.cmd = newValue.cmd;
state.height = newValue.height;
});
});
onMounted(() => {
state.machineId = props.machineId;
state.height = props.height;
state.cmd = props.cmd;
});
onMounted(() => {
state.machineId = props.machineId as any;
state.height = props.height as any;
state.cmd = props.cmd as any;
});
onBeforeUnmount(() => {
onBeforeUnmount(() => {
closeAll();
});
});
const store = useStore();
const store = useStore();
// 获取布局配置信息
const getThemeConfig: any = computed(() => {
// 获取布局配置信息
const getThemeConfig: any = computed(() => {
return store.state.themeConfig.themeConfig;
});
});
nextTick(() => {
nextTick(() => {
initXterm();
initSocket();
});
});
function initXterm() {
function initXterm() {
const term: any = new Terminal({
fontSize: getThemeConfig.value.terminalFontSize || 15,
fontWeight: getThemeConfig.value.terminalFontWeight || 'normal',
@@ -115,13 +117,12 @@ export default defineComponent({
term.onData((key: any) => {
sendCmd(key);
});
}
}
let pingInterval: any;
function initSocket() {
let pingInterval: any;
function initSocket() {
state.socket = new WebSocket(
`${config.baseWsUrl}/machines/${state.machineId}/terminal?token=${getSession('token')}&cols=${state.term.cols}&rows=${
state.term.rows
`${config.baseWsUrl}/machines/${state.machineId}/terminal?token=${getSession('token')}&cols=${state.term.cols}&rows=${state.term.rows
}`
);
@@ -156,42 +157,36 @@ export default defineComponent({
// 监听socket消息
state.socket.onmessage = getMessage;
}
}
function getMessage(msg: any) {
function getMessage(msg: any) {
// msg.data是真正后端返回的数据
state.term.write(msg.data);
}
}
function send(msg: any) {
function send(msg: any) {
state.socket.send(JSON.stringify(msg));
}
}
function sendCmd(key: any) {
function sendCmd(key: any) {
send({
type: data,
msg: key,
});
}
}
function close() {
function close() {
if (state.socket) {
state.socket.close();
console.log('socket关闭');
}
}
}
function closeAll() {
function closeAll() {
close();
if (state.term) {
state.term.dispose();
state.term = null;
}
}
return {
...toRefs(state),
};
},
});
}
</script>

View File

@@ -4,36 +4,27 @@
</div>
</template>
<script lang="ts">
<script lang="ts" setup>
import SshTerminal from './SshTerminal.vue';
import { reactive, toRefs, defineComponent, onMounted } from 'vue';
import { reactive, toRefs, onMounted } from 'vue';
import { useRoute } from 'vue-router';
export default defineComponent({
name: 'SshTerminalPage',
components: {
SshTerminal,
},
props: {
machineId: { type: Number },
},
setup() {
const route = useRoute();
const state = reactive({
const route = useRoute();
const state = reactive({
machineId: 0,
height: 700,
});
});
onMounted(() => {
const {
machineId,
height,
} = toRefs(state)
onMounted(() => {
state.height = window.innerHeight + 5;
state.machineId = Number.parseInt(route.query.id as string);
});
return {
...toRefs(state),
};
},
});
</script>
<style lang="scss">
</style>

View File

@@ -5,14 +5,8 @@
<el-col :span="24">
<el-form class="search-form" label-position="right" :inline="true">
<el-form-item label="标签">
<el-select
@change="changeTag"
@focus="getTags"
v-model="query.tagPath"
placeholder="请选择标签"
filterable
style="width: 250px"
>
<el-select @change="changeTag" @focus="getTags" v-model="query.tagPath" placeholder="请选择标签"
filterable style="width: 250px">
<el-option v-for="item in tags" :key="item" :label="item" :value="item"> </el-option>
</el-select>
</el-form-item>
@@ -20,14 +14,17 @@
<el-select v-model="mongoId" placeholder="请选择mongo" @change="changeMongo">
<el-option v-for="item in mongoList" :key="item.id" :label="item.name" :value="item.id">
<span style="float: left">{{ item.name }}</span>
<span style="float: right; color: #8492a6; margin-left: 6px; font-size: 13px">{{ ` [${item.uri}]` }}</span>
<span style="float: right; color: #8492a6; margin-left: 6px; font-size: 13px">{{ `
[${item.uri}]`
}}</span>
</el-option>
</el-select>
</el-form-item>
<el-form-item label="库" label-width="20px">
<el-select v-model="database" placeholder="请选择库" @change="changeDatabase" filterable>
<el-option v-for="item in databases" :key="item.Name" :label="item.Name" :value="item.Name">
<el-option v-for="item in databases" :key="item.Name" :label="item.Name"
:value="item.Name">
<span style="float: left">{{ item.Name }}</span>
<span style="float: right; color: #8492a6; margin-left: 4px; font-size: 13px">{{
` [${formatByteSize(item.SizeOnDisk)}]`
@@ -38,7 +35,8 @@
<el-form-item label="集合" label-width="40px">
<el-select v-model="collection" placeholder="请选择集合" @change="changeCollection" filterable>
<el-option v-for="item in collections" :key="item" :label="item" :value="item"> </el-option>
<el-option v-for="item in collections" :key="item" :label="item" :value="item">
</el-option>
</el-select>
</el-form-item>
</el-form>
@@ -47,19 +45,18 @@
</div>
<el-container id="data-exec" style="border: 1px solid #eee; margin-top: 1px">
<el-tabs @tab-remove="removeDataTab" @tab-click="onDataTabClick" style="width: 100%; margin-left: 5px" v-model="activeName">
<el-tabs @tab-remove="removeDataTab" @tab-click="onDataTabClick" style="width: 100%; margin-left: 5px"
v-model="activeName">
<el-tab-pane closable v-for="dt in dataTabs" :key="dt.name" :label="dt.name" :name="dt.name">
<el-row v-if="mongoId">
<el-link @click="findCommand(activeName)" icon="refresh" :underline="false" class="ml5"></el-link>
<el-link @click="showInsertDocDialog" class="ml5" type="primary" icon="plus" :underline="false"></el-link>
<el-link @click="findCommand(activeName)" icon="refresh" :underline="false" class="ml5">
</el-link>
<el-link @click="showInsertDocDialog" class="ml5" type="primary" icon="plus" :underline="false">
</el-link>
</el-row>
<el-row class="mt5 mb5">
<el-input
ref="findParamInputRef"
v-model="dt.findParamStr"
placeholder="点击输入相应查询条件"
@focus="showFindDialog(dt.name)"
>
<el-input ref="findParamInputRef" v-model="dt.findParamStr" placeholder="点击输入相应查询条件"
@focus="showFindDialog(dt.name)">
<template #prepend>查询参数</template>
</el-input>
</el-row>
@@ -69,17 +66,20 @@
<el-input type="textarea" v-model="item.value" :rows="12" />
<div style="padding: 3px; float: right" class="mr5 mongo-doc-btns">
<div>
<el-link @click="onJsonEditor(item)" :underline="false" type="success" icon="MagicStick"></el-link>
<el-link @click="onJsonEditor(item)" :underline="false" type="success"
icon="MagicStick"></el-link>
<el-divider direction="vertical" border-style="dashed" />
<el-link @click="onSaveDoc(item.value)" :underline="false" type="warning" icon="DocumentChecked"></el-link>
<el-link @click="onSaveDoc(item.value)" :underline="false" type="warning"
icon="DocumentChecked"></el-link>
<el-divider direction="vertical" border-style="dashed" />
<el-popconfirm @confirm="onDeleteDoc(item.value)" title="确定删除该文档?">
<template #reference>
<el-link :underline="false" type="danger" icon="DocumentDelete"></el-link>
<el-link :underline="false" type="danger" icon="DocumentDelete">
</el-link>
</template>
</el-popconfirm>
</div>
@@ -94,10 +94,12 @@
<el-dialog width="600px" title="find参数" v-model="findDialog.visible">
<el-form label-width="70px">
<el-form-item label="filter">
<el-input v-model="findDialog.findParam.filter" type="textarea" :rows="6" clearable auto-complete="off"></el-input>
<el-input v-model="findDialog.findParam.filter" type="textarea" :rows="6" clearable
auto-complete="off"></el-input>
</el-form-item>
<el-form-item label="sort">
<el-input v-model="findDialog.findParam.sort" type="textarea" :rows="3" clearable auto-complete="off"></el-input>
<el-input v-model="findDialog.findParam.sort" type="textarea" :rows="3" clearable
auto-complete="off"></el-input>
</el-form-item>
<el-form-item label="limit">
<el-input v-model.number="findDialog.findParam.limit" type="number" auto-complete="off"></el-input>
@@ -114,7 +116,8 @@
</template>
</el-dialog>
<el-dialog width="800px" :title="`新增'${activeName}'集合文档`" v-model="insertDocDialog.visible" :close-on-click-modal="false">
<el-dialog width="800px" :title="`新增'${activeName}'集合文档`" v-model="insertDocDialog.visible"
:close-on-click-modal="false">
<json-edit currentMode="code" v-model="insertDocDialog.doc" />
<template #footer>
<div>
@@ -124,7 +127,8 @@
</template>
</el-dialog>
<el-dialog width="70%" title="json编辑器" v-model="jsoneditorDialog.visible" @close="onCloseJsonEditDialog" :close-on-click-modal="false">
<el-dialog width="70%" title="json编辑器" v-model="jsoneditorDialog.visible" @close="onCloseJsonEditDialog"
:close-on-click-modal="false">
<json-edit v-model="jsoneditorDialog.doc" />
</el-dialog>
@@ -132,9 +136,9 @@
</div>
</template>
<script lang="ts">
<script lang="ts" setup>
import { mongoApi } from './api';
import {toRefs, ref, reactive, defineComponent, watch} from 'vue';
import { toRefs, ref, reactive, watch } from 'vue';
import { ElMessage } from 'element-plus';
import { isTrue, notBlank, notNull } from '@/common/assert';
@@ -143,16 +147,9 @@ import JsonEdit from '@/components/jsonedit/index.vue';
import { tagApi } from '../tag/api.ts';
import { useStore } from '@/store/index.ts';
export default defineComponent({
name: 'MongoDataOp',
components: {
JsonEdit,
},
setup() {
const store = useStore();
const findParamInputRef: any = ref(null);
const state = reactive({
loading: false,
const store = useStore();
const findParamInputRef: any = ref(null);
const state = reactive({
tags: [],
mongoList: [] as any,
query: {
@@ -183,15 +180,31 @@ export default defineComponent({
doc: '',
item: {} as any,
},
});
});
const searchMongo = async () => {
const {
tags,
mongoList,
query,
mongoId,
database,
collection,
activeName,
databases,
collections,
dataTabs,
findDialog,
insertDocDialog,
jsoneditorDialog,
} = toRefs(state)
const searchMongo = async () => {
notNull(state.query.tagPath, '请先选择标签');
const res = await mongoApi.mongoList.request(state.query);
state.mongoList = res.list;
};
};
const changeTag = (tagPath: string) => {
const changeTag = (tagPath: string) => {
state.databases = [];
state.collections = [];
state.mongoId = null;
@@ -201,36 +214,36 @@ export default defineComponent({
if (tagPath != null) {
searchMongo();
}
};
};
const getTags = async () => {
const getTags = async () => {
state.tags = await tagApi.getAccountTags.request(null);
};
};
const changeMongo = () => {
const changeMongo = () => {
state.databases = [];
state.collections = [];
state.dataTabs = {};
getDatabases();
};
};
const getDatabases = async () => {
const getDatabases = async () => {
const res = await mongoApi.databases.request({ id: state.mongoId });
state.databases = res.Databases;
};
};
const changeDatabase = () => {
const changeDatabase = () => {
state.collections = [];
state.collection = '';
state.dataTabs = {};
getCollections();
};
};
const getCollections = async () => {
const getCollections = async () => {
state.collections = await mongoApi.collections.request({ id: state.mongoId, database: state.database });
};
};
const changeCollection = () => {
const changeCollection = () => {
const collection = state.collection;
let dataTab = state.dataTabs[collection];
if (!dataTab) {
@@ -251,9 +264,9 @@ export default defineComponent({
}
state.activeName = collection;
findCommand(collection);
};
};
const showFindDialog = (collection: string) => {
const showFindDialog = (collection: string) => {
// 获取当前tab的索引位置将其输入框失去焦点防止输入以及重复获取焦点
const dataTabNames = Object.keys(state.dataTabs);
for (let i = 0; i < dataTabNames.length; i++) {
@@ -264,16 +277,16 @@ export default defineComponent({
state.findDialog.findParam = state.dataTabs[collection].findParam;
state.findDialog.visible = true;
};
};
const confirmFindDialog = () => {
const confirmFindDialog = () => {
state.dataTabs[state.activeName].findParam = state.findDialog.findParam;
state.dataTabs[state.activeName].findParamStr = JSON.stringify(state.findDialog.findParam);
state.findDialog.visible = false;
findCommand(state.activeName);
};
};
const findCommand = async (collection: string) => {
const findCommand = async (collection: string) => {
const dataTab = state.dataTabs[collection];
const findParma = dataTab.findParam;
let filter, sort;
@@ -294,12 +307,12 @@ export default defineComponent({
skip: findParma.skip || 0,
});
state.dataTabs[collection].datas = wrapDatas(datas);
};
};
/**
/**
* 包装mongo查询回来的对象即将其都转为json字符串并用value属性值描述方便显示
*/
const wrapDatas = (datas: any) => {
const wrapDatas = (datas: any) => {
const wrapDatas = [] as any;
if (!datas) {
return wrapDatas;
@@ -308,9 +321,9 @@ export default defineComponent({
wrapDatas.push({ value: JSON.stringify(data, null, 4) });
}
return wrapDatas;
};
};
const showInsertDocDialog = () => {
const showInsertDocDialog = () => {
// tab数据中的第一个文档因为该集合的文档都类似故使用第一个文档赋值至需要新增的文档输入框方便直接修改新增
const datasFirstDoc = state.dataTabs[state.activeName].datas[0];
let doc = '';
@@ -322,9 +335,9 @@ export default defineComponent({
}
state.insertDocDialog.doc = doc;
state.insertDocDialog.visible = true;
};
};
const onInsertDoc = async () => {
const onInsertDoc = async () => {
let docObj;
try {
docObj = JSON.parse(state.insertDocDialog.doc);
@@ -341,19 +354,19 @@ export default defineComponent({
ElMessage.success('新增成功');
findCommand(state.activeName);
state.insertDocDialog.visible = false;
};
};
const onJsonEditor = (item: any) => {
const onJsonEditor = (item: any) => {
state.jsoneditorDialog.item = item;
state.jsoneditorDialog.doc = item.value;
state.jsoneditorDialog.visible = true;
};
};
const onCloseJsonEditDialog = () => {
const onCloseJsonEditDialog = () => {
state.jsoneditorDialog.item.value = JSON.stringify(JSON.parse(state.jsoneditorDialog.doc), null, 4);
};
};
const onSaveDoc = async (doc: string) => {
const onSaveDoc = async (doc: string) => {
const docObj = parseDocJsonString(doc);
const id = docObj._id;
notBlank(id, '文档的_id属性不存在');
@@ -367,9 +380,9 @@ export default defineComponent({
});
isTrue(res.ModifiedCount == 1, '修改失败');
ElMessage.success('保存成功');
};
};
const onDeleteDoc = async (doc: string) => {
const onDeleteDoc = async (doc: string) => {
const docObj = parseDocJsonString(doc);
const id = docObj._id;
notBlank(id, '文档的_id属性不存在');
@@ -382,30 +395,30 @@ export default defineComponent({
isTrue(res.DeletedCount == 1, '删除失败');
ElMessage.success('删除成功');
findCommand(state.activeName);
};
};
/**
/**
* 将json字符串解析为json对象
*/
const parseDocJsonString = (doc: string) => {
const parseDocJsonString = (doc: string) => {
try {
return JSON.parse(doc);
} catch (e) {
ElMessage.error('文档内容解析为json对象失败');
throw e;
}
};
};
/**
/**
* 数据tab点击
*/
const onDataTabClick = (tab: any) => {
const onDataTabClick = (tab: any) => {
const name = tab.props.name;
// 修改选择框绑定的表信息
state.collection = name;
};
};
const removeDataTab = (targetName: string) => {
const removeDataTab = (targetName: string) => {
const tabNames = Object.keys(state.dataTabs);
let activeName = state.activeName;
tabNames.forEach((name, index) => {
@@ -425,57 +438,33 @@ export default defineComponent({
}
delete state.dataTabs[targetName];
};
};
// 加载选中的tagPath
const setSelects = async (mongoDbOptInfo: any) =>{
const { tagPath, dbId, db} = mongoDbOptInfo.dbOptInfo;
// 加载选中的tagPath
const setSelects = async (mongoDbOptInfo: any) => {
const { tagPath, dbId, db } = mongoDbOptInfo.dbOptInfo;
state.query.tagPath = tagPath
await searchMongo();
state.mongoId = dbId
await getDatabases();
state.database = db
await getCollections();
if(state.collection){
if (state.collection) {
state.collection = ''
state.dataTabs = {}
}
}
}
// 判断如果有数据则加载下拉选项
let mongoDbOptInfo = store.state.mongoDbOptInfo
if(mongoDbOptInfo.dbOptInfo.tagPath){
// 判断如果有数据则加载下拉选项
let mongoDbOptInfo = store.state.mongoDbOptInfo
if (mongoDbOptInfo.dbOptInfo.tagPath) {
setSelects(mongoDbOptInfo)
}
}
// 监听选中操作的db变化并加载下拉选项
watch(store.state.mongoDbOptInfo,async (newValue) => {
// 监听选中操作的db变化并加载下拉选项
watch(store.state.mongoDbOptInfo, async (newValue) => {
await setSelects(newValue)
})
return {
...toRefs(state),
findParamInputRef,
getTags,
changeTag,
changeMongo,
changeDatabase,
changeCollection,
onDataTabClick,
removeDataTab,
showFindDialog,
confirmFindDialog,
findCommand,
showInsertDocDialog,
onInsertDoc,
onSaveDoc,
onDeleteDoc,
onJsonEditor,
onCloseJsonEditDialog,
formatByteSize,
};
},
});
})
</script>
<style>

View File

@@ -1,6 +1,7 @@
<template>
<div>
<el-dialog :title="title" v-model="dialogVisible" :before-close="cancel" :close-on-click-modal="false" width="38%" :destroy-on-close="true">
<el-dialog :title="title" v-model="dialogVisible" :before-close="cancel" :close-on-click-modal="false"
width="38%" :destroy-on-close="true">
<el-form :model="form" ref="mongoForm" :rules="rules" label-width="85px">
<el-form-item prop="tagId" label="标签:" required>
<tag-select v-model:tag-id="form.tagId" v-model:tag-path="form.tagPath" style="width: 100%" />
@@ -10,28 +11,20 @@
<el-input v-model.trim="form.name" placeholder="请输入名称" auto-complete="off"></el-input>
</el-form-item>
<el-form-item prop="uri" label="uri" required>
<el-input
type="textarea"
:rows="2"
v-model.trim="form.uri"
placeholder="形如 mongodb://username:password@host1:port1"
auto-complete="off"
></el-input>
<el-input type="textarea" :rows="2" v-model.trim="form.uri"
placeholder="形如 mongodb://username:password@host1:port1" auto-complete="off"></el-input>
</el-form-item>
<el-form-item prop="enableSshTunnel" label="SSH隧道:">
<el-col :span="3">
<el-checkbox @change="getSshTunnelMachines" v-model="form.enableSshTunnel" :true-label="1" :false-label="-1"></el-checkbox>
<el-checkbox @change="getSshTunnelMachines" v-model="form.enableSshTunnel" :true-label="1"
:false-label="-1"></el-checkbox>
</el-col>
<el-col :span="2" v-if="form.enableSshTunnel == 1"> 机器: </el-col>
<el-col :span="19" v-if="form.enableSshTunnel == 1">
<el-select style="width: 100%" v-model="form.sshTunnelMachineId" placeholder="请选择SSH隧道机器">
<el-option
v-for="item in sshTunnelMachineList"
:key="item.id"
:label="`${item.ip}:${item.port} [${item.name}]`"
:value="item.id"
>
<el-option v-for="item in sshTunnelMachineList" :key="item.id"
:label="`${item.ip}:${item.port} [${item.name}]`" :value="item.id">
</el-option>
</el-select>
</el-col>
@@ -48,19 +41,14 @@
</div>
</template>
<script lang="ts">
import { toRefs, reactive, watch, defineComponent, ref } from 'vue';
<script lang="ts" setup>
import { toRefs, reactive, watch, ref } from 'vue';
import { mongoApi } from './api';
import { machineApi } from '../machine/api.ts';
import { ElMessage } from 'element-plus';
import TagSelect from '../component/TagSelect.vue';
export default defineComponent({
name: 'MongoEdit',
components: {
TagSelect,
},
props: {
const props = defineProps({
visible: {
type: Boolean,
},
@@ -70,23 +58,12 @@ export default defineComponent({
title: {
type: String,
},
},
setup(props: any, { emit }) {
const mongoForm: any = ref(null);
const state = reactive({
dialogVisible: false,
sshTunnelMachineList: [] as any,
form: {
id: null,
name: null,
uri: null,
enableSshTunnel: -1,
sshTunnelMachineId: null,
tagId: null as any,
tagPath: null as any,
},
btnLoading: false,
rules: {
})
//定义事件
const emit = defineEmits(['update:visible', 'cancel', 'val-change'])
const rules = {
tagId: [
{
required: true,
@@ -108,10 +85,32 @@ export default defineComponent({
trigger: ['change', 'blur'],
},
],
},
});
}
watch(props, async (newValue) => {
const mongoForm: any = ref(null);
const state = reactive({
dialogVisible: false,
sshTunnelMachineList: [] as any,
form: {
id: null,
name: null,
uri: null,
enableSshTunnel: -1,
sshTunnelMachineId: null,
tagId: null as any,
tagPath: null as any,
},
btnLoading: false,
});
const {
dialogVisible,
sshTunnelMachineList,
form,
btnLoading,
} = toRefs(state)
watch(props, async (newValue: any) => {
state.dialogVisible = newValue.visible;
if (!state.dialogVisible) {
return;
@@ -122,16 +121,16 @@ export default defineComponent({
state.form = { db: 0 } as any;
}
getSshTunnelMachines();
});
});
const getSshTunnelMachines = async () => {
const getSshTunnelMachines = async () => {
if (state.form.enableSshTunnel == 1 && state.sshTunnelMachineList.length == 0) {
const res = await machineApi.list.request({ pageNum: 1, pageSize: 100 });
state.sshTunnelMachineList = res.list;
}
};
};
const btnOk = async () => {
const btnOk = async () => {
mongoForm.value.validate(async (valid: boolean) => {
if (valid) {
const reqForm = { ...state.form };
@@ -151,22 +150,13 @@ export default defineComponent({
return false;
}
});
};
};
const cancel = () => {
const cancel = () => {
emit('update:visible', false);
emit('cancel');
};
return {
...toRefs(state),
mongoForm,
getSshTunnelMachines,
btnOk,
cancel,
};
},
});
};
</script>
<style lang="scss">
</style>

View File

@@ -2,8 +2,10 @@
<div>
<el-card>
<el-button type="primary" icon="plus" @click="editMongo(true)" plain>添加</el-button>
<el-button type="primary" icon="edit" :disabled="currentId == null" @click="editMongo(false)" plain>编辑</el-button>
<el-button type="danger" icon="delete" :disabled="currentId == null" @click="deleteMongo" plain>删除</el-button>
<el-button type="primary" icon="edit" :disabled="currentId == null" @click="editMongo(false)" plain>编辑
</el-button>
<el-button type="danger" icon="delete" :disabled="currentId == null" @click="deleteMongo" plain>删除
</el-button>
<div style="float: right">
<el-select @focus="getTags" v-model="query.tagPath" placeholder="请选择标签" filterable clearable>
<el-option v-for="item in tags" :key="item" :label="item" :value="item"> </el-option>
@@ -34,19 +36,15 @@
<el-table-column label="操作" width>
<template #default="scope">
<el-link type="primary" @click="showDatabases(scope.row.id, scope.row)" plain size="small" :underline="false">数据库</el-link>
<el-link type="primary" @click="showDatabases(scope.row.id, scope.row)" plain size="small"
:underline="false">数据库</el-link>
</template>
</el-table-column>
</el-table>
<el-row style="margin-top: 20px" type="flex" justify="end">
<el-pagination
style="text-align: right"
@current-change="handlePageChange"
:total="total"
layout="prev, pager, next, total, jumper"
v-model:current-page="query.pageNum"
:page-size="query.pageSize"
></el-pagination>
<el-pagination style="text-align: right" @current-change="handlePageChange" :total="total"
layout="prev, pager, next, total, jumper" v-model:current-page="query.pageNum"
:page-size="query.pageSize"></el-pagination>
</el-row>
</el-card>
@@ -62,16 +60,20 @@
<el-table-column min-width="150" label="操作">
<template #default="scope">
<el-link type="success" @click="showDatabaseStats(scope.row.Name)" plain size="small" :underline="false">stats</el-link>
<el-link type="success" @click="showDatabaseStats(scope.row.Name)" plain size="small"
:underline="false">stats</el-link>
<el-divider direction="vertical" border-style="dashed" />
<el-link type="primary" @click="showCollections(scope.row.Name)" plain size="small" :underline="false">集合</el-link>
<el-link type="primary" @click="showCollections(scope.row.Name)" plain size="small"
:underline="false">集合</el-link>
<el-divider direction="vertical" border-style="dashed" />
<el-link type="primary" @click="openDataOps(scope.row)" plain size="small" :underline="false">数据操作</el-link>
<el-link type="primary" @click="openDataOps(scope.row)" plain size="small" :underline="false">
数据操作</el-link>
</template>
</el-table-column>
</el-table>
<el-dialog width="700px" :title="databaseDialog.statsDialog.title" v-model="databaseDialog.statsDialog.visible">
<el-dialog width="700px" :title="databaseDialog.statsDialog.title"
v-model="databaseDialog.statsDialog.visible">
<el-descriptions title="库状态信息" :column="3" border size="small">
<el-descriptions-item label="db" label-align="right" align="center">
{{ databaseDialog.statsDialog.data.db }}
@@ -120,7 +122,8 @@
<el-table-column prop="name" label="名称" show-overflow-tooltip> </el-table-column>
<el-table-column min-width="80" label="操作">
<template #default="scope">
<el-link type="success" @click="showCollectionStats(scope.row.name)" plain size="small" :underline="false">stats</el-link>
<el-link type="success" @click="showCollectionStats(scope.row.name)" plain size="small"
:underline="false">stats</el-link>
<el-divider direction="vertical" border-style="dashed" />
<el-popconfirm @confirm="onDeleteCollection(scope.row.name)" title="确定删除该集合?">
<template #reference>
@@ -131,7 +134,8 @@
</el-table-column>
</el-table>
<el-dialog width="700px" :title="collectionsDialog.statsDialog.title" v-model="collectionsDialog.statsDialog.visible">
<el-dialog width="700px" :title="collectionsDialog.statsDialog.title"
v-model="collectionsDialog.statsDialog.visible">
<el-descriptions title="集合状态信息" :column="3" border size="small">
<el-descriptions-item label="ns" label-align="right" :span="2" align="center">
{{ collectionsDialog.statsDialog.data.ns }}
@@ -179,39 +183,28 @@
</template>
</el-dialog>
<mongo-edit
@val-change="valChange"
:title="mongoEditDialog.title"
v-model:visible="mongoEditDialog.visible"
v-model:mongo="mongoEditDialog.data"
></mongo-edit>
<mongo-edit @val-change="valChange" :title="mongoEditDialog.title" v-model:visible="mongoEditDialog.visible"
v-model:mongo="mongoEditDialog.data"></mongo-edit>
</div>
</template>
<script lang="ts">
<script lang="ts" setup>
import { mongoApi } from './api';
import { toRefs, reactive, defineComponent, onMounted } from 'vue';
import { toRefs, reactive, onMounted } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import { tagApi } from '../tag/api.ts';
import MongoEdit from './MongoEdit.vue';
import { formatByteSize } from '@/common/utils/format';
import {store} from '@/store';
import { store } from '@/store';
import router from '@/router';
import { dateFormat } from '@/common/utils/date';
export default defineComponent({
name: 'MongoList',
components: {
MongoEdit,
},
setup() {
const state = reactive({
const state = reactive({
tags: [],
dbOps: {
dbId: 0,
db: '',
},
projects: [],
list: [],
total: 0,
currentId: null,
@@ -253,26 +246,38 @@ export default defineComponent({
name: '',
},
},
});
});
onMounted(async () => {
const {
tags,
list,
total,
currentId,
query,
mongoEditDialog,
databaseDialog,
collectionsDialog,
createCollectionDialog,
} = toRefs(state)
onMounted(async () => {
search();
});
});
const handlePageChange = (curPage: number) => {
const handlePageChange = (curPage: number) => {
state.query.pageNum = curPage;
search();
};
};
const choose = (item: any) => {
const choose = (item: any) => {
if (!item) {
return;
}
state.currentId = item.id;
state.currentData = item;
};
};
const showDatabases = async (id: number, row: any) => {
const showDatabases = async (id: number, row: any) => {
console.log(row)
state.query.tagPath = row.tagPath
state.dbOps.dbId = id
@@ -280,9 +285,9 @@ export default defineComponent({
state.databaseDialog.data = (await mongoApi.databases.request({ id })).Databases;
state.databaseDialog.title = `数据库列表`;
state.databaseDialog.visible = true;
};
};
const showDatabaseStats = async (dbName: string) => {
const showDatabaseStats = async (dbName: string) => {
state.databaseDialog.statsDialog.data = await mongoApi.runCommand.request({
id: state.currentId,
database: dbName,
@@ -292,29 +297,29 @@ export default defineComponent({
});
state.databaseDialog.statsDialog.title = `'${dbName}' stats`;
state.databaseDialog.statsDialog.visible = true;
};
};
const showCollections = async (database: string) => {
const showCollections = async (database: string) => {
state.collectionsDialog.database = database;
state.collectionsDialog.data = [];
setCollections(database);
state.collectionsDialog.title = `'${database}' 集合`;
state.collectionsDialog.visible = true;
};
};
const setCollections = async (database: string) => {
const setCollections = async (database: string) => {
const res = await mongoApi.collections.request({ id: state.currentId, database });
const collections = [] as any;
for (let r of res) {
collections.push({ name: r });
}
state.collectionsDialog.data = collections;
};
};
/**
/**
* 显示集合状态
*/
const showCollectionStats = async (collection: string) => {
const showCollectionStats = async (collection: string) => {
state.collectionsDialog.statsDialog.data = await mongoApi.runCommand.request({
id: state.currentId,
database: state.collectionsDialog.database,
@@ -324,12 +329,12 @@ export default defineComponent({
});
state.collectionsDialog.statsDialog.title = `'${collection}' stats`;
state.collectionsDialog.statsDialog.visible = true;
};
};
/**
/**
* 删除集合
*/
const onDeleteCollection = async (collection: string) => {
const onDeleteCollection = async (collection: string) => {
await mongoApi.runCommand.request({
id: state.currentId,
database: state.collectionsDialog.database,
@@ -339,13 +344,13 @@ export default defineComponent({
});
ElMessage.success('集合删除成功');
setCollections(state.collectionsDialog.database);
};
};
const showCreateCollectionDialog = () => {
const showCreateCollectionDialog = () => {
state.createCollectionDialog.visible = true;
};
};
const onCreateCollection = async () => {
const onCreateCollection = async () => {
const form = state.createCollectionDialog.form;
await mongoApi.runCommand.request({
id: state.currentId,
@@ -358,9 +363,9 @@ export default defineComponent({
state.createCollectionDialog.visible = false;
state.createCollectionDialog.form = {} as any;
setCollections(state.collectionsDialog.database);
};
};
const deleteMongo = async () => {
const deleteMongo = async () => {
try {
await ElMessageBox.confirm(`确定删除该mongo?`, '提示', {
confirmButtonText: '确定',
@@ -372,20 +377,20 @@ export default defineComponent({
state.currentData = null;
state.currentId = null;
search();
} catch (err) {}
};
} catch (err) { }
};
const search = async () => {
const search = async () => {
const res = await mongoApi.mongoList.request(state.query);
state.list = res.list;
state.total = res.total;
};
};
const getTags = async () => {
const getTags = async () => {
state.tags = await tagApi.getAccountTags.request(null);
};
};
const editMongo = async (isAdd = false) => {
const editMongo = async (isAdd = false) => {
if (isAdd) {
state.mongoEditDialog.data = null;
state.mongoEditDialog.title = '新增mongo';
@@ -394,15 +399,15 @@ export default defineComponent({
state.mongoEditDialog.title = '修改mongo';
}
state.mongoEditDialog.visible = true;
};
};
const valChange = () => {
const valChange = () => {
state.currentId = null;
state.currentData = null;
search();
};
};
const openDataOps = ( row: any) => {
const openDataOps = (row: any) => {
state.dbOps.db = row.Name
debugger
@@ -413,35 +418,14 @@ export default defineComponent({
}
// 判断db是否发生改变
let oldDb = store.state.mongoDbOptInfo.dbOptInfo.db;
if(oldDb !== row.Name){
if (oldDb !== row.Name) {
store.dispatch('mongoDbOptInfo/setMongoDbOptInfo', data);
}
router.push({name: 'MongoDataOp'});
}
router.push({ name: 'MongoDataOp' });
}
return {
...toRefs(state),
dateFormat,
getTags,
search,
handlePageChange,
choose,
showDatabases,
showDatabaseStats,
showCollections,
showCollectionStats,
onDeleteCollection,
showCreateCollectionDialog,
onCreateCollection,
formatByteSize,
deleteMongo,
editMongo,
valChange,
openDataOps,
};
},
});
</script>
<style>
</style>

View File

@@ -6,37 +6,23 @@
<el-col :span="24">
<el-form class="search-form" label-position="right" :inline="true">
<el-form-item label="标签">
<el-select
@change="changeTag"
@focus="getTags"
v-model="query.tagPath"
placeholder="请选择标签"
filterable
style="width: 250px"
>
<el-option v-for="item in tags" :key="item" :label="item" :value="item"> </el-option>
<el-select @change="changeTag" @focus="getTags" v-model="query.tagPath"
placeholder="请选择标签" filterable style="width: 250px">
<el-option v-for="item in tags" :key="item" :label="item" :value="item">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="redis" label-width="40px">
<el-select
v-model="scanParam.id"
placeholder="请选择redis"
@change="changeRedis"
@clear="clearRedis"
clearable
style="width: 250px"
>
<el-option
v-for="item in redisList"
:key="item.id"
:label="`${item.name ? item.name : ''} [${item.host}]`"
:value="item.id"
>
<el-select v-model="scanParam.id" placeholder="请选择redis" @change="changeRedis"
@clear="clearRedis" clearable style="width: 250px">
<el-option v-for="item in redisList" :key="item.id"
:label="`${item.name ? item.name : ''} [${item.host}]`" :value="item.id">
</el-option>
</el-select>
</el-form-item>
<el-form-item label="库" label-width="20px">
<el-select v-model="scanParam.db" @change="changeDb" placeholder="库" style="width: 85px">
<el-select v-model="scanParam.db" @change="changeDb" placeholder="库"
style="width: 85px">
<el-option v-for="db in dbList" :key="db" :label="db" :value="db"> </el-option>
</el-select>
</el-form-item>
@@ -45,16 +31,12 @@
<el-col class="mt10">
<el-form class="search-form" label-position="right" :inline="true" label-width="60px">
<el-form-item label="key" label-width="40px">
<el-input
placeholder="match 支持*模糊key"
style="width: 250px"
v-model="scanParam.match"
@clear="clear()"
clearable
></el-input>
<el-input placeholder="match 支持*模糊key" style="width: 250px" v-model="scanParam.match"
@clear="clear()" clearable></el-input>
</el-form-item>
<el-form-item label="count" label-width="40px">
<el-input placeholder="count" style="width: 70px" v-model.number="scanParam.count"></el-input>
<el-input placeholder="count" style="width: 70px" v-model.number="scanParam.count">
</el-input>
</el-form-item>
<el-form-item>
<el-button @click="searchKey()" type="success" icon="search" plain></el-button>
@@ -63,9 +45,12 @@
<template #reference>
<el-button type="primary" icon="plus" plain></el-button>
</template>
<el-tag @click="onAddData('string')" :color="getTypeColor('string')" style="cursor: pointer">string</el-tag>
<el-tag @click="onAddData('hash')" :color="getTypeColor('hash')" class="ml5" style="cursor: pointer">hash</el-tag>
<el-tag @click="onAddData('set')" :color="getTypeColor('set')" class="ml5" style="cursor: pointer">set</el-tag>
<el-tag @click="onAddData('string')" :color="getTypeColor('string')"
style="cursor: pointer">string</el-tag>
<el-tag @click="onAddData('hash')" :color="getTypeColor('hash')" class="ml5"
style="cursor: pointer">hash</el-tag>
<el-tag @click="onAddData('set')" :color="getTypeColor('set')" class="ml5"
style="cursor: pointer">set</el-tag>
<!-- <el-tag @click="onAddData('list')" :color="getTypeColor('list')" class="ml5" style="cursor: pointer">list</el-tag> -->
</el-popover>
</el-form-item>
@@ -91,8 +76,10 @@
</el-table-column>
<el-table-column label="操作">
<template #default="scope">
<el-button @click="getValue(scope.row)" type="success" icon="search" plain size="small">查看</el-button>
<el-button @click="del(scope.row.key)" type="danger" icon="delete" plain size="small">删除</el-button>
<el-button @click="getValue(scope.row)" type="success" icon="search" plain size="small">查看
</el-button>
<el-button @click="del(scope.row.key)" type="danger" icon="delete" plain size="small">删除
</el-button>
</template>
</el-table-column>
</el-table>
@@ -100,55 +87,27 @@
<div style="text-align: center; margin-top: 10px"></div>
<hash-value
v-model:visible="hashValueDialog.visible"
:operationType="dataEdit.operationType"
:title="dataEdit.title"
:keyInfo="dataEdit.keyInfo"
:redisId="scanParam.id"
:db="scanParam.db"
@cancel="onCancelDataEdit"
@valChange="searchKey"
/>
<hash-value v-model:visible="hashValueDialog.visible" :operationType="dataEdit.operationType"
:title="dataEdit.title" :keyInfo="dataEdit.keyInfo" :redisId="scanParam.id" :db="scanParam.db"
@cancel="onCancelDataEdit" @valChange="searchKey" />
<string-value
v-model:visible="stringValueDialog.visible"
:operationType="dataEdit.operationType"
:title="dataEdit.title"
:keyInfo="dataEdit.keyInfo"
:redisId="scanParam.id"
:db="scanParam.db"
@cancel="onCancelDataEdit"
@valChange="searchKey"
/>
<string-value v-model:visible="stringValueDialog.visible" :operationType="dataEdit.operationType"
:title="dataEdit.title" :keyInfo="dataEdit.keyInfo" :redisId="scanParam.id" :db="scanParam.db"
@cancel="onCancelDataEdit" @valChange="searchKey" />
<set-value
v-model:visible="setValueDialog.visible"
:title="dataEdit.title"
:keyInfo="dataEdit.keyInfo"
:redisId="scanParam.id"
:db="scanParam.db"
:operationType="dataEdit.operationType"
@valChange="searchKey"
@cancel="onCancelDataEdit"
/>
<set-value v-model:visible="setValueDialog.visible" :title="dataEdit.title" :keyInfo="dataEdit.keyInfo"
:redisId="scanParam.id" :db="scanParam.db" :operationType="dataEdit.operationType" @valChange="searchKey"
@cancel="onCancelDataEdit" />
<list-value
v-model:visible="listValueDialog.visible"
:title="dataEdit.title"
:keyInfo="dataEdit.keyInfo"
:redisId="scanParam.id"
:db="scanParam.db"
:operationType="dataEdit.operationType"
@valChange="searchKey"
@cancel="onCancelDataEdit"
/>
<list-value v-model:visible="listValueDialog.visible" :title="dataEdit.title" :keyInfo="dataEdit.keyInfo"
:redisId="scanParam.id" :db="scanParam.db" :operationType="dataEdit.operationType" @valChange="searchKey"
@cancel="onCancelDataEdit" />
</div>
</template>
<script lang="ts">
<script lang="ts" setup>
import { redisApi } from './api';
import { toRefs, reactive, defineComponent, watch } from 'vue';
import { toRefs, reactive, watch } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import HashValue from './HashValue.vue';
import StringValue from './StringValue.vue';
@@ -159,18 +118,8 @@ import { isTrue, notBlank, notNull } from '@/common/assert';
import { useStore } from '@/store/index.ts';
import { tagApi } from '../tag/api.ts';
export default defineComponent({
name: 'DataOperation',
components: {
StringValue,
HashValue,
SetValue,
ListValue,
},
setup() {
let store = useStore();
const state = reactive({
projectId: null,
let store = useStore();
const state = reactive({
loading: false,
tags: [],
redisList: [] as any,
@@ -179,7 +128,7 @@ export default defineComponent({
tagPath: null,
},
scanParam: {
id: null,
id: null as any,
db: '',
match: null,
count: 10,
@@ -209,42 +158,58 @@ export default defineComponent({
},
keys: [],
dbsize: 0,
});
});
const searchRedis = async () => {
const {
loading,
tags,
redisList,
dbList,
query,
scanParam,
dataEdit,
hashValueDialog,
stringValueDialog,
setValueDialog,
listValueDialog,
keys,
dbsize,
} = toRefs(state)
const searchRedis = async () => {
notBlank(state.query.tagPath, '请先选择标签');
const res = await redisApi.redisList.request(state.query);
state.redisList = res.list;
};
};
const changeTag = (tagPath: string) => {
const changeTag = (tagPath: string) => {
clearRedis();
if (tagPath != null) {
searchRedis();
}
};
};
const getTags = async () => {
const getTags = async () => {
state.tags = await tagApi.getAccountTags.request(null);
};
};
const changeRedis = (id: number) => {
const changeRedis = (id: number) => {
resetScanParam(id);
state.dbList = (state.redisList.find((x: any) => x.id == id) as any).db.split(',');
// 默认选中配置的第一个库
state.scanParam.db = state.dbList[0];
state.keys = [];
state.dbsize = 0;
};
};
const changeDb = () => {
const changeDb = () => {
resetScanParam(state.scanParam.id as any);
state.keys = [];
state.dbsize = 0;
searchKey();
};
};
const scan = async () => {
const scan = async () => {
isTrue(state.scanParam.id != null, '请先选择redis');
notBlank(state.scanParam.count, 'count不能为空');
@@ -264,30 +229,30 @@ export default defineComponent({
} finally {
state.loading = false;
}
};
};
const searchKey = async () => {
const searchKey = async () => {
state.scanParam.cursor = {};
await scan();
};
};
const clearRedis = () => {
const clearRedis = () => {
state.redisList = [];
state.scanParam.id = null;
resetScanParam();
state.scanParam.db = '';
state.keys = [];
state.dbsize = 0;
};
};
const clear = () => {
const clear = () => {
resetScanParam();
if (state.scanParam.id) {
scan();
}
};
};
const resetScanParam = (id: number = 0) => {
const resetScanParam = (id: number = 0) => {
state.scanParam.count = 10;
if (id != 0) {
const redis: any = state.redisList.find((x: any) => x.id == id);
@@ -298,9 +263,9 @@ export default defineComponent({
}
state.scanParam.match = null;
state.scanParam.cursor = {};
};
};
const getValue = async (row: any) => {
const getValue = async (row: any) => {
const type = row.type;
state.dataEdit.keyInfo.type = type;
@@ -320,9 +285,9 @@ export default defineComponent({
} else {
ElMessage.warning('暂不支持该类型');
}
};
};
const onAddData = (type: string) => {
const onAddData = (type: string) => {
notNull(state.scanParam.id, '请先选择redis');
state.dataEdit.operationType = 1;
state.dataEdit.title = '新增数据';
@@ -339,13 +304,13 @@ export default defineComponent({
} else {
ElMessage.warning('暂不支持该类型');
}
};
};
const onCancelDataEdit = () => {
const onCancelDataEdit = () => {
state.dataEdit.keyInfo = {} as any;
};
};
const del = (key: string) => {
const del = (key: string) => {
ElMessageBox.confirm(`确定删除[ ${key} ] 该key?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
@@ -363,10 +328,10 @@ export default defineComponent({
searchKey();
});
})
.catch(() => {});
};
.catch(() => { });
};
const ttlConveter = (ttl: any) => {
const ttlConveter = (ttl: any) => {
if (ttl == -1 || ttl == 0) {
return '永久';
}
@@ -400,9 +365,9 @@ export default defineComponent({
result = '' + day + 'd:' + result;
}
return result;
};
};
const getTypeColor = (type: string) => {
const getTypeColor = (type: string) => {
if (type == 'string') {
return '#E4F5EB';
}
@@ -412,10 +377,10 @@ export default defineComponent({
if (type == 'set') {
return '#A8DEE0';
}
};
};
// 加载选中的db
const setSelects = async (redisDbOptInfo: any) => {
// 加载选中的db
const setSelects = async (redisDbOptInfo: any) => {
// 设置标签路径等
const { tagPath, dbId } = redisDbOptInfo.dbOptInfo;
state.query.tagPath = tagPath;
@@ -423,39 +388,21 @@ export default defineComponent({
state.scanParam.id = dbId;
changeRedis(dbId);
changeDb();
};
};
// 判断如果有数据则加载下拉选项
let redisDbOptInfo = store.state.redisDbOptInfo;
if (redisDbOptInfo.dbOptInfo.tagPath) {
// 判断如果有数据则加载下拉选项
let redisDbOptInfo = store.state.redisDbOptInfo;
if (redisDbOptInfo.dbOptInfo.tagPath) {
setSelects(redisDbOptInfo);
}
}
// 监听选中操作的db变化并加载下拉选项
watch(store.state.redisDbOptInfo, async (newValue) => {
// 监听选中操作的db变化并加载下拉选项
watch(store.state.redisDbOptInfo, async (newValue) => {
await setSelects(newValue);
});
return {
...toRefs(state),
getTags,
changeTag,
changeRedis,
changeDb,
clearRedis,
searchKey,
scan,
clear,
getValue,
del,
ttlConveter,
getTypeColor,
onAddData,
onCancelDataEdit,
};
},
});
</script>
<style>
</style>

View File

@@ -14,14 +14,18 @@
<el-row class="mt10">
<el-form label-position="right" :inline="true">
<el-form-item label="field" label-width="40px" v-if="operationType == 2">
<el-input placeholder="支持*模糊field" style="width: 140px" v-model="scanParam.match" clearable size="small"></el-input>
<el-input placeholder="支持*模糊field" style="width: 140px" v-model="scanParam.match" clearable
size="small"></el-input>
</el-form-item>
<el-form-item label="count" v-if="operationType == 2">
<el-input placeholder="count" style="width: 62px" v-model.number="scanParam.count" size="small"></el-input>
<el-input placeholder="count" style="width: 62px" v-model.number="scanParam.count" size="small">
</el-input>
</el-form-item>
<el-form-item>
<el-button v-if="operationType == 2" @click="reHscan()" type="success" icon="search" plain size="small"></el-button>
<el-button v-if="operationType == 2" @click="hscan()" icon="bottom" plain size="small">scan</el-button>
<el-button v-if="operationType == 2" @click="reHscan()" type="success" icon="search" plain
size="small"></el-button>
<el-button v-if="operationType == 2" @click="hscan()" icon="bottom" plain size="small">scan
</el-button>
<el-button @click="onAddHashValue" icon="plus" size="small" plain>添加</el-button>
</el-form-item>
<div v-if="operationType == 2" class="mt10" style="float: right">
@@ -37,13 +41,16 @@
</el-table-column>
<el-table-column prop="value" label="value" min-width="200">
<template #default="scope">
<el-input v-model="scope.row.value" clearable type="textarea" :autosize="{ minRows: 2, maxRows: 10 }" size="small"></el-input>
<el-input v-model="scope.row.value" clearable type="textarea"
:autosize="{ minRows: 2, maxRows: 10 }" size="small"></el-input>
</template>
</el-table-column>
<el-table-column label="操作" width="120">
<template #default="scope">
<el-button v-if="operationType == 2" type="success" @click="hset(scope.row)" icon="check" size="small" plain></el-button>
<el-button type="danger" @click="hdel(scope.row.field, scope.$index)" icon="delete" size="small" plain></el-button>
<el-button v-if="operationType == 2" type="success" @click="hset(scope.row)" icon="check"
size="small" plain></el-button>
<el-button type="danger" @click="hdel(scope.row.field, scope.$index)" icon="delete" size="small"
plain></el-button>
</template>
</el-table-column>
</el-table>
@@ -56,16 +63,13 @@
</template>
</el-dialog>
</template>
<script lang="ts">
import { defineComponent, reactive, watch, toRefs } from 'vue';
<script lang="ts" setup>
import { reactive, watch, toRefs } from 'vue';
import { redisApi } from './api';
import { ElMessage, ElMessageBox } from 'element-plus';
import { isTrue, notEmpty } from '@/common/assert';
export default defineComponent({
name: 'HashValue',
components: {},
props: {
const props = defineProps({
visible: {
type: Boolean,
},
@@ -91,10 +95,11 @@ export default defineComponent({
hashValue: {
type: [Array, Object],
},
},
emits: ['valChange', 'cancel', 'update:visible'],
setup(props: any, { emit }) {
const state = reactive({
})
const emit = defineEmits(['update:visible', 'cancel', 'valChange'])
const state = reactive({
dialogVisible: false,
operationType: 1,
redisId: 0,
@@ -119,18 +124,27 @@ export default defineComponent({
value: '',
},
],
});
});
const cancel = () => {
const {
dialogVisible,
operationType,
key,
scanParam,
keySize,
hashValues,
} = toRefs(state)
const cancel = () => {
emit('update:visible', false);
emit('cancel');
setTimeout(() => {
state.hashValues = [];
state.key = {} as any;
}, 500);
};
};
watch(props, async (newValue) => {
watch(props, async (newValue: any) => {
const visible = newValue.visible;
state.redisId = newValue.redisId;
state.db = newValue.db;
@@ -138,22 +152,22 @@ export default defineComponent({
state.operationType = newValue.operationType;
if (visible && state.operationType == 2) {
state.scanParam.id = props.redisId;
state.scanParam.id = props.redisId as any;
state.scanParam.key = state.key.key;
await reHscan();
}
state.dialogVisible = visible;
});
});
const reHscan = async () => {
const reHscan = async () => {
state.scanParam.id = state.redisId;
state.scanParam.db = state.db;
state.scanParam.cursor = 0;
hscan();
};
};
const hscan = async () => {
const hscan = async () => {
const match = state.scanParam.match;
if (!match || match == '' || match == '*') {
if (state.scanParam.count > 100) {
@@ -179,9 +193,9 @@ export default defineComponent({
hashValue.push({ field: keys[nextFieldIndex++], value: keys[nextFieldIndex++] });
}
state.hashValues = hashValue;
};
};
const hdel = async (field: any, index: any) => {
const hdel = async (field: any, index: any) => {
// 如果是新增操作,则直接数组移除即可
if (state.operationType == 1) {
state.hashValues.splice(index, 1);
@@ -200,9 +214,9 @@ export default defineComponent({
});
ElMessage.success('删除成功');
reHscan();
};
};
const hset = async (row: any) => {
const hset = async (row: any) => {
await redisApi.saveHashValue.request({
id: state.redisId,
db: state.db,
@@ -216,13 +230,13 @@ export default defineComponent({
],
});
ElMessage.success('保存成功');
};
};
const onAddHashValue = () => {
const onAddHashValue = () => {
state.hashValues.unshift({ field: '', value: '' });
};
};
const saveValue = async () => {
const saveValue = async () => {
notEmpty(state.key.key, 'key不能为空');
isTrue(state.hashValues.length > 0, 'hash内容不能为空');
const sv = { value: state.hashValues, id: state.redisId, db: state.db };
@@ -232,20 +246,7 @@ export default defineComponent({
cancel();
emit('valChange');
};
return {
...toRefs(state),
reHscan,
hscan,
cancel,
hdel,
hset,
onAddHashValue,
saveValue,
};
},
});
};
</script>
<style lang="scss">
#string-value-text {

View File

@@ -145,12 +145,10 @@
</div>
</template>
<script lang="ts">
import { defineComponent, reactive, watch, toRefs } from 'vue';
<script lang="ts" setup>
import { reactive, watch, toRefs } from 'vue';
export default defineComponent({
name: 'Info',
props: {
const props = defineProps({
visible: {
type: Boolean,
},
@@ -160,30 +158,29 @@ export default defineComponent({
info: {
type: [Boolean, Object],
},
},
setup(props: any, { emit }) {
const state = reactive({
dialogVisible: false,
});
})
watch(
const emit = defineEmits(['update:visible', 'close'])
const state = reactive({
dialogVisible: false,
});
const {
dialogVisible,
} = toRefs(state)
watch(
() => props.visible,
(val) => {
state.dialogVisible = val;
}
);
);
const close = () => {
const close = () => {
emit('update:visible', false);
emit('close');
};
return {
...toRefs(state),
close,
};
},
});
};
</script>
<style>

View File

@@ -18,32 +18,22 @@
<el-table :data="value" stripe style="width: 100%">
<el-table-column prop="value" label="value" min-width="200">
<template #default="scope">
<el-input v-model="scope.row.value" clearable type="textarea" :autosize="{ minRows: 2, maxRows: 10 }" size="small"></el-input>
<el-input v-model="scope.row.value" clearable type="textarea"
:autosize="{ minRows: 2, maxRows: 10 }" size="small"></el-input>
</template>
</el-table-column>
<el-table-column label="操作" width="140">
<template #default="scope">
<el-button
v-if="operationType == 2"
type="success"
@click="lset(scope.row, scope.$index)"
icon="check"
size="small"
plain
></el-button>
<el-button v-if="operationType == 2" type="success" @click="lset(scope.row, scope.$index)"
icon="check" size="small" plain></el-button>
<!-- <el-button type="danger" @click="set.value.splice(scope.$index, 1)" icon="delete" size="small" plain></el-button> -->
</template>
</el-table-column>
</el-table>
<el-row style="margin-top: 20px" type="flex" justify="end">
<el-pagination
style="text-align: right"
:total="len"
layout="prev, pager, next, total"
@current-change="handlePageChange"
v-model:current-page="pageNum"
:page-size="pageSize"
></el-pagination>
<el-pagination style="text-align: right" :total="len" layout="prev, pager, next, total"
@current-change="handlePageChange" v-model:current-page="pageNum" :page-size="pageSize">
</el-pagination>
</el-row>
</el-form>
<!-- <template #footer>
@@ -54,16 +44,12 @@
</template> -->
</el-dialog>
</template>
<script lang="ts">
import { defineComponent, reactive, watch, toRefs } from 'vue';
<script lang="ts" setup>
import { reactive, watch, toRefs } from 'vue';
import { redisApi } from './api';
import { ElMessage } from 'element-plus';
import { isTrue, notEmpty } from '@/common/assert';
export default defineComponent({
name: 'ListValue',
components: {},
props: {
const props = defineProps({
visible: {
type: Boolean,
},
@@ -88,10 +74,11 @@ export default defineComponent({
listValue: {
type: [Array, Object],
},
},
emits: ['valChange', 'cancel', 'update:visible'],
setup(props: any, { emit }) {
const state = reactive({
})
const emit = defineEmits(['update:visible', 'cancel', 'valChange'])
const state = reactive({
dialogVisible: false,
operationType: 1,
redisId: '',
@@ -107,9 +94,19 @@ export default defineComponent({
stop: 0,
pageNum: 1,
pageSize: 10,
});
});
const cancel = () => {
const {
dialogVisible,
operationType,
key,
value,
len,
pageNum,
pageSize,
} = toRefs(state)
const cancel = () => {
emit('update:visible', false);
emit('cancel');
setTimeout(() => {
@@ -120,9 +117,9 @@ export default defineComponent({
};
state.value = [];
}, 500);
};
};
watch(props, async (newValue) => {
watch(props, async (newValue: any) => {
state.dialogVisible = newValue.visible;
state.key = newValue.key;
state.redisId = newValue.redisId;
@@ -133,9 +130,9 @@ export default defineComponent({
if (state.dialogVisible && state.operationType == 2) {
getListValue();
}
});
});
const getListValue = async () => {
const getListValue = async () => {
const pageNum = state.pageNum;
const pageSize = state.pageSize;
const res = await redisApi.getListValue.request({
@@ -151,9 +148,9 @@ export default defineComponent({
value: x,
};
});
};
};
const lset = async (row: any, rowIndex: number) => {
const lset = async (row: any, rowIndex: number) => {
await redisApi.setListValue.request({
id: state.redisId,
db: state.db,
@@ -162,39 +159,28 @@ export default defineComponent({
value: row.value,
});
ElMessage.success('数据保存成功');
};
};
const saveValue = async () => {
notEmpty(state.key.key, 'key不能为空');
isTrue(state.value.length > 0, 'list内容不能为空');
// const sv = { value: state.value.map((x) => x.value), id: state.redisId };
// Object.assign(sv, state.key);
// await redisApi.saveSetValue.request(sv);
// const saveValue = async () => {
// notEmpty(state.key.key, 'key不能为空');
// isTrue(state.value.length > 0, 'list内容不能为空');
// // const sv = { value: state.value.map((x) => x.value), id: state.redisId };
// // Object.assign(sv, state.key);
// // await redisApi.saveSetValue.request(sv);
ElMessage.success('数据保存成功');
cancel();
emit('valChange');
};
// ElMessage.success('数据保存成功');
// cancel();
// emit('valChange');
// };
const onAddListValue = () => {
state.value.unshift({ value: '' });
};
// const onAddListValue = () => {
// state.value.unshift({ value: '' });
// };
const handlePageChange = (curPage: number) => {
const handlePageChange = (curPage: number) => {
state.pageNum = curPage;
getListValue();
};
return {
...toRefs(state),
saveValue,
handlePageChange,
cancel,
lset,
onAddListValue,
};
},
});
};
</script>
<style lang="scss">
#string-value-text {

View File

@@ -1,6 +1,7 @@
<template>
<div>
<el-dialog :title="title" v-model="dialogVisible" :before-close="cancel" :close-on-click-modal="false" :destroy-on-close="true" width="38%">
<el-dialog :title="title" v-model="dialogVisible" :before-close="cancel" :close-on-click-modal="false"
:destroy-on-close="true" width="38%">
<el-form :model="form" ref="redisForm" :rules="rules" label-width="85px">
<el-form-item prop="tagId" label="标签:" required>
<tag-select v-model:tag-id="form.tagId" v-model:tag-path="form.tagPath" style="width: 100%" />
@@ -16,32 +17,25 @@
</el-select>
</el-form-item>
<el-form-item prop="host" label="host:" required>
<el-input
v-model.trim="form.host"
<el-input v-model.trim="form.host"
placeholder="请输入host:portsentinel模式为: mastername=sentinelhost:port若集群或哨兵需设多个节点可使用','分割"
auto-complete="off"
type="textarea"
></el-input>
auto-complete="off" type="textarea"></el-input>
</el-form-item>
<el-form-item prop="password" label="密码:">
<el-input
type="password"
show-password
v-model.trim="form.password"
placeholder="请输入密码, 修改操作可不填"
autocomplete="new-password"
><template v-if="form.id && form.id != 0" #suffix>
<el-popover @hide="pwd = ''" placement="right" title="原密码" :width="200" trigger="click" :content="pwd">
<el-input type="password" show-password v-model.trim="form.password" placeholder="请输入密码, 修改操作可不填"
autocomplete="new-password"><template v-if="form.id && form.id != 0" #suffix>
<el-popover @hide="pwd = ''" placement="right" title="原密码" :width="200" trigger="click"
:content="pwd">
<template #reference>
<el-link @click="getPwd" :underline="false" type="primary" class="mr5">原密码</el-link>
</template>
</el-popover>
</template></el-input
>
</template></el-input>
</el-form-item>
<el-form-item prop="db" label="库号:" required>
<el-select @change="changeDb" v-model="dbList" multiple placeholder="请选择可操作库号" style="width: 100%">
<el-option v-for="db in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]" :key="db" :label="db" :value="db" />
<el-option v-for="db in [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15]" :key="db"
:label="db" :value="db" />
</el-select>
</el-form-item>
<el-form-item prop="remark" label="备注:">
@@ -49,17 +43,14 @@
</el-form-item>
<el-form-item prop="enableSshTunnel" label="SSH隧道:">
<el-col :span="3">
<el-checkbox @change="getSshTunnelMachines" v-model="form.enableSshTunnel" :true-label="1" :false-label="-1"></el-checkbox>
<el-checkbox @change="getSshTunnelMachines" v-model="form.enableSshTunnel" :true-label="1"
:false-label="-1"></el-checkbox>
</el-col>
<el-col :span="2" v-if="form.enableSshTunnel == 1"> 机器: </el-col>
<el-col :span="19" v-if="form.enableSshTunnel == 1">
<el-select style="width: 100%" v-model="form.sshTunnelMachineId" placeholder="请选择SSH隧道机器">
<el-option
v-for="item in sshTunnelMachineList"
:key="item.id"
:label="`${item.ip}:${item.port} [${item.name}]`"
:value="item.id"
>
<el-option v-for="item in sshTunnelMachineList as any" :key="item.id"
:label="`${item.ip}:${item.port} [${item.name}]`" :value="item.id">
</el-option>
</el-select>
</el-col>
@@ -76,62 +67,29 @@
</div>
</template>
<script lang="ts">
import { toRefs, reactive, watch, defineComponent, ref } from 'vue';
<script lang="ts" setup>
import { toRefs, reactive, watch, ref } from 'vue';
import { redisApi } from './api';
import { projectApi } from '../project/api.ts';
import { machineApi } from '../machine/api.ts';
import { ElMessage } from 'element-plus';
import { RsaEncrypt } from '@/common/rsa';
import TagSelect from '../component/TagSelect.vue';
export default defineComponent({
name: 'RedisEdit',
components: {
TagSelect,
},
props: {
const props = defineProps({
visible: {
type: Boolean,
},
projects: {
type: Array,
},
redis: {
type: [Boolean, Object],
},
title: {
type: String,
},
},
setup(props: any, { emit }) {
const redisForm: any = ref(null);
const state = reactive({
dialogVisible: false,
projects: [],
envs: [],
sshTunnelMachineList: [],
form: {
id: null,
tagId: null as any,
tatPath: null as any,
name: null,
mode: 'standalone',
host: '',
password: null,
db: '',
project: null,
projectId: null,
envId: null,
env: null,
remark: '',
enableSshTunnel: null,
sshTunnelMachineId: null,
},
dbList: [0],
pwd: '',
btnLoading: false,
rules: {
})
const emit = defineEmits(['update:visible', 'val-change', 'cancel'])
const rules = {
projectId: [
{
required: true,
@@ -167,49 +125,82 @@ export default defineComponent({
trigger: ['change', 'blur'],
},
],
},
});
}
watch(props, async (newValue) => {
const redisForm: any = ref(null);
const state = reactive({
dialogVisible: false,
sshTunnelMachineList: [],
form: {
id: null,
tagId: null as any,
tagPath: null as any,
name: null,
mode: 'standalone',
host: '',
password: null,
db: '',
project: null,
projectId: null,
envId: null,
env: null,
remark: '',
enableSshTunnel: null,
sshTunnelMachineId: null,
},
dbList: [0],
pwd: '',
btnLoading: false,
});
const {
dialogVisible,
sshTunnelMachineList,
form,
dbList,
pwd,
btnLoading,
} = toRefs(state)
watch(props, async (newValue: any) => {
state.dialogVisible = newValue.visible;
if (!state.dialogVisible) {
return;
}
state.projects = newValue.projects;
if (newValue.redis) {
state.form = { ...newValue.redis };
convertDb(state.form.db);
} else {
state.envs = [];
state.form = { db: '0', enableSshTunnel: -1 } as any;
state.dbList = [];
}
getSshTunnelMachines();
});
});
const convertDb = (db: string) => {
const convertDb = (db: string) => {
state.dbList = db.split(',').map((x) => Number.parseInt(x));
};
};
/**
/**
* 改变表单中的数据库字段,方便表单错误提示。如全部删光,可提示请添加库号
*/
const changeDb = () => {
const changeDb = () => {
state.form.db = state.dbList.length == 0 ? '' : state.dbList.join(',');
};
};
const getSshTunnelMachines = async () => {
const getSshTunnelMachines = async () => {
if (state.form.enableSshTunnel == 1 && state.sshTunnelMachineList.length == 0) {
const res = await machineApi.list.request({ pageNum: 1, pageSize: 100 });
state.sshTunnelMachineList = res.list;
}
};
};
const getPwd = async () => {
const getPwd = async () => {
state.pwd = await redisApi.getRedisPwd.request({ id: state.form.id });
};
};
const btnOk = async () => {
const btnOk = async () => {
redisForm.value.validate(async (valid: boolean) => {
if (valid) {
const reqForm = { ...state.form };
@@ -233,24 +224,13 @@ export default defineComponent({
return false;
}
});
};
};
const cancel = () => {
const cancel = () => {
emit('update:visible', false);
emit('cancel');
};
return {
...toRefs(state),
redisForm,
changeDb,
getSshTunnelMachines,
getPwd,
btnOk,
cancel,
};
},
});
};
</script>
<style lang="scss">
</style>

View File

@@ -2,8 +2,10 @@
<div>
<el-card>
<el-button type="primary" icon="plus" @click="editRedis(true)" plain>添加</el-button>
<el-button type="primary" icon="edit" :disabled="currentId == null" @click="editRedis(false)" plain>编辑</el-button>
<el-button type="danger" icon="delete" :disabled="currentId == null" @click="deleteRedis" plain>删除</el-button>
<el-button type="primary" icon="edit" :disabled="currentId == null" @click="editRedis(false)" plain>编辑
</el-button>
<el-button type="danger" icon="delete" :disabled="currentId == null" @click="deleteRedis" plain>删除
</el-button>
<div style="float: right">
<el-select @focus="getTags" v-model="query.tagPath" placeholder="请选择标签" filterable clearable>
<el-option v-for="item in tags" :key="item" :label="item" :value="item"> </el-option>
@@ -31,34 +33,27 @@
<el-table-column prop="creator" label="创建人" min-width="100"></el-table-column>
<el-table-column label="更多" min-width="155" fixed="right">
<template #default="scope">
<el-link
v-if="scope.row.mode === 'standalone' || scope.row.mode === 'sentinel'"
type="primary"
@click="info(scope.row)"
:underline="false"
>单机信息</el-link>
<el-link @click="onShowClusterInfo(scope.row)" v-if="scope.row.mode === 'cluster'" type="success" :underline="false">集群信息</el-link>
<el-link v-if="scope.row.mode === 'standalone' || scope.row.mode === 'sentinel'" type="primary"
@click="showInfoDialog(scope.row)" :underline="false">单机信息</el-link>
<el-link @click="onShowClusterInfo(scope.row)" v-if="scope.row.mode === 'cluster'"
type="success" :underline="false">集群信息</el-link>
<el-divider direction="vertical" border-style="dashed" />
<el-link @click="openDataOpt(scope.row)" type="success" :underline="false">数据操作</el-link>
</template>
</el-table-column>
</el-table>
<el-row style="margin-top: 20px" type="flex" justify="end">
<el-pagination
style="text-align: right"
@current-change="handlePageChange"
:total="total"
layout="prev, pager, next, total, jumper"
v-model:current-page="query.pageNum"
:page-size="query.pageSize"
></el-pagination>
<el-pagination style="text-align: right" @current-change="handlePageChange" :total="total"
layout="prev, pager, next, total, jumper" v-model:current-page="query.pageNum"
:page-size="query.pageSize"></el-pagination>
</el-row>
</el-card>
<info v-model:visible="infoDialog.visible" :title="infoDialog.title" :info="infoDialog.info"></info>
<el-dialog width="1000px" title="集群信息" v-model="clusterInfoDialog.visible">
<el-input type="textarea" :autosize="{ minRows: 12, maxRows: 12 }" v-model="clusterInfoDialog.info"> </el-input>
<el-input type="textarea" :autosize="{ minRows: 12, maxRows: 12 }" v-model="clusterInfoDialog.info">
</el-input>
<el-divider content-position="left">节点信息</el-divider>
<el-table :data="clusterInfoDialog.nodes" stripe size="small" border>
@@ -66,44 +61,36 @@
<template #header>
nodeId
<el-tooltip class="box-item" effect="dark" content="节点id" placement="top">
<el-icon><question-filled /></el-icon>
<el-icon>
<question-filled />
</el-icon>
</el-tooltip>
</template>
</el-table-column>
<el-table-column prop="ip" label="ip" min-width="180">
<template #header>
ip
<el-tooltip
class="box-item"
effect="dark"
content="ip:port1@port2port1指redis服务器与客户端通信的端口port2则是集群内部节点间通信的端口"
placement="top"
>
<el-icon><question-filled /></el-icon>
<el-tooltip class="box-item" effect="dark"
content="ip:port1@port2port1指redis服务器与客户端通信的端口port2则是集群内部节点间通信的端口" placement="top">
<el-icon>
<question-filled />
</el-icon>
</el-tooltip>
</template>
<template #default="scope">
<el-tag
@click="info({ id: clusterInfoDialog.redisId, ip: scope.row.ip })"
effect="plain"
type="success"
size="small"
style="cursor: pointer"
>{{ scope.row.ip }}</el-tag
>
<el-tag @click="showInfoDialog({ id: clusterInfoDialog.redisId, ip: scope.row.ip })" effect="plain"
type="success" size="small" style="cursor: pointer">{{ scope.row.ip }}</el-tag>
</template>
</el-table-column>
<el-table-column prop="flags" label="flags" min-width="110"></el-table-column>
<el-table-column prop="masterSlaveRelation" label="masterSlaveRelation" min-width="300">
<template #header>
masterSlaveRelation
<el-tooltip
class="box-item"
effect="dark"
content="如果节点是slave并且已知master节点则为master节点ID否则为符号'-'"
placement="top"
>
<el-icon><question-filled /></el-icon>
<el-tooltip class="box-item" effect="dark"
content="如果节点是slave并且已知master节点则为master节点ID否则为符号'-'" placement="top">
<el-icon>
<question-filled />
</el-icon>
</el-tooltip>
</template>
</el-table-column>
@@ -120,13 +107,12 @@
<el-table-column prop="configEpoch" label="configEpoch" min-width="130">
<template #header>
configEpoch
<el-tooltip
class="box-item"
effect="dark"
<el-tooltip class="box-item" effect="dark"
content="节点的epoch值如果该节点是从节点则为其主节点的epoch值。每当节点发生失败切换时都会创建一个新的独特的递增的epoch。"
placement="top"
>
<el-icon><question-filled /></el-icon>
placement="top">
<el-icon>
<question-filled />
</el-icon>
</el-tooltip>
</template>
</el-table-column>
@@ -135,35 +121,23 @@
</el-table>
</el-dialog>
<redis-edit
@val-change="valChange"
:tags="tags"
:title="redisEditDialog.title"
v-model:visible="redisEditDialog.visible"
v-model:redis="redisEditDialog.data"
></redis-edit>
<redis-edit @val-change="valChange" :tags="tags" :title="redisEditDialog.title"
v-model:visible="redisEditDialog.visible" v-model:redis="redisEditDialog.data"></redis-edit>
</div>
</template>
<script lang="ts">
<script lang="ts" setup>
import Info from './Info.vue';
import { redisApi } from './api';
import { toRefs, reactive, defineComponent, onMounted } from 'vue';
import { toRefs, reactive, onMounted } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import { tagApi } from '../tag/api.ts';
import RedisEdit from './RedisEdit.vue';
import { dateFormat } from '@/common/utils/date';
import {store} from '@/store';
import { store } from '@/store';
import router from '@/router';
export default defineComponent({
name: 'RedisList',
components: {
Info,
RedisEdit,
},
setup() {
const state = reactive({
const state = reactive({
tags: [],
redisTable: [],
total: 0,
@@ -175,21 +149,12 @@ export default defineComponent({
pageSize: 10,
clusterId: null,
},
redisInfo: {
url: '',
},
clusterInfoDialog: {
visible: false,
redisId: 0,
info: '',
nodes: [],
},
clusters: [
{
id: 0,
name: '单机',
},
],
infoDialog: {
title: '',
visible: false,
@@ -203,29 +168,40 @@ export default defineComponent({
},
redisEditDialog: {
visible: false,
data: null,
data: null as any,
title: '新增redis',
},
});
});
onMounted(async () => {
const {
tags,
redisTable,
total,
currentId,
query,
clusterInfoDialog,
infoDialog,
redisEditDialog,
} = toRefs(state)
onMounted(async () => {
search();
});
});
const handlePageChange = (curPage: number) => {
const handlePageChange = (curPage: number) => {
state.query.pageNum = curPage;
search();
};
};
const choose = (item: any) => {
const choose = (item: any) => {
if (!item) {
return;
}
state.currentId = item.id;
state.currentData = item;
};
};
const deleteRedis = async () => {
const deleteRedis = async () => {
try {
await ElMessageBox.confirm(`确定删除该redis?`, '提示', {
confirmButtonText: '确定',
@@ -237,10 +213,10 @@ export default defineComponent({
state.currentData = null;
state.currentId = null;
search();
} catch (err) {}
};
} catch (err) { }
};
const info = async (redis: any) => {
const showInfoDialog = async (redis: any) => {
var host = redis.host;
if (redis.ip) {
host = redis.ip.split('@')[0];
@@ -249,27 +225,27 @@ export default defineComponent({
state.infoDialog.info = res;
state.infoDialog.title = `'${host}' info`;
state.infoDialog.visible = true;
};
};
const onShowClusterInfo = async (redis: any) => {
const onShowClusterInfo = async (redis: any) => {
const ci = await redisApi.clusterInfo.request({ id: redis.id });
state.clusterInfoDialog.info = ci.clusterInfo;
state.clusterInfoDialog.nodes = ci.clusterNodes;
state.clusterInfoDialog.redisId = redis.id;
state.clusterInfoDialog.visible = true;
};
};
const search = async () => {
const search = async () => {
const res = await redisApi.redisList.request(state.query);
state.redisTable = res.list;
state.total = res.total;
};
};
const getTags = async () => {
const getTags = async () => {
state.tags = await tagApi.getAccountTags.request(null);
};
};
const editRedis = async (isAdd = false) => {
const editRedis = async (isAdd = false) => {
if (isAdd) {
state.redisEditDialog.data = null;
state.redisEditDialog.title = '新增redis';
@@ -278,19 +254,19 @@ export default defineComponent({
state.redisEditDialog.title = '修改redis';
}
state.redisEditDialog.visible = true;
};
};
const valChange = () => {
const valChange = () => {
state.currentId = null;
state.currentData = null;
search();
};
// 打开redis数据操作页
const openDataOpt = (row : any) => {
const {tagPath, id, db} = row;
};
// 打开redis数据操作页
const openDataOpt = (row: any) => {
const { tagPath, id, db } = row;
// 判断db是否发生改变
let oldDbId = store.state.redisDbOptInfo.dbOptInfo.dbId;
if(oldDbId !== id){
if (oldDbId !== id) {
let params = {
tagPath,
dbId: id,
@@ -298,26 +274,10 @@ export default defineComponent({
}
store.dispatch('redisDbOptInfo/setRedisDbOptInfo', params);
}
router.push({name: 'DataOperation'});
}
return {
...toRefs(state),
dateFormat,
getTags,
search,
handlePageChange,
choose,
info,
onShowClusterInfo,
deleteRedis,
editRedis,
valChange,
openDataOpt,
};
},
});
router.push({ name: 'DataOperation' });
}
</script>
<style>
</style>

View File

@@ -15,12 +15,14 @@
<el-table :data="value" stripe style="width: 100%">
<el-table-column prop="value" label="value" min-width="200">
<template #default="scope">
<el-input v-model="scope.row.value" clearable type="textarea" :autosize="{ minRows: 2, maxRows: 10 }" size="small"></el-input>
<el-input v-model="scope.row.value" clearable type="textarea"
:autosize="{ minRows: 2, maxRows: 10 }" size="small"></el-input>
</template>
</el-table-column>
<el-table-column label="操作" width="90">
<template #default="scope">
<el-button type="danger" @click="value.splice(scope.$index, 1)" icon="delete" size="small" plain>删除</el-button>
<el-button type="danger" @click="value.splice(scope.$index, 1)" icon="delete" size="small"
plain>删除</el-button>
</template>
</el-table-column>
</el-table>
@@ -33,16 +35,13 @@
</template>
</el-dialog>
</template>
<script lang="ts">
import { defineComponent, reactive, watch, toRefs } from 'vue';
<script lang="ts" setup>
import { reactive, watch, toRefs } from 'vue';
import { redisApi } from './api';
import { ElMessage } from 'element-plus';
import { isTrue, notEmpty } from '@/common/assert';
export default defineComponent({
name: 'SetValue',
components: {},
props: {
const props = defineProps({
visible: {
type: Boolean,
},
@@ -67,10 +66,11 @@ export default defineComponent({
setValue: {
type: [Array, Object],
},
},
emits: ['valChange', 'cancel', 'update:visible'],
setup(props: any, { emit }) {
const state = reactive({
})
const emit = defineEmits(['update:visible', 'cancel', 'valChange'])
const state = reactive({
dialogVisible: false,
operationType: 1,
redisId: '',
@@ -81,9 +81,16 @@ export default defineComponent({
timed: -1,
},
value: [{ value: '' }],
});
});
const cancel = () => {
const {
dialogVisible,
operationType,
key,
value,
} = toRefs(state)
const cancel = () => {
emit('update:visible', false);
emit('cancel');
setTimeout(() => {
@@ -94,9 +101,9 @@ export default defineComponent({
};
state.value = [];
}, 500);
};
};
watch(props, async (newValue) => {
watch(props, async (newValue: any) => {
state.dialogVisible = newValue.visible;
state.key = newValue.key;
state.redisId = newValue.redisId;
@@ -107,9 +114,9 @@ export default defineComponent({
if (state.dialogVisible && state.operationType == 2) {
getSetValue();
}
});
});
const getSetValue = async () => {
const getSetValue = async () => {
const res = await redisApi.getSetValue.request({
id: state.redisId,
db: state.db,
@@ -120,9 +127,9 @@ export default defineComponent({
value: x,
};
});
};
};
const saveValue = async () => {
const saveValue = async () => {
notEmpty(state.key.key, 'key不能为空');
isTrue(state.value.length > 0, 'set内容不能为空');
const sv = { value: state.value.map((x) => x.value), id: state.redisId, db: state.db };
@@ -132,20 +139,11 @@ export default defineComponent({
ElMessage.success('数据保存成功');
cancel();
emit('valChange');
};
};
const onAddSetValue = () => {
const onAddSetValue = () => {
state.value.unshift({ value: '' });
};
return {
...toRefs(state),
saveValue,
cancel,
onAddSetValue,
};
},
});
};
</script>
<style lang="scss">
#string-value-text {

View File

@@ -12,7 +12,8 @@
</el-form-item>
<div id="string-value-text" style="width: 100%">
<el-input class="json-text" v-model="string.value" type="textarea" :autosize="{ minRows: 10, maxRows: 20 }"></el-input>
<el-input class="json-text" v-model="string.value" type="textarea"
:autosize="{ minRows: 10, maxRows: 20 }"></el-input>
<el-select class="text-type-select" @change="onChangeTextType" v-model="string.type">
<el-option key="text" label="text" value="text"> </el-option>
<el-option key="json" label="json" value="json"> </el-option>
@@ -27,17 +28,14 @@
</template>
</el-dialog>
</template>
<script lang="ts">
import { defineComponent, reactive, watch, toRefs } from 'vue';
<script lang="ts" setup>
import { reactive, watch, toRefs } from 'vue';
import { redisApi } from './api';
import { ElMessage } from 'element-plus';
import { notEmpty } from '@/common/assert';
import { formatJsonString } from '@/common/utils/format';
export default defineComponent({
name: 'StringValue',
components: {},
props: {
const props = defineProps({
visible: {
type: Boolean,
},
@@ -59,10 +57,11 @@ export default defineComponent({
operationType: {
type: [Number],
},
},
emits: ['valChange', 'cancel', 'update:visible'],
setup(props: any, { emit }) {
const state = reactive({
})
const emit = defineEmits(['update:visible', 'cancel', 'valChange'])
const state = reactive({
dialogVisible: false,
operationType: 1,
redisId: '',
@@ -76,9 +75,16 @@ export default defineComponent({
type: 'text',
value: '',
},
});
});
const cancel = () => {
const {
dialogVisible,
operationType,
key,
string,
} = toRefs(state)
const cancel = () => {
emit('update:visible', false);
emit('cancel');
setTimeout(() => {
@@ -90,30 +96,30 @@ export default defineComponent({
state.string.value = '';
state.string.type = 'text';
}, 500);
};
};
watch(
watch(
() => props.visible,
(val) => {
state.dialogVisible = val;
}
);
);
watch(
watch(
() => props.redisId,
(val) => {
state.redisId = val;
state.redisId = val as any;
}
);
);
watch(
watch(
() => props.db,
(val) => {
state.db = val;
state.db = val as any;
}
);
);
watch(props, async (newValue) => {
watch(props, async (newValue: any) => {
state.dialogVisible = newValue.visible;
state.key = newValue.key;
state.redisId = newValue.redisId;
@@ -124,17 +130,17 @@ export default defineComponent({
if (state.dialogVisible && state.operationType == 2) {
getStringValue();
}
});
});
const getStringValue = async () => {
const getStringValue = async () => {
state.string.value = await redisApi.getStringValue.request({
id: state.redisId,
db: state.db,
key: state.key.key,
});
};
};
const saveValue = async () => {
const saveValue = async () => {
notEmpty(state.key.key, 'key不能为空');
notEmpty(state.string.value, 'value不能为空');
@@ -144,10 +150,10 @@ export default defineComponent({
ElMessage.success('数据保存成功');
cancel();
emit('valChange');
};
};
// 更改文本类型
const onChangeTextType = (val: string) => {
// 更改文本类型
const onChangeTextType = (val: string) => {
if (val == 'json') {
state.string.value = formatJsonString(state.string.value, false);
return;
@@ -155,16 +161,7 @@ export default defineComponent({
if (val == 'text') {
state.string.value = formatJsonString(state.string.value, true);
}
};
return {
...toRefs(state),
saveValue,
cancel,
onChangeTextType,
};
},
});
};
</script>
<style lang="scss">
#string-value-text {

View File

@@ -1,88 +0,0 @@
<template>
<el-dialog :title="keyValue.key" v-model="dialogVisible" :before-close="cancel" :show-close="false" width="900px">
<el-form>
<el-form-item>
<el-input class="json-text" v-model="keyValue2.jsonValue" type="textarea" :autosize="{ minRows: 10, maxRows: 20 }"></el-input>
</el-form-item>
<!-- <vue3-json-editor v-model="keyValue2.jsonValue" @json-change="valueChange" :show-btns="false" :expandedOnStart="true" /> -->
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button @click="cancel()"> </el-button>
<el-button @click="saveValue" type="primary"> </el-button>
</div>
</template>
</el-dialog>
</template>
<script lang="ts">
import { defineComponent, reactive, watch, toRefs } from 'vue';
import { redisApi } from './api';
import { ElMessage } from 'element-plus';
import { isTrue } from '@/common/assert';
export default defineComponent({
name: 'ValueDialog',
components: {},
props: {
visible: {
type: Boolean,
},
title: {
type: String,
},
keyValue: {
type: [String, Object],
},
},
setup(props: any, { emit }) {
const state = reactive({
dialogVisible: false,
keyValue2: {} as any,
});
const cancel = () => {
emit('update:visible', false);
emit('cancel');
};
watch(
() => props.visible,
(val) => {
state.dialogVisible = val;
}
);
watch(
() => props.keyValue,
(val) => {
state.keyValue2 = val;
if (typeof val.value == 'string') {
state.keyValue2.jsonValue = JSON.stringify(JSON.parse(val.value), null, 2);
} else {
state.keyValue2.jsonValue = JSON.stringify(val.value, null, 2);
}
}
);
const saveValue = async () => {
isTrue(state.keyValue2.type == 'string', '暂不支持除string外其他类型修改');
state.keyValue2.value = state.keyValue2.jsonValue;
await redisApi.saveStringValue.request(state.keyValue2);
ElMessage.success('保存成功');
cancel();
};
const valueChange = (val: any) => {
state.keyValue2.value = JSON.stringify(val);
};
return {
...toRefs(state),
saveValue,
valueChange,
cancel,
};
},
});
</script>

View File

@@ -1,29 +1,24 @@
<template>
<div class="menu">
<div class="toolbar">
<el-input v-model="filterTag" placeholder="输入标签关键字过滤" style="width: 200px; margin-right: 10px" />
<el-button v-auth="'tag:save'" type="primary" icon="plus" @click="showSaveTabDialog(null)">添加</el-button>
<div style="float: right">
<el-tooltip effect="dark" placement="top">
<template #content>
1. 用于将资产进行归类
<br />2. 可在团队管理中进行分配用于资源隔离
<br />3. 父标签可访问及操作所有子标签关联的资源
<br />2. 可在团队管理中进行分配用于资源隔离 <br />3. 拥有父标签的团队成员可访问操作其自身或子标签关联的资源
</template>
<span>标签作用<el-icon><question-filled /></el-icon></span>
<span>标签作用<el-icon>
<question-filled />
</el-icon>
</span>
</el-tooltip>
</div>
</div>
<el-tree
class="none-select"
:indent="38"
node-key="id"
:props="props"
:data="data"
@node-expand="handleNodeExpand"
@node-collapse="handleNodeCollapse"
:default-expanded-keys="defaultExpandedKeys"
:expand-on-click-node="false"
>
<el-tree ref="tagTreeRef" class="none-select" :indent="38" node-key="id" :props="props" :data="data"
@node-expand="handleNodeExpand" @node-collapse="handleNodeCollapse"
:default-expanded-keys="defaultExpandedKeys" :expand-on-click-node="false" :filter-node-method="filterNode">
<template #default="{ data }">
<span class="custom-tree-node">
<span style="font-size: 13px">
@@ -34,18 +29,14 @@
<el-tag v-if="data.children !== null" size="small">{{ data.children.length }}</el-tag>
</span>
<el-link @click.prevent="info(data)" style="margin-left: 25px" icon="view" type="info" :underline="false" />
<el-link @click.prevent="info(data)" style="margin-left: 25px" icon="view" type="info"
:underline="false" />
<el-link v-auth="'tag:save'" @click.prevent="showEditTagDialog(data)" class="ml5" type="primary" icon="edit" :underline="false" />
<el-link v-auth="'tag:save'" @click.prevent="showEditTagDialog(data)" class="ml5" type="primary"
icon="edit" :underline="false" />
<el-link
v-auth="'tag:save'"
@click.prevent="showSaveTabDialog(data)"
icon="circle-plus"
:underline="false"
type="success"
class="ml5"
/>
<el-link v-auth="'tag:save'" @click.prevent="showSaveTabDialog(data)" icon="circle-plus"
:underline="false" type="success" class="ml5" />
<!-- <el-link
v-auth="'resource:changeStatus'"
@@ -68,24 +59,18 @@
class="ml5"
/> -->
<el-link
v-auth="'tag:del'"
@click.prevent="deleteTag(data)"
v-if="data.children == null"
type="danger"
icon="delete"
:underline="false"
plain
class="ml5"
/>
<el-link v-auth="'tag:del'" @click.prevent="deleteTag(data)" v-if="data.children == null"
type="danger" icon="delete" :underline="false" plain class="ml5" />
</span>
</template>
</el-tree>
<el-dialog width="500px" :title="saveTabDialog.title" :before-close="cancelSaveTag" v-model="saveTabDialog.visible">
<el-dialog width="500px" :title="saveTabDialog.title" :before-close="cancelSaveTag"
v-model="saveTabDialog.visible">
<el-form ref="tagForm" :rules="rules" :model="saveTabDialog.form" label-width="70px">
<el-form-item prop="code" label="标识:" required>
<el-input :disabled="saveTabDialog.form.id ? true : false" v-model="saveTabDialog.form.code" auto-complete="off"></el-input>
<el-input :disabled="saveTabDialog.form.id ? true : false" v-model="saveTabDialog.form.code"
auto-complete="off"></el-input>
</el-form-item>
<el-form-item prop="name" label="名称:" required>
<el-input v-model="saveTabDialog.form.name" auto-complete="off"></el-input>
@@ -118,38 +103,53 @@
</div>
</template>
<script lang="ts">
import { toRefs, ref, reactive, onMounted, defineComponent } from 'vue';
<script lang="ts" setup>
import { toRefs, ref, watch, reactive, onMounted } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import { tagApi } from './api';
import { dateFormat } from '@/common/utils/date';
export default defineComponent({
name: 'TagTreeList',
components: {},
setup() {
const tagForm: any = ref(null);
const state = reactive({
interface Tree {
id: number;
codePath: string;
name: string;
children?: Tree[];
}
const tagForm: any = ref(null);
const tagTreeRef: any = ref(null);
const filterTag = ref('');
const state = reactive({
data: [],
saveTabDialog: {
title: '新增标签',
visible: false,
form: { id: 0, pid: 0, code: '', name: '', remark: '' },
},
//资源信息弹出框对象
infoDialog: {
title: '',
visible: false,
// 资源类型选择是否选
data: null as any,
},
data: [],
props: {
// 展开的节点
defaultExpandedKeys: [] as any
});
const {
data,
saveTabDialog,
infoDialog,
defaultExpandedKeys,
} = toRefs(state)
const props = {
label: 'name',
children: 'children',
},
// 展开的节点
defaultExpandedKeys: [] as any[],
rules: {
};
const rules = {
code: [
{ required: true, message: '标识符不能为空', trigger: 'blur' },
// {
@@ -159,24 +159,32 @@ export default defineComponent({
// },
],
name: [{ required: true, message: '名称不能为空', trigger: 'blur' }],
},
});
};
onMounted(() => {
onMounted(() => {
search();
});
});
const search = async () => {
watch(filterTag, (val) => {
tagTreeRef.value!.filter(val);
});
const filterNode = (value: string, data: Tree) => {
if (!value) return true;
return data.codePath.includes(value) || data.name.includes(value);
};
const search = async () => {
let res = await tagApi.getTagTrees.request(null);
state.data = res;
};
};
const info = async (data: any) => {
const info = async (data: any) => {
state.infoDialog.data = data;
state.infoDialog.visible = true;
};
};
const showSaveTabDialog = (data: any) => {
const showSaveTabDialog = (data: any) => {
if (data) {
state.saveTabDialog.form.pid = data.id;
state.saveTabDialog.title = `新增 [${data.codePath}] 子标签信息`;
@@ -184,18 +192,18 @@ export default defineComponent({
state.saveTabDialog.title = '新增根标签信息';
}
state.saveTabDialog.visible = true;
};
};
const showEditTagDialog = (data: any) => {
const showEditTagDialog = (data: any) => {
state.saveTabDialog.form.id = data.id;
state.saveTabDialog.form.code = data.code;
state.saveTabDialog.form.name = data.name;
state.saveTabDialog.form.remark = data.remark;
state.saveTabDialog.title = `修改 [${data.codePath}] 信息`;
state.saveTabDialog.visible = true;
};
};
const saveTag = async () => {
const saveTag = async () => {
tagForm.value.validate(async (valid: any) => {
if (valid) {
const form = state.saveTabDialog.form;
@@ -205,15 +213,15 @@ export default defineComponent({
cancelSaveTag();
}
});
};
};
const cancelSaveTag = () => {
const cancelSaveTag = () => {
state.saveTabDialog.visible = false;
state.saveTabDialog.form = {} as any;
tagForm.value.resetFields();
};
};
const deleteTag = (data: any) => {
const deleteTag = (data: any) => {
ElMessageBox.confirm(`此操作将删除 [${data.codePath}], 是否继续?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
@@ -223,27 +231,27 @@ export default defineComponent({
ElMessage.success('删除成功!');
search();
});
};
};
// const changeStatus = async (data: any, status: any) => {
// await resourceApi.changeStatus.request({
// id: data.id,
// status: status,
// });
// data.status = status;
// ElMessage.success((status === 1 ? '启用' : '禁用') + '成功!');
// };
// const changeStatus = async (data: any, status: any) => {
// await resourceApi.changeStatus.request({
// id: data.id,
// status: status,
// });
// data.status = status;
// ElMessage.success((status === 1 ? '启用' : '禁用') + '成功!');
// };
// 节点被展开时触发的事件
const handleNodeExpand = (data: any, node: any) => {
// 节点被展开时触发的事件
const handleNodeExpand = (data: any, node: any) => {
const id: any = node.data.id;
if (!state.defaultExpandedKeys.includes(id)) {
state.defaultExpandedKeys.push(id);
}
};
};
// 关闭节点
const handleNodeCollapse = (data: any, node: any) => {
// 关闭节点
const handleNodeCollapse = (data: any, node: any) => {
removeDeafultExpandId(node.data.id);
let childNodes = node.childNodes;
@@ -254,34 +262,19 @@ export default defineComponent({
// 递归删除展开的子节点节点id
handleNodeCollapse(data, cn);
}
};
};
const removeDeafultExpandId = (id: any) => {
const removeDeafultExpandId = (id: any) => {
let index = state.defaultExpandedKeys.indexOf(id);
if (index > -1) {
state.defaultExpandedKeys.splice(index, 1);
}
};
return {
...toRefs(state),
dateFormat,
tagForm,
info,
saveTag,
showSaveTabDialog,
showEditTagDialog,
cancelSaveTag,
deleteTag,
handleNodeExpand,
handleNodeCollapse,
};
},
});
};
</script>
<style lang="scss">
.menu {
height: 100%;
.el-tree-node__content {
height: 40px;
line-height: 40px;

View File

@@ -2,13 +2,14 @@
<div class="role-list">
<el-card>
<el-button v-auth="'team:save'" type="primary" icon="plus" @click="showSaveTeamDialog(false)">添加</el-button>
<el-button v-auth="'team:save'" :disabled="chooseId == null" @click="showSaveTeamDialog(chooseData)" type="primary" icon="edit"
>编辑</el-button
>
<el-button v-auth="'team:del'" :disabled="chooseId == null" @click="deleteTeam(chooseData)" type="danger" icon="delete">删除</el-button>
<el-button v-auth="'team:save'" :disabled="!chooseId" @click="showSaveTeamDialog(chooseData)"
type="primary" icon="edit">编辑</el-button>
<el-button v-auth="'team:del'" :disabled="!chooseId" @click="deleteTeam(chooseData)" type="danger"
icon="delete">删除</el-button>
<div style="float: right">
<el-input placeholder="请输入团队名称" class="mr2" style="width: 200px" v-model="query.name" @clear="search" clearable></el-input>
<el-input placeholder="请输入团队名称" class="mr2" style="width: 200px" v-model="query.name" @clear="search"
clearable></el-input>
<el-button @click="search" type="success" icon="search"></el-button>
</div>
<el-table :data="data" @current-change="choose" ref="table" style="width: 100%">
@@ -36,14 +37,9 @@
</el-table-column>
</el-table>
<el-row style="margin-top: 20px" type="flex" justify="end">
<el-pagination
style="text-align: right"
@current-change="handlePageChange"
:total="total"
layout="prev, pager, next, total, jumper"
v-model:current-page="query.pageNum"
:page-size="query.pageSize"
></el-pagination>
<el-pagination style="text-align: right" @current-change="handlePageChange" :total="total"
layout="prev, pager, next, total, jumper" v-model:current-page="query.pageNum"
:page-size="query.pageSize"></el-pagination>
</el-row>
</el-card>
@@ -64,23 +60,14 @@
</template>
</el-dialog>
<el-dialog width="500px" :title="showTagDialog.title" :before-close="closeTagDialog" v-model="showTagDialog.visible">
<el-dialog width="500px" :title="showTagDialog.title" :before-close="closeTagDialog"
v-model="showTagDialog.visible">
<el-form label-width="70px">
<el-form-item prop="project" label="标签:">
<el-tree-select
ref="tagTreeRef"
style="width: 100%"
v-model="showTagDialog.tagTreeTeams"
:data="showTagDialog.tags"
:default-expanded-keys="showTagDialog.tagTreeTeams"
multiple
:render-after-expand="true"
show-checkbox
check-strictly
node-key="id"
:props="showTagDialog.props"
@check="tagTreeNodeCheck"
>
<el-tree-select ref="tagTreeRef" style="width: 100%" v-model="showTagDialog.tagTreeTeams"
:data="showTagDialog.tags" :default-expanded-keys="showTagDialog.tagTreeTeams" multiple
:render-after-expand="true" show-checkbox check-strictly node-key="id"
:props="showTagDialog.props" @check="tagTreeNodeCheck">
<template #default="{ data }">
<span class="custom-tree-node">
<span style="font-size: 13px">
@@ -88,7 +75,8 @@
<span style="color: #3c8dbc"></span>
{{ data.name }}
<span style="color: #3c8dbc"></span>
<el-tag v-if="data.children !== null" size="small">{{ data.children.length }}</el-tag>
<el-tag v-if="data.children !== null" size="small">{{ data.children.length }}
</el-tag>
</span>
</span>
</template>
@@ -103,12 +91,19 @@
</template>
</el-dialog>
<el-dialog width="600px" :title="showMemDialog.title" v-model="showMemDialog.visible">
<el-dialog width="700px" :title="showMemDialog.title" v-model="showMemDialog.visible">
<div class="toolbar">
<el-button v-auth="'team:member:save'" @click="showAddMemberDialog()" type="primary" icon="plus">添加</el-button>
<el-button v-auth="'team:member:del'" @click="deleteMember" :disabled="showMemDialog.chooseId == null" type="danger" icon="delete">移除</el-button>
<el-button v-auth="'team:member:save'" @click="showAddMemberDialog()" type="primary" icon="plus"
size="small">添加</el-button>
<el-button v-auth="'team:member:del'" @click="deleteMember" :disabled="showMemDialog.chooseId == null"
type="danger" icon="delete" size="small">移除</el-button>
<div style="float: right">
<el-input placeholder="请输入用户名" class="mr2" style="width: 150px"
v-model="showMemDialog.query.username" size="small" @clear="search" clearable></el-input>
<el-button @click="setMemebers" type="success" icon="search" size="small"></el-button>
</div>
<el-table @current-change="chooseMember" border :data="showMemDialog.members.list">
</div>
<el-table @current-change="chooseMember" border :data="showMemDialog.members.list" size="small">
<el-table-column label="选择" width="50px">
<template #default="scope">
<el-radio v-model="showMemDialog.chooseId" :label="scope.row.id">
@@ -116,6 +111,7 @@
</el-radio>
</template>
</el-table-column>
<el-table-column property="name" label="姓名" width="115"></el-table-column>
<el-table-column property="username" label="账号" width="135"></el-table-column>
<el-table-column property="createTime" label="加入时间">
<template #default="scope">
@@ -124,28 +120,18 @@
</el-table-column>
<el-table-column property="creator" label="分配者" width="135"></el-table-column>
</el-table>
<el-pagination
@current-change="setMemebers"
style="text-align: center"
background
layout="prev, pager, next, total, jumper"
:total="showMemDialog.members.total"
v-model:current-page="showMemDialog.query.pageNum"
:page-size="showMemDialog.query.pageSize"
/>
<el-pagination size="small" @current-change="setMemebers" style="text-align: center" background
layout="prev, pager, next, total, jumper" :total="showMemDialog.members.total"
v-model:current-page="showMemDialog.query.pageNum" :page-size="showMemDialog.query.pageSize" />
<el-dialog width="400px" title="添加成员" :before-close="cancelAddMember" v-model="showMemDialog.addVisible">
<el-form :model="showMemDialog.memForm" label-width="70px">
<el-form-item label="账号:">
<el-select
style="width: 100%"
remote
:remote-method="getAccount"
v-model="showMemDialog.memForm.accountId"
filterable
placeholder="请输入账号模糊搜索并选择"
>
<el-option v-for="item in showMemDialog.accounts" :key="item.id" :label="item.username" :value="item.id"> </el-option>
<el-select style="width: 100%" remote :remote-method="getAccount"
v-model="showMemDialog.memForm.accountIds" filterable multiple placeholder="请输入账号模糊搜索并选择">
<el-option v-for="item in showMemDialog.accounts" :key="item.id"
:label="`${item.username} [${item.name}]`" :value="item.id">
</el-option>
</el-select>
</el-form-item>
</el-form>
@@ -160,21 +146,17 @@
</div>
</template>
<script lang="ts">
import { ref, toRefs, reactive, onMounted, defineComponent } from 'vue';
<script lang="ts" setup>
import { ref, toRefs, reactive, onMounted } from 'vue';
import { tagApi } from './api';
import { accountApi } from '../../system/api';
import { ElMessage, ElMessageBox } from 'element-plus';
import { dateFormat } from '@/common/utils/date';
import { notBlank } from '@/common/assert';
export default defineComponent({
name: 'TeamList',
components: {},
setup() {
const teamForm: any = ref(null);
const tagTreeRef: any = ref(null);
const state = reactive({
dialogFormVisible: false,
const teamForm: any = ref(null);
const tagTreeRef: any = ref(null);
const state = reactive({
currentEditPermissions: false,
addTeamDialog: {
title: '新增团队',
@@ -195,9 +177,10 @@ export default defineComponent({
chooseId: 0,
chooseData: null,
query: {
pageSize: 8,
pageSize: 10,
pageNum: 1,
teamId: null,
username: null,
},
members: {
list: [],
@@ -206,7 +189,7 @@ export default defineComponent({
title: '',
addVisible: false,
memForm: {
accountId: null as any,
accountIds: [] as any,
teamId: 0,
},
accounts: Array(),
@@ -223,32 +206,43 @@ export default defineComponent({
children: 'children',
},
},
});
});
onMounted(() => {
const {
query,
addTeamDialog,
total,
data,
chooseId,
chooseData,
showMemDialog,
showTagDialog,
} = toRefs(state)
onMounted(() => {
search();
});
});
const search = async () => {
const search = async () => {
let res = await tagApi.getTeams.request(state.query);
state.data = res.list;
state.total = res.total;
};
};
const handlePageChange = (curPage: number) => {
const handlePageChange = (curPage: number) => {
state.query.pageNum = curPage;
search();
};
};
const choose = (item: any) => {
const choose = (item: any) => {
if (!item) {
return;
}
state.chooseId = item.id;
state.chooseData = item;
};
};
const showSaveTeamDialog = (data: any) => {
const showSaveTeamDialog = (data: any) => {
if (data) {
state.addTeamDialog.form.id = data.id;
state.addTeamDialog.form.name = data.name;
@@ -256,9 +250,9 @@ export default defineComponent({
state.addTeamDialog.title = `修改 [${data.codePath}] 信息`;
}
state.addTeamDialog.visible = true;
};
};
const saveTeam = async () => {
const saveTeam = async () => {
teamForm.value.validate(async (valid: any) => {
if (valid) {
const form = state.addTeamDialog.form;
@@ -268,15 +262,15 @@ export default defineComponent({
cancelSaveTeam();
}
});
};
};
const cancelSaveTeam = () => {
const cancelSaveTeam = () => {
state.addTeamDialog.visible = false;
state.addTeamDialog.form = {} as any;
teamForm.value.resetFields();
};
};
const deleteTeam = (data: any) => {
const deleteTeam = (data: any) => {
ElMessageBox.confirm(`此操作将删除 [${data.name}], 是否继续?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
@@ -286,99 +280,99 @@ export default defineComponent({
ElMessage.success('删除成功!');
search();
});
};
};
/********** 团队成员相关 ***********/
/********** 团队成员相关 ***********/
const showMembers = async (team: any) => {
const showMembers = async (team: any) => {
state.showMemDialog.query.teamId = team.id;
await setMemebers();
state.showMemDialog.title = `[${team.name}] 成员信息`;
state.showMemDialog.visible = true;
};
};
const getAccount = (username: any) => {
const getAccount = (username: any) => {
accountApi.list.request({ username }).then((res) => {
state.showMemDialog.accounts = res.list;
});
};
};
/**
/**
* 选中成员
*/
const chooseMember = (item: any) => {
const chooseMember = (item: any) => {
if (!item) {
return;
}
state.showMemDialog.chooseData = item;
state.showMemDialog.chooseId = item.id;
};
};
const deleteMember = async () => {
const deleteMember = async () => {
await tagApi.delTeamMem.request(state.showMemDialog.chooseData);
ElMessage.success('移除成功');
// 重新赋值成员列表
setMemebers();
};
};
/**
/**
* 设置成员列表信息
*/
const setMemebers = async () => {
const setMemebers = async () => {
const res = await tagApi.getTeamMem.request(state.showMemDialog.query);
state.showMemDialog.members.list = res.list;
state.showMemDialog.members.total = res.total;
};
};
const showAddMemberDialog = () => {
const showAddMemberDialog = () => {
state.showMemDialog.addVisible = true;
};
};
const addMember = async () => {
const addMember = async () => {
const memForm = state.showMemDialog.memForm;
memForm.teamId = state.chooseId;
notBlank(memForm.accountId, '请先选择账号');
notBlank(memForm.accountIds, '请先选择账号');
await tagApi.saveTeamMem.request(memForm);
ElMessage.success('保存成功');
setMemebers();
cancelAddMember();
};
};
const cancelAddMember = () => {
const cancelAddMember = () => {
state.showMemDialog.memForm = {} as any;
state.showMemDialog.addVisible = false;
state.showMemDialog.chooseData = null;
state.showMemDialog.chooseId = 0;
};
};
/********** 标签相关 ***********/
/********** 标签相关 ***********/
const showTags = async (team: any) => {
const showTags = async (team: any) => {
state.showTagDialog.tags = await tagApi.getTagTrees.request(null);
state.showTagDialog.tagTreeTeams = await tagApi.getTeamTagIds.request({ teamId: team.id });
state.showTagDialog.title = `[${team.name}] 团队标签信息`;
state.showTagDialog.teamId = team.id;
state.showTagDialog.visible = true;
};
};
const closeTagDialog = () => {
const closeTagDialog = () => {
state.showTagDialog.visible = false;
setTimeout(() => {
state.showTagDialog.tagTreeTeams = [];
}, 500);
};
};
const saveTags = async () => {
const saveTags = async () => {
await tagApi.saveTeamTags.request({
teamId: state.showTagDialog.teamId,
tagIds: state.showTagDialog.tagTreeTeams,
});
ElMessage.success('保存成功');
closeTagDialog();
};
};
const tagTreeNodeCheck = (data: any, checkInfo: any) => {
const tagTreeNodeCheck = (data: any, checkInfo: any) => {
const node = tagTreeRef.value.getNode(data.id);
console.log(node);
// state.showTagDialog.tagTreeTeams = [16]
@@ -395,7 +389,7 @@ export default defineComponent({
}
console.log(data);
console.log(checkInfo);
};
};
// function removeCheckedTagId(id: any) {
// console.log(state.showTagDialog.tagTreeTeams);
@@ -407,34 +401,7 @@ export default defineComponent({
// }
// console.log(state.showTagDialog.tagTreeTeams);
// }
return {
...toRefs(state),
teamForm,
tagTreeRef,
dateFormat,
choose,
search,
handlePageChange,
showSaveTeamDialog,
saveTeam,
cancelSaveTeam,
deleteTeam,
showMembers,
setMemebers,
getAccount,
showAddMemberDialog,
addMember,
cancelAddMember,
chooseMember,
deleteMember,
showTags,
closeTagDialog,
saveTags,
tagTreeNodeCheck,
};
},
});
</script>
<style lang="scss">
</style>

View File

@@ -12,8 +12,9 @@
</div>
<div class="personal-user-right">
<el-row>
<el-col :span="24" class="personal-title mb18"
>{{ currentTime }}{{ getUserInfos.username }}生活变的再糟糕也不妨碍我变得更好
<el-col :span="24" class="personal-title mb18">{{ currentTime }}{{
getUserInfos.username
}}生活变的再糟糕也不妨碍我变得更好
</el-col>
<el-col :span="24">
<el-row>
@@ -35,7 +36,9 @@
</el-col>
<el-col :xs="24" :sm="16" class="personal-item mb6">
<div class="personal-item-label">上次登录时间</div>
<div class="personal-item-value">{{ $filters.dateFormat(getUserInfos.lastLoginTime) }}</div>
<div class="personal-item-value">{{
dateFormat(getUserInfos.lastLoginTime)
}}</div>
</el-col>
</el-row>
</el-col>
@@ -54,7 +57,7 @@
</template>
<div class="personal-info-box">
<ul class="personal-info-ul">
<li v-for="(v, k) in msgDialog.msgs.list" :key="k" class="personal-info-li">
<li v-for="(v, k) in msgDialog.msgs.list as any" :key="k" class="personal-info-li">
<a class="personal-info-li-title">{{ `[${getMsgTypeDesc(v.type)}] ${v.msg}` }}</a>
</li>
</ul>
@@ -72,19 +75,13 @@
<el-table-column property="msg" label="消息"></el-table-column>
<el-table-column property="createTime" label="时间" width="150">
<template #default="scope">
{{ $filters.dateFormat(scope.row.createTime) }}
{{ dateFormat(scope.row.createTime) }}
</template>
</el-table-column>
</el-table>
<el-pagination
@current-change="getMsgs"
style="text-align: center"
background
layout="prev, pager, next, total, jumper"
:total="msgDialog.msgs.total"
v-model:current-page="msgDialog.query.pageNum"
:page-size="msgDialog.query.pageSize"
/>
<el-pagination @current-change="getMsgs" style="text-align: center" background
layout="prev, pager, next, total, jumper" :total="msgDialog.msgs.total"
v-model:current-page="msgDialog.query.pageNum" :page-size="msgDialog.query.pageSize" />
</el-dialog>
<!-- 营销推荐 -->
@@ -112,13 +109,8 @@
<el-row :gutter="35">
<el-col :xs="24" :sm="12" :md="8" :lg="6" :xl="4" class="mb20">
<el-form-item label="密码">
<el-input
type="password"
show-password
v-model="accountForm.password"
placeholder="请输入新密码"
clearable
></el-input>
<el-input type="password" show-password v-model="accountForm.password"
placeholder="请输入新密码" clearable></el-input>
</el-form-item>
</el-col>
<!-- -->
@@ -181,18 +173,17 @@
</div>
</template>
<script lang="ts">
<script lang="ts" setup>
import { toRefs, reactive, computed, onMounted } from 'vue';
import { ElMessage } from 'element-plus';
import { formatAxis } from '@/common/utils/formatTime.ts';
import { recommendList } from './mock.ts';
import { useStore } from '@/store/index.ts';
import { personApi } from './api';
export default {
name: 'PersonalPage',
setup() {
const store = useStore();
const state = reactive({
import { dateFormat } from '@/common/utils/date';
const store = useStore();
const state = reactive({
accountInfo: {
roles: [],
},
@@ -208,95 +199,92 @@ export default {
total: null,
},
},
recommendList,
recommendList: [],
accountForm: {
password: '',
},
});
// 当前时间提示语
const currentTime = computed(() => {
});
const {
msgDialog,
accountForm,
} = toRefs(state)
// 当前时间提示语
const currentTime = computed(() => {
return formatAxis(new Date());
});
});
// 获取用户信息 vuex
const getUserInfos = computed(() => {
// 获取用户信息 vuex
const getUserInfos = computed(() => {
return store.state.userInfos.userInfos;
});
});
const showMsgs = () => {
const showMsgs = () => {
state.msgDialog.visible = true;
};
};
const roleInfo = computed(() => {
const roleInfo = computed(() => {
if (state.accountInfo.roles.length == 0) {
return '';
}
return state.accountInfo.roles.map((val: any) => val.name).join('、');
});
});
onMounted(() => {
onMounted(() => {
getAccountInfo();
getMsgs();
});
});
const getAccountInfo = async () => {
const getAccountInfo = async () => {
state.accountInfo = await personApi.accountInfo.request();
};
};
const updateAccount = async () => {
const updateAccount = async () => {
await personApi.updateAccount.request(state.accountForm);
ElMessage.success('更新成功');
};
};
const getMsgs = async () => {
const getMsgs = async () => {
const res = await personApi.getMsgs.request(state.msgDialog.query);
state.msgDialog.msgs = res;
};
};
const getMsgTypeDesc = (type: number) => {
const getMsgTypeDesc = (type: number) => {
if (type == 1) {
return '登录';
}
if (type == 2) {
return '通知';
}
};
return {
getUserInfos,
currentTime,
roleInfo,
showMsgs,
getAccountInfo,
getMsgs,
getMsgTypeDesc,
updateAccount,
...toRefs(state),
};
},
};
</script>
<style scoped lang="scss">
@import '../../theme/mixins/mixins.scss';
.personal {
.personal-user {
height: 130px;
display: flex;
align-items: center;
.personal-user-left {
width: 100px;
height: 130px;
border-radius: 3px;
::v-deep(.el-upload) {
height: 100%;
}
.personal-user-left-upload {
img {
width: 100%;
height: 100%;
border-radius: 3px;
}
&:hover {
img {
animation: logoAnimation 0.3s ease-in-out;
@@ -304,51 +292,63 @@ export default {
}
}
}
.personal-user-right {
flex: 1;
padding: 0 15px;
.personal-title {
font-size: 18px;
@include text-ellipsis(1);
}
.personal-item {
display: flex;
align-items: center;
font-size: 13px;
.personal-item-label {
color: gray;
@include text-ellipsis(1);
}
.personal-item-value {
@include text-ellipsis(1);
}
}
}
}
.personal-info {
.personal-info-more {
float: right;
color: gray;
font-size: 13px;
&:hover {
color: var(--color-primary);
cursor: pointer;
}
}
.personal-info-box {
height: 130px;
overflow: hidden;
.personal-info-ul {
list-style: none;
.personal-info-li {
font-size: 13px;
padding-bottom: 10px;
.personal-info-li-title {
display: inline-block;
@include text-ellipsis(1);
color: grey;
text-decoration: none;
}
& a:hover {
color: var(--color-primary);
cursor: pointer;
@@ -357,6 +357,7 @@ export default {
}
}
}
.personal-recommend-row {
.personal-recommend-col {
.personal-recommend {
@@ -366,6 +367,7 @@ export default {
border-radius: 3px;
overflow: hidden;
cursor: pointer;
&:hover {
i {
right: 0px !important;
@@ -373,6 +375,7 @@ export default {
transition: all ease 0.3s;
}
}
i {
position: absolute;
right: -10px;
@@ -381,11 +384,13 @@ export default {
transform: rotate(-30deg);
transition: all ease 0.3s;
}
.personal-recommend-auto {
padding: 15px;
position: absolute;
left: 0;
top: 5%;
.personal-recommend-msg {
font-size: 12px;
margin-top: 10px;
@@ -394,11 +399,13 @@ export default {
}
}
}
.personal-edit {
.personal-edit-title {
position: relative;
padding-left: 10px;
color: #606266;
&::after {
content: '';
width: 2px;
@@ -410,21 +417,26 @@ export default {
background: var(--color-primary);
}
}
.personal-edit-safe-box {
border-bottom: 1px solid #ebeef5;
padding: 15px 0;
.personal-edit-safe-item {
width: 100%;
display: flex;
align-items: center;
justify-content: space-between;
.personal-edit-safe-item-left {
flex: 1;
overflow: hidden;
.personal-edit-safe-item-left-label {
color: #606266;
margin-bottom: 5px;
}
.personal-edit-safe-item-left-value {
color: gray;
@include text-ellipsis(1);
@@ -432,6 +444,7 @@ export default {
}
}
}
&:last-of-type {
padding-bottom: 0;
border-bottom: none;

View File

@@ -1,12 +1,18 @@
<template>
<div class="account-dialog">
<el-dialog :title="title" v-model="dialogVisible" :before-close="cancel" :show-close="false" width="35%" :destroy-on-close="true">
<el-dialog :title="title" v-model="dialogVisible" :before-close="cancel" :show-close="false" width="35%"
:destroy-on-close="true">
<el-form :model="form" ref="accountForm" :rules="rules" label-width="85px">
<el-form-item prop="username" label="用户名:" required>
<el-input :disabled="edit" v-model.trim="form.username" placeholder="请输入账号用户名,密码默认与账号名一致" auto-complete="off"></el-input>
<el-form-item prop="name" label="名:" required>
<el-input v-model.trim="form.name" placeholder="请输入姓名" auto-complete="off"></el-input>
</el-form-item>
<el-form-item v-if="edit" prop="password" label="密码:" required>
<el-input type="password" v-model.trim="form.password" placeholder="请输入密码" autocomplete="new-password"></el-input>
<el-form-item prop="username" label="用户名:" required>
<el-input :disabled="edit" v-model.trim="form.username" placeholder="请输入账号用户名,密码默认与账号名一致"
auto-complete="off"></el-input>
</el-form-item>
<el-form-item v-if="edit" prop="password" label="密码:">
<el-input type="password" v-model.trim="form.password" placeholder="输入密码可修改用户密码"
autocomplete="new-password"></el-input>
</el-form-item>
</el-form>
@@ -20,14 +26,12 @@
</div>
</template>
<script lang="ts">
import { toRefs, reactive, watch, defineComponent, ref } from 'vue';
<script lang="ts" setup>
import { toRefs, reactive, watch, ref } from 'vue';
import { accountApi } from '../api';
import { ElMessage } from 'element-plus';
export default defineComponent({
name: 'AccountEdit',
props: {
const props = defineProps({
visible: {
type: Boolean,
},
@@ -37,20 +41,21 @@ export default defineComponent({
title: {
type: String,
},
})
//定义事件
const emit = defineEmits(['update:visible', 'cancel', 'val-change'])
const accountForm: any = ref(null);
const rules = {
name: [
{
required: true,
message: '请输入姓名',
trigger: ['change', 'blur'],
},
setup(props: any, { emit }) {
const accountForm: any = ref(null);
const state = reactive({
dialogVisible: false,
edit: false,
form: {
id: null,
username: null,
password: null,
repassword: null,
},
btnLoading: false,
rules: {
],
username: [
{
required: true,
@@ -58,17 +63,29 @@ export default defineComponent({
trigger: ['change', 'blur'],
},
],
// password: [
// {
// required: true,
// message: '请输入密码',
// trigger: ['change', 'blur'],
// },
// ],
},
});
}
watch(props, (newValue) => {
const state = reactive({
dialogVisible: false,
edit: false,
form: {
id: null,
name: null,
username: null,
password: null,
repassword: null,
},
btnLoading: false
});
const {
dialogVisible,
edit,
form,
btnLoading,
} = toRefs(state)
watch(props, (newValue: any) => {
if (newValue.account) {
state.form = { ...newValue.account };
state.edit = true;
@@ -77,9 +94,9 @@ export default defineComponent({
state.form = {} as any;
}
state.dialogVisible = newValue.visible;
});
});
const btnOk = async () => {
const btnOk = async () => {
accountForm.value.validate((valid: boolean) => {
if (valid) {
accountApi.save.request(state.form).then(() => {
@@ -98,21 +115,13 @@ export default defineComponent({
return false;
}
});
};
};
const cancel = () => {
const cancel = () => {
emit('update:visible', false);
emit('cancel');
};
return {
...toRefs(state),
accountForm,
btnOk,
cancel,
};
},
});
};
</script>
<style lang="scss">
</style>

View File

@@ -2,21 +2,15 @@
<div class="role-list">
<el-card>
<el-button v-auth="'account:add'" type="primary" icon="plus" @click="editAccount(true)">添加</el-button>
<el-button v-auth="'account:add'" :disabled="chooseId == null" @click="editAccount(false)" type="primary" icon="edit">编辑</el-button>
<el-button v-auth="'account:saveRoles'" :disabled="chooseId == null" @click="roleEdit()" type="success" icon="setting"
>角色分配</el-button
>
<el-button v-auth="'account:del'" :disabled="chooseId == null" @click="deleteAccount()" type="danger" icon="delete">删除</el-button>
<el-button v-auth="'account:add'" :disabled="chooseId == null" @click="editAccount(false)" type="primary"
icon="edit">编辑</el-button>
<el-button v-auth="'account:saveRoles'" :disabled="chooseId == null" @click="showRoleEdit()" type="success"
icon="setting">角色分配</el-button>
<el-button v-auth="'account:del'" :disabled="chooseId == null" @click="deleteAccount()" type="danger"
icon="delete">删除</el-button>
<div style="float: right">
<el-input
class="mr2"
placeholder="请输入账号名"
size="small"
style="width: 300px"
v-model="query.username"
@clear="search()"
clearable
></el-input>
<el-input class="mr2" placeholder="请输入账号名" size="small" style="width: 300px" v-model="query.username"
@clear="search()" clearable></el-input>
<el-button @click="search()" type="success" icon="search" size="small"></el-button>
</div>
<el-table :data="datas" ref="table" @current-change="choose" show-overflow-tooltip>
@@ -27,9 +21,10 @@
</el-radio>
</template>
</el-table-column>
<el-table-column prop="name" label="姓名" min-width="115"></el-table-column>
<el-table-column prop="username" label="用户名" min-width="115"></el-table-column>
<el-table-column align="center" prop="status" label="状态" min-width="65">
<el-table-column align="center" prop="status" label="状态" min-width="70">
<template #default="scope">
<el-tag v-if="scope.row.status == 1" type="success">正常</el-tag>
<el-tag v-if="scope.row.status == -1" type="danger">禁用</el-tag>
@@ -37,20 +32,20 @@
</el-table-column>
<el-table-column min-width="160" prop="lastLoginTime" label="最后登录时间" show-overflow-tooltip>
<template #default="scope">
{{ $filters.dateFormat(scope.row.lastLoginTime) }}
{{ dateFormat(scope.row.lastLoginTime) }}
</template>
</el-table-column>
<el-table-column min-width="115" prop="creator" label="创建账号"></el-table-column>
<el-table-column min-width="160" prop="createTime" label="创建时间" show-overflow-tooltip>
<template #default="scope">
{{ $filters.dateFormat(scope.row.createTime) }}
{{ dateFormat(scope.row.createTime) }}
</template>
</el-table-column>
<!-- <el-table-column min-width="115" prop="modifier" label="更新账号"></el-table-column>
<el-table-column min-width="160" prop="updateTime" label="修改时间">
<template #default="scope">
{{ $filters.dateFormat(scope.row.updateTime) }}
{{ dateFormat(scope.row.updateTime) }}
</template>
</el-table-column> -->
@@ -65,37 +60,17 @@
<el-table-column label="操作" min-width="200px">
<template #default="scope">
<el-button
v-auth="'account:changeStatus'"
@click="changeStatus(scope.row)"
v-if="scope.row.status == 1"
type="danger"
icom="tickets"
size="small"
plain
>禁用</el-button
>
<el-button
v-auth="'account:changeStatus'"
v-if="scope.row.status == -1"
type="success"
@click="changeStatus(scope.row)"
size="small"
plain
>启用</el-button
>
<el-button v-auth="'account:changeStatus'" @click="changeStatus(scope.row)"
v-if="scope.row.status == 1" type="danger" icom="tickets" size="small" plain>禁用</el-button>
<el-button v-auth="'account:changeStatus'" v-if="scope.row.status == -1" type="success"
@click="changeStatus(scope.row)" size="small" plain>启用</el-button>
</template>
</el-table-column>
</el-table>
<el-row style="margin-top: 20px" type="flex" justify="end">
<el-pagination
style="text-align: right"
@current-change="handlePageChange"
:total="total"
layout="prev, pager, next, total, jumper"
v-model:current-page="query.pageNum"
:page-size="query.pageSize"
></el-pagination>
<el-pagination style="text-align: right" @current-change="handlePageChange" :total="total"
layout="prev, pager, next, total, jumper" v-model:current-page="query.pageNum"
:page-size="query.pageSize"></el-pagination>
</el-row>
</el-card>
@@ -105,49 +80,42 @@
<el-table-column property="creator" label="分配账号" width="125"></el-table-column>
<el-table-column property="createTime" label="分配时间">
<template #default="scope">
{{ $filters.dateFormat(scope.row.createTime) }}
{{ dateFormat(scope.row.createTime) }}
</template>
</el-table-column>
</el-table>
</el-dialog>
<el-dialog :title="showResourceDialog.title" v-model="showResourceDialog.visible" width="400px">
<el-tree
style="height: 50vh; overflow: auto"
:data="showResourceDialog.resources"
node-key="id"
:props="showResourceDialog.defaultProps"
:expand-on-click-node="true"
>
<el-tree style="height: 50vh; overflow: auto" :data="showResourceDialog.resources" node-key="id"
:props="showResourceDialog.defaultProps" :expand-on-click-node="true">
<template #default="{ node, data }">
<span class="custom-tree-node">
<span v-if="data.type == enums.ResourceTypeEnum.MENU.value">{{ node.label }}</span>
<span v-if="data.type == enums.ResourceTypeEnum.PERMISSION.value" style="color: #67c23a">{{ node.label }}</span>
<span v-if="data.type == enums.ResourceTypeEnum['MENU'].value">{{ node.label }}</span>
<span v-if="data.type == enums.ResourceTypeEnum['PERMISSION'].value" style="color: #67c23a">{{
node.label
}}</span>
</span>
</template>
</el-tree>
</el-dialog>
<role-edit v-model:visible="roleDialog.visible" :account="roleDialog.account" @cancel="cancel()" />
<account-edit v-model:visible="accountDialog.visible" v-model:account="accountDialog.data" @val-change="valChange()" />
<account-edit v-model:visible="accountDialog.visible" v-model:account="accountDialog.data"
@val-change="valChange()" />
</div>
</template>
<script lang='ts'>
import { toRefs, reactive, onMounted, defineComponent } from 'vue';
<script lang='ts' setup>
import { toRefs, reactive, onMounted } from 'vue';
import RoleEdit from './RoleEdit.vue';
import AccountEdit from './AccountEdit.vue';
import enums from '../enums';
import { accountApi } from '../api';
import { ElMessage, ElMessageBox } from 'element-plus';
export default defineComponent({
name: 'AccountList',
components: {
RoleEdit,
AccountEdit,
},
setup() {
const state = reactive({
import { dateFormat } from '@/common/utils/date';
const state = reactive({
chooseId: null,
/**
* 选中的数据
@@ -157,6 +125,7 @@ export default defineComponent({
* 查询条件
*/
query: {
username: '',
pageNum: 1,
pageSize: 10,
},
@@ -178,34 +147,45 @@ export default defineComponent({
},
roleDialog: {
visible: false,
account: null,
account: null as any,
roles: [],
},
accountDialog: {
visible: false,
data: null,
data: null as any,
},
});
});
onMounted(() => {
const {
chooseId,
query,
datas,
total,
showRoleDialog,
showResourceDialog,
roleDialog,
accountDialog,
} = toRefs(state)
onMounted(() => {
search();
});
});
const choose = (item: any) => {
const choose = (item: any) => {
if (!item) {
return;
}
state.chooseId = item.id;
state.chooseData = item;
};
};
const search = async () => {
const search = async () => {
let res: any = await accountApi.list.request(state.query);
state.datas = res.list;
state.total = res.total;
};
};
const showResources = async (row: any) => {
const showResources = async (row: any) => {
let showResourceDialog = state.showResourceDialog;
showResourceDialog.title = '"' + row.username + '" 的菜单&权限';
showResourceDialog.resources = [];
@@ -213,18 +193,18 @@ export default defineComponent({
id: row.id,
});
showResourceDialog.visible = true;
};
};
const showRoles = async (row: any) => {
const showRoles = async (row: any) => {
let showRoleDialog = state.showRoleDialog;
showRoleDialog.title = '"' + row.username + '" 的角色信息';
showRoleDialog.accountRoles = await accountApi.roles.request({
id: row.id,
});
showRoleDialog.visible = true;
};
};
const changeStatus = async (row: any) => {
const changeStatus = async (row: any) => {
let id = row.id;
let status = row.status == -1 ? 1 : -1;
await accountApi.changeStatus.request({
@@ -233,42 +213,42 @@ export default defineComponent({
});
ElMessage.success('操作成功');
search();
};
};
const handlePageChange = (curPage: number) => {
const handlePageChange = (curPage: number) => {
state.query.pageNum = curPage;
search();
};
};
const roleEdit = () => {
const showRoleEdit = () => {
if (!state.chooseId) {
ElMessage.error('请选择账号');
}
state.roleDialog.visible = true;
state.roleDialog.account = state.chooseData;
};
};
const editAccount = (isAdd = false) => {
const editAccount = (isAdd = false) => {
if (isAdd) {
state.accountDialog.data = null;
} else {
state.accountDialog.data = state.chooseData;
}
state.accountDialog.visible = true;
};
};
const cancel = () => {
const cancel = () => {
state.roleDialog.visible = false;
state.roleDialog.account = null;
search();
};
};
const valChange = () => {
const valChange = () => {
state.accountDialog.visible = false;
search();
};
};
const deleteAccount = async () => {
const deleteAccount = async () => {
try {
await ElMessageBox.confirm(`确定删除该账号?`, '提示', {
confirmButtonText: '确定',
@@ -280,26 +260,9 @@ export default defineComponent({
state.chooseData = null;
state.chooseId = null;
search();
} catch (err) {}
};
return {
...toRefs(state),
enums,
search,
choose,
showResources,
showRoles,
changeStatus,
handlePageChange,
roleEdit,
editAccount,
cancel,
valChange,
deleteAccount,
};
},
});
} catch (err) { }
};
</script>
<style lang="scss">
</style>

View File

@@ -1,14 +1,11 @@
<template>
<div class="account-dialog">
<el-dialog
:title="account == null ? '' : '分配“' + account.username + '”的角色'"
v-model="dialogVisible"
:before-close="cancel"
:show-close="false"
>
<el-dialog :title="account == null ? '' : '分配“' + account.username + '”的角色'" v-model="dialogVisible"
:before-close="cancel" :show-close="false">
<div class="toolbar">
<div style="float: left">
<el-input placeholder="请输入角色名" style="width: 150px" v-model="query.name" @clear="clear()" clearable></el-input>
<el-input placeholder="请输入角色名" style="width: 150px" v-model="query.name" @clear="clear()" clearable>
</el-input>
<el-button @click="search" type="success" icon="search"></el-button>
</div>
</div>
@@ -22,15 +19,9 @@
</template>
</el-table-column>
</el-table>
<el-pagination
@current-change="handlePageChange"
style="text-align: center; margin-top: 20px"
background
layout="prev, pager, next, total, jumper"
:total="total"
v-model:current-page="query.pageNum"
:page-size="query.pageSize"
></el-pagination>
<el-pagination @current-change="handlePageChange" style="text-align: center; margin-top: 20px" background
layout="prev, pager, next, total, jumper" :total="total" v-model:current-page="query.pageNum"
:page-size="query.pageSize"></el-pagination>
<template #footer>
<div class="dialog-footer">
@@ -42,64 +33,72 @@
</div>
</template>
<script lang="ts">
import { toRefs, reactive, watch, defineComponent, ref } from 'vue';
<script lang="ts" setup>
import { toRefs, reactive, watch, ref } from 'vue';
import { roleApi, accountApi } from '../api';
import { ElMessage } from 'element-plus';
export default defineComponent({
name: 'RoleEdit',
props: {
const props = defineProps({
visible: {
type: Boolean,
},
account: {
type: [Boolean, Object],
},
},
setup(props: any, { emit }) {
const roleTable: any = ref(null);
const state = reactive({
account: Object
})
//定义事件
const emit = defineEmits(['update:visible', 'cancel', 'val-change'])
const roleTable: any = ref(null);
const state = reactive({
dialogVisible: false,
btnLoading: false,
// 所有角色
allRole: [] as any,
// 该账号拥有的角色id
roles: [] as any,
query: {
name: null,
pageNum: 1,
pageSize: 5,
},
total: 0,
});
});
watch(props, (newValue) => {
const {
dialogVisible,
btnLoading,
allRole,
query,
total,
} = toRefs(state)
// 用户拥有的角色信息
let roles: any[] = []
watch(props, (newValue: any) => {
state.dialogVisible = newValue.visible;
if (newValue.account && newValue.account.id != 0) {
if (state.dialogVisible && newValue.account && newValue.account.id != 0) {
accountApi.roleIds
.request({
id: props.account['id'],
id: props.account!.id,
})
.then((res) => {
state.roles = res || [];
roles = res || [];
search();
});
} else {
return;
}
});
});
const handlePageChange = () => {
const handlePageChange = () => {
search();
};
};
const selectable = (row: any) => {
const selectable = (row: any) => {
// 角色code不以COMMON开头才可勾选
return row.code.indexOf('COMMON') != 0;
};
};
const select = (val: any, row: any) => {
let roles = state.roles;
const select = (val: any, row: any) => {
// 如果账号的角色id存在则为取消该角色(删除角色id列表中的该记录id),否则为新增角色
if (roles.includes(row.id)) {
for (let i = 0; i < roles.length; i++) {
@@ -112,70 +111,56 @@ export default defineComponent({
} else {
roles.push(row.id);
}
};
};
/**
/**
* 检查是否勾选权限,即是否拥有权限
*/
const checkSelected = () => {
const checkSelected = () => {
// 必须用异步,否则勾选不了
setTimeout(() => {
roleTable.value.clearSelection();
state.allRole.forEach((r: any) => {
if (state.roles.includes(r.id)) {
if (roles.includes(r.id)) {
roleTable.value.toggleRowSelection(r, true);
}
});
}, 50);
};
};
const btnOk = async () => {
let roleIds = state.roles.join(',');
const btnOk = async () => {
let roleIds = roles.join(',');
await accountApi.saveRoles.request({
id: props.account['id'],
id: props.account!.id,
roleIds: roleIds,
});
ElMessage.success('保存成功!');
cancel();
};
};
/**
/**
* 取消
*/
const cancel = () => {
const cancel = () => {
state.query.pageNum = 1;
state.query.name = null;
emit('update:visible', false);
emit('cancel');
};
};
/**
/**
* 清空查询框
*/
const clear = () => {
const clear = () => {
state.query.pageNum = 1;
state.query.name = null;
search();
};
};
const search = async () => {
const search = async () => {
let res = await roleApi.list.request(state.query);
state.allRole = res.list;
state.total = res.total;
checkSelected();
};
return {
...toRefs(state),
roleTable,
search,
handlePageChange,
selectable,
select,
btnOk,
cancel,
clear,
};
},
});
};
</script>

View File

@@ -1,6 +1,7 @@
<template>
<div>
<el-dialog :title="title" v-model="dvisible" :show-close="false" :before-close="cancel" width="750px" :destroy-on-close="true">
<el-dialog :title="title" v-model="dvisible" :show-close="false" :before-close="cancel" width="750px"
:destroy-on-close="true">
<el-form ref="configForm" :model="form" label-width="90px">
<el-form-item prop="name" label="配置项:" required>
<el-input v-model="form.name"></el-input>
@@ -14,17 +15,25 @@
</el-row>
<el-form-item :key="param" v-for="(param, index) in params" prop="params" :label="`参数${index + 1}`">
<el-row>
<el-col :span="5"><el-input v-model="param.model" placeholder="model"></el-input></el-col>
<el-col :span="5">
<el-input v-model="param.model" placeholder="model"></el-input>
</el-col>
<el-divider :span="1" direction="vertical" border-style="dashed" />
<el-col :span="4"><el-input v-model="param.name" placeholder="字段名"></el-input></el-col>
<el-col :span="4">
<el-input v-model="param.name" placeholder="字段名"></el-input>
</el-col>
<el-divider :span="1" direction="vertical" border-style="dashed" />
<el-col :span="4"><el-input v-model="param.placeholder" placeholder="字段说明"></el-input></el-col>
<el-col :span="4">
<el-input v-model="param.placeholder" placeholder="字段说明"></el-input>
</el-col>
<el-divider :span="1" direction="vertical" border-style="dashed" />
<el-col :span="4">
<el-input v-model="param.options" placeholder="可选值 ,分割"></el-input>
</el-col>
<el-divider :span="1" direction="vertical" border-style="dashed" />
<el-col :span="2"><el-button @click="onDeleteParam(index)" size="small" type="danger">删除</el-button></el-col>
<el-col :span="2">
<el-button @click="onDeleteParam(index)" size="small" type="danger">删除</el-button>
</el-col>
</el-row>
</el-form-item>
<!-- <el-form-item prop="value" label="配置值:" required>
@@ -44,13 +53,11 @@
</div>
</template>
<script lang="ts">
<script lang="ts" setup>
import { ref, toRefs, reactive, watch, defineComponent } from 'vue';
import { configApi } from '../api';
export default defineComponent({
name: 'ConfigEdit',
props: {
const props = defineProps({
visible: {
type: Boolean,
},
@@ -60,10 +67,15 @@ export default defineComponent({
title: {
type: String,
},
},
setup(props: any, { emit }) {
const configForm: any = ref(null);
const state = reactive({
})
//定义事件
const emit = defineEmits(['update:visible', 'cancel', 'val-change'])
const configForm: any = ref(null);
const state = reactive({
dvisible: false,
params: [] as any,
form: {
@@ -75,9 +87,16 @@ export default defineComponent({
remark: '',
},
btnLoading: false,
});
});
watch(props, (newValue) => {
const {
dvisible,
params,
form,
btnLoading,
} = toRefs(state)
watch(props, (newValue: any) => {
state.dvisible = newValue.visible;
if (newValue.data) {
state.form = { ...newValue.data };
@@ -90,24 +109,24 @@ export default defineComponent({
state.form = {} as any;
state.params = [];
}
});
});
const onAddParam = () => {
const onAddParam = () => {
state.params.push({ name: '', model: '', placeholder: '' });
};
};
const onDeleteParam = (idx: number) => {
const onDeleteParam = (idx: number) => {
state.params.splice(idx, 1);
};
};
const cancel = () => {
const cancel = () => {
// 更新父组件visible prop对应的值为false
emit('update:visible', false);
// 若父组件有取消事件,则调用
emit('cancel');
};
};
const btnOk = async () => {
const btnOk = async () => {
configForm.value.validate(async (valid: boolean) => {
if (valid) {
if (state.params) {
@@ -122,18 +141,8 @@ export default defineComponent({
}, 1000);
}
});
};
return {
...toRefs(state),
onAddParam,
onDeleteParam,
configForm,
btnOk,
cancel,
};
},
});
};
</script>
<style lang="scss">
</style>

View File

@@ -2,7 +2,8 @@
<div class="role-list">
<el-card>
<el-button type="primary" icon="plus" @click="editConfig(false)">添加</el-button>
<el-button :disabled="chooseId == null" @click="editConfig(chooseData)" type="primary" icon="edit">编辑</el-button>
<el-button :disabled="chooseId == null" @click="editConfig(chooseData)" type="primary" icon="edit">编辑
</el-button>
<el-table :data="configs" @current-change="choose" ref="table" style="width: 100%">
<el-table-column label="选择" width="55px">
@@ -18,62 +19,42 @@
<el-table-column prop="remark" label="备注" min-width="100px" show-overflow-tooltip></el-table-column>
<el-table-column prop="updateTime" label="更新时间" min-width="100px">
<template #default="scope">
{{ $filters.dateFormat(scope.row.createTime) }}
{{ dateFormat(scope.row.createTime) }}
</template>
</el-table-column>
<el-table-column prop="modifier" label="修改者" show-overflow-tooltip></el-table-column>
<el-table-column label="操作" min-width="50" fixed="right">
<template #default="scope">
<el-link
:disabled="scope.row.status == -1"
type="warning"
@click="showSetConfigDialog(scope.row)"
plain
size="small"
:underline="false"
>配置</el-link
>
<el-link :disabled="scope.row.status == -1" type="warning"
@click="showSetConfigDialog(scope.row)" plain size="small" :underline="false">配置</el-link>
</template>
</el-table-column>
</el-table>
<el-row style="margin-top: 20px" type="flex" justify="end">
<el-pagination
style="text-align: right"
@current-change="handlePageChange"
:total="total"
layout="prev, pager, next, total, jumper"
v-model:current-page="query.pageNum"
:page-size="query.pageSize"
></el-pagination>
<el-pagination style="text-align: right" @current-change="handlePageChange" :total="total"
layout="prev, pager, next, total, jumper" v-model:current-page="query.pageNum"
:page-size="query.pageSize"></el-pagination>
</el-row>
</el-card>
<el-dialog :before-close="closeSetConfigDialog" title="配置项设置" v-model="paramsDialog.visible" width="500px">
<el-form v-if="paramsDialog.paramsFormItem.length > 0" ref="paramsForm" :model="paramsDialog.params" label-width="90px">
<el-form-item v-for="item in paramsDialog.paramsFormItem" :key="item.name" :prop="item.model" :label="item.name" required>
<el-input
v-if="!item.options"
v-model="paramsDialog.params[item.model]"
:placeholder="item.placeholder"
autocomplete="off"
clearable
></el-input>
<el-select
v-else
v-model="paramsDialog.params[item.model]"
:placeholder="item.placeholder"
filterable
autocomplete="off"
clearable
style="width: 100%"
>
<el-option v-for="option in item.options.split(',')" :key="option" :label="option" :value="option" />
<el-form v-if="paramsDialog.paramsFormItem.length > 0" ref="paramsForm" :model="paramsDialog.params"
label-width="90px">
<el-form-item v-for="item in paramsDialog.paramsFormItem" :key="item.name" :prop="item.model"
:label="item.name" required>
<el-input v-if="!item.options" v-model="paramsDialog.params[item.model]"
:placeholder="item.placeholder" autocomplete="off" clearable></el-input>
<el-select v-else v-model="paramsDialog.params[item.model]" :placeholder="item.placeholder"
filterable autocomplete="off" clearable style="width: 100%">
<el-option v-for="option in item.options.split(',')" :key="option" :label="option"
:value="option" />
</el-select>
</el-form-item>
</el-form>
<el-form v-else ref="paramsForm" label-width="90px">
<el-form-item label="配置值" required>
<el-input v-model="paramsDialog.params" :placeholder="paramsDialog.config.remark" autocomplete="off" clearable></el-input>
<el-input v-model="paramsDialog.params" :placeholder="paramsDialog.config.remark" autocomplete="off"
clearable></el-input>
</el-form-item>
</el-form>
<template #footer>
@@ -84,24 +65,19 @@
</template>
</el-dialog>
<config-edit :title="configEdit.title" v-model:visible="configEdit.visible" :data="configEdit.config" @val-change="configEditChange" />
<config-edit :title="configEdit.title" v-model:visible="configEdit.visible" :data="configEdit.config"
@val-change="configEditChange" />
</div>
</template>
<script lang="ts">
import { toRefs, reactive, onMounted, defineComponent } from 'vue';
<script lang="ts" setup>
import { toRefs, reactive, onMounted } from 'vue';
import ConfigEdit from './ConfigEdit.vue';
import { configApi } from '../api';
import { ElMessage, ElMessageBox } from 'element-plus';
export default defineComponent({
name: 'ConfigList',
components: {
ConfigEdit,
},
setup() {
const state = reactive({
dialogFormVisible: false,
currentEditPermissions: false,
import { ElMessage } from 'element-plus';
import { dateFormat } from '@/common/utils/date';
const state = reactive({
query: {
pageNum: 1,
pageSize: 10,
@@ -122,24 +98,34 @@ export default defineComponent({
visible: false,
config: {},
},
});
});
onMounted(() => {
const {
query,
total,
configs,
chooseId,
chooseData,
paramsDialog,
configEdit,
} = toRefs(state)
onMounted(() => {
search();
});
});
const search = async () => {
const search = async () => {
let res = await configApi.list.request(state.query);
state.configs = res.list;
state.total = res.total;
};
};
const handlePageChange = (curPage: number) => {
const handlePageChange = (curPage: number) => {
state.query.pageNum = curPage;
search();
};
};
const showSetConfigDialog = (row: any) => {
const showSetConfigDialog = (row: any) => {
state.paramsDialog.config = row;
// 存在配置项则弹窗提示输入对应的配置项
if (row.params) {
@@ -153,18 +139,18 @@ export default defineComponent({
state.paramsDialog.params = row.value;
}
state.paramsDialog.visible = true;
};
};
const closeSetConfigDialog = () => {
const closeSetConfigDialog = () => {
state.paramsDialog.visible = false;
setTimeout(() => {
state.paramsDialog.config = {};
state.paramsDialog.params = {};
state.paramsDialog.paramsFormItem = [];
}, 300);
};
};
const setConfig = async () => {
const setConfig = async () => {
let paramsValue = state.paramsDialog.params;
if (state.paramsDialog.paramsFormItem.length > 0) {
// 如果配置项删除则需要将value中对应的字段移除
@@ -184,33 +170,33 @@ export default defineComponent({
ElMessage.success('保存成功');
closeSetConfigDialog();
search();
};
};
const hasParam = (paramKey: string, paramItems: any) => {
const hasParam = (paramKey: string, paramItems: any) => {
for (let paramItem of paramItems) {
if (paramItem.model == paramKey) {
return true;
}
}
return false;
};
};
const choose = (item: any) => {
const choose = (item: any) => {
if (!item) {
return;
}
state.chooseId = item.id;
state.chooseData = item;
};
};
const configEditChange = () => {
const configEditChange = () => {
ElMessage.success('保存成功');
state.chooseId = null;
state.chooseData = null;
search();
};
};
const editConfig = (data: any) => {
const editConfig = (data: any) => {
if (data) {
state.configEdit.config = data;
} else {
@@ -218,21 +204,8 @@ export default defineComponent({
}
state.configEdit.visible = true;
};
return {
...toRefs(state),
showSetConfigDialog,
closeSetConfigDialog,
setConfig,
search,
handlePageChange,
choose,
configEditChange,
editConfig,
};
},
});
};
</script>
<style lang="scss">
</style>

View File

@@ -5,8 +5,9 @@
<el-row :gutter="10">
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb10">
<el-form-item prop="type" label="类型" required>
<el-select v-model="form.type" :disabled="typeDisabled" placeholder="请选择" >
<el-option v-for="item in enums.ResourceTypeEnum" :key="item.value" :label="item.label" :value="item.value">
<el-select v-model="form.type" :disabled="typeDisabled" placeholder="请选择">
<el-option v-for="item in enums.ResourceTypeEnum as any" :key="item.value" :label="item.label"
:value="item.value">
</el-option>
</el-select>
</el-form-item>
@@ -27,55 +28,56 @@
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb10">
<el-form-item v-if="form.type === enums.ResourceTypeEnum.MENU.value" label="图标">
<el-form-item v-if="form.type === menuTypeValue" label="图标">
<icon-selector v-model="form.meta.icon" type="ele" />
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb10">
<el-form-item v-if="form.type === enums.ResourceTypeEnum.MENU.value" prop="code" label="路由名">
<el-form-item v-if="form.type === menuTypeValue" prop="code" label="路由名">
<el-input v-model.trim="form.meta.routeName" placeholder="请输入路由名称"></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb10">
<el-form-item v-if="form.type === enums.ResourceTypeEnum.MENU.value" prop="code" label="组件">
<el-form-item v-if="form.type === menuTypeValue" prop="code" label="组件">
<el-input v-model.trim="form.meta.component" placeholder="请输入组件名"></el-input>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb10">
<el-form-item v-if="form.type === enums.ResourceTypeEnum.MENU.value" prop="code" label="是否缓存">
<el-form-item v-if="form.type === menuTypeValue" prop="code" label="是否缓存">
<el-select v-model="form.meta.isKeepAlive" placeholder="请选择" width="w100">
<el-option v-for="item in trueFalseOption" :key="item.value" :label="item.label" :value="item.value"> </el-option>
<el-option v-for="item in trueFalseOption" :key="item.value" :label="item.label"
:value="item.value"> </el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb10">
<el-form-item v-if="form.type === enums.ResourceTypeEnum.MENU.value" prop="code" label="是否隐藏">
<el-form-item v-if="form.type === menuTypeValue" prop="code" label="是否隐藏">
<el-select v-model="form.meta.isHide" placeholder="请选择" width="w100">
<el-option v-for="item in trueFalseOption" :key="item.value" :label="item.label" :value="item.value"> </el-option>
<el-option v-for="item in trueFalseOption" :key="item.value" :label="item.label"
:value="item.value"> </el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb10">
<el-form-item v-if="form.type === enums.ResourceTypeEnum.MENU.value" prop="code" label="tag不可删除">
<el-form-item v-if="form.type === menuTypeValue" prop="code" label="tag不可删除">
<el-select v-model="form.meta.isAffix" placeholder="请选择" width="w100">
<el-option v-for="item in trueFalseOption" :key="item.value" :label="item.label" :value="item.value"> </el-option>
<el-option v-for="item in trueFalseOption" :key="item.value" :label="item.label"
:value="item.value"> </el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb10">
<el-form-item v-if="form.type === enums.ResourceTypeEnum.MENU.value" prop="code" label="是否iframe">
<el-select @change="changeIsIframe" v-model="form.meta.isIframe" placeholder="请选择" width="w100">
<el-option v-for="item in trueFalseOption" :key="item.value" :label="item.label" :value="item.value"> </el-option>
<el-form-item v-if="form.type === menuTypeValue" prop="code" label="是否iframe">
<el-select @change="changeIsIframe" v-model="form.meta.isIframe" placeholder="请选择"
width="w100">
<el-option v-for="item in trueFalseOption" :key="item.value" :label="item.label"
:value="item.value"> </el-option>
</el-select>
</el-form-item>
</el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb10">
<el-form-item
v-if="form.type === enums.ResourceTypeEnum.MENU.value && form.meta.isIframe"
prop="code"
label="iframe地址"
width="w100"
>
<el-form-item v-if="form.type === menuTypeValue && form.meta.isIframe" prop="code"
label="iframe地址" width="w100">
<el-input v-model.trim="form.meta.link" placeholder="请输入iframe url"></el-input>
</el-form-item>
</el-col>
@@ -92,20 +94,15 @@
</div>
</template>
<script lang="ts">
import { ref, toRefs, reactive, watch, defineComponent } from 'vue';
<script lang="ts" setup>
import { ref, toRefs, reactive, watch } from 'vue';
import { ElMessage } from 'element-plus';
import { resourceApi } from '../api';
import enums from '../enums';
import { notEmpty } from '@/common/assert';
import iconSelector from '@/components/iconSelector/index.vue';
export default defineComponent({
name: 'ResourceEdit',
components: {
iconSelector,
},
props: {
const props = defineProps({
visible: {
type: Boolean,
},
@@ -118,11 +115,16 @@ export default defineComponent({
typeDisabled: {
type: Boolean,
},
},
setup(props: any, { emit }) {
const menuForm: any = ref(null);
})
const defaultMeta = {
//定义事件
const emit = defineEmits(['update:visible', 'cancel', 'val-change'])
const menuForm: any = ref(null);
const menuTypeValue = enums.ResourceTypeEnum['MENU'].value
const defaultMeta = {
routeName: '',
icon: 'Menu',
redirect: '',
@@ -131,10 +133,27 @@ export default defineComponent({
isHide: false,
isAffix: false,
isIframe: false,
};
link: '',
};
const state = reactive({
trueFalseOption: [
const rules = {
name: [
{
required: true,
message: '请输入资源名称',
trigger: ['change', 'blur'],
},
],
weight: [
{
required: true,
message: '请输入序号',
trigger: ['change', 'blur'],
},
],
}
const trueFalseOption = [
{
label: '是',
value: true,
@@ -143,19 +162,10 @@ export default defineComponent({
label: '否',
value: false,
},
],
]
const state = reactive({
dialogVisible: false,
//弹出框对象
dialogForm: {
title: '',
visible: false,
data: {},
},
props: {
value: 'id',
label: 'name',
children: 'children',
},
form: {
id: null,
name: null,
@@ -172,30 +182,19 @@ export default defineComponent({
isHide: false,
isAffix: false,
isIframe: false,
link: '',
},
},
// 资源类型选择是否禁用
// typeDisabled: false,
btnLoading: false,
rules: {
name: [
{
required: true,
message: '请输入资源名称',
trigger: ['change', 'blur'],
},
],
weight: [
{
required: true,
message: '请输入序号',
trigger: ['change', 'blur'],
},
],
},
});
});
watch(props, (newValue) => {
const {
dialogVisible,
form,
btnLoading,
} = toRefs(state)
watch(props, (newValue: any) => {
state.dialogVisible = newValue.visible;
if (newValue.data) {
state.form = { ...newValue.data };
@@ -213,16 +212,16 @@ export default defineComponent({
state.form.meta.isHide = meta.isHide ? true : false;
state.form.meta.isAffix = meta.isAffix ? true : false;
state.form.meta.isIframe = meta.isIframe ? true : false;
});
});
// 改变iframe字段如果为是则设置默认的组件
const changeIsIframe = (value: boolean) => {
// 改变iframe字段如果为是则设置默认的组件
const changeIsIframe = (value: boolean) => {
if (value) {
state.form.meta.component = 'RouterParent';
}
};
};
const btnOk = () => {
const btnOk = () => {
const submitForm = { ...state.form };
if (submitForm.type == 1) {
// 如果是菜单则解析meta如果值为false或者''则去除该值
@@ -247,9 +246,9 @@ export default defineComponent({
return false;
}
});
};
};
const parseMenuMeta = (meta: any) => {
const parseMenuMeta = (meta: any) => {
let metaForm: any = {};
// 如果是菜单则校验meta
notEmpty(meta.routeName, '路由名不能为空');
@@ -279,23 +278,12 @@ export default defineComponent({
metaForm.icon = meta.icon;
}
return metaForm;
};
};
const cancel = () => {
const cancel = () => {
emit('update:visible', false);
emit('cancel');
};
return {
...toRefs(state),
enums,
changeIsIframe,
menuForm,
btnOk,
cancel,
};
},
});
};
</script>
<style lang="scss">
// .m-dialog {

View File

@@ -2,100 +2,57 @@
<div class="menu">
<div class="toolbar">
<div>
<span style="font-size: 14px"><SvgIcon name="info-filled"/>红色字体表示禁用状态</span>
<span style="font-size: 14px">
<SvgIcon name="info-filled" />红色字体表示禁用状态
</span>
</div>
<el-button v-auth="'resource:add'" type="primary" icon="plus" @click="addResource(false)">添加</el-button>
</div>
<el-tree
class="none-select"
:indent="38"
node-key="id"
:props="props"
:data="data"
@node-expand="handleNodeExpand"
@node-collapse="handleNodeCollapse"
:default-expanded-keys="defaultExpandedKeys"
:expand-on-click-node="false"
>
<el-tree class="none-select" :indent="38" node-key="id" :props="props" :data="data"
@node-expand="handleNodeExpand" @node-collapse="handleNodeCollapse"
:default-expanded-keys="defaultExpandedKeys" :expand-on-click-node="false">
<template #default="{ data }">
<span class="custom-tree-node">
<span style="font-size: 13px" v-if="data.type === enums.ResourceTypeEnum.MENU.value">
<span style="font-size: 13px" v-if="data.type === menuTypeValue">
<span style="color: #3c8dbc"></span>
{{ data.name }}
<span style="color: #3c8dbc"></span>
<el-tag v-if="data.children !== null" size="small">{{ data.children.length }}</el-tag>
</span>
<span style="font-size: 13px" v-if="data.type === enums.ResourceTypeEnum.PERMISSION.value">
<span style="font-size: 13px" v-if="data.type === permissionTypeValue">
<span style="color: #3c8dbc"></span>
<span :style="data.status == 1 ? 'color: #67c23a;' : 'color: #f67c6c;'">{{ data.name }}</span>
<span style="color: #3c8dbc"></span>
</span>
<el-link @click.prevent="info(data)" style="margin-left: 25px" icon="view" type="info" :underline="false" />
<el-link @click.prevent="info(data)" style="margin-left: 25px" icon="view" type="info"
:underline="false" />
<el-link
v-auth="'resource:update'"
@click.prevent="editResource(data)"
class="ml5"
type="primary"
icon="edit"
:underline="false"
/>
<el-link v-auth="'resource:update'" @click.prevent="editResource(data)" class="ml5" type="primary"
icon="edit" :underline="false" />
<el-link
v-auth="'resource:add'"
@click.prevent="addResource(data)"
v-if="data.type === enums.ResourceTypeEnum.MENU.value"
icon="circle-plus"
:underline="false"
type="success"
class="ml5"
/>
<el-link v-auth="'resource:add'" @click.prevent="addResource(data)"
v-if="data.type === menuTypeValue" icon="circle-plus" :underline="false"
type="success" class="ml5" />
<el-link
v-auth="'resource:changeStatus'"
@click.prevent="changeStatus(data, -1)"
v-if="data.status === 1 && data.type === enums.ResourceTypeEnum.PERMISSION.value"
icon="circle-close"
:underline="false"
type="warning"
class="ml5"
/>
<el-link v-auth="'resource:changeStatus'" @click.prevent="changeStatus(data, -1)"
v-if="data.status === 1 && data.type === permissionTypeValue"
icon="circle-close" :underline="false" type="warning" class="ml5" />
<el-link
v-auth="'resource:changeStatus'"
@click.prevent="changeStatus(data, 1)"
v-if="data.status === -1 && data.type === enums.ResourceTypeEnum.PERMISSION.value"
type="success"
icon="circle-check"
:underline="false"
plain
class="ml5"
/>
<el-link v-auth="'resource:changeStatus'" @click.prevent="changeStatus(data, 1)"
v-if="data.status === -1 && data.type === permissionTypeValue"
type="success" icon="circle-check" :underline="false" plain class="ml5" />
<el-link
v-auth="'resource:delete'"
v-if="data.children == null && data.name !== '首页'"
@click.prevent="deleteMenu(data)"
type="danger"
icon="delete"
:underline="false"
plain
class="ml5"
/>
<el-link v-auth="'resource:delete'" v-if="data.children == null && data.name !== '首页'"
@click.prevent="deleteMenu(data)" type="danger" icon="delete" :underline="false" plain
class="ml5" />
</span>
</template>
</el-tree>
<ResourceEdit
:title="dialogForm.title"
v-model:visible="dialogForm.visible"
v-model:data="dialogForm.data"
:typeDisabled="dialogForm.typeDisabled"
:departTree="data"
:type="dialogForm.type"
@val-change="valChange"
></ResourceEdit>
<ResourceEdit :title="dialogForm.title" v-model:visible="dialogForm.visible" v-model:data="dialogForm.data"
:typeDisabled="dialogForm.typeDisabled" :departTree="data" :type="dialogForm.type" @val-change="valChange">
</ResourceEdit>
<el-dialog v-model="infoDialog.visible">
<el-descriptions title="资源信息" :column="2" border>
@@ -123,40 +80,41 @@
<el-descriptions-item v-if="infoDialog.data.type == menuTypeValue" label="是否iframe">
{{ infoDialog.data.meta.isIframe ? '' : '' }}
</el-descriptions-item>
<el-descriptions-item v-if="infoDialog.data.type == menuTypeValue && infoDialog.data.meta.isIframe" label="iframe url">
<el-descriptions-item v-if="infoDialog.data.type == menuTypeValue && infoDialog.data.meta.isIframe"
label="iframe url">
{{ infoDialog.data.meta.link }}
</el-descriptions-item>
<el-descriptions-item label="创建者">{{ infoDialog.data.creator }}</el-descriptions-item>
<el-descriptions-item label="创建时间">{{ $filters.dateFormat(infoDialog.data.createTime) }}</el-descriptions-item>
<el-descriptions-item label="创建时间">{{ dateFormat(infoDialog.data.createTime) }}
</el-descriptions-item>
<el-descriptions-item label="修改者">{{ infoDialog.data.modifier }}</el-descriptions-item>
<el-descriptions-item label="更新时间">{{ $filters.dateFormat(infoDialog.data.updateTime) }}</el-descriptions-item>
<el-descriptions-item label="更新时间">{{ dateFormat(infoDialog.data.updateTime) }}
</el-descriptions-item>
</el-descriptions>
</el-dialog>
</div>
</template>
<script lang="ts">
import { toRefs, reactive, onMounted, defineComponent } from 'vue';
<script lang="ts" setup>
import { toRefs, reactive, onMounted } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import ResourceEdit from './ResourceEdit.vue';
import enums from '../enums';
import { resourceApi } from '../api';
import { dateFormat } from '@/common/utils/date';
export default defineComponent({
name: 'ResourceList',
components: {
ResourceEdit,
},
setup() {
const state = reactive({
menuTypeValue: enums.ResourceTypeEnum['MENU'].value,
permissionTypeValue: enums.ResourceTypeEnum['PERMISSION'].value,
showBtns: false,
// 当前鼠标右击的节点数据
rightClickData: {},
const menuTypeValue = enums.ResourceTypeEnum['MENU'].value
const permissionTypeValue = enums.ResourceTypeEnum['PERMISSION'].value
const props = {
label: 'name',
children: 'children',
}
const state = reactive({
//弹出框对象
dialogForm: {
type: null,
title: '',
visible: false,
data: { pid: 0, type: 1, weight: 1 },
@@ -169,28 +127,42 @@ export default defineComponent({
visible: false,
// 资源类型选择是否选
data: {
meta: {},
meta: {} as any,
name: '',
type: null,
creator: '',
modifier: '',
createTime: '',
updateTime: '',
weight: null,
code: '',
},
},
data: [],
props: {
label: 'name',
children: 'children',
},
// 展开的节点
defaultExpandedKeys: [] as any[],
});
});
onMounted(() => {
const {
dialogForm,
infoDialog,
data,
defaultExpandedKeys,
} = toRefs(state)
onMounted(() => {
search();
});
});
const search = async () => {
const search = async () => {
let res = await resourceApi.list.request(null);
state.data = res;
};
};
const deleteMenu = (data: any) => {
const deleteMenu = (data: any) => {
ElMessageBox.confirm(`此操作将删除 [${data.name}], 是否继续?`, '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
@@ -206,15 +178,15 @@ export default defineComponent({
search();
});
});
};
};
const addResource = (data: any) => {
const addResource = (data: any) => {
let dialog = state.dialogForm;
dialog.data = { pid: 0, type: 1, weight: 1 };
// 添加顶级菜单情况
if (!data) {
dialog.typeDisabled = true;
dialog.data.type = state.menuTypeValue;
dialog.data.type = menuTypeValue;
dialog.title = '添加顶级菜单';
dialog.visible = true;
return;
@@ -230,23 +202,23 @@ export default defineComponent({
dialog.typeDisabled = true;
let hasPermission = false;
for (let c of data.children) {
if (c.type === state.permissionTypeValue) {
if (c.type === permissionTypeValue) {
hasPermission = true;
break;
}
}
// 如果子节点中存在权限资源,则只能新增权限资源,否则只能新增菜单资源
if (hasPermission) {
dialog.data.type = state.permissionTypeValue;
dialog.data.type = permissionTypeValue;
} else {
dialog.data.type = state.menuTypeValue;
dialog.data.type = menuTypeValue;
}
dialog.data.weight = data.children.length + 1;
}
dialog.visible = true;
};
};
const editResource = async (data: any) => {
const editResource = async (data: any) => {
state.dialogForm.visible = true;
const res = await resourceApi.detail.request({
id: data.id,
@@ -258,32 +230,32 @@ export default defineComponent({
state.dialogForm.data = res;
state.dialogForm.typeDisabled = true;
state.dialogForm.title = '修改“' + data.name + '”菜单';
};
};
const valChange = () => {
const valChange = () => {
search();
state.dialogForm.visible = false;
};
};
const changeStatus = async (data: any, status: any) => {
const changeStatus = async (data: any, status: any) => {
await resourceApi.changeStatus.request({
id: data.id,
status: status,
});
data.status = status;
ElMessage.success((status === 1 ? '启用' : '禁用') + '成功!');
};
};
// 节点被展开时触发的事件
const handleNodeExpand = (data: any, node: any) => {
// 节点被展开时触发的事件
const handleNodeExpand = (data: any, node: any) => {
const id: any = node.data.id;
if (!state.defaultExpandedKeys.includes(id)) {
state.defaultExpandedKeys.push(id);
}
};
};
// 关闭节点
const handleNodeCollapse = (data: any, node: any) => {
// 关闭节点
const handleNodeCollapse = (data: any, node: any) => {
removeDeafultExpandId(node.data.id);
let childNodes = node.childNodes;
@@ -297,38 +269,23 @@ export default defineComponent({
// 递归删除展开的子节点节点id
handleNodeCollapse(data, cn);
}
};
};
const removeDeafultExpandId = (id: any) => {
const removeDeafultExpandId = (id: any) => {
let index = state.defaultExpandedKeys.indexOf(id);
if (index > -1) {
state.defaultExpandedKeys.splice(index, 1);
}
};
};
const info = async (data: any) => {
const info = async (data: any) => {
let info = await resourceApi.detail.request({ id: data.id });
state.infoDialog.data = info;
if (info.meta && info.meta != '') {
state.infoDialog.data.meta = JSON.parse(info.meta);
}
state.infoDialog.visible = true;
};
return {
...toRefs(state),
enums,
deleteMenu,
addResource,
editResource,
valChange,
changeStatus,
handleNodeExpand,
handleNodeCollapse,
info,
};
},
});
};
</script>
<style lang="scss">
.menu {

View File

@@ -1,19 +1,15 @@
<template>
<div>
<el-dialog :title="'分配“' + role.name + '”菜单&权限'" v-model="dialogVisible" :before-close="cancel" :show-close="false" width="400px">
<el-tree
style="height: 50vh; overflow: auto"
ref="menuTree"
:data="resources"
show-checkbox
node-key="id"
:default-checked-keys="defaultCheckedKeys"
:props="defaultProps"
>
<el-dialog :title="'分配“' + roleInfo?.name + '”菜单&权限'" v-model="dialogVisible" :before-close="cancel"
:show-close="false" width="400px">
<el-tree style="height: 50vh; overflow: auto" ref="menuTree" :data="resources" show-checkbox node-key="id"
:default-checked-keys="defaultCheckedKeys" :props="defaultProps">
<template #default="{ node, data }">
<span class="custom-tree-node">
<span v-if="data.type == enums.ResourceTypeEnum.MENU.value">{{ node.label }}</span>
<span v-if="data.type == enums.ResourceTypeEnum.PERMISSION.value" style="color: #67c23a">{{ node.label }}</span>
<span v-if="data.type == enums.ResourceTypeEnum['MENU'].value">{{ node.label }}</span>
<span v-if="data.type == enums.ResourceTypeEnum['PERMISSION'].value" style="color: #67c23a">{{
node.label
}}</span>
</span>
</template>
</el-tree>
@@ -27,15 +23,13 @@
</div>
</template>
<script lang="ts">
import { toRefs, reactive, watch, defineComponent, ref } from 'vue';
<script lang="ts" setup>
import { toRefs, reactive, watch, ref } from 'vue';
import { ElMessage } from 'element-plus';
import { roleApi } from '../api';
import enums from '../enums';
export default defineComponent({
name: 'ResourceEdit',
props: {
const props = defineProps({
visible: {
type: Boolean,
},
@@ -53,76 +47,77 @@ export default defineComponent({
resources: {
type: Array,
},
},
setup(props: any, { emit }) {
const menuTree: any = ref(null);
})
const state = reactive({
dialogVisible: false,
defaultProps: {
//定义事件
const emit = defineEmits(['update:visible', 'cancel', 'val-change'])
const defaultProps = {
children: 'children',
label: 'name',
},
});
}
watch(
const menuTree: any = ref(null);
const state = reactive({
dialogVisible: false,
roleInfo: null as any,
});
const {
dialogVisible,
roleInfo,
} = toRefs(state)
watch(
() => props.visible,
(newValue) => {
state.dialogVisible = newValue;
state.roleInfo = props.role
}
);
);
/**
/**
* 获取所有菜单树的叶子节点
* @param {Object} trees 菜单树列表
*/
const getAllLeafIds = (trees: any) => {
let leafIds: any = [];
for (let tree of trees) {
setLeafIds(tree, leafIds);
}
return leafIds;
};
// const getAllLeafIds = (trees: any) => {
// let leafIds: any = [];
// for (let tree of trees) {
// setLeafIds(tree, leafIds);
// }
// return leafIds;
// };
const setLeafIds = (tree: any, ids: any) => {
if (tree.children !== null) {
for (let t of tree.children) {
setLeafIds(t, ids);
}
} else {
ids.push(tree.id);
}
};
// const setLeafIds = (tree: any, ids: any) => {
// if (tree.children !== null) {
// for (let t of tree.children) {
// setLeafIds(t, ids);
// }
// } else {
// ids.push(tree.id);
// }
// };
const btnOk = async () => {
const btnOk = async () => {
let menuIds = menuTree.value.getCheckedKeys();
let halfMenuIds = menuTree.value.getHalfCheckedKeys();
let resources = [].concat(menuIds, halfMenuIds).join(',');
await roleApi.saveResources.request({
id: props.role['id'],
id: props.role!.id,
resourceIds: resources,
});
ElMessage.success('保存成功!');
emit('cancel');
};
};
const cancel = () => {
const cancel = () => {
// 更新父组件visible prop对应的值为false
emit('update:visible', false);
emit('cancel');
};
return {
...toRefs(state),
enums,
menuTree,
btnOk,
getAllLeafIds,
cancel,
};
},
});
};
</script>
<style>
</style>

View File

@@ -1,17 +1,14 @@
<template>
<div class="role-dialog">
<el-dialog :title="title" v-model="dvisible" :show-close="false" :before-close="cancel" width="500px" :destroy-on-close="true">
<el-dialog :title="title" v-model="dvisible" :show-close="false" :before-close="cancel" width="500px"
:destroy-on-close="true">
<el-form ref="roleForm" :model="form" label-width="90px">
<el-form-item prop="name" label="角色名称:" required>
<el-input v-model="form.name" auto-complete="off"></el-input>
</el-form-item>
<el-form-item prop="code" label="角色code:" required>
<el-input
:disabled="form.id != null"
v-model="form.code"
placeholder="COMMON开头则为所有账号共有角色"
auto-complete="off"
></el-input>
<el-input :disabled="form.id != null" v-model="form.code" placeholder="COMMON开头则为所有账号共有角色"
auto-complete="off"></el-input>
</el-form-item>
<el-form-item label="角色描述:">
<el-input v-model="form.remark" type="textarea" :rows="3" placeholder="请输入角色描述"></el-input>
@@ -27,13 +24,11 @@
</div>
</template>
<script lang="ts">
import { ref, toRefs, reactive, watch, defineComponent } from 'vue';
<script lang="ts" setup>
import { ref, toRefs, reactive, watch } from 'vue';
import { roleApi } from '../api';
export default defineComponent({
name: 'RoleEdit',
props: {
const props = defineProps({
visible: {
type: Boolean,
},
@@ -43,37 +38,47 @@ export default defineComponent({
title: {
type: String,
},
},
setup(props: any, { emit }) {
const roleForm: any = ref(null);
const state = reactive({
})
//定义事件
const emit = defineEmits(['update:visible', 'cancel', 'val-change'])
const roleForm: any = ref(null);
const state = reactive({
dvisible: false,
form: {
id: null,
name: '',
code: '',
status: 1,
remark: '',
},
btnLoading: false,
});
});
watch(props, (newValue) => {
const {
dvisible,
form,
btnLoading,
} = toRefs(state)
watch(props, (newValue: any) => {
state.dvisible = newValue.visible;
if (newValue.data) {
state.form = { ...newValue.data };
} else {
state.form = {} as any;
}
});
});
const cancel = () => {
const cancel = () => {
// 更新父组件visible prop对应的值为false
emit('update:visible', false);
// 若父组件有取消事件,则调用
emit('cancel');
};
};
const btnOk = async () => {
const btnOk = async () => {
roleForm.value.validate(async (valid: boolean) => {
if (valid) {
await roleApi.save.request(state.form);
@@ -85,16 +90,8 @@ export default defineComponent({
}, 1000);
}
});
};
return {
...toRefs(state),
roleForm,
btnOk,
cancel,
};
},
});
};
</script>
<style lang="scss">
</style>

View File

@@ -2,21 +2,16 @@
<div class="role-list">
<el-card>
<el-button v-auth="'role:add'" type="primary" icon="plus" @click="editRole(false)">添加</el-button>
<el-button v-auth="'role:update'" :disabled="chooseId == null" @click="editRole(chooseData)" type="primary" icon="edit">编辑</el-button>
<el-button v-auth="'role:saveResources'" :disabled="chooseId == null" @click="editResource(chooseData)" type="success" icon="setting"
>分配菜单&权限</el-button
>
<el-button v-auth="'role:del'" :disabled="chooseId == null" @click="deleteRole(chooseData)" type="danger" icon="delete">删除</el-button>
<el-button v-auth="'role:update'" :disabled="chooseId == null" @click="editRole(chooseData)" type="primary"
icon="edit">编辑</el-button>
<el-button v-auth="'role:saveResources'" :disabled="chooseId == null" @click="editResource(chooseData)"
type="success" icon="setting">分配菜单&权限</el-button>
<el-button v-auth="'role:del'" :disabled="chooseId == null" @click="deleteRole(chooseData)" type="danger"
icon="delete">删除</el-button>
<div style="float: right">
<el-input
placeholder="请输入角色名称"
class="mr2"
style="width: 200px"
v-model="query.name"
@clear="search"
clearable
></el-input>
<el-input placeholder="请输入角色名称" class="mr2" style="width: 200px" v-model="query.name" @clear="search"
clearable></el-input>
<el-button @click="search" type="success" icon="search"></el-button>
</div>
<el-table :data="roles" @current-change="choose" ref="table" style="width: 100%">
@@ -32,12 +27,12 @@
<el-table-column prop="remark" label="描述" min-width="160px" show-overflow-tooltip></el-table-column>
<el-table-column prop="createTime" label="创建时间">
<template #default="scope">
{{ $filters.dateFormat(scope.row.createTime) }}
{{ dateFormat(scope.row.createTime) }}
</template>
</el-table-column>
<el-table-column prop="updateTime" label="修改时间">
<template #default="scope">
{{ $filters.dateFormat(scope.row.updateTime) }}
{{ dateFormat(scope.row.updateTime) }}
</template>
</el-table-column>
<el-table-column label="查看更多" min-width="80px">
@@ -47,51 +42,32 @@
</el-table-column>
</el-table>
<el-row style="margin-top: 20px" type="flex" justify="end">
<el-pagination
style="text-align: right"
@current-change="handlePageChange"
:total="total"
layout="prev, pager, next, total, jumper"
v-model:current-page="query.pageNum"
:page-size="query.pageSize"
></el-pagination>
<el-pagination style="text-align: right" @current-change="handlePageChange" :total="total"
layout="prev, pager, next, total, jumper" v-model:current-page="query.pageNum"
:page-size="query.pageSize"></el-pagination>
</el-row>
</el-card>
<role-edit :title="roleEdit.title" v-model:visible="roleEdit.visible" :data="roleEdit.role" @val-change="roleEditChange" />
<resource-edit
v-model:visible="resourceDialog.visible"
:role="resourceDialog.role"
:resources="resourceDialog.resources"
:defaultCheckedKeys="resourceDialog.defaultCheckedKeys"
@cancel="cancelEditResources()"
/>
<show-resource
v-model:visible="showResourceDialog.visible"
:title="showResourceDialog.title"
v-model:resources="showResourceDialog.resources"
/>
<role-edit :title="roleEditDialog.title" v-model:visible="roleEditDialog.visible" :data="roleEditDialog.role"
@val-change="roleEditChange" />
<resource-edit v-model:visible="resourceDialog.visible" :role="resourceDialog.role"
:resources="resourceDialog.resources" :defaultCheckedKeys="resourceDialog.defaultCheckedKeys"
@cancel="cancelEditResources()" />
<show-resource v-model:visible="showResourceDialog.visible" :title="showResourceDialog.title"
v-model:resources="showResourceDialog.resources" />
</div>
</template>
<script lang="ts">
import { toRefs, reactive, onMounted, defineComponent } from 'vue';
<script lang="ts" setup>
import { toRefs, reactive, onMounted } from 'vue';
import RoleEdit from './RoleEdit.vue';
import ResourceEdit from './ResourceEdit.vue';
import ShowResource from './ShowResource.vue';
import { roleApi, resourceApi } from '../api';
import { ElMessage, ElMessageBox } from 'element-plus';
export default defineComponent({
name: 'RoleList',
components: {
RoleEdit,
ResourceEdit,
ShowResource,
},
setup() {
const state = reactive({
dialogFormVisible: false,
currentEditPermissions: false,
import { dateFormat } from '@/common/utils/date';
const state = reactive({
query: {
pageNum: 1,
pageSize: 10,
@@ -107,7 +83,7 @@ export default defineComponent({
resources: [],
defaultCheckedKeys: [],
},
roleEdit: {
roleEditDialog: {
title: '角色编辑',
visible: false,
role: {},
@@ -117,49 +93,60 @@ export default defineComponent({
resources: [],
title: '',
},
});
});
onMounted(() => {
const {
query,
total,
roles,
chooseId,
chooseData,
resourceDialog,
roleEditDialog,
showResourceDialog,
} = toRefs(state)
onMounted(() => {
search();
});
});
const search = async () => {
const search = async () => {
let res = await roleApi.list.request(state.query);
state.roles = res.list;
state.total = res.total;
};
};
const handlePageChange = (curPage: number) => {
const handlePageChange = (curPage: number) => {
state.query.pageNum = curPage;
search();
};
};
const choose = (item: any) => {
const choose = (item: any) => {
if (!item) {
return;
}
state.chooseId = item.id;
state.chooseData = item;
};
};
const roleEditChange = () => {
const roleEditChange = () => {
ElMessage.success('修改成功!');
state.chooseId = null;
state.chooseData = null;
search();
};
};
const editRole = (data: any) => {
const editRole = (data: any) => {
if (data) {
state.roleEdit.role = data;
state.roleEditDialog.role = data;
} else {
state.roleEdit.role = false;
state.roleEditDialog.role = false;
}
state.roleEdit.visible = true;
};
state.roleEditDialog.visible = true;
};
const deleteRole = async (data: any) => {
const deleteRole = async (data: any) => {
try {
await ElMessageBox.confirm(
`此操作将删除 [${data.name}] 该角色,以及与该角色有关的账号角色关联信息和资源角色关联信息, 是否继续?`,
@@ -175,23 +162,18 @@ export default defineComponent({
});
ElMessage.success('删除成功!');
search();
} catch (err) {}
};
} catch (err) { }
};
const showResources = async (row: any) => {
const showResources = async (row: any) => {
state.showResourceDialog.resources = await roleApi.roleResources.request({
id: row.id,
});
state.showResourceDialog.title = '"' + row.name + '"的菜单&权限';
state.showResourceDialog.visible = true;
};
};
const closeShowResourceDialog = () => {
state.showResourceDialog.visible = false;
state.showResourceDialog.resources = [];
};
const editResource = async (row: any) => {
const editResource = async (row: any) => {
let menus = await resourceApi.list.request(null);
// 获取所有菜单列表
state.resourceDialog.resources = menus;
@@ -213,21 +195,21 @@ export default defineComponent({
// 显示
state.resourceDialog.visible = true;
state.resourceDialog.role = row;
};
};
/**
/**
* 获取所有菜单树的叶子节点
* @param {Object} trees 菜单树列表
*/
const getAllLeafIds = (trees: any) => {
const getAllLeafIds = (trees: any) => {
let leafIds: any = [];
for (let tree of trees) {
setLeafIds(tree, leafIds);
}
return leafIds;
};
};
const setLeafIds = (tree: any, ids: any) => {
const setLeafIds = (tree: any, ids: any) => {
if (tree.children !== null) {
for (let t of tree.children) {
setLeafIds(t, ids);
@@ -235,34 +217,19 @@ export default defineComponent({
} else {
ids.push(tree.id);
}
};
};
/**
/**
* 取消编辑资源权限树
*/
const cancelEditResources = () => {
const cancelEditResources = () => {
state.resourceDialog.visible = false;
setTimeout(() => {
state.resourceDialog.role = {};
state.resourceDialog.defaultCheckedKeys = [];
}, 10);
};
return {
...toRefs(state),
search,
handlePageChange,
choose,
roleEditChange,
editRole,
deleteRole,
showResources,
closeShowResourceDialog,
editResource,
cancelEditResources,
};
},
});
};
</script>
<style lang="scss">
</style>

View File

@@ -1,13 +1,17 @@
<template>
<div>
<el-dialog @close="closeDialog" :title="title" :before-close="closeDialog" v-model="dialogVisible" width="400px">
<el-dialog @close="closeDialog" :title="title" :before-close="closeDialog" v-model="dialogVisible"
width="400px">
<el-tree style="height: 50vh; overflow: auto" :data="resources" node-key="id" :props="defaultProps">
<template #default="{ node, data }">
<span class="custom-tree-node">
<span v-if="data.type == enums.ResourceTypeEnum.MENU.value">{{ node.label }}</span>
<span v-if="data.type == enums.ResourceTypeEnum.PERMISSION.value" style="color: #67c23a">{{ node.label }}</span>
<span v-if="data.type == enums.ResourceTypeEnum['MENU'].value">{{ node.label }}</span>
<span v-if="data.type == enums.ResourceTypeEnum['PERMISSION'].value" style="color: #67c23a">{{
node.label
}}</span>
<el-link @click.prevent="info(data)" style="margin-left: 25px" icon="el-icon-view" type="info" :underline="false" />
<el-link @click.prevent="info(data)" style="margin-left: 25px" icon="InfoFilled" type="info"
:underline="false" />
</span>
</template>
</el-tree>
@@ -15,14 +19,12 @@
</div>
</template>
<script lang="ts">
import { getCurrentInstance, toRefs, reactive, watch, defineComponent } from 'vue';
<script lang="ts" setup>
import { getCurrentInstance, toRefs, reactive, watch } from 'vue';
import { ElMessageBox } from 'element-plus';
import enums from '../enums';
export default defineComponent({
name: 'ShowResource',
props: {
const props = defineProps({
visible: {
type: Boolean,
},
@@ -32,25 +34,33 @@ export default defineComponent({
title: {
type: String,
},
},
setup(props: any, { emit }) {
const { proxy } = getCurrentInstance() as any;
const state = reactive({
dialogVisible: false,
defaultProps: {
})
//定义事件
const emit = defineEmits(['update:visible', 'update:resources'])
const { proxy } = getCurrentInstance() as any;
const defaultProps = {
children: 'children',
label: 'name',
},
});
}
watch(
const state = reactive({
dialogVisible: false,
});
const {
dialogVisible,
} = toRefs(state)
watch(
() => props.visible,
(newValue) => {
state.dialogVisible = newValue;
}
);
);
const info = (info: any) => {
const info = (info: any) => {
ElMessageBox.alert(
'<strong style="margin-right: 18px">资源名称:</strong>' +
info.name +
@@ -66,24 +76,16 @@ export default defineComponent({
closeOnClickModal: true,
showConfirmButton: false,
}
).catch(() => {});
).catch(() => { });
return;
};
};
const closeDialog = () => {
const closeDialog = () => {
emit('update:visible', false);
emit('update:resources', []);
};
return {
...toRefs(state),
enums,
info,
closeDialog,
};
},
});
};
</script>
<style>
</style>

View File

@@ -2,16 +2,10 @@
<div class="role-list">
<el-card>
<div style="float: right">
<el-select
remote
:remote-method="getAccount"
v-model="query.creatorId"
filterable
placeholder="请输入并选择账号"
clearable
class="mr5"
>
<el-option v-for="item in accounts" :key="item.id" :label="item.username" :value="item.id"> </el-option>
<el-select remote :remote-method="getAccount" v-model="query.creatorId" filterable
placeholder="请输入并选择账号" clearable class="mr5">
<el-option v-for="item in accounts" :key="item.id" :label="item.username" :value="item.id">
</el-option>
</el-select>
<el-select v-model="query.type" filterable placeholder="请选择操作结果" clearable class="mr5">
<el-option label="成功" :value="1"> </el-option>
@@ -23,7 +17,7 @@
<el-table-column prop="creator" label="操作人" min-width="100" show-overflow-tooltip></el-table-column>
<el-table-column prop="createTime" label="操作时间" min-width="160">
<template #default="scope">
{{ $filters.dateFormat(scope.row.createTime) }}
{{ dateFormat(scope.row.createTime) }}
</template>
</el-table-column>
<el-table-column prop="type" label="结果" min-width="65">
@@ -38,66 +32,61 @@
<el-table-column prop="resp" label="响应信息" min-width="200" show-overflow-tooltip></el-table-column>
</el-table>
<el-row style="margin-top: 20px" type="flex" justify="end">
<el-pagination
style="text-align: right"
@current-change="handlePageChange"
:total="total"
layout="prev, pager, next, total, jumper"
v-model:current-page="query.pageNum"
:page-size="query.pageSize"
></el-pagination>
<el-pagination style="text-align: right" @current-change="handlePageChange" :total="total"
layout="prev, pager, next, total, jumper" v-model:current-page="query.pageNum"
:page-size="query.pageSize"></el-pagination>
</el-row>
</el-card>
</div>
</template>
<script lang="ts">
import { toRefs, reactive, onMounted, defineComponent } from 'vue';
<script lang="ts" setup>
import { toRefs, reactive, onMounted } from 'vue';
import { logApi, accountApi } from '../api';
export default defineComponent({
name: 'SyslogList',
components: {},
setup() {
const state = reactive({
import { dateFormat } from '@/common/utils/date';
const state = reactive({
query: {
type: null,
creatorId: null,
pageNum: 1,
pageSize: 10,
name: null,
},
total: 0,
logs: [],
accounts: [],
});
accounts: [] as any,
});
onMounted(() => {
const {
query,
total,
logs,
accounts,
} = toRefs(state)
onMounted(() => {
search();
});
});
const search = async () => {
const search = async () => {
let res = await logApi.list.request(state.query);
state.logs = res.list;
state.total = res.total;
};
};
const handlePageChange = (curPage: number) => {
const handlePageChange = (curPage: number) => {
state.query.pageNum = curPage;
search();
};
};
const getAccount = (username: any) => {
const getAccount = (username: any) => {
accountApi.list.request({ username }).then((res) => {
state.accounts = res.list;
});
};
};
return {
...toRefs(state),
search,
handlePageChange,
getAccount,
};
},
});
</script>
<style lang="scss">
</style>

View File

@@ -17,7 +17,7 @@ const (
WHERE table_schema = (SELECT database())`
// mysql 索引信息
MYSQL_INDEX_INFO = `SELECT INDEX_NAME indexName, COLUMN_NAME columnName, INDEX_TYPE indexType, NON_UNIQUE nonUnique,
MYSQL_INDEX_INFO = `SELECT index_name indexName, column_name columnName, index_type indexType, non_unique nonUnique,
SEQ_IN_INDEX seqInIndex, INDEX_COMMENT indexComment
FROM information_schema.STATISTICS
WHERE table_schema = (SELECT database()) AND table_name = '%s' ORDER BY index_name asc , SEQ_IN_INDEX asc`
@@ -73,20 +73,16 @@ func (mm *MysqlMetadata) GetTableInfos() []map[string]interface{} {
func (mm *MysqlMetadata) GetTableIndex(tableName string) []map[string]interface{} {
res, err := mm.di.innerSelect(fmt.Sprintf(MYSQL_INDEX_INFO, tableName))
biz.ErrIsNilAppendErr(err, "获取表索引信息失败: %s")
// 把查询结果以索引名分组,索引字段以逗号连接
result := make([]map[string]interface{}, 0)
key := ""
i := 0
for k, v := range res {
// 当前的索引名
in := fmt.Sprintf("%v", v["indexName"])
cl := fmt.Sprintf("%v", v["columnName"])
in := v["indexName"].(string)
if key == in {
// 同索引字段以逗号连接
cl1 := fmt.Sprintf("%v", result[i]["columnName"])
result[i]["columnName"] = cl1 + "," + cl
result[i]["columnName"] = result[i]["columnName"].(string) + "," + v["columnName"].(string)
} else {
i = k
key = in

View File

@@ -31,7 +31,7 @@ func (d *dbRepoImpl) GetDbList(condition *entity.DbQuery, pageParam *model.PageP
if condition.TagPathLike != "" {
sql = sql + " AND d.tag_path LIKE '" + condition.TagPathLike + "%'"
}
sql = sql + " ORDER BY d.create_time DESC"
sql = sql + " ORDER BY d.tag_path"
return model.GetPageBySql(sql, pageParam, toEntity)
}

View File

@@ -31,7 +31,7 @@ func (m *machineRepoImpl) GetMachineList(condition *entity.MachineQuery, pagePar
if condition.TagPathLike != "" {
sql = sql + " AND m.tag_path LIKE '" + condition.TagPathLike + "%'"
}
sql = sql + " ORDER BY m.tag_id, m.create_time DESC"
sql = sql + " ORDER BY m.tag_path"
return model.GetPageBySql(sql, pageParam, toEntity)
}

View File

@@ -26,7 +26,7 @@ func (d *mongoRepoImpl) GetList(condition *entity.MongoQuery, pageParam *model.P
if condition.TagPathLike != "" {
sql = sql + " AND d.tag_path LIKE '" + condition.TagPathLike + "%'"
}
sql = sql + " ORDER BY d.create_time DESC"
sql = sql + " ORDER BY d.tag_path"
return model.GetPageBySql(sql, pageParam, toEntity)
}

View File

@@ -29,7 +29,7 @@ func (r *redisRepoImpl) GetRedisList(condition *entity.RedisQuery, pageParam *mo
if condition.TagPathLike != "" {
sql = sql + " AND d.tag_path LIKE '" + condition.TagPathLike + "%'"
}
sql = sql + " ORDER BY d.create_time DESC"
sql = sql + " ORDER BY d.tag_path"
return model.GetPageBySql(sql, pageParam, toEntity)
}

View File

@@ -2,8 +2,9 @@ package form
type AccountCreateForm struct {
Id uint64
Username *string `json:"username" binding:"required,min=4,max=16"`
Password *string `json:"password"`
Name string `json:"name" binding:"required"`
Username string `json:"username" binding:"required,min=4,max=16"`
Password string `json:"password"`
}
type AccountUpdateForm struct {

View File

@@ -7,7 +7,8 @@ import (
type AccountManageVO struct {
model.Model
Username *string `json:"username"`
Name string `json:"name"`
Username string `json:"username"`
Status int `json:"status"`
LastLoginTime *time.Time `json:"lastLoginTime"`
}

View File

@@ -8,6 +8,7 @@ import (
type Account struct {
model.Model
Name string `json:"name"`
Username string `json:"username"`
Password string `json:"-"`
Status int8 `json:"status"`

View File

@@ -56,7 +56,7 @@ func InitAccountRouter(router *gin.RouterGroup) {
ctx.NewReqCtxWithGin(c).Handle(a.Accounts)
})
createAccount := ctx.NewLogInfo("创建账号").WithSave(true)
createAccount := ctx.NewLogInfo("保存账号信息").WithSave(true)
addAccountPermission := ctx.NewPermission("account:add")
account.POST("", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c).

View File

@@ -0,0 +1,6 @@
package form
type TeamMember struct {
TeamId uint64 `json:"teamId"`
AccountIds []uint64 `json:"accountIds"`
}

View File

@@ -5,6 +5,7 @@ import (
sys_applicaiton "mayfly-go/internal/sys/application"
sys_entity "mayfly-go/internal/sys/domain/entity"
"mayfly-go/internal/tag/api/form"
"mayfly-go/internal/tag/api/vo"
"mayfly-go/internal/tag/application"
"mayfly-go/internal/tag/domain/entity"
"mayfly-go/pkg/biz"
@@ -52,26 +53,38 @@ func (p *Team) DelTeam(rc *ctx.ReqCtx) {
// 获取团队的成员信息
func (p *Team) GetTeamMembers(rc *ctx.ReqCtx) {
teamMems := &[]entity.TeamMember{}
rc.ResData = p.TeamApp.GetMemberPage(&entity.TeamMember{TeamId: uint64(ginx.PathParamInt(rc.GinCtx, "id"))},
ginx.GetPageParam(rc.GinCtx), teamMems)
condition := &entity.TeamMember{TeamId: uint64(ginx.PathParamInt(rc.GinCtx, "id"))}
condition.Username = rc.GinCtx.Query("username")
rc.ResData = p.TeamApp.GetMemberPage(condition, ginx.GetPageParam(rc.GinCtx), &[]vo.TeamMember{})
}
// 保存团队信息
func (p *Team) SaveTeamMember(rc *ctx.ReqCtx) {
projectMem := &entity.TeamMember{}
ginx.BindJsonAndValid(rc.GinCtx, projectMem)
teamMems := &form.TeamMember{}
ginx.BindJsonAndValid(rc.GinCtx, teamMems)
rc.ReqParam = fmt.Sprintf("projectId: %d, username: %s", projectMem.TeamId, projectMem.Username)
teamId := teamMems.TeamId
for _, accountId := range teamMems.AccountIds {
if p.TeamApp.IsExistMember(teamId, accountId) {
continue
}
// 校验账号并赋值username
account := &sys_entity.Account{}
account.Id = projectMem.AccountId
account.Id = accountId
biz.ErrIsNil(p.AccountApp.GetAccount(account, "Id", "Username"), "账号不存在")
projectMem.Username = account.Username
projectMem.SetBaseInfo(rc.LoginAccount)
p.TeamApp.SaveMember(projectMem)
teamMember := new(entity.TeamMember)
teamMember.TeamId = teamId
teamMember.AccountId = accountId
teamMember.Username = account.Username
teamMember.SetBaseInfo(rc.LoginAccount)
p.TeamApp.SaveMember(teamMember)
}
rc.ReqParam = teamMems
}
// 删除团队成员

View File

@@ -1,10 +0,0 @@
package vo
// 用户选择项目
type AccountProject struct {
Id uint64 `json:"id"`
Name string `json:"name"`
Remark string `json:"remark"`
}
type AccountProjects []AccountProject

View File

@@ -0,0 +1,14 @@
package vo
import "time"
// 团队成员信息
type TeamMember struct {
Id uint64 `json:"id"`
TeamId uint64 `json:"teamId"`
AccountId uint64 `json:"accountId"`
Username string `json:"username"`
Name string `json:"name"`
Creator string `json:"creator"`
CreateTime *time.Time `json:"createTime"`
}

View File

@@ -17,12 +17,14 @@ type Team interface {
//--------------- 团队成员相关接口 ---------------
GetMemberPage(condition *entity.TeamMember, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult
GetMemberPage(condition *entity.TeamMember, pageParam *model.PageParam, toEntity interface{}) *model.PageResult
SaveMember(projectTeamMember *entity.TeamMember)
DeleteMember(teamId, accountId uint64)
IsExistMember(teamId, accounId uint64) bool
// 账号是否有权限访问该项目关联的资源信息
// CanAccess(accountId, projectId uint64) error
@@ -71,8 +73,8 @@ func (p *projectTeamAppImpl) Delete(id uint64) {
// --------------- 团队成员相关接口 ---------------
func (p *projectTeamAppImpl) GetMemberPage(condition *entity.TeamMember, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult {
return p.projectTeamMemberRepo.GetPageList(condition, pageParam, toEntity, orderBy...)
func (p *projectTeamAppImpl) GetMemberPage(condition *entity.TeamMember, pageParam *model.PageParam, toEntity interface{}) *model.PageResult {
return p.projectTeamMemberRepo.GetPageList(condition, pageParam, toEntity)
}
// 保存团队成员信息
@@ -87,6 +89,10 @@ func (p *projectTeamAppImpl) DeleteMember(teamId, accountId uint64) {
p.projectTeamMemberRepo.DeleteBy(&entity.TeamMember{TeamId: teamId, AccountId: accountId})
}
func (p *projectTeamAppImpl) IsExistMember(teamId, accounId uint64) bool {
return p.projectTeamMemberRepo.IsExist(teamId, accounId)
}
//--------------- 关联项目相关接口 ---------------
func (p *projectTeamAppImpl) ListTagIds(teamId uint64) []uint64 {

View File

@@ -12,7 +12,7 @@ type TeamMember interface {
Save(mp *entity.TeamMember)
GetPageList(condition *entity.TeamMember, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult
GetPageList(condition *entity.TeamMember, pageParam *model.PageParam, toEntity interface{}) *model.PageResult
DeleteBy(condition *entity.TeamMember)

View File

@@ -27,6 +27,6 @@ func (p *tagTreeTeamRepoImpl) DeleteBy(condition *entity.TagTreeTeam) {
func (p *tagTreeTeamRepoImpl) SelectTagPathsByAccountId(accountId uint64) []string {
var res []string
model.GetListBySql2Model("SELECT DISTINCT(t1.tag_path) FROM t_tag_tree_team t1 JOIN t_team_member t2 ON t1.team_id = t2.team_id WHERE t2.account_id = ?", &res, accountId)
model.GetListBySql2Model("SELECT DISTINCT(t1.tag_path) FROM t_tag_tree_team t1 JOIN t_team_member t2 ON t1.team_id = t2.team_id WHERE t2.account_id = ? ORDER BY t1.tag_path", &res, accountId)
return res
}

View File

@@ -1,6 +1,7 @@
package persistence
import (
"fmt"
"mayfly-go/internal/tag/domain/entity"
"mayfly-go/internal/tag/domain/repository"
"mayfly-go/pkg/biz"
@@ -21,8 +22,20 @@ func (p *teamMemberRepoImpl) Save(pm *entity.TeamMember) {
biz.ErrIsNilAppendErr(model.Insert(pm), "保存团队成员失败:%s")
}
func (p *teamMemberRepoImpl) GetPageList(condition *entity.TeamMember, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult {
return model.GetPage(pageParam, condition, condition, toEntity, orderBy...)
func (p *teamMemberRepoImpl) GetPageList(condition *entity.TeamMember, pageParam *model.PageParam, toEntity interface{}) *model.PageResult {
sql := "SELECT d.*, a.name FROM t_team_member d JOIN t_sys_account a ON d.account_id = a.id WHERE a.status = 1 "
if condition.AccountId != 0 {
sql = fmt.Sprintf("%s AND d.account_id = %d", sql, condition.AccountId)
}
if condition.TeamId != 0 {
sql = fmt.Sprintf("%s AND d.team_id = %d", sql, condition.TeamId)
}
if condition.Username != "" {
sql = sql + " AND d.Username LIKE '%" + condition.Username + "%'"
}
sql = sql + " ORDER BY d.id DESC"
return model.GetPageBySql(sql, pageParam, toEntity)
}
func (p *teamMemberRepoImpl) DeleteBy(condition *entity.TeamMember) {

View File

@@ -283,6 +283,7 @@ COMMIT;
DROP TABLE IF EXISTS `t_sys_account`;
CREATE TABLE `t_sys_account` (
`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
`username` varchar(30) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
`password` varchar(64) CHARACTER SET utf8mb4 COLLATE utf8mb4_bin NOT NULL,
`status` tinyint(4) DEFAULT NULL,

View File

@@ -1 +0,0 @@
import{_ as s,b as n,k as l,p as c,y as e,q as d,w as m,I as f,B as u,C as _,m as p,A as h}from"./index.1666839152545.js";var x="assets/401.1666839152545.png";const v={name:"401",setup(){const t=n();return{onSetAuth:()=>{f(),t.push("/login")}}}},o=t=>(u("data-v-6ec92039"),t=t(),_(),t),g={class:"error"},y={class:"error-flex"},C={class:"left"},b={class:"left-item"},A=o(()=>e("div",{class:"left-item-animation left-item-num"},"401",-1)),B=o(()=>e("div",{class:"left-item-animation left-item-title"},"\u60A8\u672A\u88AB\u6388\u6743\u6216\u767B\u5F55\u8D85\u65F6\uFF0C\u6CA1\u6709\u64CD\u4F5C\u6743\u9650",-1)),w=o(()=>e("div",{class:"left-item-animation left-item-msg"},null,-1)),S={class:"left-item-animation left-item-btn"},k=o(()=>e("div",{class:"right"},[e("img",{src:x})],-1));function F(t,r,I,a,z,D){const i=l("el-button");return p(),c("div",g,[e("div",y,[e("div",C,[e("div",b,[A,B,w,e("div",S,[d(i,{type:"primary",round:"",onClick:a.onSetAuth},{default:m(()=>[h("\u91CD\u65B0\u767B\u5F55")]),_:1},8,["onClick"])])])]),k])])}var V=s(v,[["render",F],["__scopeId","data-v-6ec92039"]]);export{V as default};

View File

@@ -0,0 +1 @@
import{_ as s,b as n,h as l,j as c,q as e,k as d,w as f,K as m,x as u,y as _,i as p,v as h}from"./index.1667044971054.js";var x="assets/401.1667044971054.png";const v={name:"401",setup(){const t=n();return{onSetAuth:()=>{m(),t.push("/login")}}}},o=t=>(u("data-v-6ec92039"),t=t(),_(),t),g={class:"error"},y={class:"error-flex"},b={class:"left"},C={class:"left-item"},w=o(()=>e("div",{class:"left-item-animation left-item-num"},"401",-1)),A=o(()=>e("div",{class:"left-item-animation left-item-title"},"\u60A8\u672A\u88AB\u6388\u6743\u6216\u767B\u5F55\u8D85\u65F6\uFF0C\u6CA1\u6709\u64CD\u4F5C\u6743\u9650",-1)),B=o(()=>e("div",{class:"left-item-animation left-item-msg"},null,-1)),S={class:"left-item-animation left-item-btn"},k=o(()=>e("div",{class:"right"},[e("img",{src:x})],-1));function F(t,r,I,a,z,D){const i=l("el-button");return p(),c("div",g,[e("div",y,[e("div",b,[e("div",C,[w,A,B,e("div",S,[d(i,{type:"primary",round:"",onClick:a.onSetAuth},{default:f(()=>[h("\u91CD\u65B0\u767B\u5F55")]),_:1},8,["onClick"])])])]),k])])}var V=s(v,[["render",F],["__scopeId","data-v-6ec92039"]]);export{V as default};

View File

Before

Width:  |  Height:  |  Size: 39 KiB

After

Width:  |  Height:  |  Size: 39 KiB

View File

@@ -1 +0,0 @@
import{_ as s,b as n,k as l,p as c,y as e,q as m,w as d,B as f,C as u,m as _,A as p}from"./index.1666839152545.js";var x="assets/404.1666839152545.png";const h={name:"404",setup(){const t=n();return{onGoHome:()=>{t.push("/")}}}},o=t=>(f("data-v-69e91ac8"),t=t(),u(),t),v={class:"error"},g={class:"error-flex"},y={class:"left"},F={class:"left-item"},C=o(()=>e("div",{class:"left-item-animation left-item-num"},"404",-1)),b=o(()=>e("div",{class:"left-item-animation left-item-title"},"\u5730\u5740\u8F93\u5165\u6709\u8BEF\uFF0C\u8BF7\u91CD\u65B0\u8F93\u5165\u5730\u5740~",-1)),B=o(()=>e("div",{class:"left-item-animation left-item-msg"},"\u60A8\u53EF\u4EE5\u5148\u68C0\u67E5\u7F51\u5740\uFF0C\u7136\u540E\u91CD\u65B0\u8F93\u5165",-1)),E={class:"left-item-animation left-item-btn"},w=o(()=>e("div",{class:"right"},[e("img",{src:x})],-1));function k(t,a,D,r,I,z){const i=l("el-button");return _(),c("div",v,[e("div",g,[e("div",y,[e("div",F,[C,b,B,e("div",E,[m(i,{type:"primary",round:"",onClick:r.onGoHome},{default:d(()=>[p("\u8FD4\u56DE\u9996\u9875")]),_:1},8,["onClick"])])])]),w])])}var H=s(h,[["render",k],["__scopeId","data-v-69e91ac8"]]);export{H as default};

View File

@@ -0,0 +1 @@
import{_ as s,b as n,h as l,j as c,q as e,k as m,w as d,x as f,y as u,i as _,v as p}from"./index.1667044971054.js";var x="assets/404.1667044971054.png";const h={name:"404",setup(){const t=n();return{onGoHome:()=>{t.push("/")}}}},o=t=>(f("data-v-69e91ac8"),t=t(),u(),t),v={class:"error"},g={class:"error-flex"},y={class:"left"},F={class:"left-item"},b=o(()=>e("div",{class:"left-item-animation left-item-num"},"404",-1)),C=o(()=>e("div",{class:"left-item-animation left-item-title"},"\u5730\u5740\u8F93\u5165\u6709\u8BEF\uFF0C\u8BF7\u91CD\u65B0\u8F93\u5165\u5730\u5740~",-1)),E=o(()=>e("div",{class:"left-item-animation left-item-msg"},"\u60A8\u53EF\u4EE5\u5148\u68C0\u67E5\u7F51\u5740\uFF0C\u7136\u540E\u91CD\u65B0\u8F93\u5165",-1)),w={class:"left-item-animation left-item-btn"},B=o(()=>e("div",{class:"right"},[e("img",{src:x})],-1));function k(t,a,D,r,I,z){const i=l("el-button");return _(),c("div",v,[e("div",g,[e("div",y,[e("div",F,[b,C,E,e("div",w,[m(i,{type:"primary",round:"",onClick:r.onGoHome},{default:d(()=>[p("\u8FD4\u56DE\u9996\u9875")]),_:1},8,["onClick"])])])]),B])])}var H=s(h,[["render",k],["__scopeId","data-v-69e91ac8"]]);export{H as default};

View File

Before

Width:  |  Height:  |  Size: 36 KiB

After

Width:  |  Height:  |  Size: 36 KiB

View File

@@ -1 +1 @@
import{Q as r}from"./index.1666839152545.js";class s{constructor(t,e){this.url=t,this.method=e}setUrl(t){return this.url=t,this}setMethod(t){return this.method=t,this}getUrl(){return r.getApiUrl(this.url)}request(t=null,e=null){return r.send(this,t,e)}requestWithHeaders(t,e){return r.sendWithHeaders(this,t,e)}static create(t,e){return new s(t,e)}}export{s as A};
import{S as r}from"./index.1667044971054.js";class s{constructor(t,e){this.url=t,this.method=e}setUrl(t){return this.url=t,this}setMethod(t){return this.method=t,this}getUrl(){return r.getApiUrl(this.url)}request(t=null,e=null){return r.send(this,t,e)}requestWithHeaders(t,e){return r.sendWithHeaders(this,t,e)}static create(t,e){return new s(t,e)}}export{s as A};

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1 @@
.home-container[data-v-8fc94e0e]{overflow-x:hidden}.home-container .home-card-item[data-v-8fc94e0e]{width:100%;height:103px;background:gray;border-radius:4px;transition:all ease .3s;cursor:pointer}.home-container .home-card-item[data-v-8fc94e0e]:hover{box-shadow:0 2px 12px #0000001a;transition:all ease .3s}.home-container .home-card-item-box[data-v-8fc94e0e]{display:flex;align-items:center;position:relative;overflow:hidden}.home-container .home-card-item-box:hover i[data-v-8fc94e0e]{right:0px!important;bottom:0px!important;transition:all ease .3s}.home-container .home-card-item-box i[data-v-8fc94e0e]{position:absolute;right:-10px;bottom:-10px;font-size:70px;transform:rotate(-30deg);transition:all ease .3s}.home-container .home-card-item-box .home-card-item-flex[data-v-8fc94e0e]{padding:0 20px;color:#fff}.home-container .home-card-item-box .home-card-item-flex .home-card-item-title[data-v-8fc94e0e],.home-container .home-card-item-box .home-card-item-flex .home-card-item-tip[data-v-8fc94e0e]{font-size:13px}.home-container .home-card-item-box .home-card-item-flex .home-card-item-title-num[data-v-8fc94e0e]{font-size:18px}.home-container .home-card-item-box .home-card-item-flex .home-card-item-tip-num[data-v-8fc94e0e]{font-size:13px}.home-container .home-card-first[data-v-8fc94e0e]{background:white;border:1px solid #ebeef5;display:flex;align-items:center}.home-container .home-card-first img[data-v-8fc94e0e]{width:60px;height:60px;border-radius:100%;border:2px solid var(--color-primary-light-5)}.home-container .home-card-first .home-card-first-right[data-v-8fc94e0e]{flex:1;display:flex;flex-direction:column}.home-container .home-card-first .home-card-first-right .home-card-first-right-msg[data-v-8fc94e0e]{font-size:13px;color:gray}.home-container .home-monitor[data-v-8fc94e0e]{height:200px}.home-container .home-monitor .flex-warp-item[data-v-8fc94e0e]{width:50%;height:100px;display:flex}.home-container .home-monitor .flex-warp-item .flex-warp-item-box[data-v-8fc94e0e]{margin:auto;height:auto;text-align:center}.home-container .home-warning-card[data-v-8fc94e0e]{height:292px}.home-container .home-warning-card[data-v-8fc94e0e] .el-card{height:100%}.home-container .home-dynamic[data-v-8fc94e0e]{height:200px}.home-container .home-dynamic .home-dynamic-item[data-v-8fc94e0e]{display:flex;width:100%;height:60px;overflow:hidden}.home-container .home-dynamic .home-dynamic-item:first-of-type .home-dynamic-item-line i[data-v-8fc94e0e]{color:orange!important}.home-container .home-dynamic .home-dynamic-item .home-dynamic-item-left[data-v-8fc94e0e]{text-align:right}.home-container .home-dynamic .home-dynamic-item .home-dynamic-item-left .home-dynamic-item-left-time2[data-v-8fc94e0e]{font-size:13px;color:gray}.home-container .home-dynamic .home-dynamic-item .home-dynamic-item-line[data-v-8fc94e0e]{height:60px;border-right:2px dashed #dfdfdf;margin:0 20px;position:relative}.home-container .home-dynamic .home-dynamic-item .home-dynamic-item-line i[data-v-8fc94e0e]{color:var(--color-primary);font-size:12px;position:absolute;top:1px;left:-6px;transform:rotate(46deg);background:white}.home-container .home-dynamic .home-dynamic-item .home-dynamic-item-right[data-v-8fc94e0e]{flex:1}.home-container .home-dynamic .home-dynamic-item .home-dynamic-item-right .home-dynamic-item-right-title i[data-v-8fc94e0e]{margin-right:5px;border:1px solid #dfdfdf;width:20px;height:20px;border-radius:100%;padding:3px 2px 2px;text-align:center;color:var(--color-primary)}.home-container .home-dynamic .home-dynamic-item .home-dynamic-item-right .home-dynamic-item-right-label[data-v-8fc94e0e]{font-size:13px;color:gray}

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -1 +0,0 @@
var i=Object.defineProperty;var a=Object.getOwnPropertySymbols;var m=Object.prototype.hasOwnProperty,c=Object.prototype.propertyIsEnumerable;var s=(n,e,t)=>e in n?i(n,e,{enumerable:!0,configurable:!0,writable:!0,value:t}):n[e]=t,o=(n,e)=>{for(var t in e||(e={}))m.call(e,t)&&s(n,t,e[t]);if(a)for(var t of a(e))c.call(e,t)&&s(n,t,e[t]);return n};import{S as h}from"./SshTerminal.1666839152545.js";import{_ as p,d,a as l,c as u,e as f,t as _,k as g,p as I,q as v,m as S}from"./index.1666839152545.js";const $=d({name:"SshTerminalPage",components:{SshTerminal:h},props:{machineId:{type:Number}},setup(){const n=l(),e=u({machineId:0,height:700});return f(()=>{e.height=window.innerHeight+5,e.machineId=Number.parseInt(n.query.id)}),o({},_(e))}});function k(n,e,t,N,T,b){const r=g("ssh-terminal");return S(),I("div",null,[v(r,{ref:"terminal",machineId:n.machineId,height:n.height+"px"},null,8,["machineId","height"])])}var y=p($,[["render",k]]);export{y as default};

View File

@@ -0,0 +1 @@
import{_ as s}from"./SshTerminal.1667044971054.js";import{d as o,a as r,c,t as h,e as m,j as d,k as u,l as t,i as l}from"./index.1667044971054.js";const k=o({__name:"SshTerminalPage",setup(_){const a=r(),e=c({machineId:0,height:700}),{machineId:n,height:i}=h(e);return m(()=>{e.height=window.innerHeight+5,e.machineId=Number.parseInt(a.query.id)}),(p,f)=>(l(),d("div",null,[u(s,{ref:"terminal",machineId:t(n),height:t(i)+"px"},null,8,["machineId","height"])]))}});export{k as default};

View File

@@ -1 +0,0 @@
var k=Object.defineProperty,B=Object.defineProperties;var N=Object.getOwnPropertyDescriptors;var f=Object.getOwnPropertySymbols;var z=Object.prototype.hasOwnProperty,A=Object.prototype.propertyIsEnumerable;var h=(e,t,a)=>t in e?k(e,t,{enumerable:!0,configurable:!0,writable:!0,value:a}):e[t]=a,w=(e,t)=>{for(var a in t||(t={}))z.call(t,a)&&h(e,a,t[a]);if(f)for(var a of f(t))A.call(t,a)&&h(e,a,t[a]);return e},C=(e,t)=>B(e,N(t));import{l as S,b as $}from"./api.16668391525453.js";import{_ as P,d as L,c as T,e as U,t as j,k as n,m as p,p as b,q as o,w as u,y as I,O as M,P as O,v as c,A as d,D as R,z as F}from"./index.1666839152545.js";import"./Api.1666839152545.js";const G=L({name:"SyslogList",components:{},setup(){const e=T({query:{pageNum:1,pageSize:10,name:null},total:0,logs:[],accounts:[]});U(()=>{t()});const t=async()=>{let r=await S.list.request(e.query);e.logs=r.list,e.total=r.total},a=r=>{e.query.pageNum=r,t()},m=r=>{$.list.request({username:r}).then(g=>{e.accounts=g.list})};return C(w({},j(e)),{search:t,handlePageChange:a,getAccount:m})}}),H={class:"role-list"},J={style:{float:"right"}};function K(e,t,a,m,r,g){const i=n("el-option"),y=n("el-select"),v=n("el-button"),s=n("el-table-column"),_=n("el-tag"),q=n("el-table"),D=n("el-pagination"),E=n("el-row"),V=n("el-card");return p(),b("div",H,[o(V,null,{default:u(()=>[I("div",J,[o(y,{remote:"","remote-method":e.getAccount,modelValue:e.query.creatorId,"onUpdate:modelValue":t[0]||(t[0]=l=>e.query.creatorId=l),filterable:"",placeholder:"\u8BF7\u8F93\u5165\u5E76\u9009\u62E9\u8D26\u53F7",clearable:"",class:"mr5"},{default:u(()=>[(p(!0),b(M,null,O(e.accounts,l=>(p(),c(i,{key:l.id,label:l.username,value:l.id},null,8,["label","value"]))),128))]),_:1},8,["remote-method","modelValue"]),o(y,{modelValue:e.query.type,"onUpdate:modelValue":t[1]||(t[1]=l=>e.query.type=l),filterable:"",placeholder:"\u8BF7\u9009\u62E9\u64CD\u4F5C\u7ED3\u679C",clearable:"",class:"mr5"},{default:u(()=>[o(i,{label:"\u6210\u529F",value:1}),o(i,{label:"\u5931\u8D25",value:2})]),_:1},8,["modelValue"]),o(v,{onClick:e.search,type:"success",icon:"search"},null,8,["onClick"])]),o(q,{data:e.logs,style:{width:"100%"}},{default:u(()=>[o(s,{prop:"creator",label:"\u64CD\u4F5C\u4EBA","min-width":"100","show-overflow-tooltip":""}),o(s,{prop:"createTime",label:"\u64CD\u4F5C\u65F6\u95F4","min-width":"160"},{default:u(l=>[d(R(e.$filters.dateFormat(l.row.createTime)),1)]),_:1}),o(s,{prop:"type",label:"\u7ED3\u679C","min-width":"65"},{default:u(l=>[l.row.type==1?(p(),c(_,{key:0,type:"success",size:"small"},{default:u(()=>[d("\u6210\u529F")]),_:1})):F("",!0),l.row.type==2?(p(),c(_,{key:1,type:"danger",size:"small"},{default:u(()=>[d("\u5931\u8D25")]),_:1})):F("",!0)]),_:1}),o(s,{prop:"description",label:"\u63CF\u8FF0","min-width":"160","show-overflow-tooltip":""}),o(s,{prop:"reqParam",label:"\u8BF7\u6C42\u4FE1\u606F","min-width":"300","show-overflow-tooltip":""}),o(s,{prop:"resp",label:"\u54CD\u5E94\u4FE1\u606F","min-width":"200","show-overflow-tooltip":""})]),_:1},8,["data"]),o(E,{style:{"margin-top":"20px"},type:"flex",justify:"end"},{default:u(()=>[o(D,{style:{"text-align":"right"},onCurrentChange:e.handlePageChange,total:e.total,layout:"prev, pager, next, total, jumper","current-page":e.query.pageNum,"onUpdate:current-page":t[2]||(t[2]=l=>e.query.pageNum=l),"page-size":e.query.pageSize},null,8,["onCurrentChange","total","current-page","page-size"])]),_:1})]),_:1})])}var Z=P(G,[["render",K]]);export{Z as default};

View File

@@ -0,0 +1 @@
import{l as x,b as B}from"./api.16670449710543.js";import{d as N,c as z,t as A,e as S,h as o,i as c,j as f,k as e,w as a,q as U,l,Q as j,R as I,m,v as _,F as T,U as L,s as w}from"./index.1667044971054.js";import"./Api.1667044971054.js";const P={class:"role-list"},R={style:{float:"right"}},J=N({__name:"SyslogList",setup(M){const r=z({query:{type:null,creatorId:null,pageNum:1,pageSize:10,name:null},total:0,logs:[],accounts:[]}),{query:n,total:F,logs:b,accounts:h}=A(r);S(()=>{i()});const i=async()=>{let s=await x.list.request(r.query);r.logs=s.list,r.total=s.total},C=s=>{r.query.pageNum=s,i()},v=s=>{B.list.request({username:s}).then(u=>{r.accounts=u.list})};return(s,u)=>{const d=o("el-option"),g=o("el-select"),D=o("el-button"),p=o("el-table-column"),y=o("el-tag"),E=o("el-table"),V=o("el-pagination"),k=o("el-row"),q=o("el-card");return c(),f("div",P,[e(q,null,{default:a(()=>[U("div",R,[e(g,{remote:"","remote-method":v,modelValue:l(n).creatorId,"onUpdate:modelValue":u[0]||(u[0]=t=>l(n).creatorId=t),filterable:"",placeholder:"\u8BF7\u8F93\u5165\u5E76\u9009\u62E9\u8D26\u53F7",clearable:"",class:"mr5"},{default:a(()=>[(c(!0),f(j,null,I(l(h),t=>(c(),m(d,{key:t.id,label:t.username,value:t.id},null,8,["label","value"]))),128))]),_:1},8,["modelValue"]),e(g,{modelValue:l(n).type,"onUpdate:modelValue":u[1]||(u[1]=t=>l(n).type=t),filterable:"",placeholder:"\u8BF7\u9009\u62E9\u64CD\u4F5C\u7ED3\u679C",clearable:"",class:"mr5"},{default:a(()=>[e(d,{label:"\u6210\u529F",value:1}),e(d,{label:"\u5931\u8D25",value:2})]),_:1},8,["modelValue"]),e(D,{onClick:i,type:"success",icon:"search"})]),e(E,{data:l(b),style:{width:"100%"}},{default:a(()=>[e(p,{prop:"creator",label:"\u64CD\u4F5C\u4EBA","min-width":"100","show-overflow-tooltip":""}),e(p,{prop:"createTime",label:"\u64CD\u4F5C\u65F6\u95F4","min-width":"160"},{default:a(t=>[_(T(l(L)(t.row.createTime)),1)]),_:1}),e(p,{prop:"type",label:"\u7ED3\u679C","min-width":"65"},{default:a(t=>[t.row.type==1?(c(),m(y,{key:0,type:"success",size:"small"},{default:a(()=>[_("\u6210\u529F")]),_:1})):w("",!0),t.row.type==2?(c(),m(y,{key:1,type:"danger",size:"small"},{default:a(()=>[_("\u5931\u8D25")]),_:1})):w("",!0)]),_:1}),e(p,{prop:"description",label:"\u63CF\u8FF0","min-width":"160","show-overflow-tooltip":""}),e(p,{prop:"reqParam",label:"\u8BF7\u6C42\u4FE1\u606F","min-width":"300","show-overflow-tooltip":""}),e(p,{prop:"resp",label:"\u54CD\u5E94\u4FE1\u606F","min-width":"200","show-overflow-tooltip":""})]),_:1},8,["data"]),e(k,{style:{"margin-top":"20px"},type:"flex",justify:"end"},{default:a(()=>[e(V,{style:{"text-align":"right"},onCurrentChange:C,total:l(F),layout:"prev, pager, next, total, jumper","current-page":l(n).pageNum,"onUpdate:current-page":u[2]||(u[2]=t=>l(n).pageNum=t),"page-size":l(n).pageSize},null,8,["total","current-page","page-size"])]),_:1})]),_:1})])}}});export{J as default};

Some files were not shown because too many files have changed in this diff Show More