admin 初始化

This commit is contained in:
zoujiandong
2023-06-05 09:25:16 +08:00
parent 12ecc21e37
commit 51d369d4c1
82 changed files with 7662 additions and 11 deletions
+3
View File
@@ -0,0 +1,3 @@
<template>
<router-view></router-view>
</template>
+30
View File
@@ -0,0 +1,30 @@
import request from '../../utils/request';
export function login(data) {
return request({
url:'/api/v1/login',
method: 'post',
data
})
}
export function getCaptcha() {
return request({
url:'/api/v1/captcha',
method: 'get'
})
}
export function getAppConfig() {
return request({
url:'/api/v1/app-config',
method: 'get'
})
}
// 根据角色获取菜单
export function getUserMenuRole() {
return request({
url:'/api/v1/menurole',
method: 'get'
})
}
+48
View File
@@ -0,0 +1,48 @@
import request from '../../utils/request'
const url = '/api/v1/menu';
export function getMenu(params) {
return request({
url,
method: 'get',
params
})
}
export function getMenuDetails(menuId) {
return request({
url: `${url}/${menuId}`,
method: 'get'
})
}
export function addMenu(data) {
return request({
url,
method: 'post',
data
})
}
export function removeMenu(data) {
return request({
url,
method: 'delete',
data
})
}
/**
* 修改菜单
* @param {Object} data
* @param {Number} id
* @returns
*/
export function updateMenu(data, id) {
return request({
url: `${url}/${id}`,
method: 'put',
data
})
}
+35
View File
@@ -0,0 +1,35 @@
import request from '../../utils/request'
const url = '/api/v1/post';
export function getPost(params) {
return request({
url,
method: 'get',
params
})
}
export function addPost(data) {
return request({
url,
method: 'post',
data
})
}
export function removePost(data) {
return request({
url,
method: 'delete',
data
})
}
export function updatePost(data, id) {
return request({
url: `${url}/${id}`,
method: 'put',
data
})
}
+50
View File
@@ -0,0 +1,50 @@
import request from '../../utils/request';
const url = '/api/v1/role';
export function getRole(params) {
return request({
url,
method: 'get',
params
})
}
export function addRole(data) {
return request({
url,
method: 'post',
data
})
}
export function removeRole(data) {
return request({
url,
method: 'delete',
data
})
}
export function updateRole(data, id) {
return request({
url: `${url}/${id}`,
method: 'put',
data,
})
}
export function updateRoleScoped(data) {
return request({
url:'/api/v1/roledatascope',
method: 'put',
data
})
}
export function getRoleMenuTree(params, roleId) {
return request({
url:`/api/v1/roleMenuTreeselect/${roleId}`,
method: 'get',
params
})
}
+35
View File
@@ -0,0 +1,35 @@
import request from '../../utils/request';
const url = '/api/v1/sys-api';
export function getSysApi(params) {
return request({
url,
method: 'get',
params
})
}
export function addSysApi(data) {
return request({
url,
method: 'post',
data
})
}
export function removeSysApi(data) {
return request({
url,
method: 'delete',
data
})
}
export function updateSysApi(data, id) {
return request({
url: `${url}/${id}`,
method: 'put',
data,
})
}
+35
View File
@@ -0,0 +1,35 @@
import request from '../../utils/request';
const url = '/api/v1/config';
export function getSysConfig(params) {
return request({
url,
method: 'get',
params
})
}
export function addSysConfig(data) {
return request({
url,
method: 'post',
data
})
}
export function removeSysConfig(data) {
return request({
url,
method: 'delete',
data
})
}
export function updateSysConfig(data, id) {
return request({
url: `${url}/${id}`,
method: 'put',
data,
})
}
+35
View File
@@ -0,0 +1,35 @@
import request from '../../utils/request';
const url = '/api/v1/dept';
export function getDept(params) {
return request({
url,
method: 'get',
params
})
}
export function addDept(data) {
return request({
url,
method: 'post',
data
})
}
export function removeDept(data) {
return request({
url,
method: 'delete',
data
})
}
export function updateDept(data, id) {
return request({
url: `${url}/${id}`,
method: 'put',
data,
})
}
+35
View File
@@ -0,0 +1,35 @@
import request from '@/utils/request';
const url = '/api/v1/dict/data';
export function getDictData(params) {
return request({
url,
method: 'get',
params
})
}
export function addDictData(data) {
return request({
url,
method: 'post',
data
})
}
export function updateDictData(data, dictCode) {
return request({
url: `${url}/${dictCode}`,
method: 'put',
data
})
}
export function deleteDictData(data) {
return request({
url,
method: 'delete',
data
})
}
+43
View File
@@ -0,0 +1,43 @@
import request from '../../utils/request';
const url = '/api/v1/dict/type';
export function getDictType(params) {
return request({
url,
method: 'get',
params
})
}
export function addDictType(data) {
return request({
url,
method: 'post',
data
})
}
export function removeDictType(data) {
return request({
url,
method: 'delete',
data
})
}
export function updateDictType(data, id) {
return request({
url: `${url}/${id}`,
method: 'put',
data,
})
}
// 获取字典选择框列表
export function optionselect() {
return request({
url: '/api/v1/dict/type-option-select',
method: 'get'
})
}
+19
View File
@@ -0,0 +1,19 @@
import request from '../../utils/request';
const url = '/api/v1/sys-login-log';
export function getSysLoginLog(params) {
return request({
url,
method: 'GET',
params
})
}
export function removeSysLoginLog(data) {
return request({
url,
method: 'DELETE',
data
})
}
+19
View File
@@ -0,0 +1,19 @@
import request from '../../utils/request';
const url = '/api/v1/sys-opera-log';
export function getSysOperaLog(params) {
return request({
url,
method: 'get',
params,
});
}
export function removeSysOperaLog(data) {
return request({
url,
method: 'delete',
data,
});
}
+67
View File
@@ -0,0 +1,67 @@
import request from '../../utils/request';
const url = '/api/v1/sys-user';
export function getUser(params) {
return request({
url,
method: 'get',
params
})
}
export function getInfo() {
return request({
url:'/api/v1/getinfo',
method: 'get'
})
}
export function addUser(data) {
return request({
url,
method: 'post',
data
})
}
export function removeUser(data) {
return request({
url,
method: 'delete',
data
})
}
export function updateUser(data) {
return request({
url,
method: 'put',
data,
})
}
export function updateUserStatus(data) {
return request({
url: '/api/v1/user/status',
method: 'put',
data
})
}
export function resetUserPwd(data) {
return request({
url:'/api/v1/user/pwd/reset',
method: 'put',
data
})
}
// 获取当前登录用户信息
export function getCurrentUser(uid) {
return request({
url: `${url}/${uid}`,
method: 'get'
})
}
+18
View File
@@ -0,0 +1,18 @@
import request from '../../utils/request';
// 获取用户信息
export function getUserProfile() {
return request({
url: '/api/v1/user/profile',
method: 'get'
})
}
// 修改用户密码
export function putUserPwd(data) {
return request({
url: '/api/v1/user/pwd/set',
method: 'put',
data
})
}
+62
View File
@@ -0,0 +1,62 @@
import request from '@/utils/request'
// 查询SysJob列表
export function listSysJob(query) {
return request({
url: '/api/v1/sysjob',
method: 'get',
params: query
})
}
// 查询SysJob详细
export function getSysJob(jobId) {
return request({
url: '/api/v1/sysjob/' + jobId,
method: 'get'
})
}
// 新增SysJob
export function addSysJob(data) {
return request({
url: '/api/v1/sysjob',
method: 'post',
data: data
})
}
// 修改SysJob
export function updateSysJob(data) {
return request({
url: '/api/v1/sysjob',
method: 'put',
data: data
})
}
// 删除SysJob
export function delSysJob(data) {
return request({
url: '/api/v1/sysjob',
method: 'delete',
data: data
})
}
// 移除SysJob
export function removeJob(jobId) {
return request({
url: '/api/v1/job/remove/' + jobId,
method: 'get'
})
}
// 启动SysJob
export function startJob(jobId) {
return request({
url: '/api/v1/job/start/' + jobId,
method: 'get'
})
}
+10
View File
@@ -0,0 +1,10 @@
import request from '../../utils/request'
const url = '/api/v1/server-monitor';
export function getServerMonitor() {
return request({
url,
method: 'GET',
})
}
+103
View File
@@ -0,0 +1,103 @@
import request from '@/utils/request'
// 查询生成表数据
export function listTable(query) {
return request({
url: '/api/v1/sys/tables/page',
method: 'get',
params: query
})
}
// 查询db数据库列表
export function listDbTable(query) {
return request({
url: '/api/v1/db/tables/page',
method: 'get',
params: query
})
}
// 查询表详细信息
export function getGenTable(tableId) {
return request({
url: '/api/v1/sys/tables/info/' + tableId,
method: 'get'
})
}
export function getGenTableInfo(tablename) {
return request({
url: '/api/v1/sys/tables?tableName=' + tablename,
method: 'get'
})
}
// 修改代码生成信息
export function updateGenTable(data) {
return request({
url: '/api/v1/sys/tables/info',
method: 'put',
data: data
})
}
// 导入表
export function importTable(data) {
return request({
url: '/api/v1/sys/tables/info',
method: 'post',
params: data
})
}
// 预览生成代码
export function previewTable(tableId) {
return request({
url: '/api/v1/gen/preview/' + tableId,
method: 'get'
})
}
// 删除表数据
export function delTable(tableId) {
return request({
url: '/api/v1/sys/tables/info/' + tableId,
method: 'delete'
})
}
// 生成代码到项目
export function toProjectTable(tableId) {
return request({
url: '/api/v1/gen/toproject/' + tableId,
method: 'get'
})
}
// 生成接口数据到迁移脚本
export function apiToFile(tableId) {
return request({
url: '/api/v1/gen/apitofile/' + tableId,
method: 'get'
})
}
export function toProjectTableCheckRole(tableId, ischeckrole) {
return request({
url: '/api/v1/gen/toproject/' + tableId + '?ischeckrole=' + ischeckrole,
method: 'get'
})
}
// 生成菜单到数据库
export function toDBTable(tableId) {
return request({
url: '/api/v1/gen/todb/' + tableId,
method: 'get'
})
}
export function getTableTree() {
return request({
url: '/api/v1/gen/tabletree',
method: 'get'
})
}
+104
View File
@@ -0,0 +1,104 @@
<template>
<a-modal v-model:visible="deleteVisible" :title="data.length > 1 ? '批量删除' : '删除'" title-align="start" @close="handleClose" width="400px">
<a-form :model="data" :rules="rules" ref="modalFormRef" size="medium" label-align="left" auto-label-width>
<a-alert type="warning" style="margin-bottom: 16px;" v-if="data.length > 1">您正在批量删除请确认删除数据是否正确</a-alert>
<a-form-item label="摘要" label-col-flex="100px">
<a-tag color="gray" style="padding: 0 0px; margin-right: 5px">
<template #icon>
<a-tag color="arcoblue">数量</a-tag>
</template>
<a-tag style="margin-left: -4px">{{ data.length }}</a-tag>
</a-tag>
</a-form-item>
<a-form-item field="deleteConfirmation" label="删除确认" label-col-flex="100px">
<a-input v-model.trim="data.deleteConfirmation" placeholder="请输入 DELETE 以确认删除"></a-input>
</a-form-item>
</a-form>
<template #footer>
<a-button @click="handleClose"><template #icon><icon-close /></template>取消</a-button>
<a-button type="primary" @click="handleConfirm"><template #icon><icon-check /></template>确认</a-button>
</template>
</a-modal>
</template>
<script setup>
import { ref, reactive, toRefs, watch, unref, getCurrentInstance } from 'vue';
import { Message } from '@arco-design/web-vue';
const { proxy } = getCurrentInstance();
const props = defineProps({
// 数组名称
data: {
type: Array,
},
// 是否显示
visible: {
type: Boolean,
default: false,
},
// 更新接口
apiDelete: {
type: Function,
},
});
const { data, visible, apiDelete } = toRefs(props);
const emits = defineEmits(['deleteVisibleChange']);
// Akiraka 20230210 对话框弹出
const deleteVisible = ref(false);
// Akiraka 20230210 删除数据校验
const rules = reactive({
deleteConfirmation: [
{
required: true,
validator: (value, cb) => {
return new Promise(resolve => {
setTimeout(() => {
if ( "DELETE" !== value) {
cb('请输入 DELETE')
}
resolve()
}, 500)
})
}
}
],
});
// Akiraka 20230210 关闭弹窗
const handleClose = () => {
emits('deleteVisibleChange', deleteVisible.value = false);
// Akiraka 20230210 关闭弹窗
deleteVisible.value = false;
}
// Akiraka 20230210 监听事件
watch(() => props.visible,(value) => {
if (value) {
// Akiraka 20230210 打开或关闭对话框
deleteVisible.value = value;
}
})
// Akiraka 20230210 确认按钮 => 开始数据检查
const handleConfirm = () => {
proxy.$refs.modalFormRef.validate((valid) => {
if (!valid) {
// Akiraka 20230210 请求接口
props.apiDelete({ ids: data.value }).then(response => {
// Akiraka 20230210 关闭弹窗
deleteVisible.value = false;
Message.success("数据删除成功");
})
}
})
}
</script>
<style lang="less" scoped>
</style>
+22
View File
@@ -0,0 +1,22 @@
import { useUserStore } from '@/store/userInfo';
export default {
checkPermission(el, binding) {
const store = useUserStore();
const { value } = binding;
const all_permission = '*:*:*'
const permissions = store.userInfo && store.userInfo.permissions;
if (typeof value === 'string') {
const hasPermission = permissions.some((permission) => {
return all_permission === permission || value === permission;
})
if (!hasPermission) {
el.parentNode && el.parentNode.removeChild(el);
}
} else {
throw new Error(`请设置操作权限标签值`)
}
}
}
View File
+1
View File
@@ -0,0 +1 @@
<svg width="24" height="24" viewBox="0 0 48 48" fill="currentColor"><path fill-rule="evenodd" clip-rule="evenodd" d="M4 15a1 1 0 001 1h2a1 1 0 001-1V8h7a1 1 0 001-1V5a1 1 0 00-1-1H6a2 2 0 00-2 2v9zm4 18a1 1 0 00-1-1H5a1 1 0 00-1 1v9a2 2 0 002 2h9a1 1 0 001-1v-2a1 1 0 00-1-1H8v-7zm35-17a1 1 0 001-1V6a2 2 0 00-2-2h-9a1 1 0 00-1 1v2a1 1 0 001 1h7v7a1 1 0 001 1h2zm1 17a1 1 0 00-1-1h-2a1 1 0 00-1 1v7h-7a1 1 0 00-1 1v2a1 1 0 001 1h9a2 2 0 002-2v-9zM32.835 11h-6.108c-6.512 0-11.882 4.804-12.636 11h-1.992c-.382 0-.52.046-.66.134a.855.855 0 00-.325.378c-.074.162-.114.324-.114.77v1.436c0 .446.04.608.114.77.075.163.185.291.324.378.14.088.279.134.66.134h2.157c1.179 5.706 6.315 10 12.472 10h6.108c.405 0 .552-.041.7-.12a.819.819 0 00.344-.337c.079-.145.121-.29.121-.688V31h.901c.382 0 .52-.046.66-.134a.855.855 0 00.325-.378c.074-.162.114-.324.114-.77v-1.436c0-.446-.04-.608-.114-.77a.855.855 0 00-.325-.378c-.14-.088-.278-.134-.66-.134H34v-7h.901c.382 0 .52-.046.66-.134a.855.855 0 00.325-.378c.074-.162.114-.324.114-.77v-1.436c0-.446-.04-.608-.114-.77a.855.855 0 00-.325-.378c-.14-.088-.278-.134-.66-.134H34v-3.855c0-.398-.042-.543-.121-.688a.819.819 0 00-.344-.338c-.148-.078-.295-.119-.7-.119zm-2.744 3.571h-3.637c-5.02 0-9.09 3.998-9.09 8.929s4.07 8.929 9.09 8.929h3.637V14.57z" fill="currentColor"/></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

