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> </div>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { ref, toRefs, reactive, nextTick, watch, onMounted, onUnmounted, defineComponent } from 'vue'; import { ref, toRefs, reactive, nextTick, watch, onMounted, onUnmounted } from 'vue';
import JSONEditor from 'jsoneditor'; import JSONEditor from 'jsoneditor';
import 'jsoneditor/dist/jsoneditor.min.css'; import 'jsoneditor/dist/jsoneditor.min.css';
export default defineComponent({ const props = defineProps({
name: 'JsonEdit',
components: {},
props: {
modelValue: { modelValue: {
type: [String, Object], type: [String, Object],
}, },
@@ -38,8 +35,11 @@ export default defineComponent({
return ['tree', 'code', 'form', 'text', 'view']; return ['tree', 'code', 'form', 'text', 'view'];
}, },
}, },
}, })
setup(props: any, { emit }) {
//定义事件
const emit = defineEmits(['update:visible', 'update:modelValue', 'onChange'])
let { modelValue, options, modeList, currentMode } = toRefs(props); let { modelValue, options, modeList, currentMode } = toRefs(props);
const jsoneditorVue = ref(null) const jsoneditorVue = ref(null)
@@ -60,7 +60,7 @@ export default defineComponent({
state.height = props.height; state.height = props.height;
init(); init();
setJson(modelValue.value); setJson(modelValue!.value);
}); });
onUnmounted(() => { onUnmounted(() => {
@@ -117,13 +117,6 @@ export default defineComponent({
}; };
editor = new JSONEditor(jsoneditorVue.value, finalOptions); editor = new JSONEditor(jsoneditorVue.value, finalOptions);
}; };
return {
...toRefs(state),
jsoneditorVue,
};
},
});
</script> </script>
<style lang="scss"> <style lang="scss">

View File

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

View File

