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,37 +35,40 @@ export default defineComponent({
return ['tree', 'code', 'form', 'text', 'view']; return ['tree', 'code', 'form', 'text', 'view'];
}, },
}, },
}, })
setup(props: any, { emit }) {
let { modelValue, options, modeList, currentMode } = toRefs(props);
const jsoneditorVue = ref(null) //定义事件
// 编辑器实例 const emit = defineEmits(['update:visible', 'update:modelValue', 'onChange'])
let editor = null as any;
// 值类型
let valueType = 'string';
// 是否内部改变(即onChange事件双向绑定)内部改变则不需要重新赋值给editor
let internalChange = false;
const state = reactive({ let { modelValue, options, modeList, currentMode } = toRefs(props);
const jsoneditorVue = ref(null)
// 编辑器实例
let editor = null as any;
// 值类型
let valueType = 'string';
// 是否内部改变(即onChange事件双向绑定)内部改变则不需要重新赋值给editor
let internalChange = false;
const state = reactive({
height: '500px', height: '500px',
width: 'auto', width: 'auto',
}); });
onMounted(() => { onMounted(() => {
state.width = props.width; state.width = props.width;
state.height = props.height; state.height = props.height;
init(); init();
setJson(modelValue.value); setJson(modelValue!.value);
}); });
onUnmounted(() => { onUnmounted(() => {
editor?.destroy(); editor?.destroy();
editor = null; editor = null;
}); });
watch( watch(
() => props.modelValue, () => props.modelValue,
(newValue) => { (newValue) => {
if (!editor) { if (!editor) {
@@ -76,9 +76,9 @@ export default defineComponent({
} }
setJson(newValue); setJson(newValue);
} }
); );
const setJson = (value: any) => { const setJson = (value: any) => {
if (internalChange) { if (internalChange) {
return; return;
} }
@@ -89,9 +89,9 @@ export default defineComponent({
valueType = 'object'; valueType = 'object';
editor.set(value); editor.set(value);
} }
}; };
const onChange = () => { const onChange = () => {
try { try {
const json = editor.get(); const json = editor.get();
if (valueType == 'string') { if (valueType == 'string') {
@@ -104,10 +104,10 @@ export default defineComponent({
nextTick(() => { nextTick(() => {
internalChange = false; internalChange = false;
}); });
} catch (error) {} } catch (error) { }
}; };
const init = () => { const init = () => {
console.log('init json editor'); console.log('init json editor');
const finalOptions = { const finalOptions = {
...options.value, ...options.value,
@@ -116,14 +116,7 @@ export default defineComponent({
onChange, onChange,
}; };
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,13 +35,10 @@ 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', const router = useRouter();
setup() { const store = useStore();
// const { proxy } = getCurrentInstance() as any; const state = reactive({
const router = useRouter();
const store = useStore();
const state = reactive({
topCardItemList: [ topCardItemList: [
{ {
title: 'Linux机器', title: 'Linux机器',
@@ -63,15 +61,19 @@ export default {
color: '#FEBB50', color: '#FEBB50',
}, },
], ],
}); });
// const {
const currentTime = computed(() => { topCardItemList,
} = toRefs(state)
//
const currentTime = computed(() => {
return formatAxis(new Date()); return formatAxis(new Date());
}); });
// //
const initNumCountUp = async () => { const initNumCountUp = async () => {
const res: any = await indexApi.getIndexCount.request(); const res: any = await indexApi.getIndexCount.request();
nextTick(() => { nextTick(() => {
new CountUp('mongoNum', res.mongoNum).start(); new CountUp('mongoNum', res.mongoNum).start();
@@ -79,9 +81,9 @@ export default {
new CountUp('dbNum', res.dbNum).start(); new CountUp('dbNum', res.dbNum).start();
new CountUp('redisNum', res.redisNum).start(); new CountUp('redisNum', res.redisNum).start();
}); });
}; };
const toPage = (item: any) => { const toPage = (item: any) => {
switch (item.id) { switch (item.id) {
case 'personal': { case 'personal': {
router.push('/personal'); router.push('/personal');
@@ -104,33 +106,25 @@ export default {
break; break;
} }
} }
}; };
// //
onMounted(() => { onMounted(() => {
initNumCountUp(); initNumCountUp();
// initHomeLaboratory(); // initHomeLaboratory();
// initHomeOvertime(); // initHomeOvertime();
}); });
// vuex // vuex
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,17 +72,20 @@ 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' }],
const store = useStore(); captcha: [{ required: true, message: '请输入验证码', trigger: 'blur' }],
const route = useRoute(); }
const router = useRouter();
const loginFormRef: any = ref(null);
const changePwdFormRef: any = ref(null);
const state = reactive({ const store = useStore();
useLoginCaptcha: false, const route = useRoute();
const router = useRouter();
const loginFormRef: any = ref(null);
const changePwdFormRef: any = ref(null);
const state = reactive({
isUseLoginCaptcha: false,
captchaImage: '', captchaImage: '',
loginForm: { loginForm: {
username: '', username: '',
@@ -121,42 +111,45 @@ export default defineComponent({
], ],
}, },
}, },
rules: {
username: [{ required: true, message: '请输入用户名', trigger: 'blur' }],
password: [{ required: true, message: '请输入密码', trigger: 'blur' }],
captcha: [{ required: true, message: '请输入验证码', trigger: 'blur' }],
},
loading: { loading: {
signIn: false, signIn: false,
changePwd: false, changePwd: false,
}, },
}); });
onMounted(async () => { const {
isUseLoginCaptcha,
captchaImage,
loginForm,
changePwdDialog,
loading,
} = toRefs(state)
onMounted(async () => {
nextTick(async () => { nextTick(async () => {
state.useLoginCaptcha = await useLoginCaptcha(); state.isUseLoginCaptcha = await useLoginCaptcha();
getCaptcha(); getCaptcha();
}); });
// 移除公钥, 方便后续重新获取 // 移除公钥, 方便后续重新获取
sessionStorage.removeItem('RsaPublicKey'); sessionStorage.removeItem('RsaPublicKey');
}); });
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();
state.captchaImage = res.base64Captcha; state.captchaImage = res.base64Captcha;
state.loginForm.cid = res.cid; state.loginForm.cid = res.cid;
}; };
// 时间获取 // 时间获取
const currentTime = computed(() => { const currentTime = computed(() => {
return formatAxis(new Date()); return formatAxis(new Date());
}); });
// 校验登录表单并登录 // 校验登录表单并登录
const login = () => { const login = () => {
loginFormRef.value.validate((valid: boolean) => { loginFormRef.value.validate((valid: boolean) => {
if (valid) { if (valid) {
onSignIn(); onSignIn();
@@ -164,10 +157,10 @@ export default defineComponent({
return false; return false;
} }
}); });
}; };
// 登录 // 登录
const onSignIn = async () => { const onSignIn = async () => {
state.loading.signIn = true; state.loading.signIn = true;
let loginRes; let loginRes;
const originPwd = state.loginForm.password; const originPwd = state.loginForm.password;
@@ -221,10 +214,10 @@ export default defineComponent({
// 执行完 initBackEndControlRoutesFun再执行 signInSuccess // 执行完 initBackEndControlRoutesFun再执行 signInSuccess
signInSuccess(); signInSuccess();
} }
}; };
// 登录成功后的跳转 // 登录成功后的跳转
const signInSuccess = () => { const signInSuccess = () => {
// 初始化登录成功时间问候语 // 初始化登录成功时间问候语
let currentTimeInfo = currentTime.value; let currentTimeInfo = currentTime.value;
// 登录成功,跳到转首页 // 登录成功,跳到转首页
@@ -240,9 +233,9 @@ export default defineComponent({
setUseWatermark2Session(true); setUseWatermark2Session(true);
} }
}, 300); }, 300);
}; };
const changePwd = () => { const changePwd = () => {
changePwdFormRef.value.validate(async (valid: boolean) => { changePwdFormRef.value.validate(async (valid: boolean) => {
if (!valid) { if (!valid) {
return false; return false;
@@ -262,37 +255,26 @@ export default defineComponent({
state.loading.changePwd = false; state.loading.changePwd = false;
} }
}); });
}; };
const cancelChangePwd = () => { const cancelChangePwd = () => {
state.changePwdDialog.visible = false; state.changePwdDialog.visible = false;
state.changePwdDialog.form.newPassword = ''; state.changePwdDialog.form.newPassword = '';
state.changePwdDialog.form.oldPassword = ''; state.changePwdDialog.form.oldPassword = '';
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,33 +31,30 @@
</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', const store = useStore();
components: { Account }, const state = reactive({
setup() {
const store = useStore();
const state = reactive({
tabsActiveName: 'account', tabsActiveName: 'account',
isTabPaneShow: true, isTabPaneShow: true,
}); });
// 获取布局配置信息
const getThemeConfig = computed(() => { const {
isTabPaneShow,
tabsActiveName,
} = toRefs(state)
// 获取布局配置信息
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>
@@ -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,35 +22,41 @@
</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 state = reactive({ //定义事件
const emit = defineEmits(['changeTag', 'update:tagId', 'update:tagPath'])
const state = reactive({
tags: [], tags: [],
// 单选则为id多选为id数组 // 单选则为id多选为id数组
selectTags: null as any, selectTags: null as any,
}); });
onMounted(async () => { const {
tags,
selectTags,
} = toRefs(state)
onMounted(async () => {
if (props.tagId) { if (props.tagId) {
state.selectTags = props.tagId; state.selectTags = props.tagId;
} }
state.tags = await tagApi.getTagTrees.request(null); state.tags = await tagApi.getTagTrees.request(null);
}); });
const changeTag = (tag: any, checkInfo: any) => { const changeTag = (tag: any, checkInfo: any) => {
if (checkInfo.checkedNodes.length > 0) { if (checkInfo.checkedNodes.length > 0) {
emit('update:tagId', tag.id); emit('update:tagId', tag.id);
emit('update:tagPath', tag.codePath); emit('update:tagPath', tag.codePath);
@@ -68,14 +65,8 @@ export default defineComponent({
emit('update:tagId', null); emit('update:tagId', null);
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,12 +149,15 @@ export default defineComponent({
db: { db: {
type: String, type: String,
} }
}, })
setup(props: any, { emit }) {
const formRef: any = ref();
const { proxy } = getCurrentInstance() as any;
const state = reactive({ //定义事件
const emit = defineEmits(['update:visible', 'cancel', 'val-change'])
const formRef: any = ref();
const { proxy } = getCurrentInstance() as any;
const state = reactive({
dialogVisible: false, dialogVisible: false,
btnloading: false, btnloading: false,
activeName: '1', activeName: '1',
@@ -229,7 +246,7 @@ export default defineComponent({
label: '操作', label: '操作',
}, },
], ],
columns: [{name: '', remark:''}], columns: [{ name: '', remark: '' }],
res: [ res: [
{ {
indexName: '', indexName: '',
@@ -246,16 +263,29 @@ export default defineComponent({
tableComment: '', tableComment: '',
height: 550 height: 550
}, },
}); });
watch(props, async (newValue) => { const {
dialogVisible,
btnloading,
activeName,
columnTypeList,
indexTypeList,
characterSetNameList,
collationNameList,
tableData,
} = toRefs(state)
watch(props, async (newValue) => {
state.dialogVisible = newValue.visible; 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: '',
type: '', type: '',
@@ -266,8 +296,9 @@ export default defineComponent({
auto_increment: false, auto_increment: false,
remark: '', remark: '',
}); });
}; };
const addIndex = () => {
const addIndex = () => {
state.tableData.indexs.res.push({ state.tableData.indexs.res.push({
indexName: '', indexName: '',
columnNames: [], columnNames: [],
@@ -275,27 +306,31 @@ export default defineComponent({
indexType: 'BTREE', indexType: 'BTREE',
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' },
{name: 'creator_id', type: 'bigint', length: '20', value: '', notNull: true, pri: false, auto_increment: false, remark: '创建人id'}, { name: 'creator_id', type: 'bigint', length: '20', value: '', notNull: true, pri: false, auto_increment: false, remark: '创建人id' },
{name: 'creator', type: 'varchar', length: '100', value: '', notNull: true, pri: false, auto_increment: false, remark: '创建人姓名'}, { name: 'creator', type: 'varchar', length: '100', value: '', notNull: true, pri: false, auto_increment: false, remark: '创建人姓名' },
{name: 'creat_time', type: 'datetime', length: '', value: '', notNull: true, pri: false, auto_increment: false, remark: '创建时间'}, { name: 'creat_time', type: 'datetime', length: '', value: '', notNull: true, pri: false, auto_increment: false, remark: '创建时间' },
{name: 'updater_id', type: 'bigint', length: '20', value: '', notNull: true, pri: false, auto_increment: false, remark: '修改人id'}, { name: 'updater_id', type: 'bigint', length: '20', value: '', notNull: true, pri: false, auto_increment: false, remark: '修改人id' },
{name: 'updater', type: 'varchar', length: '100', value: '', notNull: true, pri: false, auto_increment: false, remark: '修改人姓名'}, { name: 'updater', type: 'varchar', length: '100', value: '', notNull: true, pri: false, auto_increment: false, remark: '修改人姓名' },
{name: 'update_time', type: 'datetime', length: '', value: '', notNull: true, pri: false, auto_increment: false, remark: '修改时间'}, { name: '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) {
ElMessage.warning('没有更改'); ElMessage.warning('没有更改');
return; return;
} }
@@ -309,56 +344,56 @@ export default defineComponent({
// cancel(); // cancel();
}, },
}); });
}; };
/** /**
* 对比两个数组,取出被修改过的对象数组 * 对比两个数组,取出被修改过的对象数组
* @param oldArr 原对象数组 * @param oldArr 原对象数组
* @param nowArr 修改后的对象数组 * @param nowArr 修改后的对象数组
* @param key 标志对象唯一属性 * @param key 标志对象唯一属性
*/ */
const filterChangedData = (oldArr: object[], nowArr: object[], key: string) : { del: any[], add: any[], upd: any[] } => { const filterChangedData = (oldArr: object[], nowArr: object[], key: string): { del: any[], add: any[], upd: any[] } => {
let data = { let data = {
del: [] as object[], // 删除的数据 del: [] as object[], // 删除的数据
add: [] as object[], // 新增的数据 add: [] as object[], // 新增的数据
upd:[] as object[] // 修改的数据 upd: [] as object[] // 修改的数据
} }
// 旧数据为空 // 旧数据为空
if(oldArr && Array.isArray(oldArr) && oldArr.length===0 if (oldArr && Array.isArray(oldArr) && oldArr.length === 0
&& nowArr && Array.isArray(nowArr) && nowArr.length>0 ){ && nowArr && Array.isArray(nowArr) && nowArr.length > 0) {
data.add = nowArr; data.add = nowArr;
return data; return data;
} }
// 新数据为空 // 新数据为空
if(nowArr && Array.isArray(nowArr) && nowArr.length===0 if (nowArr && Array.isArray(nowArr) && nowArr.length === 0
&& oldArr && Array.isArray(oldArr) && oldArr.length>0 ){ && oldArr && Array.isArray(oldArr) && oldArr.length > 0) {
data.del = oldArr; data.del = oldArr;
return data; return data;
} }
let oldMap= {}, newMap = {}; let oldMap = {}, newMap = {};
oldArr.forEach(a => oldMap[a[key]] = a) oldArr.forEach(a => oldMap[a[key]] = a)
nowArr.forEach(a => { nowArr.forEach(a => {
let k = a[key] let k = a[key]
newMap[k] = a; newMap[k] = a;
if(!oldMap.hasOwnProperty(k)){// 新增 if (!oldMap.hasOwnProperty(k)) {// 新增
data.add.push(a) data.add.push(a)
} }
}) })
oldArr.forEach(a=>{ oldArr.forEach(a => {
let k = a[key]; let k = a[key];
let newData = newMap[k]; let newData = newMap[k];
if(!newData){ // 删除 if (!newData) { // 删除
data.del.push(a) data.del.push(a)
}else{ // 判断每个字段是否相等,否则为修改 } else { // 判断每个字段是否相等,否则为修改
for(let f in a){ for (let f in a) {
let oldV = a[f] let oldV = a[f]
let newV = newData[f] let newV = newData[f]
if(oldV.toString() !== newV.toString()){ if (oldV.toString() !== newV.toString()) {
data.upd.push(newData) data.upd.push(newData)
break; break;
} }
@@ -366,21 +401,21 @@ export default defineComponent({
} }
}) })
return data; return data;
} }
const genSql = () => { const genSql = () => {
const genColumnBasicSql = (cl: any) => { const genColumnBasicSql = (cl: any) => {
let val = cl.value ? (cl.value === 'CURRENT_TIMESTAMP' ? cl.value : '\'' + cl.value + '\'') : ''; let val = cl.value ? (cl.value === 'CURRENT_TIMESTAMP' ? cl.value : '\'' + cl.value + '\'') : '';
let defVal = `${val ? ('DEFAULT ' + val) :''}`; let defVal = `${val ? ('DEFAULT ' + val) : ''}`;
let length = cl.length?`(${cl.length})`:''; let length = cl.length ? `(${cl.length})` : '';
return ` ${cl.name} ${cl.type}${length} ${cl.notNull?'NOT NULL':'NULL'} ${cl.auto_increment?'AUTO_INCREMENT':''} ${defVal} comment '${cl.remark||''}' ` return ` ${cl.name} ${cl.type}${length} ${cl.notNull ? 'NOT NULL' : 'NULL'} ${cl.auto_increment ? 'AUTO_INCREMENT' : ''} ${defVal} comment '${cl.remark || ''}' `
} }
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[] = [];
data.fields.res.forEach((item) => { data.fields.res.forEach((item) => {
@@ -395,43 +430,43 @@ export default defineComponent({
${primary_key ? `, PRIMARY KEY (${primary_key.slice(0, -1)})` : ''} ${primary_key ? `, PRIMARY KEY (${primary_key.slice(0, -1)})` : ''}
) ENGINE=InnoDB DEFAULT CHARSET=${data.characterSet} COLLATE =${data.collation} COMMENT='${data.tableComment}';`; ) ENGINE=InnoDB DEFAULT CHARSET=${data.characterSet} COLLATE =${data.collation} COMMENT='${data.tableComment}';`;
} else if (state.activeName === '2' && data.indexs.res.length > 0){ // 创建索引 } else if (state.activeName === '2' && data.indexs.res.length > 0) { // 创建索引
let sql = `ALTER TABLE ${data.tableName}`; let sql = `ALTER TABLE ${data.tableName}`;
state.tableData.indexs.res.forEach(a=>{ state.tableData.indexs.res.forEach(a => {
sql += ` ADD ${a.unique?'UNIQUE':''} INDEX ${a.indexName}(${a.columnNames.join(',')}) USING ${a.indexType} COMMENT '${a.indexComment}',`; sql += ` ADD ${a.unique ? 'UNIQUE' : ''} INDEX ${a.indexName}(${a.columnNames.join(',')}) USING ${a.indexType} COMMENT '${a.indexComment}',`;
}) })
return sql.substring(0, sql.length - 1) + ';' return sql.substring(0, sql.length - 1) + ';'
} }
} else { // 修改 } else { // 修改
let addSql = '', updSql = '', delSql = ''; let addSql = '', updSql = '', delSql = '';
if(state.activeName === '1'){// 修改列 if (state.activeName === '1') {// 修改列
let changeData = filterChangedData(oldData.fields, state.tableData.fields.res, 'name') let changeData = filterChangedData(oldData.fields, state.tableData.fields.res, 'name')
if(changeData.add.length > 0){ if (changeData.add.length > 0) {
addSql = `ALTER TABLE ${data.tableName}` addSql = `ALTER TABLE ${data.tableName}`
changeData.add.forEach(a=>{ changeData.add.forEach(a => {
addSql += ` ADD ${genColumnBasicSql(a)},` addSql += ` ADD ${genColumnBasicSql(a)},`
}) })
addSql = addSql.substring(0, addSql.length - 1) addSql = addSql.substring(0, addSql.length - 1)
addSql +=';' addSql += ';'
} }
if(changeData.upd.length > 0){ if (changeData.upd.length > 0) {
updSql = `ALTER TABLE ${data.tableName}`; updSql = `ALTER TABLE ${data.tableName}`;
changeData.upd.forEach(a=>{ changeData.upd.forEach(a => {
updSql += ` MODIFY ${genColumnBasicSql(a)},` updSql += ` MODIFY ${genColumnBasicSql(a)},`
}) })
updSql = updSql.substring(0, updSql.length - 1) updSql = updSql.substring(0, updSql.length - 1)
updSql +=';' updSql += ';'
} }
if(changeData.del.length > 0){ if (changeData.del.length > 0) {
changeData.del.forEach(a=>{ changeData.del.forEach(a => {
delSql += ` ALTER TABLE ${data.tableName} DROP COLUMN ${a.name}; ` delSql += ` ALTER TABLE ${data.tableName} DROP COLUMN ${a.name}; `
}) })
} }
return addSql + updSql + delSql; return addSql + updSql + delSql;
} else if (state.activeName === '2'){ // 修改索引 } else if (state.activeName === '2') { // 修改索引
let changeData = filterChangedData(oldData.indexs, state.tableData.indexs.res, 'indexName') let changeData = filterChangedData(oldData.indexs, state.tableData.indexs.res, 'indexName')
// 搜集修改和删除的索引添加到drop index xx // 搜集修改和删除的索引添加到drop index xx
// 收集新增和修改的索引添加到ADD xx // 收集新增和修改的索引添加到ADD xx
@@ -444,29 +479,29 @@ export default defineComponent({
let dropIndexNames: string[] = []; let dropIndexNames: string[] = [];
let addIndexs: any[] = []; let addIndexs: any[] = [];
if(changeData.upd.length > 0){ if (changeData.upd.length > 0) {
changeData.upd.forEach(a=>{ changeData.upd.forEach(a => {
dropIndexNames.push(a.indexName) dropIndexNames.push(a.indexName)
addIndexs.push(a) addIndexs.push(a)
}) })
} }
if(changeData.del.length > 0){ if (changeData.del.length > 0) {
changeData.del.forEach(a=>{ changeData.del.forEach(a => {
dropIndexNames.push(a.indexName) dropIndexNames.push(a.indexName)
}) })
} }
if(changeData.add.length > 0){ if (changeData.add.length > 0) {
changeData.add.forEach(a=>{ changeData.add.forEach(a => {
addIndexs.push(a) addIndexs.push(a)
}) })
} }
if(dropIndexNames.length > 0 || addIndexs.length > 0){ if (dropIndexNames.length > 0 || addIndexs.length > 0) {
let sql = `ALTER TABLE ${data.tableName} `; let sql = `ALTER TABLE ${data.tableName} `;
if(dropIndexNames.length > 0){ if (dropIndexNames.length > 0) {
dropIndexNames.forEach(a=>{ dropIndexNames.forEach(a => {
sql += `DROP INDEX ${a},` sql += `DROP INDEX ${a},`
}) })
sql = sql.substring(0, sql.length - 1) sql = sql.substring(0, sql.length - 1)
@@ -485,9 +520,9 @@ export default defineComponent({
} }
} }
} }
}; };
const reset = () => { const reset = () => {
state.activeName = '1' state.activeName = '1'
formRef.value.resetFields() formRef.value.resetFields()
state.tableData.tableName = '' state.tableData.tableName = ''
@@ -511,24 +546,24 @@ export default defineComponent({
indexType: 'BTREE', indexType: 'BTREE',
indexComment: '', indexComment: '',
},] },]
}; };
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
state.tableData.tableComment = row.tableComment state.tableData.tableComment = row.tableComment
// 回显列 // 回显列
if(columns && Array.isArray(columns) && columns.length > 0){ if (columns && Array.isArray(columns) && columns.length > 0) {
oldData.fields = []; oldData.fields = [];
state.tableData.fields.res = []; state.tableData.fields.res = [];
// 索引列下拉选 // 索引列下拉选
state.tableData.indexs.columns = []; state.tableData.indexs.columns = [];
columns.forEach(a=>{ columns.forEach(a => {
let typeObj = a.columnType.replace(')','').split('(') let typeObj = a.columnType.replace(')', '').split('(')
let type = typeObj[0]; let type = typeObj[0];
let length = typeObj.length > 1&& typeObj[1] || ''; let length = typeObj.length > 1 && typeObj[1] || '';
let data = { let data = {
name: a.columnName, name: a.columnName,
type, type,
@@ -542,15 +577,15 @@ export default defineComponent({
state.tableData.fields.res.push(data) state.tableData.fields.res.push(data)
oldData.fields.push(JSON.parse(JSON.stringify(data))) oldData.fields.push(JSON.parse(JSON.stringify(data)))
// 索引字段下拉选项 // 索引字段下拉选项
state.tableData.indexs.columns.push({name: a.columnName, remark: a.columnComment}) state.tableData.indexs.columns.push({ name: a.columnName, remark: a.columnComment })
}) })
} }
// 回显索引 // 回显索引
if(indexs && Array.isArray(indexs) && indexs.length > 0){ if (indexs && Array.isArray(indexs) && indexs.length > 0) {
oldData.indexs = []; oldData.indexs = [];
state.tableData.indexs.res = []; state.tableData.indexs.res = [];
// 索引过滤掉主键 // 索引过滤掉主键
indexs.filter(a=>a.indexName!=="PRIMARY").forEach(a=>{ indexs.filter(a => a.indexName !== "PRIMARY").forEach(a => {
let data = { let data = {
indexName: a.indexName, indexName: a.indexName,
columnNames: a.columnName?.split(','), columnNames: a.columnName?.split(','),
@@ -562,21 +597,6 @@ export default defineComponent({
oldData.indexs.push(JSON.parse(JSON.stringify(data))) oldData.indexs.push(JSON.parse(JSON.stringify(data)))
}) })
} }
}) })
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,61 +164,92 @@ export default defineComponent({
trigger: ['change', 'blur'], trigger: ['change', 'blur'],
}, },
], ],
}, }
});
watch(props, (newValue) => { const dbForm: any = ref(null);
const state = reactive({
dialogVisible: false,
allDatabases: [] as any,
databaseList: [] as any,
sshTunnelMachineList: [] as any,
form: {
id: null,
tagId: null as any,
tagPath: null as any,
type: null,
name: null,
host: '',
port: 3306,
username: null,
password: null,
params: null,
database: '',
project: null,
projectId: null,
envId: null,
env: null,
remark: '',
enableSshTunnel: null,
sshTunnelMachineId: null,
},
// 原密码
pwd: '',
btnLoading: false,
});
const {
dialogVisible,
allDatabases,
databaseList,
sshTunnelMachineList,
form,
pwd,
btnLoading,
} = toRefs(state)
watch(props, (newValue: any) => {
state.dialogVisible = newValue.visible; 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 = [];
} }
getSshTunnelMachines(); getSshTunnelMachines();
}); });
/** /**
* 改变表单中的数据库字段,方便表单错误提示。如全部删光,可提示请添加数据库 * 改变表单中的数据库字段,方便表单错误提示。如全部删光,可提示请添加数据库
*/ */
const changeDatabase = () => { const changeDatabase = () => {
state.form.database = state.databaseList.length == 0 ? '' : state.databaseList.join(' '); state.form.database = state.databaseList.length == 0 ? '' : state.databaseList.join(' ');
}; };
const getSshTunnelMachines = async () => { const getSshTunnelMachines = async () => {
if (state.form.enableSshTunnel == 1 && state.sshTunnelMachineList.length == 0) { if (state.form.enableSshTunnel == 1 && state.sshTunnelMachineList.length == 0) {
const res = await machineApi.list.request({ pageNum: 1, pageSize: 100 }); const res = await machineApi.list.request({ pageNum: 1, pageSize: 100 });
state.sshTunnelMachineList = res.list; state.sshTunnelMachineList = res.list;
} }
}; };
const changeEnv = (envId: number) => { const getAllDatabase = async () => {
for (let p of state.envs as any) {
if (p.id == envId) {
state.form.env = p.name;
}
}
};
const getAllDatabase = async () => {
const reqForm = { ...state.form }; const reqForm = { ...state.form };
reqForm.password = await RsaEncrypt(reqForm.password); reqForm.password = await RsaEncrypt(reqForm.password);
state.allDatabases = await dbApi.getAllDatabase.request(reqForm); state.allDatabases = await dbApi.getAllDatabase.request(reqForm);
ElMessage.success('获取成功, 请选择需要管理操作的数据库'); ElMessage.success('获取成功, 请选择需要管理操作的数据库');
}; };
const getDbPwd = async () => { const getDbPwd = async () => {
state.pwd = await dbApi.getDbPwd.request({ id: state.form.id }); state.pwd = await dbApi.getDbPwd.request({ id: state.form.id });
}; };
const btnOk = async () => { const btnOk = async () => {
if (!state.form.id) { if (!state.form.id) {
notBlank(state.form.password, '新增操作,密码不可为空'); notBlank(state.form.password, '新增操作,密码不可为空');
} }
@@ -295,34 +272,21 @@ export default defineComponent({
return false; return false;
} }
}); });
}; };
const resetInputDb = () => { const resetInputDb = () => {
state.databaseList = []; state.databaseList = [];
state.allDatabases = []; state.allDatabases = [];
}; };
const cancel = () => { const cancel = () => {
emit('update:visible', false); emit('update:visible', false);
emit('cancel'); emit('cancel');
setTimeout(() => { setTimeout(() => {
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>
@@ -79,7 +82,7 @@
<el-row class="mb10"> <el-row class="mb10">
<el-popover v-model:visible="showDumpInfo" :width="470" placement="right" trigger="click"> <el-popover v-model:visible="showDumpInfo" :width="470" placement="right" trigger="click">
<template #reference> <template #reference>
<el-button class="ml5" type="success" size="small" >导出</el-button> <el-button class="ml5" type="success" size="small">导出</el-button>
</template> </template>
<el-form-item label="导出内容: "> <el-form-item label="导出内容: ">
<el-radio-group v-model="dumpInfo.type"> <el-radio-group v-model="dumpInfo.type">
@@ -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';
@@ -272,28 +261,21 @@ import { getSession } from '@/common/utils/storage';
import { isTrue } from '@/common/assert'; import { isTrue } from '@/common/assert';
import { Search as SearchIcon } from '@element-plus/icons-vue' import { Search as SearchIcon } from '@element-plus/icons-vue'
import router from '@/router'; import router from '@/router';
import {store} from '@/store'; 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, const state = reactive({
},
setup() {
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,
/** /**
* 选中的数据 * 选中的数据
*/ */
@@ -303,7 +285,7 @@ export default defineComponent({
*/ */
query: { query: {
tagPath: null, tagPath: null,
projectId:null, projectId: null,
pageNum: 1, pageNum: 1,
pageSize: 10, pageSize: 10,
}, },
@@ -358,11 +340,11 @@ export default defineComponent({
}, },
dbEditDialog: { dbEditDialog: {
visible: false, visible: false,
data: null, data: null as any,
title: '新增数据库', title: '新增数据库',
}, },
tableCreateDialog: { tableCreateDialog: {
title:'创建表', title: '创建表',
visible: false, visible: false,
activeName: '1', activeName: '1',
data: { // 修改表时,传递修改数据 data: { // 修改表时,传递修改数据
@@ -372,18 +354,41 @@ export default defineComponent({
columns: [] columns: []
}, },
}, },
filterDb:{ filterDb: {
param:'', param: '',
cache:[], cache: [],
list:[], list: [],
} }
}); });
onMounted(async () => { const {
dbId,
db,
tags,
chooseId,
query,
datas,
total,
showDumpInfo,
dumpInfo,
sqlExecLogDialog,
rollbackSqlDialog,
chooseTableName,
tableInfoDialog,
columnDialog,
indexDialog,
ddlDialog,
dbEditDialog,
tableCreateDialog,
filterDb,
} = toRefs(state)
onMounted(async () => {
search(); search();
}); });
const filterTableInfos = computed(() => { const filterTableInfos = computed(() => {
const infos = state.tableInfoDialog.infos; const infos = state.tableInfoDialog.infos;
const tableNameSearch = state.tableInfoDialog.tableNameSearch; const tableNameSearch = state.tableInfoDialog.tableNameSearch;
const tableCommentSearch = state.tableInfoDialog.tableCommentSearch; const tableCommentSearch = state.tableInfoDialog.tableCommentSearch;
@@ -401,17 +406,17 @@ export default defineComponent({
} }
return tnMatch && tcMatch; return tnMatch && tcMatch;
}); });
}); });
const choose = (item: any) => { const choose = (item: any) => {
if (!item) { if (!item) {
return; return;
} }
state.chooseId = item.id; state.chooseId = item.id;
state.chooseData = item; state.chooseData = item;
}; };
const search = async () => { const search = async () => {
let res: any = await dbApi.dbs.request(state.query); let res: any = await dbApi.dbs.request(state.query);
// 切割数据库 // 切割数据库
res.list.forEach((e: any) => { res.list.forEach((e: any) => {
@@ -420,18 +425,18 @@ export default defineComponent({
}); });
state.datas = res.list; state.datas = res.list;
state.total = res.total; state.total = res.total;
}; };
const handlePageChange = (curPage: number) => { const handlePageChange = (curPage: number) => {
state.query.pageNum = curPage; state.query.pageNum = curPage;
search(); search();
}; };
const getTags = async () => { const getTags = async () => {
state.tags = await tagApi.getAccountTags.request(null); state.tags = await tagApi.getAccountTags.request(null);
}; };
const editDb = async (isAdd = false) => { const editDb = async (isAdd = false) => {
if (isAdd) { if (isAdd) {
state.dbEditDialog.data = null; state.dbEditDialog.data = null;
state.dbEditDialog.title = '新增数据库资源'; state.dbEditDialog.title = '新增数据库资源';
@@ -440,15 +445,15 @@ export default defineComponent({
state.dbEditDialog.title = '修改数据库资源'; state.dbEditDialog.title = '修改数据库资源';
} }
state.dbEditDialog.visible = true; state.dbEditDialog.visible = true;
}; };
const valChange = () => { const valChange = () => {
state.chooseData = null; state.chooseData = null;
state.chooseId = null; state.chooseId = null;
search(); search();
}; };
const deleteDb = async (id: number) => { const deleteDb = async (id: number) => {
try { try {
await ElMessageBox.confirm(`确定删除该库?`, '提示', { await ElMessageBox.confirm(`确定删除该库?`, '提示', {
confirmButtonText: '确定', confirmButtonText: '确定',
@@ -460,18 +465,18 @@ export default defineComponent({
state.chooseData = null; state.chooseData = null;
state.chooseId = null; state.chooseId = null;
search(); search();
} catch (err) {} } catch (err) { }
}; };
const onShowSqlExec = async (row: any) => { const onShowSqlExec = async (row: any) => {
state.sqlExecLogDialog.title = `${row.name}[${row.host}:${row.port}]`; state.sqlExecLogDialog.title = `${row.name}[${row.host}:${row.port}]`;
state.sqlExecLogDialog.query.dbId = row.id; state.sqlExecLogDialog.query.dbId = row.id;
state.sqlExecLogDialog.dbs = row.database.split(' '); state.sqlExecLogDialog.dbs = row.database.split(' ');
searchSqlExecLog(); searchSqlExecLog();
state.sqlExecLogDialog.visible = true; state.sqlExecLogDialog.visible = true;
}; };
const onBeforeCloseSqlExecDialog = () => { const onBeforeCloseSqlExecDialog = () => {
state.sqlExecLogDialog.visible = false; state.sqlExecLogDialog.visible = false;
state.sqlExecLogDialog.data = []; state.sqlExecLogDialog.data = [];
state.sqlExecLogDialog.dbs = []; state.sqlExecLogDialog.dbs = [];
@@ -481,30 +486,30 @@ export default defineComponent({
state.sqlExecLogDialog.query.table = ''; state.sqlExecLogDialog.query.table = '';
state.sqlExecLogDialog.query.db = ''; state.sqlExecLogDialog.query.db = '';
state.sqlExecLogDialog.query.type = null; state.sqlExecLogDialog.query.type = null;
}; };
const searchSqlExecLog = async () => { const searchSqlExecLog = async () => {
const res = await dbApi.getSqlExecs.request(state.sqlExecLogDialog.query); const res = await dbApi.getSqlExecs.request(state.sqlExecLogDialog.query);
state.sqlExecLogDialog.data = res.list; state.sqlExecLogDialog.data = res.list;
state.sqlExecLogDialog.total = res.total; state.sqlExecLogDialog.total = res.total;
}; };
const handleSqlExecPageChange = (curPage: number) => { const handleSqlExecPageChange = (curPage: number) => {
state.sqlExecLogDialog.query.pageNum = curPage; state.sqlExecLogDialog.query.pageNum = curPage;
searchSqlExecLog(); searchSqlExecLog();
}; };
/** /**
* 选择导出数据库表 * 选择导出数据库表
*/ */
const handleDumpTableSelectionChange = (vals: any) => { const handleDumpTableSelectionChange = (vals: any) => {
state.dumpInfo.tables = vals.map((x: any) => x.tableName); state.dumpInfo.tables = vals.map((x: any) => x.tableName);
}; };
/** /**
* 数据库信息导出 * 数据库信息导出
*/ */
const dump = (db: string) => { const dump = (db: string) => {
isTrue(state.dumpInfo.tables.length > 0, '请选择要导出的表'); isTrue(state.dumpInfo.tables.length > 0, '请选择要导出的表');
const a = document.createElement('a'); const a = document.createElement('a');
a.setAttribute( a.setAttribute(
@@ -515,9 +520,9 @@ export default defineComponent({
); );
a.click(); a.click();
state.showDumpInfo = false; state.showDumpInfo = false;
}; };
const onShowRollbackSql = async (sqlExecLog: any) => { const onShowRollbackSql = async (sqlExecLog: any) => {
const columns = await dbApi.columnMetadata.request({ id: sqlExecLog.dbId, db: sqlExecLog.db, tableName: sqlExecLog.table }); const columns = await dbApi.columnMetadata.request({ id: sqlExecLog.dbId, db: sqlExecLog.db, tableName: sqlExecLog.table });
const primaryKey = columns[0].columnName; const primaryKey = columns[0].columnName;
const oldValue = JSON.parse(sqlExecLog.oldValue); const oldValue = JSON.parse(sqlExecLog.oldValue);
@@ -547,19 +552,19 @@ export default defineComponent({
state.rollbackSqlDialog.sql = rollbackSqls.join('\n'); state.rollbackSqlDialog.sql = rollbackSqls.join('\n');
state.rollbackSqlDialog.visible = true; state.rollbackSqlDialog.visible = true;
}; };
/** /**
* 包装值如果值类型为number则直接返回其他则需要使用''包装 * 包装值如果值类型为number则直接返回其他则需要使用''包装
*/ */
const wrapValue = (val: any) => { const wrapValue = (val: any) => {
if (typeof val == 'number') { if (typeof val == 'number') {
return val; return val;
} }
return `'${val}'`; return `'${val}'`;
}; };
const showTableInfo = async (row: any, db: string) => { const showTableInfo = async (row: any, db: string) => {
state.tableInfoDialog.loading = true; state.tableInfoDialog.loading = true;
state.tableInfoDialog.visible = true; state.tableInfoDialog.visible = true;
try { try {
@@ -572,19 +577,19 @@ export default defineComponent({
} finally { } finally {
state.tableInfoDialog.loading = false; state.tableInfoDialog.loading = false;
} }
}; };
const refreshTableInfo = async () => { const refreshTableInfo = async () => {
state.tableInfoDialog.infos = await dbApi.tableInfos.request({ id: state.dbId, db: state.db }); state.tableInfoDialog.infos = await dbApi.tableInfos.request({ id: state.dbId, db: state.db });
} }
const closeTableInfo = () => { const closeTableInfo = () => {
state.showDumpInfo = false; state.showDumpInfo = false;
state.tableInfoDialog.visible = false; state.tableInfoDialog.visible = false;
state.tableInfoDialog.infos = []; state.tableInfoDialog.infos = [];
}; };
const showColumns = async (row: any) => { const showColumns = async (row: any) => {
state.chooseTableName = row.tableName; state.chooseTableName = row.tableName;
state.columnDialog.columns = await dbApi.columnMetadata.request({ state.columnDialog.columns = await dbApi.columnMetadata.request({
id: state.chooseId, id: state.chooseId,
@@ -593,9 +598,9 @@ export default defineComponent({
}); });
state.columnDialog.visible = true; state.columnDialog.visible = true;
}; };
const showTableIndex = async (row: any) => { const showTableIndex = async (row: any) => {
state.chooseTableName = row.tableName; state.chooseTableName = row.tableName;
state.indexDialog.indexs = await dbApi.tableIndex.request({ state.indexDialog.indexs = await dbApi.tableIndex.request({
id: state.chooseId, id: state.chooseId,
@@ -604,9 +609,9 @@ export default defineComponent({
}); });
state.indexDialog.visible = true; state.indexDialog.visible = true;
}; };
const showCreateDdl = async (row: any) => { const showCreateDdl = async (row: any) => {
state.chooseTableName = row.tableName; state.chooseTableName = row.tableName;
const res = await dbApi.tableDdl.request({ const res = await dbApi.tableDdl.request({
id: state.chooseId, id: state.chooseId,
@@ -615,12 +620,12 @@ export default defineComponent({
}); });
state.ddlDialog.ddl = res[0]['Create Table']; state.ddlDialog.ddl = res[0]['Create Table'];
state.ddlDialog.visible = true; state.ddlDialog.visible = true;
}; };
/** /**
* 删除表 * 删除表
*/ */
const dropTable = async (row: any) => { const dropTable = async (row: any) => {
try { try {
const tableName = row.tableName; const tableName = row.tableName;
await ElMessageBox.confirm(`确定删除'${tableName}'表?`, '提示', { await ElMessageBox.confirm(`确定删除'${tableName}'表?`, '提示', {
@@ -636,13 +641,13 @@ export default defineComponent({
state.tableInfoDialog.infos = await dbApi.tableInfos.request({ id: state.chooseId, db: state.db }); state.tableInfoDialog.infos = await dbApi.tableInfos.request({ id: state.chooseId, db: state.db });
}, },
}); });
} catch (err) {} } catch (err) { }
}; };
const openSqlExec = (row: any, db: any) => { const openSqlExec = (row: any, db: any) => {
// 判断db是否发生改变 // 判断db是否发生改变
let oldDb = store.state.sqlExecInfo.dbOptInfo.db; let oldDb = store.state.sqlExecInfo.dbOptInfo.db;
if(db && oldDb !== db){ if (db && oldDb !== db) {
const {tagPath, id} = row; const { tagPath, id } = row;
let params = { let params = {
tagPath, tagPath,
dbId: id, dbId: id,
@@ -650,33 +655,33 @@ export default defineComponent({
} }
store.dispatch('sqlExecInfo/setSqlExecInfo', params); store.dispatch('sqlExecInfo/setSqlExecInfo', params);
} }
router.push({name: 'SqlExec'}); router.push({ name: 'SqlExec' });
} }
// 点击查看时初始化数据 // 点击查看时初始化数据
const selectDb = (row: any) => { const selectDb = (row: any) => {
state.filterDb.param = '' state.filterDb.param = ''
state.filterDb.cache = row; state.filterDb.cache = row;
state.filterDb.list = row; state.filterDb.list = row;
} }
// 输入字符过滤schema // 输入字符过滤schema
const filterSchema = () => { const filterSchema = () => {
if(state.filterDb.param){ if (state.filterDb.param) {
state.filterDb.list = state.filterDb.cache.filter((a)=>{return String(a).toLowerCase().indexOf(state.filterDb.param) > -1 }) state.filterDb.list = state.filterDb.cache.filter((a) => { return String(a).toLowerCase().indexOf(state.filterDb.param) > -1 })
}else{ } else {
state.filterDb.list = state.filterDb.cache; state.filterDb.list = state.filterDb.cache;
} }
} }
// 打开编辑表 // 打开编辑表
const openEditTable = async (row: any) => { const openEditTable = async (row: any) => {
state.tableCreateDialog.visible = true state.tableCreateDialog.visible = true
state.tableCreateDialog.activeName = '1' state.tableCreateDialog.activeName = '1'
if(row === false){ if (row === false) {
state.tableCreateDialog.data = {edit: false, row: {}, indexs: [], columns: []} state.tableCreateDialog.data = { edit: false, row: {}, indexs: [], columns: [] }
state.tableCreateDialog.title = '创建表' state.tableCreateDialog.title = '创建表'
} }
@@ -692,44 +697,10 @@ export default defineComponent({
db: state.db, db: state.db,
tableName: row.tableName, tableName: row.tableName,
}); });
state.tableCreateDialog.data = {edit: true, row, indexs, columns} state.tableCreateDialog.data = { edit: true, row, indexs, columns }
} }
} }
return {
...toRefs(state),
dateFormat,
getTags,
filterTableInfos,
enums,
search,
choose,
handlePageChange,
editDb,
valChange,
deleteDb,
onShowSqlExec,
handleDumpTableSelectionChange,
dump,
onBeforeCloseSqlExecDialog,
handleSqlExecPageChange,
searchSqlExecLog,
onShowRollbackSql,
showTableInfo,
refreshTableInfo,
closeTableInfo,
showColumns,
showTableIndex,
showCreateDdl,
dropTable,
formatByteSize,
openSqlExec,
selectDb,
filterSchema,
openEditTable,
};
},
});
</script> </script>
<style lang="scss"> <style lang="scss">
</style> </style>

File diff suppressed because it is too large Load Diff

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,22 +23,31 @@ export default defineComponent({
data: { data: {
type: Object, type: Object,
}, },
}, })
setup(props: any, { emit }) {
const state = reactive({ //定义事件
const emit = defineEmits(['update:visible'])
const state = reactive({
dialogVisible: false, dialogVisible: false,
data: { data: {
res: [], res: [],
colNames: [], colNames: [],
}, },
}); });
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];
@@ -53,16 +61,11 @@ export default defineComponent({
cell.replaceChildren(div); cell.replaceChildren(div);
}); });
} }
}; };
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,18 +55,34 @@ export default defineComponent({
theme: 'base16-light', theme: 'base16-light',
autofocus: true, autofocus: true,
extraKeys: { Tab: 'autocomplete' }, // 自定义快捷键 extraKeys: { Tab: 'autocomplete' }, // 自定义快捷键
}, }
});
state.sqlValue = props.sql;
let runSuccessCallback: any; const remarkInputRef = ref<InputInstance>();
let cancelCallback: any; const state = reactive({
let runSuccess: boolean = false; dialogVisible: false,
sqlValue: '',
dbId: 0,
db: '',
remark: '',
btnLoading: false,
});
/** const {
dialogVisible,
sqlValue,
remark,
btnLoading
} = toRefs(state)
state.sqlValue = props.sql as any;
let runSuccessCallback: any;
let cancelCallback: any;
let runSuccess: boolean = false;
/**
* 执行sql * 执行sql
*/ */
const runSql = async () => { const runSql = async () => {
if (!state.remark) { if (!state.remark) {
ElMessage.error('请输入执行的备注信息'); ElMessage.error('请输入执行的备注信息');
return; return;
@@ -107,9 +108,9 @@ export default defineComponent({
cancel(); cancel();
} }
state.btnLoading = false; state.btnLoading = false;
}; };
const cancel = () => { const cancel = () => {
state.dialogVisible = false; state.dialogVisible = false;
// 没有执行成功,并且取消回调函数存在,则执行 // 没有执行成功,并且取消回调函数存在,则执行
if (!runSuccess && cancelCallback) { if (!runSuccess && cancelCallback) {
@@ -123,9 +124,9 @@ export default defineComponent({
cancelCallback = null; cancelCallback = null;
runSuccess = false; runSuccess = false;
}, 200); }, 200);
}; };
const open = (props: SqlExecProps) => { const open = (props: SqlExecProps) => {
runSuccessCallback = props.runSuccessCallback; runSuccessCallback = props.runSuccessCallback;
cancelCallback = props.cancelCallback; cancelCallback = props.cancelCallback;
state.sqlValue = sqlFormatter(props.sql); state.sqlValue = sqlFormatter(props.sql);
@@ -137,17 +138,7 @@ export default defineComponent({
remarkInputRef.value?.focus(); remarkInputRef.value?.focus();
}); });
}); });
}; };
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,29 +176,31 @@ 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 },
}, })
setup(props: any, { emit }) { const emit = defineEmits(['update:visible', 'cancel', 'update:machineId'])
const addFile = machineApi.addConf;
const delFile = machineApi.delConf;
const updateFileContent = machineApi.updateFileContent;
const files = machineApi.files;
const fileTree: any = ref(null);
const token = getSession('token');
const folderType = 'd'; const treeProps = {
const fileType = '-'; label: 'name',
children: 'zones',
isLeaf: 'leaf',
}
const state = reactive({ const addFile = machineApi.addConf;
const delFile = machineApi.delConf;
const updateFileContent = machineApi.updateFileContent;
const files = machineApi.files;
const fileTree: any = ref(null);
const token = getSession('token');
const folderType = 'd';
const fileType = '-';
const state = reactive({
dialogVisible: false, dialogVisible: false,
query: { query: {
id: 0, id: 0,
@@ -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: '',
@@ -287,40 +247,52 @@ export default defineComponent({
node: null as any, node: null as any,
}, },
file: null as any, file: null as any,
}); });
watch(props, async (newValue) => { const {
dialogVisible,
query,
total,
fileTable,
fileContent,
tree,
progressNum,
uploadProgressShow,
createFileDialog,
} = toRefs(state)
watch(props, async (newValue) => {
if (newValue.machineId && newValue.visible) { if (newValue.machineId && newValue.visible) {
await getFiles(); await getFiles();
} }
state.dialogVisible = newValue.visible; state.dialogVisible = newValue.visible;
}); });
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;
}; };
const handlePageChange = (curPage: number) => { const handlePageChange = (curPage: number) => {
state.query.pageNum = curPage; state.query.pageNum = curPage;
getFiles(); getFiles();
}; };
const add = () => { const add = () => {
// 往数组头部添加元素 // 往数组头部添加元素
state.fileTable = [{}].concat(state.fileTable); state.fileTable = [{}].concat(state.fileTable);
}; };
const addFiles = async (row: any) => { const addFiles = async (row: any) => {
row.machineId = props.machineId; row.machineId = props.machineId;
await addFile.request(row); await addFile.request(row);
ElMessage.success('添加成功'); ElMessage.success('添加成功');
getFiles(); getFiles();
}; };
const deleteRow = (idx: any, row: any) => { const deleteRow = (idx: any, row: any) => {
if (row.id) { if (row.id) {
ElMessageBox.confirm(`此操作将删除 [${row.name}], 是否继续?`, '提示', { ElMessageBox.confirm(`此操作将删除 [${row.name}], 是否继续?`, '提示', {
confirmButtonText: '确定', confirmButtonText: '确定',
@@ -340,9 +312,9 @@ export default defineComponent({
} else { } else {
state.fileTable.splice(idx, 1); state.fileTable.splice(idx, 1);
} }
}; };
const getConf = (row: any) => { const getConf = (row: any) => {
if (row.type == 1) { if (row.type == 1) {
state.tree.folder = row; state.tree.folder = row;
state.tree.title = row.name; state.tree.title = row.name;
@@ -351,9 +323,9 @@ export default defineComponent({
return; return;
} }
getFileContent(row.id, row.path); getFileContent(row.id, row.path);
}; };
const getFileContent = async (fileId: number, path: string) => { const getFileContent = async (fileId: number, path: string) => {
const res = await machineApi.fileContent.request({ const res = await machineApi.fileContent.request({
fileId, fileId,
path, path,
@@ -365,9 +337,9 @@ export default defineComponent({
state.fileContent.path = path; state.fileContent.path = path;
state.fileContent.type = getFileType(path); state.fileContent.type = getFileType(path);
state.fileContent.contentVisible = true; state.fileContent.contentVisible = true;
}; };
const getFileType = (path: string) => { const getFileType = (path: string) => {
if (path.endsWith('.sh')) { if (path.endsWith('.sh')) {
return 'shell'; return 'shell';
} }
@@ -390,9 +362,9 @@ export default defineComponent({
return 'html'; return 'html';
} }
return 'text'; return 'text';
}; };
const updateContent = async () => { const updateContent = async () => {
await updateFileContent.request({ await updateFileContent.request({
content: state.fileContent.content, content: state.fileContent.content,
id: state.fileContent.fileId, id: state.fileContent.fileId,
@@ -402,25 +374,25 @@ export default defineComponent({
ElMessage.success('修改成功'); ElMessage.success('修改成功');
state.fileContent.contentVisible = false; state.fileContent.contentVisible = false;
state.fileContent.content = ''; state.fileContent.content = '';
}; };
/** /**
* 关闭取消按钮触发的事件 * 关闭取消按钮触发的事件
*/ */
const handleClose = () => { const handleClose = () => {
emit('update:visible', false); emit('update:visible', false);
emit('update:machineId', null); emit('update:machineId', null);
emit('cancel'); emit('cancel');
state.fileTable = []; state.fileTable = [];
state.tree.folder = { id: 0 }; state.tree.folder = { id: 0 };
}; };
/** /**
* 加载文件树节点 * 加载文件树节点
* @param {Object} node * @param {Object} node
* @param {Object} resolve * @param {Object} resolve
*/ */
const loadNode = async (node: any, resolve: any) => { const loadNode = async (node: any, resolve: any) => {
if (typeof resolve !== 'function') { if (typeof resolve !== 'function') {
return; return;
} }
@@ -462,15 +434,15 @@ export default defineComponent({
} }
} }
return resolve(res); return resolve(res);
}; };
const showCreateFileDialog = (node: any) => { const showCreateFileDialog = (node: any) => {
isTrue(node.expanded, '请先点击展开该节点后再创建'); isTrue(node.expanded, '请先点击展开该节点后再创建');
state.createFileDialog.node = node; state.createFileDialog.node = node;
state.createFileDialog.visible = true; state.createFileDialog.visible = true;
}; };
const createFile = async () => { const createFile = async () => {
const node = state.createFileDialog.node; const node = state.createFileDialog.node;
console.log(node.data); console.log(node.data);
const name = state.createFileDialog.name; const name = state.createFileDialog.name;
@@ -484,16 +456,16 @@ export default defineComponent({
}); });
fileTree.value.append({ name: name, path: path, type: type, leaf: type === fileType, size: 0 }, node); fileTree.value.append({ name: name, path: path, type: type, leaf: type === fileType, size: 0 }, node);
closeCreateFileDialog(); closeCreateFileDialog();
}; };
const closeCreateFileDialog = () => { const closeCreateFileDialog = () => {
state.createFileDialog.visible = false; state.createFileDialog.visible = false;
state.createFileDialog.node = null; state.createFileDialog.node = null;
state.createFileDialog.name = ''; state.createFileDialog.name = '';
state.createFileDialog.type = folderType; state.createFileDialog.type = folderType;
}; };
const deleteFile = (node: any, data: any) => { const deleteFile = (node: any, data: any) => {
const file = data.path; const file = data.path;
ElMessageBox.confirm(`此操作将删除 [${file}], 是否继续?`, '提示', { ElMessageBox.confirm(`此操作将删除 [${file}], 是否继续?`, '提示', {
confirmButtonText: '确定', confirmButtonText: '确定',
@@ -515,28 +487,28 @@ export default defineComponent({
.catch(() => { .catch(() => {
// skip // skip
}); });
}; };
const downloadFile = (node: any, data: any) => { const downloadFile = (node: any, data: any) => {
const a = document.createElement('a'); const a = document.createElement('a');
a.setAttribute( a.setAttribute(
'href', 'href',
`${config.baseApiUrl}/machines/${props.machineId}/files/${state.tree.folder.id}/read?type=1&path=${data.path}&token=${token}` `${config.baseApiUrl}/machines/${props.machineId}/files/${state.tree.folder.id}/read?type=1&path=${data.path}&token=${token}`
); );
a.click(); a.click();
}; };
const onUploadProgress = (progressEvent: any) => { const onUploadProgress = (progressEvent: any) => {
state.uploadProgressShow = true; state.uploadProgressShow = true;
let complete = ((progressEvent.loaded / progressEvent.total) * 100) | 0; let complete = ((progressEvent.loaded / progressEvent.total) * 100) | 0;
state.progressNum = complete; state.progressNum = complete;
}; };
const getUploadFile = (content: any) => { const getUploadFile = (content: any) => {
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
@@ -556,23 +528,23 @@ export default defineComponent({
.catch(() => { .catch(() => {
state.uploadProgressShow = false; state.uploadProgressShow = false;
}); });
}; };
const uploadSuccess = (res: any) => { const uploadSuccess = (res: any) => {
if (res.code !== 200) { if (res.code !== 200) {
ElMessage.error(res.msg); ElMessage.error(res.msg);
} }
}; };
const beforeUpload = (file: File) => { const beforeUpload = (file: File) => {
state.file = file; state.file = file;
}; };
const getFilePath = (data: object, visible: boolean) => { const getFilePath = (data: object, visible: boolean) => {
if (visible) { if (visible) {
state.dataObj = data as any; state.dataObj = data as any;
} }
}; };
const dontOperate = (data: any) => { const dontOperate = (data: any) => {
const path = data.path; const path = data.path;
const ls = [ const ls = [
'/', '/',
@@ -593,13 +565,13 @@ export default defineComponent({
'/root', '/root',
]; ];
return ls.indexOf(path) != -1; return ls.indexOf(path) != -1;
}; };
/** /**
* 格式化文件大小 * 格式化文件大小
* @param {*} value * @param {*} value
*/ */
const formatFileSize = (size: any) => { const formatFileSize = (size: any) => {
const value = Number(size); const value = Number(size);
if (size && !isNaN(value)) { if (size && !isNaN(value)) {
const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB', 'BB']; const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB', 'BB'];
@@ -614,37 +586,8 @@ export default defineComponent({
return `${k.toFixed(2)}${units[index]}`; return `${k.toFixed(2)}${units[index]}`;
} }
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,40 +141,69 @@ export default defineComponent({
trigger: ['change', 'blur'], trigger: ['change', 'blur'],
}, },
], ],
}, }
});
watch(props, async (newValue) => { const machineForm: any = ref(null);
const state = reactive({
dialogVisible: false,
sshTunnelMachineList: [] as any,
form: {
id: null,
tagId: null as any,
tagPath: '',
ip: null,
name: null,
authMethod: 1,
port: 22,
username: '',
password: '',
remark: '',
enableSshTunnel: null,
sshTunnelMachineId: null,
enableRecorder: -1,
},
pwd: '',
btnLoading: false,
});
const {
dialogVisible,
sshTunnelMachineList,
form,
pwd,
btnLoading,
} = toRefs(state)
watch(props, async (newValue: any) => {
state.dialogVisible = newValue.visible; 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 {
state.form = { port: 22, authMethod: 1 } as any; state.form = { port: 22, authMethod: 1 } as any;
} }
getSshTunnelMachines(); getSshTunnelMachines();
}); });
const getSshTunnelMachines = async () => { const getSshTunnelMachines = async () => {
if (state.form.enableSshTunnel == 1 && state.sshTunnelMachineList.length == 0) { if (state.form.enableSshTunnel == 1 && state.sshTunnelMachineList.length == 0) {
const res = await machineApi.list.request({ pageNum: 1, pageSize: 100 }); const res = await machineApi.list.request({ pageNum: 1, pageSize: 100 });
state.sshTunnelMachineList = res.list; state.sshTunnelMachineList = res.list;
} }
}; };
const getSshTunnelMachine = (machineId: any) => { const getSshTunnelMachine = (machineId: any) => {
notBlank(machineId, '请选择或先创建一台隧道机器'); notBlank(machineId, '请选择或先创建一台隧道机器');
return state.sshTunnelMachineList.find((x: any) => x.id == machineId); return state.sshTunnelMachineList.find((x: any) => x.id == machineId);
}; };
const getPwd = async () => { const getPwd = async () => {
state.pwd = await machineApi.getMachinePwd.request({ id: state.form.id }); state.pwd = await machineApi.getMachinePwd.request({ id: state.form.id });
}; };
const btnOk = async () => { const btnOk = async () => {
if (!state.form.id) { if (!state.form.id) {
notBlank(state.form.password, '新增操作,密码不可为空'); notBlank(state.form.password, '新增操作,密码不可为空');
} }
@@ -236,23 +235,13 @@ export default defineComponent({
return false; return false;
} }
}); });
}; };
const cancel = () => { const cancel = () => {
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({ const router = useRouter();
name: 'MachineList', const state = reactive({
components: {
ServiceManage,
ProcessList,
FileManage,
MachineEdit,
MachineStats,
MachineRec,
},
setup() {
const router = useRouter();
const state = reactive({
tags: [] as any, tags: [] as any,
stats: '',
params: { params: {
pageNum: 1, pageNum: 1,
pageSize: 10, pageSize: 10,
@@ -259,21 +191,35 @@ export default defineComponent({
machineId: 0, machineId: 0,
title: '', title: '',
}, },
}); });
onMounted(async () => { const {
tags,
params,
data,
currentId,
currentData,
serviceDialog,
processDialog,
fileDialog,
machineStatsDialog,
machineEditDialog,
machineRecDialog,
} = toRefs(state)
onMounted(async () => {
search(); search();
}); });
const choose = (item: any) => { const choose = (item: any) => {
if (!item) { if (!item) {
return; return;
} }
state.currentId = item.id; state.currentId = item.id;
state.currentData = item; state.currentData = item;
}; };
const showTerminal = (row: any) => { const showTerminal = (row: any) => {
const { href } = router.resolve({ const { href } = router.resolve({
path: `/machine/terminal`, path: `/machine/terminal`,
query: { query: {
@@ -282,9 +228,9 @@ export default defineComponent({
}, },
}); });
window.open(href, '_blank'); window.open(href, '_blank');
}; };
const closeCli = async (row: any) => { const closeCli = async (row: any) => {
await ElMessageBox.confirm(`确定关闭该机器客户端连接?`, '提示', { await ElMessageBox.confirm(`确定关闭该机器客户端连接?`, '提示', {
confirmButtonText: '确定', confirmButtonText: '确定',
cancelButtonText: '取消', cancelButtonText: '取消',
@@ -293,13 +239,13 @@ export default defineComponent({
await machineApi.closeCli.request({ id: row.id }); await machineApi.closeCli.request({ id: row.id });
ElMessage.success('关闭成功'); ElMessage.success('关闭成功');
search(); search();
}; };
const getTags = async () => { const getTags = async () => {
state.tags = await tagApi.getAccountTags.request(null); state.tags = await tagApi.getAccountTags.request(null);
}; };
const openFormDialog = async (machine: any) => { const openFormDialog = async (machine: any) => {
let dialogTitle; let dialogTitle;
if (machine) { if (machine) {
state.machineEditDialog.data = state.currentData as any; state.machineEditDialog.data = state.currentData as any;
@@ -311,9 +257,9 @@ export default defineComponent({
state.machineEditDialog.title = dialogTitle; state.machineEditDialog.title = dialogTitle;
state.machineEditDialog.visible = true; state.machineEditDialog.visible = true;
}; };
const deleteMachine = async (id: number) => { const deleteMachine = async (id: number) => {
try { try {
await ElMessageBox.confirm(`确定删除该机器信息? 该操作将同时删除脚本及文件配置信息`, '提示', { await ElMessageBox.confirm(`确定删除该机器信息? 该操作将同时删除脚本及文件配置信息`, '提示', {
confirmButtonText: '确定', confirmButtonText: '确定',
@@ -325,91 +271,70 @@ export default defineComponent({
state.currentId = 0; state.currentId = 0;
state.currentData = null; state.currentData = null;
search(); search();
} catch (err) {} } catch (err) { }
}; };
const serviceManager = (row: any) => { const serviceManager = (row: any) => {
state.serviceDialog.machineId = row.id; state.serviceDialog.machineId = row.id;
state.serviceDialog.visible = true; state.serviceDialog.visible = true;
state.serviceDialog.title = `${row.name} => ${row.ip}`; state.serviceDialog.title = `${row.name} => ${row.ip}`;
}; };
/** /**
* 调整机器状态 * 调整机器状态
*/ */
const changeStatus = async (row: any) => { const changeStatus = async (row: any) => {
await machineApi.changeStatus.request({ id: row.id, status: row.status }); await machineApi.changeStatus.request({ id: row.id, status: row.status });
}; };
/** /**
* 显示机器状态统计信息 * 显示机器状态统计信息
*/ */
const showMachineStats = async (machine: any) => { const showMachineStats = async (machine: any) => {
state.machineStatsDialog.machineId = machine.id; state.machineStatsDialog.machineId = machine.id;
state.machineStatsDialog.title = `机器状态: ${machine.name} => ${machine.ip}`; state.machineStatsDialog.title = `机器状态: ${machine.name} => ${machine.ip}`;
state.machineStatsDialog.visible = true; state.machineStatsDialog.visible = true;
}; };
const submitSuccess = () => { const submitSuccess = () => {
state.currentId = 0; state.currentId = 0;
state.currentData = null; state.currentData = null;
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}`;
}; };
const search = async () => { const search = async () => {
const res = await machineApi.list.request(state.params); const res = await machineApi.list.request(state.params);
state.data = res; state.data = res;
}; };
const handlePageChange = (curPage: number) => { const handlePageChange = (curPage: number) => {
state.params.pageNum = curPage; state.params.pageNum = curPage;
search(); search();
}; };
const showProcess = (row: any) => { const showProcess = (row: any) => {
state.processDialog.machineId = row.id; state.processDialog.machineId = row.id;
state.processDialog.visible = true; state.processDialog.visible = true;
}; };
const showRec = (row: any) => { const showRec = (row: any) => {
state.machineRecDialog.title = `${row.name}[${row.ip}]-终端回放记录`; state.machineRecDialog.title = `${row.name}[${row.ip}]-终端回放记录`;
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,23 +20,22 @@
</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 playerRef = ref(null); const emit = defineEmits(['update:visible', 'cancel', 'update:machineId'])
const state = reactive({
const playerRef = ref(null);
const state = reactive({
dialogVisible: false, dialogVisible: false,
title: '', title: '',
machineId: 0, machineId: 0,
@@ -52,9 +45,20 @@ export default defineComponent({
operateDate: '', operateDate: '',
user: '', user: '',
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;
@@ -62,32 +66,32 @@ export default defineComponent({
await getOperateDate(); await getOperateDate();
} }
state.dialogVisible = visible; state.dialogVisible = visible;
}); });
const getOperateDate = async () => { const getOperateDate = async () => {
const res = await machineApi.recDirNames.request({ path: state.machineId }); const res = await machineApi.recDirNames.request({ path: state.machineId });
state.operateDates = res as any; state.operateDates = res as any;
}; };
const getUsers = async (operateDate: string) => { const getUsers = async (operateDate: string) => {
state.users = []; state.users = [];
state.user = ''; state.user = '';
state.recs = []; state.recs = [];
state.rec = ''; state.rec = '';
const res = await machineApi.recDirNames.request({ path: `${state.machineId}/${operateDate}` }); const res = await machineApi.recDirNames.request({ path: `${state.machineId}/${operateDate}` });
state.users = res as any; state.users = res as any;
}; };
const getRecs = async (user: string) => { const getRecs = async (user: string) => {
state.recs = []; state.recs = [];
state.rec = ''; state.rec = '';
const res = await machineApi.recDirNames.request({ path: `${state.machineId}/${state.operateDate}/${user}` }); const res = await machineApi.recDirNames.request({ path: `${state.machineId}/${state.operateDate}/${user}` });
state.recs = res as any; state.recs = res as any;
}; };
let player: any = null; let player: any = null;
const playRec = async (rec: string) => { const playRec = async (rec: string) => {
if (player) { if (player) {
player.dispose(); player.dispose();
} }
@@ -100,33 +104,22 @@ export default defineComponent({
speed: 1.0, speed: 1.0,
idleTimeLimit: 2, idleTimeLimit: 2,
}); });
}; };
/** /**
* 关闭取消按钮触发的事件 * 关闭取消按钮触发的事件
*/ */
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 = [];
state.operateDate = ''; state.operateDate = '';
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,22 +98,31 @@ export default defineComponent({
title: { title: {
type: String, type: String,
}, },
}, })
setup(props: any, { emit }) {
const cpuRef: any = ref();
const memRef: any = ref();
let cpuChart: any = null; const emit = defineEmits(['update:visible', 'cancel', 'update:machineId'])
let memChart: any = null;
const state = reactive({ const cpuRef: any = ref();
const memRef: any = ref();
let cpuChart: any = null;
let memChart: any = null;
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();
@@ -120,18 +131,18 @@ export default defineComponent({
if (visible) { if (visible) {
initCharts(); initCharts();
} }
}); });
const setStats = async () => { const setStats = async () => {
state.stats = await machineApi.stats.request({ id: props.machineId }); state.stats = await machineApi.stats.request({ id: props.machineId });
}; };
const onRefresh = async () => { const onRefresh = async () => {
await setStats(); await setStats();
initCharts(); initCharts();
}; };
const initMemStats = () => { const initMemStats = () => {
const data = [ const data = [
{ name: '可用内存', value: state.stats.MemAvailable }, { name: '可用内存', value: state.stats.MemAvailable },
{ {
@@ -186,10 +197,10 @@ 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 = () => {
const cpu = state.stats.CPU; const cpu = state.stats.CPU;
const data = [ const data = [
{ name: 'Idle', value: cpu.Idle }, { name: 'Idle', value: cpu.Idle },
@@ -253,33 +264,33 @@ 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 = () => {
nextTick(() => { nextTick(() => {
initMemStats(); initMemStats();
initCpuStats(); initCpuStats();
}); });
parseNetInter(); parseNetInter();
initEchartsResize(); initEchartsResize();
}; };
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);
} }
}); });
}; };
const initEchartsResize = () => { const initEchartsResize = () => {
window.addEventListener('resize', initEchartResizeFun); window.addEventListener('resize', initEchartResizeFun);
}; };
const parseNetInter = () => { const parseNetInter = () => {
state.netInter = []; state.netInter = [];
const netInter = state.stats.NetIntf; const netInter = state.stats.NetIntf;
const keys = Object.keys(netInter); const keys = Object.keys(netInter);
@@ -290,9 +301,9 @@ export default defineComponent({
value.name = keys[i]; value.name = keys[i];
state.netInter.push(value); state.netInter.push(value);
} }
}; };
const cancel = () => { const cancel = () => {
emit('update:visible', false); emit('update:visible', false);
emit('cancel'); emit('cancel');
@@ -300,18 +311,7 @@ export default defineComponent({
cpuChart = null; cpuChart = null;
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,21 +102,20 @@
</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 state = reactive({ const emit = defineEmits(['update:visible', 'cancel', 'update:machineId'])
const state = reactive({
dialogVisible: false, dialogVisible: false,
params: { params: {
name: '', name: '',
@@ -111,17 +124,24 @@ export default defineComponent({
id: 0, id: 0,
}, },
processList: [], processList: [],
}); });
watch(props, (newValue) => { const {
dialogVisible,
params,
processList,
} = toRefs(state)
watch(props, (newValue) => {
if (props.machineId) { if (props.machineId) {
state.params.id = props.machineId; state.params.id = props.machineId;
getProcess(); getProcess();
} }
state.dialogVisible = newValue.visible; state.dialogVisible = newValue.visible;
}); });
const getProcess = async () => { const getProcess = async () => {
const res = await machineApi.process.request(state.params); const res = await machineApi.process.request(state.params);
// 解析字符串 // 解析字符串
// USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND // USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
@@ -161,9 +181,9 @@ export default defineComponent({
}); });
} }
state.processList = ps as any; state.processList = ps as any;
}; };
const confirmKillProcess = async (pid: any) => { const confirmKillProcess = async (pid: any) => {
await machineApi.killProcess.request({ await machineApi.killProcess.request({
pid, pid,
id: state.params.id, id: state.params.id,
@@ -171,19 +191,19 @@ export default defineComponent({
ElMessage.success('kill success'); ElMessage.success('kill success');
state.params.name = ''; state.params.name = '';
getProcess(); getProcess();
}; };
const kb2Mb = (kb: string) => { const kb2Mb = (kb: string) => {
return (parseInt(kb) / 1024).toFixed(2) + 'M'; return (parseInt(kb) / 1024).toFixed(2) + 'M';
}; };
/** /**
* 关闭取消按钮触发的事件 * 关闭取消按钮触发的事件
*/ */
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',
@@ -191,15 +211,5 @@ export default defineComponent({
id: 0, id: 0,
}; };
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,12 +86,14 @@ export default defineComponent({
isCommon: { isCommon: {
type: Boolean, type: Boolean,
}, },
}, })
setup(props: any, { emit }) {
const { isCommon, machineId } = toRefs(props);
const scriptForm: any = ref(null);
const state = reactive({ const emit = defineEmits(['update:visible', 'cancel', 'submitSuccess'])
const { isCommon, machineId } = toRefs(props);
const scriptForm: any = ref(null);
const state = reactive({
dialogVisible: false, dialogVisible: false,
submitDisabled: false, submitDisabled: false,
params: [] as any, params: [] as any,
@@ -114,9 +107,17 @@ export default defineComponent({
type: null, type: null,
}, },
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;
@@ -130,18 +131,18 @@ export default defineComponent({
state.form = {} as any; state.form = {} as any;
state.form.script = ''; state.form.script = '';
} }
}); });
const onAddParam = () => { const onAddParam = () => {
state.params.push({ name: '', model: '', placeholder: '' }); state.params.push({ name: '', model: '', placeholder: '' });
}; };
const onDeleteParam = (idx: number) => { const onDeleteParam = (idx: number) => {
state.params.splice(idx, 1); state.params.splice(idx, 1);
}; };
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) {
@@ -166,25 +167,13 @@ export default defineComponent({
return false; return false;
} }
}); });
}; };
const cancel = () => { const cancel = () => {
emit('update:visible', false); emit('update:visible', false);
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,63 +78,47 @@
</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 paramsForm: any = ref(null); const emit = defineEmits(['update:visible', 'cancel', 'update:machineId'])
const state = reactive({
const paramsForm: any = ref(null);
const state = reactive({
dialogVisible: false, dialogVisible: false,
type: 0, type: 0,
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,
}, },
@@ -185,30 +138,44 @@ export default defineComponent({
cmd: '', cmd: '',
machineId: 0, machineId: 0,
}, },
}); });
watch(props, async (newValue) => { const {
dialogVisible,
type,
currentId,
currentData,
query,
editDialog,
total,
scriptTable,
scriptParamsDialog,
resultDialog,
terminalDialog,
} = toRefs(state)
watch(props, async (newValue) => {
if (props.machineId && newValue.visible) { if (props.machineId && newValue.visible) {
await getScripts(); await getScripts();
} }
state.dialogVisible = newValue.visible; state.dialogVisible = newValue.visible;
}); });
const getScripts = async () => { const getScripts = async () => {
state.currentId = null; state.currentId = null;
state.currentData = null; state.currentData = null;
state.query.machineId = state.type == 0 ? props.machineId : 9999999; state.query.machineId = state.type == 0 ? props.machineId : 9999999;
const res = await machineApi.scripts.request(state.query); const res = await machineApi.scripts.request(state.query);
state.scriptTable = res.list; state.scriptTable = res.list;
state.total = res.total; state.total = res.total;
}; };
const handlePageChange = (curPage: number) => { const handlePageChange = (curPage: number) => {
state.query.pageNum = curPage; state.query.pageNum = curPage;
getScripts(); getScripts();
}; };
const runScript = async (script: any) => { const runScript = async (script: any) => {
// 如果存在参数,则弹窗输入参数后执行 // 如果存在参数,则弹窗输入参数后执行
if (script.params) { if (script.params) {
state.scriptParamsDialog.paramsFormItem = JSON.parse(script.params); state.scriptParamsDialog.paramsFormItem = JSON.parse(script.params);
@@ -219,10 +186,10 @@ export default defineComponent({
} }
run(script); run(script);
}; };
// 有参数的脚本执行函数 // 有参数的脚本执行函数
const hasParamsRun = async (script: any) => { const hasParamsRun = async (script: any) => {
// 如果脚本参数弹窗显示,则校验参数表单数据通过后执行 // 如果脚本参数弹窗显示,则校验参数表单数据通过后执行
if (state.scriptParamsDialog.visible) { if (state.scriptParamsDialog.visible) {
paramsForm.value.validate((valid: any) => { paramsForm.value.validate((valid: any) => {
@@ -236,9 +203,9 @@ export default defineComponent({
} }
}); });
} }
}; };
const run = async (script: any) => { const run = async (script: any) => {
const noResult = script.type == enums.scriptTypeEnum['NO_RESULT'].value; const noResult = script.type == enums.scriptTypeEnum['NO_RESULT'].value;
// 如果脚本类型为有结果类型,则显示结果信息 // 如果脚本类型为有结果类型,则显示结果信息
if (script.type == enums.scriptTypeEnum['RESULT'].value || noResult) { if (script.type == enums.scriptTypeEnum['RESULT'].value || noResult) {
@@ -264,15 +231,15 @@ 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;
} }
}; };
/** /**
* 解析 {{.param}} 形式模板字符串 * 解析 {{.param}} 形式模板字符串
*/ */
function templateResolve(template: string, param: any) { function templateResolve(template: string, param: any) {
return template.replace(/\{{.\w+\}}/g, (word) => { return template.replace(/\{{.\w+\}}/g, (word) => {
const key = word.substring(3, word.length - 2); const key = word.substring(3, word.length - 2);
const value = param[key]; const value = param[key];
@@ -281,26 +248,26 @@ export default defineComponent({
} }
return ''; return '';
}); });
} }
const closeTermnial = () => { const closeTermnial = () => {
state.terminalDialog.visible = false; state.terminalDialog.visible = false;
state.terminalDialog.machineId = 0; state.terminalDialog.machineId = 0;
}; };
/** /**
* 选择数据 * 选择数据
*/ */
const choose = (item: any) => { const choose = (item: any) => {
if (!item) { if (!item) {
return; return;
} }
state.currentId = item.id; state.currentId = item.id;
state.currentData = item; state.currentData = item;
}; };
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 = '查看编辑脚本';
@@ -308,13 +275,13 @@ export default defineComponent({
state.editDialog.title = '新增脚本'; state.editDialog.title = '新增脚本';
} }
state.editDialog.visible = true; state.editDialog.visible = true;
}; };
const submitSuccess = () => { const submitSuccess = () => {
getScripts(); getScripts();
}; };
const deleteRow = (row: any) => { const deleteRow = (row: any) => {
ElMessageBox.confirm(`此操作将删除 [${row.name}], 是否继续?`, '提示', { ElMessageBox.confirm(`此操作将删除 [${row.name}], 是否继续?`, '提示', {
confirmButtonText: '确定', confirmButtonText: '确定',
cancelButtonText: '取消', cancelButtonText: '取消',
@@ -330,36 +297,18 @@ 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,64 +2,66 @@
<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: '',
height: '', height: '',
term: null as any, term: null as any,
socket: null as any, socket: null as any,
}); });
const resize = 1; const {
const data = 2; height,
const ping = 3; } = toRefs(state)
watch(props, (newValue) => { const resize = 1;
const data = 2;
const ping = 3;
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(() => {
closeAll(); closeAll();
}); });
const store = useStore(); const store = useStore();
// 获取布局配置信息 // 获取布局配置信息
const getThemeConfig: any = computed(() => { const getThemeConfig: any = computed(() => {
return store.state.themeConfig.themeConfig; return store.state.themeConfig.themeConfig;
}); });
nextTick(() => { nextTick(() => {
initXterm(); initXterm();
initSocket(); initSocket();
}); });
function initXterm() { function initXterm() {
const term: any = new Terminal({ const term: any = new Terminal({
fontSize: getThemeConfig.value.terminalFontSize || 15, fontSize: getThemeConfig.value.terminalFontSize || 15,
fontWeight: getThemeConfig.value.terminalFontWeight || 'normal', fontWeight: getThemeConfig.value.terminalFontWeight || 'normal',
@@ -115,13 +117,12 @@ export default defineComponent({
term.onData((key: any) => { term.onData((key: any) => {
sendCmd(key); sendCmd(key);
}); });
} }
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
}` }`
); );
@@ -156,42 +157,36 @@ export default defineComponent({
// 监听socket消息 // 监听socket消息
state.socket.onmessage = getMessage; state.socket.onmessage = getMessage;
} }
function getMessage(msg: any) { function getMessage(msg: any) {
// msg.data是真正后端返回的数据 // msg.data是真正后端返回的数据
state.term.write(msg.data); state.term.write(msg.data);
} }
function send(msg: any) { function send(msg: any) {
state.socket.send(JSON.stringify(msg)); state.socket.send(JSON.stringify(msg));
} }
function sendCmd(key: any) { function sendCmd(key: any) {
send({ send({
type: data, type: data,
msg: key, msg: key,
}); });
} }
function close() { function close() {
if (state.socket) { if (state.socket) {
state.socket.close(); state.socket.close();
console.log('socket关闭'); console.log('socket关闭');
} }
} }
function closeAll() { function closeAll() {
close(); close();
if (state.term) { if (state.term) {
state.term.dispose(); state.term.dispose();
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({ const route = useRoute();
name: 'SshTerminalPage', const state = reactive({
components: {
SshTerminal,
},
props: {
machineId: { type: Number },
},
setup() {
const route = useRoute();
const state = reactive({
machineId: 0, machineId: 0,
height: 700, height: 700,
}); });
onMounted(() => { const {
machineId,
height,
} = toRefs(state)
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({ const store = useStore();
name: 'MongoDataOp', const findParamInputRef: any = ref(null);
components: { const state = reactive({
JsonEdit,
},
setup() {
const store = useStore();
const findParamInputRef: any = ref(null);
const state = reactive({
loading: false,
tags: [], tags: [],
mongoList: [] as any, mongoList: [] as any,
query: { query: {
@@ -183,15 +180,31 @@ export default defineComponent({
doc: '', doc: '',
item: {} as any, item: {} as any,
}, },
}); });
const searchMongo = async () => { const {
tags,
mongoList,
query,
mongoId,
database,
collection,
activeName,
databases,
collections,
dataTabs,
findDialog,
insertDocDialog,
jsoneditorDialog,
} = toRefs(state)
const searchMongo = async () => {
notNull(state.query.tagPath, '请先选择标签'); notNull(state.query.tagPath, '请先选择标签');
const res = await mongoApi.mongoList.request(state.query); const res = await mongoApi.mongoList.request(state.query);
state.mongoList = res.list; state.mongoList = res.list;
}; };
const changeTag = (tagPath: string) => { const changeTag = (tagPath: string) => {
state.databases = []; state.databases = [];
state.collections = []; state.collections = [];
state.mongoId = null; state.mongoId = null;
@@ -201,36 +214,36 @@ export default defineComponent({
if (tagPath != null) { if (tagPath != null) {
searchMongo(); searchMongo();
} }
}; };
const getTags = async () => { const getTags = async () => {
state.tags = await tagApi.getAccountTags.request(null); state.tags = await tagApi.getAccountTags.request(null);
}; };
const changeMongo = () => { const changeMongo = () => {
state.databases = []; state.databases = [];
state.collections = []; state.collections = [];
state.dataTabs = {}; state.dataTabs = {};
getDatabases(); getDatabases();
}; };
const getDatabases = async () => { const getDatabases = async () => {
const res = await mongoApi.databases.request({ id: state.mongoId }); const res = await mongoApi.databases.request({ id: state.mongoId });
state.databases = res.Databases; state.databases = res.Databases;
}; };
const changeDatabase = () => { const changeDatabase = () => {
state.collections = []; state.collections = [];
state.collection = ''; state.collection = '';
state.dataTabs = {}; state.dataTabs = {};
getCollections(); getCollections();
}; };
const getCollections = async () => { const getCollections = async () => {
state.collections = await mongoApi.collections.request({ id: state.mongoId, database: state.database }); state.collections = await mongoApi.collections.request({ id: state.mongoId, database: state.database });
}; };
const changeCollection = () => { const changeCollection = () => {
const collection = state.collection; const collection = state.collection;
let dataTab = state.dataTabs[collection]; let dataTab = state.dataTabs[collection];
if (!dataTab) { if (!dataTab) {
@@ -251,9 +264,9 @@ export default defineComponent({
} }
state.activeName = collection; state.activeName = collection;
findCommand(collection); findCommand(collection);
}; };
const showFindDialog = (collection: string) => { const showFindDialog = (collection: string) => {
// 获取当前tab的索引位置将其输入框失去焦点防止输入以及重复获取焦点 // 获取当前tab的索引位置将其输入框失去焦点防止输入以及重复获取焦点
const dataTabNames = Object.keys(state.dataTabs); const dataTabNames = Object.keys(state.dataTabs);
for (let i = 0; i < dataTabNames.length; i++) { for (let i = 0; i < dataTabNames.length; i++) {
@@ -264,16 +277,16 @@ export default defineComponent({
state.findDialog.findParam = state.dataTabs[collection].findParam; state.findDialog.findParam = state.dataTabs[collection].findParam;
state.findDialog.visible = true; state.findDialog.visible = true;
}; };
const confirmFindDialog = () => { const confirmFindDialog = () => {
state.dataTabs[state.activeName].findParam = state.findDialog.findParam; state.dataTabs[state.activeName].findParam = state.findDialog.findParam;
state.dataTabs[state.activeName].findParamStr = JSON.stringify(state.findDialog.findParam); state.dataTabs[state.activeName].findParamStr = JSON.stringify(state.findDialog.findParam);
state.findDialog.visible = false; state.findDialog.visible = false;
findCommand(state.activeName); findCommand(state.activeName);
}; };
const findCommand = async (collection: string) => { const findCommand = async (collection: string) => {
const dataTab = state.dataTabs[collection]; const dataTab = state.dataTabs[collection];
const findParma = dataTab.findParam; const findParma = dataTab.findParam;
let filter, sort; let filter, sort;
@@ -294,12 +307,12 @@ export default defineComponent({
skip: findParma.skip || 0, skip: findParma.skip || 0,
}); });
state.dataTabs[collection].datas = wrapDatas(datas); state.dataTabs[collection].datas = wrapDatas(datas);
}; };
/** /**
* 包装mongo查询回来的对象即将其都转为json字符串并用value属性值描述方便显示 * 包装mongo查询回来的对象即将其都转为json字符串并用value属性值描述方便显示
*/ */
const wrapDatas = (datas: any) => { const wrapDatas = (datas: any) => {
const wrapDatas = [] as any; const wrapDatas = [] as any;
if (!datas) { if (!datas) {
return wrapDatas; return wrapDatas;
@@ -308,9 +321,9 @@ export default defineComponent({
wrapDatas.push({ value: JSON.stringify(data, null, 4) }); wrapDatas.push({ value: JSON.stringify(data, null, 4) });
} }
return wrapDatas; return wrapDatas;
}; };
const showInsertDocDialog = () => { const showInsertDocDialog = () => {
// tab数据中的第一个文档因为该集合的文档都类似故使用第一个文档赋值至需要新增的文档输入框方便直接修改新增 // tab数据中的第一个文档因为该集合的文档都类似故使用第一个文档赋值至需要新增的文档输入框方便直接修改新增
const datasFirstDoc = state.dataTabs[state.activeName].datas[0]; const datasFirstDoc = state.dataTabs[state.activeName].datas[0];
let doc = ''; let doc = '';
@@ -322,9 +335,9 @@ export default defineComponent({
} }
state.insertDocDialog.doc = doc; state.insertDocDialog.doc = doc;
state.insertDocDialog.visible = true; state.insertDocDialog.visible = true;
}; };
const onInsertDoc = async () => { const onInsertDoc = async () => {
let docObj; let docObj;
try { try {
docObj = JSON.parse(state.insertDocDialog.doc); docObj = JSON.parse(state.insertDocDialog.doc);
@@ -341,19 +354,19 @@ export default defineComponent({
ElMessage.success('新增成功'); ElMessage.success('新增成功');
findCommand(state.activeName); findCommand(state.activeName);
state.insertDocDialog.visible = false; state.insertDocDialog.visible = false;
}; };
const onJsonEditor = (item: any) => { const onJsonEditor = (item: any) => {
state.jsoneditorDialog.item = item; state.jsoneditorDialog.item = item;
state.jsoneditorDialog.doc = item.value; state.jsoneditorDialog.doc = item.value;
state.jsoneditorDialog.visible = true; state.jsoneditorDialog.visible = true;
}; };
const onCloseJsonEditDialog = () => { const onCloseJsonEditDialog = () => {
state.jsoneditorDialog.item.value = JSON.stringify(JSON.parse(state.jsoneditorDialog.doc), null, 4); state.jsoneditorDialog.item.value = JSON.stringify(JSON.parse(state.jsoneditorDialog.doc), null, 4);
}; };
const onSaveDoc = async (doc: string) => { const onSaveDoc = async (doc: string) => {
const docObj = parseDocJsonString(doc); const docObj = parseDocJsonString(doc);
const id = docObj._id; const id = docObj._id;
notBlank(id, '文档的_id属性不存在'); notBlank(id, '文档的_id属性不存在');
@@ -367,9 +380,9 @@ export default defineComponent({
}); });
isTrue(res.ModifiedCount == 1, '修改失败'); isTrue(res.ModifiedCount == 1, '修改失败');
ElMessage.success('保存成功'); ElMessage.success('保存成功');
}; };
const onDeleteDoc = async (doc: string) => { const onDeleteDoc = async (doc: string) => {
const docObj = parseDocJsonString(doc); const docObj = parseDocJsonString(doc);
const id = docObj._id; const id = docObj._id;
notBlank(id, '文档的_id属性不存在'); notBlank(id, '文档的_id属性不存在');
@@ -382,30 +395,30 @@ export default defineComponent({
isTrue(res.DeletedCount == 1, '删除失败'); isTrue(res.DeletedCount == 1, '删除失败');
ElMessage.success('删除成功'); ElMessage.success('删除成功');
findCommand(state.activeName); findCommand(state.activeName);
}; };
/** /**
* 将json字符串解析为json对象 * 将json字符串解析为json对象
*/ */
const parseDocJsonString = (doc: string) => { const parseDocJsonString = (doc: string) => {
try { try {
return JSON.parse(doc); return JSON.parse(doc);
} catch (e) { } catch (e) {
ElMessage.error('文档内容解析为json对象失败'); ElMessage.error('文档内容解析为json对象失败');
throw e; throw e;
} }
}; };
/** /**
* 数据tab点击 * 数据tab点击
*/ */
const onDataTabClick = (tab: any) => { const onDataTabClick = (tab: any) => {
const name = tab.props.name; const name = tab.props.name;
// 修改选择框绑定的表信息 // 修改选择框绑定的表信息
state.collection = name; state.collection = name;
}; };
const removeDataTab = (targetName: string) => { const removeDataTab = (targetName: string) => {
const tabNames = Object.keys(state.dataTabs); const tabNames = Object.keys(state.dataTabs);
let activeName = state.activeName; let activeName = state.activeName;
tabNames.forEach((name, index) => { tabNames.forEach((name, index) => {
@@ -425,57 +438,33 @@ export default defineComponent({
} }
delete state.dataTabs[targetName]; delete state.dataTabs[targetName];
}; };
// 加载选中的tagPath // 加载选中的tagPath
const setSelects = async (mongoDbOptInfo: any) =>{ const setSelects = async (mongoDbOptInfo: any) => {
const { tagPath, dbId, db} = mongoDbOptInfo.dbOptInfo; const { tagPath, dbId, db } = mongoDbOptInfo.dbOptInfo;
state.query.tagPath = tagPath state.query.tagPath = tagPath
await searchMongo(); await searchMongo();
state.mongoId = dbId state.mongoId = dbId
await getDatabases(); await getDatabases();
state.database = db state.database = db
await getCollections(); await getCollections();
if(state.collection){ if (state.collection) {
state.collection = '' state.collection = ''
state.dataTabs = {} state.dataTabs = {}
} }
} }
// 判断如果有数据则加载下拉选项 // 判断如果有数据则加载下拉选项
let mongoDbOptInfo = store.state.mongoDbOptInfo let mongoDbOptInfo = store.state.mongoDbOptInfo
if(mongoDbOptInfo.dbOptInfo.tagPath){ if (mongoDbOptInfo.dbOptInfo.tagPath) {
setSelects(mongoDbOptInfo) setSelects(mongoDbOptInfo)
} }
// 监听选中操作的db变化并加载下拉选项 // 监听选中操作的db变化并加载下拉选项
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'],
}, },
], ],
}, }
});
watch(props, async (newValue) => { const mongoForm: any = ref(null);
const state = reactive({
dialogVisible: false,
sshTunnelMachineList: [] as any,
form: {
id: null,
name: null,
uri: null,
enableSshTunnel: -1,
sshTunnelMachineId: null,
tagId: null as any,
tagPath: null as any,
},
btnLoading: false,
});
const {
dialogVisible,
sshTunnelMachineList,
form,
btnLoading,
} = toRefs(state)
watch(props, async (newValue: any) => {
state.dialogVisible = newValue.visible; state.dialogVisible = newValue.visible;
if (!state.dialogVisible) { if (!state.dialogVisible) {
return; return;
@@ -122,16 +121,16 @@ export default defineComponent({
state.form = { db: 0 } as any; state.form = { db: 0 } as any;
} }
getSshTunnelMachines(); getSshTunnelMachines();
}); });
const getSshTunnelMachines = async () => { const getSshTunnelMachines = async () => {
if (state.form.enableSshTunnel == 1 && state.sshTunnelMachineList.length == 0) { if (state.form.enableSshTunnel == 1 && state.sshTunnelMachineList.length == 0) {
const res = await machineApi.list.request({ pageNum: 1, pageSize: 100 }); const res = await machineApi.list.request({ pageNum: 1, pageSize: 100 });
state.sshTunnelMachineList = res.list; state.sshTunnelMachineList = res.list;
} }
}; };
const btnOk = async () => { const btnOk = async () => {
mongoForm.value.validate(async (valid: boolean) => { mongoForm.value.validate(async (valid: boolean) => {
if (valid) { if (valid) {
const reqForm = { ...state.form }; const reqForm = { ...state.form };
@@ -151,22 +150,13 @@ export default defineComponent({
return false; return false;
} }
}); });
}; };
const cancel = () => { const cancel = () => {
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,39 +183,28 @@
</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';
import { formatByteSize } from '@/common/utils/format'; import { formatByteSize } from '@/common/utils/format';
import {store} from '@/store'; 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({ const state = reactive({
name: 'MongoList',
components: {
MongoEdit,
},
setup() {
const state = reactive({
tags: [], tags: [],
dbOps: { dbOps: {
dbId: 0, dbId: 0,
db: '', db: '',
}, },
projects: [],
list: [], list: [],
total: 0, total: 0,
currentId: null, currentId: null,
@@ -253,26 +246,38 @@ export default defineComponent({
name: '', name: '',
}, },
}, },
}); });
onMounted(async () => { const {
tags,
list,
total,
currentId,
query,
mongoEditDialog,
databaseDialog,
collectionsDialog,
createCollectionDialog,
} = toRefs(state)
onMounted(async () => {
search(); search();
}); });
const handlePageChange = (curPage: number) => { const handlePageChange = (curPage: number) => {
state.query.pageNum = curPage; state.query.pageNum = curPage;
search(); search();
}; };
const choose = (item: any) => { const choose = (item: any) => {
if (!item) { if (!item) {
return; return;
} }
state.currentId = item.id; state.currentId = item.id;
state.currentData = item; state.currentData = item;
}; };
const showDatabases = async (id: number, row: any) => { const showDatabases = async (id: number, row: any) => {
console.log(row) console.log(row)
state.query.tagPath = row.tagPath state.query.tagPath = row.tagPath
state.dbOps.dbId = id state.dbOps.dbId = id
@@ -280,9 +285,9 @@ export default defineComponent({
state.databaseDialog.data = (await mongoApi.databases.request({ id })).Databases; state.databaseDialog.data = (await mongoApi.databases.request({ id })).Databases;
state.databaseDialog.title = `数据库列表`; state.databaseDialog.title = `数据库列表`;
state.databaseDialog.visible = true; state.databaseDialog.visible = true;
}; };
const showDatabaseStats = async (dbName: string) => { const showDatabaseStats = async (dbName: string) => {
state.databaseDialog.statsDialog.data = await mongoApi.runCommand.request({ state.databaseDialog.statsDialog.data = await mongoApi.runCommand.request({
id: state.currentId, id: state.currentId,
database: dbName, database: dbName,
@@ -292,29 +297,29 @@ export default defineComponent({
}); });
state.databaseDialog.statsDialog.title = `'${dbName}' stats`; state.databaseDialog.statsDialog.title = `'${dbName}' stats`;
state.databaseDialog.statsDialog.visible = true; state.databaseDialog.statsDialog.visible = true;
}; };
const showCollections = async (database: string) => { const showCollections = async (database: string) => {
state.collectionsDialog.database = database; state.collectionsDialog.database = database;
state.collectionsDialog.data = []; state.collectionsDialog.data = [];
setCollections(database); setCollections(database);
state.collectionsDialog.title = `'${database}' 集合`; state.collectionsDialog.title = `'${database}' 集合`;
state.collectionsDialog.visible = true; state.collectionsDialog.visible = true;
}; };
const setCollections = async (database: string) => { const setCollections = async (database: string) => {
const res = await mongoApi.collections.request({ id: state.currentId, database }); const res = await mongoApi.collections.request({ id: state.currentId, database });
const collections = [] as any; const collections = [] as any;
for (let r of res) { for (let r of res) {
collections.push({ name: r }); collections.push({ name: r });
} }
state.collectionsDialog.data = collections; state.collectionsDialog.data = collections;
}; };
/** /**
* 显示集合状态 * 显示集合状态
*/ */
const showCollectionStats = async (collection: string) => { const showCollectionStats = async (collection: string) => {
state.collectionsDialog.statsDialog.data = await mongoApi.runCommand.request({ state.collectionsDialog.statsDialog.data = await mongoApi.runCommand.request({
id: state.currentId, id: state.currentId,
database: state.collectionsDialog.database, database: state.collectionsDialog.database,
@@ -324,12 +329,12 @@ export default defineComponent({
}); });
state.collectionsDialog.statsDialog.title = `'${collection}' stats`; state.collectionsDialog.statsDialog.title = `'${collection}' stats`;
state.collectionsDialog.statsDialog.visible = true; state.collectionsDialog.statsDialog.visible = true;
}; };
/** /**
* 删除集合 * 删除集合
*/ */
const onDeleteCollection = async (collection: string) => { const onDeleteCollection = async (collection: string) => {
await mongoApi.runCommand.request({ await mongoApi.runCommand.request({
id: state.currentId, id: state.currentId,
database: state.collectionsDialog.database, database: state.collectionsDialog.database,
@@ -339,13 +344,13 @@ export default defineComponent({
}); });
ElMessage.success('集合删除成功'); ElMessage.success('集合删除成功');
setCollections(state.collectionsDialog.database); setCollections(state.collectionsDialog.database);
}; };
const showCreateCollectionDialog = () => { const showCreateCollectionDialog = () => {
state.createCollectionDialog.visible = true; state.createCollectionDialog.visible = true;
}; };
const onCreateCollection = async () => { const onCreateCollection = async () => {
const form = state.createCollectionDialog.form; const form = state.createCollectionDialog.form;
await mongoApi.runCommand.request({ await mongoApi.runCommand.request({
id: state.currentId, id: state.currentId,
@@ -358,9 +363,9 @@ export default defineComponent({
state.createCollectionDialog.visible = false; state.createCollectionDialog.visible = false;
state.createCollectionDialog.form = {} as any; state.createCollectionDialog.form = {} as any;
setCollections(state.collectionsDialog.database); setCollections(state.collectionsDialog.database);
}; };
const deleteMongo = async () => { const deleteMongo = async () => {
try { try {
await ElMessageBox.confirm(`确定删除该mongo?`, '提示', { await ElMessageBox.confirm(`确定删除该mongo?`, '提示', {
confirmButtonText: '确定', confirmButtonText: '确定',
@@ -372,20 +377,20 @@ export default defineComponent({
state.currentData = null; state.currentData = null;
state.currentId = null; state.currentId = null;
search(); search();
} catch (err) {} } catch (err) { }
}; };
const search = async () => { const search = async () => {
const res = await mongoApi.mongoList.request(state.query); const res = await mongoApi.mongoList.request(state.query);
state.list = res.list; state.list = res.list;
state.total = res.total; state.total = res.total;
}; };
const getTags = async () => { const getTags = async () => {
state.tags = await tagApi.getAccountTags.request(null); state.tags = await tagApi.getAccountTags.request(null);
}; };
const editMongo = async (isAdd = false) => { const editMongo = async (isAdd = false) => {
if (isAdd) { if (isAdd) {
state.mongoEditDialog.data = null; state.mongoEditDialog.data = null;
state.mongoEditDialog.title = '新增mongo'; state.mongoEditDialog.title = '新增mongo';
@@ -394,15 +399,15 @@ export default defineComponent({
state.mongoEditDialog.title = '修改mongo'; state.mongoEditDialog.title = '修改mongo';
} }
state.mongoEditDialog.visible = true; state.mongoEditDialog.visible = true;
}; };
const valChange = () => { const valChange = () => {
state.currentId = null; state.currentId = null;
state.currentData = null; state.currentData = null;
search(); search();
}; };
const openDataOps = ( row: any) => { const openDataOps = (row: any) => {
state.dbOps.db = row.Name state.dbOps.db = row.Name
debugger debugger
@@ -413,35 +418,14 @@ export default defineComponent({
} }
// 判断db是否发生改变 // 判断db是否发生改变
let oldDb = store.state.mongoDbOptInfo.dbOptInfo.db; let oldDb = store.state.mongoDbOptInfo.dbOptInfo.db;
if(oldDb !== row.Name){ if (oldDb !== row.Name) {
store.dispatch('mongoDbOptInfo/setMongoDbOptInfo', data); store.dispatch('mongoDbOptInfo/setMongoDbOptInfo', data);
} }
router.push({name: 'MongoDataOp'}); router.push({ name: 'MongoDataOp' });
} }
return {
...toRefs(state),
dateFormat,
getTags,
search,
handlePageChange,
choose,
showDatabases,
showDatabaseStats,
showCollections,
showCollectionStats,
onDeleteCollection,
showCreateCollectionDialog,
onCreateCollection,
formatByteSize,
deleteMongo,
editMongo,
valChange,
openDataOps,
};
},
});
</script> </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({ let store = useStore();
name: 'DataOperation', const state = reactive({
components: {
StringValue,
HashValue,
SetValue,
ListValue,
},
setup() {
let store = useStore();
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,
@@ -209,42 +158,58 @@ export default defineComponent({
}, },
keys: [], keys: [],
dbsize: 0, dbsize: 0,
}); });
const searchRedis = async () => { const {
loading,
tags,
redisList,
dbList,
query,
scanParam,
dataEdit,
hashValueDialog,
stringValueDialog,
setValueDialog,
listValueDialog,
keys,
dbsize,
} = toRefs(state)
const searchRedis = async () => {
notBlank(state.query.tagPath, '请先选择标签'); notBlank(state.query.tagPath, '请先选择标签');
const res = await redisApi.redisList.request(state.query); const res = await redisApi.redisList.request(state.query);
state.redisList = res.list; state.redisList = res.list;
}; };
const changeTag = (tagPath: string) => { const changeTag = (tagPath: string) => {
clearRedis(); clearRedis();
if (tagPath != null) { if (tagPath != null) {
searchRedis(); searchRedis();
} }
}; };
const getTags = async () => { const getTags = async () => {
state.tags = await tagApi.getAccountTags.request(null); state.tags = await tagApi.getAccountTags.request(null);
}; };
const changeRedis = (id: number) => { const changeRedis = (id: number) => {
resetScanParam(id); resetScanParam(id);
state.dbList = (state.redisList.find((x: any) => x.id == id) as any).db.split(','); state.dbList = (state.redisList.find((x: any) => x.id == id) as any).db.split(',');
// 默认选中配置的第一个库 // 默认选中配置的第一个库
state.scanParam.db = state.dbList[0]; state.scanParam.db = state.dbList[0];
state.keys = []; state.keys = [];
state.dbsize = 0; state.dbsize = 0;
}; };
const changeDb = () => { const changeDb = () => {
resetScanParam(state.scanParam.id as any); resetScanParam(state.scanParam.id as any);
state.keys = []; state.keys = [];
state.dbsize = 0; state.dbsize = 0;
searchKey(); searchKey();
}; };
const scan = async () => { const scan = async () => {
isTrue(state.scanParam.id != null, '请先选择redis'); isTrue(state.scanParam.id != null, '请先选择redis');
notBlank(state.scanParam.count, 'count不能为空'); notBlank(state.scanParam.count, 'count不能为空');
@@ -264,30 +229,30 @@ export default defineComponent({
} finally { } finally {
state.loading = false; state.loading = false;
} }
}; };
const searchKey = async () => { const searchKey = async () => {
state.scanParam.cursor = {}; state.scanParam.cursor = {};
await scan(); await scan();
}; };
const clearRedis = () => { const clearRedis = () => {
state.redisList = []; state.redisList = [];
state.scanParam.id = null; state.scanParam.id = null;
resetScanParam(); resetScanParam();
state.scanParam.db = ''; state.scanParam.db = '';
state.keys = []; state.keys = [];
state.dbsize = 0; state.dbsize = 0;
}; };
const clear = () => { const clear = () => {
resetScanParam(); resetScanParam();
if (state.scanParam.id) { if (state.scanParam.id) {
scan(); scan();
} }
}; };
const resetScanParam = (id: number = 0) => { const resetScanParam = (id: number = 0) => {
state.scanParam.count = 10; state.scanParam.count = 10;
if (id != 0) { if (id != 0) {
const redis: any = state.redisList.find((x: any) => x.id == id); const redis: any = state.redisList.find((x: any) => x.id == id);
@@ -298,9 +263,9 @@ export default defineComponent({
} }
state.scanParam.match = null; state.scanParam.match = null;
state.scanParam.cursor = {}; state.scanParam.cursor = {};
}; };
const getValue = async (row: any) => { const getValue = async (row: any) => {
const type = row.type; const type = row.type;
state.dataEdit.keyInfo.type = type; state.dataEdit.keyInfo.type = type;
@@ -320,9 +285,9 @@ export default defineComponent({
} else { } else {
ElMessage.warning('暂不支持该类型'); ElMessage.warning('暂不支持该类型');
} }
}; };
const onAddData = (type: string) => { const onAddData = (type: string) => {
notNull(state.scanParam.id, '请先选择redis'); notNull(state.scanParam.id, '请先选择redis');
state.dataEdit.operationType = 1; state.dataEdit.operationType = 1;
state.dataEdit.title = '新增数据'; state.dataEdit.title = '新增数据';
@@ -339,13 +304,13 @@ export default defineComponent({
} else { } else {
ElMessage.warning('暂不支持该类型'); ElMessage.warning('暂不支持该类型');
} }
}; };
const onCancelDataEdit = () => { const onCancelDataEdit = () => {
state.dataEdit.keyInfo = {} as any; state.dataEdit.keyInfo = {} as any;
}; };
const del = (key: string) => { const del = (key: string) => {
ElMessageBox.confirm(`确定删除[ ${key} ] 该key?`, '提示', { ElMessageBox.confirm(`确定删除[ ${key} ] 该key?`, '提示', {
confirmButtonText: '确定', confirmButtonText: '确定',
cancelButtonText: '取消', cancelButtonText: '取消',
@@ -363,10 +328,10 @@ export default defineComponent({
searchKey(); searchKey();
}); });
}) })
.catch(() => {}); .catch(() => { });
}; };
const ttlConveter = (ttl: any) => { const ttlConveter = (ttl: any) => {
if (ttl == -1 || ttl == 0) { if (ttl == -1 || ttl == 0) {
return '永久'; return '永久';
} }
@@ -400,9 +365,9 @@ export default defineComponent({
result = '' + day + 'd:' + result; result = '' + day + 'd:' + result;
} }
return result; return result;
}; };
const getTypeColor = (type: string) => { const getTypeColor = (type: string) => {
if (type == 'string') { if (type == 'string') {
return '#E4F5EB'; return '#E4F5EB';
} }
@@ -412,10 +377,10 @@ export default defineComponent({
if (type == 'set') { if (type == 'set') {
return '#A8DEE0'; return '#A8DEE0';
} }
}; };
// 加载选中的db // 加载选中的db
const setSelects = async (redisDbOptInfo: any) => { const setSelects = async (redisDbOptInfo: any) => {
// 设置标签路径等 // 设置标签路径等
const { tagPath, dbId } = redisDbOptInfo.dbOptInfo; const { tagPath, dbId } = redisDbOptInfo.dbOptInfo;
state.query.tagPath = tagPath; state.query.tagPath = tagPath;
@@ -423,39 +388,21 @@ export default defineComponent({
state.scanParam.id = dbId; state.scanParam.id = dbId;
changeRedis(dbId); changeRedis(dbId);
changeDb(); changeDb();
}; };
// 判断如果有数据则加载下拉选项 // 判断如果有数据则加载下拉选项
let redisDbOptInfo = store.state.redisDbOptInfo; let redisDbOptInfo = store.state.redisDbOptInfo;
if (redisDbOptInfo.dbOptInfo.tagPath) { if (redisDbOptInfo.dbOptInfo.tagPath) {
setSelects(redisDbOptInfo); setSelects(redisDbOptInfo);
} }
// 监听选中操作的db变化并加载下拉选项 // 监听选中操作的db变化并加载下拉选项
watch(store.state.redisDbOptInfo, async (newValue) => { watch(store.state.redisDbOptInfo, async (newValue) => {
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,10 +95,11 @@ 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,
redisId: 0, redisId: 0,
@@ -119,18 +124,27 @@ export default defineComponent({
value: '', value: '',
}, },
], ],
}); });
const cancel = () => { const {
dialogVisible,
operationType,
key,
scanParam,
keySize,
hashValues,
} = toRefs(state)
const cancel = () => {
emit('update:visible', false); emit('update:visible', false);
emit('cancel'); emit('cancel');
setTimeout(() => { setTimeout(() => {
state.hashValues = []; state.hashValues = [];
state.key = {} as any; state.key = {} as any;
}, 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,22 +152,22 @@ 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();
} }
state.dialogVisible = visible; state.dialogVisible = visible;
}); });
const reHscan = async () => { const reHscan = async () => {
state.scanParam.id = state.redisId; state.scanParam.id = state.redisId;
state.scanParam.db = state.db; state.scanParam.db = state.db;
state.scanParam.cursor = 0; state.scanParam.cursor = 0;
hscan(); hscan();
}; };
const hscan = async () => { const hscan = async () => {
const match = state.scanParam.match; const match = state.scanParam.match;
if (!match || match == '' || match == '*') { if (!match || match == '' || match == '*') {
if (state.scanParam.count > 100) { if (state.scanParam.count > 100) {
@@ -179,9 +193,9 @@ export default defineComponent({
hashValue.push({ field: keys[nextFieldIndex++], value: keys[nextFieldIndex++] }); hashValue.push({ field: keys[nextFieldIndex++], value: keys[nextFieldIndex++] });
} }
state.hashValues = hashValue; state.hashValues = hashValue;
}; };
const hdel = async (field: any, index: any) => { const hdel = async (field: any, index: any) => {
// 如果是新增操作,则直接数组移除即可 // 如果是新增操作,则直接数组移除即可
if (state.operationType == 1) { if (state.operationType == 1) {
state.hashValues.splice(index, 1); state.hashValues.splice(index, 1);
@@ -200,9 +214,9 @@ export default defineComponent({
}); });
ElMessage.success('删除成功'); ElMessage.success('删除成功');
reHscan(); reHscan();
}; };
const hset = async (row: any) => { const hset = async (row: any) => {
await redisApi.saveHashValue.request({ await redisApi.saveHashValue.request({
id: state.redisId, id: state.redisId,
db: state.db, db: state.db,
@@ -216,13 +230,13 @@ export default defineComponent({
], ],
}); });
ElMessage.success('保存成功'); ElMessage.success('保存成功');
}; };
const onAddHashValue = () => { const onAddHashValue = () => {
state.hashValues.unshift({ field: '', value: '' }); state.hashValues.unshift({ field: '', value: '' });
}; };
const saveValue = async () => { const saveValue = async () => {
notEmpty(state.key.key, 'key不能为空'); notEmpty(state.key.key, 'key不能为空');
isTrue(state.hashValues.length > 0, 'hash内容不能为空'); isTrue(state.hashValues.length > 0, 'hash内容不能为空');
const sv = { value: state.hashValues, id: state.redisId, db: state.db }; const sv = { value: state.hashValues, id: state.redisId, db: state.db };
@@ -232,20 +246,7 @@ 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,30 +158,29 @@ export default defineComponent({
info: { info: {
type: [Boolean, Object], type: [Boolean, Object],
}, },
}, })
setup(props: any, { emit }) {
const state = reactive({
dialogVisible: false,
});
watch( const emit = defineEmits(['update:visible', 'close'])
const state = reactive({
dialogVisible: false,
});
const {
dialogVisible,
} = toRefs(state)
watch(
() => props.visible, () => props.visible,
(val) => { (val) => {
state.dialogVisible = val; state.dialogVisible = val;
} }
); );
const close = () => { const close = () => {
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,10 +74,11 @@ 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,
redisId: '', redisId: '',
@@ -107,9 +94,19 @@ export default defineComponent({
stop: 0, stop: 0,
pageNum: 1, pageNum: 1,
pageSize: 10, pageSize: 10,
}); });
const cancel = () => { const {
dialogVisible,
operationType,
key,
value,
len,
pageNum,
pageSize,
} = toRefs(state)
const cancel = () => {
emit('update:visible', false); emit('update:visible', false);
emit('cancel'); emit('cancel');
setTimeout(() => { setTimeout(() => {
@@ -120,9 +117,9 @@ export default defineComponent({
}; };
state.value = []; state.value = [];
}, 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;
@@ -133,9 +130,9 @@ export default defineComponent({
if (state.dialogVisible && state.operationType == 2) { if (state.dialogVisible && state.operationType == 2) {
getListValue(); getListValue();
} }
}); });
const getListValue = async () => { const getListValue = async () => {
const pageNum = state.pageNum; const pageNum = state.pageNum;
const pageSize = state.pageSize; const pageSize = state.pageSize;
const res = await redisApi.getListValue.request({ const res = await redisApi.getListValue.request({
@@ -151,9 +148,9 @@ export default defineComponent({
value: x, value: x,
}; };
}); });
}; };
const lset = async (row: any, rowIndex: number) => { const lset = async (row: any, rowIndex: number) => {
await redisApi.setListValue.request({ await redisApi.setListValue.request({
id: state.redisId, id: state.redisId,
db: state.db, db: state.db,
@@ -162,39 +159,28 @@ export default defineComponent({
value: row.value, value: row.value,
}); });
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,49 +125,82 @@ export default defineComponent({
trigger: ['change', 'blur'], trigger: ['change', 'blur'],
}, },
], ],
}, }
});
watch(props, async (newValue) => { const redisForm: any = ref(null);
const state = reactive({
dialogVisible: false,
sshTunnelMachineList: [],
form: {
id: null,
tagId: null as any,
tagPath: null as any,
name: null,
mode: 'standalone',
host: '',
password: null,
db: '',
project: null,
projectId: null,
envId: null,
env: null,
remark: '',
enableSshTunnel: null,
sshTunnelMachineId: null,
},
dbList: [0],
pwd: '',
btnLoading: false,
});
const {
dialogVisible,
sshTunnelMachineList,
form,
dbList,
pwd,
btnLoading,
} = toRefs(state)
watch(props, async (newValue: any) => {
state.dialogVisible = newValue.visible; 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 = [];
} }
getSshTunnelMachines(); getSshTunnelMachines();
}); });
const convertDb = (db: string) => { const convertDb = (db: string) => {
state.dbList = db.split(',').map((x) => Number.parseInt(x)); state.dbList = db.split(',').map((x) => Number.parseInt(x));
}; };
/** /**
* 改变表单中的数据库字段,方便表单错误提示。如全部删光,可提示请添加库号 * 改变表单中的数据库字段,方便表单错误提示。如全部删光,可提示请添加库号
*/ */
const changeDb = () => { const changeDb = () => {
state.form.db = state.dbList.length == 0 ? '' : state.dbList.join(','); state.form.db = state.dbList.length == 0 ? '' : state.dbList.join(',');
}; };
const getSshTunnelMachines = async () => { const getSshTunnelMachines = async () => {
if (state.form.enableSshTunnel == 1 && state.sshTunnelMachineList.length == 0) { if (state.form.enableSshTunnel == 1 && state.sshTunnelMachineList.length == 0) {
const res = await machineApi.list.request({ pageNum: 1, pageSize: 100 }); const res = await machineApi.list.request({ pageNum: 1, pageSize: 100 });
state.sshTunnelMachineList = res.list; state.sshTunnelMachineList = res.list;
} }
}; };
const getPwd = async () => { const getPwd = async () => {
state.pwd = await redisApi.getRedisPwd.request({ id: state.form.id }); state.pwd = await redisApi.getRedisPwd.request({ id: state.form.id });
}; };
const btnOk = async () => { const btnOk = async () => {
redisForm.value.validate(async (valid: boolean) => { redisForm.value.validate(async (valid: boolean) => {
if (valid) { if (valid) {
const reqForm = { ...state.form }; const reqForm = { ...state.form };
@@ -233,24 +224,13 @@ export default defineComponent({
return false; return false;
} }
}); });
}; };
const cancel = () => { const cancel = () => {
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,35 +121,23 @@
</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';
import { dateFormat } from '@/common/utils/date'; 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({ const state = reactive({
name: 'RedisList',
components: {
Info,
RedisEdit,
},
setup() {
const state = reactive({
tags: [], tags: [],
redisTable: [], redisTable: [],
total: 0, total: 0,
@@ -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,29 +168,40 @@ export default defineComponent({
}, },
redisEditDialog: { redisEditDialog: {
visible: false, visible: false,
data: null, data: null as any,
title: '新增redis', title: '新增redis',
}, },
}); });
onMounted(async () => { const {
tags,
redisTable,
total,
currentId,
query,
clusterInfoDialog,
infoDialog,
redisEditDialog,
} = toRefs(state)
onMounted(async () => {
search(); search();
}); });
const handlePageChange = (curPage: number) => { const handlePageChange = (curPage: number) => {
state.query.pageNum = curPage; state.query.pageNum = curPage;
search(); search();
}; };
const choose = (item: any) => { const choose = (item: any) => {
if (!item) { if (!item) {
return; return;
} }
state.currentId = item.id; state.currentId = item.id;
state.currentData = item; state.currentData = item;
}; };
const deleteRedis = async () => { const deleteRedis = async () => {
try { try {
await ElMessageBox.confirm(`确定删除该redis?`, '提示', { await ElMessageBox.confirm(`确定删除该redis?`, '提示', {
confirmButtonText: '确定', confirmButtonText: '确定',
@@ -237,10 +213,10 @@ export default defineComponent({
state.currentData = null; state.currentData = null;
state.currentId = null; state.currentId = null;
search(); search();
} 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];
@@ -249,27 +225,27 @@ export default defineComponent({
state.infoDialog.info = res; state.infoDialog.info = res;
state.infoDialog.title = `'${host}' info`; state.infoDialog.title = `'${host}' info`;
state.infoDialog.visible = true; state.infoDialog.visible = true;
}; };
const onShowClusterInfo = async (redis: any) => { const onShowClusterInfo = async (redis: any) => {
const ci = await redisApi.clusterInfo.request({ id: redis.id }); const ci = await redisApi.clusterInfo.request({ id: redis.id });
state.clusterInfoDialog.info = ci.clusterInfo; state.clusterInfoDialog.info = ci.clusterInfo;
state.clusterInfoDialog.nodes = ci.clusterNodes; state.clusterInfoDialog.nodes = ci.clusterNodes;
state.clusterInfoDialog.redisId = redis.id; state.clusterInfoDialog.redisId = redis.id;
state.clusterInfoDialog.visible = true; state.clusterInfoDialog.visible = true;
}; };
const search = async () => { const search = async () => {
const res = await redisApi.redisList.request(state.query); const res = await redisApi.redisList.request(state.query);
state.redisTable = res.list; state.redisTable = res.list;
state.total = res.total; state.total = res.total;
}; };
const getTags = async () => { const getTags = async () => {
state.tags = await tagApi.getAccountTags.request(null); state.tags = await tagApi.getAccountTags.request(null);
}; };
const editRedis = async (isAdd = false) => { const editRedis = async (isAdd = false) => {
if (isAdd) { if (isAdd) {
state.redisEditDialog.data = null; state.redisEditDialog.data = null;
state.redisEditDialog.title = '新增redis'; state.redisEditDialog.title = '新增redis';
@@ -278,19 +254,19 @@ export default defineComponent({
state.redisEditDialog.title = '修改redis'; state.redisEditDialog.title = '修改redis';
} }
state.redisEditDialog.visible = true; state.redisEditDialog.visible = true;
}; };
const valChange = () => { const valChange = () => {
state.currentId = null; state.currentId = null;
state.currentData = null; state.currentData = null;
search(); search();
}; };
// 打开redis数据操作页 // 打开redis数据操作页
const openDataOpt = (row : any) => { const openDataOpt = (row: any) => {
const {tagPath, id, db} = row; const { tagPath, id, db } = row;
// 判断db是否发生改变 // 判断db是否发生改变
let oldDbId = store.state.redisDbOptInfo.dbOptInfo.dbId; let oldDbId = store.state.redisDbOptInfo.dbOptInfo.dbId;
if(oldDbId !== id){ if (oldDbId !== id) {
let params = { let params = {
tagPath, tagPath,
dbId: id, dbId: id,
@@ -298,26 +274,10 @@ export default defineComponent({
} }
store.dispatch('redisDbOptInfo/setRedisDbOptInfo', params); store.dispatch('redisDbOptInfo/setRedisDbOptInfo', params);
} }
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,10 +66,11 @@ 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,
redisId: '', redisId: '',
@@ -81,9 +81,16 @@ export default defineComponent({
timed: -1, timed: -1,
}, },
value: [{ value: '' }], value: [{ value: '' }],
}); });
const cancel = () => { const {
dialogVisible,
operationType,
key,
value,
} = toRefs(state)
const cancel = () => {
emit('update:visible', false); emit('update:visible', false);
emit('cancel'); emit('cancel');
setTimeout(() => { setTimeout(() => {
@@ -94,9 +101,9 @@ export default defineComponent({
}; };
state.value = []; state.value = [];
}, 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;
@@ -107,9 +114,9 @@ export default defineComponent({
if (state.dialogVisible && state.operationType == 2) { if (state.dialogVisible && state.operationType == 2) {
getSetValue(); getSetValue();
} }
}); });
const getSetValue = async () => { const getSetValue = async () => {
const res = await redisApi.getSetValue.request({ const res = await redisApi.getSetValue.request({
id: state.redisId, id: state.redisId,
db: state.db, db: state.db,
@@ -120,9 +127,9 @@ export default defineComponent({
value: x, value: x,
}; };
}); });
}; };
const saveValue = async () => { const saveValue = async () => {
notEmpty(state.key.key, 'key不能为空'); notEmpty(state.key.key, 'key不能为空');
isTrue(state.value.length > 0, 'set内容不能为空'); isTrue(state.value.length > 0, 'set内容不能为空');
const sv = { value: state.value.map((x) => x.value), id: state.redisId, db: state.db }; const sv = { value: state.value.map((x) => x.value), id: state.redisId, db: state.db };
@@ -132,20 +139,11 @@ export default defineComponent({
ElMessage.success('数据保存成功'); ElMessage.success('数据保存成功');
cancel(); cancel();
emit('valChange'); emit('valChange');
}; };
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,10 +57,11 @@ 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,
redisId: '', redisId: '',
@@ -76,9 +75,16 @@ export default defineComponent({
type: 'text', type: 'text',
value: '', value: '',
}, },
}); });
const cancel = () => { const {
dialogVisible,
operationType,
key,
string,
} = toRefs(state)
const cancel = () => {
emit('update:visible', false); emit('update:visible', false);
emit('cancel'); emit('cancel');
setTimeout(() => { setTimeout(() => {
@@ -90,30 +96,30 @@ export default defineComponent({
state.string.value = ''; state.string.value = '';
state.string.type = 'text'; state.string.type = 'text';
}, 500); }, 500);
}; };
watch( watch(
() => props.visible, () => props.visible,
(val) => { (val) => {
state.dialogVisible = val; state.dialogVisible = val;
} }
); );
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;
@@ -124,17 +130,17 @@ export default defineComponent({
if (state.dialogVisible && state.operationType == 2) { if (state.dialogVisible && state.operationType == 2) {
getStringValue(); getStringValue();
} }
}); });
const getStringValue = async () => { const getStringValue = async () => {
state.string.value = await redisApi.getStringValue.request({ state.string.value = await redisApi.getStringValue.request({
id: state.redisId, id: state.redisId,
db: state.db, db: state.db,
key: state.key.key, key: state.key.key,
}); });
}; };
const saveValue = async () => { const saveValue = async () => {
notEmpty(state.key.key, 'key不能为空'); notEmpty(state.key.key, 'key不能为空');
notEmpty(state.string.value, 'value不能为空'); notEmpty(state.string.value, 'value不能为空');
@@ -144,10 +150,10 @@ export default defineComponent({
ElMessage.success('数据保存成功'); ElMessage.success('数据保存成功');
cancel(); cancel();
emit('valChange'); emit('valChange');
}; };
// 更改文本类型 // 更改文本类型
const onChangeTextType = (val: string) => { const onChangeTextType = (val: string) => {
if (val == 'json') { if (val == 'json') {
state.string.value = formatJsonString(state.string.value, false); state.string.value = formatJsonString(state.string.value, false);
return; return;
@@ -155,16 +161,7 @@ export default defineComponent({
if (val == 'text') { if (val == 'text') {
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;
const tagForm: any = ref(null); children?: Tree[];
const state = reactive({ }
const tagForm: any = ref(null);
const tagTreeRef: any = ref(null);
const filterTag = ref('');
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,24 +159,32 @@ export default defineComponent({
// }, // },
], ],
name: [{ required: true, message: '名称不能为空', trigger: 'blur' }], name: [{ required: true, message: '名称不能为空', trigger: 'blur' }],
}, };
});
onMounted(() => { onMounted(() => {
search(); search();
}); });
const search = async () => { watch(filterTag, (val) => {
tagTreeRef.value!.filter(val);
});
const filterNode = (value: string, data: Tree) => {
if (!value) return true;
return data.codePath.includes(value) || data.name.includes(value);
};
const search = async () => {
let res = await tagApi.getTagTrees.request(null); let res = await tagApi.getTagTrees.request(null);
state.data = res; state.data = res;
}; };
const info = async (data: any) => { const info = async (data: any) => {
state.infoDialog.data = data; state.infoDialog.data = data;
state.infoDialog.visible = true; state.infoDialog.visible = true;
}; };
const showSaveTabDialog = (data: any) => { const showSaveTabDialog = (data: any) => {
if (data) { if (data) {
state.saveTabDialog.form.pid = data.id; state.saveTabDialog.form.pid = data.id;
state.saveTabDialog.title = `新增 [${data.codePath}] 子标签信息`; state.saveTabDialog.title = `新增 [${data.codePath}] 子标签信息`;
@@ -184,18 +192,18 @@ export default defineComponent({
state.saveTabDialog.title = '新增根标签信息'; state.saveTabDialog.title = '新增根标签信息';
} }
state.saveTabDialog.visible = true; state.saveTabDialog.visible = true;
}; };
const showEditTagDialog = (data: any) => { const showEditTagDialog = (data: any) => {
state.saveTabDialog.form.id = data.id; state.saveTabDialog.form.id = data.id;
state.saveTabDialog.form.code = data.code; state.saveTabDialog.form.code = data.code;
state.saveTabDialog.form.name = data.name; state.saveTabDialog.form.name = data.name;
state.saveTabDialog.form.remark = data.remark; state.saveTabDialog.form.remark = data.remark;
state.saveTabDialog.title = `修改 [${data.codePath}] 信息`; state.saveTabDialog.title = `修改 [${data.codePath}] 信息`;
state.saveTabDialog.visible = true; state.saveTabDialog.visible = true;
}; };
const saveTag = async () => { const saveTag = async () => {
tagForm.value.validate(async (valid: any) => { tagForm.value.validate(async (valid: any) => {
if (valid) { if (valid) {
const form = state.saveTabDialog.form; const form = state.saveTabDialog.form;
@@ -205,15 +213,15 @@ export default defineComponent({
cancelSaveTag(); cancelSaveTag();
} }
}); });
}; };
const cancelSaveTag = () => { const cancelSaveTag = () => {
state.saveTabDialog.visible = false; state.saveTabDialog.visible = false;
state.saveTabDialog.form = {} as any; state.saveTabDialog.form = {} as any;
tagForm.value.resetFields(); tagForm.value.resetFields();
}; };
const deleteTag = (data: any) => { const deleteTag = (data: any) => {
ElMessageBox.confirm(`此操作将删除 [${data.codePath}], 是否继续?`, '提示', { ElMessageBox.confirm(`此操作将删除 [${data.codePath}], 是否继续?`, '提示', {
confirmButtonText: '确定', confirmButtonText: '确定',
cancelButtonText: '取消', cancelButtonText: '取消',
@@ -223,27 +231,27 @@ export default defineComponent({
ElMessage.success('删除成功!'); ElMessage.success('删除成功!');
search(); search();
}); });
}; };
// const changeStatus = async (data: any, status: any) => { // const changeStatus = async (data: any, status: any) => {
// await resourceApi.changeStatus.request({ // await resourceApi.changeStatus.request({
// id: data.id, // id: data.id,
// status: status, // status: status,
// }); // });
// data.status = status; // data.status = status;
// ElMessage.success((status === 1 ? '启用' : '禁用') + '成功!'); // ElMessage.success((status === 1 ? '启用' : '禁用') + '成功!');
// }; // };
// 节点被展开时触发的事件 // 节点被展开时触发的事件
const handleNodeExpand = (data: any, node: any) => { const handleNodeExpand = (data: any, node: any) => {
const id: any = node.data.id; const id: any = node.data.id;
if (!state.defaultExpandedKeys.includes(id)) { if (!state.defaultExpandedKeys.includes(id)) {
state.defaultExpandedKeys.push(id); state.defaultExpandedKeys.push(id);
} }
}; };
// 关闭节点 // 关闭节点
const handleNodeCollapse = (data: any, node: any) => { const handleNodeCollapse = (data: any, node: any) => {
removeDeafultExpandId(node.data.id); removeDeafultExpandId(node.data.id);
let childNodes = node.childNodes; let childNodes = node.childNodes;
@@ -254,34 +262,19 @@ export default defineComponent({
// 递归删除展开的子节点节点id // 递归删除展开的子节点节点id
handleNodeCollapse(data, cn); handleNodeCollapse(data, cn);
} }
}; };
const removeDeafultExpandId = (id: any) => { const removeDeafultExpandId = (id: any) => {
let index = state.defaultExpandedKeys.indexOf(id); let index = state.defaultExpandedKeys.indexOf(id);
if (index > -1) { if (index > -1) {
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', const teamForm: any = ref(null);
components: {}, const tagTreeRef: any = ref(null);
setup() { const state = reactive({
const teamForm: any = ref(null);
const tagTreeRef: any = ref(null);
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(),
@@ -223,32 +206,43 @@ export default defineComponent({
children: 'children', children: 'children',
}, },
}, },
}); });
onMounted(() => { const {
query,
addTeamDialog,
total,
data,
chooseId,
chooseData,
showMemDialog,
showTagDialog,
} = toRefs(state)
onMounted(() => {
search(); search();
}); });
const search = async () => { const search = async () => {
let res = await tagApi.getTeams.request(state.query); let res = await tagApi.getTeams.request(state.query);
state.data = res.list; state.data = res.list;
state.total = res.total; state.total = res.total;
}; };
const handlePageChange = (curPage: number) => { const handlePageChange = (curPage: number) => {
state.query.pageNum = curPage; state.query.pageNum = curPage;
search(); search();
}; };
const choose = (item: any) => { const choose = (item: any) => {
if (!item) { if (!item) {
return; return;
} }
state.chooseId = item.id; state.chooseId = item.id;
state.chooseData = item; state.chooseData = item;
}; };
const showSaveTeamDialog = (data: any) => { const showSaveTeamDialog = (data: any) => {
if (data) { if (data) {
state.addTeamDialog.form.id = data.id; state.addTeamDialog.form.id = data.id;
state.addTeamDialog.form.name = data.name; state.addTeamDialog.form.name = data.name;
@@ -256,9 +250,9 @@ export default defineComponent({
state.addTeamDialog.title = `修改 [${data.codePath}] 信息`; state.addTeamDialog.title = `修改 [${data.codePath}] 信息`;
} }
state.addTeamDialog.visible = true; state.addTeamDialog.visible = true;
}; };
const saveTeam = async () => { const saveTeam = async () => {
teamForm.value.validate(async (valid: any) => { teamForm.value.validate(async (valid: any) => {
if (valid) { if (valid) {
const form = state.addTeamDialog.form; const form = state.addTeamDialog.form;
@@ -268,15 +262,15 @@ export default defineComponent({
cancelSaveTeam(); cancelSaveTeam();
} }
}); });
}; };
const cancelSaveTeam = () => { const cancelSaveTeam = () => {
state.addTeamDialog.visible = false; state.addTeamDialog.visible = false;
state.addTeamDialog.form = {} as any; state.addTeamDialog.form = {} as any;
teamForm.value.resetFields(); teamForm.value.resetFields();
}; };
const deleteTeam = (data: any) => { const deleteTeam = (data: any) => {
ElMessageBox.confirm(`此操作将删除 [${data.name}], 是否继续?`, '提示', { ElMessageBox.confirm(`此操作将删除 [${data.name}], 是否继续?`, '提示', {
confirmButtonText: '确定', confirmButtonText: '确定',
cancelButtonText: '取消', cancelButtonText: '取消',
@@ -286,99 +280,99 @@ export default defineComponent({
ElMessage.success('删除成功!'); ElMessage.success('删除成功!');
search(); search();
}); });
}; };
/********** 团队成员相关 ***********/ /********** 团队成员相关 ***********/
const showMembers = async (team: any) => { const showMembers = async (team: any) => {
state.showMemDialog.query.teamId = team.id; state.showMemDialog.query.teamId = team.id;
await setMemebers(); await setMemebers();
state.showMemDialog.title = `[${team.name}] 成员信息`; state.showMemDialog.title = `[${team.name}] 成员信息`;
state.showMemDialog.visible = true; state.showMemDialog.visible = true;
}; };
const getAccount = (username: any) => { const getAccount = (username: any) => {
accountApi.list.request({ username }).then((res) => { accountApi.list.request({ username }).then((res) => {
state.showMemDialog.accounts = res.list; state.showMemDialog.accounts = res.list;
}); });
}; };
/** /**
* 选中成员 * 选中成员
*/ */
const chooseMember = (item: any) => { const chooseMember = (item: any) => {
if (!item) { if (!item) {
return; return;
} }
state.showMemDialog.chooseData = item; state.showMemDialog.chooseData = item;
state.showMemDialog.chooseId = item.id; state.showMemDialog.chooseId = item.id;
}; };
const deleteMember = async () => { const deleteMember = async () => {
await tagApi.delTeamMem.request(state.showMemDialog.chooseData); await tagApi.delTeamMem.request(state.showMemDialog.chooseData);
ElMessage.success('移除成功'); ElMessage.success('移除成功');
// 重新赋值成员列表 // 重新赋值成员列表
setMemebers(); setMemebers();
}; };
/** /**
* 设置成员列表信息 * 设置成员列表信息
*/ */
const setMemebers = async () => { const setMemebers = async () => {
const res = await tagApi.getTeamMem.request(state.showMemDialog.query); const res = await tagApi.getTeamMem.request(state.showMemDialog.query);
state.showMemDialog.members.list = res.list; state.showMemDialog.members.list = res.list;
state.showMemDialog.members.total = res.total; state.showMemDialog.members.total = res.total;
}; };
const showAddMemberDialog = () => { const showAddMemberDialog = () => {
state.showMemDialog.addVisible = true; state.showMemDialog.addVisible = true;
}; };
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('保存成功');
setMemebers(); setMemebers();
cancelAddMember(); cancelAddMember();
}; };
const cancelAddMember = () => { const cancelAddMember = () => {
state.showMemDialog.memForm = {} as any; state.showMemDialog.memForm = {} as any;
state.showMemDialog.addVisible = false; state.showMemDialog.addVisible = false;
state.showMemDialog.chooseData = null; state.showMemDialog.chooseData = null;
state.showMemDialog.chooseId = 0; state.showMemDialog.chooseId = 0;
}; };
/********** 标签相关 ***********/ /********** 标签相关 ***********/
const showTags = async (team: any) => { const showTags = async (team: any) => {
state.showTagDialog.tags = await tagApi.getTagTrees.request(null); state.showTagDialog.tags = await tagApi.getTagTrees.request(null);
state.showTagDialog.tagTreeTeams = await tagApi.getTeamTagIds.request({ teamId: team.id }); state.showTagDialog.tagTreeTeams = await tagApi.getTeamTagIds.request({ teamId: team.id });
state.showTagDialog.title = `[${team.name}] 团队标签信息`; state.showTagDialog.title = `[${team.name}] 团队标签信息`;
state.showTagDialog.teamId = team.id; state.showTagDialog.teamId = team.id;
state.showTagDialog.visible = true; state.showTagDialog.visible = true;
}; };
const closeTagDialog = () => { const closeTagDialog = () => {
state.showTagDialog.visible = false; state.showTagDialog.visible = false;
setTimeout(() => { setTimeout(() => {
state.showTagDialog.tagTreeTeams = []; state.showTagDialog.tagTreeTeams = [];
}, 500); }, 500);
}; };
const saveTags = async () => { const saveTags = async () => {
await tagApi.saveTeamTags.request({ await tagApi.saveTeamTags.request({
teamId: state.showTagDialog.teamId, teamId: state.showTagDialog.teamId,
tagIds: state.showTagDialog.tagTreeTeams, tagIds: state.showTagDialog.tagTreeTeams,
}); });
ElMessage.success('保存成功'); ElMessage.success('保存成功');
closeTagDialog(); closeTagDialog();
}; };
const tagTreeNodeCheck = (data: any, checkInfo: any) => { const tagTreeNodeCheck = (data: any, checkInfo: any) => {
const node = tagTreeRef.value.getNode(data.id); const node = tagTreeRef.value.getNode(data.id);
console.log(node); console.log(node);
// state.showTagDialog.tagTreeTeams = [16] // state.showTagDialog.tagTreeTeams = [16]
@@ -395,7 +389,7 @@ export default defineComponent({
} }
console.log(data); console.log(data);
console.log(checkInfo); console.log(checkInfo);
}; };
// function removeCheckedTagId(id: any) { // function removeCheckedTagId(id: any) {
// console.log(state.showTagDialog.tagTreeTeams); // console.log(state.showTagDialog.tagTreeTeams);
@@ -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,18 +173,17 @@
</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,95 +199,92 @@ export default {
total: null, total: null,
}, },
}, },
recommendList, recommendList: [],
accountForm: { accountForm: {
password: '', password: '',
}, },
}); });
// 当前时间提示语
const currentTime = computed(() => { const {
msgDialog,
accountForm,
} = toRefs(state)
// 当前时间提示语
const currentTime = computed(() => {
return formatAxis(new Date()); return formatAxis(new Date());
}); });
// 获取用户信息 vuex // 获取用户信息 vuex
const getUserInfos = computed(() => { const getUserInfos = computed(() => {
return store.state.userInfos.userInfos; return store.state.userInfos.userInfos;
}); });
const showMsgs = () => { const showMsgs = () => {
state.msgDialog.visible = true; state.msgDialog.visible = true;
}; };
const roleInfo = computed(() => { const roleInfo = computed(() => {
if (state.accountInfo.roles.length == 0) { if (state.accountInfo.roles.length == 0) {
return ''; return '';
} }
return state.accountInfo.roles.map((val: any) => val.name).join('、'); return state.accountInfo.roles.map((val: any) => val.name).join('、');
}); });
onMounted(() => { onMounted(() => {
getAccountInfo(); getAccountInfo();
getMsgs(); getMsgs();
}); });
const getAccountInfo = async () => { const getAccountInfo = async () => {
state.accountInfo = await personApi.accountInfo.request(); state.accountInfo = await personApi.accountInfo.request();
}; };
const updateAccount = async () => { const updateAccount = async () => {
await personApi.updateAccount.request(state.accountForm); await personApi.updateAccount.request(state.accountForm);
ElMessage.success('更新成功'); ElMessage.success('更新成功');
}; };
const getMsgs = async () => { const getMsgs = async () => {
const res = await personApi.getMsgs.request(state.msgDialog.query); const res = await personApi.getMsgs.request(state.msgDialog.query);
state.msgDialog.msgs = res; state.msgDialog.msgs = res;
}; };
const getMsgTypeDesc = (type: number) => { const getMsgTypeDesc = (type: number) => {
if (type == 1) { if (type == 1) {
return '登录'; return '登录';
} }
if (type == 2) { if (type == 2) {
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,
}, },
})
//定义事件
const emit = defineEmits(['update:visible', 'cancel', 'val-change'])
const accountForm: any = ref(null);
const rules = {
name: [
{
required: true,
message: '请输入姓名',
trigger: ['change', 'blur'],
}, },
setup(props: any, { emit }) { ],
const accountForm: any = ref(null);
const state = reactive({
dialogVisible: false,
edit: false,
form: {
id: null,
username: null,
password: null,
repassword: null,
},
btnLoading: false,
rules: {
username: [ username: [
{ {
required: true, required: true,
@@ -58,17 +63,29 @@ export default defineComponent({
trigger: ['change', 'blur'], trigger: ['change', 'blur'],
}, },
], ],
// password: [ }
// {
// required: true,
// message: '请输入密码',
// trigger: ['change', 'blur'],
// },
// ],
},
});
watch(props, (newValue) => { const state = reactive({
dialogVisible: false,
edit: false,
form: {
id: null,
name: null,
username: null,
password: null,
repassword: null,
},
btnLoading: false
});
const {
dialogVisible,
edit,
form,
btnLoading,
} = toRefs(state)
watch(props, (newValue: any) => {
if (newValue.account) { if (newValue.account) {
state.form = { ...newValue.account }; state.form = { ...newValue.account };
state.edit = true; state.edit = true;
@@ -77,9 +94,9 @@ export default defineComponent({
state.form = {} as any; state.form = {} as any;
} }
state.dialogVisible = newValue.visible; state.dialogVisible = newValue.visible;
}); });
const btnOk = async () => { const btnOk = async () => {
accountForm.value.validate((valid: boolean) => { accountForm.value.validate((valid: boolean) => {
if (valid) { if (valid) {
accountApi.save.request(state.form).then(() => { accountApi.save.request(state.form).then(() => {
@@ -98,21 +115,13 @@ export default defineComponent({
return false; return false;
} }
}); });
}; };
const cancel = () => { const cancel = () => {
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,49 +80,42 @@
<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: { const state = reactive({
RoleEdit,
AccountEdit,
},
setup() {
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,34 +147,45 @@ 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,
}, },
}); });
onMounted(() => { const {
chooseId,
query,
datas,
total,
showRoleDialog,
showResourceDialog,
roleDialog,
accountDialog,
} = toRefs(state)
onMounted(() => {
search(); search();
}); });
const choose = (item: any) => { const choose = (item: any) => {
if (!item) { if (!item) {
return; return;
} }
state.chooseId = item.id; state.chooseId = item.id;
state.chooseData = item; state.chooseData = item;
}; };
const search = async () => { const search = async () => {
let res: any = await accountApi.list.request(state.query); let res: any = await accountApi.list.request(state.query);
state.datas = res.list; state.datas = res.list;
state.total = res.total; state.total = res.total;
}; };
const showResources = async (row: any) => { const showResources = async (row: any) => {
let showResourceDialog = state.showResourceDialog; let showResourceDialog = state.showResourceDialog;
showResourceDialog.title = '"' + row.username + '" 的菜单&权限'; showResourceDialog.title = '"' + row.username + '" 的菜单&权限';
showResourceDialog.resources = []; showResourceDialog.resources = [];
@@ -213,18 +193,18 @@ export default defineComponent({
id: row.id, id: row.id,
}); });
showResourceDialog.visible = true; showResourceDialog.visible = true;
}; };
const showRoles = async (row: any) => { const showRoles = async (row: any) => {
let showRoleDialog = state.showRoleDialog; let showRoleDialog = state.showRoleDialog;
showRoleDialog.title = '"' + row.username + '" 的角色信息'; showRoleDialog.title = '"' + row.username + '" 的角色信息';
showRoleDialog.accountRoles = await accountApi.roles.request({ showRoleDialog.accountRoles = await accountApi.roles.request({
id: row.id, id: row.id,
}); });
showRoleDialog.visible = true; showRoleDialog.visible = true;
}; };
const changeStatus = async (row: any) => { const changeStatus = async (row: any) => {
let id = row.id; let id = row.id;
let status = row.status == -1 ? 1 : -1; let status = row.status == -1 ? 1 : -1;
await accountApi.changeStatus.request({ await accountApi.changeStatus.request({
@@ -233,42 +213,42 @@ export default defineComponent({
}); });
ElMessage.success('操作成功'); ElMessage.success('操作成功');
search(); search();
}; };
const handlePageChange = (curPage: number) => { const handlePageChange = (curPage: number) => {
state.query.pageNum = curPage; state.query.pageNum = curPage;
search(); search();
}; };
const roleEdit = () => { const showRoleEdit = () => {
if (!state.chooseId) { if (!state.chooseId) {
ElMessage.error('请选择账号'); ElMessage.error('请选择账号');
} }
state.roleDialog.visible = true; state.roleDialog.visible = true;
state.roleDialog.account = state.chooseData; state.roleDialog.account = state.chooseData;
}; };
const editAccount = (isAdd = false) => { const editAccount = (isAdd = false) => {
if (isAdd) { if (isAdd) {
state.accountDialog.data = null; state.accountDialog.data = null;
} else { } else {
state.accountDialog.data = state.chooseData; state.accountDialog.data = state.chooseData;
} }
state.accountDialog.visible = true; state.accountDialog.visible = true;
}; };
const cancel = () => { const cancel = () => {
state.roleDialog.visible = false; state.roleDialog.visible = false;
state.roleDialog.account = null; state.roleDialog.account = null;
search(); search();
}; };
const valChange = () => { const valChange = () => {
state.accountDialog.visible = false; state.accountDialog.visible = false;
search(); search();
}; };
const deleteAccount = async () => { const deleteAccount = async () => {
try { try {
await ElMessageBox.confirm(`确定删除该账号?`, '提示', { await ElMessageBox.confirm(`确定删除该账号?`, '提示', {
confirmButtonText: '确定', confirmButtonText: '确定',
@@ -280,26 +260,9 @@ export default defineComponent({
state.chooseData = null; state.chooseData = null;
state.chooseId = null; state.chooseId = null;
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,64 +33,72 @@
</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 state = reactive({ const roleTable: any = ref(null);
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,
pageSize: 5, pageSize: 5,
}, },
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;
} }
}); });
const handlePageChange = () => { const handlePageChange = () => {
search(); search();
}; };
const selectable = (row: any) => { const selectable = (row: any) => {
// 角色code不以COMMON开头才可勾选 // 角色code不以COMMON开头才可勾选
return row.code.indexOf('COMMON') != 0; return row.code.indexOf('COMMON') != 0;
}; };
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++) {
@@ -112,70 +111,56 @@ export default defineComponent({
} else { } else {
roles.push(row.id); roles.push(row.id);
} }
}; };
/** /**
* 检查是否勾选权限,即是否拥有权限 * 检查是否勾选权限,即是否拥有权限
*/ */
const checkSelected = () => { const checkSelected = () => {
// 必须用异步,否则勾选不了 // 必须用异步,否则勾选不了
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);
} }
}); });
}, 50); }, 50);
}; };
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('保存成功!');
cancel(); cancel();
}; };
/** /**
* 取消 * 取消
*/ */
const cancel = () => { const cancel = () => {
state.query.pageNum = 1; state.query.pageNum = 1;
state.query.name = null; state.query.name = null;
emit('update:visible', false); emit('update:visible', false);
emit('cancel'); emit('cancel');
}; };
/** /**
* 清空查询框 * 清空查询框
*/ */
const clear = () => { const clear = () => {
state.query.pageNum = 1; state.query.pageNum = 1;
state.query.name = null; state.query.name = null;
search(); search();
}; };
const search = async () => { const search = async () => {
let res = await roleApi.list.request(state.query); let res = await roleApi.list.request(state.query);
state.allRole = res.list; state.allRole = res.list;
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,10 +67,15 @@ export default defineComponent({
title: { title: {
type: String, type: String,
}, },
}, })
setup(props: any, { emit }) {
const configForm: any = ref(null); //定义事件
const state = reactive({ const emit = defineEmits(['update:visible', 'cancel', 'val-change'])
const configForm: any = ref(null);
const state = reactive({
dvisible: false, dvisible: false,
params: [] as any, params: [] as any,
form: { form: {
@@ -75,9 +87,16 @@ export default defineComponent({
remark: '', remark: '',
}, },
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 };
@@ -90,24 +109,24 @@ export default defineComponent({
state.form = {} as any; state.form = {} as any;
state.params = []; state.params = [];
} }
}); });
const onAddParam = () => { const onAddParam = () => {
state.params.push({ name: '', model: '', placeholder: '' }); state.params.push({ name: '', model: '', placeholder: '' });
}; };
const onDeleteParam = (idx: number) => { const onDeleteParam = (idx: number) => {
state.params.splice(idx, 1); state.params.splice(idx, 1);
}; };
const cancel = () => { const cancel = () => {
// 更新父组件visible prop对应的值为false // 更新父组件visible prop对应的值为false
emit('update:visible', false); emit('update:visible', false);
// 若父组件有取消事件,则调用 // 若父组件有取消事件,则调用
emit('cancel'); emit('cancel');
}; };
const btnOk = async () => { const btnOk = async () => {
configForm.value.validate(async (valid: boolean) => { configForm.value.validate(async (valid: boolean) => {
if (valid) { if (valid) {
if (state.params) { if (state.params) {
@@ -122,18 +141,8 @@ export default defineComponent({
}, 1000); }, 1000);
} }
}); });
}; };
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: { const state = reactive({
ConfigEdit,
},
setup() {
const state = reactive({
dialogFormVisible: false,
currentEditPermissions: false,
query: { query: {
pageNum: 1, pageNum: 1,
pageSize: 10, pageSize: 10,
@@ -122,24 +98,34 @@ export default defineComponent({
visible: false, visible: false,
config: {}, config: {},
}, },
}); });
onMounted(() => { const {
query,
total,
configs,
chooseId,
chooseData,
paramsDialog,
configEdit,
} = toRefs(state)
onMounted(() => {
search(); search();
}); });
const search = async () => { const search = async () => {
let res = await configApi.list.request(state.query); let res = await configApi.list.request(state.query);
state.configs = res.list; state.configs = res.list;
state.total = res.total; state.total = res.total;
}; };
const handlePageChange = (curPage: number) => { const handlePageChange = (curPage: number) => {
state.query.pageNum = curPage; state.query.pageNum = curPage;
search(); search();
}; };
const showSetConfigDialog = (row: any) => { const showSetConfigDialog = (row: any) => {
state.paramsDialog.config = row; state.paramsDialog.config = row;
// 存在配置项则弹窗提示输入对应的配置项 // 存在配置项则弹窗提示输入对应的配置项
if (row.params) { if (row.params) {
@@ -153,18 +139,18 @@ export default defineComponent({
state.paramsDialog.params = row.value; state.paramsDialog.params = row.value;
} }
state.paramsDialog.visible = true; state.paramsDialog.visible = true;
}; };
const closeSetConfigDialog = () => { const closeSetConfigDialog = () => {
state.paramsDialog.visible = false; state.paramsDialog.visible = false;
setTimeout(() => { setTimeout(() => {
state.paramsDialog.config = {}; state.paramsDialog.config = {};
state.paramsDialog.params = {}; state.paramsDialog.params = {};
state.paramsDialog.paramsFormItem = []; state.paramsDialog.paramsFormItem = [];
}, 300); }, 300);
}; };
const setConfig = async () => { const setConfig = async () => {
let paramsValue = state.paramsDialog.params; let paramsValue = state.paramsDialog.params;
if (state.paramsDialog.paramsFormItem.length > 0) { if (state.paramsDialog.paramsFormItem.length > 0) {
// 如果配置项删除则需要将value中对应的字段移除 // 如果配置项删除则需要将value中对应的字段移除
@@ -184,33 +170,33 @@ export default defineComponent({
ElMessage.success('保存成功'); ElMessage.success('保存成功');
closeSetConfigDialog(); closeSetConfigDialog();
search(); search();
}; };
const hasParam = (paramKey: string, paramItems: any) => { const hasParam = (paramKey: string, paramItems: any) => {
for (let paramItem of paramItems) { for (let paramItem of paramItems) {
if (paramItem.model == paramKey) { if (paramItem.model == paramKey) {
return true; return true;
} }
} }
return false; return false;
}; };
const choose = (item: any) => { const choose = (item: any) => {
if (!item) { if (!item) {
return; return;
} }
state.chooseId = item.id; state.chooseId = item.id;
state.chooseData = item; state.chooseData = item;
}; };
const configEditChange = () => { const configEditChange = () => {
ElMessage.success('保存成功'); ElMessage.success('保存成功');
state.chooseId = null; state.chooseId = null;
state.chooseData = null; state.chooseData = null;
search(); search();
}; };
const editConfig = (data: any) => { const editConfig = (data: any) => {
if (data) { if (data) {
state.configEdit.config = data; state.configEdit.config = data;
} else { } else {
@@ -218,21 +204,8 @@ 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

@@ -5,8 +5,9 @@
<el-row :gutter="10"> <el-row :gutter="10">
<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,11 +115,16 @@ export default defineComponent({
typeDisabled: { typeDisabled: {
type: Boolean, type: Boolean,
}, },
}, })
setup(props: any, { emit }) {
const menuForm: any = ref(null);
const defaultMeta = { //定义事件
const emit = defineEmits(['update:visible', 'cancel', 'val-change'])
const menuForm: any = ref(null);
const menuTypeValue = enums.ResourceTypeEnum['MENU'].value
const defaultMeta = {
routeName: '', routeName: '',
icon: 'Menu', icon: 'Menu',
redirect: '', redirect: '',
@@ -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 };
@@ -213,16 +212,16 @@ export default defineComponent({
state.form.meta.isHide = meta.isHide ? true : false; state.form.meta.isHide = meta.isHide ? true : false;
state.form.meta.isAffix = meta.isAffix ? true : false; state.form.meta.isAffix = meta.isAffix ? true : false;
state.form.meta.isIframe = meta.isIframe ? true : false; state.form.meta.isIframe = meta.isIframe ? true : false;
}); });
// 改变iframe字段如果为是则设置默认的组件 // 改变iframe字段如果为是则设置默认的组件
const changeIsIframe = (value: boolean) => { const changeIsIframe = (value: boolean) => {
if (value) { if (value) {
state.form.meta.component = 'RouterParent'; state.form.meta.component = 'RouterParent';
} }
}; };
const btnOk = () => { const btnOk = () => {
const submitForm = { ...state.form }; const submitForm = { ...state.form };
if (submitForm.type == 1) { if (submitForm.type == 1) {
// 如果是菜单则解析meta如果值为false或者''则去除该值 // 如果是菜单则解析meta如果值为false或者''则去除该值
@@ -247,9 +246,9 @@ export default defineComponent({
return false; return false;
} }
}); });
}; };
const parseMenuMeta = (meta: any) => { const parseMenuMeta = (meta: any) => {
let metaForm: any = {}; let metaForm: any = {};
// 如果是菜单则校验meta // 如果是菜单则校验meta
notEmpty(meta.routeName, '路由名不能为空'); notEmpty(meta.routeName, '路由名不能为空');
@@ -279,23 +278,12 @@ export default defineComponent({
metaForm.icon = meta.icon; metaForm.icon = meta.icon;
} }
return metaForm; return metaForm;
}; };
const cancel = () => { const cancel = () => {
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';
export default defineComponent({ const menuTypeValue = enums.ResourceTypeEnum['MENU'].value
name: 'ResourceList', const permissionTypeValue = enums.ResourceTypeEnum['PERMISSION'].value
components: { const props = {
ResourceEdit, label: 'name',
}, children: 'children',
setup() { }
const state = reactive({
menuTypeValue: enums.ResourceTypeEnum['MENU'].value, const state = reactive({
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,28 +127,42 @@ 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[],
}); });
onMounted(() => {
const {
dialogForm,
infoDialog,
data,
defaultExpandedKeys,
} = toRefs(state)
onMounted(() => {
search(); search();
}); });
const search = async () => { const search = async () => {
let res = await resourceApi.list.request(null); let res = await resourceApi.list.request(null);
state.data = res; state.data = res;
}; };
const deleteMenu = (data: any) => { const deleteMenu = (data: any) => {
ElMessageBox.confirm(`此操作将删除 [${data.name}], 是否继续?`, '提示', { ElMessageBox.confirm(`此操作将删除 [${data.name}], 是否继续?`, '提示', {
confirmButtonText: '确定', confirmButtonText: '确定',
cancelButtonText: '取消', cancelButtonText: '取消',
@@ -206,15 +178,15 @@ export default defineComponent({
search(); search();
}); });
}); });
}; };
const addResource = (data: any) => { const addResource = (data: any) => {
let dialog = state.dialogForm; let dialog = state.dialogForm;
dialog.data = { pid: 0, type: 1, weight: 1 }; dialog.data = { pid: 0, type: 1, weight: 1 };
// 添加顶级菜单情况 // 添加顶级菜单情况
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,23 +202,23 @@ 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;
} }
dialog.visible = true; dialog.visible = true;
}; };
const editResource = async (data: any) => { const editResource = async (data: any) => {
state.dialogForm.visible = true; state.dialogForm.visible = true;
const res = await resourceApi.detail.request({ const res = await resourceApi.detail.request({
id: data.id, id: data.id,
@@ -258,32 +230,32 @@ export default defineComponent({
state.dialogForm.data = res; state.dialogForm.data = res;
state.dialogForm.typeDisabled = true; state.dialogForm.typeDisabled = true;
state.dialogForm.title = '修改“' + data.name + '”菜单'; state.dialogForm.title = '修改“' + data.name + '”菜单';
}; };
const valChange = () => { const valChange = () => {
search(); search();
state.dialogForm.visible = false; state.dialogForm.visible = false;
}; };
const changeStatus = async (data: any, status: any) => { const changeStatus = async (data: any, status: any) => {
await resourceApi.changeStatus.request({ await resourceApi.changeStatus.request({
id: data.id, id: data.id,
status: status, status: status,
}); });
data.status = status; data.status = status;
ElMessage.success((status === 1 ? '启用' : '禁用') + '成功!'); ElMessage.success((status === 1 ? '启用' : '禁用') + '成功!');
}; };
// 节点被展开时触发的事件 // 节点被展开时触发的事件
const handleNodeExpand = (data: any, node: any) => { const handleNodeExpand = (data: any, node: any) => {
const id: any = node.data.id; const id: any = node.data.id;
if (!state.defaultExpandedKeys.includes(id)) { if (!state.defaultExpandedKeys.includes(id)) {
state.defaultExpandedKeys.push(id); state.defaultExpandedKeys.push(id);
} }
}; };
// 关闭节点 // 关闭节点
const handleNodeCollapse = (data: any, node: any) => { const handleNodeCollapse = (data: any, node: any) => {
removeDeafultExpandId(node.data.id); removeDeafultExpandId(node.data.id);
let childNodes = node.childNodes; let childNodes = node.childNodes;
@@ -297,38 +269,23 @@ export default defineComponent({
// 递归删除展开的子节点节点id // 递归删除展开的子节点节点id
handleNodeCollapse(data, cn); handleNodeCollapse(data, cn);
} }
}; };
const removeDeafultExpandId = (id: any) => { const removeDeafultExpandId = (id: any) => {
let index = state.defaultExpandedKeys.indexOf(id); let index = state.defaultExpandedKeys.indexOf(id);
if (index > -1) { if (index > -1) {
state.defaultExpandedKeys.splice(index, 1); state.defaultExpandedKeys.splice(index, 1);
} }
}; };
const info = async (data: any) => { const info = async (data: any) => {
let info = await resourceApi.detail.request({ id: data.id }); let info = await resourceApi.detail.request({ id: data.id });
state.infoDialog.data = info; state.infoDialog.data = info;
if (info.meta && info.meta != '') { if (info.meta && info.meta != '') {
state.infoDialog.data.meta = JSON.parse(info.meta); state.infoDialog.data.meta = JSON.parse(info.meta);
} }
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,76 +47,77 @@ export default defineComponent({
resources: { resources: {
type: Array, type: Array,
}, },
}, })
setup(props: any, { emit }) {
const menuTree: any = ref(null);
const state = reactive({ //定义事件
dialogVisible: false, const emit = defineEmits(['update:visible', 'cancel', 'val-change'])
defaultProps: {
const defaultProps = {
children: 'children', children: 'children',
label: 'name', label: 'name',
}, }
});
watch( const menuTree: any = ref(null);
const state = reactive({
dialogVisible: false,
roleInfo: null as any,
});
const {
dialogVisible,
roleInfo,
} = toRefs(state)
watch(
() => props.visible, () => props.visible,
(newValue) => { (newValue) => {
state.dialogVisible = newValue; state.dialogVisible = newValue;
state.roleInfo = props.role
} }
); );
/** /**
* 获取所有菜单树的叶子节点 * 获取所有菜单树的叶子节点
* @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('保存成功!');
emit('cancel'); emit('cancel');
}; };
const cancel = () => { const cancel = () => {
// 更新父组件visible prop对应的值为false // 更新父组件visible prop对应的值为false
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,37 +38,47 @@ export default defineComponent({
title: { title: {
type: String, type: String,
}, },
}, })
setup(props: any, { emit }) {
const roleForm: any = ref(null); //定义事件
const state = reactive({ const emit = defineEmits(['update:visible', 'cancel', 'val-change'])
const roleForm: any = ref(null);
const state = reactive({
dvisible: false, 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 };
} else { } else {
state.form = {} as any; state.form = {} as any;
} }
}); });
const cancel = () => { const cancel = () => {
// 更新父组件visible prop对应的值为false // 更新父组件visible prop对应的值为false
emit('update:visible', false); emit('update:visible', false);
// 若父组件有取消事件,则调用 // 若父组件有取消事件,则调用
emit('cancel'); emit('cancel');
}; };
const btnOk = async () => { const btnOk = async () => {
roleForm.value.validate(async (valid: boolean) => { roleForm.value.validate(async (valid: boolean) => {
if (valid) { if (valid) {
await roleApi.save.request(state.form); await roleApi.save.request(state.form);
@@ -85,16 +90,8 @@ export default defineComponent({
}, 1000); }, 1000);
} }
}); });
}; };
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: { const state = reactive({
RoleEdit,
ResourceEdit,
ShowResource,
},
setup() {
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: {},
@@ -117,49 +93,60 @@ export default defineComponent({
resources: [], resources: [],
title: '', title: '',
}, },
}); });
onMounted(() => { const {
query,
total,
roles,
chooseId,
chooseData,
resourceDialog,
roleEditDialog,
showResourceDialog,
} = toRefs(state)
onMounted(() => {
search(); search();
}); });
const search = async () => { const search = async () => {
let res = await roleApi.list.request(state.query); let res = await roleApi.list.request(state.query);
state.roles = res.list; state.roles = res.list;
state.total = res.total; state.total = res.total;
}; };
const handlePageChange = (curPage: number) => { const handlePageChange = (curPage: number) => {
state.query.pageNum = curPage; state.query.pageNum = curPage;
search(); search();
}; };
const choose = (item: any) => { const choose = (item: any) => {
if (!item) { if (!item) {
return; return;
} }
state.chooseId = item.id; state.chooseId = item.id;
state.chooseData = item; state.chooseData = item;
}; };
const roleEditChange = () => { const roleEditChange = () => {
ElMessage.success('修改成功!'); ElMessage.success('修改成功!');
state.chooseId = null; state.chooseId = null;
state.chooseData = null; state.chooseData = null;
search(); search();
}; };
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) => {
try { try {
await ElMessageBox.confirm( await ElMessageBox.confirm(
`此操作将删除 [${data.name}] 该角色,以及与该角色有关的账号角色关联信息和资源角色关联信息, 是否继续?`, `此操作将删除 [${data.name}] 该角色,以及与该角色有关的账号角色关联信息和资源角色关联信息, 是否继续?`,
@@ -175,23 +162,18 @@ export default defineComponent({
}); });
ElMessage.success('删除成功!'); ElMessage.success('删除成功!');
search(); search();
} catch (err) {} } catch (err) { }
}; };
const showResources = async (row: any) => { const showResources = async (row: any) => {
state.showResourceDialog.resources = await roleApi.roleResources.request({ state.showResourceDialog.resources = await roleApi.roleResources.request({
id: row.id, id: row.id,
}); });
state.showResourceDialog.title = '"' + row.name + '"的菜单&权限'; state.showResourceDialog.title = '"' + row.name + '"的菜单&权限';
state.showResourceDialog.visible = true; state.showResourceDialog.visible = true;
}; };
const closeShowResourceDialog = () => { const editResource = async (row: any) => {
state.showResourceDialog.visible = false;
state.showResourceDialog.resources = [];
};
const editResource = async (row: any) => {
let menus = await resourceApi.list.request(null); let menus = await resourceApi.list.request(null);
// 获取所有菜单列表 // 获取所有菜单列表
state.resourceDialog.resources = menus; state.resourceDialog.resources = menus;
@@ -213,21 +195,21 @@ export default defineComponent({
// 显示 // 显示
state.resourceDialog.visible = true; state.resourceDialog.visible = true;
state.resourceDialog.role = row; state.resourceDialog.role = row;
}; };
/** /**
* 获取所有菜单树的叶子节点 * 获取所有菜单树的叶子节点
* @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);
@@ -235,34 +217,19 @@ export default defineComponent({
} else { } else {
ids.push(tree.id); ids.push(tree.id);
} }
}; };
/** /**
* 取消编辑资源权限树 * 取消编辑资源权限树
*/ */
const cancelEditResources = () => { const cancelEditResources = () => {
state.resourceDialog.visible = false; state.resourceDialog.visible = false;
setTimeout(() => { setTimeout(() => {
state.resourceDialog.role = {}; state.resourceDialog.role = {};
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,25 +34,33 @@ export default defineComponent({
title: { title: {
type: String, type: String,
}, },
}, })
setup(props: any, { emit }) {
const { proxy } = getCurrentInstance() as any; //定义事件
const state = reactive({ const emit = defineEmits(['update:visible', 'update:resources'])
dialogVisible: false,
defaultProps: { const { proxy } = getCurrentInstance() as any;
const defaultProps = {
children: 'children', children: 'children',
label: 'name', label: 'name',
}, }
});
watch( const state = reactive({
dialogVisible: false,
});
const {
dialogVisible,
} = toRefs(state)
watch(
() => props.visible, () => props.visible,
(newValue) => { (newValue) => {
state.dialogVisible = newValue; state.dialogVisible = newValue;
} }
); );
const info = (info: any) => { const info = (info: any) => {
ElMessageBox.alert( ElMessageBox.alert(
'<strong style="margin-right: 18px">资源名称:</strong>' + '<strong style="margin-right: 18px">资源名称:</strong>' +
info.name + info.name +
@@ -66,24 +76,16 @@ export default defineComponent({
closeOnClickModal: true, closeOnClickModal: true,
showConfirmButton: false, showConfirmButton: false,
} }
).catch(() => {}); ).catch(() => { });
return; return;
}; };
const closeDialog = () => { const closeDialog = () => {
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,66 +32,61 @@
<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: {}, const state = reactive({
setup() {
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,
}); });
onMounted(() => { const {
query,
total,
logs,
accounts,
} = toRefs(state)
onMounted(() => {
search(); search();
}); });
const search = async () => { const search = async () => {
let res = await logApi.list.request(state.query); let res = await logApi.list.request(state.query);
state.logs = res.list; state.logs = res.list;
state.total = res.total; state.total = res.total;
}; };
const handlePageChange = (curPage: number) => { const handlePageChange = (curPage: number) => {
state.query.pageNum = curPage; state.query.pageNum = curPage;
search(); search();
}; };
const getAccount = (username: any) => { const getAccount = (username: any) => {
accountApi.list.request({ username }).then((res) => { accountApi.list.request({ username }).then((res) => {
state.accounts = res.list; state.accounts = res.list;
}); });
}; };
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