+12
View File
@@ -0,0 +1,12 @@
<template>
<div class="app-main">
<router-view v-slot="{ Component, route }">
<transition name="fade-transform" mode="out-in">
<component :is="Component" :key="route" />
</transition>
</router-view>
</div>
</template>
<style lang="scss">
</style>
+76
View File
@@ -0,0 +1,76 @@
<template>
<a-dropdown position="bl" :style="{ top: '52px' }">
<div class="avatar-container">
<a-avatar :size="32" :style="{ backgroundColor: '#3370ff' }">
<img alt="avatar" :src="userInfo.avatar" />
</a-avatar>
<div class="user-info">
<div class="user-info-name">{{ userInfo.name }}</div>
<!-- <div class="user-info-desc">{{ userInfo.introduction }}</div> -->
</div>
</div>
<template #content>
<a-doption @click="$router.push('/profile')">
<template #icon>
<icon-settings />
</template>
<template #default>用户设置</template>
</a-doption>
<a-doption @click="handleLogout()">
<template #icon>
<icon-export />
</template>
<template #default>退出登陆</template>
</a-doption>
</template>
</a-dropdown>
</template>
<script setup>
import { getCurrentInstance } from 'vue';
import { storeToRefs } from 'pinia';
import {
IconSettings,
IconExport,
} from '@arco-design/web-vue/es/icon';
import { useUserStore } from '@/store/userInfo';
import { clearLocalStorage } from '@/utils/storage';
const store = useUserStore();
const { userInfo } = storeToRefs(store);
const { proxy } = getCurrentInstance();
const handleLogout = () => {
proxy.$modal.warning({
title: '提示',
content: '确定注销并退出登陆系统吗?',
hideCancel: false,
onOk: () => {
window.sessionStorage.removeItem('token');
clearLocalStorage();
proxy.$router.push('/login');
},
});
};
</script>
<style lang="scss" scoped>
.avatar-container {
display: flex;
cursor: pointer;
.user-info {
margin-left: 10px;
&-name {
color: var(--color-text-1);
font-weight: 700;
padding-top: 8px;
}
&-desc {
color: var(--color-text-1);
font-size: 12px;
line-height: 1.4;
}
}
}
</style>
+95
View File
@@ -0,0 +1,95 @@
<template>
<div class="sider-logo">
<img :src="store.sysConfig.sys_app_logo" />
<span class="sider-title" v-if="!props.collapsed">{{store.sysConfig.sys_app_name}}</span>
</div>
<a-menu
class="menu"
@menu-item-click="handleMenuClick"
:default-open-keys="['/admin']"
:default-selected-keys="defaultSelectKeys"
:auto-open-selected="true"
>
<sub-menu :menu-list="permissionStore.menuList" />
</a-menu>
</template>
<script setup>
import { ref, onBeforeMount } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import { useUserStore } from '@/store/userInfo';
import { usePermissionStore } from '@/store/permission';
import SubMenu from './SubMenu.vue';
const store = useUserStore();
const permissionStore = usePermissionStore();
const props = defineProps({
collapsed: {
type: Boolean,
default: false,
},
});
const route = useRoute();
const router = useRouter();
// 默认菜单选中
const defaultSelectKeys = ref([]);
// 刷新保持菜单选中
const keepDefaultSelect = () => {
defaultSelectKeys.value = [];
defaultSelectKeys.value.push(route.fullPath);
};
const handleMenuClick = (key) => {
router.push(key);
};
onBeforeMount(() => {
keepDefaultSelect();
});
</script>
<style lang="scss" scoped>
.sider-logo {
margin: 10px 0;
display: flex;
justify-content: center;
padding-bottom: 8px;
border-bottom: 1px solid var(--color-text-4);
& img {
height: 32px;
}
}
.sider-title {
margin-left: 10px;
display: flex;
align-items: center;
font-size: 16px;
color: var(--color-text-4);
}
.left-side {
height: 50px;
width: 50px;
font-size: 18px;
line-height: 50px;
text-align: center;
transition: all 0.3s ease-in-out;
cursor: pointer;
&:hover {
background-color: #e5e5e5;
}
}
</style>
<style lang="scss">
.arco-menu-indent {
width: 30px;
}
</style>
+37
View File
@@ -0,0 +1,37 @@
<template>
<template v-for="menu in props.menuList" :key="menu.menuId">
<a-sub-menu
v-if="menu.children && menu.menuType == 'M' && menu.visible == 0"
:key="menu.path"
>
<template #icon>
<component :is="menu.icon" />
</template>
<template #title>{{ menu.title }}</template>
<sub-menu :menuList="menu.children" />
</a-sub-menu>
<a-menu-item
v-if="menu.menuType == 'C' && menu.visible && isRouteParams(menu.path)"
:key="menu.path"
>{{ menu.title }}</a-menu-item>
</template>
</template>
<script setup>
import { computed } from 'vue'
const props = defineProps({
menuList: {
type: Array,
default: () => [],
},
});
// 判断是否路由需要带参匹配
const isRouteParams = computed(() => {
return (path) => {
if (path.indexOf(':') == -1) return true;
return false;
}
})
</script>
+93
View File
@@ -0,0 +1,93 @@
<template>
<div class="navbar">
<div class="left-side" @click="onCollapse">
<icon-menu-fold v-if="!props.collapsed" />
<icon-menu-unfold v-else />
</div>
<ul class="right-side">
<li>
<a-button shape="circle" @click="handleDarkTheme">
<template #icon>
<component
:is="!darkTheme ? IconSunFill : IconMoonFill"
></component>
</template>
</a-button>
</li>
<li>
<Avatar />
</li>
</ul>
</div>
</template>
<script setup>
import { ref } from 'vue';
import Avatar from './Avatar/index.vue';
import {
IconMenuFold,
IconMenuUnfold,
IconSunFill,
IconMoonFill,
} from '@arco-design/web-vue/es/icon';
const darkTheme = ref(false);
const props = defineProps({
collapsed: Boolean,
});
const emit = defineEmits(['onCollapse']);
const onCollapse = () => {
emit('onCollapse');
};
// 切换亮色和暗色
const handleDarkTheme = () => {
const theme = document.body.getAttribute('arco-theme');
if (!theme) {
document.body.setAttribute('arco-theme', 'dark');
darkTheme.value = true;
} else {
document.body.removeAttribute('arco-theme');
darkTheme.value = false;
}
};
</script>
<style lang="scss" scoped>
.navbar {
display: flex;
justify-content: space-between;
height: 50px;
border-bottom: 1px solid #e5e6eb;
box-shadow: 0 2px 5px 0 rgba(0,0,0, .1);
.left-side {
height: 50px;
width: 50px;
font-size: 18px;
line-height: 50px;
text-align: center;
transition: all 0.3s ease-in-out;
cursor: pointer;
&:hover {
background-color: #e5e5e5;
}
}
.right-side {
list-style: none;
display: flex;
padding-right: 30px;
& > li {
display: flex;
align-items: center;
padding: 10px;
}
}
}
</style>
+2
View File
@@ -0,0 +1,2 @@
export { default as AppMain } from './AppMain.vue';
export { default as Navbar } from './Navbar.vue';
+37
View File
@@ -0,0 +1,37 @@
<template>
<a-layout :style="{ height: '100vh' }">
<a-layout-sider theme="dark" breakpoint="lg" :width="220" collapsible :collapsed="collapsed" @collapse="onCollapse">
<Menu :collapsed="collapsed" />
</a-layout-sider>
<a-layout>
<a-layout-header>
<Navbar :collapsed="collapsed" @on-collapse="onCollapse" />
</a-layout-header>
<a-layout-content class="layout-content">
<AppMain />
</a-layout-content>
</a-layout>
</a-layout>
</template>
<script setup>
import { ref } from 'vue';
import { AppMain, Navbar } from './components';
import Menu from './components/Menu/Menu.vue';
const collapsed = ref(false);
const onCollapse = () => {
collapsed.value = !collapsed.value;
};
</script>
<style lang="scss">
@import '../style/index.scss';
@import '../style/transition.scss';
@import '../style/dark-theme.scss';
.layout-content {
padding: 16px;
background-color: #f2f3f5;
}
</style>
+39
View File
@@ -0,0 +1,39 @@
import { createApp } from 'vue';
import { createPinia } from 'pinia';
import App from './App.vue';
import ArcoVue from '@arco-design/web-vue';
import { Message, Modal, Notification } from '@arco-design/web-vue';
import '@arco-design/web-vue/dist/arco.css';
import router from './router/';
import { parseTime } from '@/utils/parseTime';
// Directive
import permission from '@/directive/permission/permission';
// 引入 Arco 图标库
import * as ArcoIconModules from '@arco-design/web-vue/es/icon';
console.log(import.meta.env);
// Initialize the Pinia instance
const pinia = createPinia();
const app = createApp(App);
app.directive('has', permission.checkPermission);
// 挂载全局变量
app.config.globalProperties.message = Message;
app.config.globalProperties.modal = Modal;
app.config.globalProperties.notification = Notification;
app.config.globalProperties.parseTime = parseTime;
// 挂载全局图标
for(const name in ArcoIconModules){
app.component(name,ArcoIconModules[name])
}
app.use(ArcoVue);
app.use(router);
app.use(pinia);
app.mount('#app');
+115
View File
@@ -0,0 +1,115 @@
import { createWebHashHistory, createRouter, createWebHistory } from 'vue-router';
import Layout from '../layout/index.vue';
import { useUserStore } from '../store/userInfo';
import { usePermissionStore } from '../store/permission';
import Watermark from '@/utils/watermark.js';
const routes = [
{
path: '/',
name: '/',
redirect: 'admin',
component: Layout,
children: [
{
path: '/profile',
name: 'profile',
component: () => import('../views/profile/index.vue'),
meta: {
title: '个人设置',
},
},
{
path: '/403',
name: '403',
component: () => import('../views/error-page/403.vue'),
meta: {
title: '找不到页面',
},
},
{
hide: true,
path: '/:catchAll(.*)',
component: () => import('../views/error-page/404.vue'),
meta: {
title: '找不到页面',
},
},
{
path: '/500',
name: '500',
component: () => import('../views/error-page/500.vue'),
meta: {
title: '找不到页面',
},
},
]
},
{
path: '/login',
name: 'login',
component: () => import('../views/login/index.vue'),
},
];
const router = createRouter({
// createWebHashHistory URL 带井号
// createWebHistory URL 去井号
history: createWebHistory(),
routes: routes,
});
// beforeEach router
router.beforeEach(async (to, from, next) => {
const store = useUserStore();
const permissionStore = usePermissionStore();
// 获取系统配置信息
await store.getSysConfig();
// 判断用户Token是否获取
if (to.name !== 'login' && !store.token) {
next({ name: 'login' });
} else {
// 判断判断权限有无获取
if (store.token && store.roles.length === 0) {
store.getUserInfo();
await permissionStore.getMenuRole();
permissionStore.addRouters.forEach((route) => {
router.addRoute('/', route);
});
// next(to.fullPath);
// 如果 addRoute 并未完成,路由守卫会一层一层的执行执行,直到 addRoute 完成,找到对应的路由
next({ ...to, replace: true })
} else {
next();
}
}
});
// afterEach Router
router.afterEach((to) => {
const store = useUserStore();
// 修改网页标题
if (to.name !== 'login') {
document.title = `${to.meta.title} - ${store.sysConfig.sys_app_name}`;
} else {
document.title = store.sysConfig.sys_app_name;
}
// Vincent 2023004 修复加载水印的bug
if (store.userInfo != undefined){
if ( store.userInfo.name != undefined ) {
Watermark.set(store.userInfo.name)
} else {
Watermark.out() // 清除水印
}
} else{
Watermark.out() // 清除水印
}
});
export default router;
+60
View File
@@ -0,0 +1,60 @@
import { defineStore } from 'pinia';
import { getUserMenuRole } from '@/api/admin/login';
const modules = import.meta.glob('../views/**/*.vue');
export const usePermissionStore = defineStore('permisson', {
state: () => {
return {
addRouters: [],
menuList: [],
};
},
getters: {
getRoutes: (state) => state.addRouters,
},
actions: {
setMenuList(menus) {
this.menuList = menus;
},
GenerateRoutes(routeList) {
const routes = [];
routeList.forEach((item) => {
const route = {};
// if (item.visible == 0) {
if (item.menuType === 'M' || item.menuType === 'C') {
route.path = item.path;
route.name = item.menuName;
if (item.menuType === 'M') {
route.component = modules[`../views/index.vue`];
} else if (item.menuType === 'C') {
route.component = modules[`../views${item.component}.vue`] || modules['../views/error-page/888.vue'];
}
route.meta = {
title: item.title,
permission: item.permission,
};
}
if (item.children) {
route.children = this.GenerateRoutes(item.children);
}
routes.push(route);
// }
});
return routes;
},
async getMenuRole() {
const res = await getUserMenuRole();
console.log(res.data);
this.setMenuList(res.data);
this.addRouters = await this.GenerateRoutes(res.data);
},
ClearMenuList() {
this.menuList = [];
}
},
});
+48
View File
@@ -0,0 +1,48 @@
import { defineStore } from 'pinia';
import { setLocalStorage, getLocalStorage } from '@/utils/storage';
import { getInfo } from '@/api/admin/sys-user';
import { getAppConfig } from '@/api/admin/login';
export const useUserStore = defineStore('user', {
state: () => {
return {
token: window.sessionStorage.getItem('token') || '',
uid: window.sessionStorage.getItem('uid') || '',
sysConfig: getLocalStorage('sysConfig'),
userInfo: '',
}
},
getters: {
roles: (state) => state.userInfo.roles || [],
},
actions: {
setToken(token) {
this.token = token;
window.sessionStorage.setItem('token', token);
},
async getUserInfo() {
try {
const res = await getInfo();
// window.sessionStorage.setItem('uid', res.data.userId);
window.localStorage.setItem('uid', res.data.userId);
this.userInfo = res.data;
} catch (err) {
console.error(err);
}
},
async getSysConfig() {
try {
const res = await getAppConfig();
setLocalStorage('sysConfig', res.data);
this.sysConfig = res.data;
} catch (err) {
console.error(err);
}
}
},
userLogout() {
this.token = null;
this.userInfo = null;
}
})
+34
View File
@@ -0,0 +1,34 @@
// Dark Theme
$dark-bg-1: #17171A;
$color-bg-2: #393A3C;
$color-bg-3: #2a2a2b;
$color-bg-4: #313132;
$color-bg-5: #373739;
$--color-text-1: rgba(255, 255, 255, 0.9);
$--color-text-2: rgba(255, 255, 255, 0.7);
$--color-text-3: rgba(255, 255, 255, 0.5);
$--color-text-4: rgba(255, 255, 255, 0.3);
body[arco-theme='dark'] {
.navbar {
background-color: $dark-bg-1;
}
.arco-layout-content {
background-color: $color-bg-2
}
.app-container {
background-color: $color-bg-3;
}
.navbar {
border-bottom: 1px solid $--color-text-4;
.left-side {
// background-color: $color-bg-2;
color: $--color-text-1;
&:hover {
background-color: $color-bg-5;
}
}
}
}
File diff suppressed because one or more lines are too long
+28
View File
@@ -0,0 +1,28 @@
// global transition css
/* fade */
.fade-enter-active,
.fade-leave-active {
transition: opacity 0.28s;
}
.fade-enter,
.fade-leave-active {
opacity: 0;
}
/* fade-transform */
.fade-transform-leave-active,
.fade-transform-enter-active {
transition: all .5s;
}
.fade-transform-enter-from {
opacity: 0;
transform: translateX(-30px);
}
.fade-transform-leave-to {
opacity: 0;
transform: translateX(30px);
}
+3
View File
@@ -0,0 +1,3 @@
// base color
$primary-font-color: #1d2129;
$secondary-font-color: #86909c;
+41
View File
@@ -0,0 +1,41 @@
// 日期格式化
export function parseTime(time, pattern) {
if (arguments.length === 0 || !time) {
return null
}
if (time.indexOf('01-01-01') > -1) {
return '-'
}
const format = pattern || '{y}-{m}-{d} {h}:{i}:{s}'
let date
if (typeof time === 'object') {
date = time
} else {
if ((typeof time === 'string') && (/^[0-9]+$/.test(time))) {
time = parseInt(time)
}
if ((typeof time === 'number') && (time.toString().length === 10)) {
time = time * 1000
}
date = new Date(time)
}
const formatObj = {
y: date.getFullYear(),
m: date.getMonth() + 1,
d: date.getDate(),
h: date.getHours(),
i: date.getMinutes(),
s: date.getSeconds(),
a: date.getDay()
}
const time_str = format.replace(/{(y|m|d|h|i|s|a)+}/g, (result, key) => {
let value = formatObj[key]
// Note: getDay() returns 0 on Sunday
if (key === 'a') { return ['日', '一', '二', '三', '四', '五', '六'][value] }
if (result.length > 0 && value < 10) {
value = '0' + value
}
return value || 0
})
return time_str
}
+58
View File
@@ -0,0 +1,58 @@
import axios from 'axios';
import { Message } from '@arco-design/web-vue';
import { useUserStore } from '../store/userInfo'
// create an axios instance
console.log("________"+import.meta.env.VITE_BASE_URL)
const service = axios.create({
baseURL:import.meta.env.VITE_BASE_URL,
timeout: 8000,
});
// request interceptor
service.interceptors.request.use(
(config) => {
// Store 必须在拦截器内部导入,在外部导入会显示 Pinia 未初始化
const store = useUserStore();
// 设置请求头部 Authorization
if (store.token) {
config.headers['Authorization'] = 'Bearer ' + store.token;
config.headers['Content-Type'] = 'application/json'
}
return config;
},
(error) => {
console.error(error);
return Promise.reject(error);
}
);
// response interceptor
service.interceptors.response.use(
(response) => {
return response.data;
},
(error) => {
const store = useUserStore();
const { code, msg } = error.response.data;
// 如果过期则退出登录
if (code === 401) {
Message.error({
content: 'Token 已过期, 请重新登陆',
duration: 3000
});
// 重定向路由到登陆页面
store.userLogout();
// Akiraka 20230410 重定向到登录页面
return router.push('/login');
} else {
Message.error({
content: error.message,
duration: 3000
})
return Promise.reject(msg);
}
}
);
export default service;
+41
View File
@@ -0,0 +1,41 @@
// LocalStorage
/**
* 存储LocalStorage
* @param {string, Object} name
* @param {*} value
*/
export const setLocalStorage = (name, value) => {
if (!name) throw new Error('name must be specified');
if (typeof value !== 'string') {
value = JSON.stringify(value);
}
window.localStorage.setItem(name, value);
}
/**
* 获取LocalStorage
* @param {string, Object} name
* @returns
*/
export const getLocalStorage = (name) => {
if(!name) throw new Error('name must be specified');
const value = window.localStorage.getItem(name);
return JSON.parse(value);
}
/**
* 移除指定name
* @param {*} name
*/
export const removeLocalStorage = (name) => {
if (!name) throw new Error('name must be specified');
window.localStorage.removeItem(name);
}
/**
* 清空所有LocalStorage
*/
export const clearLocalStorage = () => {
window.localStorage.clear();
}
+60
View File
@@ -0,0 +1,60 @@
let watermark = {}
let setWatermark = (str) => {
let id = '1.23452384164.123412415'
if (document.getElementById(id) !== null) {
document.body.removeChild(document.getElementById(id))
}
let can = document.createElement('canvas')
can.width = 200
can.height = 120
let cans = can.getContext('2d')
cans.rotate(-15 * Math.PI / 150)
cans.font = '18px Vedana'
cans.fillStyle = 'rgba(200, 200, 200, 0.20)'
cans.textAlign = 'left'
cans.textBaseline = 'Middle'
cans.fillText(str, can.width / 8, can.height / 2)
let div = document.createElement('div')
div.id = id
div.style.pointerEvents = 'none'
div.style.top = '35px'
div.style.left = '200px'
div.style.position = 'fixed'
div.style.zIndex = '100000'
div.style.width = document.documentElement.clientWidth + 'px'
div.style.height = document.documentElement.clientHeight + 'px'
div.style.background = 'url(' + can.toDataURL('image/png') + ') left top repeat'
document.body.appendChild(div)
return id
}
// 该方法只允许调用一次
watermark.set = (str) => {
let id = setWatermark(str)
setInterval(() => {
if (document.getElementById(id) === null) {
id = setWatermark(str)
}
}, 500)
window.onresize = () => {
setWatermark(str)
}
}
const outWatermark = (id) => {
if (document.getElementById(id) !== null) {
const div = document.getElementById(id)
div.style.display = 'none'
}
}
watermark.out = () => {
const str = '1.23452384164.123412415'
outWatermark(str)
}
export default watermark
+253
View File
@@ -0,0 +1,253 @@
<template>
<div class="app-container">
<a-form :model="queryForm" layout="inline">
<a-form-item field="dictType" label="字典名称">
<a-select v-model="queryForm.dictType" :options="dictTypeOptions" :field-names="{ value: 'dictType', label: 'dictName' }" />
</a-form-item>
<a-form-item field="dictTag" label="字典标签">
<a-input v-model="queryForm.dictTag" placeholder="请输入字典标签" />
</a-form-item>
<a-form-item field="status" label="字典状态">
<a-select v-model="queryForm.status" placeholder="请选择字典状态">
<a-option :value="2">正常</a-option>
<a-option :value="1">关闭</a-option>
</a-select>
</a-form-item>
<a-form-item>
<a-space>
<a-button v-has="'admin:sysDictData:query'" type="primary">搜索</a-button>
<a-button>重置</a-button>
</a-space>
</a-form-item>
</a-form>
<a-divider />
<div class="table-action">
<a-button v-has="'admin:sysDictData:add'" type="primary" @click="handleAdd">新增</a-button>
</div>
<a-table
:data="tableData"
:columns="columns"
:pagination="{ 'show-total': true, 'show-jumper': true, 'show-page-size': true, total: pager.count, current: currentPage }"
@page-change="handlePageChange"
>
<template #createdAt="{ record }">
{{ parseTime(record.createdAt) }}
</template>
<template #action="{ record }">
<a-button v-has="'admin:sysDictData:edit'" type="text" @click="handleEdit(record)">修改</a-button>
<a-button v-has="'admin:sysDictData:remove'" type="text" @click="() => { deleteVisible = true; deleteData = [record.dictCode]; }">删除</a-button>
</template>
<template #status="{ record }">
<a-tag v-if="record.status == 2" color="green">正常</a-tag>
<a-tag v-else-if="record.status == 1" color="red">停用</a-tag>
</template>
</a-table>
<a-modal
v-model:visible="modalVisible"
:title="modalTitle"
title-align="start"
@close="$refs.modalFormRef.resetFields()"
@before-ok="handleBeforeOk"
@ok="handleSubmit"
>
<a-form :model="modalForm" :rules="rules" ref="modalFormRef">
<a-form-item field="dictType" label="字典类型">
<a-input v-model="modalForm.dictType" disabled></a-input>
</a-form-item>
<a-form-item field="dictLabel" label="数据标签">
<a-input
v-model="modalForm.dictLabel"
placeholder="请输入数据标签"
></a-input>
</a-form-item>
<a-form-item field="dictValue" label="数据键值">
<a-input
v-model="modalForm.dictValue"
placeholder="请输入数据键值"
></a-input>
</a-form-item>
<a-form-item field="dictSort" label="显示排序">
<a-input-number
v-model="modalForm.dictSort"
:default-value="0"
mode="button"
></a-input-number>
</a-form-item>
<a-form-item field="status" label="状态">
<a-radio-group v-model="modalForm.status">
<a-radio :value="2">正常</a-radio>
<a-radio :value="1">停用</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item field="remark" label="备注">
<a-textarea v-model="modalForm.remark"></a-textarea>
</a-form-item>
</a-form>
</a-modal>
<!-- Akiraka 20230223 删除与批量删除 开始 -->
<DeleteModal
:data="deleteData"
:visible="deleteVisible"
:apiDelete="deleteDictData"
@deleteVisibleChange="() => deleteVisible = false"
/>
<!-- Akiraka 20230223 删除与批量删除 结束 -->
</div>
</template>
<script setup>
import { reactive, ref, getCurrentInstance, onBeforeMount, watch } from 'vue';
import { getDictData, addDictData, updateDictData, deleteDictData } from '@/api/admin/sys-dict-data';
import { getDictType } from '@/api/admin/sys-dict';
// Akiraka 20230210 删除数据
const deleteData = ref([])
// Akiraka 20230210 删除对话框
const deleteVisible = ref(false)
// Akiraka 20230210 监听删除事件
watch(() => deleteVisible.value ,(value) => {
if ( value == false ) {
getDictDataInfo({...proxy.$route.params, ...pager});
}
})
const { proxy } = getCurrentInstance();
// 当前页码
const currentPage = ref(1);
// 分页
const pager = {
count: 0,
pageIndex: 1,
pageSize: 10,
};
// Rules
const rules = {
dictLabel: [{ required: true, message: '请输入数据标签' }],
dictValue: [{ required: true, message: '请输入数据键值' }],
dictSort: [{ required: true, message: '请选择排序' }],
};
// Modal
const modalVisible = ref(false);
const modalTitle = ref('默认标题');
// DictType Options
const dictTypeOptions = ref([]);
// 搜索表单
const queryForm = reactive({});
// Modal表单
const modalForm = reactive({
status: 2
});
// 表格
const tableData = ref([]);
const columns = [
{ title: '字典编码', dataIndex: 'dictCode' },
{ title: '字典标签', dataIndex: 'dictLabel' },
{ title: '字典键值', dataIndex: 'dictValue' },
{ title: '字典排序', dataIndex: 'dictSort' },
{ title: '状态', dataIndex: 'status', slotName: 'status' },
{ title: '备注', dataIndex: 'remark' },
{ title: '创建时间', dataIndex: 'createdAt', slotName: 'createdAt' },
{ title: '操作', dataIndex: 'action', slotName: 'action' },
];
// 新增字典数据
const handleAdd = () => {
modalVisible.value = true;
modalTitle.value = '新增字典数据';
Object.assign(modalForm, proxy.$route.params);
};
// 修改字典数据
const handleEdit = (record) => {
modalVisible.value = true;
modalTitle.value = '修改字典数据';
Object.assign(modalForm, proxy.$route.params, record);
console.log(modalForm)
};
// 提交表单前检查
const handleBeforeOk = (done) => {
proxy.$refs.modalFormRef.validate((err) => {
if (err) {
proxy.$message.error('表单校验失败');
done(false);
} else {
done();
}
})
}
// 提交表单
const handleSubmit = async () => {
if (modalForm.dictCode) {
const { code, msg } = await updateDictData(modalForm, modalForm.dictCode);
if ( code == 200 ) {
proxy.$notification.success('修改成功');
} else {
proxy.$notification.error(msg);
}
} else {
const { code, msg } = await addDictData(modalForm);
if (code == 200 ) {
proxy.$notification.success('新增成功');
} else {
proxy.$notification.error(msg);
}
}
getDictDataInfo({...proxy.$route.params, ...pager});
}
// 页码改变
const handlePageChange = (pagerIndex) => {
currentPage.value = pagerIndex;
pager.pageIndex = pagerIndex;
getDictDataInfo({...proxy.$route.params, ...pager});
}
// 获取字典数据
const getDictDataInfo = async (params = {}) => {
const { data, code, msg } = await getDictData(params);
if ( code == 200 ) {
tableData.value = data.list;
Object.assign(pager, { count: data.count, pageIndex: data.pageIndex, pageSize: data.pageSize });
} else {
proxy.$notification.error(msg);
}
};
// 获取字典类型
const getDictTypeInfo = async () => {
const res = await getDictType({ pageSize: 1000 });
dictTypeOptions.value = res.data.list;
Object.assign(queryForm, proxy.$route.params);
};
onBeforeMount(() => {
getDictDataInfo(proxy.$route.params);
getDictTypeInfo();
});
</script>
<style lang="scss" scoped>
.table-action {
margin-bottom: 12px;
}
</style>
+268
View File
@@ -0,0 +1,268 @@
<template>
<div class="app-container">
<a-form :model="queryForm" ref="queryFormRef" layout="inline">
<a-form-item field="dictName" label="字典名称">
<a-input v-model="queryForm.dictName" placeholder="请输入字典名称" @press-enter="handleQuery" />
</a-form-item>
<a-form-item field="dictType" label="字典类型">
<a-input v-model="queryForm.dictType" placeholder="请输入字典类型" @press-enter="handleQuery" />
</a-form-item>
<a-form-item field="status" label="状态">
<a-select v-model="queryForm.status" placeholder="请选择字典状态">
<a-option :value="2">正常</a-option>
<a-option :value="1">停用</a-option>
</a-select>
</a-form-item>
<a-form-item>
<a-space>
<a-button v-has="'admin:sysDictType:query'" type="primary" @click="handleQuery"><icon-search /> 搜索</a-button>
<a-button v-has="'admin:sysDictType:query'" @click="handleResetQuery"><icon-loop /> 重置</a-button>
</a-space>
</a-form-item>
</a-form>
<a-divider />
<div class="action">
<a-space>
<a-button v-has="'admin:sysDictType:add'" type="primary" @click="handleAdd"><icon-plus /> 新增</a-button>
<a-button v-has="'system:sysdicttype:remove'" type="primary" status="danger" @click="() => { deleteVisible = true; }"><icon-delete /> 批量删除</a-button>
<a-button type="primary" status="warning" disabled><icon-download /> 导出</a-button>
</a-space>
</div>
<!-- table -->
<a-table
:columns="tableColumns"
:data="tableData"
:row-selection="{ type: 'checkbox', showCheckedAll: true }"
:pagination="{ 'show-total': true, 'show-jumper': true, 'show-page-size': true, total: pager.count, current: currentPage }"
row-key="id"
@selection-change="(selection) => {deleteData = selection;}"
@page-change="handlePageChange"
@page-size-change="handlePageSizeChange"
>
<template #dictType="{ record }">
<router-link :to="`/admin/dict/data/${record.dictType}`">{{ record.dictType }}</router-link>
</template>
<template #status="{ record }">
<a-tag v-if="record.status == 2" color="green">正常</a-tag>
<a-tag v-else color="red">停用</a-tag>
</template>
<template #createdAt="{ record }">
{{ parseTime(record.createdAt) }}
</template>
<template #action="{ record }">
<a-button v-has="'admin:sysDictType:edit'" type="text" @click="handleUpdate(record)"><icon-edit /> 修改</a-button>
<a-button v-has="'admin:sysDictType:remove'" type="text" @click="() => { deleteVisible = true; deleteData = [record.id]; }"><icon-edit /> 删除</a-button>
</template>
</a-table>
<!-- Modal弹框 -->
<a-modal
v-model:visible="modalVisible"
title-align="start"
:title="modalTitle"
@before-ok="handleSubmit"
@close="$refs.modalFormRef.resetFields()"
>
<a-form :model="modalForm" ref="modalFormRef">
<a-form-item field="dictName" label="字典名称">
<a-input
v-model="modalForm.dictName"
placeholder="请输入字典名称"
:disabled="modalForm.id ? true : false"
></a-input>
</a-form-item>
<a-form-item field="dictType" label="字典类型">
<a-input
v-model="modalForm.dictType"
placeholder="请输入字典类型"
:disabled="modalForm.id ? true : false"
></a-input>
</a-form-item>
<a-form-item field="status" label="状态">
<a-radio-group v-model="modalForm.status">
<a-radio :value="2">正常</a-radio>
<a-radio :value="1">停用</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item field="remark" label="备注">
<a-textarea
v-model="modalForm.remark"
placeholder="请输入内容备注"
></a-textarea>
</a-form-item>
</a-form>
</a-modal>
<!-- Akiraka 20230223 删除与批量删除 开始 -->
<DeleteModal
:data="deleteData"
:visible="deleteVisible"
:apiDelete="removeDictType"
@deleteVisibleChange="() => deleteVisible = false"
/>
<!-- Akiraka 20230223 删除与批量删除 结束 -->
</div>
</template>
<script setup>
import { reactive, ref, getCurrentInstance, nextTick, onMounted, watch } from 'vue';
import { getDictType, addDictType, removeDictType, updateDictType } from '@/api/admin/sys-dict';
// Akiraka 20230210 删除数据
const deleteData = ref([])
// Akiraka 20230210 删除对话框
const deleteVisible = ref(false)
// Akiraka 20230210 监听删除事件
watch(() => deleteVisible.value ,(value) => {
if ( value == false ) {
getSysDictTypeInfo({ ...pager, ...queryForm });
}
})
const { proxy } = getCurrentInstance();
const currentPage = ref(1);
// pager
const pager = {
count: 0,
pageIndex: 1,
pageSize: 10,
};
// form
const modalForm = reactive({
status: 2,
});
// Table
const tableData = ref([]);
const tableColumns = [
{ title: '编号', dataIndex: 'id' },
{ title: '字典名称', dataIndex: 'dictName' },
{ title: '字典类型', dataIndex: 'dictType', slotName: 'dictType' },
{ title: '字典状态', dataIndex: 'status', slotName: 'status' },
{ title: '备注', dataIndex: 'remark' },
{ title: '创建时间', dataIndex: 'createdAt', slotName: 'createdAt' },
{ title: '操作', slotName: 'action' },
];
// Modal
const modalVisible = ref(false);
const modalTitle = ref('默认标题');
const { queryForm, handleQuery, handleResetQuery } = useQueryDict();
// 搜索
function useQueryDict() {
const queryForm = reactive({});
const handleQuery = () => {
getSysDictTypeInfo(queryForm);
};
const handleResetQuery = () => {
proxy.$refs.queryFormRef.resetFields();
currentPage.value = 1;
getSysDictTypeInfo(queryForm);
};
return {
queryForm,
handleQuery,
handleResetQuery,
};
}
// 页码改变
const handlePageChange = (page) => {
pager.pageIndex = page;
currentPage.value = page;
getSysDictTypeInfo({ ...pager, ...queryForm });
};
// 每页数据量改变
const handlePageSizeChange = (pageSize) => {
pager.pageSize = pageSize;
getSysDictTypeInfo({ ...pager, ...queryForm });
};
// 新增
const handleAdd = () => {
modalVisible.value = true;
modalTitle.value = '新增字典';
};
// 修改
const handleUpdate = async (record) => {
modalVisible.value = true;
modalTitle.value = '修改字典';
await nextTick();
Object.assign(modalForm, record);
};
// Submit
const handleSubmit = (done) => {
proxy.$refs.modalFormRef.validate(async (err) => {
if (!err) {
try {
if (Reflect.has(modalForm, 'id')) {
const { code, msg } = await updateDictType(modalForm, modalForm.id);
if (code == 200 ) {
proxy.$notification.success('更新成功');
} else {
proxy.$notification.error(msg);
}
} else {
const { code, msg } = await addDictType(modalForm);
currentPage.value = Math.ceil(++pager.count / pager.pageSize);
pager.pageIndex = currentPage.value;
if (code == 200 ) {
proxy.$notification.success('新增成功');
} else {
proxy.$notification.error(msg);
}
}
modalVisible.value = false;
getSysDictTypeInfo(pager);
done();
} catch (e) {
done(false);
}
} else {
proxy.$message.error('表单校验失败!');
done(false);
}
});
};
// 获取字典数据
const getSysDictTypeInfo = async (params = {}) => {
const { data, code, msg } = await getDictType(params);
if ( code == 200 ) {
tableData.value = data.list;
Object.assign(pager, { count: data.count, pageIndex: data.pageIndex, pageSize: data.pageSize });
} else {
proxy.$notification.error(msg);
}
};
onMounted(() => {
getSysDictTypeInfo();
});
</script>
<style lang="scss">
.action {
margin-bottom: 12px;
}
</style>
+254
View File
@@ -0,0 +1,254 @@
<template>
<div class="app-container">
<a-form :model="queryForm" ref="queryFormRef" layout="inline">
<a-form-item field="title" label="标题">
<a-input v-model="queryForm.title" placeholder="请输入标题" @press-enter="handleQuery" />
</a-form-item>
<a-form-item field="path" label="地址">
<a-input v-model="queryForm.path" placeholder="请输入地址" @press-enter="handleQuery" />
</a-form-item>
<a-form-item field="action" label="类型">
<a-select v-model="queryForm.action" placeholder="请选择类型">
<a-option>GET</a-option>
<a-option>POST</a-option>
<a-option>PUT</a-option>
<a-option>DELETE</a-option>
</a-select>
</a-form-item>
<a-form-item>
<a-space size="medium">
<a-button v-has="'admin:sysApi:query'" type="primary" @click="handleQuery">
<template #icon>
<icon-search />
</template>
搜索
</a-button>
<a-button @click="handlResetQuery">
<template #icon>
<icon-loop />
</template>
重置
</a-button>
</a-space>
</a-form-item>
</a-form>
<a-divider />
<!-- Table -->
<a-table
:columns="columns"
:data="tableData"
:pagination="{
'show-total': true,
'show-jumper': true,
'show-page-size': true,
total: pager.count,
current: currentPage,
}"
row-key="id"
@page-change="handlePageChange"
@page-size-change="handlePageSizeChange"
>
<template #reqType="{ record }">
<a-tag v-if="record.action.toLowerCase() === 'get'" color="cyan">{{record.action}}</a-tag>
<a-tag v-else-if="record.action.toLowerCase() === 'post'" color="gold" >{{ record.action }}</a-tag>
<a-tag v-else-if="record.action.toLowerCase() === 'put'" color="green" >{{ record.action }}</a-tag>
<a-tag v-else-if="record.action.toLowerCase() === 'delete'" color="pinkpurple" >{{ record.action }}</a-tag>
</template>
<template #createdAt="{ record }"> {{ parseTime(record.createdAt) }}</template>
<template #action="{ record }">
<a-button v-has="'admin:sysApi:edit'" type="text" @click="handleUpdate(record)"><icon-edit /> 修改 </a-button>
</template>
</a-table>
<!-- Drawer -->
<a-drawer
:visible="drawerVisible"
:width="450"
@before-ok="handleDrawerSubmit"
@cancel="handleDrawerCancel"
>
<template #title> 修改接口管理 </template>
<a-form :model="drawerForm" ref="drawerFormRef" :rules="rules">
<a-form-item field="handle" label="Handle">
<a-input v-model="drawerForm.handle" placeholder="请输入Handle" />
</a-form-item>
<a-form-item field="title" label="标题">
<a-input v-model="drawerForm.title" placeholder="请输入标题" />
</a-form-item>
<a-form-item field="type" label="类型">
<a-select
v-model="drawerForm.type"
placeholder="请选择类型"
allow-clear
>
<a-option>SYS</a-option>
<a-option>BUS</a-option>
</a-select>
</a-form-item>
<a-form-item field="action" label="方式">
<a-select
v-model="drawerForm.action"
placeholder="请选择请求方式"
allow-clear
>
<a-option>GET</a-option>
<a-option>POST</a-option>
<a-option>PUT</a-option>
<a-option>DELETE</a-option>
</a-select>
</a-form-item>
<a-form-item field="path" label="路径">
<a-input v-model="drawerForm.path" disabled />
</a-form-item>
</a-form>
</a-drawer>
</div>
</template>
<script setup>
import { reactive, ref, getCurrentInstance, onMounted, nextTick } from 'vue';
import { IconSearch, IconLoop } from '@arco-design/web-vue/es/icon';
import { getSysApi,addSysApi,updateSysApi } from '@/api/admin/sys-api';
const { proxy } = getCurrentInstance();
// 分页当前页
const currentPage = ref(1);
// Pager
const pager = {
count: 0,
pageIndex: 1,
pageSize: 10,
};
// From 定义
const queryForm = reactive({});
const drawerForm = reactive({});
// From 校验规则
const rules = {
title: [{ required: true, message: '请输入标题' }],
method: [{ required: true, message: '请选择方式' }],
};
// 抽屉显示
const drawerVisible = ref(false);
// Table Columns
const columns = [
{ title: '标题', dataIndex: 'title' },
{ title: '请求类型', dataIndex: 'action', slotName: 'reqType' },
{ title: '请求信息', dataIndex: 'path' },
{ title: '创建时间', dataIndex: 'createdAt', slotName: 'createdAt' },
{ title: '操作', slotName: 'action' },
];
// Table Data
const tableData = ref([]);
// Drawer 确定事件
// 异步关闭Modal需要调用 done()
const handleDrawerSubmit = (done) => {
proxy.$refs.drawerFormRef.validate(async (valid) => {
if (!valid) {
let res;
if (!drawerForm.id) {
const { code, msg } = await addSysApi(drawerForm);
if (code == 200 ) {
proxy.$notification.success('新增成功');
} else {
proxy.$notification.error(msg);
}
} else {
const { code, msg } = await updateSysApi(drawerForm, drawerForm.id);
if (code == 200 ) {
proxy.$notification.success('修改成功');
} else {
proxy.$notification.error(msg);
}
}
drawerVisible.value = false;
done();
getSysApiInfo(pager);
} else {
proxy.$message.error('表单校验失败');
done(false);
}
});
};
// Drawer 关闭事件
const handleDrawerCancel = () => {
drawerVisible.value = false;
};
// 查询
const handleQuery = async () => {
getSysApiInfo(queryForm);
currentPage.value = 1;
};
// 重置查询
const handlResetQuery = () => {
proxy.$refs.queryFormRef.resetFields();
handlePageChange(1);
};
// 修改
const handleUpdate = async (record) => {
drawerVisible.value = true;
// updateSysApi(record);
await nextTick();
Object.assign(drawerForm, record);
};
/**
* 分页改变
* @param {Number} [page]
*/
const handlePageChange = (page) => {
pager.pageIndex = page;
// 修改当前页码
currentPage.value = page;
getSysApiInfo({ ...pager, ...queryForm });
};
// 每页数据量
const handlePageSizeChange = (pageSize) => {
pager.pageSize = pageSize;
getSysApiInfo({ ...pager, ...queryForm });
};
// 获取接口信息
const getSysApiInfo = async (params = {}) => {
const { data, code, msg } = await getSysApi(params);
if ( code == 200 ) {
tableData.value = data.list;
Object.assign(pager, { count: data.count, pageIndex: data.pageIndex, pageSize: data.pageSize });
} else {
proxy.$notification.error(msg);
}
};
onMounted(() => {
getSysApiInfo(pager);
});
</script>
<style lang="scss">
.pagination {
margin-top: 12px;
display: flex;
justify-content: flex-end;
}
// Table 操作列样式
.arco-table-th:last-child > span {
margin-left: 15px;
}
</style>
+328
View File
@@ -0,0 +1,328 @@
<template>
<div class="app-container">
<a-form :model="queryForm" ref="queryFormRef" layout="inline">
<a-form-item field="configName" label="参数名称">
<a-input
v-model="queryForm.configName"
placeholder="请输入参数名称"
></a-input>
</a-form-item>
<a-form-item field="configKey" label="参数键名">
<a-input
v-model="queryForm.configKey"
placeholder="请输入参数键名"
></a-input>
</a-form-item>
<a-form-item field="configType" label="系统内置">
<a-select v-model="queryForm.configType" placeholder="选择系统内置">
<a-option value="Y"></a-option>
<a-option value="N"></a-option>
</a-select>
</a-form-item>
<a-form-item>
<a-space>
<a-button type="primary" @click="handleQuery()"><icon-search /> 搜索</a-button>
<a-button @click="handleResetQuery()"><icon-loop /> 重置</a-button>
</a-space>
</a-form-item>
</a-form>
<!-- 分割线 -->
<a-divider />
<div class="action">
<a-space>
<a-button v-has="'admin:sysConfig:add'" type="primary" @click="handleAdd"><icon-plus /> 新增</a-button>
<a-button v-has="'admin:sysConfig:remove'" type="primary" status="danger" disabled><icon-delete /> 删除</a-button>
<a-button type="primary" status="warning" disabled><icon-download /> 导出</a-button>
</a-space>
</div>
<a-table
:data="tableData"
:columns="columns"
:pagination="{ 'show-total': true, 'show-jumper': true, 'show-page-size': true, total: pager.count, current: currentPage }"
@page-change="handlePageChange"
@page-size-change="handlePageSizeChange"
>
<template #configType="{ record }">
<a-tag v-if="record.configType === 'Y'" color="green"></a-tag>
<a-tag v-else-if="record.configType === 'N'" color="red"></a-tag>
</template>
<template #createdAt="{ record }">
{{ parseTime(record.createdAt) }}
</template>
<template #action="{ record }">
<a-button v-has="'admin:sysConfig:edit'" type="text" @click="handleUpdate(record)"><icon-edit /> 修改</a-button>
<a-button v-has="'admin:sysConfig:edit'" type="text" @click="() => { deleteVisible = true; deleteData = [record.id]; }"><icon-delete /> 删除</a-button>
</template>
</a-table>
<a-modal
v-model:visible="visible"
:title="title"
title-align="start"
@before-ok="handleBeforeOk"
@close="handleResetModalForm"
>
<a-form :model="modalForm" :rules="rules" ref="modalFormRef">
<a-form-item field="configName" label="参数名称">
<a-input
v-model="modalForm.configName"
placeholder="请输入参数名称"
/>
</a-form-item>
<a-form-item field="configKey" label="参数键名">
<a-input v-model="modalForm.configKey" placeholder="请输入参数键名" />
</a-form-item>
<a-form-item field="configValue" label="参数键值">
<a-input
v-model="modalForm.configValue"
placeholder="请输入参数键值"
/>
</a-form-item>
<a-form-item field="configType" label="系统内置">
<a-radio-group v-model="modalForm.configType">
<a-radio value="Y"></a-radio>
<a-radio value="N"></a-radio>
</a-radio-group>
</a-form-item>
<a-form-item field="isFrontend" label="前台显示">
<a-radio-group v-model="modalForm.isFrontend">
<a-radio :value="1"></a-radio>
<a-radio :value="0"></a-radio>
</a-radio-group>
</a-form-item>
<a-form-item field="remark" label="备注">
<a-textarea
v-model="modalForm.remark"
placeholder="请输入备注内容"
></a-textarea>
</a-form-item>
</a-form>
</a-modal>
<!-- Akiraka 20230223 删除与批量删除 开始 -->
<DeleteModal
:data="deleteData"
:visible="deleteVisible"
:apiDelete="removeSysConfig"
@deleteVisibleChange="() => deleteVisible = false"
/>
<!-- Akiraka 20230223 删除与批量删除 结束 -->
</div>
</template>
<script setup>
import { onMounted, reactive, ref, getCurrentInstance, watch } from 'vue';
import { getSysConfig, addSysConfig, removeSysConfig, updateSysConfig } from '@/api/admin/sys-config';
// Akiraka 20230210 删除数据
const deleteData = ref([])
// Akiraka 20230210 删除对话框
const deleteVisible = ref(false)
// Akiraka 20230210 监听删除事件
watch(() => deleteVisible.value ,(value) => {
if ( value == false ) {
getSysConfigInfo(pager);
}
})
const { proxy } = getCurrentInstance();
const currentPage = ref(1);
const pager = {
count: 0,
pageIndex: 1,
pageSize: 10,
};
const { queryForm, handleQuery, handleResetQuery } = useQueryForm();
const { tableData, columns, handlePageChange, handlePageSizeChange } =
useTableData();
const {
rules,
title,
visible,
modalForm,
handleAdd,
handleUpdate,
handleBeforeOk,
} = useModal();
function useQueryForm() {
const queryForm = reactive({});
const handleQuery = () => {
getSysConfigInfo(queryForm);
};
const handleResetQuery = () => {
proxy.$refs.queryFormRef.resetFields();
handlePageChange(1);
};
return {
queryForm,
handleQuery,
handleResetQuery,
};
}
function useTableData() {
const tableData = ref([]);
const columns = [
{ title: '编码', dataIndex: 'id', width: 70 },
{ title: '名称', dataIndex: 'configName' },
{ title: '键名', dataIndex: 'configKey' },
{
title: '内置',
dataIndex: 'configType',
slotName: 'configType',
width: 100,
},
{
title: '备注',
dataIndex: 'remark',
width: 350,
ellipsis: true,
tooltip: true,
},
{ title: '创建时间', dataIndex: 'createdAt', slotName: 'createdAt' },
{ title: '操作', dataIndex: 'action', slotName: 'action' },
];
// 分页改变
const handlePageChange = (page) => {
pager.pageIndex = page;
getSysConfigInfo(pager);
};
// 每页数据量
const handlePageSizeChange = (pageSize) => {
pager.pageSize = pageSize;
getSysConfigInfo(pager);
};
return {
columns,
tableData,
handlePageChange,
handlePageSizeChange,
};
}
function useModal() {
const visible = ref(false);
const title = ref('默认标题');
const modalForm = reactive({
configType: 'Y',
isFrontend: 1,
});
const rules = {
configName: [{ required: true, message: '请输入参数名称' }],
configKey: [{ required: true, message: '请输入参数键名' }],
configValue: [{ required: true, message: '请输入参数键值' }],
};
const handleAdd = () => {
visible.value = true;
title.value = '新增参数';
};
const handleUpdate = async (record) => {
visible.value = true;
title.value = '修改参数';
Object.assign(modalForm, record);
};
const handleBeforeOk = (done) => {
proxy.$refs.modalFormRef.validate(async (err) => {
if (!err) {
try {
const msg = await handleSubmit(modalForm);
proxy.$message.success(msg);
done();
getSysConfigInfo();
} catch (e) {
proxy.$message.error(e);
done(false);
}
} else {
proxy.message.error('表单校验失败');
done(false);
}
});
};
return {
rules,
title,
visible,
modalForm,
handleAdd,
handleUpdate,
handleBeforeOk,
};
}
// 获取系统配置
const getSysConfigInfo = async (params = {}) => {
const { data, code, msg } = await getSysConfig(params);
if ( code == 200 ) {
tableData.value = data.list;
Object.assign(pager, { count: data.count, pageIndex: data.pageIndex, pageSize: data.pageSize });
} else {
proxy.$notification.error(msg);
}
};
// 提交
const handleSubmit = (data) => {
return new Promise(async (resolve, reject) => {
try {
let res;
if (!data.id) {
const { code, msg } = await addSysConfig(data);
if (code == 200 ) {
proxy.$notification.success('新增成功');
} else {
proxy.$notification.error(msg);
}
} else {
const { code, msg } = await updateSysConfig(data, data.id);
if (code == 200 ) {
proxy.$notification.success('修改成功');
} else {
proxy.$notification.error(msg);
}
}
} catch (err) {
reject(err);
}
});
};
// 重置表单
const handleResetModalForm = () => {
proxy.$refs.modalFormRef.resetFields();
modalForm.id = null;
}
onMounted(() => {
getSysConfigInfo();
});
</script>
<style lang="scss">
.action {
margin-bottom: 8px;
}
</style>
+266
View File
@@ -0,0 +1,266 @@
<template>
<div class="app-container">
<a-form :model="queryForm" ref="queryFormRef" layout="inline">
<a-form-item field="deptName" label="部门名称">
<a-input v-model="queryForm.deptName" placeholder="请输入部门名称" @press-enter="handleQuery" />
</a-form-item>
<a-form-item field="status" label="部门状态">
<a-select v-model="queryForm.status" placeholder="请选择部门状态">
<a-option :value="2">正常</a-option>
<a-option :value="1">停用</a-option>
</a-select>
</a-form-item>
<a-form-item>
<a-space>
<a-button v-has="'admin:sysDept:query'" type="primary" @click="handleQuery"><icon-search /> 搜索</a-button>
<a-button @click="handleResetQuery"><icon-loop /> 重置</a-button>
</a-space>
</a-form-item>
</a-form>
<a-divider />
<div class="action">
<a-button v-has="'admin:sysDept:add'" type="primary" @click="handleAdd()"><icon-plus /> 新增</a-button>
</div>
<!-- 异步数据需要defualt-expanded-keys 传入所有行Key才能默认展开 -->
<a-table
:columns="columns"
:data="tableData"
:pagination="false"
:default-expanded-keys="[1]"
row-key="deptId"
>
<template #status="{ record }">
<a-tag color="green" v-if="record.status === 2">正常</a-tag>
<a-tag color="red" v-else> 停用 </a-tag>
</template>
<template #createdAt="{ record }">
{{ parseTime(record.createdAt) }}
</template>
<template #action="{ record }">
<a-button v-has="'admin:sysDept:edit'" type="text" @click="handleUpdate(record)"><icon-edit /> 修改</a-button>
<a-button v-has="'admin:sysDept:add'" type="text" @click="handleAdd(record)"><icon-plus /> 新增</a-button>
<a-button v-has="'admin:sysDept:remove'" type="text" @click="() => { deleteVisible = true; deleteData = [record.deptId]; }"><icon-delete /> 删除</a-button>
</template>
</a-table>
<!-- Modal -->
<a-modal
v-model:visible="modalVisible"
:title="modalTitle"
title-align="start"
@before-ok="handleBeforeOk"
@close="() => $refs.modalFormRef.resetFields()"
>
<a-form :model="modalForm" :rules="rules" ref="modalFormRef">
<a-form-item field="parentId" label="上级部门">
<a-tree-select
v-model="modalForm.parentId"
:data="tableData"
:field-names="{ key: 'deptId', title: 'deptName' }"
placeholder="请选择上级部门"
allow-clear
/>
</a-form-item>
<a-form-item field="deptName" label="部门名称">
<a-input v-model="modalForm.deptName" placeholder="请输入部门名称" />
</a-form-item>
<a-form-item field="leader" label="负责人">
<a-input v-model="modalForm.leader" placeholder="请输入负责人" />
</a-form-item>
<a-form-item field="phone" label="联系电话">
<a-input v-model="modalForm.phone" placeholder="请输入联系电话" />
</a-form-item>
<a-form-item field="email" label="邮箱">
<a-input v-model="modalForm.email" placeholder="请输入邮箱" />
</a-form-item>
<a-form-item field="status" label="部门状态">
<a-radio-group v-model="modalForm.status">
<a-radio :value="2">正常</a-radio>
<a-radio :value="1">停用</a-radio>
</a-radio-group>
</a-form-item>
</a-form>
</a-modal>
<!-- Akiraka 20230223 删除与批量删除 开始 -->
<DeleteModal
:data="deleteData"
:visible="deleteVisible"
:apiDelete="removeDept"
@deleteVisibleChange="() => deleteVisible = false"
/>
<!-- Akiraka 20230223 删除与批量删除 结束 -->
</div>
</template>
<script setup>
import { reactive, ref, onMounted, getCurrentInstance, nextTick, watch } from 'vue';
import { getDept, addDept, removeDept, updateDept } from '@/api/admin/sys-dept';
// Akiraka 20230210 删除数据
const deleteData = ref([])
// Akiraka 20230210 删除对话框
const deleteVisible = ref(false)
// Akiraka 20230210 监听删除事件
watch(() => deleteVisible.value ,(value) => {
if ( value == false ) {
getDeptInfo(queryForm);
}
})
const { proxy } = getCurrentInstance();
// Modal
const modalVisible = ref(false);
const modalTitle = ref('默认标题');
// Form
const queryForm = reactive({});
const modalForm = reactive({
status: 2,
});
// rules
const rules = {
parentId: [{ required: true, message: '请选择上级部门' }],
deptName: [{ required: true, message: '请输入部门名称' }],
leader: [{ required: true, message: '请输入负责人' }],
};
// Columns
const columns = [
{
title: '部门名称',
dataIndex: 'deptName',
},
{
title: '排序',
dataIndex: 'sort',
},
{
title: '状态',
dataIndex: 'status',
slotName: 'status',
},
{
title: '创建时间',
dataIndex: 'createdAt',
slotName: 'createdAt',
},
{
title: '操作',
slotName: 'action',
},
];
// Table Data
const tableData = ref([]);
// 查询
const handleQuery = async () => {
const res = await getDept(queryForm);
tableData.value = deepDelChildren(res.data);
};
// 重置查询
const handleResetQuery = () => {
proxy.$refs.queryFormRef.resetFields();
getDeptInfo(queryForm);
}
// 新增
const handleAdd = ({ deptId, status = 2 } = {}) => {
modalVisible.value = true;
modalTitle.value = '新增部门';
if (deptId) Object.assign(modalForm, {parentId: deptId, status});
};
// 修改
const handleUpdate = async (record) => {
modalVisible.value = true;
modalTitle.value = '修改部门信息';
await nextTick();
const { parentId, deptName, leader, phone, email, status, deptId } = record;
Object.assign(modalForm, {
parentId,
deptName,
leader,
phone,
email,
status,
deptId,
});
};
// 递归删除空Children
function deepDelChildren(data) {
const depts = data;
let len = depts?.length;
// let len = depts && depts.length;
for (let i = 0; i < len; i++) {
if (depts[i].children.length > 0) {
deepDelChildren(depts[i].children);
} else {
delete depts[i].children;
}
}
return depts;
}
// Modal 表单提交前检查
const handleBeforeOk = (done) => {
proxy.$refs.modalFormRef.validate(async (err) => {
if (!err) {
let res;
if (Reflect.has(modalForm, 'deptId')) {
const { code, msg } = await updateDept(modalForm, modalForm.deptId);
if (code == 200 ) {
proxy.$notification.success('修改成功');
} else {
proxy.$notification.error(msg);
}
} else {
const { code, msg } = await addDept(modalForm);
if (code == 200 ) {
proxy.$notification.success('新增成功');
} else {
proxy.$notification.error(msg);
}
}
done();
getDeptInfo();
} else {
proxy.$message.error('数据校验失败');
done(false);
}
});
};
// 获取部门信息
const getDeptInfo = async (params = {}) => {
const { data, code, msg } = await getDept(params);
if ( code == 200 ) {
tableData.value = deepDelChildren(data);
} else {
proxy.$notification.error(msg);
}
};
onMounted(() => {
getDeptInfo();
});
</script>
<style lang="scss">
.action {
margin-bottom: 8px;
}
</style>
+169
View File
@@ -0,0 +1,169 @@
<template>
<div class="app-container">
<a-form :model="queryForm" ref="queryFormRef" layout="inline">
<a-form-item field="username" label="用户名">
<a-input v-model="queryForm.username" placeholder="请输入用户名" @press-enter="handleQuery" />
</a-form-item>
<a-form-item field="status" label="状态">
<a-select v-model="queryForm.status" placeholder="请选择系统登录日志状态">
<a-option :value="2">正常</a-option>
<a-option :value="1">关闭</a-option>
</a-select>
</a-form-item>
<a-form-item field="ipaddr" label="IP地址">
<a-input v-model="queryForm.ipaddr" placeholder="请输入IP地址" />
</a-form-item>
<a-form-item>
<a-space>
<a-button type="primary" @click="handleQuery"><icon-search /> 搜索</a-button>
<a-button @click="handleResetQuery"><icon-loop /> 重置</a-button>
</a-space>
</a-form-item>
</a-form>
<a-divider />
<div class="action">
<a-space>
<a-button
type="primary"
status="danger"
:disabled="selectedKeys.length === 0"
@click="handleBatchDelete"
><icon-delete /> 批量删除</a-button
>
</a-space>
</div>
<a-table
:data="tableData"
:columns="columns"
row-key="id"
:row-selection="{ type: 'checkbox', showCheckedAll: true }"
:pagination="{
'show-total': true,
'show-jumper': true,
'show-page-size': true,
current: currentPage,
total: pager.count,
}"
v-model:selectedKeys="selectedKeys"
@page-change="handlePageChange"
@page-size-change="handlePageSizeChange"
>
<template #status="{ record }">
<a-tag v-if="record.status == 2" color="green">正常</a-tag>
<a-tag v-if="record.status == 1" color="red">失败</a-tag>
</template>
<template #loginTime="{ record }">
{{ parseTime(record.loginTime) }}
</template>
<template #action="{ record }">
<a-popconfirm
content="是否删除当前数据?"
@ok="removeSysLoginLogInfo([record.id])"
>
<a-button type="text" status="danger">删除</a-button>
</a-popconfirm>
</template>
</a-table>
</div>
</template>
<script setup>
import { ref, reactive, onMounted, getCurrentInstance } from 'vue';
import { getSysLoginLog, removeSysLoginLog } from '@/api/admin/sys-login-log';
const { proxy } = getCurrentInstance();
// 当前页
const currentPage = ref(1);
// Pager
const pager = {
count: 0,
pageIndex: 1,
pageSize: 10,
};
// 表格多选
const selectedKeys = ref([]);
const queryForm = reactive({});
const tableData = ref([]);
const columns = [
{ title: '用户名', dataIndex: 'username' },
{ title: '类型', dataIndex: 'msg' },
{ title: '状态', dataIndex: 'status', slotName: 'status' },
{ title: 'IP地址', dataIndex: 'ipaddr' },
{ title: '登陆时间', dataIndex: 'loginTime', slotName: 'loginTime' },
{ title: '操作', slotName: 'action', slotName: 'action' },
];
// 查询
const handleQuery = () => {
getSysLoginLogInfo({ ...pager, ...queryForm });
}
// 重置查询
const handleResetQuery = () => {
proxy.$refs.queryFormRef.resetFields();
handlePageChange(1);
};
// 页码改变
const handlePageChange = (page) => {
currentPage.value = page;
pager.pageIndex = page;
getSysLoginLogInfo({ ...pager, ...queryForm });
};
// 每页数据量
const handlePageSizeChange = (pageSize) => {
pager.pageSize = pageSize;
getSysLoginLogInfo({ ...pager, ...queryForm });
};
// 批量删除
const handleBatchDelete = () => {
proxy.$modal.confirm({
title: '注意',
content: '是否删除当前所有勾选数据?',
onOk: () => {
removeSysLoginLogInfo(selectedKeys.value);
},
onCancel: () => {
proxy.$message.info('已取消操作');
}
})
}
// 获取登陆日志信息
const getSysLoginLogInfo = async (params = {}) => {
const res = await getSysLoginLog(params);
const { count, list, pageIndex, pageSize } = res.data;
tableData.value = list;
Object.assign(pager, { total: count, pageIndex, pageSize });
};
// 删除登陆日志信息
const removeSysLoginLogInfo = async (ids) => {
const res = await removeSysLoginLog({ ids });
proxy.$message.success(res.msg);
getSysLoginLogInfo();
};
onMounted(() => {
getSysLoginLogInfo();
});
</script>
<style lang="scss">
.action {
margin-bottom: 8px;
}
</style>
+350
View File
@@ -0,0 +1,350 @@
<template>
<div class="app-container">
<a-form :model="queryForm" ref="queryFormRef" layout="inline">
<a-form-item field="title" label="菜单名称">
<a-input v-model="queryForm.title" placeholder="请输入菜单名称" @press-enter="handleQuery"/>
</a-form-item>
<a-form-item field="visible" label="状态">
<a-select v-model="queryForm.visible" placeholder="请选择菜单状态">
<a-option value="1">显示</a-option>
<a-option value="0">隐藏</a-option>
</a-select>
</a-form-item>
<a-form-item>
<a-space>
<a-button type="primary" @click="handleQuery">搜索</a-button>
<a-button @click="$refs.queryFormRef.resetFields()">重置</a-button>
</a-space>
</a-form-item>
</a-form>
<a-divider />
<!-- 动作 -->
<div class="action">
<a-space>
<a-button type="primary" @click="handleAddMenu()">新增菜单</a-button>
</a-space>
</div>
<!-- 菜单管理列表 -->
<a-table :columns="columns" :data="tableData" row-key="menuId" :pagination="false">
<template #icon="{ record }">
<component :is="record.icon" :style="{ fontSize: '18px' }"></component>
</template>
<template #menutype="{ record }">
<a-tag color="purple" v-if="record.menuType === 'M'">目录</a-tag>
<a-tag color="orange" v-else-if="record.menuType === 'C'">菜单</a-tag>
<a-tag color="blue" v-else-if="record.menuType === 'F'">按钮</a-tag>
</template>
<template #isFrame="{ record }">
<a-tag v-if="record.isFrame == '1'" color="green">内部</a-tag>
<a-tag v-else color="red">外部</a-tag>
</template>
<template #visible="{ record }">
<a-tag v-if="record.visible == '0'" color="green">显示</a-tag>
<a-tag v-else color="red">隐藏</a-tag>
</template>
<template #action="{ record }">
<a-button v-has="'admin:sysMenu:add'" type="text" @click="handleAddMenu(record.menuId)">新增</a-button>
<a-button v-has="'admin:sysMenu:edit'" type="text" @click="handleUpdate(record)">修改</a-button>
<a-button v-has="'admin:sysMenu:remove'" type="text" @click="() => { deleteVisible = true; deleteData = [record.menuId]; }">删除</a-button>
</template>
</a-table>
<!-- 菜单管理新增与提交弹窗 -->
<a-modal v-model:visible="modalVisible" :title="modalTitle" title-align="start" :width="800" modal-class="menu-modal" @before-ok="handleSubmit" @close="() => {$refs.modalFormRef.resetFields(); modalForm.menuId = null;}">
<a-form :model="modalForm" :rules="modalRules" ref="modalFormRef" auto-label-width label-align="left">
<a-form-item field="menuType" label="菜单类型">
<a-radio-group v-model="modalForm.menuType">
<a-radio value="M">目录</a-radio>
<a-radio value="C">菜单</a-radio>
<a-radio value="F">按钮</a-radio>
</a-radio-group>
</a-form-item>
<a-row :gutter="24">
<a-col :span="12">
<a-form-item field="parentId" label="上级菜单">
<a-tree-select v-model="modalForm.parentId" :data="tableData" :field-names="{ key: 'menuId', icon: '_' }" :allow-search="true" :filter-tree-node="filterTreeNode" placeholder="请选择上级菜单"/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item field="title" label="菜单标题">
<a-input v-model="modalForm.title" placeholder="请输入菜单标题" />
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="24">
<a-col :span="12">
<a-form-item field="icon" label="菜单图标">
<a-select v-model="modalForm.icon" :options="parseIconName" :field-names="{ name: 'value' }" :trigger-props="{ contentClass: 'iconselect-trigger' }" allow-search placeholder="请选择菜单图标">
<template #option="{ data }">
<component :is="data.value" />
</template>
</a-select>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item field="sort" label="显示排序">
<a-input-number v-model="modalForm.sort" mode="button" />
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="24">
<a-col :span="12" v-if="modalForm.menuType !== 'F'">
<a-form-item field="menuName" label="路由名称">
<a-input v-model="modalForm.menuName" placeholder="请输入路由名称" />
</a-form-item>
</a-col>
<a-col :span="12" v-if="modalForm.menuType !== 'F'">
<a-form-item field="component" label="组件名称">
<a-input v-model="modalForm.component" placeholder="请输入组件名称"/>
</a-form-item>
</a-col>
</a-row>
<a-row :gutter="24">
<a-col :span="12" v-if="modalForm.menuType !== 'F'">
<a-form-item field="path" label="路由地址">
<a-input v-model="modalForm.path" placeholder="请输入路由地址" />
</a-form-item>
</a-col>
<a-col :span="12" v-if="modalForm.menuType === 'C' || modalForm.menuType === 'F'">
<a-form-item field="permission" label="权限标识">
<a-input v-model="modalForm.permission" placeholder="请输入权限标识"/>
</a-form-item>
</a-col>
</a-row>
<a-form-item field="isFrame" label="是否外链" v-if="modalForm.menuType !== 'F'">
<a-radio-group v-model="modalForm.isFrame">
<a-radio value="0"></a-radio>
<a-radio value="1"></a-radio>
</a-radio-group>
</a-form-item>
<a-form-item field="visible" label="菜单状态" v-if="modalForm.menuType !== 'F'">
<a-radio-group v-model="modalForm.visible">
<a-radio value="0">显示</a-radio>
<a-radio value="1">隐藏</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item field="apis" label="API权限" v-if="modalForm.menuType !== 'M'">
<a-transfer v-model:model-value="modalForm.apis" :data="transferData" :title="['未授权', '已授权']" show-search />
</a-form-item>
</a-form>
</a-modal>
<!-- Akiraka 20230210 删除与批量删除 开始 -->
<DeleteModal
:data="deleteData"
:visible="deleteVisible"
:apiDelete="removeMenu"
@deleteVisibleChange="() => deleteVisible = false"
/>
<!-- Akiraka 20230210 删除与批量删除 结束 -->
</div>
</template>
<script setup>
import { onMounted, reactive, ref, getCurrentInstance, computed, watch, watchEffect } from 'vue';
import * as ArcoIconModules from '@arco-design/web-vue/es/icon';
import { getMenu } from '@/api/admin/menu';
import { getSysApi } from '@/api/admin/sys-api';
import { addMenu, removeMenu, updateMenu, getMenuDetails } from '@/api/admin/menu';
// Akiraka 20230210 删除数据
const deleteData = ref([])
// Akiraka 20230210 删除对话框
const deleteVisible = ref(false)
// Akiraka 20230210 监听删除事件
watch(() => deleteVisible.value ,(value) => {
if ( value == false ) {
getSysMenuInfo(pager);
}
})
const { proxy } = getCurrentInstance();
const { queryForm, handleQuery } = useQueryData();
// 默认页码
const currentPage = ref(1);
// 表格分页
const pager = {
count: 0,
current: 1,
pageSize: 10,
};
// QueryModel
function useQueryData() {
// 表单查询
const queryForm = reactive({});
const handleQuery = () => {
getSysMenuInfo(queryForm);
};
return {
queryForm,
handleQuery,
};
}
// 默认表单
const modalForm = reactive({
menuType: 'M',
sort: 0,
isFrame: '1',
visible: '0',
});
// 表单校验
const modalRules = {
title: [{ required: true, message: '请输入菜单名称' }],
path: [{ required: true, message: '请输入菜单路由地址' }],
component: [
{ required: true, message: '请输入菜单路由地址' },
{ pattern: /^[\/A-Za-z.-.()*]+$/, trigger: 'blur', message: '校验规则: 只允许输入字母 a-z 或大写 A-Z 与 -'}
],
};
// 监听事件 20220715
watchEffect(() => {
// 当菜单类型为目录时组件地址则为 Layout
if (modalForm.menuType == "M") {
modalForm.component = "Layout"
} else {
// 当菜单类型设置为菜单时 如果为 Layout 则为空
if (modalForm.component == "Layout") {
modalForm.component = ""
// 当菜单类型设置为菜单时 编辑页面显示正常内容
} else {
modalForm.component = modalForm.component
}
}
})
// Table
const columns = [
{ title: '菜单名称', dataIndex: 'title',width: "220" },
// { title: '图标', dataIndex: 'icon', slotName: 'icon' },
{ title: '路由地址', dataIndex: 'path', width: "400" },
{ title: '组件地址', dataIndex: 'component', width: "300" },
{ title: '排序', dataIndex: 'sort', width: "80" },
{ title: '类型', dataIndex: 'menuType', slotName: 'menutype', width: "100" },
{ title: '是否外联', dataIndex: 'isFrame', slotName: 'isFrame', width: "100" },
{ title: '显示状态', dataIndex: 'visible', slotName: 'visible', width: "100" },
{ title: '操作', slotName: 'action' ,width: "220", fixed: "right" },
];
const tableData = ref([]);
// Transfer Data
const transferData = ref([]);
// Modal
const modalVisible = ref(false);
const modalTitle = ref('默认标题');
// 创建新菜单
const handleAddMenu = (parentId = null) => {
modalVisible.value = true;
modalTitle.value = '新增菜单';
if (parentId) modalForm.parentId = parentId;
getSysMenuInfo();
};
// TreeSearchFilter
const filterTreeNode = (searchVal, nodeData) => {
return nodeData.title.indexOf(searchVal) > -1;
};
// 修改菜单
const handleUpdate = async (record) => {
const res = await getMenuDetails(record.menuId);
Object.assign(modalForm, res.data);
modalTitle.value = '修改菜单';
modalVisible.value = true;
};
// handleSubmit 新增与修改按钮方法 20220713
const handleSubmit = (done) => {
proxy.$refs.modalFormRef.validate(async (valid) => {
if (!valid) {
if (!modalForm.menuId) {
console.log("dasdasd",modalForm)
const { success } = await addMenu(modalForm);
if (success) proxy.$message.success('新增成功');
} else {
const { success } = await updateMenu(modalForm, modalForm.menuId);
if (success) proxy.$message.success('修改成功');
}
getSysMenuInfo(pager);
done();
} else {
proxy.$message.error('表单校验失败');
done(false);
}
});
};
// 获取菜单信息
const getSysMenuInfo = async (params = {}) => {
const { code, data, msg } = await getMenu(params);
if ( code == 200 ) {
tableData.value = data;
} else {
proxy.$notification.error(msg)
}
};
// 获取API接口信息
const getSysApiInfo = async () => {
const { code, data, msg } = await getSysApi({ pageSize: 10000, type: 'BUS' });
if ( code == 200 ) {
transferData.value = data.list.map((item) => {
return { value: item.id, label: item.title };
});
} else {
proxy.$notification.error(msg)
}
};
// 转换Icon Object 为 list
const parseIconName = computed(() => {
const iconNameList = [];
for (let key in ArcoIconModules) {
if (ArcoIconModules[key].name) {
iconNameList.push({ value: ArcoIconModules[key].name });
}
}
return iconNameList;
});
onMounted(() => {
getSysMenuInfo();
getSysApiInfo();
});
</script>
<style lang="scss">
// 覆盖默认 select trigger 样式
.iconselect-trigger .arco-select-dropdown-list {
display: flex;
flex-wrap: wrap;
}
.iconselect-trigger .arco-select-option {
width: auto;
}
// 覆盖默认穿梭框样式
.menu-modal {
.arco-transfer-view {
height: 350px;
width: 250px;
}
}
</style>
+185
View File
@@ -0,0 +1,185 @@
<template>
<div class="app-container">
<a-form :model="queryForm" ref="queryFormRef" layout="inline">
<a-form-item field="status" label="状态">
<a-select
v-model="queryForm.status"
placeholder="请选择系统操作日志状态"
>
<a-option :value="2">正常</a-option>
<a-option :value="1">关闭</a-option>
</a-select>
</a-form-item>
<a-form-item field="createdAt" label="创建时间">
<a-range-picker
disabled
v-model="queryForm.createdAt"
style="width: 254px"
/>
</a-form-item>
<a-form-item>
<a-space>
<a-button type="primary" @click="handleQuery"
><icon-search /> 搜索</a-button
>
<a-button @click="handleResetQuery"><icon-loop /> 重置</a-button>
</a-space>
</a-form-item>
</a-form>
<a-divider />
<div class="action">
<a-space>
<a-button
type="primary"
status="danger"
@click="handleBatchDelete"
:disabled="selectedKeys.length === 0"
><icon-delete /> 批量删除</a-button
>
</a-space>
</div>
<a-table
:data="tableData"
:columns="columns"
:row-selection="{ type: 'checkbox', showCheckedAll: true }"
row-key="id"
:pagination="{
'show-total': true,
'show-jumper': true,
'show-page-size': true,
current: currentPage,
total: pager.count,
}"
v-model:selectedKeys="selectedKeys"
@page-change="handlePageChange"
@page-size-change="handlePageSizeChange"
>
<template #status="{ record }">
<a-tag v-if="record.status == 2" color="green">正常</a-tag>
<a-tag v-if="record.status == 1" color="red">关闭</a-tag>
</template>
<template #operTime="{ record }">
{{ parseTime(record.operTime) }}
</template>
<template #action="{ record }">
<a-popconfirm
content="是否删除当前数据?"
type="warning"
@ok="removeSysOperaLogInfo([record.id])"
>
<a-button type="text" status="danger">删除</a-button>
</a-popconfirm>
</template>
</a-table>
</div>
</template>
<script setup>
import { ref, reactive, onMounted, getCurrentInstance } from 'vue';
import { getSysOperaLog, removeSysOperaLog } from '@/api/admin/sys-opera-log';
const { proxy } = getCurrentInstance();
// 当前页
const currentPage = ref(1);
// 分页
const pager = {
count: 0,
pageIndex: 1,
pageSize: 10,
};
//
const selectedKeys = ref([]);
const queryForm = reactive({});
const tableData = ref([]);
const columns = [
{ title: '编号', dataIndex: 'id' },
{ title: '请求信息', dataIndex: 'operUrl', width: 500 },
{ title: '操作人员', dataIndex: 'operName' },
{ title: '状态', dataIndex: 'status', slotName: 'status' },
{ title: '操作日期', dataIndex: 'operTime', slotName: 'operTime' },
{ title: '操作', slotName: 'action', slotName: 'action' },
];
// 查询
const handleQuery = () => {
handlePageChange(1);
};
// 重置查询
const handleResetQuery = () => {
proxy.$refs.queryFormRef.resetFields();
handlePageChange(1);
};
// 批量删除
const handleBatchDelete = () => {
console.log(selectedKeys.value);
if (selectedKeys.value.length === 0) {
proxy.$message.error('请勾选需要删除的数据!');
return;
}
proxy.$modal.confirm({
title: '注意',
content: '是否批量删除选中?',
onOk: () => {
removeSysOperaLogInfo(selectedKeys.value);
},
onCancel: () => {
proxy.$message.info('已取消操作');
},
});
};
// 页码改变
const handlePageChange = (page) => {
currentPage.value = page;
pager.pageIndex = page;
getSysOperaLogInfo({ ...pager, ...queryForm });
};
//
const handlePageSizeChange = (pageSize) => {
pager.pageSize = pageSize;
getSysOperaLogInfo({ ...pager, ...queryForm });
};
// 获取操作日志
const getSysOperaLogInfo = async (params = {}) => {
const res = await getSysOperaLog(params);
const { count, list, pageIndex, pageSize } = res.data;
tableData.value = list;
Object.assign(pager, { total: count, pageIndex, pageSize });
};
/**
* 删除操作日志
* @params {array} ids
*/
const removeSysOperaLogInfo = async (ids) => {
const res = await removeSysOperaLog({ ids });
proxy.$message.success(res.msg);
getSysOperaLogInfo();
};
onMounted(() => {
getSysOperaLogInfo();
});
</script>
<style lang="scss">
.action {
margin-bottom: 8px;
}
</style>
+289
View File
@@ -0,0 +1,289 @@
<template>
<div class="app-container">
<a-form :model="queryForm" ref="queryFormRef" layout="inline">
<a-form-item field="postCode" label="岗位编码">
<a-input v-model="queryForm.postCode" placeholder="请输入岗位编码" @press-enter="handleQuery" />
</a-form-item>
<a-form-item field="postName" label="岗位名称">
<a-input v-model="queryForm.postName" placeholder="请输入岗位名称" @press-enter="handleQuery" />
</a-form-item>
<a-form-item field="status" label="岗位状态">
<a-select v-model="queryForm.status" placeholder="请选择岗位状态" :style="{ width: '181px' }">
<a-option :value="2">正常</a-option>
<a-option :value="1">停用</a-option>
</a-select>
</a-form-item>
<a-form-item>
<a-space>
<a-button v-has="'admin:sysPost:query'" type="primary" @click="handleQuery"><icon-search /> 搜索</a-button>
<a-button @click="handleResetQuery"><icon-loop /> 重置</a-button>
</a-space>
</a-form-item>
</a-form>
<a-divider />
<!-- action -->
<div class="action">
<a-space>
<a-button v-has="'admin:sysPost:add'" type="primary" @click="handleAdd"><icon-plus /> 新增 </a-button>
<a-button v-has="'admin:sysPost:remove'" type="primary" status="danger" @click="() => { deleteVisible = true; }"><icon-delete /> 批量删除 </a-button>
<a-button type="primary" status="warning" disabled><icon-download /> 导出 </a-button>
</a-space>
</div>
<!-- table -->
<a-table
:columns="columns"
:data="tableData"
:pagination="{ 'show-total': true, 'show-jumper': true, 'show-page-size': true, total: pager.count, current: currentPage }"
:row-selection="{ type: 'checkbox', showCheckedAll: true }"
row-key="postId"
@selection-change="(selection) => {deleteData = selection;}"
@page-change="handlePageChange"
@page-size-change="handlePageSizeChange"
>
<template #createdAt="{ record }">
{{ parseTime(record.createdAt) }}
</template>
<template #status="{ record }">
<a-tag v-if="record.status == 2" color="green">正常</a-tag>
<a-tag v-else color="red">停用</a-tag>
</template>
<template #action="{ record }">
<a-space>
<a-button v-has="'admin:sysPost:edit'" type="text" @click="handleUpdate(record)"><icon-edit /> 修改</a-button>
<a-button v-has="'admin:sysPost:remove'" type="text" @click="() => { deleteVisible = true; deleteData = [record.postId]; }"><icon-delete /> 删除</a-button>
</a-space>
</template>
</a-table>
<!-- Modal -->
<a-modal
v-model:visible="modalVisible"
:title="modalTitle"
title-align="start"
@before-ok="handleSubmit"
@close="() => {$refs.modalFormRef.resetFields(); modalForm.postId = null;}"
>
<a-form :model="modalForm" :rules="rules" ref="modalFormRef">
<a-form-item field="postName" label="岗位名称">
<a-input v-model="modalForm.postName" placeholder="请输入岗位名称" />
</a-form-item>
<a-form-item field="postCode" label="岗位编码">
<a-input v-model="modalForm.postCode" placeholder="请输入岗位编码" />
</a-form-item>
<a-form-item field="sort" label="岗位排序">
<a-input-number
v-model="modalForm.sort"
mode="button"
:default-value="0"
:style="{ width: '150px' }"
/>
</a-form-item>
<a-form-item field="status" label="岗位状态">
<a-radio-group v-model="modalForm.status">
<a-radio :value="2"> 正常 </a-radio>
<a-radio :value="1"> 停用 </a-radio>
</a-radio-group>
</a-form-item>
<a-form-item field="remark" label="备注">
<a-textarea v-model="modalForm.remark" placeholder="请输入备注内容" />
</a-form-item>
</a-form>
</a-modal>
<!-- Akiraka 20230223 删除与批量删除 开始 -->
<DeleteModal
:data="deleteData"
:visible="deleteVisible"
:apiDelete="removePost"
@deleteVisibleChange="() => deleteVisible = false"
/>
<!-- Akiraka 20230223 删除与批量删除 结束 -->
</div>
</template>
<script setup>
import { reactive, ref, getCurrentInstance, onMounted, nextTick, watch } from 'vue';
import { getPost, addPost, removePost, updatePost } from '@/api/admin/post';
import { parseTime } from '@/utils/parseTime';
// Akiraka 20230210 删除数据
const deleteData = ref([])
// Akiraka 20230210 删除对话框
const deleteVisible = ref(false)
// Akiraka 20230210 监听删除事件
watch(() => deleteVisible.value ,(value) => {
if ( value == false ) {
getPostInfo(pager);
}
})
const { proxy } = getCurrentInstance();
const currentPage = ref(1);
// Pager
const pager = {
count: 0,
pageIndex: 1,
pageSize: 10,
};
// form
const queryForm = reactive({});
const modalForm = reactive({
sort: 0,
status: 2,
});
// Rules
const rules = {
postName: [{ required: true, message: '请输入岗位名称' }],
postCode: [{ required: true, message: '请输入岗位编码' }],
sort: [{ required: true, message: '请选择岗位排序' }],
};
// Modal
const modalVisible = ref(false);
const modalTitle = ref('默认标题');
// Batch Del List
let batchList = [];
// Table Columns
const columns = [
{ title: '岗位编号', dataIndex: 'postId' },
{ title: '岗位编码', dataIndex: 'postCode' },
{ title: '岗位名称', dataIndex: 'postName' },
{ title: '岗位排序', dataIndex: 'sort' },
{ title: '状态', dataIndex: 'status', slotName: 'status' },
{ title: '创建时间', dataIndex: 'createdAt', slotName: 'createdAt' },
{ title: '操作', slotName: 'action' },
];
// Table Data
const tableData = ref([]);
// 新增
const handleAdd = () => {
modalVisible.value = true;
modalTitle.value = '新增岗位';
};
// 修改
const handleUpdate = async (record) => {
modalVisible.value = true;
modalTitle.value = '修改岗位';
await nextTick();
Object.assign(modalForm, record);
};
// Modal ok
// 异步关闭Modal需要调用 done()
const handleSubmit = (done) => {
proxy.$refs.modalFormRef.validate(async (valid) => {
if (!valid) {
let res;
if (!modalForm.postId) {
const { code, msg } = await addPost(modalForm);
if (code == 200 ) {
proxy.$notification.success('新增成功');
} else {
proxy.$notification.error(msg);
}
} else {
const { code, msg } = await updatePost(modalForm, modalForm.postId);
if (code == 200 ) {
proxy.$notification.success('更新成功');
} else {
proxy.$notification.error(msg);
}
}
done();
getPostInfo(pager);
} else {
proxy.$message.error('表单校验失败');
done(false);
}
});
};
// 批量删除
const handleBatchDelete = () => {
if (batchList.length !== 0) {
proxy.$modal.warning({
title: '提示',
content: '是否批量删除以下选中的数据?',
hideCancel: false,
onOk: async () => {
const res = await removePost({ ids: batchList });
proxy.$message.success(res.msg);
getPostInfo(pager);
},
onCancel: () => {
proxy.$message.info('已取消批量删除数据');
},
});
} else {
proxy.$message.error('请勾选需要删除的数据!');
}
};
/**
* 分页改变
* @param {Number} [page]
*/
const handlePageChange = (page) => {
pager.pageIndex = page;
// 修改当前页码
currentPage.value = page;
getPostInfo({ ...pager, ...queryForm });
};
// 每页数据量
const handlePageSizeChange = (pageSize) => {
pager.pageSize = pageSize;
getPostInfo({ ...pager, ...queryForm });
};
// 获取岗位信息
const getPostInfo = async (params = {}) => {
const { data, code, msg } = await getPost(params);
if ( code == 200 ) {
tableData.value = data.list;
Object.assign(pager, { count: data.count, pageIndex: data.pageIndex, pageSize: data.pageSize });
} else {
proxy.$notification.error(msg);
}
};
// 查询岗位信息
const handleQuery = async () => {
const params = {
pageIndex: pager.pageIndex,
pageSize: pager.pageSize,
...queryForm,
};
getPostInfo(params);
};
// 重置搜索
const handleResetQuery = () => {
proxy.$refs.queryFormRef.resetFields();
getPostInfo(queryForm);
}
onMounted(() => {
getPostInfo(pager);
});
</script>
<style lang="scss">
.action {
margin-bottom: 12px;
}
</style>
+382
View File
@@ -0,0 +1,382 @@
<template>
<div class="app-container">
<a-form :model="queryForm" ref="queryFormRef" layout="inline">
<a-form-item field="roleName" label="角色名称">
<a-input v-model="queryForm.roleName" placeholder="请输入角色名称" @press-enter="handleQuery" />
</a-form-item>
<a-form-item field="roleKey" label="权限字符">
<a-input v-model="queryForm.roleKey" placeholder="请输入权限字符" @press-enter="handleQuery" />
</a-form-item>
<a-form-item field="status" label="状态">
<a-select v-model="queryForm.status" placeholder="请选择角色状态">
<a-option :value="2">正常</a-option>
<a-option :value="1">停用</a-option>
</a-select>
</a-form-item>
<a-form-item>
<a-space>
<a-button type="primary" @click="handleQuery"><icon-search /> 搜索</a-button>
<a-button @click="handleResetQuery"><icon-loop /> 重置</a-button>
</a-space>
</a-form-item>
</a-form>
<a-divider />
<div class="action">
<a-space>
<a-button v-has="'admin:sysRole:add'" type="primary" @click="handleAdd"><icon-plus /> 新增</a-button>
<a-button v-has="'admin:sysRole:remove'" type="primary" status="danger" @click="() => { deleteVisible = true; }"><icon-delete /> 批量删除</a-button>
<a-button type="primary" status="warning" disabled><icon-download /> 导出</a-button>
</a-space>
</div>
<a-table
:columns="columns"
:data="tableData"
:pagination="{ 'show-total': true, 'show-jumper': true, 'show-page-size': true, total: pager.count, current: currentPage }"
row-key="roleId"
:row-selection="{ type: 'checkbox', showCheckedAll: true }"
@selection-change="(selection) => {deleteData = selection;}"
@select="handleSelect"
@page-change="handlePageChange"
@page-size-change="handlePageSizeChange"
>
<template #status="{ record }">
<a-tag v-if="record.status == 2" color="green">正常</a-tag>
<a-tag v-else color="red">停用</a-tag>
</template>
<template #createdAt="{ record }">
{{ parseTime(record.createdAt) }}
</template>
<template #action="{ record }">
<a-space>
<a-button v-has="'admin:sysRole:edit'" type="text" @click="handleUpdate(record)"><icon-edit /> 修改</a-button>
<a-button v-has="'admin:sysRole:update'" type="text" @click="handleDataScope(record)"><icon-check-circle /> 数据权限 </a-button>
<a-button v-has="'admin:sysRole:remove'" type="text" @click="() => { deleteVisible = true; deleteData = [record.roleId]; }"><icon-check-circle /> 删除 </a-button>
</a-space>
</template>
</a-table>
<!-- Role Modal -->
<a-modal
v-model:visible="modalVisible"
:title="title"
title-align="start"
@before-ok="handleBeforeOk"
@close="handleCancel"
>
<a-form :model="modalForm" :rules="rules" ref="modalFormRef">
<a-form-item field="roleName" label="角色名称">
<a-input v-model="modalForm.roleName" placeholder="请输入角色名称" />
</a-form-item>
<a-form-item field="roleKey" label="权限字符">
<a-input v-model="modalForm.roleKey" placeholder="请输入权限字符" />
</a-form-item>
<a-form-item field="roleSort" label="角色排序">
<a-input-number
v-model="modalForm.roleSort"
mode="button"
:default-value="0"
:style="{ width: '150px' }"
/>
</a-form-item>
<a-form-item field="status" label="状态">
<a-radio-group v-model="modalForm.status">
<a-radio value="2">正常</a-radio>
<a-radio value="1">停用</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item label="权限设置">
<a-tree
v-model:checked-keys="checkedKeys"
:checkable="true"
:check-strictly="false"
:data="treeData"
:default-expand-all="false"
:field-names="{ key: 'id', title: 'label' }"
/>
</a-form-item>
<a-form-item field="remark" label="备注">
<a-textarea v-model="modalForm.remark" placeholder="请输入备注内容" />
</a-form-item>
</a-form>
</a-modal>
<!-- DataScope Modal -->
<a-modal
v-model:visible="scopedModalVisible"
:title="title"
title-align="start"
@before-ok="handleScopeBeforeOk"
@close="handleCancel"
>
<a-form :model="scopeForm">
<a-form-item field="roleName" label="角色名称">
<a-input v-model="scopeForm.roleName" disabled />
</a-form-item>
<a-form-item field="roleKey" label="权限字符">
<a-input v-model="scopeForm.roleKey" disabled />
</a-form-item>
<a-form-item field="dataScope" label="权限范围">
<a-select v-model="scopeForm.dataScope" placeholder="请选择权限范围">
<a-option
v-for="item in dataScopeOptions"
:key="item.value"
:value="item.value"
:label="item.label"
/>
</a-select>
</a-form-item>
</a-form>
</a-modal>
<!-- Akiraka 20230223 删除与批量删除 开始 -->
<DeleteModal
:data="deleteData"
:visible="deleteVisible"
:apiDelete="removeRole"
@deleteVisibleChange="() => deleteVisible = false"
/>
<!-- Akiraka 20230223 删除与批量删除 结束 -->
</div>
</template>
<script setup>
import { onMounted, reactive, ref, getCurrentInstance, nextTick, watch } from 'vue';
import { getRole, addRole, updateRole, removeRole, updateRoleScoped, getRoleMenuTree } from '@/api/admin/role';
// Akiraka 20230210 删除数据
const deleteData = ref([])
// Akiraka 20230210 删除对话框
const deleteVisible = ref(false)
// Akiraka 20230210 监听删除事件
watch(() => deleteVisible.value ,(value) => {
if ( value == false ) {
getRoleInfo({ ...pager, ...queryForm });
}
})
const { proxy } = getCurrentInstance();
const currentPage = ref(1);
// Pager
const pager = {
count: 0,
pageIndex: 1,
pageSize: 10,
};
// Batch Delete List
const batchDeleteList = ref([]);
// Form
const queryForm = reactive({});
const modalForm = reactive({
sort: 0,
status: '2',
});
const scopeForm = reactive({});
// rules
const rules = {
roleName: [{ required: true, message: '请输入角色名称' }],
roleKey: [{ required: true, message: '请输入权限字符' }],
};
// ScopeOption
const dataScopeOptions = [
{
value: '1',
label: '全部数据权限',
},
{
value: '2',
label: '自定数据权限',
},
{
value: '3',
label: '本部门数据权限',
},
{
value: '4',
label: '本部门及以下数据权限',
},
{
value: '5',
label: '仅本人数据权限',
},
];
// Table Columns
const columns = [
{ title: '编号', dataIndex: 'roleId' },
{ title: '角色名称', dataIndex: 'roleName' },
{ title: '权限字符', dataIndex: 'roleKey' },
{ title: '排序', dataIndex: 'roleSort' },
{ title: '状态', dataIndex: 'status', slotName: 'status' },
{ title: '创建时间', dataIndex: 'createdAt', slotName: 'createdAt' },
{ title: '操作', slotName: 'action', width: 250 },
];
// Table Data;
const tableData = ref([]);
// Tree Data;
const checkedKeys = ref([]);
const treeData = ref([]);
// Modal
const modalVisible = ref(false);
const scopedModalVisible = ref(false);
const title = ref('默认标题');
// Table Select
const handleSelect = (rowKey) => {
batchDeleteList.value = rowKey;
};
// 查询
const handleQuery = async () => {
const res = await getRole({ ...pager, ...queryForm });
const { count, list, pageIndex, pageSize } = res.data;
tableData.value = list;
Object.assign(pager, { count, pageIndex, pageSize });
};
// 重置查询
const handleResetQuery = () => {
proxy.$refs.queryFormRef.resetFields();
getRoleInfo(queryForm);
};
// 创建
const handleAdd = () => {
modalVisible.value = true;
title.value = '创建角色';
};
// 修改角色
const handleUpdate = async (record) => {
modalVisible.value = true;
title.value = '修改角色';
await nextTick();
Object.assign(modalForm, record);
// 显示勾选的菜单,checkedKeys 传入id数组即可
const menuIdsChecked = [];
record.sysMenu.forEach((item) => {
menuIdsChecked.push(item.menuId);
});
checkedKeys.value = menuIdsChecked;
};
// 分配数据权限
const handleDataScope = async (record) => {
scopedModalVisible.value = true;
title.value = '分配数据权限';
const { roleKey, roleName, dataScope, roleId } = record;
await nextTick();
Object.assign(scopeForm, { roleKey, roleName, dataScope, roleId });
};
/**
* 分页改变
* @param {Number} [page]
*/
const handlePageChange = (page) => {
pager.pageIndex = page;
// 修改当前页码
currentPage.value = page;
getRoleInfo({ ...pager, ...queryForm });
};
// 每页数据量
const handlePageSizeChange = (pageSize) => {
pager.pageSize = pageSize;
getRoleInfo({ ...pager, ...queryForm });
};
// 角色管理表单提交
// 异步关闭表单需要使用 done() 回调函数
const handleBeforeOk = (done) => {
proxy.$refs.modalFormRef.validate(async (valid) => {
// 如果 valid 为空则数据校验通过
if (!valid) {
modalForm.menuIds = checkedKeys.value;
let res;
if (modalForm.roleId) {
const { code, msg } = await updateRole(modalForm, modalForm.roleId);
if (code == 200 ) {
proxy.$notification.success('更新成功');
} else {
proxy.$notification.error(msg);
}
} else {
const { code, msg } = await addRole(modalForm);
if (code == 200 ) {
proxy.$notification.success('新增成功');
} else {
proxy.$notification.error(msg);
}
}
getRoleInfo();
done();
} else {
proxy.$message.error('数据校验失败');
done(false);
}
});
};
// 数据权限表单提交
const handleScopeBeforeOk = async (done) => {
const res = await updateRoleScoped(scopeForm);
proxy.$message.success(res.msg);
done();
getRoleInfo();
};
// 重置数据表单
const handleCancel = () => {
modalVisible.value = false;
modalForm.roleId = null;
scopeForm.roleId = null;
checkedKeys.value = [];
proxy.$refs.modalFormRef.resetFields();
};
// 获取角色信息
const getRoleInfo = async (params = {}) => {
const { data, code, msg } = await getRole(params);
if ( code == 200 ) {
tableData.value = data.list;
Object.assign(pager, { count: data.count, pageIndex: data.pageIndex, pageSize: data.pageSize });
} else {
proxy.$notification.error(msg);
}
};
// 获取角色菜单信息
const getRoleMenuTreeInfo = async () => {
const res = await getRoleMenuTree({}, 0);
treeData.value = res.data.menus;
checkedKeys.value = res.data.checkedKeys;
};
onMounted(() => {
getRoleInfo();
getRoleMenuTreeInfo();
});
</script>
<style setup>
.action {
margin-bottom: 12px;
}
</style>
+77
View File
@@ -0,0 +1,77 @@
<template>
<div class="app-container">
<a-tabs default-active-key="1" position="left" :style="{ textAlign: 'right' }">
<a-tab-pane key="1" title="系统内置">
<a-form :model="form" :style="{ width: '50%' }">
<a-form-item field="sys_app_name" label="系统名称">
<a-input v-model="form.sys_app_name"></a-input>
</a-form-item>
<a-form-item field="sys_app_logo" label="系统Logo">
<a-space>
<div class="upload-logo-preview">
<img width="150" height="150" />
</div>
<a-upload action="/">
<template #upload-button>
<div class="upload-logo-card">
<div class="upload-logo-card-text">
<IconPlus />
</div>
</div>
</template>
</a-upload>
</a-space>
</a-form-item>
<a-form-item field="sys_user_initPassword" label="初始密码">
<a-input-password v-model="form.sys_user_initPassword"></a-input-password>
</a-form-item>
<a-form-item field="sys_index_skinName" label="皮肤样式">
<a-select v-model="form.sys_index_skinName">
<a-option>蓝色</a-option>
</a-select>
</a-form-item>
<a-form-item field="sys_index_sideTheme" label="侧边栏主题">
<a-select v-model="form.sys_index_sideTheme">
<a-option>深色主题</a-option>
</a-select>
</a-form-item>
<a-form-item>
<a-space>
<a-button type="primary">提交</a-button>
<a-button>重置</a-button>
</a-space>
</a-form-item>
</a-form>
</a-tab-pane>
<a-tab-pane key="2" title="其它">暂无内容</a-tab-pane>
</a-tabs>
</div>
</template>
<script setup>
import { reactive } from 'vue';
import { IconPlus } from '@arco-design/web-vue/es/icon'
const form = reactive({});
</script>
<style lang="scss">
.upload-logo-preview {
font-size: 0px;
}
.upload-logo-card {
display: flex;
align-items: center;
justify-content: center;
width: 150px;
height: 150px;
box-sizing: border-box;
border: 1px dashed #c0ccda;
border-radius: 5px;
color: #4e5969;
&-text {
font-size: 24px;
}
}
</style>
@@ -0,0 +1,31 @@
<template>
<div class="tree-container">
<a-tree
:data="props.data"
:field-names="{ key: 'deptId', title: 'deptName' }"
block-node
@select="handleTreeSelect"
/>
</div>
</template>
<script setup>
const props = defineProps({
data: {
type: Array,
default: () => [],
},
});
const emits = defineEmits(['nodeClick']);
const handleTreeSelect = (selectKeys) => {
emits('nodeClick', { deptId: `/${selectKeys}/` });
}
</script>
<style lang="scss">
.tree-container {
padding-right: 20px;
}
</style>
+621
View File
@@ -0,0 +1,621 @@
<template>
<div class="app-container">
<!-- Query -->
<a-form ref="queryFormRef" :model="queryForm" layout="inline">
<a-form-item field="deptName" label="部门名称">
<a-input v-model="queryForm.deptName" placeholder="请输入部门名称" @press-enter="handleQuery" />
</a-form-item>
<a-form-item field="username" label="用户名称">
<a-input v-model="queryForm.username" placeholder="请输入用户名称" @press-enter="handleQuery" />
</a-form-item>
<a-form-item field="phone" label="手机号码">
<a-input v-model="queryForm.phone" placeholder="请输入用户手机号" @press-enter="handleQuery" />
</a-form-item>
<a-form-item field="status" label="用户状态">
<a-select
v-model="queryForm.status"
placeholder="请选择用户状态"
:style="{ width: '205px' }"
>
<a-option value="2">正常</a-option>
<a-option value="1">停用</a-option>
</a-select>
</a-form-item>
<a-divider direction="vertical" :style="{ height: '30px' }" />
<a-form-item class="form-action">
<a-space size="medium">
<a-button v-has="'admin:sysUser:query'" type="primary" @click="handleQuery"><icon-search /> 搜索</a-button>
<a-button @click="handleResetQuery"><icon-loop /> 重置</a-button>
</a-space>
</a-form-item>
</a-form>
<!-- divider -->
<a-divider />
<!-- Table -->
<a-row>
<a-col :span="4">
<!-- tree组件只在组件第一次渲染时展开此处等待数据加载完成再渲染组件 -->
<tree-dept v-if="treeDeptData" :data="treeDeptData" @node-click="getSysUserInfo" />
</a-col>
<a-col :span="20">
<!-- Action -->
<a-space class="action">
<a-button v-has="'admin:sysUser:add'" type="primary" @click="handleAdd" data-test="newUser"><icon-plus /> 新增</a-button>
<a-button v-has="'admin:sysUser:remove'" type="primary" status="danger" @click="() => { deleteVisible = true; }"><icon-delete /> 批量删除</a-button>
</a-space>
<!-- Table -->
<a-table
:columns="columns"
:data="tableData"
:bordered="false"
:row-selection="{ type: 'checkbox', showCheckedAll: true }"
:pagination="{ 'show-total': true, 'show-jumper': true, 'show-page-size': true, total: pager.count, current: currentPage }"
row-key="userId"
@selection-change="(selection) => {deleteData = selection;}"
@page-change="handlePageChange"
@page-size-change="handlePageSizeChange"
>
<template #dept="{ record }">
{{ record.dept.deptName }}
</template>
<template #status="{ record }">
<a-switch
v-model="record.status"
checked-value="2"
unchecked-value="1"
@change="handleSwitchChange(record)"
/>
</template>
<template #createdAt="{ record }">
{{ parseTime(record.createdAt) }}
</template>
<template #action="{ record }">
<a-button v-has="'admin:sysUser:edit'" type="text" @click="handleUpdate(record)"><icon-edit /> 修改</a-button>
<a-button v-has="'admin:sysUser:edit'" type="text" @click="() => { deleteVisible = true; deleteData = [record.userId]; }"><icon-delete /> 删除</a-button>
<a-button v-has="'admin:sysUser:resetPassword'" type="text" @click="handleReset(record.userId)"><icon-refresh /> 重置</a-button>
</template>
</a-table>
</a-col>
</a-row>
<!-- Modal -->
<a-modal
v-model:visible="modalVisible"
title-align="start"
:width="600"
@cancel="handleModalCancel('modalFormRef')"
@before-ok="handleBeforeOk"
>
<template #title>
{{ modalTitle }}
</template>
<a-form
ref="modalFormRef"
:model="modalForm"
:rules="rules"
auto-label-width
>
<a-row :gutter="16">
<a-col :span="12">
<a-form-item field="nickName" label="用户昵称">
<a-input
v-model="modalForm.nickName"
placeholder="请输入用户昵称"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item field="deptId" label="所属部门">
<a-tree-select
v-model="modalForm.deptId"
:data="treeDeptData"
:field-names="{ key: 'deptId', title: 'deptName' }"
placeholder="请选择所属部门"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item field="phone" label="手机号码">
<a-input v-model="modalForm.phone" placeholder="请输入手机号码" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item field="email" label="邮箱">
<a-input v-model="modalForm.email" placeholder="请输入邮箱" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item field="username" label="用户名称">
<a-input
v-model="modalForm.username"
placeholder="请输入用户名称"
/>
</a-form-item>
</a-col>
<a-col v-if="!modalForm.userId" :span="12">
<a-form-item field="password" label="用户密码">
<a-input-password
v-model="modalForm.password"
placeholder="请输入用户密码"
/>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item field="sex" label="用户性别">
<a-select v-model="modalForm.sex" placeholder="请选择用户性别">
<a-option value="0"> 男 </a-option>
<a-option value="1"> 女 </a-option>
</a-select>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item field="status" label="状态">
<a-radio-group v-model="modalForm.status">
<a-radio value="2"> 正常 </a-radio>
<a-radio value="1"> 停用 </a-radio>
</a-radio-group>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item field="postId" label="岗位">
<a-select v-model="modalForm.postId" placeholder="请选择岗位">
<a-option
v-for="item in postList"
:key="item.postId"
:value="item.postId"
:label="item.postName"
/>
</a-select>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item field="roleId" label="角色">
<a-select v-model="modalForm.roleId" placeholder="请选择角色">
<a-option
v-for="item in roleList"
:key="item.roleId"
:value="item.roleId"
:label="item.roleName"
/>
</a-select>
</a-form-item>
</a-col>
</a-row>
<a-form-item field="remark" label="备注">
<a-textarea placeholder="请输入备注" allow-clear />
</a-form-item>
</a-form>
</a-modal>
<a-modal
v-model:visible="resetPwdVisible"
title="重置密码"
@before-ok="handleResetPwd"
@cancel="$refs.resetPwdFormRef.resetFields()"
>
<a-form
ref="resetPwdFormRef"
:model="resetPwdForm"
:rules="resetPwdRules"
auto-label-width
>
<a-form-item field="password" label="新密码">
<a-input-password
v-model="resetPwdForm.password"
placeholder="请输入新密码"
/>
</a-form-item>
<a-form-item field="repeatPwd" label="确认密码">
<a-input-password
v-model="resetPwdForm.repeatPwd"
placeholder="请输入确认密码"
/>
</a-form-item>
</a-form>
</a-modal>
<!-- Akiraka 20230223 删除与批量删除 开始 -->
<DeleteModal
:data="deleteData"
:visible="deleteVisible"
:apiDelete="removeUser"
@deleteVisibleChange="() => deleteVisible = false"
/>
<!-- Akiraka 20230223 删除与批量删除 结束 -->
</div>
</template>
<script setup>
import { reactive, ref, getCurrentInstance, onMounted, watch } from 'vue';
import { IconSearch, IconLoop } from '@arco-design/web-vue/es/icon';
import TreeDept from './components/TreeDept.vue';
import { getUser, addUser, updateUser, removeUser, updateUserStatus, resetUserPwd } from '@/api/admin/sys-user';
import { getRole } from '@/api/admin/role';
import { getPost } from '@/api/admin/post';
import { getDept } from '@/api/admin/sys-dept';
// Akiraka 20230210 删除数据
const deleteData = ref([])
// Akiraka 20230210 删除对话框
const deleteVisible = ref(false)
// Akiraka 20230210 监听删除事件
watch(() => deleteVisible.value ,(value) => {
if ( value == false ) {
getSysUserInfo({ ...pager, ...queryForm });
}
})
const { proxy } = getCurrentInstance();
// Query
const { queryForm, handleQuery, handleResetQuery } = useQueryData();
// ApiInfo
const { currentPage, getSysPostInfo, getSysRoleInfo, getSysDeptTreeInfo, getSysUserInfo } =
useApiInfo();
// Pager
const pager = reactive({
count: 0,
pageIndex: 1,
pageSize: 10
});
// Reset Pwd
const {
resetPwdForm,
resetPwdVisible,
resetPwdRules,
handleReset,
handleResetPwd,
} = useResetPwd();
// Table Operate
const {
columns,
tableData,
treeDeptData,
roleList,
postList,
handlePageChange,
handlePageSizeChange,
handleSwitchChange,
} = useTableList();
// ModalForm Operate
const {
rules,
modalForm,
modalVisible,
modalTitle,
handleAdd,
handleUpdate,
handleBeforeOk,
handleModalCancel,
} = useModalOperate();
function useQueryData() {
const queryForm = reactive({});
// 查询
const handleQuery = () => {
getSysUserInfo(queryForm);
};
// 重置查询
const handleResetQuery = () => {
proxy.$refs.queryFormRef.resetFields();
getSysUserInfo(queryForm);
};
return { queryForm, handleQuery, handleResetQuery };
}
function useResetPwd() {
const resetPwdVisible = ref(false);
const resetPwdForm = reactive({});
// Rules
const resetPwdRules = {
password: [{ required: true, message: '请输入密码' }],
repeatPwd: [
{
required: true,
message: '请重复输入密码',
},
{
validator: (value, cb) => {
if (value !== resetPwdForm.password) {
cb('两次输入的密码不一致');
}
},
},
],
};
// 用户重置密码API参数
const resetParams = {};
// 重置
const handleReset = (userId) => {
resetPwdVisible.value = true;
resetParams.userId = userId;
};
// 重置用户密码
const handleResetPwd = (done) => {
proxy.$refs.resetPwdFormRef.validate(async (err) => {
if (!err) {
resetParams.password = resetPwdForm.password;
const { msg } = await resetUserPwd(resetParams);
proxy.$message.success(msg);
done();
getSysUserInfo();
} else {
done(false);
}
});
};
return {
resetPwdForm,
resetPwdVisible,
resetPwdRules,
handleReset,
handleResetPwd,
};
}
function useApiInfo() {
const currentPage = ref(1);
/**
* 获取用户信息
* @param {*} [params]
*/
const getSysUserInfo = async (params = {}) => {
const { data, code, msg } = await getUser(params);
if ( code == 200 ) {
tableData.value = data.list;
Object.assign(pager, { count: data.count, pageIndex: data.pageIndex, pageSize: data.pageSize });
} else {
proxy.$notification.error(msg);
}
};
// 获取角色信息
const getSysRoleInfo = async () => {
const res = await getRole();
roleList.value = res.data.list;
};
// 获取岗位信息
const getSysPostInfo = async () => {
const res = await getPost();
postList.value = res.data.list;
};
// 获取部门树信息
const getSysDeptTreeInfo = async () => {
const res = await getDept();
treeDeptData.value = res.data;
};
return {
currentPage,
getSysPostInfo,
getSysRoleInfo,
getSysDeptTreeInfo,
getSysUserInfo,
};
}
function useTableList() {
// 部门数据
const treeDeptData = ref();
const roleList = ref([]);
const postList = ref([]);
// Table columns
const columns = [
{
title: '编号',
dataIndex: 'userId',
},
{
title: '登录名',
dataIndex: 'username',
},
{
title: '昵称',
dataIndex: 'nickName',
},
{
title: '部门',
dataIndex: 'deptName',
slotName: 'dept',
},
{
title: '手机号',
dataIndex: 'phone',
},
{
title: '状态',
dataIndex: 'status',
slotName: 'status',
},
{
title: '创建时间',
dataIndex: 'createdAt',
slotName: 'createdAt',
},
{
title: '操作',
slotName: 'action',
},
];
// Table Data
const tableData = ref([]);
/**
* 分页改变
* @param {Number} [page]
*/
const handlePageChange = (page) => {
pager.pageIndex = page;
// 修改当前页码
currentPage.value = page;
getSysUserInfo({ ...pager, ...queryForm });
};
// 每页数据量
const handlePageSizeChange = (pageSize) => {
pager.pageSize = pageSize;
getSysUserInfo({ ...pager, ...queryForm });
};
// 用户状态快速切换
const handleSwitchChange = (record) => {
proxy.$modal.warning({
title: '注意',
content: `是否${record.status == 1 ? '停用' : '启用'} ${
record.username
} 用户?`,
hideCancel: false,
onOk: async () => {
const params = { userId: record.userId, status: record.status };
const res = await updateUserStatus(params);
proxy.$message.success(res.msg);
},
onCancel: () => {
record.status = record.status == '2' ? '1' : '2';
},
});
};
return {
treeDeptData,
roleList,
postList,
columns,
tableData,
handlePageChange,
handlePageSizeChange,
handleSwitchChange,
};
}
function useModalOperate() {
const modalVisible = ref(false);
const modalTitle = ref('默认标题');
// Form
const modalForm = reactive({ status: '2' });
// AddRules
const rules = {
nickName: [{ required: true, message: '请输入用户昵称' }],
deptId: [{ required: true, message: '请选择所属部门' }],
phone: [{ required: true, message: '请输入手机号' }],
email: [
{ required: true, message: '请输入邮箱' },
{ type: 'email', message: '请输入正确的邮箱格式' },
],
username: [{ required: true, message: '请输入用户名称' }],
password: [{ required: true, message: '请输入用户密码' }],
};
// Modal 取消后重置表单
const handleModalCancel = (formEl) => {
modalVisible.value = false;
resetForm(formEl);
modalForm.userId = null;
};
// 新增用户
const handleAdd = () => {
modalVisible.value = true;
modalTitle.value = '新增用户';
};
/**
* 修改用户
* @param {Object} val
*/
const handleUpdate = (val) => {
modalVisible.value = true;
Object.assign(modalForm, val);
};
// 重置Form
const resetForm = (formEl) => {
if (!formEl) return;
proxy.$refs[formEl].resetFields();
};
// 表单提交
const handleBeforeOk = (done) => {
proxy.$refs.modalFormRef.validate(async (valid) => {
if (!valid) {
if (!modalForm.userId) {
const { code, msg } = await addUser(modalForm);
if (code == 200 ) {
proxy.$notification.success('新增成功');
} else {
proxy.$notification.error(msg);
}
} else {
const { code, msg } = await updateUser(modalForm, modalForm.userId);
if (code == 200 ) {
proxy.$notification.success('更新成功');
} else {
proxy.$notification.error(msg);
}
}
done();
proxy.$refs.modalFormRef.resetFields();
getSysUserInfo();
} else {
proxy.$message.error('表单校验失败');
done(false);
}
});
};
return {
rules,
modalForm,
modalVisible,
modalTitle,
handleAdd,
handleUpdate,
handleBeforeOk,
handleModalCancel,
};
}
onMounted(() => {
getSysUserInfo();
getSysDeptTreeInfo();
getSysRoleInfo();
getSysPostInfo();
});
</script>
<style lang="scss">
@media screen and (max-width: 1720px) {
.form-action {
margin-top: 12px;
}
}
.action {
margin-bottom: 8px;
}
</style>
+410
View File
@@ -0,0 +1,410 @@
<template>
<div class="app-container">
<a-form :model="dataInfo" :rules="rules" ref="modalFormRef" auto-label-width size="mini">
<a-card bordered>
<!-- 卡片插槽 开始 -->
<template #actions>
<a-button type="primary" @click="handleSubmit" long>提交</a-button>
<a-button type="primary" @click="close" long>返回</a-button>
</template>
<!-- 卡片插槽 结束 -->
<a-tabs default-active-key="2">
<a-tab-pane key="1" title="基本信息">
<a-row :gutter="24">
<a-col :span="12">
<a-form-item field="tableName">
<template #label>
数据表名称
<a-tooltip content="数据库表名称,针对gorm对应的table()使用,⚠️这里必须是蛇形结构">
<icon-question-circle-fill />
</a-tooltip>
</template>
<a-input v-model="dataInfo.tableName" placeholder="请输入表名称" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item field="tableComment">
<template #label>
菜单名称
<a-tooltip content="同步的数据库表名称,生成配置数据时,用作菜单名称">
<icon-question-circle-fill />
</a-tooltip>
</template>
<a-input v-model="dataInfo.tableComment" placeholder="请输入菜单名称" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item field="className">
<template #label>
结构体模型名称
<a-tooltip content="结构体模型名称,代码中的struct名称定义使用">
<icon-question-circle-fill />
</a-tooltip>
</template>
<a-input v-model="dataInfo.className" placeholder="请输入" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item field="functionAuthor" label="作者名称">
<a-input v-model="dataInfo.functionAuthor" placeholder="请输入作者名称" />
</a-form-item>
</a-col>
<a-col :span="24">
<a-form-item field="remark" label="备注">
<a-textarea v-model="dataInfo.remark" placeholder="请输入备注" />
</a-form-item>
</a-col>
</a-row>
</a-tab-pane>
<a-tab-pane key="2" title="字段信息">
<a-alert type="warning" style="margin-bottom: 16px;">表字段中的idcreate_byupdate_bycreated_atupdated_atdeleted_at的字段在此列表中已经隐藏.</a-alert>
<a-table :data="tableData" :virtual-list-props="{height:600}" :pagination="false" :scroll="{x: '100%'}">
<template #columns>
<!-- <a-table-column title="序号" data-index="tableId" :width="180" ellipsis tooltip/> -->
<a-table-column title="字段列名" data-index="columnName"/>
<a-table-column title="字段描述" :width="120">
<template #cell="{ record }">
<a-input v-model="record.columnComment" />
</template>
</a-table-column>
<a-table-column title="物理类型" data-index="columnType" align="center" :width="120" />
<a-table-column title="go类型" align="center" :width="140">
<template #cell="{ record }">
<a-select v-model="record.goType" placeholder="请选择 ...">
<a-option value="int64">int64</a-option>
<a-option value="string">string</a-option>
</a-select>
</template>
</a-table-column>
<a-table-column title="go属性" align="center" >
<template #cell="{ record }">
<a-input v-model="record.goField"/>
</template>
</a-table-column>
<a-table-column title="json属性" align="center" >
<template #cell="{ record }">
<a-input v-model="record.jsonField"/>
</template>
</a-table-column>
<a-table-column title="编辑" align="center" :width="60">
<template #cell="{ record }">
<a-switch v-model="record.isInsert" type="large" checked-value="1" unchecked-value="0">
<template #checked-icon>
<icon-check/>
</template>
<template #unchecked-icon>
<icon-close/>
</template>
</a-switch>
</template>
</a-table-column>
<a-table-column title="列表" align="center" :width="60">
<template #cell="{ record }">
<a-switch v-model="record.isList" type="large" checked-value="1" unchecked-value="0">
<template #checked-icon>
<icon-check/>
</template>
<template #unchecked-icon>
<icon-close/>
</template>
</a-switch>
</template>
</a-table-column>
<a-table-column title="查询" align="center" :width="60">
<template #cell="{ record }">
<a-switch v-model="record.isQuery" type="large" checked-value="1" unchecked-value="0">
<template #checked-icon>
<icon-check/>
</template>
<template #unchecked-icon>
<icon-close/>
</template>
</a-switch>
</template>
</a-table-column>
<a-table-column title="查询方式" :width="90">
<template #cell="{ record }">
<a-select v-model="record.queryType" placeholder="请选择 ...">
<a-option label="=" value="EQ" />
<a-option label="!=" value="NE" />
<a-option label=">" value="GT" />
<a-option label=">=" value="GTE" />
<a-option label="<" value="LT" />
<a-option label="<=" value="LTE" />
<a-option label="LIKE" value="LIKE" />
</a-select>
</template>
</a-table-column>
<a-table-column title="必填" :width="70">
<template #cell="{ record }">
<!-- <a-checkbox v-model="record.isRequired"/> -->
<a-switch v-model="record.isRequired" type="large" checked-value="1" unchecked-value="0">
<template #checked-icon>
<icon-check/>
</template>
<template #unchecked-icon>
<icon-close/>
</template>
</a-switch>
</template>
</a-table-column>
<a-table-column title="显示类型" :width="140">
<template #cell="{ record }">
<a-select v-model="record.htmlType" placeholder="请选择 ...">
<a-option value="input">文本框</a-option>
<a-option value="select">下拉框</a-option>
<a-option value="radio">单选框</a-option>
<a-option value="textarea">文本域</a-option>
</a-select>
</template>
</a-table-column>
<a-table-column title="字典类型">
<template #cell="{ record }">
<a-select v-model="record.dictType" allow-clear allow-search placeholder="请选择">
<a-option v-for="dict in dictOptions" :key="dict.dictType" :label="dict.dictName" :value="dict.dictType" >
<a-descriptions :column="1" size="mini">
<a-descriptions-item :label="dict.dictName">
{{ dict.dictType }}
</a-descriptions-item>
</a-descriptions>
</a-option>
</a-select>
</template>
</a-table-column>
<a-table-column title="关系表">
<template #cell="{ record }">
<a-select v-model="record.fkTableName" allow-clear allow-search placeholder="请选择" @change="handleChangeConfig(record)">
<a-option v-for="table in tableTree" :key="table.tableName" :label="table.tableName" :value="table.tableName">
<span style="float: left; margin-right: 5px; font-size: 12px;">{{ table.tableName }}</span>
<span style="float: right; color: #8492a6; font-size: 13px">{{ table.tableComment }}</span>
</a-option>
</a-select>
</template>
</a-table-column>
<a-table-column title="关系表key">
<template #cell="{ record }">
<a-select v-model="record.fkLabelId" allow-clear allow-search placeholder="请选择">
<a-option v-for="column in record.fkCol" :key="column.columnName" :label="column.columnName" :value="column.jsonField">
<span style="float: left; margin-right: 5px; font-size: 12px;">{{ column.jsonField }}</span>
<span style="float: right; color: #8492a6; font-size: 12px;">{{ column.columnComment }}</span>
</a-option>
</a-select>
</template>
</a-table-column>
<a-table-column title="关系表value">
<template #cell="{ record }">
<a-select v-model="record.fkLabelName" allow-clear allow-search placeholder="请选择">
<a-option v-for="column in record.fkCol" :key="column.columnName" :label="column.columnName" :value="column.jsonField">
<span style="float: left; margin-right: 5px; font-size: 12px;">{{ column.jsonField }}</span>
<span style="float: right; color: #8492a6; font-size: 13px">{{ column.columnComment }}</span>
</a-option>
</a-select>
</template>
</a-table-column>
</template>
</a-table>
</a-tab-pane>
<a-tab-pane key="3" title="生成信息">
<a-row :gutter="24">
<a-col :span="12">
<a-form-item field="tplCategory" label="生成模板">
<a-select v-model="dataInfo.tplCategory">
<a-option label="关系表(增删改查)" value="crud" />
</a-select>
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item field="packageName">
<template #label>
应用名
<a-tooltip content="应用名,例如:在app文件夹下将该功能发到那个应用中,默认:admin">
<icon-question-circle-fill />
</a-tooltip>
</template>
<a-input v-model="dataInfo.packageName" placeholder="请输入应用名" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item field="businessName">
<template #label>
业务名
<a-tooltip content="可理解为功能英文名,例如 user">
<icon-question-circle-fill />
</a-tooltip>
</template>
<a-input v-model="dataInfo.businessName" placeholder="请输入业务名" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item field="functionName" label="功能描述">
<template #label>
功能描述
<a-tooltip content="同步的数据库表备注,用作类描述,例如:用户">
<icon-question-circle-fill />
</a-tooltip>
</template>
<a-input v-model="dataInfo.functionName" placeholder="请输入功能描述" />
</a-form-item>
</a-col>
<a-col :span="12">
<a-form-item field="moduleName">
<template #label>
接口路径
<a-tooltip content="接口路径,例如:api/v1/{sys-user}">
<icon-question-circle-fill />
</a-tooltip>
</template>
<a-input v-model="dataInfo.moduleName" placeholder="请输入接口路径" >
<template #prepend>api/{version}/</template>
</a-input>
</a-form-item>
</a-col>
</a-row>
</a-tab-pane>
</a-tabs>
</a-card>
</a-form>
</div>
</template>
<script setup>
import { reactive, ref, getCurrentInstance, onMounted, unref } from 'vue';
import { optionselect as getDictOptionselect } from '@/api/admin/sys-dict'
import { getGenTable, updateGenTable, getTableTree } from '@/api/tools/gen';
const { proxy } = getCurrentInstance();
// Rules
const rules = {
tableName: [
{ required: true, message: '请输入表名称' },
{ match: /^[a-z\._]*$/g, message: '只允许小写字母,例如 sys_demo 格式'}
],
tableComment: [
{ required: true, message: '请输入菜单名称' },
],
className: [
{ required: true, message: '请输入模型名称' },
{ match: /^[A-Z][A-z0-9]*$/g, message: '只允许小写字母,例如 sys_demo 格式'}
],
functionAuthor: [
{ required: true, message: '请输入作者' },
{ match: /^[A-Za-z]+$/, message: '必须以大写字母开头,例如 SysDemo 格式'}
],
tplCategory: [
{ required: true, message: '请选择生成模板' },
],
packageName: [
{ required: true, message: '请输入生成包路径' },
],
moduleName: [
{ required: true, message: '请输入生成模块名' },
{ match: /^[a-z\-]*[a-z]$/g, message: '只允许小写字母,例如 sys-demo 格式'}
],
businessName: [
{ required: true, message: '请输入生成业务名' },
{ match: /^[a-z][A-Za-z]+$/, message: '校验规则: 只允许输入字母 a-z 或大写 A-Z ,并且小写字母开头'}
],
functionName: [
{ required: true, message: '请输入生成功能名' },
],
}
// 字符串转布尔类型
const stringToBool = (value) => {
if (value == "1") {
return true
} else {
return false
}
}
// 表详细信息
const dataInfo = ref({})
// 默认表格内容
const tableData = ref([]);
// 获取 服务数据
const getGenTableQuery = async (params = {}) => {
const res = await getGenTable(params);
tableData.value = res.data.list;
// 编辑 isInsert
// 列表 isList
// 查询 isQuery
// 必填 isRequired
dataInfo.value = res.data.info;
};
// 接收数据库表树
const tableTree = ref([]);
// 获取数据库表树
const getTableTreeQuery = async (params = {}) => {
getTableTree().then(response => {
tableTree.value = response.data
tableTree.value.unshift({ tableId: 0, className: '请选择' })
})
};
// 关系表
const handleChangeConfig = (row) => {
tableTree.value.filter(function(item) {
if (item.tableName === row.fkTableName) {
row.fkCol = item.columns
}
})
}
// 字典类型列表
const dictOptions = ref([]);
// 查询字典下拉列表
const getDictOption = async () => {
const res =await getDictOptionselect();
dictOptions.value = res.data
};
const modalFormRef = ref(null);
// handleSubmit 修改按钮方法 20221101
const handleSubmit = () => {
const genTable = Object.assign({}, dataInfo.value)
genTable.columns = tableData.value
genTable.params = {
treeCode: genTable.treeCode,
treeName: genTable.treeName,
treeParentCode: genTable.treeParentCode
}
proxy.$refs.modalFormRef.validate(async (valid) => {
if (!valid) {
const { code, msg } = await updateGenTable(genTable);
if (code == 200 ) {
proxy.$notification.success('修改成功');
} else {
proxy.$notification.error(msg);
}
// 成功调用关闭方法
close()
} else {
proxy.$message.error('表单校验失败');
}
});
};
/** 关闭按钮 */
const close = () => {
proxy.$router.push({ path: '/dev-tools/gen', query: { t: Date.now() }})
}
onMounted(() => {
const { tableId } = proxy.$route.query
getGenTableQuery(tableId);
getTableTreeQuery()
getDictOption()
});
</script>
<style lang="scss">
</style>
+145
View File
@@ -0,0 +1,145 @@
<template>
<a-modal v-model:visible="visible" title="导入表" @ok="handleImportTable" :ok-loading="loading" @cancel="visible = false" width="50%">
<a-form :model="queryForm" ref="queryFormRef" layout="inline">
<a-form-item field="tableName" label="表名称">
<a-input v-model="queryForm.tableName" placeholder="请输入表名称" />
</a-form-item>
<a-form-item field="tableComment" label="表描述">
<a-input v-model="queryForm.tableComment" placeholder="请输入表描述" />
</a-form-item>
<a-form-item>
<a-space>
<a-button type="primary" @click="handleQuery"><icon-search /> 搜索</a-button>
<a-button @click="handleResetQuery"><icon-loop /> 重置</a-button>
</a-space>
</a-form-item>
</a-form>
<a-divider />
<a-table
:data="tableData"
:pagination="{
'show-total': true,
'show-jumper': true,
'show-page-size': true,
total: pager.count,
current: currentPage,
}"
:row-selection="{ type: 'checkbox', showCheckedAll: true }"
row-key="tableName"
@selection-change="selectionChange"
@page-change="handlePageChange"
@page-size-change="handlePageSizeChange"
>
<template #columns>
<a-table-column title="表名称" data-index="tableName" :width="180" ellipsis tooltip/>
<a-table-column title="表描述" data-index="tableComment" :width="180" ellipsis tooltip/>
<a-table-column title="创建时间" :width="180" ellipsis>
<template #cell="{ record }">
{{ parseTime(record.createTime) }}
</template>
</a-table-column>
<a-table-column title="更新时间" :width="180" ellipsis>
<template #cell="{ record }">
{{ parseTime(record.updateTime) }}
</template>
</a-table-column>
</template>
</a-table>
</a-modal>
</template>
<script setup>
import { reactive, ref, onMounted, getCurrentInstance } from 'vue';
import { listDbTable,importTable,listTable} from '@/api/tools/gen';
const { proxy } = getCurrentInstance();
// 对话框默认值
const visible = ref(false);
// 按钮加载中状态
const loading = ref(false)
// 对话框确认
const handleImportTable = () => {
loading.value = true
importTable({ tables: batchList.join(',') }).then(res => {
if (res.code === 200) {
proxy.$message.info(res.msg);
// 关闭对话框
visible.value = false;
// 完成刷新下页面
location.reload();
} else {
proxy.$message.info(res.msg);
}
loading.value = false
})
};
const queryForm = reactive({});
// 查询列表信息
const handleQuery = async () => {
const params = {
pageIndex: pager.pageIndex,
pageSize: pager.pageSize,
...queryForm,
};
getDbTables(params);
};
// 重置搜索
const handleResetQuery = () => {
proxy.$refs.queryFormRef.resetFields();
getDbTables(queryForm);
}
const tableData = ref([]);
// Batch Del List
let batchList = [];
// Table 勾选数据
const selectionChange = (rowKeys) => {
batchList = rowKeys;
};
/**
* 分页改变
* @param {Number} [page]
*/
const handlePageChange = (page) => {
pager.pageIndex = page;
// 修改当前页码
currentPage.value = page;
getDbTables({ ...pager, ...queryForm });
};
// 每页数据量
const handlePageSizeChange = (pageSize) => {
pager.pageSize = pageSize;
getDbTables({ ...pager, ...queryForm });
};
// 初始页数
const currentPage = ref(1);
// Pager
const pager = {
count: 0,
pageIndex: 1,
pageSize: 10,
};
// 获取列表数据
const getDbTables = async (params = {}) => {
const { data, code, msg } = await listDbTable(params);
if ( code == 200 ) {
tableData.value = data.list;
Object.assign(pager, { count: data.count, pageIndex: data.pageIndex, pageSize: data.pageSize });
} else {
proxy.$notification.error(msg);
}
};
onMounted(() => {
getDbTables(pager);
});
</script>
+270
View File
@@ -0,0 +1,270 @@
<template>
<div class="app-container">
<a-form :model="queryForm" ref="queryFormRef" layout="inline">
<a-form-item field="tableName" label="表名称">
<a-input v-model="queryForm.tableName" placeholder="请输入表名称" @press-enter="handleQuery" />
</a-form-item>
<a-form-item field="tableComment" label="表描述">
<a-input v-model="queryForm.tableComment" placeholder="请输入描述" @press-enter="handleQuery" />
</a-form-item>
<a-form-item>
<a-space>
<a-button type="primary" @click="handleQuery"><icon-search /> 搜索</a-button>
<a-button @click="handleResetQuery"><icon-loop /> 重置</a-button>
</a-space>
</a-form-item>
</a-form>
<a-divider />
<!-- action -->
<div class="action">
<a-space>
<a-button type="primary" @click="openImportTable"><icon-plus /> 导入 </a-button>
<a-button type="primary" status="danger" @click="() => { deleteVisible = true; }"><icon-delete /> 批量删除</a-button>
</a-space>
</div>
<!-- table -->
<a-table
:data="tableData"
:row-selection="{ type: 'checkbox', showCheckedAll: true }"
row-key="tableId"
@selection-change="(selection) => {deleteData = selection;}"
@page-change="handlePageChange"
@page-size-change="handlePageSizeChange"
>
<template #columns>
<a-table-column title="序号" data-index="tableId" :width="180" ellipsis tooltip/>
<a-table-column title="表名称" data-index="tableName" :width="180" ellipsis tooltip/>
<a-table-column title="表描述" data-index="tableComment" :width="180" ellipsis tooltip/>
<a-table-column title="模型名称" data-index="className" :width="180" ellipsis tooltip/>
<a-table-column title="动作" :width="370" align="center" >
<template #cell="{ record }">
<a-button-group>
<a-button size="small" @click="handleEditTable(record)">编辑</a-button>
<a-button size="small" @click="handlePreview(record)">预览</a-button>
<a-popconfirm content="正在使用代码生成请确认?" okText="生成" type="warning" @ok="handleToProject(record)">
<a-button size="small">代码生成 </a-button>
</a-popconfirm>
<a-popconfirm content="正在使用菜单以及API生成到数据库请确认?" okText="写入DB" type="warning" @ok="handleToDB(record)">
<a-button size="small">生成配置 </a-button>
</a-popconfirm>
<a-popconfirm content="正在使用代码生成配置迁移脚本请确认?" okText="迁移" type="warning" @ok="handleToApiFile(record)">
<a-button size="small">生成迁移脚本 </a-button>
</a-popconfirm>
<a-button size="small" @click="() => { deleteVisible = true; deleteData = [record.jobId]; }">删除</a-button>
</a-button-group>
</template>
</a-table-column>
</template>
</a-table>
<!-- 导入对话框 -->
<import-table v-model:visible="modalVisible" />
<!-- 预览代码对话框 -->
<a-modal v-model:visible="codeVisible" title="预览代码" width="80%">
<a-space style="margin-bottom: 16px;">
<a-tag v-for="(value, key) of preview.data" :key="key" bordered color="#165dff" @click="codeChange(key)">{{ key.substring(key.lastIndexOf('/')+1,key.indexOf('.go.template')) }}</a-tag>
</a-space>
<!-- 代码编辑器 -->
<codemirror v-model="codestr" :style="{ height: '600px' }" :autofocus="true" :indent-with-tab="true" :tab-size="2" :extensions="extensions" />
</a-modal>
<!-- Akiraka 20230223 删除与批量删除 开始 -->
<DeleteModal
:data="deleteData"
:visible="deleteVisible"
:apiDelete="delTable"
@deleteVisibleChange="() => deleteVisible = false"
/>
<!-- Akiraka 20230223 删除与批量删除 结束 -->
</div>
</template>
<script setup>
import { reactive, ref, getCurrentInstance, onMounted, nextTick, watch } from 'vue';
import importTable from './importTable.vue'
import { listTable,previewTable,delTable,apiToFile,toProjectTableCheckRole,toDBTable } from '@/api/tools/gen';
import { parseTime } from '@/utils/parseTime';
import { Codemirror } from 'vue-codemirror'
import { javascript } from '@codemirror/lang-javascript'
import { oneDark } from '@codemirror/theme-one-dark'
// Akiraka 20230210 删除数据
const deleteData = ref([])
// Akiraka 20230210 删除对话框
const deleteVisible = ref(false)
// Akiraka 20230210 监听删除事件
watch(() => deleteVisible.value ,(value) => {
if ( value == false ) {
getSysConfigInfo(pager);
}
})
const { proxy } = getCurrentInstance();
const currentPage = ref(1);
// Pager
const pager = {
count: 0,
pageIndex: 1,
pageSize: 10,
};
// form
const queryForm = reactive({});
const modalForm = reactive({
sort: 0,
status: 2,
});
const modalVisible = ref(false);
const openImportTable = () => {
modalVisible.value = true;
};
// Batch Del List
let batchList = [];
// Table Data
const tableData = ref([]);
// 修改
const handleUpdate = async (record) => {
modalVisible.value = true;
modalTitle.value = '修改岗位';
await nextTick();
Object.assign(modalForm, record);
};
// Table 勾选数据
const selectionChange = (rowKeys) => {
batchList = rowKeys;
};
/**
* 分页改变
* @param {Number} [page]
*/
const handlePageChange = (page) => {
pager.pageIndex = page;
// 修改当前页码
currentPage.value = page;
getListTable({ ...pager, ...queryForm });
};
// 每页数据量
const handlePageSizeChange = (pageSize) => {
pager.pageSize = pageSize;
getListTable({ ...pager, ...queryForm });
};
// 获取列表数据
const getListTable = async (params = {}) => {
const { data, code, msg } = await listTable(params);
if ( code == 200 ) {
tableData.value = data.list;
Object.assign(pager, { count: data.count, pageIndex: data.pageIndex, pageSize: data.pageSize });
} else {
proxy.$notification.error(msg);
}
};
// 查询列表信息
const handleQuery = async () => {
const params = {
pageIndex: pager.pageIndex,
pageSize: pager.pageSize,
...queryForm,
};
getListTable(params);
};
// 重置搜索
const handleResetQuery = () => {
proxy.$refs.queryFormRef.resetFields();
getListTable(queryForm);
}
// 按钮块
// 编辑
const handleEditTable = (row) => {
const tableId = row.tableId || this.ids[0]
proxy.$router.push({ path: '/dev-tools/editTable', query: { tableId: tableId }})
}
// 预览代码参数
// 预览代码对话框
const codeVisible = ref(false);
// 预览参数
const preview = ref({
open: false,
title: '代码预览',
data: {},
activeName: 'api.go'
})
/** 预览按钮 */
const handlePreview = (row) => {
codeVisible.value = true;
previewTable(row.tableId).then(response => {
console.log("sadasdasdasd",response)
preview.value.data = response.data
preview.value.open = true
codeChange('template/api.go.template')
})
}
// 代码编辑器参数
const cmOptions = ref({
tabSize: 4,
theme: 'material-palenight',
mode: 'text/javascript',
lineNumbers: true,
line: true
})
const extensions = [javascript(), oneDark];
const codestr = ref('')
// 预览代码 代码更改
const codeChange = (e) => {
if (e.indexOf('js') > -1) {
cmOptions.value.mode = 'text/javascript'
}
if (e.indexOf('model') > -1 || e.indexOf('router') > -1 || e.indexOf('api') > -1 || e.indexOf('service') > -1 || e.indexOf('dto') > -1) {
cmOptions.value.mode = 'text/x-go'
}
if (e.indexOf('vue') > -1) {
cmOptions.value.mode = 'text/x-vue'
}
codestr.value = preview.value.data[e]
}
// 代码生成
const handleToProject = (row) => {
console.log("dasdasd",row)
toProjectTableCheckRole(row.tableId, false).then((response) => {
proxy.$message.success(response.msg);
}).catch(function() {})
}
// 生成配置
const handleToDB = (row) => {
toDBTable(row.tableId).then((response) => {
proxy.$message.success(response.msg);
}).catch(function() {})
}
// 生成迁移脚本
const handleToApiFile = (row) => {
apiToFile(row.tableId, true).then((response) => {
proxy.$message.success(response.msg);
}).catch(function() {})
}
onMounted(() => {
getListTable(pager);
});
</script>
<style lang="scss">
.action {
margin-bottom: 12px;
}
</style>
+11
View File
@@ -0,0 +1,11 @@
<template>
<iframe class="iframe-swagger" src="http://124.222.72.164:8000/swagger/index.html"></iframe>
</template>
<style lang="scss" scoped>
.iframe-swagger {
width: 100%;
height: calc(100vh - 90px);
border: none
}
</style>
+25
View File
@@ -0,0 +1,25 @@
<template>
<div class="app-container">
<a-result
status="403"
subtitle="对不起,您没有访问该资源的权限"
>
<template #extra>
<a-space>
<a-button type="primary">
返回
</a-button>
</a-space>
</template>
</a-result>
</div>
</template>
<style lang="scss" scoped>
.app-container {
display: flex;
flex-direction: column;
justify-content: center;
height: calc(100vh - 122px);
}
</style>
+25
View File
@@ -0,0 +1,25 @@
<template>
<div class="app-container">
<a-result
status="404"
subtitle="抱歉,页面走丢了~"
>
<template #extra>
<a-space>
<a-button type="primary">
返回
</a-button>
</a-space>
</template>
</a-result>
</div>
</template>
<style lang="scss" scoped>
.app-container {
display: flex;
flex-direction: column;
justify-content: center;
height: calc(100vh - 122px);
}
</style>
+25
View File
@@ -0,0 +1,25 @@
<template>
<div class="app-container">
<a-result
status="500"
subtitle="抱歉,服务器出了点问题~"
>
<template #extra>
<a-space>
<a-button type="primary">
返回
</a-button>
</a-space>
</template>
</a-result>
</div>
</template>
<style lang="scss" scoped>
.app-container {
display: flex;
flex-direction: column;
justify-content: center;
height: calc(100vh - 122px);
}
</style>
+19
View File
@@ -0,0 +1,19 @@
<template>
<div class="app-container">
<a-result
subtitle="找不到组件地址,请检查菜单: 菜单管理 > 对应菜单 > 组件管理"
>
<template #extra>
</template>
</a-result>
</div>
</template>
<style lang="scss" scoped>
.app-container {
display: flex;
flex-direction: column;
justify-content: center;
height: calc(100vh - 122px);
}
</style>
+3
View File
@@ -0,0 +1,3 @@
<template>
<router-view></router-view>
</template>
+290
View File
@@ -0,0 +1,290 @@
<template>
<div class="account">
<div class="account-container">
<div class="account-wrap-login">
<div class="login-pic">
<div>
<img
src="/public/login_left_bg.jpg"
/>
</div>
</div>
<div class="login-form" style="padding: 3rem !important">
<div class="login-form-container">
<div class="account-top">
<div class="account-top-logo">
<img :src="store.sysConfig.sys_app_logo" />
<span class="project-title">用户登录</span>
</div>
<div class="account-top-desc">横看成峰侧成岭 远近高低各不同</div>
</div>
<!-- 登录表单 -->
<a-form
:model="loginForm"
:rules="loginRules"
ref="loginFormRef"
layout="vertical"
@keyup.enter="handleLogin"
>
<a-form-item field="userName" hide-asterisk>
<a-input
v-model="loginForm.userName"
placeholder="请输入用户名"
>
<template #prefix>
<icon-user />
</template>
</a-input>
</a-form-item>
<a-form-item field="passWord" hide-asterisk>
<a-input-password
v-model="loginForm.passWord"
placeholder="请输入密码"
>
<template #prefix>
<icon-lock />
</template>
</a-input-password>
</a-form-item>
<div style="display: flex">
<div style="width: 65%">
<a-form-item field="code" hide-asterisk>
<a-input
v-model="loginForm.code"
placeholder="请输入验证码"
>
<template #prefix>
<icon-safe />
</template>
</a-input>
</a-form-item>
</div>
<div style="width: 5%"></div>
<div style="width: 20%">
<a-form-item field="code" hide-asterisk>
<img
:src="captchUrl"
class="captcha"
@click="loadCaptcha()"
/>
</a-form-item>
</div>
</div>
<a-space direction="vertical" size="medium">
<a-checkbox>记住密码</a-checkbox>
<a-button
size="large"
type="primary"
:loading="loading"
long
@click="handleLogin"
>登录</a-button
>
</a-space>
</a-form>
</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, reactive, onMounted, getCurrentInstance } from 'vue';
import { IconUser, IconLock, IconSafe } from '@arco-design/web-vue/es/icon';
import { login, getCaptcha } from '@/api/admin/login';
import { useUserStore } from '@/store/userInfo';
const { proxy } = getCurrentInstance();
const store = useUserStore();
// form
const loginForm = reactive({});
// 验证码
const captchUrl = ref(null);
// 按钮loading
const loading = ref(false);
// rules
const loginRules = {
userName: [{ required: true, message: '请输入用户名' }],
passWord: [{ required: true, message: '请输入密码' }],
code: [{ required: true, message: '请输入验证码' }],
};
// 获取验证码
const loadCaptcha = async () => {
const res = await getCaptcha();
captchUrl.value = res.data;
loginForm.uuid = res.id;
};
// 登陆
const handleLogin = () => {
loading.value = true;
// 如果 valid 为空则校验成功
proxy.$refs.loginFormRef.validate(async (valid) => {
if (!valid) {
try {
const { code, token, msg } = await login(loginForm);
if ( code == 200 ) {
await store.setToken(token);
proxy.$message.success({
content: '登陆成功',
duration: 2000,
});
setTimeout(() => {
proxy.$router.push('/admin/sys-api');
loading.value = false;
}, 500);
} else {
proxy.$message.error(`登陆失败:${msg}`);
}
} catch (err) {
// 登录失败 重新获取验证码
loadCaptcha();
} finally {
loading.value = false;
}
}
});
};
onMounted(async () => {
await loadCaptcha();
});
</script>
<style lang="scss" scoped>
.captcha {
width: 100px;
height: 32px;
cursor: pointer;
}
*,
:before,
:after {
box-sizing: border-box;
border-width: 0;
border-style: solid;
border-color: #e5e7eb;
}
// 输入框重写
.arco-input-wrapper {
border-radius: 20px;
height: 40px;
border: 1px solid #ddd;
background: #fff;
}
// 输入框 重写框颜色
.arco-input-wrapper:hover {
background: #fff;
border: 1px solid #1e6fff;
}
.account {
width: 100%;
margin: 0 auto;
}
.account-container {
width: 100%;
min-height: 100vh;
display: flex;
flex-wrap: wrap;
justify-content: center;
align-items: center;
padding: 15px;
background: #9053c7;
background: linear-gradient(-135deg, #c850c0, #4158d0);
}
.account-wrap-login {
width: 960px;
height: 554px;
// background: #fff;
border-radius: 10px;
overflow: hidden;
display: flex;
flex-wrap: wrap;
// justify-content:space-between;
// padding:30px 95px 33px
}
.account-wrap-login .login-pic {
background-color: #0259e6 !important;
display: flex;
align-items: center;
flex-direction: column;
justify-content: center;
width: 50%;
}
.account-wrap-login .login-pic img {
max-width: 100%;
}
.account-wrap-login .login-form {
width: 50%;
display: flex;
flex-direction: column;
background: #fff;
}
.account-wrap-login .login-form-container {
margin: auto;
width: 100%;
}
.account-wrap-login .login-form-title {
padding-bottom: 15px;
text-align: center;
}
@media (max-width: 991px) {
.account-wrap-login .login-pic {
display: none;
}
.account-wrap-login .login-form {
width: 100%;
margin: auto;
}
}
.account-wrap-login .account-top {
text-align: center;
}
.account-wrap-login .account-top-logo {
text-align: center;
margin-bottom: 10px;
display: flex;
align-items: center;
justify-content: center;
}
.account-wrap-login .account-top-logo img {
width: 45px;
}
.account-wrap-login .account-top-logo .project-title {
background: linear-gradient(
92.06deg,
#33c2ff -17.9%,
#1e6fff 43.39%,
#1e6fff 99.4%
);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
font-size: 24px;
line-height: 1.25;
font-weight: 500;
margin-left: 10px;
}
.account-wrap-login .account-top-desc {
font-size: 14px;
color: #808695;
margin-bottom: 20px;
}
@media (max-width: 640px) {
.account-wrap-login {
width: 100%;
padding: 30px;
height: auto;
}
}
// 20230105 自动填充背景色问题
:deep(.arco-input-wrapper .arco-input.arco-input-size-medium) {
box-shadow: 0 0 0px 1000px #fff inset;
}
</style>
+1
View File
@@ -0,0 +1 @@
<svg width="24" height="24" viewBox="0 0 48 48" fill="currentColor"><path fill-rule="evenodd" clip-rule="evenodd" d="M4 15a1 1 0 001 1h2a1 1 0 001-1V8h7a1 1 0 001-1V5a1 1 0 00-1-1H6a2 2 0 00-2 2v9zm4 18a1 1 0 00-1-1H5a1 1 0 00-1 1v9a2 2 0 002 2h9a1 1 0 001-1v-2a1 1 0 00-1-1H8v-7zm35-17a1 1 0 001-1V6a2 2 0 00-2-2h-9a1 1 0 00-1 1v2a1 1 0 001 1h7v7a1 1 0 001 1h2zm1 17a1 1 0 00-1-1h-2a1 1 0 00-1 1v7h-7a1 1 0 00-1 1v2a1 1 0 001 1h9a2 2 0 002-2v-9zM32.835 11h-6.108c-6.512 0-11.882 4.804-12.636 11h-1.992c-.382 0-.52.046-.66.134a.855.855 0 00-.325.378c-.074.162-.114.324-.114.77v1.436c0 .446.04.608.114.77.075.163.185.291.324.378.14.088.279.134.66.134h2.157c1.179 5.706 6.315 10 12.472 10h6.108c.405 0 .552-.041.7-.12a.819.819 0 00.344-.337c.079-.145.121-.29.121-.688V31h.901c.382 0 .52-.046.66-.134a.855.855 0 00.325-.378c.074-.162.114-.324.114-.77v-1.436c0-.446-.04-.608-.114-.77a.855.855 0 00-.325-.378c-.14-.088-.278-.134-.66-.134H34v-7h.901c.382 0 .52-.046.66-.134a.855.855 0 00.325-.378c.074-.162.114-.324.114-.77v-1.436c0-.446-.04-.608-.114-.77a.855.855 0 00-.325-.378c-.14-.088-.278-.134-.66-.134H34v-3.855c0-.398-.042-.543-.121-.688a.819.819 0 00-.344-.338c-.148-.078-.295-.119-.7-.119zm-2.744 3.571h-3.637c-5.02 0-9.09 3.998-9.09 8.929s4.07 8.929 9.09 8.929h3.637V14.57z" fill="currentColor"/></svg>

After

Width:  |  Height:  |  Size: 1.3 KiB

+234
View File
@@ -0,0 +1,234 @@
<template>
<a-row :gutter="8" class="dict-wrapper">
<a-col :span="8">
<a-card hoverable>
<div style="text-align: center;">
<a-space direction="vertical" fill>
<a-avatar :size="120">
<template v-if="userInfoForm.user.avatar == '' ">
<img alt="avatar" :src="store.sysConfig.sys_user_avatar" />
</template>
<template v-else>
<img alt="avatar" :src="userInfoForm.user.avatar"/>
</template>
</a-avatar>
<a-typography-title :heading="6">{{userInfoForm.user.nickName}}</a-typography-title>
</a-space>
</div>
<a-divider />
<a-card :bordered="false">
<a-descriptions title="用户详情" :column="1">
<a-descriptions-item label="用户昵称">{{userInfoForm.user.nickName}}</a-descriptions-item>
<a-descriptions-item label="登录账号">{{userInfoForm.user.username}}</a-descriptions-item>
<a-descriptions-item label="用户手机">{{userInfoForm.user.phone}}</a-descriptions-item>
<a-descriptions-item label="用户邮箱">{{userInfoForm.user.email}}</a-descriptions-item>
<a-descriptions-item label="用户性别">
<template v-if="userInfoForm.user.sex == '0' "><a-tag color="arcoblue"></a-tag></template>
<template v-else-if="userInfoForm.user.sex == '1' "><a-tag color="pinkpurple"></a-tag></template>
<template v-else><a-tag color="gold">中性</a-tag></template>
</a-descriptions-item>
<a-descriptions-item label="所属部门" v-for="item of userInfoForm.posts" >
<a-tag>{{ item.postName }}</a-tag>
</a-descriptions-item>
<a-descriptions-item label="用户角色" v-for="item of userInfoForm.roles" >
<a-tag>{{ item.roleName }}</a-tag>
</a-descriptions-item>
</a-descriptions>
</a-card>
</a-card>
</a-col>
<a-col :span="16">
<a-card hoverable>
<a-tabs>
<a-tab-pane key="1">
<template #title>
<icon-exclamation-circle /> 基础信息
</template>
<a-card hoverable :bordered="false">
<a-form :model="modalForm" :rules="rules" ref="modalFormRef" label-align="left" auto-label-width>
<a-form-item field="nickName" label="用户昵称">
<a-input v-model="modalForm.nickName" placeholder="请输入用户昵称" />
</a-form-item>
<a-form-item field="phone" label="手机号码">
<a-input v-model="modalForm.phone" placeholder="请输入手机号码" />
</a-form-item>
<a-form-item field="email" label="用户邮箱">
<a-input v-model="modalForm.email" placeholder="请输入邮箱" />
</a-form-item>
<a-form-item field="sex" label="用户性别">
<a-radio-group v-model="modalForm.sex">
<a-radio value="0"></a-radio>
<a-radio value="1"></a-radio>
</a-radio-group>
</a-form-item>
<a-form-item>
<a-space>
<a-button @click="handleSubmit" type="primary">保存</a-button>
</a-space>
</a-form-item>
</a-form>
</a-card>
</a-tab-pane>
<a-tab-pane key="2">
<template #title>
<icon-user /> 修改密码
</template>
<a-card hoverable :bordered="false">
<a-form :model="userPwdForm" :rules="rules" ref="userPwdFormRef" label-align="left" auto-label-width>
<a-form-item field="oldPassword" label="旧密码">
<a-input v-model="userPwdForm.oldPassword" placeholder="请输入旧密码" />
</a-form-item>
<a-form-item field="newPassword" label="新密码">
<a-input v-model="userPwdForm.newPassword" placeholder="请输入新密码" />
</a-form-item>
<a-form-item field="confirmPassword" label="确认密码">
<a-input v-model="userPwdForm.confirmPassword" placeholder="请输入确认密码" />
</a-form-item>
<a-form-item>
<a-space>
<a-button @click="handleSubmitUserPwd" type="primary">保存</a-button>
</a-space>
</a-form-item>
</a-form>
</a-card>
</a-tab-pane>
</a-tabs>
</a-card>
</a-col>
</a-row>
</template>
<script setup>
import { onMounted, reactive, ref, getCurrentInstance } from 'vue';
import { useUserStore } from '@/store/userInfo';
import { updateUser } from '@/api/admin/sys-user';
import { getUserProfile,putUserPwd } from '@/api/profile/profile';
// use Store
const store = useUserStore();
// 获取当前实例
const { proxy } = getCurrentInstance();
const userInfoForm = ref({
user: {},
posts: [],
roles: []
});
const userPwdForm = reactive({});
// 默认表单
const modalForm = reactive({});
// Rules
const rules = {
nickName: [{ required: true, message: '请输入用户昵称' }],
phone: [
{ required: true, message: '请输入联系手机' },
// { match: /^1[3456789]\d{9}$/, message: '校验规则: 13011112222'}
],
email: [
{ required: true, message: '请输入邮箱' },
// { match: /^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+(\.[a-zA-Z0-9_-])+/, message: '校验规则: aka@aka.com'}
],
sex: [
{ required: true, message: '请选择性别' },
// { match: /^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+(\.[a-zA-Z0-9_-])+/, message: '校验规则: aka@aka.com'}
],
oldPassword: [
{ required: true, message: '输入旧密码' },
// { match: /^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+(\.[a-zA-Z0-9_-])+/, message: '校验规则: aka@aka.com'}
],
newPassword: [
{ required: true, message: '输入新密码' },
// { match: /^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+(\.[a-zA-Z0-9_-])+/, message: '校验规则: aka@aka.com'}
],
confirmPassword: [
{ required: true, message: '确认新密码', validator: (value, cb) => {
return new Promise(resolve => {
window.setTimeout(() => {
if (userPwdForm.newPassword !== value) {
cb('两次输入的密码不一致')
}
resolve()
}, 1)
})
} }
// { match: /^([a-zA-Z0-9_-])+@([a-zA-Z0-9_-])+(\.[a-zA-Z0-9_-])+/, message: '校验规则: aka@aka.com'}
],
};
const equalToPassword = (value, callback) => {
if (modalForm.newPassword !== value) {
cb('两次输入的密码不一致')
console.log("aaa")
} else {
cb('密码输入正确')
console.log("bbb")
}
}
// handleSubmit 新增与修改按钮方法 20220713
const handleSubmit = () => {
proxy.$refs.modalFormRef.validate(async (valid) => {
if (!valid) {
const { success } = await updateUser(modalForm);
if (success) proxy.$message.success('信息修改成功');
getCurrentUserInfo();
} else {
proxy.$message.error('表单校验失败');
}
});
};
// 修改用户密码 20221025
const handleSubmitUserPwd = () => {
proxy.$refs.userPwdFormRef.validate(async (valid) => {
if (!valid) {
const { res } = await putUserPwd(userPwdForm);
console.loh("success",res)
if (success) proxy.$message.success('密码修改成功');
getCurrentUserInfo();
} else {
proxy.$message.error('表单校验失败');
}
});
};
const getCurrentUserInfo = async () => {
const res = await getUserProfile()
userInfoForm.value = res.data
Object.assign(modalForm, res.data.user);
};
onMounted(() => {
getCurrentUserInfo();
});
</script>
<style lang="scss">
.userinfo-row {
font-size: 14px;
line-height: 1.5;
.userinfo-item {
&-label {
width: 140px;
color: #4e5969;
text-align: right;
}
&-value {
width: 140px;
color: $primary-font-color;
}
div {
display: inline-block;
}
}
}
.userinfo-form {
width: 520px;
margin: 0 auto;
}
</style>
+277
View File
@@ -0,0 +1,277 @@
<template>
<div class="app-container">
<a-form :model="queryForm" ref="queryFormRef" layout="inline">
<a-form-item field="jobName" label="任务名称">
<a-input v-model="queryForm.jobName" placeholder="请输入任务名称" @press-enter="handleQuery" />
</a-form-item>
<a-form-item field="jobGroup" label="任务分组">
<a-select v-model="queryForm.jobGroup" placeholder="请选择任务分组">
<a-option value="DEFAULT">默认</a-option>
<a-option value="SYSTEM">系统</a-option>
</a-select>
</a-form-item>
<a-form-item field="status" label="状态">
<a-select v-model="queryForm.status" placeholder="请选择任务状态">
<a-option :value="2">正常</a-option>
<a-option :value="1">关闭</a-option>
</a-select>
</a-form-item>
<a-form-item>
<a-space>
<a-button type="primary" @click="handleQuery">查询</a-button>
<a-button @click="handleResetQuery">重置</a-button>
</a-space>
</a-form-item>
</a-form>
<a-divider />
<div class="table-action">
<a-button type="primary" @click="handleAdd">新增定时任务</a-button>
</div>
<a-table :data="tableData" :columns="columns" :pagination="{ 'show-total': true, 'show-jumper': true, 'show-page-size': true, total: pager.count, current: currentPage }">
<template #status="{ record }">
<a-tag v-if="record.status == 2" color="green">正常</a-tag>
<a-tag v-if="record.status == 1" color="red">停用</a-tag>
</template>
<template #action="{ record }">
<a-button type="text" @click="handleUpdate(record)">修改</a-button>
<a-button type="text" status="success" v-if="record.entry_id == 0" @click="handleStart(record.jobId)">启动</a-button>
<a-button type="text" status="danger" v-if="record.entry_id !== 0" @click="handleStop(record.jobId)">停止</a-button>
<a-button type="text" status="danger" @click="() => { deleteVisible = true; deleteData = [record.jobId]; }">删除</a-button>
</template>
</a-table>
<a-modal
v-model:visible="modalVisible"
title-align="start"
:title="modalTitle"
:on-before-ok="onBeforeOk"
@ok="handleOk"
@cancel="$refs.modalFormRef.resetFields()"
>
<a-form
:model="modalForm"
:rules="rules"
ref="modalFormRef"
auto-label-width
>
<a-form-item field="jobName" label="任务名称">
<a-input
v-model="modalForm.jobName"
placeholder="请输入任务名称"
></a-input>
</a-form-item>
<a-form-item field="jobGroup" label="任务分组">
<a-select v-model="modalForm.jobGroup" placeholder="请选择任务分组">
<a-option value="DEFAULT">默认</a-option>
<a-option value="SYSTEM">系统</a-option>
</a-select>
</a-form-item>
<a-form-item field="invokeTarget" label="调用目标">
<a-input
v-model="modalForm.invokeTarget"
placeholder="调用目标"
></a-input>
</a-form-item>
<a-form-item field="args" label="目标参数">
<a-input v-model="modalForm.args" placeholder="目标参iuu数"></a-input>
</a-form-item>
<a-form-item field="cronExpression" label="Cron表达式">
<a-input
v-model="modalForm.cronExpression"
placeholder="Cron表达式"
></a-input>
</a-form-item>
<a-form-item field="concurrent" label="是否并发">
<a-radio-group v-model="modalForm.concurrent" type="button">
<a-radio :value="0">允许</a-radio>
<a-radio :value="1">禁止</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item field="jobType" label="调用类型">
<a-radio-group v-model="modalForm.jobType" type="button">
<a-radio :value="1">接口</a-radio>
<a-radio :value="2">函数</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item field="misfirePolicy" label="执行策略">
<a-radio-group v-model="modalForm.misfirePolicy" type="button">
<a-radio :value="1">立即执行</a-radio>
<a-radio :value="2">执行一次</a-radio>
<a-radio :value="3">放弃执行</a-radio>
</a-radio-group>
</a-form-item>
<a-form-item field="status" label="状态">
<a-select v-model="modalForm.status">
<a-option :value="2">正常</a-option>
<a-option :value="1">停用</a-option>
</a-select>
</a-form-item>
</a-form>
</a-modal>
<!-- Akiraka 20230223 删除与批量删除 开始 -->
<DeleteModal
:data="deleteData"
:visible="deleteVisible"
:apiDelete="delSysJob"
@deleteVisibleChange="() => deleteVisible = false"
/>
<!-- Akiraka 20230223 删除与批量删除 结束 -->
</div>
</template>
<script setup>
import { reactive, ref, onMounted, getCurrentInstance, watch } from 'vue';
import { listSysJob, addSysJob, updateSysJob, delSysJob, startJob, removeJob } from '@/api/sys-job';
// Akiraka 20230210 删除数据
const deleteData = ref([])
// Akiraka 20230210 删除对话框
const deleteVisible = ref(false)
// Akiraka 20230210 监听删除事件
watch(() => deleteVisible.value ,(value) => {
if ( value == false ) {
getSysJobListInfo(queryForm);
}
})
const { proxy } = getCurrentInstance();
const currentPage = ref(1);
// Pager
const pager = {
count: 0,
pageIndex: 1,
pageSize: 10,
};
const queryForm = reactive({});
const modalForm = reactive({
concurrent: 1,
jobType: 1,
misfirePolicy: 1,
status: 2,
});
// 表单检验规则
const rules = {
jobName: [{ required: true, message: '请输入任务名称' }],
jobGroup: [{ required: true, message: '请选择任务分组' }],
invokeTarget: [{ required: true, message: '请输入调用目标' }],
cronExpression: [{ required: true, message: '请输入Cron表达式' }],
status: [{ required: true, message: '请选择状态' }],
};
const modalVisible = ref(false);
const modalTitle = ref('默认标题');
const tableData = ref([]);
const columns = [
{ title: '编号', dataIndex: 'jobId' },
{ title: '任务名称', dataIndex: 'jobName' },
{ title: '任务分组', dataIndex: 'jobGroup' },
{ title: '任务表达式', dataIndex: 'cronExpression' },
{ title: '调用目标', dataIndex: 'invokeTarget' },
{ title: '状态', dataIndex: 'status', slotName: 'status' },
{ title: '操作', slotName: 'action' },
];
// 查询任务
const handleQuery = () => {
getSysJobListInfo(queryForm);
}
// 重置查询
const handleResetQuery = () => {
proxy.$refs.queryFormRef.resetFields();
getSysJobListInfo();
}
// 新增任务
const handleAdd = () => {
modalVisible.value = true;
modalTitle.value = '新增任务';
};
// 修改任务
const handleUpdate = (record) => {
modalVisible.value = true;
modalTitle.value = '修改任务';
Object.assign(modalForm, record);
};
// 启动定时任务
const handleStart = async (jobId) => {
await startJob(jobId);
proxy.$notification.success('启动任务成功!')
getSysJobListInfo();
}
// 关闭定时任务
const handleStop = async (jobId) => {
await removeJob(jobId);
proxy.$notification.success('已停止任务!')
getSysJobListInfo();
}
// Modal 触发oK事件前
const onBeforeOk = (done) => {
proxy.$refs.modalFormRef.validate((err) => {
if (!err) {
return done();
} else {
proxy.$message.error('表单校验失败');
return done(false);
}
});
};
// Modal 触发ok事件
const handleOk = async () => {
if (modalForm.jobId) {
const { code, msg } = await updateSysJob(modalForm);
if (code == 200 ) {
proxy.$notification.success('修改成功');
} else {
proxy.$notification.error(msg);
}
} else {
const { code, msg } = await addSysJob(modalForm);
if (code == 200 ) {
proxy.$notification.success('新增成功');
} else {
proxy.$notification.error(msg);
}
}
getSysJobListInfo();
};
// 获取系统任务信息
const getSysJobListInfo = async (params = {}) => {
const { data, code, msg } = await listSysJob(params);
if ( code == 200 ) {
tableData.value = data.list;
Object.assign(pager, { count: data.count, pageIndex: data.pageIndex, pageSize: data.pageSize });
} else {
proxy.$notification.error(msg);
}
};
onMounted(() => {
getSysJobListInfo();
});
</script>
<style lang="scss" scoped>
.table-action {
margin-bottom: 12px;
}
</style>
+160
View File
@@ -0,0 +1,160 @@
<template>
<div class="card-wrapper">
<a-row :gutter="12">
<a-col :span="12">
<a-card title="服务器信息">
<div class="card-item card-item-server">
<span class="card-item-title">主机名称</span>
<span class="card-item-desc">{{ SystemInfo?.os?.hostName}}</span>
</div>
<div class="card-item card-item-server">
<span class="card-item-title">操作系统</span>
<span class="card-item-desc">{{ SystemInfo?.os?.goOs }}</span>
</div>
<div class="card-item card-item-server">
<span class="card-item-title">服务器IP</span>
<span class="card-item-desc">{{ SystemInfo?.os?.ip }}</span>
</div>
<div class="card-item card-item-server">
<span class="card-item-title">系统架构</span>
<span class="card-item-desc">{{ SystemInfo?.os?.arch }}</span>
</div>
<div class="card-item card-item-server">
<span class="card-item-title">CPU</span>
<span class="card-item-desc">{{ SystemInfo?.cpu?.cpuInfo[0]?.modelName }}</span>
</div>
<div class="card-item card-item-server">
<span class="card-item-title">当前时间</span>
<span class="card-item-desc">{{ SystemInfo?.os?.time }}</span>
</div>
</a-card>
</a-col>
<a-col :span="12">
<a-card :title="SystemInfo.location">
<a-row :gutter="12">
<a-col :span="12">
<div class="card-item">
<span class="card-item-title">系统</span>
<span class="card-item-desc">Linux</span>
</div>
</a-col>
<a-col :span="12">
<div class="card-item">
<span class="card-item-title">时间</span>
<span class="card-item-desc">2022-05-06 15:32:22</span>
</div>
</a-col>
</a-row>
<a-row :gutter="12">
<a-col :span="12">
<div class="card-item">
<span class="card-item-title">内存</span>
<span class="card-item-desc">129MB/2349MB</span>
</div>
</a-col>
<a-col :span="12">
<div class="card-item">
<span class="card-item-title">在线时间</span>
<span class="card-item-desc">{{ SystemInfo.bootTime }}分钟</span>
</div>
</a-col>
</a-row>
<a-row :gutter="12">
<a-col :span="12">
<div class="card-item">
<span class="card-item-title">交换</span>
<span class="card-item-desc">0/0</span>
</div>
</a-col>
<a-col :span="12">
<div class="card-item">
<span class="card-item-title">硬盘</span>
<span class="card-item-desc">{{ SystemInfo?.disk?.used }}GB/{{ SystemInfo?.disk?.total }}GB</span>
</div>
</a-col>
</a-row>
<a-row :gutter="12">
<a-col :span="12">
<div class="card-item">
<span class="card-item-title">下载</span>
<span class="card-item-desc">{{ SystemInfo?.net?.in }}KB</span>
</div>
</a-col>
<a-col :span="12">
<div class="card-item">
<span class="card-item-title">上传</span>
<span class="card-item-desc">{{ SystemInfo?.net?.out }}KB</span>
</div>
</a-col>
</a-row>
<!-- progress -->
<div class="progress-wrapper">
<span class="progress-title">CPU</span>
<a-progress status="success" :percent="SystemInfo?.cpu?.percent / 100" />
</div>
<div class="progress-wrapper">
<span class="progress-title">RAM</span>
<a-progress status="warning" :percent="SystemInfo?.mem?.percent / 100" />
</div>
<div class="progress-wrapper">
<span class="progress-title">硬盘</span>
<a-progress :percent="SystemInfo?.disk?.percent / 100" />
</div>
</a-card>
</a-col>
</a-row>
</div>
</template>
<script setup>
import { onMounted, reactive } from 'vue';
import { getServerMonitor } from '@/api/sys-tools/monitor';
const SystemInfo = reactive({});
const getServerMonitorInfo = async () => {
const res = await getServerMonitor();
Object.assign(SystemInfo, res)
}
onMounted(() => {
getServerMonitorInfo();
});
</script>
<style lang="scss" scoped>
.card-wrapper {
width: 100%;
}
.card-item {
display: flex;
justify-content: space-between;
padding: 15px 0;
border-bottom: 1px solid rgb(229, 230, 235);
&-title {
color: $primary-font-color;
}
&-desc {
color: $secondary-font-color;
}
}
.card-item-server:last-child {
border-bottom: none;
}
.progress-wrapper {
margin-top: 15px;
display: flex;
.progress-title {
font-weight: bold;
margin-right: 10px;
width: 50px;
}
}
</style>