@@ -7,13 +7,14 @@
<img :src="getUserInfos.photo" /> <img :src="getUserInfos.photo" />
<div class="home-card-first-right ml15"> <div class="home-card-first-right ml15">
<div class="flex-margin"> <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>
</div> </div>
</div> </div>
</el-col> </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 @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-flex">
<div class="home-card-item-title pb3">{{ v.title }}</div> <div class="home-card-item-title pb3">{{ v.title }}</div>
@@ -26,7 +27,7 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { toRefs, reactive, onMounted, nextTick, computed } from 'vue'; import { toRefs, reactive, onMounted, nextTick, computed } from 'vue';
import { useStore } from '@/store/index.ts'; import { useStore } from '@/store/index.ts';
// import * as echarts from 'echarts'; // import * as echarts from 'echarts';
@@ -34,10 +35,7 @@ import { CountUp } from 'countup.js';
import { formatAxis } from '@/common/utils/formatTime.ts'; import { formatAxis } from '@/common/utils/formatTime.ts';
import { indexApi } from './api'; import { indexApi } from './api';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
export default {
name: 'HomePage',
setup() {
// const { proxy } = getCurrentInstance() as any;
const router = useRouter(); const router = useRouter();
const store = useStore(); const store = useStore();
const state = reactive({ const state = reactive({
@@ -65,6 +63,10 @@ export default {
], ],
}); });
const {
topCardItemList,
} = toRefs(state)
// //
const currentTime = computed(() => { const currentTime = computed(() => {
return formatAxis(new Date()); return formatAxis(new Date());
@@ -117,20 +119,12 @@ export default {
const getUserInfos = computed(() => { const getUserInfos = computed(() => {
return store.state.userInfos.userInfos; return store.state.userInfos.userInfos;
}); });
return {
getUserInfos,
currentTime,
toPage,
...toRefs(state),
};
},
};
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.home-container { .home-container {
overflow-x: hidden; overflow-x: hidden;
.home-card-item { .home-card-item {
width: 100%; width: 100%;
height: 103px; height: 103px;
@@ -138,16 +132,19 @@ export default {
border-radius: 4px; border-radius: 4px;
transition: all ease 0.3s; transition: all ease 0.3s;
cursor: pointer; cursor: pointer;
&:hover { &:hover {
box-shadow: 0 2px 12px 0 rgb(0 0 0 / 10%); box-shadow: 0 2px 12px 0 rgb(0 0 0 / 10%);
transition: all ease 0.3s; transition: all ease 0.3s;
} }
} }
.home-card-item-box { .home-card-item-box {
display: flex; display: flex;
align-items: center; align-items: center;
position: relative; position: relative;
overflow: hidden; overflow: hidden;
&:hover { &:hover {
i { i {
right: 0px !important; right: 0px !important;
@@ -155,6 +152,7 @@ export default {
transition: all ease 0.3s; transition: all ease 0.3s;
} }
} }
i { i {
position: absolute; position: absolute;
right: -10px; right: -10px;
@@ -163,48 +161,59 @@ export default {
transform: rotate(-30deg); transform: rotate(-30deg);
transition: all ease 0.3s; transition: all ease 0.3s;
} }
.home-card-item-flex { .home-card-item-flex {
padding: 0 20px; padding: 0 20px;
color: white; color: white;
.home-card-item-title, .home-card-item-title,
.home-card-item-tip { .home-card-item-tip {
font-size: 13px; font-size: 13px;
} }
.home-card-item-title-num { .home-card-item-title-num {
font-size: 18px; font-size: 18px;
} }
.home-card-item-tip-num { .home-card-item-tip-num {
font-size: 13px; font-size: 13px;
} }
} }
} }
.home-card-first { .home-card-first {
background: white; background: white;
border: 1px solid #ebeef5; border: 1px solid #ebeef5;
display: flex; display: flex;
align-items: center; align-items: center;
img { img {
width: 60px; width: 60px;
height: 60px; height: 60px;
border-radius: 100%; border-radius: 100%;
border: 2px solid var(--color-primary-light-5); border: 2px solid var(--color-primary-light-5);
} }
.home-card-first-right { .home-card-first-right {
flex: 1; flex: 1;
display: flex; display: flex;
flex-direction: column; flex-direction: column;
.home-card-first-right-msg { .home-card-first-right-msg {
font-size: 13px; font-size: 13px;
color: gray; color: gray;
} }
} }
} }
.home-monitor { .home-monitor {
height: 200px; height: 200px;
.flex-warp-item { .flex-warp-item {
width: 50%; width: 50%;
height: 100px; height: 100px;
display: flex; display: flex;
.flex-warp-item-box { .flex-warp-item-box {
margin: auto; margin: auto;
height: auto; height: auto;
@@ -212,19 +221,24 @@ export default {
} }
} }
} }
.home-warning-card { .home-warning-card {
height: 292px; height: 292px;
::v-deep(.el-card) { ::v-deep(.el-card) {
height: 100%; height: 100%;
} }
} }
.home-dynamic { .home-dynamic {
height: 200px; height: 200px;
.home-dynamic-item { .home-dynamic-item {
display: flex; display: flex;
width: 100%; width: 100%;
height: 60px; height: 60px;
overflow: hidden; overflow: hidden;
&:first-of-type { &:first-of-type {
.home-dynamic-item-line { .home-dynamic-item-line {
i { i {
@@ -232,20 +246,24 @@ export default {
} }
} }
} }
.home-dynamic-item-left { .home-dynamic-item-left {
text-align: right; text-align: right;
.home-dynamic-item-left-time1 {
} .home-dynamic-item-left-time1 {}
.home-dynamic-item-left-time2 { .home-dynamic-item-left-time2 {
font-size: 13px; font-size: 13px;
color: gray; color: gray;
} }
} }
.home-dynamic-item-line { .home-dynamic-item-line {
height: 60px; height: 60px;
border-right: 2px dashed #dfdfdf; border-right: 2px dashed #dfdfdf;
margin: 0 20px; margin: 0 20px;
position: relative; position: relative;
i { i {
color: var(--color-primary); color: var(--color-primary);
font-size: 12px; font-size: 12px;
@@ -256,8 +274,10 @@ export default {
background: white; background: white;
} }
} }
.home-dynamic-item-right { .home-dynamic-item-right {
flex: 1; flex: 1;
.home-dynamic-item-right-title { .home-dynamic-item-right-title {
i { i {
margin-right: 5px; margin-right: 5px;
@@ -270,6 +290,7 @@ export default {
color: var(--color-primary); color: var(--color-primary);
} }
} }
.home-dynamic-item-right-label { .home-dynamic-item-right-label {
font-size: 13px; font-size: 13px;
color: gray; color: gray;

View File

@@ -2,37 +2,25 @@
<div> <div>
<el-form ref="loginFormRef" :model="loginForm" :rules="rules" class="login-content-form" size="large"> <el-form ref="loginFormRef" :model="loginForm" :rules="rules" class="login-content-form" size="large">
<el-form-item prop="username"> <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-input>
</el-form-item> </el-form-item>
<el-form-item prop="password"> <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-input>
</el-form-item> </el-form-item>
<el-form-item v-if="useLoginCaptcha" prop="captcha"> <el-form-item v-if="isUseLoginCaptcha" prop="captcha">
<el-row :gutter="15"> <el-row :gutter="15">
<el-col :span="16"> <el-col :span="16">
<el-input <el-input type="text" maxlength="6" placeholder="请输入验证码" prefix-icon="position"
type="text" v-model="loginForm.captcha" clearable autocomplete="off" @keyup.enter="login"></el-input>
maxlength="6"
placeholder="请输入验证码"
prefix-icon="position"
v-model="loginForm.captcha"
clearable
autocomplete="off"
@keyup.enter="login"
></el-input>
</el-col> </el-col>
<el-col :span="8"> <el-col :span="8">
<div class="login-content-code"> <div class="login-content-code">
<img <img class="login-content-code-img" @click="getCaptcha" width="130px" height="40px"
class="login-content-code-img" :src="captchaImage" style="cursor: pointer" />
@click="getCaptcha"
width="130px"
height="40px"
:src="captchaImage"
style="cursor: pointer"
/>
</div> </div>
</el-col> </el-col>
</el-row> </el-row>
@@ -44,21 +32,20 @@
</el-form-item> </el-form-item>
</el-form> </el-form>
<el-dialog title="修改密码" v-model="changePwdDialog.visible" :close-on-click-modal="false" width="450px" :destroy-on-close="true"> <el-dialog title="修改密码" v-model="changePwdDialog.visible" :close-on-click-modal="false" width="450px"
<el-form :model="changePwdDialog.form" :rules="changePwdDialog.rules" ref="changePwdFormRef" label-width="65px"> :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-form-item prop="username" label="用户名" required>
<el-input v-model.trim="changePwdDialog.form.username" disabled></el-input> <el-input v-model.trim="changePwdDialog.form.username" disabled></el-input>
</el-form-item> </el-form-item>
<el-form-item prop="oldPassword" label="旧密码" required> <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>
<el-form-item prop="newPassword" label="新密码" required> <el-form-item prop="newPassword" label="新密码" required>
<el-input <el-input v-model.trim="changePwdDialog.form.newPassword" placeholder="须为8位以上且包含字⺟⼤⼩写+数字+特殊符号"
v-model.trim="changePwdDialog.form.newPassword" type="password" autocomplete="new-password"></el-input>
placeholder="须为8位以上且包含字⺟⼤⼩写+数字+特殊符号"
type="password"
autocomplete="new-password"
></el-input>
</el-form-item> </el-form-item>
</el-form> </el-form>
@@ -72,8 +59,8 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { nextTick, onMounted, ref, toRefs, reactive, defineComponent, computed } from 'vue'; import { nextTick, onMounted, ref, toRefs, reactive, computed } from 'vue';
import { useRoute, useRouter } from 'vue-router'; import { useRoute, useRouter } from 'vue-router';
import { ElMessage } from 'element-plus'; import { ElMessage } from 'element-plus';
import { initBackEndControlRoutesFun } from '@/router/index.ts'; import { initBackEndControlRoutesFun } from '@/router/index.ts';
@@ -85,9 +72,12 @@ import { RsaEncrypt } from '@/common/rsa';
import { useLoginCaptcha, useWartermark } from '@/common/sysconfig'; import { useLoginCaptcha, useWartermark } from '@/common/sysconfig';
import { letterAvatar } from '@/common/utils/string'; import { letterAvatar } from '@/common/utils/string';
export default defineComponent({ const rules = {
name: 'AccountLogin', username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
setup() { password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
captcha: [{ required: true, message: '请输入验证码', trigger: 'blur' }],
}
const store = useStore(); const store = useStore();
const route = useRoute(); const route = useRoute();
const router = useRouter(); const router = useRouter();
@@ -95,7 +85,7 @@ export default defineComponent({
const changePwdFormRef: any = ref(null); const changePwdFormRef: any = ref(null);
const state = reactive({ const state = reactive({
useLoginCaptcha: false, isUseLoginCaptcha: false,
captchaImage: '', captchaImage: '',
loginForm: { loginForm: {
username: '', username: '',
@@ -121,20 +111,23 @@ export default defineComponent({
], ],
}, },
}, },
rules: {
username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
captcha: [{ required: true, message: '请输入验证码', trigger: 'blur' }],
},
loading: { loading: {
signIn: false, signIn: false,
changePwd: false, changePwd: false,
}, },
}); });
const {
isUseLoginCaptcha,
captchaImage,
loginForm,
changePwdDialog,
loading,
} = toRefs(state)
onMounted(async () => { onMounted(async () => {
nextTick(async () => { nextTick(async () => {
state.useLoginCaptcha = await useLoginCaptcha(); state.isUseLoginCaptcha = await useLoginCaptcha();
getCaptcha(); getCaptcha();
}); });
// 移除公钥, 方便后续重新获取 // 移除公钥, 方便后续重新获取
@@ -142,7 +135,7 @@ export default defineComponent({
}); });
const getCaptcha = async () => { const getCaptcha = async () => {
if (!state.useLoginCaptcha) { if (!state.isUseLoginCaptcha) {
return; return;
} }
let res: any = await openApi.captcha(); let res: any = await openApi.captcha();
@@ -271,28 +264,17 @@ export default defineComponent({
state.changePwdDialog.form.username = ''; state.changePwdDialog.form.username = '';
getCaptcha(); getCaptcha();
}; };
return {
getCaptcha,
currentTime,
loginFormRef,
changePwdFormRef,
login,
changePwd,
cancelChangePwd,
...toRefs(state),
};
},
});
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
.login-content-form { .login-content-form {
margin-top: 20px; margin-top: 20px;
.login-content-code { .login-content-code {
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: space-around; justify-content: space-around;
.login-content-code-img { .login-content-code-img {
width: 100%; width: 100%;
height: 40px; height: 40px;
@@ -309,12 +291,14 @@ export default defineComponent({
transition: all ease 0.2s; transition: all ease 0.2s;
border-radius: 4px; border-radius: 4px;
user-select: none; user-select: none;
&:hover { &:hover {
border-color: #c0c4cc; border-color: #c0c4cc;
transition: all ease 0.2s; transition: all ease 0.2s;
} }
} }
} }
.login-content-submit { .login-content-submit {
width: 100%; width: 100%;
letter-spacing: 2px; letter-spacing: 2px;

View File

@@ -31,34 +31,31 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { toRefs, reactive, computed } from 'vue'; import { toRefs, reactive, computed } from 'vue';
import Account from '@/views/login/component/AccountLogin.vue'; import Account from '@/views/login/component/AccountLogin.vue';
import { useStore } from '@/store/index.ts'; import { useStore } from '@/store/index.ts';
export default {
name: 'LoginPage',
components: { Account },
setup() {
const store = useStore(); const store = useStore();
const state = reactive({ const state = reactive({
tabsActiveName: 'account', tabsActiveName: 'account',
isTabPaneShow: true, isTabPaneShow: true,
}); });
const {
isTabPaneShow,
tabsActiveName,
} = toRefs(state)
// 获取布局配置信息 // 获取布局配置信息
const getThemeConfig = computed(() => { const getThemeConfig = computed(() => {
return store.state.themeConfig.themeConfig; return store.state.themeConfig.themeConfig;
}); });
// 切换密码、手机登录 // 切换密码、手机登录
const onTabsClick = () => { const onTabsClick = () => {
state.isTabPaneShow = !state.isTabPaneShow; state.isTabPaneShow = !state.isTabPaneShow;
}; };
return {
onTabsClick,
getThemeConfig,
...toRefs(state),
};
},
};
</script> </script>
<style scoped lang="scss"> <style scoped lang="scss">
@@ -67,6 +64,7 @@ export default {
height: 100%; height: 100%;
background: url('@/assets/image/bg-login.png') no-repeat; background: url('@/assets/image/bg-login.png') no-repeat;
background-size: 100% 100%; background-size: 100% 100%;
.login-logo { .login-logo {
position: absolute; position: absolute;
top: 30px; top: 30px;
@@ -80,6 +78,7 @@ export default {
width: 90%; width: 90%;
transform: translateX(-50%); transform: translateX(-50%);
} }
.login-content { .login-content {
width: 500px; width: 500px;
padding: 20px; padding: 20px;
@@ -94,9 +93,11 @@ export default {
height: 480px; height: 480px;
overflow: hidden; overflow: hidden;
z-index: 1; z-index: 1;
.login-content-main { .login-content-main {
margin: 0 auto; margin: 0 auto;
width: 80%; width: 80%;
.login-content-title { .login-content-title {
color: #333; color: #333;
font-weight: 500; font-weight: 500;
@@ -108,9 +109,11 @@ export default {
} }
} }
} }
.login-content-mobile { .login-content-mobile {
height: 418px; height: 418px;
} }
.login-copyright { .login-copyright {
position: absolute; position: absolute;
left: 50%; left: 50%;
@@ -120,9 +123,11 @@ export default {
color: white; color: white;
font-size: 12px; font-size: 12px;
opacity: 0.8; opacity: 0.8;
.login-copyright-company { .login-copyright-company {
white-space: nowrap; white-space: nowrap;
} }
.login-copyright-msg { .login-copyright-msg {
@extend .login-copyright-company; @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> <template>
<div> <div>
<el-tree-select <el-tree-select @check="changeTag" style="width: 100%" v-model="selectTags" :data="tags"
@check="changeTag" :render-after-expand="true" :default-expanded-keys="[selectTags]" show-checkbox check-strictly node-key="id"
style="width: 100%"
v-model="selectTags"
:data="tags"
:render-after-expand="true"
:default-expanded-keys="[selectTags]"
show-checkbox
check-strictly
node-key="id"
:props="{ :props="{
value: 'id', value: 'id',
label: 'codePath', label: 'codePath',
children: 'children', children: 'children',
}" }">
>
<template #default="{ data }"> <template #default="{ data }">
<span class="custom-tree-node"> <span class="custom-tree-node">
<span style="font-size: 13px"> <span style="font-size: 13px">
@@ -31,27 +22,33 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { toRefs, reactive, defineComponent, onMounted } from 'vue'; import { toRefs, reactive, onMounted } from 'vue';
import { tagApi } from '../tag/api'; import { tagApi } from '../tag/api';
export default defineComponent({ const props = defineProps({
name: 'TagSelect',
props: {
tagId: { tagId: {
type: Number, type: Number,
}, },
tagPath: { tagPath: {
type: String, type: String,
}, },
}, })
setup(props: any, { emit }) {
//定义事件
const emit = defineEmits(['changeTag', 'update:tagId', 'update:tagPath'])
const state = reactive({ const state = reactive({
tags: [], tags: [],
// 单选则为id多选为id数组 // 单选则为id多选为id数组
selectTags: null as any, selectTags: null as any,
}); });
const {
tags,
selectTags,
} = toRefs(state)
onMounted(async () => { onMounted(async () => {
if (props.tagId) { if (props.tagId) {
state.selectTags = props.tagId; state.selectTags = props.tagId;
@@ -69,13 +66,7 @@ export default defineComponent({
emit('update:tagPath', null); emit('update:tagPath', null);
} }
}; };
return {
...toRefs(state),
changeTag,
};
},
});
</script> </script>
<style lang="scss"> <style lang="scss">
</style> </style>

View File

@@ -16,14 +16,18 @@
<el-col :span="12"> <el-col :span="12">
<el-form-item prop="characterSet" label="charset"> <el-form-item prop="characterSet" label="charset">
<el-select filterable style="width: 80%" v-model="tableData.characterSet" size="small"> <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-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :span="12"> <el-col :span="12">
<el-form-item prop="characterSet" label="collation"> <el-form-item prop="characterSet" label="collation">
<el-select filterable style="width: 80%" v-model="tableData.collation" size="small"> <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-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
@@ -32,27 +36,38 @@
<el-tabs v-model="activeName"> <el-tabs v-model="activeName">
<el-tab-pane label="字段" name="1"> <el-tab-pane label="字段" name="1">
<el-table :data="tableData.fields.res" :max-height="tableData.height"> <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"> <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-select v-if="item.prop === 'type'" filterable size="small"
<el-option v-for="typeValue in columnTypeList" :key="typeValue" :value="typeValue">{{ typeValue }}</el-option> v-model="scope.row.type">
<el-option v-for="typeValue in columnTypeList" :key="typeValue"
:value="typeValue">{{ typeValue }}</el-option>
</el-select> </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> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
@@ -63,35 +78,36 @@
</el-tab-pane> </el-tab-pane>
<el-tab-pane label="索引" name="2"> <el-tab-pane label="索引" name="2">
<el-table :data="tableData.indexs.res" :max-height="tableData.height"> <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"> <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 <el-select v-if="item.prop === 'columnNames'" v-model="scope.row.columnNames"
v-if="item.prop === 'columnNames'" multiple collapse-tags collapse-tags-tooltip filterable placeholder="请选择字段"
v-model="scope.row.columnNames" style="width: 100%">
multiple <el-option v-for="cl in tableData.indexs.columns" :key="cl.name"
collapse-tags :label="cl.name" :value="cl.name">
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 || '') }} {{ cl.name + ' - ' + (cl.remark || '') }}
</el-option> </el-option>
</el-select> </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-select v-if="item.prop === 'indexType'" filterable size="small"
<el-option v-for="typeValue in indexTypeList" :key="typeValue" :value="typeValue">{{ typeValue }}</el-option> v-model="scope.row.indexType">
<el-option v-for="typeValue in indexTypeList" :key="typeValue"
:value="typeValue">{{ typeValue }}</el-option>
</el-select> </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> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
@@ -111,15 +127,13 @@
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { watch, toRefs, reactive, defineComponent, ref, getCurrentInstance } from 'vue'; import { watch, toRefs, reactive, ref, getCurrentInstance } from 'vue';
import { TYPE_LIST, CHARACTER_SET_NAME_LIST, COLLATION_SUFFIX_LIST } from './service.ts'; import { TYPE_LIST, CHARACTER_SET_NAME_LIST, COLLATION_SUFFIX_LIST } from './service.ts';
import { ElMessage } from 'element-plus'; import { ElMessage } from 'element-plus';
import SqlExecBox from './component/SqlExecBox.ts'; import SqlExecBox from './component/SqlExecBox.ts';
export default defineComponent({ const props = defineProps({
name: 'createTable',
props: {
visible: { visible: {
type: Boolean, type: Boolean,
}, },
@@ -135,8 +149,11 @@ export default defineComponent({
db: { db: {
type: String, type: String,
} }
}, })
setup(props: any, { emit }) {
//定义事件
const emit = defineEmits(['update:visible', 'cancel', 'val-change'])
const formRef: any = ref(); const formRef: any = ref();
const { proxy } = getCurrentInstance() as any; const { proxy } = getCurrentInstance() as any;
@@ -248,13 +265,26 @@ export default defineComponent({
}, },
}); });
const {
dialogVisible,
btnloading,
activeName,
columnTypeList,
indexTypeList,
characterSetNameList,
collationNameList,
tableData,
} = toRefs(state)
watch(props, async (newValue) => { watch(props, async (newValue) => {
state.dialogVisible = newValue.visible; state.dialogVisible = newValue.visible;
}); });
const cancel = () => { const cancel = () => {
emit('update:visible', false); emit('update:visible', false);
reset(); reset();
}; };
const addRow = () => { const addRow = () => {
state.tableData.fields.res.push({ state.tableData.fields.res.push({
name: '', name: '',
@@ -267,6 +297,7 @@ export default defineComponent({
remark: '', remark: '',
}); });
}; };
const addIndex = () => { const addIndex = () => {
state.tableData.indexs.res.push({ state.tableData.indexs.res.push({
indexName: '', indexName: '',
@@ -276,6 +307,7 @@ export default defineComponent({
indexComment: '', indexComment: '',
}); });
}; };
const addDefaultRows = () => { const addDefaultRows = () => {
state.tableData.fields.res.push( state.tableData.fields.res.push(
{ name: 'id', type: 'bigint', length: '20', value: '', notNull: true, pri: true, auto_increment: true, remark: '主键ID' }, { name: 'id', type: 'bigint', length: '20', value: '', notNull: true, pri: true, auto_increment: true, remark: '主键ID' },
@@ -287,12 +319,15 @@ export default defineComponent({
{ name: 'update_time', type: 'datetime', length: '', 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); state.tableData.fields.res.splice(index, 1);
}; };
const deleteIndex = (index: any) => { const deleteIndex = (index: any) => {
state.tableData.indexs.res.splice(index, 1); state.tableData.indexs.res.splice(index, 1);
}; };
const submit = async () => { const submit = async () => {
let sql = genSql(); let sql = genSql();
if (!sql) { if (!sql) {
@@ -379,7 +414,7 @@ export default defineComponent({
let data = state.tableData; let data = state.tableData;
// 创建表 // 创建表
if(!props.data.edit){ if (!props.data?.edit) {
if (state.activeName === '1') {// 创建表结构 if (state.activeName === '1') {// 创建表结构
let primary_key = ''; let primary_key = '';
let fields: string[] = []; let fields: string[] = [];
@@ -514,7 +549,7 @@ export default defineComponent({
}; };
const oldData = { indexs: [] as any[], fields: [] as any[] } const oldData = { indexs: [] as any[], fields: [] as any[] }
watch(()=>props.data, (newValue)=>{ watch(() => props.data, (newValue: any) => {
const { row, indexs, columns } = newValue; const { row, indexs, columns } = newValue;
// 回显表名表注释 // 回显表名表注释
state.tableData.tableName = row.tableName state.tableData.tableName = row.tableName
@@ -563,20 +598,5 @@ export default defineComponent({
}) })
} }
}) })
return {
...toRefs(state),
formRef,
cancel,
reset,
addDefaultRows,
addRow,
deleteRow,
addIndex,
deleteIndex,
submit,
};
},
});
</script> </script>

View File

@@ -1,6 +1,7 @@
<template> <template>
<div> <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 :model="form" ref="dbForm" :rules="rules" label-width="95px">
<el-form-item prop="tagId" label="标签:" required> <el-form-item prop="tagId" label="标签:" required>
<tag-select v-model:tag-id="form.tagId" v-model:tag-path="form.tagPath" style="width: 100%" /> <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>
<el-form-item prop="host" label="host:" required> <el-form-item prop="host" label="host:" required>
<el-col :span="18"> <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>
<el-col style="text-align: center" :span="1">:</el-col> <el-col style="text-align: center" :span="1">:</el-col>
<el-col :span="5"> <el-col :span="5">
@@ -28,17 +30,14 @@
<el-input v-model.trim="form.username" placeholder="请输入用户名"></el-input> <el-input v-model.trim="form.username" placeholder="请输入用户名"></el-input>
</el-form-item> </el-form-item>
<el-form-item prop="password" label="密码:"> <el-form-item prop="password" label="密码:">
<el-input <el-input type="password" show-password v-model.trim="form.password" placeholder="请输入密码,修改操作可不填"
type="password" autocomplete="new-password">
show-password
v-model.trim="form.password"
placeholder="请输入密码,修改操作可不填"
autocomplete="new-password"
>
<template v-if="form.id && form.id != 0" #suffix> <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> <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> </template>
</el-popover> </el-popover>
</template> </template>
@@ -47,28 +46,22 @@
<el-form-item prop="params" label="连接参数:"> <el-form-item prop="params" label="连接参数:">
<el-input v-model.trim="form.params" placeholder="其他连接参数,形如: key1=value1&key2=value2"> <el-input v-model.trim="form.params" placeholder="其他连接参数,形如: key1=value1&key2=value2">
<template v-if="form.id && form.id != 0" #suffix> <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> </template>
</el-input> </el-input>
</el-form-item> </el-form-item>
<el-form-item prop="database" label="数据库名:" required> <el-form-item prop="database" label="数据库名:" required>
<el-col :span="19"> <el-col :span="19">
<el-select <el-select @change="changeDatabase" v-model="databaseList" multiple clearable collapse-tags
@change="changeDatabase" collapse-tags-tooltip filterable allow-create placeholder="请确保数据库实例信息填写完整后获取库名"
v-model="databaseList" style="width: 100%">
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-option v-for="db in allDatabases" :key="db" :label="db" :value="db" />
</el-select> </el-select>
</el-col> </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-col :span="4">
<el-link @click="getAllDatabase" :underline="false" type="success">获取库名</el-link> <el-link @click="getAllDatabase" :underline="false" type="success">获取库名</el-link>
</el-col> </el-col>
@@ -80,17 +73,14 @@
<el-form-item prop="enableSshTunnel" label="SSH隧道:"> <el-form-item prop="enableSshTunnel" label="SSH隧道:">
<el-col :span="3"> <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>
<el-col :span="5" v-if="form.enableSshTunnel == 1"> 机器: </el-col> <el-col :span="5" v-if="form.enableSshTunnel == 1"> 机器: </el-col>
<el-col :span="16" v-if="form.enableSshTunnel == 1"> <el-col :span="16" v-if="form.enableSshTunnel == 1">
<el-select style="width: 100%" v-model="form.sshTunnelMachineId" placeholder="请选择SSH隧道机器"> <el-select style="width: 100%" v-model="form.sshTunnelMachineId" placeholder="请选择SSH隧道机器">
<el-option <el-option v-for="item in sshTunnelMachineList" :key="item.id"
v-for="item in sshTunnelMachineList" :label="`${item.ip}:${item.port} [${item.name}]`" :value="item.id">
:key="item.id"
:label="`${item.ip}:${item.port} [${item.name}]`"
:value="item.id"
>
</el-option> </el-option>
</el-select> </el-select>
</el-col> </el-col>
@@ -107,8 +97,8 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { toRefs, reactive, watch, defineComponent, ref } from 'vue'; import { toRefs, reactive, watch, ref } from 'vue';
import { dbApi } from './api'; import { dbApi } from './api';
import { machineApi } from '../machine/api.ts'; import { machineApi } from '../machine/api.ts';
import { ElMessage } from 'element-plus'; import { ElMessage } from 'element-plus';
@@ -116,70 +106,26 @@ import { notBlank } from '@/common/assert';
import { RsaEncrypt } from '@/common/rsa'; import { RsaEncrypt } from '@/common/rsa';
import TagSelect from '../component/TagSelect.vue'; import TagSelect from '../component/TagSelect.vue';
export default defineComponent({ const props = defineProps({
name: 'DbEdit',
components: {
TagSelect,
},
props: {
visible: { visible: {
type: Boolean, type: Boolean,
}, },
projects: {
type: Array,
},
db: { db: {
type: [Boolean, Object], type: [Boolean, Object],
}, },
title: { title: {
type: String, type: String,
}, },
}, })
setup(props: any, { emit }) {
const dbForm: any = ref(null);
const state = reactive({ //定义事件
dialogVisible: false, const emit = defineEmits(['update:visible', 'cancel', 'val-change'])
projects: [],
envs: [], const rules = {
allDatabases: [] as any, tagId: [
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: [
{ {
required: true, required: true,
message: '请选择项目', message: '请选择标签',
trigger: ['change', 'blur'],
},
],
envId: [
{
required: true,
message: '请选择环境',
trigger: ['change', 'blur'], trigger: ['change', 'blur'],
}, },
], ],
@@ -218,21 +164,60 @@ export default defineComponent({
trigger: ['change', 'blur'], trigger: ['change', 'blur'],
}, },
], ],
}
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,
}); });
watch(props, (newValue) => { const {
dialogVisible,
allDatabases,
databaseList,
sshTunnelMachineList,
form,
pwd,
btnLoading,
} = toRefs(state)
watch(props, (newValue: any) => {
state.dialogVisible = newValue.visible; state.dialogVisible = newValue.visible;
if (!state.dialogVisible) { if (!state.dialogVisible) {
return; return;
} }
state.projects = newValue.projects;
if (newValue.db) { if (newValue.db) {
state.form = { ...newValue.db }; state.form = { ...newValue.db };
// 将数据库名使用空格切割,获取所有数据库列表 // 将数据库名使用空格切割,获取所有数据库列表
state.databaseList = newValue.db.database.split(' '); state.databaseList = newValue.db.database.split(' ');
} else { } else {
state.envs = [];
state.form = { port: 3306, enableSshTunnel: -1 } as any; state.form = { port: 3306, enableSshTunnel: -1 } as any;
state.databaseList = []; state.databaseList = [];
} }
@@ -253,14 +238,6 @@ export default defineComponent({
} }
}; };
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 }; const reqForm = { ...state.form };
reqForm.password = await RsaEncrypt(reqForm.password); reqForm.password = await RsaEncrypt(reqForm.password);
@@ -309,20 +286,7 @@ export default defineComponent({
resetInputDb(); resetInputDb();
}, 500); }, 500);
}; };
return {
...toRefs(state),
dbForm,
getAllDatabase,
getDbPwd,
changeDatabase,
getSshTunnelMachines,
changeEnv,
btnOk,
cancel,
};
},
});
</script> </script>
<style lang="scss"> <style lang="scss">
</style> </style>

View File

@@ -2,8 +2,10 @@
<div class="db-list"> <div class="db-list">
<el-card> <el-card>
<el-button v-auth="permissions.saveDb" type="primary" icon="plus" @click="editDb(true)">添加</el-button> <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.saveDb" :disabled="chooseId == null" @click="editDb(false)" type="primary"
<el-button v-auth="permissions.delDb" :disabled="chooseId == null" @click="deleteDb(chooseId)" type="danger" icon="delete">删除</el-button> 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"> <div style="float: right">
<el-select @focus="getTags" v-model="query.tagPath" placeholder="请选择标签" filterable clearable> <el-select @focus="getTags" v-model="query.tagPath" placeholder="请选择标签" filterable clearable>
<el-option v-for="item in tags" :key="item" :label="item" :value="item"> </el-option> <el-option v-for="item in tags" :key="item" :label="item" :value="item"> </el-option>
@@ -30,19 +32,24 @@
<template #default="scope"> <template #default="scope">
<el-popover placement="right" trigger="click" :width="300"> <el-popover placement="right" trigger="click" :width="300">
<template #reference> <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> </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> <template #prefix>
<el-icon class="el-input__icon"><search-icon /></el-icon> <el-icon class="el-input__icon">
<search-icon />
</el-icon>
</template> </template>
</el-input> </el-input>
<div class="el-tag--plain el-tag--success" <div class="el-tag--plain el-tag--success" v-for="db in filterDb.list" :key="db"
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">
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="success" plain size="small" :underline="false" @click="showTableInfo(scope.row, db)">{{ db }}</el-link> <el-link type="primary" plain size="small" :underline="false"
<el-link type="primary" plain size="small" :underline="false" @click="openSqlExec(scope.row, db)" style="position: absolute; right: 4px">数据操作</el-link> @click="openSqlExec(scope.row, db)" style="position: absolute; right: 4px">数据操作
</el-link>
</div> </div>
</el-popover> </el-popover>
</template> </template>
@@ -53,25 +60,21 @@
<el-table-column min-width="115" prop="creator" label="创建账号"></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> <el-table-column min-width="160" prop="createTime" label="创建时间" show-overflow-tooltip>
<template #default="scope"> <template #default="scope">
{{ $filters.dateFormat(scope.row.createTime) }} {{ dateFormat(scope.row.createTime) }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="操作" min-width="120" fixed="right"> <el-table-column label="操作" min-width="120" fixed="right">
<template #default="scope"> <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> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
<el-row style="margin-top: 20px" type="flex" justify="end"> <el-row style="margin-top: 20px" type="flex" justify="end">
<el-pagination <el-pagination style="text-align: right" @current-change="handlePageChange" :total="total"
style="text-align: right" layout="prev, pager, next, total, jumper" v-model:current-page="query.pageNum"
@current-change="handlePageChange" :page-size="query.pageSize"></el-pagination>
:total="total"
layout="prev, pager, next, total, jumper"
v-model:current-page="query.pageNum"
:page-size="query.pageSize"
></el-pagination>
</el-row> </el-row>
</el-card> </el-card>
@@ -90,10 +93,13 @@
</el-form-item> </el-form-item>
<el-form-item label="导出表: "> <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 type="selection" width="45" />
<el-table-column property="tableName" 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 property="tableComment" label="备注" min-width="150" show-overflow-tooltip></el-table-column> </el-table-column>
<el-table-column property="tableComment" label="备注" min-width="150" show-overflow-tooltip>
</el-table-column>
</el-table> </el-table>
</el-form-item> </el-form-item>
@@ -105,40 +111,30 @@
<el-button type="primary" size="small" @click="openEditTable(false)">创建表</el-button> <el-button type="primary" size="small" @click="openEditTable(false)">创建表</el-button>
</el-row> </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> <el-table-column property="tableName" label="表名" min-width="150" show-overflow-tooltip>
<template #header> <template #header>
<el-input v-model="tableInfoDialog.tableNameSearch" size="small" placeholder="表名: 输入可过滤" clearable /> <el-input v-model="tableInfoDialog.tableNameSearch" size="small" placeholder="表名: 输入可过滤"
clearable />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column property="tableComment" label="备注" min-width="150" show-overflow-tooltip> <el-table-column property="tableComment" label="备注" min-width="150" show-overflow-tooltip>
<template #header> <template #header>
<el-input v-model="tableInfoDialog.tableCommentSearch" size="small" placeholder="备注: 输入可过滤" clearable /> <el-input v-model="tableInfoDialog.tableCommentSearch" size="small" placeholder="备注: 输入可过滤"
clearable />
</template> </template>
</el-table-column> </el-table-column>
<el-table-column <el-table-column prop="tableRows" label="Rows" min-width="70" sortable
prop="tableRows" :sort-method="(a: any, b: any) => parseInt(a.tableRows) - parseInt(b.tableRows)"></el-table-column>
label="Rows" <el-table-column property="dataLength" label="数据大小" sortable
min-width="70" :sort-method="(a: any, b: any) => parseInt(a.dataLength) - parseInt(b.dataLength)">
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)"
>
<template #default="scope"> <template #default="scope">
{{ formatByteSize(scope.row.dataLength) }} {{ formatByteSize(scope.row.dataLength) }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column <el-table-column property="indexLength" label="索引大小" sortable
property="indexLength" :sort-method="(a: any, b: any) => parseInt(a.indexLength) - parseInt(b.indexLength)">
label="索引大小"
sortable
:sort-method="(a, b) => parseInt(a.indexLength) - parseInt(b.indexLength)"
>
<template #default="scope"> <template #default="scope">
{{ formatByteSize(scope.row.indexLength) }} {{ formatByteSize(scope.row.indexLength) }}
</template> </template>
@@ -160,19 +156,18 @@
</el-table> </el-table>
</el-dialog> </el-dialog>
<el-dialog <el-dialog width="90%" :title="`${sqlExecLogDialog.title} - SQL执行记录`" :before-close="onBeforeCloseSqlExecDialog"
width="90%" v-model="sqlExecLogDialog.visible">
:title="`${sqlExecLogDialog.title} - SQL执行记录`"
:before-close="onBeforeCloseSqlExecDialog"
v-model="sqlExecLogDialog.visible"
>
<div class="toolbar"> <div class="toolbar">
<el-select v-model="sqlExecLogDialog.query.db" placeholder="请选择数据库" filterable clearable> <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-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-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-select>
<el-button class="ml5" @click="searchSqlExecLog" type="success" icon="search"></el-button> <el-button class="ml5" @click="searchSqlExecLog" type="success" icon="search"></el-button>
</div> </div>
@@ -181,9 +176,12 @@
<el-table-column prop="table" label="" min-width="60" show-overflow-tooltip> </el-table-column> <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> <el-table-column prop="type" label="类型" width="85" show-overflow-tooltip>
<template #default="scope"> <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['UPDATE'].value" color="#E4F5EB"
<el-tag v-if="scope.row.type == enums.DbSqlExecTypeEnum.DELETE.value" color="#F9E2AE" size="small">DELETE</el-tag> size="small">UPDATE</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['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> </template>
</el-table-column> </el-table-column>
<el-table-column prop="sql" label="SQL" min-width="230" show-overflow-tooltip> </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"> <el-table-column label="操作" min-width="50" fixed="right">
<template #default="scope"> <template #default="scope">
<el-link <el-link
v-if="scope.row.type == enums.DbSqlExecTypeEnum.UPDATE.value || scope.row.type == enums.DbSqlExecTypeEnum.DELETE.value" v-if="scope.row.type == enums.DbSqlExecTypeEnum['UPDATE'].value || scope.row.type == enums.DbSqlExecTypeEnum['DELETE'].value"
type="primary" type="primary" plain size="small" :underline="false" @click="onShowRollbackSql(scope.row)">
plain 还原SQL</el-link>
size="small"
:underline="false"
@click="onShowRollbackSql(scope.row)"
>还原SQL</el-link
>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
<el-row style="margin-top: 20px" type="flex" justify="end"> <el-row style="margin-top: 20px" type="flex" justify="end">
<el-pagination <el-pagination style="text-align: right" @current-change="handleSqlExecPageChange"
style="text-align: right" :total="sqlExecLogDialog.total" layout="prev, pager, next, total, jumper"
@current-change="handleSqlExecPageChange" v-model:current-page="sqlExecLogDialog.query.pageNum" :page-size="sqlExecLogDialog.query.pageSize">
:total="sqlExecLogDialog.total" </el-pagination>
layout="prev, pager, next, total, jumper"
v-model:current-page="sqlExecLogDialog.query.pageNum"
:page-size="sqlExecLogDialog.query.pageSize"
></el-pagination>
</el-row> </el-row>
</el-dialog> </el-dialog>
<el-dialog width="55%" :title="`还原SQL`" v-model="rollbackSqlDialog.visible"> <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>
<el-dialog width="40%" :title="`${chooseTableName} 字段信息`" v-model="columnDialog.visible"> <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-dialog width="40%" :title="`${chooseTableName} 索引信息`" v-model="indexDialog.visible">
<el-table border stripe :data="indexDialog.indexs" size="small"> <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="indexName" label="索引名" min-width="120" show-overflow-tooltip> </el-table-column>
<el-table-column prop="columnName" label="列名" 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="seqInIndex" label="列序列号" show-overflow-tooltip> </el-table-column>
<el-table-column prop="indexType" label="类型"> </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-table>
</el-dialog> </el-dialog>
<el-dialog width="55%" :title="`${chooseTableName} Create-DDL`" v-model="ddlDialog.visible"> <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> </el-dialog>
<db-edit <db-edit @val-change="valChange" :title="dbEditDialog.title" v-model:visible="dbEditDialog.visible"
@val-change="valChange" v-model:db="dbEditDialog.data"></db-edit>
:title="dbEditDialog.title" <create-table :title="tableCreateDialog.title" :active-name="tableCreateDialog.activeName" :dbId="dbId" :db="db"
v-model:visible="dbEditDialog.visible" :data="tableCreateDialog.data" v-model:visible="tableCreateDialog.visible"></create-table>
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> </div>
</template> </template>
<script lang='ts'> <script lang='ts' setup>
import { toRefs, reactive, computed, onMounted, defineComponent } from 'vue'; import { toRefs, reactive, computed, onMounted } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus'; import { ElMessage, ElMessageBox } from 'element-plus';
import { formatByteSize } from '@/common/utils/format'; import { formatByteSize } from '@/common/utils/format';
import DbEdit from './DbEdit.vue'; import DbEdit from './DbEdit.vue';
@@ -276,24 +265,17 @@ import {store} from '@/store';
import { tagApi } from '../tag/api.ts'; import { tagApi } from '../tag/api.ts';
import { dateFormat } from '@/common/utils/date'; import { dateFormat } from '@/common/utils/date';
export default defineComponent({ const permissions = {
name: 'DbList', saveDb: 'db:save',
components: { delDb: 'db:del',
DbEdit, }
CreateTable,
SearchIcon,
},
setup() {
const state = reactive({ const state = reactive({
row: {}, row: {},
dbId: 0, dbId: 0,
db: '', db: '',
permissions: {
saveDb: 'db:save',
delDb: 'db:del',
},
tags: [], tags: [],
chooseId: null, chooseId: null as any,
/** /**
* 选中的数据 * 选中的数据
*/ */
@@ -358,7 +340,7 @@ export default defineComponent({
}, },
dbEditDialog: { dbEditDialog: {
visible: false, visible: false,
data: null, data: null as any,
title: '新增数据库', title: '新增数据库',
}, },
tableCreateDialog: { tableCreateDialog: {
@@ -379,6 +361,29 @@ export default defineComponent({
} }
}); });
const {
dbId,
db,
tags,
chooseId,
query,
datas,
total,
showDumpInfo,
dumpInfo,
sqlExecLogDialog,
rollbackSqlDialog,
chooseTableName,
tableInfoDialog,
columnDialog,
indexDialog,
ddlDialog,
dbEditDialog,
tableCreateDialog,
filterDb,
} = toRefs(state)
onMounted(async () => { onMounted(async () => {
search(); search();
}); });
@@ -695,41 +700,7 @@ export default defineComponent({
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> </script>
<style lang="scss"> <style lang="scss">
</style> </style>

View File

@@ -5,21 +5,17 @@
<el-col :span="24"> <el-col :span="24">
<el-form class="search-form" label-position="right" :inline="true"> <el-form class="search-form" label-position="right" :inline="true">
<el-form-item label="标签"> <el-form-item label="标签">
<el-select <el-select @change="changeTag" @focus="getTags" v-model="params.tagPath" placeholder="请选择标签"
@change="changeTag" filterable style="width: 220px">
@focus="getTags"
v-model="params.tagPath"
placeholder="请选择标签"
filterable
style="width: 220px"
>
<el-option v-for="item in tags" :key="item" :label="item" :value="item"> </el-option> <el-option v-for="item in tags" :key="item" :label="item" :value="item"> </el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="资源"> <el-form-item label="资源">
<el-select v-model="dbId" placeholder="请选择资源实例" @change="changeDbInstance" filterable style="width: 220px"> <el-select v-model="dbId" placeholder="请选择资源实例" @change="changeDbInstance" filterable
<el-option v-for="item in dbs" :key="item.id" :label="`${item.name} [${item.tagPath}]`" :value="item.id"> style="width: 220px">
<el-option v-for="item in dbs" :key="item.id" :label="`${item.name} [${item.tagPath}]`"
:value="item.id">
<span style="float: left">{{ `${item.name} [${item.tagPath}]` }}</span> <span style="float: left">{{ `${item.name} [${item.tagPath}]` }}</span>
<span style="float: right; color: #8492a6; margin-left: 10px; font-size: 13px">{{ <span style="float: right; color: #8492a6; margin-left: 10px; font-size: 13px">{{
`${item.host}:${item.port} ${item.type}` `${item.host}:${item.port} ${item.type}`
@@ -29,27 +25,19 @@
</el-form-item> </el-form-item>
<el-form-item label="数据库"> <el-form-item label="数据库">
<el-select <el-select v-model="db" placeholder="请选择数据库" @change="changeDb" @clear="clearDb" clearable
v-model="db" filterable style="width: 150px">
placeholder="请选择数据库" <el-option v-for="item in databaseList" :key="item" :label="item" :value="item">
@change="changeDb" </el-option>
@clear="clearDb"
clearable
filterable
style="width: 150px"
>
<el-option v-for="item in databaseList" :key="item" :label="item" :value="item"> </el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label-width="20" label="表"> <el-form-item label-width="20" label="表">
<el-select v-model="tableName" placeholder="选择表查看表数据" @change="changeTable" filterable style="width: 250px"> <el-select v-model="tableName" placeholder="选择表查看表数据" @change="changeTable" filterable
<el-option style="width: 250px">
v-for="item in tableMetadata" <el-option v-for="item in tableMetadata as any" :key="item.tableName"
:key="item.tableName"
:label="item.tableName + (item.tableComment != '' ? `【${item.tableComment}】` : '')" :label="item.tableName + (item.tableComment != '' ? `【${item.tableComment}】` : '')"
:value="item.tableName" :value="item.tableName">
>
</el-option> </el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
@@ -71,33 +59,27 @@
<div> <div>
<div class="toolbar"> <div class="toolbar">
<div class="fl"> <div class="fl">
<el-link @click="onRunSql" :underline="false" class="ml15" icon="VideoPlay"></el-link> <el-link @click="onRunSql" :underline="false" class="ml15" icon="VideoPlay">
</el-link>
<el-divider direction="vertical" border-style="dashed" /> <el-divider direction="vertical" border-style="dashed" />
<el-tooltip class="box-item" effect="dark" content="format sql" placement="top"> <el-tooltip class="box-item" effect="dark" content="format sql" placement="top">
<el-link @click="formatSql" type="primary" :underline="false" icon="MagicStick"></el-link> <el-link @click="formatSql" type="primary" :underline="false" icon="MagicStick">
</el-link>
</el-tooltip> </el-tooltip>
<el-divider direction="vertical" border-style="dashed" /> <el-divider direction="vertical" border-style="dashed" />
<el-tooltip class="box-item" effect="dark" content="commit" placement="top"> <el-tooltip class="box-item" effect="dark" content="commit" placement="top">
<el-link @click="onCommit" type="success" :underline="false" icon="CircleCheck"></el-link> <el-link @click="onCommit" type="success" :underline="false" icon="CircleCheck">
</el-link>
</el-tooltip> </el-tooltip>
<el-divider direction="vertical" border-style="dashed" /> <el-divider direction="vertical" border-style="dashed" />
<el-upload <el-upload style="display: inline-block" :before-upload="beforeUpload"
style="display: inline-block" :on-success="execSqlFileSuccess" :headers="{ Authorization: token }" :data="{
:before-upload="beforeUpload"
:on-success="execSqlFileSuccess"
:headers="{ Authorization: token }"
:data="{
dbId: 1, dbId: 1,
}" }" :action="getUploadSqlFileUrl()" :show-file-list="false" name="file" multiple
:action="getUploadSqlFileUrl()" :limit="100">
:show-file-list="false"
name="file"
multiple
:limit="100"
>
<el-tooltip class="box-item" effect="dark" content="SQL脚本执行" placement="top"> <el-tooltip class="box-item" effect="dark" content="SQL脚本执行" placement="top">
<el-link type="success" :underline="false" icon="Document"></el-link> <el-link type="success" :underline="false" icon="Document"></el-link>
</el-tooltip> </el-tooltip>
@@ -105,23 +87,18 @@
</div> </div>
<div style="float: right" class="fl"> <div style="float: right" class="fl">
<el-select <el-select v-model="sqlName" placeholder="选择or输入SQL模板名" @change="changeSqlTemplate"
v-model="sqlName" filterable allow-create default-first-option size="small" class="mr10">
placeholder="选择or输入SQL模板名" <el-option v-for="item in sqlNames as any" :key="item" :label="item.database"
@change="changeSqlTemplate" :value="item">
filterable
allow-create
default-first-option
size="small"
class="mr10"
>
<el-option v-for="item in sqlNames" :key="item" :label="item.database" :value="item">
{{ item }} {{ item }}
</el-option> </el-option>
</el-select> </el-select>
<el-button @click="saveSql" type="primary" icon="document-add" plain size="small">保存</el-button> <el-button @click="saveSql" type="primary" icon="document-add" plain size="small">保存
<el-button @click="deleteSql" type="danger" icon="delete" plain size="small">删除</el-button> </el-button>
<el-button @click="deleteSql" type="danger" icon="delete" plain size="small">删除
</el-button>
</div> </div>
</div> </div>
</div> </div>
@@ -134,48 +111,24 @@
</div> </div>
<div class="mt5"> <div class="mt5">
<el-row> <el-row>
<el-link <el-link v-if="queryTab.nowTableName" @click="onDeleteData" class="ml5" type="danger"
v-if="queryTab.nowTableName" icon="delete" :underline="false"></el-link>
@click="onDeleteData"
class="ml5"
type="danger"
icon="delete"
:underline="false"
></el-link>
<span v-if="queryTab.execRes.data.length > 0"> <span v-if="queryTab.execRes.data.length > 0">
<el-divider direction="vertical" border-style="dashed" /> <el-divider direction="vertical" border-style="dashed" />
<el-link type="success" :underline="false" @click="exportData"><span style="font-size: 12px">导出</span></el-link> <el-link type="success" :underline="false" @click="exportData"><span
style="font-size: 12px">导出</span></el-link>
</span> </span>
</el-row> </el-row>
<el-table <el-table @cell-dblclick="cellClick" @selection-change="onDataSelectionChange"
@cell-dblclick="cellClick" :data="queryTab.execRes.data" v-loading="queryTab.loading" element-loading-text="查询中..."
@selection-change="onDataSelectionChange" size="small" max-height="250" empty-text="tips: select *开头的单表查询或点击表名默认查询的数据,可双击数据在线修改"
:data="queryTab.execRes.data" stripe border class="mt5">
v-loading="queryTab.loading" <el-table-column v-if="queryTab.execRes.tableColumn.length > 0 && queryTab.nowTableName"
element-loading-text="查询中..." type="selection" width="35" />
size="small" <el-table-column min-width="100" :width="flexColumnWidth(item, queryTab.execRes.data)"
max-height="800" align="center" v-for="item in queryTab.execRes.tableColumn" :key="item" :prop="item"
empty-text="tips: select *开头的单表查询或点击表名默认查询的数据,可双击数据在线修改" :label="item" show-overflow-tooltip>
stripe
border
class="mt5"
>
<el-table-column
v-if="queryTab.execRes.tableColumn.length > 0 && queryTab.nowTableName"
type="selection"
width="35"
/>
<el-table-column
min-width="100"
:width="flexColumnWidth(item, queryTab.execRes.data)"
align="center"
v-for="item in queryTab.execRes.tableColumn"
:key="item"
:prop="item"
:label="item"
show-overflow-tooltip
>
</el-table-column> </el-table-column>
</el-table> </el-table>
</div> </div>
@@ -186,7 +139,8 @@
<el-tab-pane closable v-for="dt in dataTabs" :key="dt.name" :label="dt.label" :name="dt.name"> <el-tab-pane closable v-for="dt in dataTabs" :key="dt.name" :label="dt.label" :name="dt.name">
<el-row v-if="dbId"> <el-row v-if="dbId">
<el-col :span="8"> <el-col :span="8">
<el-link @click="onRefresh(dt.name)" icon="refresh" :underline="false" class="ml5"></el-link> <el-link @click="onRefresh(dt.name)" icon="refresh" :underline="false" class="ml5">
</el-link>
<el-divider direction="vertical" border-style="dashed" /> <el-divider direction="vertical" border-style="dashed" />
<el-link @click="addRow" type="primary" icon="plus" :underline="false"></el-link> <el-link @click="addRow" type="primary" icon="plus" :underline="false"></el-link>
@@ -196,7 +150,8 @@
<el-divider direction="vertical" border-style="dashed" /> <el-divider direction="vertical" border-style="dashed" />
<el-tooltip class="box-item" effect="dark" content="commit" placement="top"> <el-tooltip class="box-item" effect="dark" content="commit" placement="top">
<el-link @click="onCommit" type="success" icon="CircleCheck" :underline="false"></el-link> <el-link @click="onCommit" type="success" icon="CircleCheck" :underline="false">
</el-link>
</el-tooltip> </el-tooltip>
<el-divider direction="vertical" border-style="dashed" /> <el-divider direction="vertical" border-style="dashed" />
@@ -205,67 +160,42 @@
</el-tooltip> </el-tooltip>
</el-col> </el-col>
<el-col :span="16"> <el-col :span="16">
<el-input <el-input v-model="dt.condition" placeholder="若需条件过滤,可选择列并点击对应的字段并输入需要过滤的内容点击查询按钮即可"
v-model="dt.condition" clearable size="small" style="width: 100%">
placeholder="若需条件过滤,可选择列并点击对应的字段并输入需要过滤的内容点击查询按钮即可"
clearable
size="small"
style="width: 100%"
>
<template #prepend> <template #prepend>
<el-popover trigger="click" :width="320" placement="right"> <el-popover trigger="click" :width="320" placement="right">
<template #reference> <template #reference>
<el-link type="success" :underline="false">选择列</el-link> <el-link type="success" :underline="false">选择列</el-link>
</template> </template>
<el-table <el-table :data="getColumns4Map(dt.name)" max-height="500" size="small"
:data="getColumns4Map(dt.name)"
max-height="500"
size="small"
@row-click=" @row-click="
(...event) => { (...event: any) => {
onConditionRowClick(event, dt); onConditionRowClick(event, dt);
} }
" " style="cursor: pointer">
style="cursor: pointer" <el-table-column property="columnName" label="列名" show-overflow-tooltip>
> </el-table-column>
<el-table-column property="columnName" label="列名" show-overflow-tooltip> </el-table-column> <el-table-column property="columnComment" label="备注" show-overflow-tooltip>
<el-table-column property="columnComment" label="备注" show-overflow-tooltip> </el-table-column> </el-table-column>
</el-table> </el-table>
</el-popover> </el-popover>
</template> </template>
<template #append> <template #append>
<el-button @click="selectByCondition(dt.name, dt.condition)" icon="search" size="small"></el-button> <el-button @click="selectByCondition(dt.name, dt.condition)" icon="search"
size="small"></el-button>
</template> </template>
</el-input> </el-input>
</el-col> </el-col>
</el-row> </el-row>
<el-table <el-table @cell-dblclick="cellClick" @sort-change="onTableSortChange"
@cell-dblclick="cellClick" @selection-change="onDataSelectionChange" :data="dt.datas" size="small"
@sort-change="onTableSortChange" :max-height="dataTabsTableHeight" v-loading="dt.loading" element-loading-text="查询中..."
@selection-change="onDataSelectionChange" empty-text="暂无数据" stripe border class="mt5">
:data="dt.datas"
size="small"
:max-height="dataTabsTableHeight"
v-loading="dt.loading"
element-loading-text="查询中..."
empty-text="暂无数据"
stripe
border
class="mt5"
>
<el-table-column v-if="dt.datas.length > 0" type="selection" width="35" /> <el-table-column v-if="dt.datas.length > 0" type="selection" width="35" />
<el-table-column <el-table-column min-width="100" :width="flexColumnWidth(item, dt.datas)" align="center"
min-width="100" v-for="item in dt.columnNames" :key="item" :prop="item" :label="item" show-overflow-tooltip
:width="flexColumnWidth(item, dt.datas)" :sortable="nowTableName != '' ? 'custom' : false">
align="center"
v-for="item in dt.columnNames"
:key="item"
:prop="item"
:label="item"
show-overflow-tooltip
:sortable="nowTableName != '' ? 'custom' : false"
>
<template #header> <template #header>
<el-tooltip raw-content placement="top" effect="customized"> <el-tooltip raw-content placement="top" effect="customized">
<template #content> {{ getColumnTip(dt.name, item) }} </template> <template #content> {{ getColumnTip(dt.name, item) }} </template>
@@ -275,14 +205,9 @@
</el-table-column> </el-table-column>
</el-table> </el-table>
<el-row type="flex" class="mt5" justify="center"> <el-row type="flex" class="mt5" justify="center">
<el-pagination <el-pagination small :total="dt.count" @current-change="handlePageChange(dt)"
small layout="prev, pager, next, total, jumper" v-model:current-page="dt.pageNum"
:total="dt.count" :page-size="defalutLimit"></el-pagination>
@current-change="handlePageChange(dt)"
layout="prev, pager, next, total, jumper"
v-model:current-page="dt.pageNum"
:page-size="defalutLimit"
></el-pagination>
</el-row> </el-row>
</el-tab-pane> </el-tab-pane>
</el-tabs> </el-tabs>
@@ -318,8 +243,8 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { onMounted, toRefs, reactive, defineComponent, ref, watch } from 'vue'; import { onMounted, toRefs, reactive, ref, watch } from 'vue';
import { dbApi } from './api'; import { dbApi } from './api';
import 'codemirror/addon/hint/show-hint.css'; import 'codemirror/addon/hint/show-hint.css';
@@ -350,10 +275,6 @@ import { editor, languages, Position} from 'monaco-editor';
import ITextModel = editor.ITextModel; import ITextModel = editor.ITextModel;
import CompletionItem = languages.CompletionItem; import CompletionItem = languages.CompletionItem;
export default defineComponent({
name: 'SqlExec',
components: {},
setup() {
const store = useStore(); const store = useStore();
const codeTextarea: any = ref(null); const codeTextarea: any = ref(null);
const monacoTextarea: any = ref(null); const monacoTextarea: any = ref(null);
@@ -361,9 +282,29 @@ export default defineComponent({
let codemirror = null as any; let codemirror = null as any;
const tableMap = new Map(); const tableMap = new Map();
const defalutLimit = 20
const cmOptions = {
tabSize: 4,
mode: 'text/x-sql',
lineNumbers: true,
line: true,
indentWithTabs: true,
smartIndent: true,
matchBrackets: true,
theme: 'base16-light',
autofocus: true,
extraKeys: { Tab: 'autocomplete' }, // 自定义快捷键
hintOptions: {
completeSingle: false,
// 自定义提示选项
tables: {},
},
// more CodeMirror options...
}
const state = reactive({ const state = reactive({
token: token, token: token,
defalutLimit: 20, // 默认查询数量
tags: [], tags: [],
dbs: [] as any, // 数据库实例列表 dbs: [] as any, // 数据库实例列表
databaseList: [], // 数据库实例拥有的数据库列表1数据库实例 -> 多数据库 databaseList: [], // 数据库实例拥有的数据库列表1数据库实例 -> 多数据库
@@ -376,7 +317,6 @@ export default defineComponent({
sqlName: '', // 当前sql模板名 sqlName: '', // 当前sql模板名
sqlNames: [], // 所有sql模板名 sqlNames: [], // 所有sql模板名
activeName: 'Query', activeName: 'Query',
queryTabName: 'Query',
nowTableName: '', // 当前表格数据操作的数据库表名,用于双击编辑表内容使用 nowTableName: '', // 当前表格数据操作的数据库表名,用于双击编辑表内容使用
dataTabs: {} as any, // 点击表信息后执行结果数据展示tabs dataTabs: {} as any, // 点击表信息后执行结果数据展示tabs
dataTabsTableHeight: 600, dataTabsTableHeight: 600,
@@ -420,9 +360,29 @@ export default defineComponent({
} }
}); });
const {
tags,
dbs,
databaseList,
db,
dbId,
tableName,
tableMetadata,
sqlName,
sqlNames,
activeName,
nowTableName,
dataTabs,
dataTabsTableHeight,
queryTab,
params,
conditionDialog,
genSqlDialog,
} = toRefs(state)
const initCodemirror = () => { const initCodemirror = () => {
// 初始化编辑器实例,传入需要被实例化的文本域对象和默认配置 // 初始化编辑器实例,传入需要被实例化的文本域对象和默认配置
codemirror = _CodeMirror.fromTextArea(codeTextarea.value, state.cmOptions); codemirror = _CodeMirror.fromTextArea(codeTextarea.value, cmOptions);
codemirror.on('inputRead', (instance: any, changeObj: any) => { codemirror.on('inputRead', (instance: any, changeObj: any) => {
if (/^[a-zA-Z]/.test(changeObj.text[0])) { if (/^[a-zA-Z]/.test(changeObj.text[0])) {
instance.showHint(); instance.showHint();
@@ -663,7 +623,7 @@ onMounted(() => {
}; };
/** /**
* 项目及环境更改后的回调事件 * 标签更改后的回调事件
*/ */
const changeTag = () => { const changeTag = () => {
state.dbs = []; state.dbs = [];
@@ -678,11 +638,11 @@ onMounted(() => {
state.tags = await tagApi.getAccountTags.request(null); state.tags = await tagApi.getAccountTags.request(null);
}; };
const onBeforeChange = (instance: any, changeObj: any) => { // const onBeforeChange = (instance: any, changeObj: any) => {
var text = changeObj.text[0]; // var text = changeObj.text[0];
// 将sql提示去除 // // 将sql提示去除
changeObj.text[0] = text.split(' ')[0]; // changeObj.text[0] = text.split(' ')[0];
}; // };
/** /**
* 执行sql * 执行sql
@@ -966,7 +926,7 @@ onMounted(() => {
db, db,
}) })
.then((res) => { .then((res) => {
state.cmOptions.hintOptions.tables = res; cmOptions.hintOptions.tables = res;
}); });
getSqlNames(); getSqlNames();
}; };
@@ -1128,10 +1088,10 @@ const addTableSuggestions = (tables: any[]) => {
const getDefaultSelectSql = (tableName: string, where: string = '', orderBy: string = '', pageNum: number = 1) => { const getDefaultSelectSql = (tableName: string, where: string = '', orderBy: string = '', pageNum: number = 1) => {
const baseSql = `SELECT * FROM ${tableName} ${where ? 'WHERE ' + where : ''} ${orderBy ? orderBy : ''}`; const baseSql = `SELECT * FROM ${tableName} ${where ? 'WHERE ' + where : ''} ${orderBy ? orderBy : ''}`;
if (state.dbType == 'mysql') { if (state.dbType == 'mysql') {
return `${baseSql} LIMIT ${(pageNum - 1) * state.defalutLimit}, ${state.defalutLimit};`; return `${baseSql} LIMIT ${(pageNum - 1) * defalutLimit}, ${defalutLimit};`;
} }
if (state.dbType == 'postgres') { if (state.dbType == 'postgres') {
return `${baseSql} OFFSET ${(pageNum - 1) * state.defalutLimit} LIMIT ${state.defalutLimit};`; return `${baseSql} OFFSET ${(pageNum - 1) * defalutLimit} LIMIT ${defalutLimit};`;
} }
return baseSql; return baseSql;
}; };
@@ -1257,7 +1217,7 @@ const addTableSuggestions = (tables: any[]) => {
state.activeName = state.queryTab.name; state.activeName = state.queryTab.name;
state.queryTab.execRes.data = []; state.queryTab.execRes.data = [];
state.queryTab.execRes.tableColumn = []; state.queryTab.execRes.tableColumn = [];
state.cmOptions.hintOptions.tables = []; cmOptions.hintOptions.tables = [];
tableMap.clear(); tableMap.clear();
}; };
@@ -1471,48 +1431,6 @@ const addTableSuggestions = (tables: any[]) => {
watch(store.state.sqlExecInfo, async (newValue) => { watch(store.state.sqlExecInfo, async (newValue) => {
await setSelects(newValue); await setSelects(newValue);
}); });
return {
...toRefs(state),
getTags,
codeTextarea,
monacoTextarea,
changeTag,
changeTable,
cellClick,
onRunSql,
exportData,
removeDataTab,
onDataTabClick,
beforeUpload,
getUploadSqlFileUrl,
execSqlFileSuccess,
flexColumnWidth,
getColumnTip,
getColumns4Map,
onConditionRowClick,
onConfirmCondition,
onCancelCondition,
changeSqlTemplate,
deleteSql,
saveSql,
changeDbInstance,
changeDb,
clearDb,
formatSql,
onBeforeChange,
onRefresh,
handlePageChange,
selectByCondition,
onCommit,
addRow,
onDataSelectionChange,
onDeleteData,
onTableSortChange,
onGenerateInsertSql,
};
},
});
</script> </script>
<style lang="scss"> <style lang="scss">
@@ -1520,15 +1438,19 @@ const addTableSuggestions = (tables: any[]) => {
font-size: 8pt; font-size: 8pt;
font-weight: 600; font-weight: 600;
border: 1px solid #ccc; border: 1px solid #ccc;
.CodeMirror { .CodeMirror {
flex-grow: 1; flex-grow: 1;
z-index: 1; z-index: 1;
.CodeMirror-code { .CodeMirror-code {
line-height: 19px; line-height: 19px;
} }
font-family: 'JetBrainsMono'; font-family: 'JetBrainsMono';
} }
} }
.el-tabs__header { .el-tabs__header {
padding: 0 10px; padding: 0 10px;
background-color: #fff; background-color: #fff;

View File

@@ -2,19 +2,18 @@
<div> <div>
<el-dialog :title="`${title} 详情`" v-model="dialogVisible" :before-close="cancel" width="90%"> <el-dialog :title="`${title} 详情`" v-model="dialogVisible" :before-close="cancel" width="90%">
<el-table @cell-click="cellClick" :data="data.res"> <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-table>
</el-dialog> </el-dialog>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { watch, toRefs, reactive, defineComponent } from 'vue'; import { watch, toRefs, reactive } from 'vue';
export default defineComponent({ const props = defineProps({
name: 'tableEdit',
props: {
visible: { visible: {
type: Boolean, type: Boolean,
}, },
@@ -24,8 +23,11 @@ export default defineComponent({
data: { data: {
type: Object, type: Object,
}, },
}, })
setup(props: any, { emit }) {
//定义事件
const emit = defineEmits(['update:visible'])
const state = reactive({ const state = reactive({
dialogVisible: false, dialogVisible: false,
data: { data: {
@@ -34,12 +36,18 @@ export default defineComponent({
}, },
}); });
watch(props, async (newValue) => { const {
dialogVisible,
data,
} = toRefs(state)
watch(props, async (newValue: any) => {
state.dialogVisible = newValue.visible; state.dialogVisible = newValue.visible;
state.data.res = newValue.data.res; state.data.res = newValue.data.res;
state.data.colNames = newValue.data.colNames; 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 isDiv = cell.children[0].tagName === 'DIV';
let text = cell.children[0].innerText; let text = cell.children[0].innerText;
let div = cell.children[0]; let div = cell.children[0];
@@ -54,15 +62,10 @@ export default defineComponent({
}); });
} }
}; };
const cancel = () => { const cancel = () => {
emit('update:visible', false); emit('update:visible', false);
}; };
return {
...toRefs(state),
cancel,
cellClick,
};
},
});
</script> </script>

View File

@@ -2,7 +2,8 @@
<div> <div>
<el-dialog title="待执行SQL" v-model="dialogVisible" :show-close="false" width="600px"> <el-dialog title="待执行SQL" v-model="dialogVisible" :show-close="false" width="600px">
如需执行多条sql需要在数据库管理配置连接参数multiStatements=true 如需执行多条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" /> <el-input ref="remarkInputRef" v-model="remark" placeholder="请输入执行备注" class="mt5" />
<template #footer> <template #footer>
<span class="dialog-footer"> <span class="dialog-footer">
@@ -14,8 +15,8 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { toRefs, ref, nextTick, reactive, defineComponent } from 'vue'; import { toRefs, ref, nextTick, reactive } from 'vue';
import { dbApi } from '../api'; import { dbApi } from '../api';
import { ElDialog, ElButton, ElInput, ElMessage, InputInstance } from 'element-plus'; import { ElDialog, ElButton, ElInput, ElMessage, InputInstance } from 'element-plus';
// import base style // import base style
@@ -28,15 +29,7 @@ import { format as sqlFormatter } from 'sql-formatter';
import { SqlExecProps } from './SqlExecBox'; import { SqlExecProps } from './SqlExecBox';
export default defineComponent({ const props = defineProps({
name: 'SqlExecDialog',
components: {
codemirror,
ElButton,
ElDialog,
ElInput,
},
props: {
visible: { visible: {
type: Boolean, type: Boolean,
}, },
@@ -49,17 +42,9 @@ export default defineComponent({
sql: { sql: {
type: String, type: String,
}, },
}, })
setup(props: any) {
const remarkInputRef = ref<InputInstance>(); const cmOptions = {
const state = reactive({
dialogVisible: false,
sqlValue: '',
dbId: 0,
db: '',
remark: '',
btnLoading: false,
cmOptions: {
tabSize: 4, tabSize: 4,
mode: 'text/x-sql', mode: 'text/x-sql',
lineNumbers: true, lineNumbers: true,
@@ -70,10 +55,26 @@ export default defineComponent({
theme: 'base16-light', theme: 'base16-light',
autofocus: true, autofocus: true,
extraKeys: { Tab: 'autocomplete' }, // 自定义快捷键 extraKeys: { Tab: 'autocomplete' }, // 自定义快捷键
}, }
});
state.sqlValue = props.sql;
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 runSuccessCallback: any;
let cancelCallback: any; let cancelCallback: any;
let runSuccess: boolean = false; let runSuccess: boolean = false;
@@ -138,16 +139,6 @@ export default defineComponent({
}); });
}); });
}; };
return {
...toRefs(state),
remarkInputRef,
open,
runSql,
cancel,
};
},
});
</script> </script>
<style lang="scss"> <style lang="scss">
.codesql { .codesql {

View File

@@ -3,79 +3,56 @@
<el-dialog :title="title" v-model="dialogVisible" :show-close="true" :before-close="handleClose" width="800px"> <el-dialog :title="title" v-model="dialogVisible" :show-close="true" :before-close="handleClose" width="800px">
<div class="toolbar"> <div class="toolbar">
<div style="float: right"> <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>
</div> </div>
<el-table :data="fileTable" stripe style="width: 100%"> <el-table :data="fileTable" stripe style="width: 100%">
<el-table-column prop="name" label="名称" width> <el-table-column prop="name" label="名称" width>
<template #default="scope"> <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> </template>
</el-table-column> </el-table-column>
<el-table-column prop="name" label="类型" min-width="50px"> <el-table-column prop="name" label="类型" min-width="50px">
<template #default="scope"> <template #default="scope">
<el-select :disabled="scope.row.id != null" size="small" v-model="scope.row.type" style="width: 100px" placeholder="请选择"> <el-select :disabled="scope.row.id != null" size="small" v-model="scope.row.type"
<el-option v-for="item in enums.FileTypeEnum" :key="item.value" :label="item.label" :value="item.value"></el-option> 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> </el-select>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="path" label="路径" width> <el-table-column prop="path" label="路径" width>
<template #default="scope"> <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> </template>
</el-table-column> </el-table-column>
<el-table-column label="操作" width> <el-table-column label="操作" width>
<template #default="scope"> <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 v-if="scope.row.id == null" @click="addFiles(scope.row)" type="success"
>确定</el-button icon="success-filled" size="small" plain>确定</el-button>
> <el-button v-if="scope.row.id != null" @click="getConf(scope.row)" type="primary" icon="tickets"
<el-button v-if="scope.row.id != null" @click="getConf(scope.row)" type="primary" icon="tickets" size="small" plain size="small" plain>查看</el-button>
>查看</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-auth="'machine:file:del'"
type="danger"
@click="deleteRow(scope.$index, scope.row)"
icon="delete"
size="small"
plain
>删除</el-button
>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
<el-row style="margin-top: 10px" type="flex" justify="end"> <el-row style="margin-top: 10px" type="flex" justify="end">
<el-pagination <el-pagination small style="text-align: center" :total="total" layout="prev, pager, next, total, jumper"
small v-model:current-page="query.pageNum" :page-size="query.pageSize" @current-change="handlePageChange">
style="text-align: center" </el-pagination>
: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-row>
</el-dialog> </el-dialog>
<el-dialog :title="tree.title" v-model="tree.visible" :close-on-click-modal="false" width="70%"> <el-dialog :title="tree.title" v-model="tree.visible" :close-on-click-modal="false" width="70%">
<el-progress <el-progress v-if="uploadProgressShow" style="width: 90%; margin-left: 20px" :text-inside="true"
v-if="uploadProgressShow" :stroke-width="20" :percentage="progressNum" />
style="width: 90%; margin-left: 20px"
:text-inside="true"
:stroke-width="20"
:percentage="progressNum"
/>
<div style="height: 45vh; overflow: auto"> <div style="height: 45vh; overflow: auto">
<el-tree <el-tree v-if="tree.visible" ref="fileTree" :highlight-current="true" :load="loadNode"
v-if="tree.visible" :props="treeProps" lazy node-key="id" :expand-on-click-node="true">
ref="fileTree"
:highlight-current="true"
:load="loadNode"
:props="props"
lazy
node-key="id"
:expand-on-click-node="true"
>
<template #default="{ node, data }"> <template #default="{ node, data }">
<span class="custom-tree-node"> <span class="custom-tree-node">
<el-dropdown size="small" @visible-change="getFilePath(data, $event)" trigger="contextmenu"> <el-dropdown size="small" @visible-change="getFilePath(data, $event)" trigger="contextmenu">
@@ -97,31 +74,25 @@
<template #dropdown> <template #dropdown>
<el-dropdown-menu> <el-dropdown-menu>
<el-dropdown-item <el-dropdown-item @click="getFileContent(tree.folder.id, data.path)"
@click="getFileContent(tree.folder.id, data.path)" v-if="data.type == '-' && data.size < 1 * 1024 * 1024">
v-if="data.type == '-' && data.size < 1 * 1024 * 1024"
>
<el-link type="info" icon="view" :underline="false">查看</el-link> <el-link type="info" icon="view" :underline="false">查看</el-link>
</el-dropdown-item> </el-dropdown-item>
<span v-auth="'machine:file:write'"> <span v-auth="'machine:file:write'">
<el-dropdown-item @click="showCreateFileDialog(node, data)" v-if="data.type == 'd'"> <el-dropdown-item @click="showCreateFileDialog(node)"
<el-link type="primary" icon="document" :underline="false" style="margin-left: 2px">新建</el-link> v-if="data.type == 'd'">
<el-link type="primary" icon="document" :underline="false"
style="margin-left: 2px">新建</el-link>
</el-dropdown-item> </el-dropdown-item>
</span> </span>
<span v-auth="'machine:file:upload'"> <span v-auth="'machine:file:upload'">
<el-dropdown-item v-if="data.type == 'd'"> <el-dropdown-item v-if="data.type == 'd'">
<el-upload <el-upload :before-upload="beforeUpload" :on-success="uploadSuccess"
:before-upload="beforeUpload" action="" :http-request="getUploadFile" :headers="{ token }"
:on-success="uploadSuccess" :show-file-list="false" name="file"
action="" style="display: inline-block; margin-left: 2px">
: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-link icon="upload" :underline="false">上传</el-link>
</el-upload> </el-upload>
</el-dropdown-item> </el-dropdown-item>
@@ -129,21 +100,25 @@
<span v-auth="'machine:file:write'"> <span v-auth="'machine:file:write'">
<el-dropdown-item @click="downloadFile(node, data)" v-if="data.type == '-'"> <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> </el-dropdown-item>
</span> </span>
<span v-auth="'machine:file:rm'"> <span v-auth="'machine:file:rm'">
<el-dropdown-item @click="deleteFile(node, data)" v-if="!dontOperate(data)"> <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> </el-dropdown-item>
</span> </span>
</el-dropdown-menu> </el-dropdown-menu>
</template> </template>
</el-dropdown> </el-dropdown>
<span style="display: inline-block" class="ml15"> <span style="display: inline-block" class="ml15">
<span style="color: #67c23a" v-if="data.type == '-'">[{{ formatFileSize(data.size) }}]</span> <span style="color: #67c23a" v-if="data.type == '-'">[{{ formatFileSize(data.size)
<span v-if="data.mode" style="color: #67c23a">&nbsp;[{{ data.mode }} {{ data.modTime }}]</span> }}]</span>
<span v-if="data.mode" style="color: #67c23a">&nbsp;[{{ data.mode }} {{ data.modTime
}}]</span>
</span> </span>
</span> </span>
</template> </template>
@@ -151,15 +126,8 @@
</div> </div>
</el-dialog> </el-dialog>
<el-dialog <el-dialog :destroy-on-close="true" title="新建文件" v-model="createFileDialog.visible"
:destroy-on-close="true" :before-close="closeCreateFileDialog" :close-on-click-modal="false" top="5vh" width="400px">
title="新建文件"
v-model="createFileDialog.visible"
:before-close="closeCreateFileDialog"
:close-on-click-modal="false"
top="5vh"
width="400px"
>
<div> <div>
<el-form-item prop="name" label="名称:"> <el-form-item prop="name" label="名称:">
<el-input v-model.trim="createFileDialog.name" placeholder="请输入名称" auto-complete="off"></el-input> <el-input v-model.trim="createFileDialog.name" placeholder="请输入名称" auto-complete="off"></el-input>
@@ -180,16 +148,11 @@
</template> </template>
</el-dialog> </el-dialog>
<el-dialog <el-dialog :destroy-on-close="true" :title="fileContent.dialogTitle" v-model="fileContent.contentVisible"
:destroy-on-close="true" :close-on-click-modal="false" top="5vh" width="70%">
:title="fileContent.dialogTitle"
v-model="fileContent.contentVisible"
:close-on-click-modal="false"
top="5vh"
width="70%"
>
<div> <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> </div>
<template #footer> <template #footer>
@@ -202,8 +165,8 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { ref, toRefs, reactive, watch, defineComponent } from 'vue'; import { ref, toRefs, reactive, watch } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus'; import { ElMessage, ElMessageBox } from 'element-plus';
import { machineApi } from './api'; import { machineApi } from './api';
@@ -213,18 +176,20 @@ import enums from './enums';
import config from '@/common/config'; import config from '@/common/config';
import { isTrue } from '@/common/assert'; import { isTrue } from '@/common/assert';
export default defineComponent({ const props = defineProps({
name: 'FileManage',
components: {
codemirror,
},
props: {
visible: { type: Boolean }, visible: { type: Boolean },
machineId: { type: Number }, machineId: { type: Number },
title: { type: String }, title: { type: String },
}, })
const emit = defineEmits(['update:visible', 'cancel', 'update:machineId'])
const treeProps = {
label: 'name',
children: 'zones',
isLeaf: 'leaf',
}
setup(props: any, { emit }) {
const addFile = machineApi.addConf; const addFile = machineApi.addConf;
const delFile = machineApi.delConf; const delFile = machineApi.delConf;
const updateFileContent = machineApi.updateFileContent; const updateFileContent = machineApi.updateFileContent;
@@ -268,18 +233,13 @@ export default defineComponent({
}, },
resolve: {}, resolve: {},
}, },
props: {
label: 'name',
children: 'zones',
isLeaf: 'leaf',
},
progressNum: 0,
uploadProgressShow: false,
dataObj: { dataObj: {
name: '', name: '',
path: '', path: '',
type: '', type: '',
}, },
progressNum: 0,
uploadProgressShow: false,
createFileDialog: { createFileDialog: {
visible: false, visible: false,
name: '', name: '',
@@ -289,6 +249,18 @@ export default defineComponent({
file: null as any, file: null as any,
}); });
const {
dialogVisible,
query,
total,
fileTable,
fileContent,
tree,
progressNum,
uploadProgressShow,
createFileDialog,
} = toRefs(state)
watch(props, async (newValue) => { watch(props, async (newValue) => {
if (newValue.machineId && newValue.visible) { if (newValue.machineId && newValue.visible) {
await getFiles(); await getFiles();
@@ -297,7 +269,7 @@ export default defineComponent({
}); });
const getFiles = async () => { const getFiles = async () => {
state.query.id = props.machineId; state.query.id = props.machineId as any;
const res = await files.request(state.query); const res = await files.request(state.query);
state.fileTable = res.list; state.fileTable = res.list;
state.total = res.total; state.total = res.total;
@@ -536,7 +508,7 @@ export default defineComponent({
const params = new FormData(); const params = new FormData();
params.append('file', content.file); params.append('file', content.file);
params.append('path', state.dataObj.path); 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('fileId', state.tree.folder.id as any);
params.append('token', token); params.append('token', token);
machineApi.uploadFile machineApi.uploadFile
@@ -615,36 +587,7 @@ export default defineComponent({
} }
return '-'; 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> </script>
<style lang="scss"> <style lang="scss">
</style> </style>

View File

@@ -1,6 +1,7 @@
<template> <template>
<div> <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 :model="form" ref="machineForm" :rules="rules" label-width="85px">
<el-form-item prop="tagId" label="标签:" required> <el-form-item prop="tagId" label="标签:" required>
<tag-select v-model:tag-id="form.tagId" v-model:tag-path="form.tagPath" style="width: 100%" /> <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>
<el-form-item prop="ip" label="ip:" required> <el-form-item prop="ip" label="ip:" required>
<el-col :span="18"> <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>
<el-col style="text-align: center" :span="1">:</el-col> <el-col style="text-align: center" :span="1">:</el-col>
<el-col :span="5"> <el-col :span="5">
@@ -27,15 +29,11 @@
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item v-if="form.authMethod == 1" prop="password" label="密码:"> <el-form-item v-if="form.authMethod == 1" prop="password" label="密码:">
<el-input <el-input type="password" show-password v-model.trim="form.password" placeholder="请输入密码,修改操作可不填"
type="password" autocomplete="new-password">
show-password
v-model.trim="form.password"
placeholder="请输入密码,修改操作可不填"
autocomplete="new-password"
>
<template v-if="form.id && form.id != 0" #suffix> <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> <template #reference>
<el-link @click="getPwd" :underline="false" type="primary" class="mr5">原密码</el-link> <el-link @click="getPwd" :underline="false" type="primary" class="mr5">原密码</el-link>
</template> </template>
@@ -44,7 +42,8 @@
</el-input> </el-input>
</el-form-item> </el-form-item>
<el-form-item v-if="form.authMethod == 2" prop="password" label="秘钥:"> <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>
<el-form-item prop="remark" label="备注:"> <el-form-item prop="remark" label="备注:">
<el-input type="textarea" v-model="form.remark"></el-input> <el-input type="textarea" v-model="form.remark"></el-input>
@@ -56,17 +55,14 @@
<el-form-item prop="enableSshTunnel" label="SSH隧道:"> <el-form-item prop="enableSshTunnel" label="SSH隧道:">
<el-col :span="3"> <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>
<el-col :span="2" v-if="form.enableSshTunnel == 1"> 机器: </el-col> <el-col :span="2" v-if="form.enableSshTunnel == 1"> 机器: </el-col>
<el-col :span="19" v-if="form.enableSshTunnel == 1"> <el-col :span="19" v-if="form.enableSshTunnel == 1">
<el-select style="width: 100%" v-model="form.sshTunnelMachineId" placeholder="请选择SSH隧道机器"> <el-select style="width: 100%" v-model="form.sshTunnelMachineId" placeholder="请选择SSH隧道机器">
<el-option <el-option v-for="item in sshTunnelMachineList" :key="item.id"
v-for="item in sshTunnelMachineList" :label="`${item.ip}:${item.port} [${item.name}]`" :value="item.id">
:key="item.id"
:label="`${item.ip}:${item.port} [${item.name}]`"
:value="item.id"
>
</el-option> </el-option>
</el-select> </el-select>
</el-col> </el-col>
@@ -83,20 +79,15 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { toRefs, reactive, watch, defineComponent, ref } from 'vue'; import { toRefs, reactive, watch, ref } from 'vue';
import { machineApi } from './api'; import { machineApi } from './api';
import { ElMessage } from 'element-plus'; import { ElMessage } from 'element-plus';
import { notBlank } from '@/common/assert'; import { notBlank } from '@/common/assert';
import { RsaEncrypt } from '@/common/rsa'; import { RsaEncrypt } from '@/common/rsa';
import TagSelect from '../component/TagSelect.vue'; import TagSelect from '../component/TagSelect.vue';
export default defineComponent({ const props = defineProps({
name: 'MachineEdit',
components: {
TagSelect,
},
props: {
visible: { visible: {
type: Boolean, type: Boolean,
}, },
@@ -109,33 +100,12 @@ export default defineComponent({
title: { title: {
type: String, type: String,
}, },
}, })
setup(props: any, { emit }) {
const machineForm: any = ref(null); //定义事件
const state = reactive({ const emit = defineEmits(['update:visible', 'cancel', 'val-change'])
dialogVisible: false,
projects: [] as any, const rules = {
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: {
tagId: [ tagId: [
{ {
required: true, required: true,
@@ -171,15 +141,44 @@ export default defineComponent({
trigger: ['change', 'blur'], trigger: ['change', 'blur'],
}, },
], ],
}
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,
}); });
watch(props, async (newValue) => { const {
dialogVisible,
sshTunnelMachineList,
form,
pwd,
btnLoading,
} = toRefs(state)
watch(props, async (newValue: any) => {
state.dialogVisible = newValue.visible; state.dialogVisible = newValue.visible;
if (!state.dialogVisible) { if (!state.dialogVisible) {
return; return;
} }
state.projects = newValue.projects;
if (newValue.machine) { if (newValue.machine) {
state.form = { ...newValue.machine }; state.form = { ...newValue.machine };
} else { } else {
@@ -242,17 +241,7 @@ export default defineComponent({
emit('update:visible', false); emit('update:visible', false);
emit('cancel'); emit('cancel');
}; };
return {
...toRefs(state),
machineForm,
getSshTunnelMachines,
getPwd,
btnOk,
cancel,
};
},
});
</script> </script>
<style lang="scss"> <style lang="scss">
</style> </style>

View File

@@ -2,27 +2,21 @@
<div> <div>
<el-card> <el-card>
<div> <div>
<el-button v-auth="'machine:add'" type="primary" icon="plus" @click="openFormDialog(false)" plain>添加</el-button> <el-button v-auth="'machine:add'" type="primary" icon="plus" @click="openFormDialog(false)" plain>添加
<el-button v-auth="'machine:update'" type="primary" icon="edit" :disabled="!currentId" @click="openFormDialog(currentData)" plain </el-button>
>编辑</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 v-auth="'machine:del'" :disabled="!currentId" @click="deleteMachine(currentId)" type="danger"
>删除</el-button icon="delete">删除</el-button>
>
<div style="float: right"> <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-option v-for="item in tags" :key="item" :label="item" :value="item"> </el-option>
</el-select> </el-select>
<el-input <el-input class="ml5" placeholder="请输入名称" style="width: 150px" v-model="params.name" @clear="search"
class="ml5" plain clearable></el-input>
placeholder="请输入名称" <el-input class="ml5" placeholder="请输入ip" style="width: 150px" v-model="params.ip" @clear="search"
style="width: 150px" plain clearable></el-input>
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> <el-button class="ml5" @click="search" type="success" icon="search"></el-button>
</div> </div>
</div> </div>
@@ -35,32 +29,25 @@
</el-radio> </el-radio>
</template> </template>
</el-table-column> </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="name" label="名称" min-width="140" show-overflow-tooltip></el-table-column>
<el-table-column prop="ip" label="ip:port" min-width="150"> <el-table-column prop="ip" label="ip:port" min-width="150">
<template #default="scope"> <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}` `${scope.row.ip}:${scope.row.port}`
}}</el-link> }}</el-link>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="status" label="状态" min-width="75"> <el-table-column prop="status" label="状态" min-width="75">
<template #default="scope"> <template #default="scope">
<el-switch <el-switch v-auth:disabled="'machine:update'" :width="47" v-model="scope.row.status"
v-auth:disabled="'machine:update'" :active-value="1" :inactive-value="-1" inline-prompt active-text="启用" inactive-text="停用"
: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" style="--el-switch-on-color: #13ce66; --el-switch-off-color: #ff4949"
@change="changeStatus(scope.row)" @change="changeStatus(scope.row)"></el-switch>
></el-switch>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="username" label="用户名" min-width="90"></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="remark" label="备注" min-width="250" show-overflow-tooltip></el-table-column>
<el-table-column prop="createTime" label="创建时间" min-width="165"> <el-table-column prop="createTime" label="创建时间" min-width="165">
<template #default="scope"> <template #default="scope">
@@ -71,40 +58,19 @@
<el-table-column label="操作" min-width="235" fixed="right"> <el-table-column label="操作" min-width="235" fixed="right">
<template #default="scope"> <template #default="scope">
<span v-auth="'machine:terminal'"> <span v-auth="'machine:terminal'">
<el-link <el-link :disabled="scope.row.status == -1" type="primary" @click="showTerminal(scope.row)"
:disabled="scope.row.status == -1" plain size="small" :underline="false">终端</el-link>
type="primary"
@click="showTerminal(scope.row)"
plain
size="small"
:underline="false"
>终端</el-link
>
<el-divider direction="vertical" border-style="dashed" /> <el-divider direction="vertical" border-style="dashed" />
</span> </span>
<span v-auth="'machine:file'"> <span v-auth="'machine:file'">
<el-link <el-link type="success" :disabled="scope.row.status == -1"
type="success" @click="showFileManage(scope.row)" plain size="small" :underline="false">文件</el-link>
:disabled="scope.row.status == -1"
@click="fileManage(scope.row)"
plain
size="small"
:underline="false"
>文件</el-link
>
<el-divider direction="vertical" border-style="dashed" /> <el-divider direction="vertical" border-style="dashed" />
</span> </span>
<el-link <el-link :disabled="scope.row.status == -1" type="warning" @click="serviceManager(scope.row)"
:disabled="scope.row.status == -1" plain size="small" :underline="false">脚本</el-link>
type="warning"
@click="serviceManager(scope.row)"
plain
size="small"
:underline="false"
>脚本</el-link
>
<el-divider direction="vertical" border-style="dashed" /> <el-divider direction="vertical" border-style="dashed" />
<el-dropdown> <el-dropdown>
@@ -116,34 +82,21 @@
</span> </span>
<template #dropdown> <template #dropdown>
<el-dropdown-menu> <el-dropdown-menu>
<el-dropdown-item <el-dropdown-item>
><el-link <el-link @click="showProcess(scope.row)" :disabled="scope.row.status == -1"
@click="showProcess(scope.row)" plain :underline="false" size="small">进程</el-link>
:disabled="scope.row.status == -1" </el-dropdown-item>
plain
:underline="false"
size="small"
>进程</el-link
></el-dropdown-item
>
<el-dropdown-item v-if="scope.row.enableRecorder == 1" <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 v-auth="'machine:update'" @click="showRec(scope.row)" plain
>终端回放</el-link :underline="false" size="small">终端回放</el-link>
></el-dropdown-item </el-dropdown-item>
>
<el-dropdown-item <el-dropdown-item>
><el-link <el-link :disabled="!scope.row.hasCli || scope.row.status == -1" type="danger"
:disabled="!scope.row.hasCli || scope.row.status == -1" @click="closeCli(scope.row)" plain size="small" :underline="false">关闭连接
type="danger" </el-link>
@click="closeCli(scope.row)" </el-dropdown-item>
plain
size="small"
:underline="false"
>关闭连接</el-link
></el-dropdown-item
>
</el-dropdown-menu> </el-dropdown-menu>
</template> </template>
</el-dropdown> </el-dropdown>
@@ -151,42 +104,33 @@
</el-table-column> </el-table-column>
</el-table> </el-table>
<el-row style="margin-top: 20px" type="flex" justify="end"> <el-row style="margin-top: 20px" type="flex" justify="end">
<el-pagination <el-pagination style="text-align: right" :total="data.total" layout="prev, pager, next, total, jumper"
style="text-align: right" v-model:current-page="params.pageNum" :page-size="params.pageSize"
:total="data.total" @current-change="handlePageChange"></el-pagination>
layout="prev, pager, next, total, jumper"
v-model:current-page="params.pageNum"
:page-size="params.pageSize"
@current-change="handlePageChange"
></el-pagination>
</el-row> </el-row>
</el-card> </el-card>
<machine-edit <machine-edit :title="machineEditDialog.title" v-model:visible="machineEditDialog.visible"
:title="machineEditDialog.title" v-model:machine="machineEditDialog.data" @valChange="submitSuccess"></machine-edit>
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" /> <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 <machine-stats v-model:visible="machineStatsDialog.visible" :machineId="machineStatsDialog.machineId"
v-model:visible="machineStatsDialog.visible" :title="machineStatsDialog.title"></machine-stats>
: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> </div>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { toRefs, reactive, onMounted, defineComponent } from 'vue'; import { toRefs, reactive, onMounted } from 'vue';
import { useRouter } from 'vue-router'; import { useRouter } from 'vue-router';
import { ElMessage, ElMessageBox } from 'element-plus'; import { ElMessage, ElMessageBox } from 'element-plus';
import { machineApi } from './api'; import { machineApi } from './api';
@@ -199,21 +143,9 @@ import MachineStats from './MachineStats.vue';
import MachineRec from './MachineRec.vue'; import MachineRec from './MachineRec.vue';
import { dateFormat } from '@/common/utils/date'; import { dateFormat } from '@/common/utils/date';
export default defineComponent({
name: 'MachineList',
components: {
ServiceManage,
ProcessList,
FileManage,
MachineEdit,
MachineStats,
MachineRec,
},
setup() {
const router = useRouter(); const router = useRouter();
const state = reactive({ const state = reactive({
tags: [] as any, tags: [] as any,
stats: '',
params: { params: {
pageNum: 1, pageNum: 1,
pageSize: 10, pageSize: 10,
@@ -261,6 +193,20 @@ export default defineComponent({
}, },
}); });
const {
tags,
params,
data,
currentId,
currentData,
serviceDialog,
processDialog,
fileDialog,
machineStatsDialog,
machineEditDialog,
machineRecDialog,
} = toRefs(state)
onMounted(async () => { onMounted(async () => {
search(); search();
}); });
@@ -356,7 +302,7 @@ export default defineComponent({
search(); search();
}; };
const fileManage = (currentData: any) => { const showFileManage = (currentData: any) => {
state.fileDialog.visible = true; state.fileDialog.visible = true;
state.fileDialog.machineId = currentData.id; state.fileDialog.machineId = currentData.id;
state.fileDialog.title = `${currentData.name} => ${currentData.ip}`; state.fileDialog.title = `${currentData.name} => ${currentData.ip}`;
@@ -382,34 +328,13 @@ export default defineComponent({
state.machineRecDialog.machineId = row.id; state.machineRecDialog.machineId = row.id;
state.machineRecDialog.visible = true; state.machineRecDialog.visible = true;
}; };
return {
...toRefs(state),
dateFormat,
choose,
getTags,
showTerminal,
openFormDialog,
deleteMachine,
closeCli,
serviceManager,
showMachineStats,
showProcess,
changeStatus,
submitSuccess,
fileManage,
search,
showRec,
handlePageChange,
};
},
});
</script> </script>
<style> <style>
.el-dialog__body { .el-dialog__body {
padding: 2px 2px; padding: 2px 2px;
} }
.el-dropdown-link-machine-list { .el-dropdown-link-machine-list {
cursor: pointer; cursor: pointer;
color: var(--el-color-primary); color: var(--el-color-primary);

View File

@@ -1,13 +1,7 @@
<template> <template>
<div id="terminalRecDialog"> <div id="terminalRecDialog">
<el-dialog <el-dialog :title="title" v-model="dialogVisible" :before-close="handleClose" :close-on-click-modal="false"
:title="title" :destroy-on-close="true" width="70%">
v-model="dialogVisible"
:before-close="handleClose"
:close-on-click-modal="false"
:destroy-on-close="true"
width="70%"
>
<div class="toolbar"> <div class="toolbar">
<el-select @change="getUsers" v-model="operateDate" placeholder="操作日期" filterable> <el-select @change="getUsers" v-model="operateDate" placeholder="操作日期" filterable>
<el-option v-for="item in operateDates" :key="item" :label="item" :value="item"> </el-option> <el-option v-for="item in operateDates" :key="item" :label="item" :value="item"> </el-option>
@@ -26,21 +20,20 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { toRefs, watch, ref, reactive, defineComponent } from 'vue'; import { toRefs, watch, ref, reactive } from 'vue';
import { machineApi } from './api'; import { machineApi } from './api';
import * as AsciinemaPlayer from 'asciinema-player'; import * as AsciinemaPlayer from 'asciinema-player';
import 'asciinema-player/dist/bundle/asciinema-player.css'; import 'asciinema-player/dist/bundle/asciinema-player.css';
export default defineComponent({ const props = defineProps({
name: 'MachineRec',
components: {},
props: {
visible: { type: Boolean }, visible: { type: Boolean },
machineId: { type: Number }, machineId: { type: Number },
title: { type: String }, title: { type: String },
}, })
setup(props: any, context) {
const emit = defineEmits(['update:visible', 'cancel', 'update:machineId'])
const playerRef = ref(null); const playerRef = ref(null);
const state = reactive({ const state = reactive({
dialogVisible: false, dialogVisible: false,
@@ -54,7 +47,18 @@ export default defineComponent({
rec: '', 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; const visible = newValue.visible;
if (visible) { if (visible) {
state.machineId = newValue.machineId; state.machineId = newValue.machineId;
@@ -106,9 +110,9 @@ export default defineComponent({
* 关闭取消按钮触发的事件 * 关闭取消按钮触发的事件
*/ */
const handleClose = () => { const handleClose = () => {
context.emit('update:visible', false); emit('update:visible', false);
context.emit('update:machineId', null); emit('update:machineId', null);
context.emit('cancel'); emit('cancel');
state.operateDates = []; state.operateDates = [];
state.users = []; state.users = [];
state.recs = []; state.recs = [];
@@ -116,17 +120,6 @@ export default defineComponent({
state.user = ''; state.user = '';
state.rec = ''; state.rec = '';
}; };
return {
...toRefs(state),
playerRef,
getUsers,
getRecs,
playRec,
handleClose,
};
},
});
</script> </script>
<style lang="scss"> <style lang="scss">
#terminalRecDialog { #terminalRecDialog {

View File

@@ -1,6 +1,7 @@
<template> <template>
<div> <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-row :gutter="20">
<el-col :lg="12" :md="12"> <el-col :lg="12" :md="12">
<el-descriptions size="small" title="基础信息" :column="2" border> <el-descriptions size="small" title="基础信息" :column="2" border>
@@ -19,7 +20,8 @@
<el-descriptions-item label="运行中任务"> <el-descriptions-item label="运行中任务">
{{ stats.RunningProcs }} {{ stats.RunningProcs }}
</el-descriptions-item> </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-descriptions>
</el-col> </el-col>
@@ -36,7 +38,8 @@
<el-col :lg="8" :md="8"> <el-col :lg="8" :md="8">
<span style="font-size: 16px; font-weight: 700">磁盘</span> <span style="font-size: 16px; font-weight: 700">磁盘</span>
<el-table :data="stats.FSInfos" stripe max-height="250" style="width: 100%" border> <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> <el-table-column prop="Used" label="可使用" min-width="70" show-overflow-tooltip>
<template #default="scope"> <template #default="scope">
{{ formatByteSize(scope.row.Free) }} {{ formatByteSize(scope.row.Free) }}
@@ -54,8 +57,10 @@
<span style="font-size: 16px; font-weight: 700">网卡</span> <span style="font-size: 16px; font-weight: 700">网卡</span>
<el-table :data="netInter" stripe max-height="250" style="width: 100%" border> <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="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="IPv4" label="IPv4" min-width="130" show-overflow-tooltip>
<el-table-column prop="IPv6" label="IPv6" min-width="130" show-overflow-tooltip></el-table-column> </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> <el-table-column prop="Rx" label="接收(rx)" min-width="110" show-overflow-tooltip>
<template #default="scope"> <template #default="scope">
{{ formatByteSize(scope.row.Rx) }} {{ formatByteSize(scope.row.Rx) }}
@@ -73,17 +78,14 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { toRefs, reactive, watch, defineComponent, ref, nextTick } from 'vue'; import { toRefs, reactive, watch, ref, nextTick } from 'vue';
import useEcharts from '@/common/echarts/useEcharts.ts'; import useEcharts from '@/common/echarts/useEcharts.ts';
import tdTheme from '@/common/echarts/theme.json'; import tdTheme from '@/common/echarts/theme.json';
import { formatByteSize } from '@/common/utils/format'; import { formatByteSize } from '@/common/utils/format';
import { machineApi } from './api'; import { machineApi } from './api';
export default defineComponent({ const props = defineProps({
name: 'MachineStats',
components: {},
props: {
visible: { visible: {
type: Boolean, type: Boolean,
}, },
@@ -96,8 +98,10 @@ export default defineComponent({
title: { title: {
type: String, type: String,
}, },
}, })
setup(props: any, { emit }) {
const emit = defineEmits(['update:visible', 'cancel', 'update:machineId'])
const cpuRef: any = ref(); const cpuRef: any = ref();
const memRef: any = ref(); const memRef: any = ref();
@@ -106,12 +110,19 @@ export default defineComponent({
const state = reactive({ const state = reactive({
dialogVisible: false, dialogVisible: false,
charts: [] as any,
stats: {} as any, stats: {} as any,
netInter: [] 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; const visible = newValue.visible;
if (visible) { if (visible) {
await setStats(); await setStats();
@@ -186,7 +197,7 @@ export default defineComponent({
} }
const chart: any = useEcharts(memRef.value, tdTheme, option); const chart: any = useEcharts(memRef.value, tdTheme, option);
memChart = chart; memChart = chart;
state.charts.push(chart); charts.push(chart);
}; };
const initCpuStats = () => { const initCpuStats = () => {
@@ -253,7 +264,7 @@ export default defineComponent({
} }
const chart: any = useEcharts(cpuRef.value, tdTheme, option); const chart: any = useEcharts(cpuRef.value, tdTheme, option);
cpuChart = chart; cpuChart = chart;
state.charts.push(chart); charts.push(chart);
}; };
const initCharts = () => { const initCharts = () => {
@@ -267,9 +278,9 @@ export default defineComponent({
const initEchartResizeFun = () => { const initEchartResizeFun = () => {
nextTick(() => { nextTick(() => {
for (let i = 0; i < state.charts.length; i++) { for (let i = 0; i < charts.length; i++) {
setTimeout(() => { setTimeout(() => {
state.charts[i].resize(); charts[i].resize();
}, i * 1000); }, i * 1000);
} }
}); });
@@ -301,17 +312,6 @@ export default defineComponent({
memChart = null; memChart = null;
}, 200); }, 200);
}; };
return {
...toRefs(state),
cpuRef,
memRef,
cancel,
formatByteSize,
onRefresh,
};
},
});
</script> </script>
<style lang="scss"> <style lang="scss">
.card-item-chart { .card-item-chart {

View File

@@ -1,6 +1,7 @@
<template> <template>
<div class="file-manage"> <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"> <div class="toolbar">
<el-row> <el-row>
<el-col :span="4"> <el-col :span="4">
@@ -21,7 +22,8 @@
</el-select> </el-select>
</el-col> </el-col>
<el-col :span="6"> <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-col>
</el-row> </el-row>
</div> </div>
@@ -35,7 +37,9 @@
<template #header> <template #header>
VSZ VSZ
<el-tooltip class="box-item" effect="dark" content="虚拟内存" placement="top"> <el-tooltip class="box-item" effect="dark" content="虚拟内存" placement="top">
<el-icon><question-filled /></el-icon> <el-icon>
<question-filled />
</el-icon>
</el-tooltip> </el-tooltip>
</template> </template>
</el-table-column> </el-table-column>
@@ -43,7 +47,9 @@
<template #header> <template #header>
RSS RSS
<el-tooltip class="box-item" effect="dark" content="固定内存" placement="top"> <el-tooltip class="box-item" effect="dark" content="固定内存" placement="top">
<el-icon><question-filled /></el-icon> <el-icon>
<question-filled />
</el-icon>
</el-tooltip> </el-tooltip>
</template> </template>
</el-table-column> </el-table-column>
@@ -51,7 +57,9 @@
<template #header> <template #header>
STAT STAT
<el-tooltip class="box-item" effect="dark" content="进程状态" placement="top"> <el-tooltip class="box-item" effect="dark" content="进程状态" placement="top">
<el-icon><question-filled /></el-icon> <el-icon>
<question-filled />
</el-icon>
</el-tooltip> </el-tooltip>
</template> </template>
</el-table-column> </el-table-column>
@@ -59,7 +67,9 @@
<template #header> <template #header>
START START
<el-tooltip class="box-item" effect="dark" content="启动时间" placement="top"> <el-tooltip class="box-item" effect="dark" content="启动时间" placement="top">
<el-icon><question-filled /></el-icon> <el-icon>
<question-filled />
</el-icon>
</el-tooltip> </el-tooltip>
</template> </template>
</el-table-column> </el-table-column>
@@ -67,17 +77,21 @@
<template #header> <template #header>
TIME TIME
<el-tooltip class="box-item" effect="dark" content="该进程实际使用CPU运作的时间" placement="top"> <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> </el-tooltip>
</template> </template>
</el-table-column> </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="操作"> <el-table-column label="操作">
<template #default="scope"> <template #default="scope">
<el-popconfirm title="确定终止该进程?" @confirm="confirmKillProcess(scope.row.pid)"> <el-popconfirm title="确定终止该进程?" @confirm="confirmKillProcess(scope.row.pid)">
<template #reference> <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> </template>
</el-popconfirm> </el-popconfirm>
<!-- <el-button @click="addFiles(scope.row)" type="danger" icon="delete" size="small" plain>终止</el-button> --> <!-- <el-button @click="addFiles(scope.row)" type="danger" icon="delete" size="small" plain>终止</el-button> -->
@@ -88,20 +102,19 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { toRefs, reactive, watch, defineComponent } from 'vue'; import { toRefs, reactive, watch } from 'vue';
import { ElMessage } from 'element-plus'; import { ElMessage } from 'element-plus';
import { machineApi } from './api'; import { machineApi } from './api';
import enums from './enums';
export default defineComponent({ const props = defineProps({
name: 'ProcessList',
components: {},
props: {
visible: { type: Boolean }, visible: { type: Boolean },
machineId: { type: Number }, machineId: { type: Number },
title: { type: String }, title: { type: String },
}, })
setup(props: any, context) {
const emit = defineEmits(['update:visible', 'cancel', 'update:machineId'])
const state = reactive({ const state = reactive({
dialogVisible: false, dialogVisible: false,
params: { params: {
@@ -113,6 +126,13 @@ export default defineComponent({
processList: [], processList: [],
}); });
const {
dialogVisible,
params,
processList,
} = toRefs(state)
watch(props, (newValue) => { watch(props, (newValue) => {
if (props.machineId) { if (props.machineId) {
state.params.id = props.machineId; state.params.id = props.machineId;
@@ -181,9 +201,9 @@ export default defineComponent({
* 关闭取消按钮触发的事件 * 关闭取消按钮触发的事件
*/ */
const handleClose = () => { const handleClose = () => {
context.emit('update:visible', false); emit('update:visible', false);
context.emit('update:machineId', null); emit('update:machineId', null);
context.emit('cancel'); emit('cancel');
state.params = { state.params = {
name: '', name: '',
sortType: '1', sortType: '1',
@@ -192,14 +212,4 @@ export default defineComponent({
}; };
state.processList = []; state.processList = [];
}; };
return {
...toRefs(state),
getProcess,
confirmKillProcess,
enums,
handleClose,
};
},
});
</script> </script>

View File

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

View File

@@ -1,6 +1,7 @@
<template> <template>
<div class="file-manage"> <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 class="toolbar">
<div style="float: left"> <div style="float: left">
<el-select v-model="type" @change="getScripts" size="small" placeholder="请选择"> <el-select v-model="type" @change="getScripts" size="small" placeholder="请选择">
@@ -9,20 +10,12 @@
</el-select> </el-select>
</div> </div>
<div style="float: right"> <div style="float: right">
<el-button @click="editScript(currentData)" :disabled="currentId == null" type="primary" icon="tickets" size="small" plain <el-button @click="editScript(currentData)" :disabled="currentId == null" type="primary"
>查看</el-button icon="tickets" size="small" plain>查看</el-button>
> <el-button v-auth="'machine:script:save'" type="primary" @click="editScript(null)" icon="plus"
<el-button v-auth="'machine:script:save'" type="primary" @click="editScript(null)" icon="plus" size="small" plain>添加</el-button> size="small" plain>添加</el-button>
<el-button <el-button v-auth="'machine:script:del'" :disabled="currentId == null" type="danger"
v-auth="'machine:script:del'" @click="deleteRow(currentData)" icon="delete" size="small" plain>删除</el-button>
:disabled="currentId == null"
type="danger"
@click="deleteRow(currentData)"
icon="delete"
size="small"
plain
>删除</el-button
>
</div> </div>
</div> </div>
@@ -43,56 +36,32 @@
</el-table-column> </el-table-column>
<el-table-column label="操作"> <el-table-column label="操作">
<template #default="scope"> <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 v-if="scope.row.id == null" type="success" icon="el-icon-success" size="small" plain>
>确定</el-button 确定</el-button>
>
<el-button <el-button v-auth="'machine:script:run'" v-if="scope.row.id != null"
v-auth="'machine:script:run'" @click="runScript(scope.row)" type="primary" icon="video-play" size="small" plain>执行
v-if="scope.row.id != null" </el-button>
@click="runScript(scope.row)"
type="primary"
icon="video-play"
size="small"
plain
>执行</el-button
>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
<el-row style="margin-top: 10px" type="flex" justify="end"> <el-row style="margin-top: 10px" type="flex" justify="end">
<el-pagination <el-pagination small style="text-align: center" :total="total" layout="prev, pager, next, total, jumper"
small v-model:current-page="query.pageNum" :page-size="query.pageSize" @current-change="handlePageChange">
style="text-align: center" </el-pagination>
: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-row>
</el-dialog> </el-dialog>
<el-dialog title="脚本参数" v-model="scriptParamsDialog.visible" width="400px"> <el-dialog title="脚本参数" v-model="scriptParamsDialog.visible" width="400px">
<el-form ref="paramsForm" :model="scriptParamsDialog.params" label-width="70px" size="small"> <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-form-item v-for="item in scriptParamsDialog.paramsFormItem as any" :key="item.name"
<el-input :prop="item.model" :label="item.name" required>
v-if="!item.options" <el-input v-if="!item.options" v-model="scriptParamsDialog.params[item.model]"
v-model="scriptParamsDialog.params[item.model]" :placeholder="item.placeholder" autocomplete="off" clearable></el-input>
:placeholder="item.placeholder" <el-select v-else v-model="scriptParamsDialog.params[item.model]" :placeholder="item.placeholder"
autocomplete="off" filterable autocomplete="off" clearable style="width: 100%">
clearable <el-option v-for="option in item.options.split(',')" :key="option" :label="option"
></el-input> :value="option" />
<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-select>
</el-form-item> </el-form-item>
</el-form> </el-form>
@@ -109,49 +78,33 @@
</div> </div>
</el-dialog> </el-dialog>
<el-dialog <el-dialog v-if="terminalDialog.visible" title="终端" v-model="terminalDialog.visible" width="80%"
v-if="terminalDialog.visible" :close-on-click-modal="false" :modal="false" @close="closeTermnial">
title="终端" <ssh-terminal ref="terminal" :cmd="terminalDialog.cmd" :machineId="terminalDialog.machineId"
v-model="terminalDialog.visible" height="560px" />
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> </el-dialog>
<script-edit <script-edit v-model:visible="editDialog.visible" v-model:data="editDialog.data" :title="editDialog.title"
v-model:visible="editDialog.visible" v-model:machineId="editDialog.machineId" :isCommon="type == 1" @submitSuccess="submitSuccess" />
v-model:data="editDialog.data"
:title="editDialog.title"
v-model:machineId="editDialog.machineId"
:isCommon="type == 1"
@submitSuccess="submitSuccess"
/>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { ref, toRefs, reactive, watch, defineComponent } from 'vue'; import { ref, toRefs, reactive, watch } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus'; import { ElMessage, ElMessageBox } from 'element-plus';
import SshTerminal from './SshTerminal.vue'; import SshTerminal from './SshTerminal.vue';
import { machineApi } from './api'; import { machineApi } from './api';
import enums from './enums'; import enums from './enums';
import ScriptEdit from './ScriptEdit.vue'; import ScriptEdit from './ScriptEdit.vue';
export default defineComponent({ const props = defineProps({
name: 'ServiceManage',
components: {
ScriptEdit,
SshTerminal,
},
props: {
visible: { type: Boolean }, visible: { type: Boolean },
machineId: { type: Number }, machineId: { type: Number },
title: { type: String }, title: { type: String },
}, })
setup(props: any, context) {
const emit = defineEmits(['update:visible', 'cancel', 'update:machineId'])
const paramsForm: any = ref(null); const paramsForm: any = ref(null);
const state = reactive({ const state = reactive({
dialogVisible: false, dialogVisible: false,
@@ -159,13 +112,13 @@ export default defineComponent({
currentId: null, currentId: null,
currentData: null, currentData: null,
query: { query: {
machineId: 0, machineId: 0 as any,
pageNum: 1, pageNum: 1,
pageSize: 8, pageSize: 8,
}, },
editDialog: { editDialog: {
visible: false, visible: false,
data: null, data: null as any,
title: '', title: '',
machineId: 9999999, machineId: 9999999,
}, },
@@ -187,6 +140,20 @@ export default defineComponent({
}, },
}); });
const {
dialogVisible,
type,
currentId,
currentData,
query,
editDialog,
total,
scriptTable,
scriptParamsDialog,
resultDialog,
terminalDialog,
} = toRefs(state)
watch(props, async (newValue) => { watch(props, async (newValue) => {
if (props.machineId && newValue.visible) { if (props.machineId && newValue.visible) {
await getScripts(); await getScripts();
@@ -264,7 +231,7 @@ export default defineComponent({
} }
state.terminalDialog.cmd = script; state.terminalDialog.cmd = script;
state.terminalDialog.visible = true; state.terminalDialog.visible = true;
state.terminalDialog.machineId = props.machineId; state.terminalDialog.machineId = props.machineId as any;
return; return;
} }
}; };
@@ -300,7 +267,7 @@ export default defineComponent({
}; };
const editScript = (data: any) => { const editScript = (data: any) => {
state.editDialog.machineId = props.machineId; state.editDialog.machineId = props.machineId as any;
state.editDialog.data = data; state.editDialog.data = data;
if (data) { if (data) {
state.editDialog.title = '查看编辑脚本'; state.editDialog.title = '查看编辑脚本';
@@ -336,30 +303,12 @@ export default defineComponent({
* 关闭取消按钮触发的事件 * 关闭取消按钮触发的事件
*/ */
const handleClose = () => { const handleClose = () => {
context.emit('update:visible', false); emit('update:visible', false);
context.emit('update:machineId', null); emit('update:machineId', null);
context.emit('cancel'); emit('cancel');
state.scriptTable = []; state.scriptTable = [];
state.scriptParamsDialog.paramsFormItem = []; state.scriptParamsDialog.paramsFormItem = [];
}; };
return {
...toRefs(state),
paramsForm,
enums,
getScripts,
handlePageChange,
runScript,
hasParamsRun,
closeTermnial,
choose,
editScript,
submitSuccess,
deleteRow,
handleClose,
};
},
});
</script> </script>
<style lang="sass"> <style lang="sass">
</style> </style>

View File

@@ -2,23 +2,21 @@
<div :style="{ height: height }" id="xterm" class="xterm" /> <div :style="{ height: height }" id="xterm" class="xterm" />
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import 'xterm/css/xterm.css'; import 'xterm/css/xterm.css';
import { Terminal } from 'xterm'; import { Terminal } from 'xterm';
import { FitAddon } from 'xterm-addon-fit'; import { FitAddon } from 'xterm-addon-fit';
import { getSession } from '@/common/utils/storage.ts'; import { getSession } from '@/common/utils/storage.ts';
import config from '@/common/config'; import config from '@/common/config';
import { useStore } from '@/store/index.ts'; 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({ const props = defineProps({
name: 'SshTerminal',
props: {
machineId: { type: Number }, machineId: { type: Number },
cmd: { type: String }, cmd: { type: String },
height: { type: String }, height: { type: String },
}, })
setup(props: any) {
const state = reactive({ const state = reactive({
machineId: 0, machineId: 0,
cmd: '', cmd: '',
@@ -27,20 +25,24 @@ export default defineComponent({
socket: null as any, socket: null as any,
}); });
const {
height,
} = toRefs(state)
const resize = 1; const resize = 1;
const data = 2; const data = 2;
const ping = 3; const ping = 3;
watch(props, (newValue) => { watch(props, (newValue: any) => {
state.machineId = newValue.machineId; state.machineId = newValue.machineId;
state.cmd = newValue.cmd; state.cmd = newValue.cmd;
state.height = newValue.height; state.height = newValue.height;
}); });
onMounted(() => { onMounted(() => {
state.machineId = props.machineId; state.machineId = props.machineId as any;
state.height = props.height; state.height = props.height as any;
state.cmd = props.cmd; state.cmd = props.cmd as any;
}); });
onBeforeUnmount(() => { onBeforeUnmount(() => {
@@ -120,8 +122,7 @@ export default defineComponent({
let pingInterval: any; let pingInterval: any;
function initSocket() { function initSocket() {
state.socket = new WebSocket( state.socket = new WebSocket(
`${config.baseWsUrl}/machines/${state.machineId}/terminal?token=${getSession('token')}&cols=${state.term.cols}&rows=${ `${config.baseWsUrl}/machines/${state.machineId}/terminal?token=${getSession('token')}&cols=${state.term.cols}&rows=${state.term.rows
state.term.rows
}` }`
); );
@@ -188,10 +189,4 @@ export default defineComponent({
state.term = null; state.term = null;
} }
} }
return {
...toRefs(state),
};
},
});
</script> </script>

View File

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

View File

@@ -5,14 +5,8 @@
<el-col :span="24"> <el-col :span="24">
<el-form class="search-form" label-position="right" :inline="true"> <el-form class="search-form" label-position="right" :inline="true">
<el-form-item label="标签"> <el-form-item label="标签">
<el-select <el-select @change="changeTag" @focus="getTags" v-model="query.tagPath" placeholder="请选择标签"
@change="changeTag" filterable style="width: 250px">
@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-option v-for="item in tags" :key="item" :label="item" :value="item"> </el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
@@ -20,14 +14,17 @@
<el-select v-model="mongoId" placeholder="请选择mongo" @change="changeMongo"> <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"> <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: 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-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="库" label-width="20px"> <el-form-item label="库" label-width="20px">
<el-select v-model="database" placeholder="请选择库" @change="changeDatabase" filterable> <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: left">{{ item.Name }}</span>
<span style="float: right; color: #8492a6; margin-left: 4px; font-size: 13px">{{ <span style="float: right; color: #8492a6; margin-left: 4px; font-size: 13px">{{
` [${formatByteSize(item.SizeOnDisk)}]` ` [${formatByteSize(item.SizeOnDisk)}]`
@@ -38,7 +35,8 @@
<el-form-item label="集合" label-width="40px"> <el-form-item label="集合" label-width="40px">
<el-select v-model="collection" placeholder="请选择集合" @change="changeCollection" filterable> <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-select>
</el-form-item> </el-form-item>
</el-form> </el-form>
@@ -47,19 +45,18 @@
</div> </div>
<el-container id="data-exec" style="border: 1px solid #eee; margin-top: 1px"> <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-tab-pane closable v-for="dt in dataTabs" :key="dt.name" :label="dt.name" :name="dt.name">
<el-row v-if="mongoId"> <el-row v-if="mongoId">
<el-link @click="findCommand(activeName)" icon="refresh" :underline="false" class="ml5"></el-link> <el-link @click="findCommand(activeName)" icon="refresh" :underline="false" class="ml5">
<el-link @click="showInsertDocDialog" class="ml5" type="primary" icon="plus" :underline="false"></el-link> </el-link>
<el-link @click="showInsertDocDialog" class="ml5" type="primary" icon="plus" :underline="false">
</el-link>
</el-row> </el-row>
<el-row class="mt5 mb5"> <el-row class="mt5 mb5">
<el-input <el-input ref="findParamInputRef" v-model="dt.findParamStr" placeholder="点击输入相应查询条件"
ref="findParamInputRef" @focus="showFindDialog(dt.name)">
v-model="dt.findParamStr"
placeholder="点击输入相应查询条件"
@focus="showFindDialog(dt.name)"
>
<template #prepend>查询参数</template> <template #prepend>查询参数</template>
</el-input> </el-input>
</el-row> </el-row>
@@ -69,17 +66,20 @@
<el-input type="textarea" v-model="item.value" :rows="12" /> <el-input type="textarea" v-model="item.value" :rows="12" />
<div style="padding: 3px; float: right" class="mr5 mongo-doc-btns"> <div style="padding: 3px; float: right" class="mr5 mongo-doc-btns">
<div> <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-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-divider direction="vertical" border-style="dashed" />
<el-popconfirm @confirm="onDeleteDoc(item.value)" title="确定删除该文档?"> <el-popconfirm @confirm="onDeleteDoc(item.value)" title="确定删除该文档?">
<template #reference> <template #reference>
<el-link :underline="false" type="danger" icon="DocumentDelete"></el-link> <el-link :underline="false" type="danger" icon="DocumentDelete">
</el-link>
</template> </template>
</el-popconfirm> </el-popconfirm>
</div> </div>
@@ -94,10 +94,12 @@
<el-dialog width="600px" title="find参数" v-model="findDialog.visible"> <el-dialog width="600px" title="find参数" v-model="findDialog.visible">
<el-form label-width="70px"> <el-form label-width="70px">
<el-form-item label="filter"> <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>
<el-form-item label="sort"> <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>
<el-form-item label="limit"> <el-form-item label="limit">
<el-input v-model.number="findDialog.findParam.limit" type="number" auto-complete="off"></el-input> <el-input v-model.number="findDialog.findParam.limit" type="number" auto-complete="off"></el-input>
@@ -114,7 +116,8 @@
</template> </template>
</el-dialog> </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" /> <json-edit currentMode="code" v-model="insertDocDialog.doc" />
<template #footer> <template #footer>
<div> <div>
@@ -124,7 +127,8 @@
</template> </template>
</el-dialog> </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" /> <json-edit v-model="jsoneditorDialog.doc" />
</el-dialog> </el-dialog>
@@ -132,9 +136,9 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { mongoApi } from './api'; 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 { ElMessage } from 'element-plus';
import { isTrue, notBlank, notNull } from '@/common/assert'; 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 { tagApi } from '../tag/api.ts';
import { useStore } from '@/store/index.ts'; import { useStore } from '@/store/index.ts';
export default defineComponent({
name: 'MongoDataOp',
components: {
JsonEdit,
},
setup() {
const store = useStore(); const store = useStore();
const findParamInputRef: any = ref(null); const findParamInputRef: any = ref(null);
const state = reactive({ const state = reactive({
loading: false,
tags: [], tags: [],
mongoList: [] as any, mongoList: [] as any,
query: { query: {
@@ -185,6 +182,22 @@ export default defineComponent({
}, },
}); });
const {
tags,
mongoList,
query,
mongoId,
database,
collection,
activeName,
databases,
collections,
dataTabs,
findDialog,
insertDocDialog,
jsoneditorDialog,
} = toRefs(state)
const searchMongo = async () => { const searchMongo = async () => {
notNull(state.query.tagPath, '请先选择标签'); notNull(state.query.tagPath, '请先选择标签');
const res = await mongoApi.mongoList.request(state.query); const res = await mongoApi.mongoList.request(state.query);
@@ -452,30 +465,6 @@ export default defineComponent({
watch(store.state.mongoDbOptInfo, async (newValue) => { watch(store.state.mongoDbOptInfo, async (newValue) => {
await setSelects(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> </script>
<style> <style>

View File

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

View File

@@ -2,8 +2,10 @@
<div> <div>
<el-card> <el-card>
<el-button type="primary" icon="plus" @click="editMongo(true)" plain>添加</el-button> <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="primary" icon="edit" :disabled="currentId == null" @click="editMongo(false)" plain>编辑
<el-button type="danger" icon="delete" :disabled="currentId == null" @click="deleteMongo" plain>删除</el-button> </el-button>
<el-button type="danger" icon="delete" :disabled="currentId == null" @click="deleteMongo" plain>删除
</el-button>
<div style="float: right"> <div style="float: right">
<el-select @focus="getTags" v-model="query.tagPath" placeholder="请选择标签" filterable clearable> <el-select @focus="getTags" v-model="query.tagPath" placeholder="请选择标签" filterable clearable>
<el-option v-for="item in tags" :key="item" :label="item" :value="item"> </el-option> <el-option v-for="item in tags" :key="item" :label="item" :value="item"> </el-option>
@@ -34,19 +36,15 @@
<el-table-column label="操作" width> <el-table-column label="操作" width>
<template #default="scope"> <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> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
<el-row style="margin-top: 20px" type="flex" justify="end"> <el-row style="margin-top: 20px" type="flex" justify="end">
<el-pagination <el-pagination style="text-align: right" @current-change="handlePageChange" :total="total"
style="text-align: right" layout="prev, pager, next, total, jumper" v-model:current-page="query.pageNum"
@current-change="handlePageChange" :page-size="query.pageSize"></el-pagination>
:total="total"
layout="prev, pager, next, total, jumper"
v-model:current-page="query.pageNum"
:page-size="query.pageSize"
></el-pagination>
</el-row> </el-row>
</el-card> </el-card>
@@ -62,16 +60,20 @@
<el-table-column min-width="150" label="操作"> <el-table-column min-width="150" label="操作">
<template #default="scope"> <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-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-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> </template>
</el-table-column> </el-table-column>
</el-table> </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 title="库状态信息" :column="3" border size="small">
<el-descriptions-item label="db" label-align="right" align="center"> <el-descriptions-item label="db" label-align="right" align="center">
{{ databaseDialog.statsDialog.data.db }} {{ databaseDialog.statsDialog.data.db }}
@@ -120,7 +122,8 @@
<el-table-column prop="name" label="名称" show-overflow-tooltip> </el-table-column> <el-table-column prop="name" label="名称" show-overflow-tooltip> </el-table-column>
<el-table-column min-width="80" label="操作"> <el-table-column min-width="80" label="操作">
<template #default="scope"> <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-divider direction="vertical" border-style="dashed" />
<el-popconfirm @confirm="onDeleteCollection(scope.row.name)" title="确定删除该集合?"> <el-popconfirm @confirm="onDeleteCollection(scope.row.name)" title="确定删除该集合?">
<template #reference> <template #reference>
@@ -131,7 +134,8 @@
</el-table-column> </el-table-column>
</el-table> </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 title="集合状态信息" :column="3" border size="small">
<el-descriptions-item label="ns" label-align="right" :span="2" align="center"> <el-descriptions-item label="ns" label-align="right" :span="2" align="center">
{{ collectionsDialog.statsDialog.data.ns }} {{ collectionsDialog.statsDialog.data.ns }}
@@ -179,18 +183,14 @@
</template> </template>
</el-dialog> </el-dialog>
<mongo-edit <mongo-edit @val-change="valChange" :title="mongoEditDialog.title" v-model:visible="mongoEditDialog.visible"
@val-change="valChange" v-model:mongo="mongoEditDialog.data"></mongo-edit>
:title="mongoEditDialog.title"
v-model:visible="mongoEditDialog.visible"
v-model:mongo="mongoEditDialog.data"
></mongo-edit>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { mongoApi } from './api'; import { mongoApi } from './api';
import { toRefs, reactive, defineComponent, onMounted } from 'vue'; import { toRefs, reactive, onMounted } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus'; import { ElMessage, ElMessageBox } from 'element-plus';
import { tagApi } from '../tag/api.ts'; import { tagApi } from '../tag/api.ts';
import MongoEdit from './MongoEdit.vue'; import MongoEdit from './MongoEdit.vue';
@@ -199,19 +199,12 @@ import {store} from '@/store';
import router from '@/router'; import router from '@/router';
import { dateFormat } from '@/common/utils/date'; import { dateFormat } from '@/common/utils/date';
export default defineComponent({
name: 'MongoList',
components: {
MongoEdit,
},
setup() {
const state = reactive({ const state = reactive({
tags: [], tags: [],
dbOps: { dbOps: {
dbId: 0, dbId: 0,
db: '', db: '',
}, },
projects: [],
list: [], list: [],
total: 0, total: 0,
currentId: null, currentId: null,
@@ -255,6 +248,18 @@ export default defineComponent({
}, },
}); });
const {
tags,
list,
total,
currentId,
query,
mongoEditDialog,
databaseDialog,
collectionsDialog,
createCollectionDialog,
} = toRefs(state)
onMounted(async () => { onMounted(async () => {
search(); search();
}); });
@@ -419,29 +424,8 @@ export default defineComponent({
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> </script>
<style> <style>
</style> </style>

View File

@@ -6,37 +6,23 @@
<el-col :span="24"> <el-col :span="24">
<el-form class="search-form" label-position="right" :inline="true"> <el-form class="search-form" label-position="right" :inline="true">
<el-form-item label="标签"> <el-form-item label="标签">
<el-select <el-select @change="changeTag" @focus="getTags" v-model="query.tagPath"
@change="changeTag" placeholder="请选择标签" filterable style="width: 250px">
@focus="getTags" <el-option v-for="item in tags" :key="item" :label="item" :value="item">
v-model="query.tagPath" </el-option>
placeholder="请选择标签"
filterable
style="width: 250px"
>
<el-option v-for="item in tags" :key="item" :label="item" :value="item"> </el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="redis" label-width="40px"> <el-form-item label="redis" label-width="40px">
<el-select <el-select v-model="scanParam.id" placeholder="请选择redis" @change="changeRedis"
v-model="scanParam.id" @clear="clearRedis" clearable style="width: 250px">
placeholder="请选择redis" <el-option v-for="item in redisList" :key="item.id"
@change="changeRedis" :label="`${item.name ? item.name : ''} [${item.host}]`" :value="item.id">
@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-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="库" label-width="20px"> <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-option v-for="db in dbList" :key="db" :label="db" :value="db"> </el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
@@ -45,16 +31,12 @@
<el-col class="mt10"> <el-col class="mt10">
<el-form class="search-form" label-position="right" :inline="true" label-width="60px"> <el-form class="search-form" label-position="right" :inline="true" label-width="60px">
<el-form-item label="key" label-width="40px"> <el-form-item label="key" label-width="40px">
<el-input <el-input placeholder="match 支持*模糊key" style="width: 250px" v-model="scanParam.match"
placeholder="match 支持*模糊key" @clear="clear()" clearable></el-input>
style="width: 250px"
v-model="scanParam.match"
@clear="clear()"
clearable
></el-input>
</el-form-item> </el-form-item>
<el-form-item label="count" label-width="40px"> <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-form-item> <el-form-item>
<el-button @click="searchKey()" type="success" icon="search" plain></el-button> <el-button @click="searchKey()" type="success" icon="search" plain></el-button>
@@ -63,9 +45,12 @@
<template #reference> <template #reference>
<el-button type="primary" icon="plus" plain></el-button> <el-button type="primary" icon="plus" plain></el-button>
</template> </template>
<el-tag @click="onAddData('string')" :color="getTypeColor('string')" style="cursor: pointer">string</el-tag> <el-tag @click="onAddData('string')" :color="getTypeColor('string')"
<el-tag @click="onAddData('hash')" :color="getTypeColor('hash')" class="ml5" style="cursor: pointer">hash</el-tag> style="cursor: pointer">string</el-tag>
<el-tag @click="onAddData('set')" :color="getTypeColor('set')" class="ml5" style="cursor: pointer">set</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-tag @click="onAddData('list')" :color="getTypeColor('list')" class="ml5" style="cursor: pointer">list</el-tag> -->
</el-popover> </el-popover>
</el-form-item> </el-form-item>
@@ -91,8 +76,10 @@
</el-table-column> </el-table-column>
<el-table-column label="操作"> <el-table-column label="操作">
<template #default="scope"> <template #default="scope">
<el-button @click="getValue(scope.row)" type="success" icon="search" plain size="small">查看</el-button> <el-button @click="getValue(scope.row)" type="success" icon="search" plain size="small">查看
<el-button @click="del(scope.row.key)" type="danger" icon="delete" plain size="small">删除</el-button> </el-button>
<el-button @click="del(scope.row.key)" type="danger" icon="delete" plain size="small">删除
</el-button>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
@@ -100,55 +87,27 @@
<div style="text-align: center; margin-top: 10px"></div> <div style="text-align: center; margin-top: 10px"></div>
<hash-value <hash-value v-model:visible="hashValueDialog.visible" :operationType="dataEdit.operationType"
v-model:visible="hashValueDialog.visible" :title="dataEdit.title" :keyInfo="dataEdit.keyInfo" :redisId="scanParam.id" :db="scanParam.db"
:operationType="dataEdit.operationType" @cancel="onCancelDataEdit" @valChange="searchKey" />
:title="dataEdit.title"
:keyInfo="dataEdit.keyInfo"
:redisId="scanParam.id"
:db="scanParam.db"
@cancel="onCancelDataEdit"
@valChange="searchKey"
/>
<string-value <string-value v-model:visible="stringValueDialog.visible" :operationType="dataEdit.operationType"
v-model:visible="stringValueDialog.visible" :title="dataEdit.title" :keyInfo="dataEdit.keyInfo" :redisId="scanParam.id" :db="scanParam.db"
:operationType="dataEdit.operationType" @cancel="onCancelDataEdit" @valChange="searchKey" />
:title="dataEdit.title"
:keyInfo="dataEdit.keyInfo"
:redisId="scanParam.id"
:db="scanParam.db"
@cancel="onCancelDataEdit"
@valChange="searchKey"
/>
<set-value <set-value v-model:visible="setValueDialog.visible" :title="dataEdit.title" :keyInfo="dataEdit.keyInfo"
v-model:visible="setValueDialog.visible" :redisId="scanParam.id" :db="scanParam.db" :operationType="dataEdit.operationType" @valChange="searchKey"
:title="dataEdit.title" @cancel="onCancelDataEdit" />
:keyInfo="dataEdit.keyInfo"
:redisId="scanParam.id"
:db="scanParam.db"
:operationType="dataEdit.operationType"
@valChange="searchKey"
@cancel="onCancelDataEdit"
/>
<list-value <list-value v-model:visible="listValueDialog.visible" :title="dataEdit.title" :keyInfo="dataEdit.keyInfo"
v-model:visible="listValueDialog.visible" :redisId="scanParam.id" :db="scanParam.db" :operationType="dataEdit.operationType" @valChange="searchKey"
:title="dataEdit.title" @cancel="onCancelDataEdit" />
:keyInfo="dataEdit.keyInfo"
:redisId="scanParam.id"
:db="scanParam.db"
:operationType="dataEdit.operationType"
@valChange="searchKey"
@cancel="onCancelDataEdit"
/>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { redisApi } from './api'; import { redisApi } from './api';
import { toRefs, reactive, defineComponent, watch } from 'vue'; import { toRefs, reactive, watch } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus'; import { ElMessage, ElMessageBox } from 'element-plus';
import HashValue from './HashValue.vue'; import HashValue from './HashValue.vue';
import StringValue from './StringValue.vue'; import StringValue from './StringValue.vue';
@@ -159,18 +118,8 @@ import { isTrue, notBlank, notNull } from '@/common/assert';
import { useStore } from '@/store/index.ts'; import { useStore } from '@/store/index.ts';
import { tagApi } from '../tag/api.ts'; import { tagApi } from '../tag/api.ts';
export default defineComponent({
name: 'DataOperation',
components: {
StringValue,
HashValue,
SetValue,
ListValue,
},
setup() {
let store = useStore(); let store = useStore();
const state = reactive({ const state = reactive({
projectId: null,
loading: false, loading: false,
tags: [], tags: [],
redisList: [] as any, redisList: [] as any,
@@ -179,7 +128,7 @@ export default defineComponent({
tagPath: null, tagPath: null,
}, },
scanParam: { scanParam: {
id: null, id: null as any,
db: '', db: '',
match: null, match: null,
count: 10, count: 10,
@@ -211,6 +160,22 @@ export default defineComponent({
dbsize: 0, dbsize: 0,
}); });
const {
loading,
tags,
redisList,
dbList,
query,
scanParam,
dataEdit,
hashValueDialog,
stringValueDialog,
setValueDialog,
listValueDialog,
keys,
dbsize,
} = toRefs(state)
const searchRedis = async () => { const searchRedis = async () => {
notBlank(state.query.tagPath, '请先选择标签'); notBlank(state.query.tagPath, '请先选择标签');
const res = await redisApi.redisList.request(state.query); const res = await redisApi.redisList.request(state.query);
@@ -436,26 +401,8 @@ export default defineComponent({
await setSelects(newValue); await setSelects(newValue);
}); });
return {
...toRefs(state),
getTags,
changeTag,
changeRedis,
changeDb,
clearRedis,
searchKey,
scan,
clear,
getValue,
del,
ttlConveter,
getTypeColor,
onAddData,
onCancelDataEdit,
};
},
});
</script> </script>
<style> <style>
</style> </style>

View File

@@ -14,14 +14,18 @@
<el-row class="mt10"> <el-row class="mt10">
<el-form label-position="right" :inline="true"> <el-form label-position="right" :inline="true">
<el-form-item label="field" label-width="40px" v-if="operationType == 2"> <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>
<el-form-item label="count" v-if="operationType == 2"> <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-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="reHscan()" type="success" icon="search" plain
<el-button v-if="operationType == 2" @click="hscan()" icon="bottom" plain size="small">scan</el-button> 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-button @click="onAddHashValue" icon="plus" size="small" plain>添加</el-button>
</el-form-item> </el-form-item>
<div v-if="operationType == 2" class="mt10" style="float: right"> <div v-if="operationType == 2" class="mt10" style="float: right">
@@ -37,13 +41,16 @@
</el-table-column> </el-table-column>
<el-table-column prop="value" label="value" min-width="200"> <el-table-column prop="value" label="value" min-width="200">
<template #default="scope"> <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> </template>
</el-table-column> </el-table-column>
<el-table-column label="操作" width="120"> <el-table-column label="操作" width="120">
<template #default="scope"> <template #default="scope">
<el-button v-if="operationType == 2" type="success" @click="hset(scope.row)" icon="check" size="small" plain></el-button> <el-button v-if="operationType == 2" type="success" @click="hset(scope.row)" icon="check"
<el-button type="danger" @click="hdel(scope.row.field, scope.$index)" icon="delete" size="small" plain></el-button> size="small" plain></el-button>
<el-button type="danger" @click="hdel(scope.row.field, scope.$index)" icon="delete" size="small"
plain></el-button>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
@@ -56,16 +63,13 @@
</template> </template>
</el-dialog> </el-dialog>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { defineComponent, reactive, watch, toRefs } from 'vue'; import { reactive, watch, toRefs } from 'vue';
import { redisApi } from './api'; import { redisApi } from './api';
import { ElMessage, ElMessageBox } from 'element-plus'; import { ElMessage, ElMessageBox } from 'element-plus';
import { isTrue, notEmpty } from '@/common/assert'; import { isTrue, notEmpty } from '@/common/assert';
export default defineComponent({ const props = defineProps({
name: 'HashValue',
components: {},
props: {
visible: { visible: {
type: Boolean, type: Boolean,
}, },
@@ -91,9 +95,10 @@ export default defineComponent({
hashValue: { hashValue: {
type: [Array, Object], type: [Array, Object],
}, },
}, })
emits: ['valChange', 'cancel', 'update:visible'],
setup(props: any, { emit }) { const emit = defineEmits(['update:visible', 'cancel', 'valChange'])
const state = reactive({ const state = reactive({
dialogVisible: false, dialogVisible: false,
operationType: 1, operationType: 1,
@@ -121,6 +126,15 @@ export default defineComponent({
], ],
}); });
const {
dialogVisible,
operationType,
key,
scanParam,
keySize,
hashValues,
} = toRefs(state)
const cancel = () => { const cancel = () => {
emit('update:visible', false); emit('update:visible', false);
emit('cancel'); emit('cancel');
@@ -130,7 +144,7 @@ export default defineComponent({
}, 500); }, 500);
}; };
watch(props, async (newValue) => { watch(props, async (newValue: any) => {
const visible = newValue.visible; const visible = newValue.visible;
state.redisId = newValue.redisId; state.redisId = newValue.redisId;
state.db = newValue.db; state.db = newValue.db;
@@ -138,7 +152,7 @@ export default defineComponent({
state.operationType = newValue.operationType; state.operationType = newValue.operationType;
if (visible && state.operationType == 2) { if (visible && state.operationType == 2) {
state.scanParam.id = props.redisId; state.scanParam.id = props.redisId as any;
state.scanParam.key = state.key.key; state.scanParam.key = state.key.key;
await reHscan(); await reHscan();
} }
@@ -233,19 +247,6 @@ export default defineComponent({
cancel(); cancel();
emit('valChange'); emit('valChange');
}; };
return {
...toRefs(state),
reHscan,
hscan,
cancel,
hdel,
hset,
onAddHashValue,
saveValue,
};
},
});
</script> </script>
<style lang="scss"> <style lang="scss">
#string-value-text { #string-value-text {

View File

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

View File

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

View File

@@ -1,6 +1,7 @@
<template> <template>
<div> <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 :model="form" ref="redisForm" :rules="rules" label-width="85px">
<el-form-item prop="tagId" label="标签:" required> <el-form-item prop="tagId" label="标签:" required>
<tag-select v-model:tag-id="form.tagId" v-model:tag-path="form.tagPath" style="width: 100%" /> <tag-select v-model:tag-id="form.tagId" v-model:tag-path="form.tagPath" style="width: 100%" />
@@ -16,32 +17,25 @@
</el-select> </el-select>
</el-form-item> </el-form-item>
<el-form-item prop="host" label="host:" required> <el-form-item prop="host" label="host:" required>
<el-input <el-input v-model.trim="form.host"
v-model.trim="form.host"
placeholder="请输入host:portsentinel模式为: mastername=sentinelhost:port若集群或哨兵需设多个节点可使用','分割" placeholder="请输入host:portsentinel模式为: mastername=sentinelhost:port若集群或哨兵需设多个节点可使用','分割"
auto-complete="off" auto-complete="off" type="textarea"></el-input>
type="textarea"
></el-input>
</el-form-item> </el-form-item>
<el-form-item prop="password" label="密码:"> <el-form-item prop="password" label="密码:">
<el-input <el-input type="password" show-password v-model.trim="form.password" placeholder="请输入密码, 修改操作可不填"
type="password" autocomplete="new-password"><template v-if="form.id && form.id != 0" #suffix>
show-password <el-popover @hide="pwd = ''" placement="right" title="原密码" :width="200" trigger="click"
v-model.trim="form.password" :content="pwd">
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> <template #reference>
<el-link @click="getPwd" :underline="false" type="primary" class="mr5">原密码</el-link> <el-link @click="getPwd" :underline="false" type="primary" class="mr5">原密码</el-link>
</template> </template>
</el-popover> </el-popover>
</template></el-input </template></el-input>
>
</el-form-item> </el-form-item>
<el-form-item prop="db" label="库号:" required> <el-form-item prop="db" label="库号:" required>
<el-select @change="changeDb" v-model="dbList" multiple placeholder="请选择可操作库号" style="width: 100%"> <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-select>
</el-form-item> </el-form-item>
<el-form-item prop="remark" label="备注:"> <el-form-item prop="remark" label="备注:">
@@ -49,17 +43,14 @@
</el-form-item> </el-form-item>
<el-form-item prop="enableSshTunnel" label="SSH隧道:"> <el-form-item prop="enableSshTunnel" label="SSH隧道:">
<el-col :span="3"> <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>
<el-col :span="2" v-if="form.enableSshTunnel == 1"> 机器: </el-col> <el-col :span="2" v-if="form.enableSshTunnel == 1"> 机器: </el-col>
<el-col :span="19" v-if="form.enableSshTunnel == 1"> <el-col :span="19" v-if="form.enableSshTunnel == 1">
<el-select style="width: 100%" v-model="form.sshTunnelMachineId" placeholder="请选择SSH隧道机器"> <el-select style="width: 100%" v-model="form.sshTunnelMachineId" placeholder="请选择SSH隧道机器">
<el-option <el-option v-for="item in sshTunnelMachineList as any" :key="item.id"
v-for="item in sshTunnelMachineList" :label="`${item.ip}:${item.port} [${item.name}]`" :value="item.id">
:key="item.id"
:label="`${item.ip}:${item.port} [${item.name}]`"
:value="item.id"
>
</el-option> </el-option>
</el-select> </el-select>
</el-col> </el-col>
@@ -76,62 +67,29 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { toRefs, reactive, watch, defineComponent, ref } from 'vue'; import { toRefs, reactive, watch, ref } from 'vue';
import { redisApi } from './api'; import { redisApi } from './api';
import { projectApi } from '../project/api.ts';
import { machineApi } from '../machine/api.ts'; import { machineApi } from '../machine/api.ts';
import { ElMessage } from 'element-plus'; import { ElMessage } from 'element-plus';
import { RsaEncrypt } from '@/common/rsa'; import { RsaEncrypt } from '@/common/rsa';
import TagSelect from '../component/TagSelect.vue'; import TagSelect from '../component/TagSelect.vue';
export default defineComponent({ const props = defineProps({
name: 'RedisEdit',
components: {
TagSelect,
},
props: {
visible: { visible: {
type: Boolean, type: Boolean,
}, },
projects: {
type: Array,
},
redis: { redis: {
type: [Boolean, Object], type: [Boolean, Object],
}, },
title: { title: {
type: String, type: String,
}, },
}, })
setup(props: any, { emit }) {
const redisForm: any = ref(null); const emit = defineEmits(['update:visible', 'val-change', 'cancel'])
const state = reactive({
dialogVisible: false, const rules = {
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: {
projectId: [ projectId: [
{ {
required: true, required: true,
@@ -167,20 +125,53 @@ export default defineComponent({
trigger: ['change', 'blur'], trigger: ['change', 'blur'],
}, },
], ],
}
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,
}); });
watch(props, async (newValue) => { const {
dialogVisible,
sshTunnelMachineList,
form,
dbList,
pwd,
btnLoading,
} = toRefs(state)
watch(props, async (newValue: any) => {
state.dialogVisible = newValue.visible; state.dialogVisible = newValue.visible;
if (!state.dialogVisible) { if (!state.dialogVisible) {
return; return;
} }
state.projects = newValue.projects;
if (newValue.redis) { if (newValue.redis) {
state.form = { ...newValue.redis }; state.form = { ...newValue.redis };
convertDb(state.form.db); convertDb(state.form.db);
} else { } else {
state.envs = [];
state.form = { db: '0', enableSshTunnel: -1 } as any; state.form = { db: '0', enableSshTunnel: -1 } as any;
state.dbList = []; state.dbList = [];
} }
@@ -239,18 +230,7 @@ export default defineComponent({
emit('update:visible', false); emit('update:visible', false);
emit('cancel'); emit('cancel');
}; };
return {
...toRefs(state),
redisForm,
changeDb,
getSshTunnelMachines,
getPwd,
btnOk,
cancel,
};
},
});
</script> </script>
<style lang="scss"> <style lang="scss">
</style> </style>

View File

@@ -2,8 +2,10 @@
<div> <div>
<el-card> <el-card>
<el-button type="primary" icon="plus" @click="editRedis(true)" plain>添加</el-button> <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="primary" icon="edit" :disabled="currentId == null" @click="editRedis(false)" plain>编辑
<el-button type="danger" icon="delete" :disabled="currentId == null" @click="deleteRedis" plain>删除</el-button> </el-button>
<el-button type="danger" icon="delete" :disabled="currentId == null" @click="deleteRedis" plain>删除
</el-button>
<div style="float: right"> <div style="float: right">
<el-select @focus="getTags" v-model="query.tagPath" placeholder="请选择标签" filterable clearable> <el-select @focus="getTags" v-model="query.tagPath" placeholder="请选择标签" filterable clearable>
<el-option v-for="item in tags" :key="item" :label="item" :value="item"> </el-option> <el-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 prop="creator" label="创建人" min-width="100"></el-table-column>
<el-table-column label="更多" min-width="155" fixed="right"> <el-table-column label="更多" min-width="155" fixed="right">
<template #default="scope"> <template #default="scope">
<el-link <el-link v-if="scope.row.mode === 'standalone' || scope.row.mode === 'sentinel'" type="primary"
v-if="scope.row.mode === 'standalone' || scope.row.mode === 'sentinel'" @click="showInfoDialog(scope.row)" :underline="false">单机信息</el-link>
type="primary" <el-link @click="onShowClusterInfo(scope.row)" v-if="scope.row.mode === 'cluster'"
@click="info(scope.row)" type="success" :underline="false">集群信息</el-link>
: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-divider direction="vertical" border-style="dashed" />
<el-link @click="openDataOpt(scope.row)" type="success" :underline="false">数据操作</el-link> <el-link @click="openDataOpt(scope.row)" type="success" :underline="false">数据操作</el-link>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
<el-row style="margin-top: 20px" type="flex" justify="end"> <el-row style="margin-top: 20px" type="flex" justify="end">
<el-pagination <el-pagination style="text-align: right" @current-change="handlePageChange" :total="total"
style="text-align: right" layout="prev, pager, next, total, jumper" v-model:current-page="query.pageNum"
@current-change="handlePageChange" :page-size="query.pageSize"></el-pagination>
:total="total"
layout="prev, pager, next, total, jumper"
v-model:current-page="query.pageNum"
:page-size="query.pageSize"
></el-pagination>
</el-row> </el-row>
</el-card> </el-card>
<info v-model:visible="infoDialog.visible" :title="infoDialog.title" :info="infoDialog.info"></info> <info v-model:visible="infoDialog.visible" :title="infoDialog.title" :info="infoDialog.info"></info>
<el-dialog width="1000px" title="集群信息" v-model="clusterInfoDialog.visible"> <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-divider content-position="left">节点信息</el-divider>
<el-table :data="clusterInfoDialog.nodes" stripe size="small" border> <el-table :data="clusterInfoDialog.nodes" stripe size="small" border>
@@ -66,44 +61,36 @@
<template #header> <template #header>
nodeId nodeId
<el-tooltip class="box-item" effect="dark" content="节点id" placement="top"> <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> </el-tooltip>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="ip" label="ip" min-width="180"> <el-table-column prop="ip" label="ip" min-width="180">
<template #header> <template #header>
ip ip
<el-tooltip <el-tooltip class="box-item" effect="dark"
class="box-item" content="ip:port1@port2port1指redis服务器与客户端通信的端口port2则是集群内部节点间通信的端口" placement="top">
effect="dark" <el-icon>
content="ip:port1@port2port1指redis服务器与客户端通信的端口port2则是集群内部节点间通信的端口" <question-filled />
placement="top" </el-icon>
>
<el-icon><question-filled /></el-icon>
</el-tooltip> </el-tooltip>
</template> </template>
<template #default="scope"> <template #default="scope">
<el-tag <el-tag @click="showInfoDialog({ id: clusterInfoDialog.redisId, ip: scope.row.ip })" effect="plain"
@click="info({ id: clusterInfoDialog.redisId, ip: scope.row.ip })" type="success" size="small" style="cursor: pointer">{{ scope.row.ip }}</el-tag>
effect="plain"
type="success"
size="small"
style="cursor: pointer"
>{{ scope.row.ip }}</el-tag
>
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="flags" label="flags" min-width="110"></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"> <el-table-column prop="masterSlaveRelation" label="masterSlaveRelation" min-width="300">
<template #header> <template #header>
masterSlaveRelation masterSlaveRelation
<el-tooltip <el-tooltip class="box-item" effect="dark"
class="box-item" content="如果节点是slave并且已知master节点则为master节点ID否则为符号'-'" placement="top">
effect="dark" <el-icon>
content="如果节点是slave并且已知master节点则为master节点ID否则为符号'-'" <question-filled />
placement="top" </el-icon>
>
<el-icon><question-filled /></el-icon>
</el-tooltip> </el-tooltip>
</template> </template>
</el-table-column> </el-table-column>
@@ -120,13 +107,12 @@
<el-table-column prop="configEpoch" label="configEpoch" min-width="130"> <el-table-column prop="configEpoch" label="configEpoch" min-width="130">
<template #header> <template #header>
configEpoch configEpoch
<el-tooltip <el-tooltip class="box-item" effect="dark"
class="box-item"
effect="dark"
content="节点的epoch值如果该节点是从节点则为其主节点的epoch值。每当节点发生失败切换时都会创建一个新的独特的递增的epoch。" content="节点的epoch值如果该节点是从节点则为其主节点的epoch值。每当节点发生失败切换时都会创建一个新的独特的递增的epoch。"
placement="top" placement="top">
> <el-icon>
<el-icon><question-filled /></el-icon> <question-filled />
</el-icon>
</el-tooltip> </el-tooltip>
</template> </template>
</el-table-column> </el-table-column>
@@ -135,20 +121,15 @@
</el-table> </el-table>
</el-dialog> </el-dialog>
<redis-edit <redis-edit @val-change="valChange" :tags="tags" :title="redisEditDialog.title"
@val-change="valChange" v-model:visible="redisEditDialog.visible" v-model:redis="redisEditDialog.data"></redis-edit>
:tags="tags"
:title="redisEditDialog.title"
v-model:visible="redisEditDialog.visible"
v-model:redis="redisEditDialog.data"
></redis-edit>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import Info from './Info.vue'; import Info from './Info.vue';
import { redisApi } from './api'; import { redisApi } from './api';
import { toRefs, reactive, defineComponent, onMounted } from 'vue'; import { toRefs, reactive, onMounted } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus'; import { ElMessage, ElMessageBox } from 'element-plus';
import { tagApi } from '../tag/api.ts'; import { tagApi } from '../tag/api.ts';
import RedisEdit from './RedisEdit.vue'; import RedisEdit from './RedisEdit.vue';
@@ -156,13 +137,6 @@ import { dateFormat } from '@/common/utils/date';
import { store } from '@/store'; import { store } from '@/store';
import router from '@/router'; import router from '@/router';
export default defineComponent({
name: 'RedisList',
components: {
Info,
RedisEdit,
},
setup() {
const state = reactive({ const state = reactive({
tags: [], tags: [],
redisTable: [], redisTable: [],
@@ -175,21 +149,12 @@ export default defineComponent({
pageSize: 10, pageSize: 10,
clusterId: null, clusterId: null,
}, },
redisInfo: {
url: '',
},
clusterInfoDialog: { clusterInfoDialog: {
visible: false, visible: false,
redisId: 0, redisId: 0,
info: '', info: '',
nodes: [], nodes: [],
}, },
clusters: [
{
id: 0,
name: '单机',
},
],
infoDialog: { infoDialog: {
title: '', title: '',
visible: false, visible: false,
@@ -203,11 +168,22 @@ export default defineComponent({
}, },
redisEditDialog: { redisEditDialog: {
visible: false, visible: false,
data: null, data: null as any,
title: '新增redis', title: '新增redis',
}, },
}); });
const {
tags,
redisTable,
total,
currentId,
query,
clusterInfoDialog,
infoDialog,
redisEditDialog,
} = toRefs(state)
onMounted(async () => { onMounted(async () => {
search(); search();
}); });
@@ -240,7 +216,7 @@ export default defineComponent({
} catch (err) { } } catch (err) { }
}; };
const info = async (redis: any) => { const showInfoDialog = async (redis: any) => {
var host = redis.host; var host = redis.host;
if (redis.ip) { if (redis.ip) {
host = redis.ip.split('@')[0]; host = redis.ip.split('@')[0];
@@ -300,24 +276,8 @@ export default defineComponent({
} }
router.push({ name: 'DataOperation' }); router.push({ name: 'DataOperation' });
} }
return {
...toRefs(state),
dateFormat,
getTags,
search,
handlePageChange,
choose,
info,
onShowClusterInfo,
deleteRedis,
editRedis,
valChange,
openDataOpt,
};
},
});
</script> </script>
<style> <style>
</style> </style>

View File

@@ -15,12 +15,14 @@
<el-table :data="value" stripe style="width: 100%"> <el-table :data="value" stripe style="width: 100%">
<el-table-column prop="value" label="value" min-width="200"> <el-table-column prop="value" label="value" min-width="200">
<template #default="scope"> <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> </template>
</el-table-column> </el-table-column>
<el-table-column label="操作" width="90"> <el-table-column label="操作" width="90">
<template #default="scope"> <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> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
@@ -33,16 +35,13 @@
</template> </template>
</el-dialog> </el-dialog>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { defineComponent, reactive, watch, toRefs } from 'vue'; import { reactive, watch, toRefs } from 'vue';
import { redisApi } from './api'; import { redisApi } from './api';
import { ElMessage } from 'element-plus'; import { ElMessage } from 'element-plus';
import { isTrue, notEmpty } from '@/common/assert'; import { isTrue, notEmpty } from '@/common/assert';
export default defineComponent({ const props = defineProps({
name: 'SetValue',
components: {},
props: {
visible: { visible: {
type: Boolean, type: Boolean,
}, },
@@ -67,9 +66,10 @@ export default defineComponent({
setValue: { setValue: {
type: [Array, Object], type: [Array, Object],
}, },
}, })
emits: ['valChange', 'cancel', 'update:visible'],
setup(props: any, { emit }) { const emit = defineEmits(['update:visible', 'cancel', 'valChange'])
const state = reactive({ const state = reactive({
dialogVisible: false, dialogVisible: false,
operationType: 1, operationType: 1,
@@ -83,6 +83,13 @@ export default defineComponent({
value: [{ value: '' }], value: [{ value: '' }],
}); });
const {
dialogVisible,
operationType,
key,
value,
} = toRefs(state)
const cancel = () => { const cancel = () => {
emit('update:visible', false); emit('update:visible', false);
emit('cancel'); emit('cancel');
@@ -96,7 +103,7 @@ export default defineComponent({
}, 500); }, 500);
}; };
watch(props, async (newValue) => { watch(props, async (newValue: any) => {
state.dialogVisible = newValue.visible; state.dialogVisible = newValue.visible;
state.key = newValue.key; state.key = newValue.key;
state.redisId = newValue.redisId; state.redisId = newValue.redisId;
@@ -137,15 +144,6 @@ export default defineComponent({
const onAddSetValue = () => { const onAddSetValue = () => {
state.value.unshift({ value: '' }); state.value.unshift({ value: '' });
}; };
return {
...toRefs(state),
saveValue,
cancel,
onAddSetValue,
};
},
});
</script> </script>
<style lang="scss"> <style lang="scss">
#string-value-text { #string-value-text {

View File

@@ -12,7 +12,8 @@
</el-form-item> </el-form-item>
<div id="string-value-text" style="width: 100%"> <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-select class="text-type-select" @change="onChangeTextType" v-model="string.type">
<el-option key="text" label="text" value="text"> </el-option> <el-option key="text" label="text" value="text"> </el-option>
<el-option key="json" label="json" value="json"> </el-option> <el-option key="json" label="json" value="json"> </el-option>
@@ -27,17 +28,14 @@
</template> </template>
</el-dialog> </el-dialog>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { defineComponent, reactive, watch, toRefs } from 'vue'; import { reactive, watch, toRefs } from 'vue';
import { redisApi } from './api'; import { redisApi } from './api';
import { ElMessage } from 'element-plus'; import { ElMessage } from 'element-plus';
import { notEmpty } from '@/common/assert'; import { notEmpty } from '@/common/assert';
import { formatJsonString } from '@/common/utils/format'; import { formatJsonString } from '@/common/utils/format';
export default defineComponent({ const props = defineProps({
name: 'StringValue',
components: {},
props: {
visible: { visible: {
type: Boolean, type: Boolean,
}, },
@@ -59,9 +57,10 @@ export default defineComponent({
operationType: { operationType: {
type: [Number], type: [Number],
}, },
}, })
emits: ['valChange', 'cancel', 'update:visible'],
setup(props: any, { emit }) { const emit = defineEmits(['update:visible', 'cancel', 'valChange'])
const state = reactive({ const state = reactive({
dialogVisible: false, dialogVisible: false,
operationType: 1, operationType: 1,
@@ -78,6 +77,13 @@ export default defineComponent({
}, },
}); });
const {
dialogVisible,
operationType,
key,
string,
} = toRefs(state)
const cancel = () => { const cancel = () => {
emit('update:visible', false); emit('update:visible', false);
emit('cancel'); emit('cancel');
@@ -102,18 +108,18 @@ export default defineComponent({
watch( watch(
() => props.redisId, () => props.redisId,
(val) => { (val) => {
state.redisId = val; state.redisId = val as any;
} }
); );
watch( watch(
() => props.db, () => props.db,
(val) => { (val) => {
state.db = val; state.db = val as any;
} }
); );
watch(props, async (newValue) => { watch(props, async (newValue: any) => {
state.dialogVisible = newValue.visible; state.dialogVisible = newValue.visible;
state.key = newValue.key; state.key = newValue.key;
state.redisId = newValue.redisId; state.redisId = newValue.redisId;
@@ -156,15 +162,6 @@ export default defineComponent({
state.string.value = formatJsonString(state.string.value, true); state.string.value = formatJsonString(state.string.value, true);
} }
}; };
return {
...toRefs(state),
saveValue,
cancel,
onChangeTextType,
};
},
});
</script> </script>
<style lang="scss"> <style lang="scss">
#string-value-text { #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> <template>
<div class="menu"> <div class="menu">
<div class="toolbar"> <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> <el-button v-auth="'tag:save'" type="primary" icon="plus" @click="showSaveTabDialog(null)">添加</el-button>
<div style="float: right"> <div style="float: right">
<el-tooltip effect="dark" placement="top"> <el-tooltip effect="dark" placement="top">
<template #content> <template #content>
1. 用于将资产进行归类 1. 用于将资产进行归类
<br />2. 可在团队管理中进行分配用于资源隔离 <br />2. 可在团队管理中进行分配用于资源隔离 <br />3. 拥有父标签的团队成员可访问操作其自身或子标签关联的资源
<br />3. 父标签可访问及操作所有子标签关联的资源
</template> </template>
<span>标签作用<el-icon><question-filled /></el-icon></span> <span>标签作用<el-icon>
<question-filled />
</el-icon>
</span>
</el-tooltip> </el-tooltip>
</div> </div>
</div> </div>
<el-tree <el-tree ref="tagTreeRef" class="none-select" :indent="38" node-key="id" :props="props" :data="data"
class="none-select" @node-expand="handleNodeExpand" @node-collapse="handleNodeCollapse"
:indent="38" :default-expanded-keys="defaultExpandedKeys" :expand-on-click-node="false" :filter-node-method="filterNode">
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 }"> <template #default="{ data }">
<span class="custom-tree-node"> <span class="custom-tree-node">
<span style="font-size: 13px"> <span style="font-size: 13px">
@@ -34,18 +29,14 @@
<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>
<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 <el-link v-auth="'tag:save'" @click.prevent="showSaveTabDialog(data)" icon="circle-plus"
v-auth="'tag:save'" :underline="false" type="success" class="ml5" />
@click.prevent="showSaveTabDialog(data)"
icon="circle-plus"
:underline="false"
type="success"
class="ml5"
/>
<!-- <el-link <!-- <el-link
v-auth="'resource:changeStatus'" v-auth="'resource:changeStatus'"
@@ -68,24 +59,18 @@
class="ml5" class="ml5"
/> --> /> -->
<el-link <el-link v-auth="'tag:del'" @click.prevent="deleteTag(data)" v-if="data.children == null"
v-auth="'tag:del'" type="danger" icon="delete" :underline="false" plain class="ml5" />
@click.prevent="deleteTag(data)"
v-if="data.children == null"
type="danger"
icon="delete"
:underline="false"
plain
class="ml5"
/>
</span> </span>
</template> </template>
</el-tree> </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 ref="tagForm" :rules="rules" :model="saveTabDialog.form" label-width="70px">
<el-form-item prop="code" label="标识:" required> <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>
<el-form-item prop="name" label="名称:" required> <el-form-item prop="name" label="名称:" required>
<el-input v-model="saveTabDialog.form.name" auto-complete="off"></el-input> <el-input v-model="saveTabDialog.form.name" auto-complete="off"></el-input>
@@ -118,38 +103,53 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { toRefs, ref, reactive, onMounted, defineComponent } from 'vue'; import { toRefs, ref, watch, reactive, onMounted } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus'; import { ElMessage, ElMessageBox } from 'element-plus';
import { tagApi } from './api'; import { tagApi } from './api';
import { dateFormat } from '@/common/utils/date'; import { dateFormat } from '@/common/utils/date';
export default defineComponent({ interface Tree {
name: 'TagTreeList', id: number;
components: {}, codePath: string;
setup() { name: string;
children?: Tree[];
}
const tagForm: any = ref(null); const tagForm: any = ref(null);
const tagTreeRef: any = ref(null);
const filterTag = ref('');
const state = reactive({ const state = reactive({
data: [],
saveTabDialog: { saveTabDialog: {
title: '新增标签', title: '新增标签',
visible: false, visible: false,
form: { id: 0, pid: 0, code: '', name: '', remark: '' }, form: { id: 0, pid: 0, code: '', name: '', remark: '' },
}, },
//资源信息弹出框对象
infoDialog: { infoDialog: {
title: '', title: '',
visible: false, visible: false,
// 资源类型选择是否选 // 资源类型选择是否选
data: null as any, data: null as any,
}, },
data: [], // 展开的节点
props: { defaultExpandedKeys: [] as any
});
const {
data,
saveTabDialog,
infoDialog,
defaultExpandedKeys,
} = toRefs(state)
const props = {
label: 'name', label: 'name',
children: 'children', children: 'children',
}, };
// 展开的节点
defaultExpandedKeys: [] as any[], const rules = {
rules: {
code: [ code: [
{ required: true, message: '标识符不能为空', trigger: 'blur' }, { required: true, message: '标识符不能为空', trigger: 'blur' },
// { // {
@@ -159,13 +159,21 @@ export default defineComponent({
// }, // },
], ],
name: [{ required: true, message: '名称不能为空', trigger: 'blur' }], name: [{ required: true, message: '名称不能为空', trigger: 'blur' }],
}, };
});
onMounted(() => { onMounted(() => {
search(); search();
}); });
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 () => { const search = async () => {
let res = await tagApi.getTagTrees.request(null); let res = await tagApi.getTagTrees.request(null);
state.data = res; state.data = res;
@@ -262,26 +270,11 @@ export default defineComponent({
state.defaultExpandedKeys.splice(index, 1); state.defaultExpandedKeys.splice(index, 1);
} }
}; };
return {
...toRefs(state),
dateFormat,
tagForm,
info,
saveTag,
showSaveTabDialog,
showEditTagDialog,
cancelSaveTag,
deleteTag,
handleNodeExpand,
handleNodeCollapse,
};
},
});
</script> </script>
<style lang="scss"> <style lang="scss">
.menu { .menu {
height: 100%; height: 100%;
.el-tree-node__content { .el-tree-node__content {
height: 40px; height: 40px;
line-height: 40px; line-height: 40px;

View File

@@ -2,13 +2,14 @@
<div class="role-list"> <div class="role-list">
<el-card> <el-card>
<el-button v-auth="'team:save'" type="primary" icon="plus" @click="showSaveTeamDialog(false)">添加</el-button> <el-button v-auth="'team:save'" type="primary" icon="plus" @click="showSaveTeamDialog(false)">添加</el-button>
<el-button v-auth="'team:save'" :disabled="chooseId == null" @click="showSaveTeamDialog(chooseData)" type="primary" icon="edit" <el-button v-auth="'team:save'" :disabled="!chooseId" @click="showSaveTeamDialog(chooseData)"
>编辑</el-button type="primary" icon="edit">编辑</el-button>
> <el-button v-auth="'team:del'" :disabled="!chooseId" @click="deleteTeam(chooseData)" type="danger"
<el-button v-auth="'team:del'" :disabled="chooseId == null" @click="deleteTeam(chooseData)" type="danger" icon="delete">删除</el-button> icon="delete">删除</el-button>
<div style="float: right"> <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> <el-button @click="search" type="success" icon="search"></el-button>
</div> </div>
<el-table :data="data" @current-change="choose" ref="table" style="width: 100%"> <el-table :data="data" @current-change="choose" ref="table" style="width: 100%">
@@ -36,14 +37,9 @@
</el-table-column> </el-table-column>
</el-table> </el-table>
<el-row style="margin-top: 20px" type="flex" justify="end"> <el-row style="margin-top: 20px" type="flex" justify="end">
<el-pagination <el-pagination style="text-align: right" @current-change="handlePageChange" :total="total"
style="text-align: right" layout="prev, pager, next, total, jumper" v-model:current-page="query.pageNum"
@current-change="handlePageChange" :page-size="query.pageSize"></el-pagination>
:total="total"
layout="prev, pager, next, total, jumper"
v-model:current-page="query.pageNum"
:page-size="query.pageSize"
></el-pagination>
</el-row> </el-row>
</el-card> </el-card>
@@ -64,23 +60,14 @@
</template> </template>
</el-dialog> </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 label-width="70px">
<el-form-item prop="project" label="标签:"> <el-form-item prop="project" label="标签:">
<el-tree-select <el-tree-select ref="tagTreeRef" style="width: 100%" v-model="showTagDialog.tagTreeTeams"
ref="tagTreeRef" :data="showTagDialog.tags" :default-expanded-keys="showTagDialog.tagTreeTeams" multiple
style="width: 100%" :render-after-expand="true" show-checkbox check-strictly node-key="id"
v-model="showTagDialog.tagTreeTeams" :props="showTagDialog.props" @check="tagTreeNodeCheck">
: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 }"> <template #default="{ data }">
<span class="custom-tree-node"> <span class="custom-tree-node">
<span style="font-size: 13px"> <span style="font-size: 13px">
@@ -88,7 +75,8 @@
<span style="color: #3c8dbc"></span> <span style="color: #3c8dbc"></span>
{{ data.name }} {{ data.name }}
<span style="color: #3c8dbc"></span> <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>
</span> </span>
</template> </template>
@@ -103,12 +91,19 @@
</template> </template>
</el-dialog> </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"> <div class="toolbar">
<el-button v-auth="'team:member:save'" @click="showAddMemberDialog()" type="primary" icon="plus">添加</el-button> <el-button v-auth="'team:member:save'" @click="showAddMemberDialog()" type="primary" icon="plus"
<el-button v-auth="'team:member:del'" @click="deleteMember" :disabled="showMemDialog.chooseId == null" type="danger" icon="delete">移除</el-button> 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> </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"> <el-table-column label="选择" width="50px">
<template #default="scope"> <template #default="scope">
<el-radio v-model="showMemDialog.chooseId" :label="scope.row.id"> <el-radio v-model="showMemDialog.chooseId" :label="scope.row.id">
@@ -116,6 +111,7 @@
</el-radio> </el-radio>
</template> </template>
</el-table-column> </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="username" label="账号" width="135"></el-table-column>
<el-table-column property="createTime" label="加入时间"> <el-table-column property="createTime" label="加入时间">
<template #default="scope"> <template #default="scope">
@@ -124,28 +120,18 @@
</el-table-column> </el-table-column>
<el-table-column property="creator" label="分配者" width="135"></el-table-column> <el-table-column property="creator" label="分配者" width="135"></el-table-column>
</el-table> </el-table>
<el-pagination <el-pagination size="small" @current-change="setMemebers" style="text-align: center" background
@current-change="setMemebers" layout="prev, pager, next, total, jumper" :total="showMemDialog.members.total"
style="text-align: center" v-model:current-page="showMemDialog.query.pageNum" :page-size="showMemDialog.query.pageSize" />
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-dialog width="400px" title="添加成员" :before-close="cancelAddMember" v-model="showMemDialog.addVisible">
<el-form :model="showMemDialog.memForm" label-width="70px"> <el-form :model="showMemDialog.memForm" label-width="70px">
<el-form-item label="账号:"> <el-form-item label="账号:">
<el-select <el-select style="width: 100%" remote :remote-method="getAccount"
style="width: 100%" v-model="showMemDialog.memForm.accountIds" filterable multiple placeholder="请输入账号模糊搜索并选择">
remote <el-option v-for="item in showMemDialog.accounts" :key="item.id"
:remote-method="getAccount" :label="`${item.username} [${item.name}]`" :value="item.id">
v-model="showMemDialog.memForm.accountId" </el-option>
filterable
placeholder="请输入账号模糊搜索并选择"
>
<el-option v-for="item in showMemDialog.accounts" :key="item.id" :label="item.username" :value="item.id"> </el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-form> </el-form>
@@ -160,21 +146,17 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { ref, toRefs, reactive, onMounted, defineComponent } from 'vue'; import { ref, toRefs, reactive, onMounted } from 'vue';
import { tagApi } from './api'; import { tagApi } from './api';
import { accountApi } from '../../system/api'; import { accountApi } from '../../system/api';
import { ElMessage, ElMessageBox } from 'element-plus'; import { ElMessage, ElMessageBox } from 'element-plus';
import { dateFormat } from '@/common/utils/date'; import { dateFormat } from '@/common/utils/date';
import { notBlank } from '@/common/assert'; import { notBlank } from '@/common/assert';
export default defineComponent({
name: 'TeamList',
components: {},
setup() {
const teamForm: any = ref(null); const teamForm: any = ref(null);
const tagTreeRef: any = ref(null); const tagTreeRef: any = ref(null);
const state = reactive({ const state = reactive({
dialogFormVisible: false,
currentEditPermissions: false, currentEditPermissions: false,
addTeamDialog: { addTeamDialog: {
title: '新增团队', title: '新增团队',
@@ -195,9 +177,10 @@ export default defineComponent({
chooseId: 0, chooseId: 0,
chooseData: null, chooseData: null,
query: { query: {
pageSize: 8, pageSize: 10,
pageNum: 1, pageNum: 1,
teamId: null, teamId: null,
username: null,
}, },
members: { members: {
list: [], list: [],
@@ -206,7 +189,7 @@ export default defineComponent({
title: '', title: '',
addVisible: false, addVisible: false,
memForm: { memForm: {
accountId: null as any, accountIds: [] as any,
teamId: 0, teamId: 0,
}, },
accounts: Array(), accounts: Array(),
@@ -225,6 +208,17 @@ export default defineComponent({
}, },
}); });
const {
query,
addTeamDialog,
total,
data,
chooseId,
chooseData,
showMemDialog,
showTagDialog,
} = toRefs(state)
onMounted(() => { onMounted(() => {
search(); search();
}); });
@@ -337,7 +331,7 @@ export default defineComponent({
const addMember = async () => { const addMember = async () => {
const memForm = state.showMemDialog.memForm; const memForm = state.showMemDialog.memForm;
memForm.teamId = state.chooseId; memForm.teamId = state.chooseId;
notBlank(memForm.accountId, '请先选择账号'); notBlank(memForm.accountIds, '请先选择账号');
await tagApi.saveTeamMem.request(memForm); await tagApi.saveTeamMem.request(memForm);
ElMessage.success('保存成功'); ElMessage.success('保存成功');
@@ -407,34 +401,7 @@ export default defineComponent({
// } // }
// console.log(state.showTagDialog.tagTreeTeams); // 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> </script>
<style lang="scss"> <style lang="scss">
</style> </style>

View File

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

View File

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

View File

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

View File

@@ -1,14 +1,11 @@
<template> <template>
<div class="account-dialog"> <div class="account-dialog">
<el-dialog <el-dialog :title="account == null ? '' : '分配“' + account.username + '”的角色'" v-model="dialogVisible"
:title="account == null ? '' : '分配“' + account.username + '”的角色'" :before-close="cancel" :show-close="false">
v-model="dialogVisible"
:before-close="cancel"
:show-close="false"
>
<div class="toolbar"> <div class="toolbar">
<div style="float: left"> <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> <el-button @click="search" type="success" icon="search"></el-button>
</div> </div>
</div> </div>
@@ -22,15 +19,9 @@
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
<el-pagination <el-pagination @current-change="handlePageChange" style="text-align: center; margin-top: 20px" background
@current-change="handlePageChange" layout="prev, pager, next, total, jumper" :total="total" v-model:current-page="query.pageNum"
style="text-align: center; margin-top: 20px" :page-size="query.pageSize"></el-pagination>
background
layout="prev, pager, next, total, jumper"
:total="total"
v-model:current-page="query.pageNum"
:page-size="query.pageSize"
></el-pagination>
<template #footer> <template #footer>
<div class="dialog-footer"> <div class="dialog-footer">
@@ -42,29 +33,29 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { toRefs, reactive, watch, defineComponent, ref } from 'vue'; import { toRefs, reactive, watch, ref } from 'vue';
import { roleApi, accountApi } from '../api'; import { roleApi, accountApi } from '../api';
import { ElMessage } from 'element-plus'; import { ElMessage } from 'element-plus';
export default defineComponent({
name: 'RoleEdit', const props = defineProps({
props: {
visible: { visible: {
type: Boolean, type: Boolean,
}, },
account: { account: Object
type: [Boolean, Object], })
},
}, //定义事件
setup(props: any, { emit }) { const emit = defineEmits(['update:visible', 'cancel', 'val-change'])
const roleTable: any = ref(null); const roleTable: any = ref(null);
const state = reactive({ const state = reactive({
dialogVisible: false, dialogVisible: false,
btnLoading: false, btnLoading: false,
// 所有角色 // 所有角色
allRole: [] as any, allRole: [] as any,
// 该账号拥有的角色id // 该账号拥有的角色id
roles: [] as any,
query: { query: {
name: null, name: null,
pageNum: 1, pageNum: 1,
@@ -73,19 +64,28 @@ export default defineComponent({
total: 0, total: 0,
}); });
watch(props, (newValue) => { const {
dialogVisible,
btnLoading,
allRole,
query,
total,
} = toRefs(state)
// 用户拥有的角色信息
let roles: any[] = []
watch(props, (newValue: any) => {
state.dialogVisible = newValue.visible; state.dialogVisible = newValue.visible;
if (newValue.account && newValue.account.id != 0) { if (state.dialogVisible && newValue.account && newValue.account.id != 0) {
accountApi.roleIds accountApi.roleIds
.request({ .request({
id: props.account['id'], id: props.account!.id,
}) })
.then((res) => { .then((res) => {
state.roles = res || []; roles = res || [];
search(); search();
}); });
} else {
return;
} }
}); });
@@ -99,7 +99,6 @@ export default defineComponent({
}; };
const select = (val: any, row: any) => { const select = (val: any, row: any) => {
let roles = state.roles;
// 如果账号的角色id存在则为取消该角色(删除角色id列表中的该记录id),否则为新增角色 // 如果账号的角色id存在则为取消该角色(删除角色id列表中的该记录id),否则为新增角色
if (roles.includes(row.id)) { if (roles.includes(row.id)) {
for (let i = 0; i < roles.length; i++) { for (let i = 0; i < roles.length; i++) {
@@ -122,7 +121,7 @@ export default defineComponent({
setTimeout(() => { setTimeout(() => {
roleTable.value.clearSelection(); roleTable.value.clearSelection();
state.allRole.forEach((r: any) => { state.allRole.forEach((r: any) => {
if (state.roles.includes(r.id)) { if (roles.includes(r.id)) {
roleTable.value.toggleRowSelection(r, true); roleTable.value.toggleRowSelection(r, true);
} }
}); });
@@ -130,9 +129,9 @@ export default defineComponent({
}; };
const btnOk = async () => { const btnOk = async () => {
let roleIds = state.roles.join(','); let roleIds = roles.join(',');
await accountApi.saveRoles.request({ await accountApi.saveRoles.request({
id: props.account['id'], id: props.account!.id,
roleIds: roleIds, roleIds: roleIds,
}); });
ElMessage.success('保存成功!'); ElMessage.success('保存成功!');
@@ -164,18 +163,4 @@ export default defineComponent({
state.total = res.total; state.total = res.total;
checkSelected(); checkSelected();
}; };
return {
...toRefs(state),
roleTable,
search,
handlePageChange,
selectable,
select,
btnOk,
cancel,
clear,
};
},
});
</script> </script>

View File

@@ -1,6 +1,7 @@
<template> <template>
<div> <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 ref="configForm" :model="form" label-width="90px">
<el-form-item prop="name" label="配置项:" required> <el-form-item prop="name" label="配置项:" required>
<el-input v-model="form.name"></el-input> <el-input v-model="form.name"></el-input>
@@ -14,17 +15,25 @@
</el-row> </el-row>
<el-form-item :key="param" v-for="(param, index) in params" prop="params" :label="`参数${index + 1}`"> <el-form-item :key="param" v-for="(param, index) in params" prop="params" :label="`参数${index + 1}`">
<el-row> <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-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-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-divider :span="1" direction="vertical" border-style="dashed" />
<el-col :span="4"> <el-col :span="4">
<el-input v-model="param.options" placeholder="可选值 ,分割"></el-input> <el-input v-model="param.options" placeholder="可选值 ,分割"></el-input>
</el-col> </el-col>
<el-divider :span="1" direction="vertical" border-style="dashed" /> <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-row>
</el-form-item> </el-form-item>
<!-- <el-form-item prop="value" label="配置值:" required> <!-- <el-form-item prop="value" label="配置值:" required>
@@ -44,13 +53,11 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { ref, toRefs, reactive, watch, defineComponent } from 'vue'; import { ref, toRefs, reactive, watch, defineComponent } from 'vue';
import { configApi } from '../api'; import { configApi } from '../api';
export default defineComponent({ const props = defineProps({
name: 'ConfigEdit',
props: {
visible: { visible: {
type: Boolean, type: Boolean,
}, },
@@ -60,9 +67,14 @@ export default defineComponent({
title: { title: {
type: String, type: String,
}, },
}, })
setup(props: any, { emit }) {
//定义事件
const emit = defineEmits(['update:visible', 'cancel', 'val-change'])
const configForm: any = ref(null); const configForm: any = ref(null);
const state = reactive({ const state = reactive({
dvisible: false, dvisible: false,
params: [] as any, params: [] as any,
@@ -77,7 +89,14 @@ export default defineComponent({
btnLoading: false, btnLoading: false,
}); });
watch(props, (newValue) => { const {
dvisible,
params,
form,
btnLoading,
} = toRefs(state)
watch(props, (newValue: any) => {
state.dvisible = newValue.visible; state.dvisible = newValue.visible;
if (newValue.data) { if (newValue.data) {
state.form = { ...newValue.data }; state.form = { ...newValue.data };
@@ -123,17 +142,7 @@ export default defineComponent({
} }
}); });
}; };
return {
...toRefs(state),
onAddParam,
onDeleteParam,
configForm,
btnOk,
cancel,
};
},
});
</script> </script>
<style lang="scss"> <style lang="scss">
</style> </style>

View File

@@ -2,7 +2,8 @@
<div class="role-list"> <div class="role-list">
<el-card> <el-card>
<el-button type="primary" icon="plus" @click="editConfig(false)">添加</el-button> <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 :data="configs" @current-change="choose" ref="table" style="width: 100%">
<el-table-column label="选择" width="55px"> <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="remark" label="备注" min-width="100px" show-overflow-tooltip></el-table-column>
<el-table-column prop="updateTime" label="更新时间" min-width="100px"> <el-table-column prop="updateTime" label="更新时间" min-width="100px">
<template #default="scope"> <template #default="scope">
{{ $filters.dateFormat(scope.row.createTime) }} {{ dateFormat(scope.row.createTime) }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="modifier" label="修改者" show-overflow-tooltip></el-table-column> <el-table-column prop="modifier" label="修改者" show-overflow-tooltip></el-table-column>
<el-table-column label="操作" min-width="50" fixed="right"> <el-table-column label="操作" min-width="50" fixed="right">
<template #default="scope"> <template #default="scope">
<el-link <el-link :disabled="scope.row.status == -1" type="warning"
:disabled="scope.row.status == -1" @click="showSetConfigDialog(scope.row)" plain size="small" :underline="false">配置</el-link>
type="warning"
@click="showSetConfigDialog(scope.row)"
plain
size="small"
:underline="false"
>配置</el-link
>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
<el-row style="margin-top: 20px" type="flex" justify="end"> <el-row style="margin-top: 20px" type="flex" justify="end">
<el-pagination <el-pagination style="text-align: right" @current-change="handlePageChange" :total="total"
style="text-align: right" layout="prev, pager, next, total, jumper" v-model:current-page="query.pageNum"
@current-change="handlePageChange" :page-size="query.pageSize"></el-pagination>
:total="total"
layout="prev, pager, next, total, jumper"
v-model:current-page="query.pageNum"
:page-size="query.pageSize"
></el-pagination>
</el-row> </el-row>
</el-card> </el-card>
<el-dialog :before-close="closeSetConfigDialog" title="配置项设置" v-model="paramsDialog.visible" width="500px"> <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 v-if="paramsDialog.paramsFormItem.length > 0" ref="paramsForm" :model="paramsDialog.params"
<el-form-item v-for="item in paramsDialog.paramsFormItem" :key="item.name" :prop="item.model" :label="item.name" required> label-width="90px">
<el-input <el-form-item v-for="item in paramsDialog.paramsFormItem" :key="item.name" :prop="item.model"
v-if="!item.options" :label="item.name" required>
v-model="paramsDialog.params[item.model]" <el-input v-if="!item.options" v-model="paramsDialog.params[item.model]"
:placeholder="item.placeholder" :placeholder="item.placeholder" autocomplete="off" clearable></el-input>
autocomplete="off" <el-select v-else v-model="paramsDialog.params[item.model]" :placeholder="item.placeholder"
clearable filterable autocomplete="off" clearable style="width: 100%">
></el-input> <el-option v-for="option in item.options.split(',')" :key="option" :label="option"
<el-select :value="option" />
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-select>
</el-form-item> </el-form-item>
</el-form> </el-form>
<el-form v-else ref="paramsForm" label-width="90px"> <el-form v-else ref="paramsForm" label-width="90px">
<el-form-item label="配置值" required> <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-item>
</el-form> </el-form>
<template #footer> <template #footer>
@@ -84,24 +65,19 @@
</template> </template>
</el-dialog> </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> </div>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { toRefs, reactive, onMounted, defineComponent } from 'vue'; import { toRefs, reactive, onMounted } from 'vue';
import ConfigEdit from './ConfigEdit.vue'; import ConfigEdit from './ConfigEdit.vue';
import { configApi } from '../api'; import { configApi } from '../api';
import { ElMessage, ElMessageBox } from 'element-plus'; import { ElMessage } from 'element-plus';
export default defineComponent({ import { dateFormat } from '@/common/utils/date';
name: 'ConfigList',
components: {
ConfigEdit,
},
setup() {
const state = reactive({ const state = reactive({
dialogFormVisible: false,
currentEditPermissions: false,
query: { query: {
pageNum: 1, pageNum: 1,
pageSize: 10, pageSize: 10,
@@ -124,6 +100,16 @@ export default defineComponent({
}, },
}); });
const {
query,
total,
configs,
chooseId,
chooseData,
paramsDialog,
configEdit,
} = toRefs(state)
onMounted(() => { onMounted(() => {
search(); search();
}); });
@@ -219,20 +205,7 @@ export default defineComponent({
state.configEdit.visible = true; state.configEdit.visible = true;
}; };
return {
...toRefs(state),
showSetConfigDialog,
closeSetConfigDialog,
setConfig,
search,
handlePageChange,
choose,
configEditChange,
editConfig,
};
},
});
</script> </script>
<style lang="scss"> <style lang="scss">
</style> </style>

View File

@@ -6,7 +6,8 @@
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb10"> <el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb10">
<el-form-item prop="type" label="类型" required> <el-form-item prop="type" label="类型" required>
<el-select v-model="form.type" :disabled="typeDisabled" placeholder="请选择"> <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-option v-for="item in enums.ResourceTypeEnum as any" :key="item.value" :label="item.label"
:value="item.value">
</el-option> </el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
@@ -27,55 +28,56 @@
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb10"> <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" /> <icon-selector v-model="form.meta.icon" type="ele" />
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb10"> <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-input v-model.trim="form.meta.routeName" placeholder="请输入路由名称"></el-input>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb10"> <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-input v-model.trim="form.meta.component" placeholder="请输入组件名"></el-input>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb10"> <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-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-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb10"> <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-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-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb10"> <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-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-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb10"> <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-form-item v-if="form.type === menuTypeValue" prop="code" label="是否iframe">
<el-select @change="changeIsIframe" v-model="form.meta.isIframe" placeholder="请选择" width="w100"> <el-select @change="changeIsIframe" v-model="form.meta.isIframe" placeholder="请选择"
<el-option v-for="item in trueFalseOption" :key="item.value" :label="item.label" :value="item.value"> </el-option> width="w100">
<el-option v-for="item in trueFalseOption" :key="item.value" :label="item.label"
:value="item.value"> </el-option>
</el-select> </el-select>
</el-form-item> </el-form-item>
</el-col> </el-col>
<el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb10"> <el-col :xs="24" :sm="12" :md="12" :lg="12" :xl="12" class="mb10">
<el-form-item <el-form-item v-if="form.type === menuTypeValue && form.meta.isIframe" prop="code"
v-if="form.type === enums.ResourceTypeEnum.MENU.value && form.meta.isIframe" label="iframe地址" width="w100">
prop="code"
label="iframe地址"
width="w100"
>
<el-input v-model.trim="form.meta.link" placeholder="请输入iframe url"></el-input> <el-input v-model.trim="form.meta.link" placeholder="请输入iframe url"></el-input>
</el-form-item> </el-form-item>
</el-col> </el-col>
@@ -92,20 +94,15 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { ref, toRefs, reactive, watch, defineComponent } from 'vue'; import { ref, toRefs, reactive, watch } from 'vue';
import { ElMessage } from 'element-plus'; import { ElMessage } from 'element-plus';
import { resourceApi } from '../api'; import { resourceApi } from '../api';
import enums from '../enums'; import enums from '../enums';
import { notEmpty } from '@/common/assert'; import { notEmpty } from '@/common/assert';
import iconSelector from '@/components/iconSelector/index.vue'; import iconSelector from '@/components/iconSelector/index.vue';
export default defineComponent({ const props = defineProps({
name: 'ResourceEdit',
components: {
iconSelector,
},
props: {
visible: { visible: {
type: Boolean, type: Boolean,
}, },
@@ -118,10 +115,15 @@ export default defineComponent({
typeDisabled: { typeDisabled: {
type: Boolean, type: Boolean,
}, },
}, })
setup(props: any, { emit }) {
//定义事件
const emit = defineEmits(['update:visible', 'cancel', 'val-change'])
const menuForm: any = ref(null); const menuForm: any = ref(null);
const menuTypeValue = enums.ResourceTypeEnum['MENU'].value
const defaultMeta = { const defaultMeta = {
routeName: '', routeName: '',
icon: 'Menu', icon: 'Menu',
@@ -131,10 +133,27 @@ export default defineComponent({
isHide: false, isHide: false,
isAffix: false, isAffix: false,
isIframe: false, isIframe: false,
link: '',
}; };
const state = reactive({ const rules = {
trueFalseOption: [ name: [
{
required: true,
message: '请输入资源名称',
trigger: ['change', 'blur'],
},
],
weight: [
{
required: true,
message: '请输入序号',
trigger: ['change', 'blur'],
},
],
}
const trueFalseOption = [
{ {
label: '是', label: '是',
value: true, value: true,
@@ -143,19 +162,10 @@ export default defineComponent({
label: '否', label: '否',
value: false, value: false,
}, },
], ]
const state = reactive({
dialogVisible: false, dialogVisible: false,
//弹出框对象
dialogForm: {
title: '',
visible: false,
data: {},
},
props: {
value: 'id',
label: 'name',
children: 'children',
},
form: { form: {
id: null, id: null,
name: null, name: null,
@@ -172,30 +182,19 @@ export default defineComponent({
isHide: false, isHide: false,
isAffix: false, isAffix: false,
isIframe: false, isIframe: false,
link: '',
}, },
}, },
// 资源类型选择是否禁用
// typeDisabled: false,
btnLoading: 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; state.dialogVisible = newValue.visible;
if (newValue.data) { if (newValue.data) {
state.form = { ...newValue.data }; state.form = { ...newValue.data };
@@ -285,17 +284,6 @@ export default defineComponent({
emit('update:visible', false); emit('update:visible', false);
emit('cancel'); emit('cancel');
}; };
return {
...toRefs(state),
enums,
changeIsIframe,
menuForm,
btnOk,
cancel,
};
},
});
</script> </script>
<style lang="scss"> <style lang="scss">
// .m-dialog { // .m-dialog {

View File

@@ -2,100 +2,57 @@
<div class="menu"> <div class="menu">
<div class="toolbar"> <div class="toolbar">
<div> <div>
<span style="font-size: 14px"><SvgIcon name="info-filled"/>红色字体表示禁用状态</span> <span style="font-size: 14px">
<SvgIcon name="info-filled" />红色字体表示禁用状态
</span>
</div> </div>
<el-button v-auth="'resource:add'" type="primary" icon="plus" @click="addResource(false)">添加</el-button> <el-button v-auth="'resource:add'" type="primary" icon="plus" @click="addResource(false)">添加</el-button>
</div> </div>
<el-tree <el-tree class="none-select" :indent="38" node-key="id" :props="props" :data="data"
class="none-select" @node-expand="handleNodeExpand" @node-collapse="handleNodeCollapse"
:indent="38" :default-expanded-keys="defaultExpandedKeys" :expand-on-click-node="false">
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 }"> <template #default="{ data }">
<span class="custom-tree-node"> <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> <span style="color: #3c8dbc"></span>
{{ data.name }} {{ data.name }}
<span style="color: #3c8dbc"></span> <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>
<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="color: #3c8dbc"></span>
<span :style="data.status == 1 ? 'color: #67c23a;' : 'color: #f67c6c;'">{{ data.name }}</span> <span :style="data.status == 1 ? 'color: #67c23a;' : 'color: #f67c6c;'">{{ data.name }}</span>
<span style="color: #3c8dbc"></span> <span style="color: #3c8dbc"></span>
</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 <el-link v-auth="'resource:update'" @click.prevent="editResource(data)" class="ml5" type="primary"
v-auth="'resource:update'" icon="edit" :underline="false" />
@click.prevent="editResource(data)"
class="ml5"
type="primary"
icon="edit"
:underline="false"
/>
<el-link <el-link v-auth="'resource:add'" @click.prevent="addResource(data)"
v-auth="'resource:add'" v-if="data.type === menuTypeValue" icon="circle-plus" :underline="false"
@click.prevent="addResource(data)" type="success" class="ml5" />
v-if="data.type === enums.ResourceTypeEnum.MENU.value"
icon="circle-plus"
:underline="false"
type="success"
class="ml5"
/>
<el-link <el-link v-auth="'resource:changeStatus'" @click.prevent="changeStatus(data, -1)"
v-auth="'resource:changeStatus'" v-if="data.status === 1 && data.type === permissionTypeValue"
@click.prevent="changeStatus(data, -1)" icon="circle-close" :underline="false" type="warning" class="ml5" />
v-if="data.status === 1 && data.type === enums.ResourceTypeEnum.PERMISSION.value"
icon="circle-close"
:underline="false"
type="warning"
class="ml5"
/>
<el-link <el-link v-auth="'resource:changeStatus'" @click.prevent="changeStatus(data, 1)"
v-auth="'resource:changeStatus'" v-if="data.status === -1 && data.type === permissionTypeValue"
@click.prevent="changeStatus(data, 1)" type="success" icon="circle-check" :underline="false" plain class="ml5" />
v-if="data.status === -1 && data.type === enums.ResourceTypeEnum.PERMISSION.value"
type="success"
icon="circle-check"
:underline="false"
plain
class="ml5"
/>
<el-link <el-link v-auth="'resource:delete'" v-if="data.children == null && data.name !== '首页'"
v-auth="'resource:delete'" @click.prevent="deleteMenu(data)" type="danger" icon="delete" :underline="false" plain
v-if="data.children == null && data.name !== '首页'" class="ml5" />
@click.prevent="deleteMenu(data)"
type="danger"
icon="delete"
:underline="false"
plain
class="ml5"
/>
</span> </span>
</template> </template>
</el-tree> </el-tree>
<ResourceEdit <ResourceEdit :title="dialogForm.title" v-model:visible="dialogForm.visible" v-model:data="dialogForm.data"
:title="dialogForm.title" :typeDisabled="dialogForm.typeDisabled" :departTree="data" :type="dialogForm.type" @val-change="valChange">
v-model:visible="dialogForm.visible" </ResourceEdit>
v-model:data="dialogForm.data"
:typeDisabled="dialogForm.typeDisabled"
:departTree="data"
:type="dialogForm.type"
@val-change="valChange"
></ResourceEdit>
<el-dialog v-model="infoDialog.visible"> <el-dialog v-model="infoDialog.visible">
<el-descriptions title="资源信息" :column="2" border> <el-descriptions title="资源信息" :column="2" border>
@@ -123,40 +80,41 @@
<el-descriptions-item v-if="infoDialog.data.type == menuTypeValue" label="是否iframe"> <el-descriptions-item v-if="infoDialog.data.type == menuTypeValue" label="是否iframe">
{{ infoDialog.data.meta.isIframe ? '' : '' }} {{ infoDialog.data.meta.isIframe ? '' : '' }}
</el-descriptions-item> </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 }} {{ infoDialog.data.meta.link }}
</el-descriptions-item> </el-descriptions-item>
<el-descriptions-item label="创建者">{{ infoDialog.data.creator }}</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="修改者">{{ 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-descriptions>
</el-dialog> </el-dialog>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { toRefs, reactive, onMounted, defineComponent } from 'vue'; import { toRefs, reactive, onMounted } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus'; import { ElMessage, ElMessageBox } from 'element-plus';
import ResourceEdit from './ResourceEdit.vue'; import ResourceEdit from './ResourceEdit.vue';
import enums from '../enums'; import enums from '../enums';
import { resourceApi } from '../api'; import { resourceApi } from '../api';
import { dateFormat } from '@/common/utils/date';
const menuTypeValue = enums.ResourceTypeEnum['MENU'].value
const permissionTypeValue = enums.ResourceTypeEnum['PERMISSION'].value
const props = {
label: 'name',
children: 'children',
}
export default defineComponent({
name: 'ResourceList',
components: {
ResourceEdit,
},
setup() {
const state = reactive({ const state = reactive({
menuTypeValue: enums.ResourceTypeEnum['MENU'].value,
permissionTypeValue: enums.ResourceTypeEnum['PERMISSION'].value,
showBtns: false,
// 当前鼠标右击的节点数据
rightClickData: {},
//弹出框对象 //弹出框对象
dialogForm: { dialogForm: {
type: null,
title: '', title: '',
visible: false, visible: false,
data: { pid: 0, type: 1, weight: 1 }, data: { pid: 0, type: 1, weight: 1 },
@@ -169,18 +127,32 @@ export default defineComponent({
visible: false, visible: false,
// 资源类型选择是否选 // 资源类型选择是否选
data: { data: {
meta: {}, meta: {} as any,
name: '',
type: null,
creator: '',
modifier: '',
createTime: '',
updateTime: '',
weight: null,
code: '',
}, },
}, },
data: [], data: [],
props: {
label: 'name',
children: 'children',
},
// 展开的节点 // 展开的节点
defaultExpandedKeys: [] as any[], defaultExpandedKeys: [] as any[],
}); });
const {
dialogForm,
infoDialog,
data,
defaultExpandedKeys,
} = toRefs(state)
onMounted(() => { onMounted(() => {
search(); search();
}); });
@@ -214,7 +186,7 @@ export default defineComponent({
// 添加顶级菜单情况 // 添加顶级菜单情况
if (!data) { if (!data) {
dialog.typeDisabled = true; dialog.typeDisabled = true;
dialog.data.type = state.menuTypeValue; dialog.data.type = menuTypeValue;
dialog.title = '添加顶级菜单'; dialog.title = '添加顶级菜单';
dialog.visible = true; dialog.visible = true;
return; return;
@@ -230,16 +202,16 @@ export default defineComponent({
dialog.typeDisabled = true; dialog.typeDisabled = true;
let hasPermission = false; let hasPermission = false;
for (let c of data.children) { for (let c of data.children) {
if (c.type === state.permissionTypeValue) { if (c.type === permissionTypeValue) {
hasPermission = true; hasPermission = true;
break; break;
} }
} }
// 如果子节点中存在权限资源,则只能新增权限资源,否则只能新增菜单资源 // 如果子节点中存在权限资源,则只能新增权限资源,否则只能新增菜单资源
if (hasPermission) { if (hasPermission) {
dialog.data.type = state.permissionTypeValue; dialog.data.type = permissionTypeValue;
} else { } else {
dialog.data.type = state.menuTypeValue; dialog.data.type = menuTypeValue;
} }
dialog.data.weight = data.children.length + 1; dialog.data.weight = data.children.length + 1;
} }
@@ -314,21 +286,6 @@ export default defineComponent({
} }
state.infoDialog.visible = true; state.infoDialog.visible = true;
}; };
return {
...toRefs(state),
enums,
deleteMenu,
addResource,
editResource,
valChange,
changeStatus,
handleNodeExpand,
handleNodeCollapse,
info,
};
},
});
</script> </script>
<style lang="scss"> <style lang="scss">
.menu { .menu {

View File

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

View File

@@ -1,17 +1,14 @@
<template> <template>
<div class="role-dialog"> <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 ref="roleForm" :model="form" label-width="90px">
<el-form-item prop="name" label="角色名称:" required> <el-form-item prop="name" label="角色名称:" required>
<el-input v-model="form.name" auto-complete="off"></el-input> <el-input v-model="form.name" auto-complete="off"></el-input>
</el-form-item> </el-form-item>
<el-form-item prop="code" label="角色code:" required> <el-form-item prop="code" label="角色code:" required>
<el-input <el-input :disabled="form.id != null" v-model="form.code" placeholder="COMMON开头则为所有账号共有角色"
:disabled="form.id != null" auto-complete="off"></el-input>
v-model="form.code"
placeholder="COMMON开头则为所有账号共有角色"
auto-complete="off"
></el-input>
</el-form-item> </el-form-item>
<el-form-item label="角色描述:"> <el-form-item label="角色描述:">
<el-input v-model="form.remark" type="textarea" :rows="3" placeholder="请输入角色描述"></el-input> <el-input v-model="form.remark" type="textarea" :rows="3" placeholder="请输入角色描述"></el-input>
@@ -27,13 +24,11 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { ref, toRefs, reactive, watch, defineComponent } from 'vue'; import { ref, toRefs, reactive, watch } from 'vue';
import { roleApi } from '../api'; import { roleApi } from '../api';
export default defineComponent({ const props = defineProps({
name: 'RoleEdit',
props: {
visible: { visible: {
type: Boolean, type: Boolean,
}, },
@@ -43,21 +38,31 @@ export default defineComponent({
title: { title: {
type: String, type: String,
}, },
}, })
setup(props: any, { emit }) {
//定义事件
const emit = defineEmits(['update:visible', 'cancel', 'val-change'])
const roleForm: any = ref(null); const roleForm: any = ref(null);
const state = reactive({ const state = reactive({
dvisible: false, dvisible: false,
form: { form: {
id: null, id: null,
name: '', name: '',
code: '',
status: 1, status: 1,
remark: '', remark: '',
}, },
btnLoading: false, btnLoading: false,
}); });
watch(props, (newValue) => { const {
dvisible,
form,
btnLoading,
} = toRefs(state)
watch(props, (newValue: any) => {
state.dvisible = newValue.visible; state.dvisible = newValue.visible;
if (newValue.data) { if (newValue.data) {
state.form = { ...newValue.data }; state.form = { ...newValue.data };
@@ -86,15 +91,7 @@ export default defineComponent({
} }
}); });
}; };
return {
...toRefs(state),
roleForm,
btnOk,
cancel,
};
},
});
</script> </script>
<style lang="scss"> <style lang="scss">
</style> </style>

View File

@@ -2,21 +2,16 @@
<div class="role-list"> <div class="role-list">
<el-card> <el-card>
<el-button v-auth="'role:add'" type="primary" icon="plus" @click="editRole(false)">添加</el-button> <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:update'" :disabled="chooseId == null" @click="editRole(chooseData)" type="primary"
<el-button v-auth="'role:saveResources'" :disabled="chooseId == null" @click="editResource(chooseData)" type="success" icon="setting" icon="edit">编辑</el-button>
>分配菜单&权限</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:del'" :disabled="chooseId == null" @click="deleteRole(chooseData)" type="danger"
icon="delete">删除</el-button>
<div style="float: right"> <div style="float: right">
<el-input <el-input placeholder="请输入角色名称" class="mr2" style="width: 200px" v-model="query.name" @clear="search"
placeholder="请输入角色名称" clearable></el-input>
class="mr2"
style="width: 200px"
v-model="query.name"
@clear="search"
clearable
></el-input>
<el-button @click="search" type="success" icon="search"></el-button> <el-button @click="search" type="success" icon="search"></el-button>
</div> </div>
<el-table :data="roles" @current-change="choose" ref="table" style="width: 100%"> <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="remark" label="描述" min-width="160px" show-overflow-tooltip></el-table-column>
<el-table-column prop="createTime" label="创建时间"> <el-table-column prop="createTime" label="创建时间">
<template #default="scope"> <template #default="scope">
{{ $filters.dateFormat(scope.row.createTime) }} {{ dateFormat(scope.row.createTime) }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column prop="updateTime" label="修改时间"> <el-table-column prop="updateTime" label="修改时间">
<template #default="scope"> <template #default="scope">
{{ $filters.dateFormat(scope.row.updateTime) }} {{ dateFormat(scope.row.updateTime) }}
</template> </template>
</el-table-column> </el-table-column>
<el-table-column label="查看更多" min-width="80px"> <el-table-column label="查看更多" min-width="80px">
@@ -47,51 +42,32 @@
</el-table-column> </el-table-column>
</el-table> </el-table>
<el-row style="margin-top: 20px" type="flex" justify="end"> <el-row style="margin-top: 20px" type="flex" justify="end">
<el-pagination <el-pagination style="text-align: right" @current-change="handlePageChange" :total="total"
style="text-align: right" layout="prev, pager, next, total, jumper" v-model:current-page="query.pageNum"
@current-change="handlePageChange" :page-size="query.pageSize"></el-pagination>
:total="total"
layout="prev, pager, next, total, jumper"
v-model:current-page="query.pageNum"
:page-size="query.pageSize"
></el-pagination>
</el-row> </el-row>
</el-card> </el-card>
<role-edit :title="roleEdit.title" v-model:visible="roleEdit.visible" :data="roleEdit.role" @val-change="roleEditChange" /> <role-edit :title="roleEditDialog.title" v-model:visible="roleEditDialog.visible" :data="roleEditDialog.role"
<resource-edit @val-change="roleEditChange" />
v-model:visible="resourceDialog.visible" <resource-edit v-model:visible="resourceDialog.visible" :role="resourceDialog.role"
:role="resourceDialog.role" :resources="resourceDialog.resources" :defaultCheckedKeys="resourceDialog.defaultCheckedKeys"
:resources="resourceDialog.resources" @cancel="cancelEditResources()" />
:defaultCheckedKeys="resourceDialog.defaultCheckedKeys" <show-resource v-model:visible="showResourceDialog.visible" :title="showResourceDialog.title"
@cancel="cancelEditResources()" v-model:resources="showResourceDialog.resources" />
/>
<show-resource
v-model:visible="showResourceDialog.visible"
:title="showResourceDialog.title"
v-model:resources="showResourceDialog.resources"
/>
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { toRefs, reactive, onMounted, defineComponent } from 'vue'; import { toRefs, reactive, onMounted } from 'vue';
import RoleEdit from './RoleEdit.vue'; import RoleEdit from './RoleEdit.vue';
import ResourceEdit from './ResourceEdit.vue'; import ResourceEdit from './ResourceEdit.vue';
import ShowResource from './ShowResource.vue'; import ShowResource from './ShowResource.vue';
import { roleApi, resourceApi } from '../api'; import { roleApi, resourceApi } from '../api';
import { ElMessage, ElMessageBox } from 'element-plus'; import { ElMessage, ElMessageBox } from 'element-plus';
export default defineComponent({ import { dateFormat } from '@/common/utils/date';
name: 'RoleList',
components: {
RoleEdit,
ResourceEdit,
ShowResource,
},
setup() {
const state = reactive({ const state = reactive({
dialogFormVisible: false,
currentEditPermissions: false,
query: { query: {
pageNum: 1, pageNum: 1,
pageSize: 10, pageSize: 10,
@@ -107,7 +83,7 @@ export default defineComponent({
resources: [], resources: [],
defaultCheckedKeys: [], defaultCheckedKeys: [],
}, },
roleEdit: { roleEditDialog: {
title: '角色编辑', title: '角色编辑',
visible: false, visible: false,
role: {}, role: {},
@@ -119,6 +95,17 @@ export default defineComponent({
}, },
}); });
const {
query,
total,
roles,
chooseId,
chooseData,
resourceDialog,
roleEditDialog,
showResourceDialog,
} = toRefs(state)
onMounted(() => { onMounted(() => {
search(); search();
}); });
@@ -151,12 +138,12 @@ export default defineComponent({
const editRole = (data: any) => { const editRole = (data: any) => {
if (data) { if (data) {
state.roleEdit.role = data; state.roleEditDialog.role = data;
} else { } 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) => {
@@ -186,11 +173,6 @@ export default defineComponent({
state.showResourceDialog.visible = true; 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); let menus = await resourceApi.list.request(null);
// 获取所有菜单列表 // 获取所有菜单列表
@@ -247,22 +229,7 @@ export default defineComponent({
state.resourceDialog.defaultCheckedKeys = []; state.resourceDialog.defaultCheckedKeys = [];
}, 10); }, 10);
}; };
return {
...toRefs(state),
search,
handlePageChange,
choose,
roleEditChange,
editRole,
deleteRole,
showResources,
closeShowResourceDialog,
editResource,
cancelEditResources,
};
},
});
</script> </script>
<style lang="scss"> <style lang="scss">
</style> </style>

View File

@@ -1,13 +1,17 @@
<template> <template>
<div> <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"> <el-tree style="height: 50vh; overflow: auto" :data="resources" node-key="id" :props="defaultProps">
<template #default="{ node, data }"> <template #default="{ node, data }">
<span class="custom-tree-node"> <span class="custom-tree-node">
<span v-if="data.type == enums.ResourceTypeEnum.MENU.value">{{ 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 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> </span>
</template> </template>
</el-tree> </el-tree>
@@ -15,14 +19,12 @@
</div> </div>
</template> </template>
<script lang="ts"> <script lang="ts" setup>
import { getCurrentInstance, toRefs, reactive, watch, defineComponent } from 'vue'; import { getCurrentInstance, toRefs, reactive, watch } from 'vue';
import { ElMessageBox } from 'element-plus'; import { ElMessageBox } from 'element-plus';
import enums from '../enums'; import enums from '../enums';
export default defineComponent({ const props = defineProps({
name: 'ShowResource',
props: {
visible: { visible: {
type: Boolean, type: Boolean,
}, },
@@ -32,16 +34,24 @@ export default defineComponent({
title: { title: {
type: String, type: String,
}, },
}, })
setup(props: any, { emit }) {
//定义事件
const emit = defineEmits(['update:visible', 'update:resources'])
const { proxy } = getCurrentInstance() as any; const { proxy } = getCurrentInstance() as any;
const state = reactive({
dialogVisible: false, const defaultProps = {
defaultProps: {
children: 'children', children: 'children',
label: 'name', label: 'name',
}, }
const state = reactive({
dialogVisible: false,
}); });
const {
dialogVisible,
} = toRefs(state)
watch( watch(
() => props.visible, () => props.visible,
@@ -74,16 +84,8 @@ export default defineComponent({
emit('update:visible', false); emit('update:visible', false);
emit('update:resources', []); emit('update:resources', []);
}; };
return {
...toRefs(state),
enums,
info,
closeDialog,
};
},
});
</script> </script>
<style> <style>
</style> </style>

View File

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

View File

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

View File

@@ -31,7 +31,7 @@ func (d *dbRepoImpl) GetDbList(condition *entity.DbQuery, pageParam *model.PageP
if condition.TagPathLike != "" { if condition.TagPathLike != "" {
sql = sql + " AND d.tag_path LIKE '" + 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) return model.GetPageBySql(sql, pageParam, toEntity)
} }

View File

@@ -31,7 +31,7 @@ func (m *machineRepoImpl) GetMachineList(condition *entity.MachineQuery, pagePar
if condition.TagPathLike != "" { if condition.TagPathLike != "" {
sql = sql + " AND m.tag_path LIKE '" + 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) 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 != "" { if condition.TagPathLike != "" {
sql = sql + " AND d.tag_path LIKE '" + 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) return model.GetPageBySql(sql, pageParam, toEntity)
} }

View File

@@ -29,7 +29,7 @@ func (r *redisRepoImpl) GetRedisList(condition *entity.RedisQuery, pageParam *mo
if condition.TagPathLike != "" { if condition.TagPathLike != "" {
sql = sql + " AND d.tag_path LIKE '" + 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) return model.GetPageBySql(sql, pageParam, toEntity)
} }

View File

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

View File

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

View File

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

View File

@@ -56,7 +56,7 @@ func InitAccountRouter(router *gin.RouterGroup) {
ctx.NewReqCtxWithGin(c).Handle(a.Accounts) ctx.NewReqCtxWithGin(c).Handle(a.Accounts)
}) })
createAccount := ctx.NewLogInfo("创建账号").WithSave(true) createAccount := ctx.NewLogInfo("保存账号信息").WithSave(true)
addAccountPermission := ctx.NewPermission("account:add") addAccountPermission := ctx.NewPermission("account:add")
account.POST("", func(c *gin.Context) { account.POST("", func(c *gin.Context) {
ctx.NewReqCtxWithGin(c). 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_applicaiton "mayfly-go/internal/sys/application"
sys_entity "mayfly-go/internal/sys/domain/entity" sys_entity "mayfly-go/internal/sys/domain/entity"
"mayfly-go/internal/tag/api/form" "mayfly-go/internal/tag/api/form"
"mayfly-go/internal/tag/api/vo"
"mayfly-go/internal/tag/application" "mayfly-go/internal/tag/application"
"mayfly-go/internal/tag/domain/entity" "mayfly-go/internal/tag/domain/entity"
"mayfly-go/pkg/biz" "mayfly-go/pkg/biz"
@@ -52,26 +53,38 @@ func (p *Team) DelTeam(rc *ctx.ReqCtx) {
// 获取团队的成员信息 // 获取团队的成员信息
func (p *Team) GetTeamMembers(rc *ctx.ReqCtx) { func (p *Team) GetTeamMembers(rc *ctx.ReqCtx) {
teamMems := &[]entity.TeamMember{} condition := &entity.TeamMember{TeamId: uint64(ginx.PathParamInt(rc.GinCtx, "id"))}
rc.ResData = p.TeamApp.GetMemberPage(&entity.TeamMember{TeamId: uint64(ginx.PathParamInt(rc.GinCtx, "id"))}, condition.Username = rc.GinCtx.Query("username")
ginx.GetPageParam(rc.GinCtx), teamMems)
rc.ResData = p.TeamApp.GetMemberPage(condition, ginx.GetPageParam(rc.GinCtx), &[]vo.TeamMember{})
} }
// 保存团队信息 // 保存团队信息
func (p *Team) SaveTeamMember(rc *ctx.ReqCtx) { func (p *Team) SaveTeamMember(rc *ctx.ReqCtx) {
projectMem := &entity.TeamMember{} teamMems := &form.TeamMember{}
ginx.BindJsonAndValid(rc.GinCtx, projectMem) 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 // 校验账号并赋值username
account := &sys_entity.Account{} account := &sys_entity.Account{}
account.Id = projectMem.AccountId account.Id = accountId
biz.ErrIsNil(p.AccountApp.GetAccount(account, "Id", "Username"), "账号不存在") biz.ErrIsNil(p.AccountApp.GetAccount(account, "Id", "Username"), "账号不存在")
projectMem.Username = account.Username
projectMem.SetBaseInfo(rc.LoginAccount) teamMember := new(entity.TeamMember)
p.TeamApp.SaveMember(projectMem) 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) SaveMember(projectTeamMember *entity.TeamMember)
DeleteMember(teamId, accountId uint64) DeleteMember(teamId, accountId uint64)
IsExistMember(teamId, accounId uint64) bool
// 账号是否有权限访问该项目关联的资源信息 // 账号是否有权限访问该项目关联的资源信息
// CanAccess(accountId, projectId uint64) error // 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 { func (p *projectTeamAppImpl) GetMemberPage(condition *entity.TeamMember, pageParam *model.PageParam, toEntity interface{}) *model.PageResult {
return p.projectTeamMemberRepo.GetPageList(condition, pageParam, toEntity, orderBy...) 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}) 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 { func (p *projectTeamAppImpl) ListTagIds(teamId uint64) []uint64 {

View File

@@ -12,7 +12,7 @@ type TeamMember interface {
Save(mp *entity.TeamMember) 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) DeleteBy(condition *entity.TeamMember)

View File

@@ -27,6 +27,6 @@ func (p *tagTreeTeamRepoImpl) DeleteBy(condition *entity.TagTreeTeam) {
func (p *tagTreeTeamRepoImpl) SelectTagPathsByAccountId(accountId uint64) []string { func (p *tagTreeTeamRepoImpl) SelectTagPathsByAccountId(accountId uint64) []string {
var res []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 return res
} }

View File

@@ -1,6 +1,7 @@
package persistence package persistence
import ( import (
"fmt"
"mayfly-go/internal/tag/domain/entity" "mayfly-go/internal/tag/domain/entity"
"mayfly-go/internal/tag/domain/repository" "mayfly-go/internal/tag/domain/repository"
"mayfly-go/pkg/biz" "mayfly-go/pkg/biz"
@@ -21,8 +22,20 @@ func (p *teamMemberRepoImpl) Save(pm *entity.TeamMember) {
biz.ErrIsNilAppendErr(model.Insert(pm), "保存团队成员失败:%s") biz.ErrIsNilAppendErr(model.Insert(pm), "保存团队成员失败:%s")
} }
func (p *teamMemberRepoImpl) GetPageList(condition *entity.TeamMember, pageParam *model.PageParam, toEntity interface{}, orderBy ...string) *model.PageResult { func (p *teamMemberRepoImpl) GetPageList(condition *entity.TeamMember, pageParam *model.PageParam, toEntity interface{}) *model.PageResult {
return model.GetPage(pageParam, condition, condition, toEntity, orderBy...) 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) { func (p *teamMemberRepoImpl) DeleteBy(condition *entity.TeamMember) {

View File

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