Commit b623cfee by 周海峰

Merge branch 'master' of https://code.palacesun.com/wuchao/nse-ui

parents 13ec4642 5889b974
......@@ -6,3 +6,4 @@ VITE_APP_ENV = 'development'
# 若依管理系统/开发环境
VITE_APP_BASE_API = '/dev-api'
VITE_APP_BASE_URL = '/'
\ No newline at end of file
......@@ -8,4 +8,6 @@ VITE_APP_ENV = 'production'
VITE_APP_BASE_API = '/prod-api'
# 是否在打包时开启压缩,支持 gzip 和 brotli
VITE_BUILD_COMPRESS = gzip
\ No newline at end of file
VITE_BUILD_COMPRESS = gzip
VITE_APP_BASE_URL = '/'
\ No newline at end of file
......@@ -20,18 +20,23 @@
"@vueup/vue-quill": "1.2.0",
"@vueuse/core": "13.3.0",
"axios": "1.9.0",
"buffer": "^6.0.3",
"clipboard": "2.0.11",
"crypto-js": "^4.2.0",
"echarts": "5.6.0",
"element-plus": "2.9.9",
"file-saver": "2.0.5",
"fuse.js": "6.6.2",
"gm-crypt": "^0.0.2",
"js-beautify": "1.14.11",
"js-cookie": "3.0.5",
"jsencrypt": "3.3.2",
"nprogress": "0.2.0",
"pinia": "3.0.2",
"sm-crypto": "^0.3.13",
"sortablejs": "^1.15.6",
"splitpanes": "4.0.4",
"view-ui-plus": "^1.3.20",
"vue": "3.5.16",
"vue-cropper": "1.1.1",
"vue-router": "4.5.1",
......
import request from '@/utils/request'
// 登录方法
export function login(username, password, code, uuid) {
export function login(username, password) {
const data = {
username,
password,
code,
uuid
password
}
return request({
url: '/login',
url: '/doLogin',
headers: {
isToken: false,
repeatSubmit: false
......
<script setup lang="ts" name="ExpressionEditor">
import { ref, onMounted, toRefs } from "vue";
import ModalPop from "./ModalPop.vue"
const props = defineProps<{
modelValue: boolean;
data?: Object;
}>();
const emit = defineEmits(["update:modelValue", "confirm", "cancel"]);
const editorValue = ref("");
const infoText = ref('')
const listData = ref([
{
name: 'convert_base1()'
},
{
name: 'convert_base2()'
}
])
// 确认
const confirm = () => {
emit("confirm", editorValue.value);
};
// 测试
const test = () => {
console.log("test");
};
const cancel = () => {
emit("cancel");
}
// 划过事件
const hoverFunc = (item) => {
infoText.value = item;
}
const change = (val) => {
if(!val) return;
console.log(props.data)
editorValue.value = props.data.name;
}
</script>
<template>
<ModalPop v-model="props.modelValue" title="编辑器" @cancel="cancel" @change="change">
<template #content>
<div class="title">算法名称 :</div>
<div class="content">
<div class="content_left">
<div class="content_left_tap">函数</div>
<div class="content_left_list">
<el-scrollbar height="362px">
<div class="function__item" @mouseover="hoverFunc(item)" v-for="(item,index) in listData" :key="index">{{ item.name }}</div>
</el-scrollbar>
</div>
</div>
<div class="content_right">
<div class="content_right_info">
<div class="content_right_info_title">说明</div>
<div class="content_right_info_content">定义:{{ infoText }}</div>
</div>
<div class="content_right_editor">
<el-input
type="textarea"
placeholder="请输入内容"
v-model="editorValue"
/>
</div>
</div>
</div>
</template>
<template #footer>
<el-button type="primary" style="width: 150px;" @click="test">测试</el-button>
<el-button type="info" style="width: 150px;" @click="cancel">取消</el-button>
<el-button type="primary" style="width: 150px;" @click="confirm">确认</el-button>
</template>
</ModalPop>
</template>
<style lang="scss" scoped>
.title{
height: 32px; line-height: 32px;
}
.content{
display: flex;
height: 400px;
width: 100%;
&_left{
height: 100%;
width: 322px;
border-width: 1px;
border-style: solid;
border-color: rgb(220, 229, 235);
border-image: initial;
&_tap{
height: 36px;
line-height: 36px;
font-size: 14px;
font-weight: 700;
border-bottom: 1px solid rgb(220, 229, 235);
padding-left: 20px;
color: rgb(44, 158, 247);
}
&_list{
height: 100%;
.function__item{
padding-left: 20px;
cursor: pointer;
color: #7A8596;
}
.function__item:hover{
background: rgb(243, 245, 250);
}
}
}
&_right{
margin-left: 10px;
height: 100%;
flex: 1;
&_info{
height: 146px;
border-style: solid;
border-color: rgb(220, 229, 235);
border-image: initial;
border-width: 1px 1px 0px;
padding: 0px 20px;
&_title{
height: 42px;
line-height: 42px;
color: rgb(122, 132, 149);
font-size: 14px;
font-weight: 700;
}
&_content{
margin-top: -4px;
color: rgb(122, 132, 149);
font-size: 12px;
word-break: break-all;
}
}
&_editor{
width: 100%;
:deep(.el-textarea__inner){
box-sizing: border-box !important;
height: 254px !important;
min-height: 254px !important;
max-height: 254px !important;
border-radius: 0px !important;
padding: 12px 20px !important;
background: rgb(243, 245, 250) !important;
}
}
}
}
</style>
\ No newline at end of file
<script setup lang="ts" name="ModalPop">
import { ref, toRefs } from "vue";
import { Modal } from "view-ui-plus";
const props = defineProps<{
modelValue: boolean;
title: string;
}>();
const emit = defineEmits(["update:modelValue", "cancel", "change"]);
// 关闭
const cancel = () => {
emit("cancel");
};
const change = (visible: boolean) => {
emit("change", visible);
};
</script>
<template>
<Modal class="ModalPop" v-model="props.modelValue" width="1000" draggable scrollable sticky @on-cancel="cancel" @on-visible-change="change">
<template #header>
<span style="font-size: 16px; font-weight: bold;color: #7A8495;">{{ props.title }}</span>
</template>
<div class="slot-warpper">
<slot name="content"></slot>
</div>
<template #footer>
<div style="display: flex; justify-content: center; padding-top: 20px;">
<slot name="footer"></slot>
</div>
</template>
</Modal>
</template>
<style lang="scss">
.ModalPop {
.ivu-modal-header,
.ivu-modal-footer {
border: none;
}
.ivu-modal-content {
background: #f3f5fa;
}
.ivu-modal-body {
background: rgb(243, 245, 250);
border-radius: 4px;
padding: 0px 8px 8px !important;
}
.slot-warpper {
border-width: 1px;
border-style: solid;
border-color: rgb(220, 229, 235);
border-image: initial;
background: rgb(255, 255, 255);
padding: 30px;
border-radius: 4px;
max-height: 500px;
}
}
</style>
\ No newline at end of file
......@@ -37,7 +37,7 @@ const { proxy } = getCurrentInstance()
const quillEditorRef = ref()
const uploadUrl = ref(import.meta.env.VITE_APP_BASE_API + "/common/upload") // 上传的图片服务器地址
const headers = ref({
Authorization: "Bearer " + getToken()
Token: getToken()
})
const props = defineProps({
......@@ -189,7 +189,7 @@ function handlePasteCapture(e) {
function insertImage(file) {
const formData = new FormData()
formData.append("file", file)
axios.post(uploadUrl.value, formData, { headers: { "Content-Type": "multipart/form-data", Authorization: headers.value.Authorization } }).then(res => {
axios.post(uploadUrl.value, formData, { headers: { "Content-Type": "multipart/form-data", Token: headers.value.Token } }).then(res => {
handleUploadSuccess(res.data)
})
}
......
......@@ -93,7 +93,7 @@ const number = ref(0)
const uploadList = ref([])
const baseUrl = import.meta.env.VITE_APP_BASE_API
const uploadFileUrl = ref(import.meta.env.VITE_APP_BASE_API + props.action) // 上传文件服务器地址
const headers = ref({ Authorization: "Bearer " + getToken() })
const headers = ref({ Token: getToken() })
const fileList = ref([])
const showTip = computed(
() => props.isShowTip && (props.fileType || props.fileSize)
......
......@@ -103,7 +103,7 @@ const dialogImageUrl = ref("")
const dialogVisible = ref(false)
const baseUrl = import.meta.env.VITE_APP_BASE_API
const uploadImgUrl = ref(import.meta.env.VITE_APP_BASE_API + props.action) // 上传的图片服务器地址
const headers = ref({ Authorization: "Bearer " + getToken() })
const headers = ref({ Token: getToken() })
const fileList = ref([])
const showTip = computed(
() => props.isShowTip && (props.fileType || props.fileSize)
......
<script setup lang="ts" name="collapseView">
import { ref, computed } from 'vue'
import { Collapse, Panel } from 'view-ui-plus';
const emit = defineEmits(["add", "view", "mainDeletion", "default", "childDelete", "change"]);
const props = defineProps<{
list: Array<any>;
}>();
const listData = computed(() => props.list)
// 新增
const addClick = (item) => {
emit("add",item);
}
// 查看
const viewClick = (item) => {
emit("view",item);
}
// 主删除
const mainDeletion = (item) => {
emit("mainDeletion",item);
}
// 子级监听
const itemClick = (item) => {
emit("change",item);
}
// 默认项
const defaultClick = (item) => {
emit("default",item);
}
// 子删除
const childDelete = (item) => {
emit("childDelete",item);
}
</script>
<template>
<Collapse simple>
<Panel :name="item.name" v-for="(item, index) in listData" :key="index">
{{ item.name }}
<span class="collapse-item__btns--box">
<el-icon color="rgb(253, 84, 81)" :size="16" v-if="item.isAdd" @click.stop="addClick(item)">
<circle-plus-filled />
</el-icon>
<el-icon color="#2c9ef7" :size="16" v-if="item.isView" @click.stop="viewClick(item)" style="margin-left: 8px;">
<View />
</el-icon>
<el-icon color="rgb(13, 215, 141)" :size="16" v-if="item.isDelete" @click.stop="mainDeletion(item)" style="margin-left: 8px;">
<delete />
</el-icon>
</span>
<template #content>
<div>
<div class="rule__item" v-for="(itemTwo, i) in item.list" :key="i" @click="itemClick(itemTwo)">
<span>{{ itemTwo.name }}</span>
<div class="default"></div>
<div class="rule__item__btns">
<el-icon color="#2c9ef7" :size="15" v-if="itemTwo.isCheck" @click.stop="defaultClick(itemTwo)" style="margin-right: 8px;" >
<circle-check />
</el-icon>
<el-icon color="rgb(13, 215, 141)" :size="15" v-if="itemTwo.isDelete" @click.stop="childDelete(itemTwo)">
<delete />
</el-icon>
</div>
</div>
</div>
</template>
</Panel>
</Collapse>
</template>
<style lang="scss" scoped>
:deep(.ivu-collapse-content) {
padding: 0 0 0 14px;
}
:deep(.ivu-collapse-content-box) {
padding-bottom: 0;
}
.ivu-collapse-header {
padding-left: 0;
}
.ivu-collapse-simple {
border: none !important;
.ivu-collapse-item {
position: relative;
border: none !important;
}
.ivu-collapse-item:before {
content: " ";
width: calc(100% - 14px);
height: 1px;
background: #f3f5fa;
position: absolute;
top: 0;
left: 14px;
}
.ivu-collapse-item:first-child:before {
height: 0;
}
}
.collapse-item__btns--box {
position: absolute;
right: 4px;
}
.rule__item {
position: relative;
padding-left: 20px;
cursor: pointer;
border-bottom: 1px solid #f3f5fa;
height: 30px;
line-height: 30px;
display: flex;
align-items: center;
justify-content: space-between;
&__btns {
padding-top: 7px;
padding-right: 28px;
display: none;
line-height: 30px;
}
.default {
position: absolute;
top: 0;
right: 0;
width: 28px;
height: 30px;
background: url("@/assets/images/assetLibrary/default.png") no-repeat
center center;
background-size: 100% 100%;
pointer-events: none;
}
}
.rule__item:hover {
color: #21333f;
background: #f3f5fa;
.rule__item__btns {
display: block;
}
}
</style>
\ No newline at end of file
......@@ -95,7 +95,17 @@ function logout() {
type: 'warning'
}).then(() => {
userStore.logOut().then(() => {
location.href = '/index'
// location.href = '/index'
appStore.signOut('logout')
appStore.setNavStatus({type: 'manage'})
appStore.setQueryData({
projectId: ''
})
window.location.replace(`${import.meta.env.BASE_URL}`)
})
}).catch(() => { })
}
......
......@@ -5,6 +5,7 @@ import Cookies from 'js-cookie'
import ElementPlus from 'element-plus'
import 'element-plus/dist/index.css'
import 'element-plus/theme-chalk/dark/css-vars.css'
import 'view-ui-plus/dist/styles/viewuiplus.css';
import locale from 'element-plus/es/locale/lang/zh-cn'
import '@/assets/styles/index.scss' // global css
......
......@@ -11,7 +11,7 @@ import usePermissionStore from '@/store/modules/permission'
NProgress.configure({ showSpinner: false })
const whiteList = ['/login', '/register', '/assetLibrary']
const whiteList = ['/login', '/register', '/assetLibrary', '/projectHome', '/projectManage','/classification']
const isWhiteList = (path) => {
return whiteList.some(pattern => isPathMatch(pattern, path))
......@@ -19,48 +19,56 @@ const isWhiteList = (path) => {
router.beforeEach((to, from, next) => {
NProgress.start()
if (getToken()) {
to.meta.title && useSettingsStore().setTitle(to.meta.title)
/* has token*/
if (to.path === '/login') {
next({ path: '/' })
NProgress.done()
} else if (isWhiteList(to.path)) {
next()
} else {
if (useUserStore().roles.length === 0) {
isRelogin.show = true
// 判断当前用户是否已拉取完user_info信息
useUserStore().getInfo().then(() => {
isRelogin.show = false
usePermissionStore().generateRoutes().then(accessRoutes => {
// 根据roles权限生成可访问的路由表
accessRoutes.forEach(route => {
if (!isHttp(route.path)) {
router.addRoute(route) // 动态添加可访问路由表
}
})
next({ ...to, replace: true }) // hack方法 确保addRoutes已完成
})
}).catch(err => {
useUserStore().logOut().then(() => {
ElMessage.error(err)
next({ path: '/' })
})
})
} else {
next()
}
}
} else {
// 没有token
if (isWhiteList(to.path)) {
// 在免登录白名单,直接进入
next()
} else {
next(`/login?redirect=${to.fullPath}`) // 否则全部重定向到登录页
NProgress.done()
}
// if (getToken()) {
// to.meta.title && useSettingsStore().setTitle(to.meta.title)
// /* has token*/
// if (to.path === '/login') {
// next({ path: '/' })
// NProgress.done()
// } else if (isWhiteList(to.path)) {
// next()
// } else {
// if (useUserStore().roles.length === 0) {
// isRelogin.show = true
// // 判断当前用户是否已拉取完user_info信息
// useUserStore().getInfo().then(() => {
// isRelogin.show = false
// usePermissionStore().generateRoutes().then(accessRoutes => {
// // 根据roles权限生成可访问的路由表
// accessRoutes.forEach(route => {
// if (!isHttp(route.path)) {
// router.addRoute(route) // 动态添加可访问路由表
// }
// })
// next({ ...to, replace: true }) // hack方法 确保addRoutes已完成
// })
// }).catch(err => {
// useUserStore().logOut().then(() => {
// ElMessage.error(err)
// next({ path: '/' })
// })
// })
// } else {
// next()
// }
// }
// } else {
// // 没有token
// if (isWhiteList(to.path)) {
// // 在免登录白名单,直接进入
// next()
// } else {
// next(`/login?redirect=${to.fullPath}`) // 否则全部重定向到登录页
// NProgress.done()
// }
// }
if (sessionStorage.getItem('__token') || isWhiteList(to.path)){
next()
}else{
next(`/login?redirect=${to.fullPath}`)
NProgress.done()
}
})
......
......@@ -15,7 +15,7 @@ export default {
method: 'get',
url: url,
responseType: 'blob',
headers: { 'Authorization': 'Bearer ' + getToken() }
headers: { 'Token': getToken() }
}).then((res) => {
const isBlob = blobValidate(res.data)
if (isBlob) {
......@@ -32,7 +32,7 @@ export default {
method: 'get',
url: url,
responseType: 'blob',
headers: { 'Authorization': 'Bearer ' + getToken() }
headers: { 'Token': getToken() }
}).then((res) => {
const isBlob = blobValidate(res.data)
if (isBlob) {
......@@ -50,7 +50,7 @@ export default {
method: 'get',
url: url,
responseType: 'blob',
headers: { 'Authorization': 'Bearer ' + getToken() }
headers: { 'Token': getToken() }
}).then((res) => {
const isBlob = blobValidate(res.data)
if (isBlob) {
......
......@@ -63,16 +63,30 @@ export const constantRoutes = [
hidden: true
},
{
// 项目首页
path: '/projectManage',
component: () => import('@/views/projectManage/index'),
hidden: true
},
{
// 项目首页
path: '/projectHome',
component: () => import('@/views/projectHome/index'),
hidden: true
},
{
// 加密设置
path: '/classification',
component: () => import('@/views/Classification/index'),
hidden: true
},
{
// 加密管理
path: '/encryptionManagement',
component: () => import('@/views/EncryptionManagement/index'),
hidden: true
},
{
path: '',
component: Layout,
redirect: '/index',
......
......@@ -4,7 +4,10 @@ import { login, logout, getInfo } from '@/api/login'
import { getToken, setToken, removeToken } from '@/utils/auth'
import { isHttp, isEmpty } from "@/utils/validate"
import defAva from '@/assets/images/profile.jpg'
import { sm4 as SM4 } from 'gm-crypt'
// import useAppStore from './app'
// const appStore = useAppStore()
const useUserStore = defineStore(
'user',
{
......@@ -21,19 +24,70 @@ const useUserStore = defineStore(
// 登录
login(userInfo) {
const username = userInfo.username.trim()
const password = userInfo.password
const code = userInfo.code
const uuid = userInfo.uuid
// const password = userInfo.password
console.log('userInfo.password',userInfo.password)
const password = this.encryptPassword(userInfo.password)
// const code = userInfo.code
// const uuid = userInfo.uuid
return new Promise((resolve, reject) => {
login(username, password, code, uuid).then(res => {
setToken(res.token)
this.token = res.token
resolve()
login(username, password).then(res => {
console.log(res)
if (res.code == "POP_00014"){
resolve(res.data)
}else{
reject(error)
}
// return
// setToken(res.token)
// this.token = res.token
reject(error)
}).catch(error => {
reject(error)
})
// login(username, password, code, uuid).then(res => {
// setToken(res.token)
// this.token = res.token
// resolve()
// }).catch(error => {
// reject(error)
// })
})
},
// 加密方法
encryptPassword(plainPassword) {
const sm4Config = {
key: "GJstSK_YBD=gSOFT",
mode: 'ecb',
cipherType: 'base64'
}
const sm4 = new SM4(sm4Config)
try {
return sm4.encrypt(plainPassword)
} catch (error) {
console.error('加密失败:', error)
throw new Error('密码加密处理失败')
}
},
// 十六进制转Base64工具方法
hexToBase64(hexString) {
// 将十六进制字符串转换为字节数组
const byteArray = new Uint8Array(
hexString.match(/.{1,2}/g).map(byte => parseInt(byte, 16))
);
// 将字节数组转换为Base64
const base64String = btoa(
String.fromCharCode.apply(null, byteArray)
);
return base64String;
},
// 获取用户信息
getInfo() {
return new Promise((resolve, reject) => {
......
import Cookies from 'js-cookie'
const TokenKey = 'Admin-Token'
const TokenKey = '__token'
export function getToken() {
return Cookies.get(TokenKey)
return sessionStorage.getItem(TokenKey) || Cookies.get(TokenKey)
}
export function setToken(token) {
return Cookies.set(TokenKey, token)
return Cookies.set(TokenKey, token)
}
export function removeToken() {
......
......@@ -27,7 +27,7 @@ service.interceptors.request.use(config => {
// 是否需要防止数据重复提交
const isRepeatSubmit = (config.headers || {}).repeatSubmit === false
if (getToken() && !isToken) {
config.headers['Authorization'] = 'Bearer ' + getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
config.headers['Token'] = getToken() // 让每个请求携带自定义token 请根据实际情况自行修改
}
// get请求映射params参数
if (config.method === 'get' && config.params) {
......@@ -74,7 +74,7 @@ service.interceptors.request.use(config => {
// 响应拦截器
service.interceptors.response.use(res => {
// 未设置状态码则默认成功状态
const code = res.data.code || 200
const code = res.data.code || 'POP_00014'
// 获取错误信息
const msg = errorCode[code] || res.data.msg || errorCode['default']
// 二进制数据则直接返回
......@@ -100,7 +100,7 @@ service.interceptors.response.use(res => {
} else if (code === 601) {
ElMessage({ message: msg, type: 'warning' })
return Promise.reject(new Error(msg))
} else if (code !== 200) {
} else if (code !== 'POP_00014') {
ElNotification.error({ title: msg })
return Promise.reject('error')
} else {
......
<script setup name="Classification">
import { ref, toRefs, reactive, getCurrentInstance, proxyRefs, onMounted } from 'vue'
import TreeFilter from './modules/TreeFilter.vue'
import BasicInfoTab from './modules/BasicInfoTab.vue'
import TableInfoTab from './modules/TableInfoTab.vue'
import StructureTab from './modules/StructureTab.vue'
import EncryptionTab from './modules/EncryptionTab.vue'
import { useDict } from '@/utils/dict'
import { useRouter } from 'vue-router'
const router = useRouter()
const { proxy } = getCurrentInstance()
const projectId = ref('')
onMounted(()=>{
projectId.value =proxy.$route.query.projectId
console.log('projectId',projectId.value)
})
// const props = defineProps({
// projectId: String, // 主键
// })
// 树形数据
const treeData = ref([
{
id: 'system',
label: '若依配测系统',
type: 'system',
children: [
{
id: 'database',
label: 'ry',
type: 'database',
children: [
{
id: 'tables',
label: '表',
type: 'category',
children: [
{ id: 'gen_table', label: 'gen_table', type: 'table' },
{ id: 'gen_table_column', label: 'gen_table_column', type: 'table' },
{ id: 'sys_config', label: 'sys_config', type: 'table' },
{ id: 'sys_dept', label: 'sys_dept', type: 'table' },
{ id: 'sys_dict_data', label: 'sys_dict_data', type: 'table' },
{ id: 'sys_dict_type', label: 'sys_dict_type', type: 'table' },
{ id: 'sys_job', label: 'sys_job', type: 'table' },
{ id: 'sys_job_log', label: 'sys_job_log', type: 'table' },
{ id: 'sys_logininfor', label: 'sys_logininfor', type: 'table' },
{ id: 'sys_menu', label: 'sys_menu', type: 'table' },
{ id: 'sys_notice', label: 'sys_notice', type: 'table' },
{ id: 'sys_oper_log', label: 'sys_oper_log', type: 'table' },
{ id: 'sys_post', label: 'sys_post', type: 'table' },
{ id: 'sys_role', label: 'sys_role', type: 'table' },
{ id: 'sys_role_dept', label: 'sys_role_dept', type: 'table' },
{ id: 'sys_role_menu', label: 'sys_role_menu', type: 'table' },
{ id: 'sys_user', label: 'sys_user', type: 'table' },
{ id: 'sys_user_online', label: 'sys_user_online', type: 'table' },
{ id: 'sys_user_post', label: 'sys_user_post', type: 'table' },
{ id: 'sys_user_role', label: 'sys_user_role', type: 'table' }
]
}
]
}
]
}
])
// 当前选中的节点数据
const currentNodeData = ref(null)
const currentNodeLevel = computed(() => {
if (!currentNodeData.value) return 0
if (currentNodeData.value.type === 'system') return 1
if (currentNodeData.value.type === 'database') return 2
if (currentNodeData.value.type === 'category') return 3
if (currentNodeData.value.type === 'table') return 4
return 0
})
// 是否启用解密插件
const enableDecryptionPlugin = ref(false)
// 当前激活的Tab
const activeTab = ref('basic')
// 当前表结构数据 (匹配图片中的gen_table结构)
const currentTableStructure = ref([
{ pk: '', fieldName: 'business_name', comment: '生成业务名', fieldType: 'VARCHAR', length: '30', precision: '', algorithm: '' },
{ pk: '', fieldName: 'business_name_...', comment: '', fieldType: 'TEXT', length: '65535', precision: '', algorithm: '' },
{ pk: '', fieldName: 'class_name', comment: '实体类名称', fieldType: 'VARCHAR', length: '100', precision: '', algorithm: '' },
{ pk: '', fieldName: 'create_by', comment: '创建者', fieldType: 'VARCHAR', length: '64', precision: '', algorithm: '' },
{ pk: '', fieldName: 'create_time', comment: '创建时间', fieldType: 'DATETIME', length: '19', precision: '', algorithm: '' },
{ pk: '', fieldName: 'function_author', comment: '生成功能作者', fieldType: 'VARCHAR', length: '50', precision: '', algorithm: '' },
{ pk: '', fieldName: 'function_name', comment: '生成功能名', fieldType: 'VARCHAR', length: '50', precision: '', algorithm: '' },
{ pk: '', fieldName: 'gen_path', comment: '生成路径(不填...', fieldType: 'VARCHAR', length: '200', precision: '', algorithm: '' },
{ pk: '', fieldName: 'gen_type', comment: '生成代码方式(0...', fieldType: 'CHAR', length: '1', precision: '', algorithm: '' },
{ pk: '', fieldName: 'module_name', comment: '生成模块名', fieldType: 'VARCHAR', length: '30', precision: '', algorithm: '' }
])
// 树节点点击处理
const handleNodeClick = (data) => {
currentNodeData.value = data
activeTab.value = 'basic'
}
// 批量加密
const handleBatchEncrypt = () => {
console.log('批量加密', currentNodeData.value)
}
// 批量解密
const handleBatchDecrypt = () => {
console.log('批量解密', currentNodeData.value)
}
// 删除多余列
const handleDeleteColumns = () => {
console.log('删除多余列', currentNodeData.value)
}
// 编辑字段
const handleEditField = (field) => {
console.log('编辑字段', field)
}
const openDecrypt = ref(false)
function pageProjectManage() {
router.push({
path: '/projectManage'
})
}
defineExpose({
// handleRedInk,
// handleVoid
})
</script>
<template>
<div class="app-container scroller">
<PageTitle :back="true" @back="pageProjectManage" >
<template #title>
返回项目管理
</template>
</PageTitle>
<div class="app-container__body">
<div class="flex-container content-container">
<div class="left">
<el-card class="image-card tree-container">
<TreeFilter :tree-data="treeData" @node-click="handleNodeClick"/>
</el-card>
</div>
<div class="right flex1">
<el-card class="image-card">
<div class="header-section">
<div class="breadcrumb flex-container justify-between align-center">
<el-breadcrumb separator="/">
<el-breadcrumb-item>若依配测系统</el-breadcrumb-item>
<el-breadcrumb-item v-if="currentNodeLevel >= 2">ry</el-breadcrumb-item>
<el-breadcrumb-item v-if="currentNodeLevel >= 3"></el-breadcrumb-item>
<el-breadcrumb-item v-if="currentNodeLevel >= 4">{{ currentNodeData?.label }}</el-breadcrumb-item>
</el-breadcrumb>
<el-checkbox v-model="openDecrypt">是否启用解密插件</el-checkbox>
</div>
<div class="radio-group" v-if="currentNodeLevel === 4">
<el-radio-group v-model="enableDecryptionPlugin">
<el-radio :label="true">启用解密插件</el-radio>
<el-radio :label="false">不启用解密插件</el-radio>
</el-radio-group>
</div>
</div>
<!-- Tab区域 -->
<div class="tab-section">
<el-tabs v-model="activeTab">
<!-- 基本信息Tab -->
<el-tab-pane label="基本信息" name="basic">
<BasicInfoTab
:node-data="currentNodeData"
v-if="activeTab === 'basic' && currentNodeLevel === 1"
/>
<TableInfoTab
:node-data="currentNodeData"
v-if="activeTab === 'basic' && currentNodeLevel === 4"
/>
</el-tab-pane>
<!-- 数据结构Tab (仅4级节点显示) -->
<el-tab-pane label="数据结构" name="structure" v-if="currentNodeLevel === 4">
<StructureTab
:table-data="currentTableStructure"
v-if="activeTab === 'structure'"
/>
</el-tab-pane>
<!-- 字段加密配置Tab (仅4级节点显示) -->
<el-tab-pane label="字段加密配置" name="encryption" v-if="currentNodeLevel === 4">
<EncryptionTab
:table-data="currentTableStructure"
@batch-encrypt="handleBatchEncrypt"
@batch-decrypt="handleBatchDecrypt"
@delete-columns="handleDeleteColumns"
@edit-field="handleEditField"
v-if="activeTab === 'encryption'"
/>
</el-tab-pane>
</el-tabs>
</div>
</el-card>
</div>
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
.flex-container {
display: flex;
}
.align-center {
align-items: center;
}
.justify-between {
justify-content: space-between;
}
.flex1 {
flex: 1;
}
.content-container {
.left {
.tree-container {
width: 300px;
}
}
.right {
margin-left: var(--container-pd);
overflow: hidden;
}
}
</style>
<template>
<div class="basic-info-tab">
<el-descriptions
title="系统基本信息"
border
:column="1"
size="medium"
>
<el-descriptions-item label="项目">若依配测系统</el-descriptions-item>
<el-descriptions-item label="数据源">若依配测系统</el-descriptions-item>
<el-descriptions-item label="数据源类型">MYSQL</el-descriptions-item>
<el-descriptions-item label="IP">172.19.1.166</el-descriptions-item>
<el-descriptions-item label="管理的SCHEMA">ry</el-descriptions-item>
</el-descriptions>
</div>
</template>
<script setup>
// 可以留空,因为数据是静态的
</script>
<style scoped>
.basic-info-tab {
padding: 20px;
background-color: white;
}
:deep(.el-descriptions__title) {
font-size: 16px;
font-weight: bold;
margin-bottom: 16px;
}
:deep(.el-descriptions__header) {
margin-bottom: 16px;
}
:deep(.el-descriptions__label) {
width: 180px;
background-color: #f5f7fa;
font-weight: bold;
color: #333;
}
:deep(.el-descriptions__content) {
background-color: white;
}
</style>
\ No newline at end of file
<template>
<el-dialog
title="设置加密规则"
v-model="dialogVisible"
width="800px"
:before-close="handleClose"
>
<div class="encryption-rule-dialog">
<!-- 上部:规则和密钥选择 -->
<div class="selection-section">
<!-- 左侧加密规则选择 -->
<div class="rule-selection">
<div class="section-title">选择加密规则</div>
<el-radio-group v-model="selectedRule" class="rule-radio-group">
<el-radio
v-for="rule in encryptionRules"
:key="rule.id"
:label="rule.id"
class="rule-radio"
>
{{ rule.name }}
</el-radio>
</el-radio-group>
</div>
<!-- 分割线 -->
<div class="divider"></div>
<!-- 右侧加密密钥选择 -->
<div class="key-selection">
<div class="section-title">选择加密密钥</div>
<el-radio-group v-model="selectedKey" class="key-radio-group">
<el-radio
v-for="key in currentKeys"
:key="key.id"
:label="key.id"
class="key-radio"
>
{{ key.name }}
</el-radio>
</el-radio-group>
</div>
</div>
<!-- 下部:配置区域 -->
<div class="config-section">
<div class="config-row">
<!-- 加密位置 -->
<div class="config-item">
<span class="required-label">*加密位置:</span>
<el-select v-model="ruleConfig.positionType" style="width: 120px">
<el-option label="保留前" value="prefix" />
<el-option label="保留后" value="suffix" />
</el-select>
<el-input-number
v-model="ruleConfig.position"
:min="0"
:max="100"
controls-position="right"
style="width: 100px; margin-left: 10px;"
/>
<span class="unit"></span>
</div>
</div>
<div class="config-row">
<!-- 源长度 -->
<div class="config-item">
<span class="label">源长度:</span>
<el-input-number
v-model="ruleConfig.sourceLength"
:min="0"
:max="1000"
controls-position="right"
style="width: 100px;"
/>
<el-button
type="primary"
size="small"
style="margin-left: 10px;"
@click="calculateLength"
>
计算
</el-button>
</div>
<!-- 加密后长度 -->
<div class="config-item">
<span class="label">加密后长度:</span>
<span class="value">{{ encryptedLength || '--' }}</span>
</div>
</div>
<div class="action-row">
<el-checkbox v-model="supportFuzzy">支持模糊</el-checkbox>
<div class="action-buttons">
<el-button @click="resetRules">重置规则</el-button>
<el-button type="primary" @click="confirmRules">确定规则</el-button>
</div>
</div>
</div>
</div>
</el-dialog>
</template>
<script setup>
import { ref, computed, watch } from 'vue'
const props = defineProps({
visible: {
type: Boolean,
default: false
},
fieldData: {
type: Object,
default: () => ({})
}
})
const emit = defineEmits(['update:visible', 'confirm'])
// 控制弹窗显示
const dialogVisible = computed({
get: () => props.visible,
set: (value) => emit('update:visible', value)
})
// 加密规则数据
const encryptionRules = ref([
{ id: 'name', name: '姓名加密规则' },
{ id: 'gender', name: '性别加密规则' }
])
// 加密密钥数据
const encryptionKeys = ref({
name: [
{ id: 'name_key1', name: '姓名加密密钥1' },
{ id: 'name_key2', name: '姓名加密密钥2' }
],
gender: [
{ id: 'gender_key1', name: '性别加密密钥1' },
{ id: 'gender_key2', name: '性别加密密钥2' }
]
})
// 当前选中的规则
const selectedRule = ref('name')
// 当前选中的密钥
const selectedKey = ref('')
// 当前显示的密钥列表
const currentKeys = computed(() => {
return encryptionKeys.value[selectedRule.value] || []
})
// 加密配置
const ruleConfig = ref({
positionType: 'prefix',
position: 0,
sourceLength: 100
})
// 加密后长度
const encryptedLength = ref('')
// 是否支持模糊
const supportFuzzy = ref(false)
// 监听规则变化,自动选择第一个密钥
watch(selectedRule, (newVal) => {
if (currentKeys.value.length > 0) {
selectedKey.value = currentKeys.value[0].id
}
}, { immediate: true })
// 计算加密后长度
const calculateLength = () => {
// 这里应该是实际的加密长度计算逻辑
encryptedLength.value = Math.floor(ruleConfig.value.sourceLength * 0.8)
}
// 重置规则
const resetRules = () => {
selectedRule.value = 'name'
ruleConfig.value = {
positionType: 'prefix',
position: 0,
sourceLength: 100
}
encryptedLength.value = ''
supportFuzzy.value = false
}
// 确认规则
const confirmRules = () => {
emit('confirm', {
rule: selectedRule.value,
key: selectedKey.value,
config: ruleConfig.value,
supportFuzzy: supportFuzzy.value,
encryptedLength: encryptedLength.value
})
dialogVisible.value = false
}
// 关闭弹窗前处理
const handleClose = (done) => {
done()
}
</script>
<style scoped>
.encryption-rule-dialog {
padding: 10px;
}
.selection-section {
display: flex;
margin-bottom: 20px;
padding-bottom: 15px;
border-bottom: 1px solid #eee;
}
.rule-selection, .key-selection {
flex: 1;
padding: 0 15px;
}
/* 新增的分割线样式 */
.divider {
width: 1px;
background-color: #ebeef5;
margin: 0 10px;
}
.section-title {
margin-bottom: 15px;
font-size: 14px;
font-weight: bold;
color: #333;
}
.rule-radio-group, .key-radio-group {
display: flex;
flex-direction: column;
}
.rule-radio, .key-radio {
margin: 5px 0;
}
.config-section {
padding: 0 15px;
}
.config-row {
display: flex;
align-items: center;
margin-bottom: 15px;
flex-wrap: wrap;
}
.config-item {
display: flex;
align-items: center;
margin-right: 20px;
}
.required-label {
width: 80px;
text-align: right;
margin-right: 10px;
font-size: 14px;
color: #f56c6c;
}
.label {
text-align: right;
margin-right: 10px;
font-size: 14px;
color: #666;
}
.unit {
margin-left: 5px;
font-size: 14px;
color: #666;
}
.value {
font-size: 14px;
color: #333;
font-weight: bold;
}
.action-row {
display: flex;
justify-content: space-between;
align-items: center;
margin-top: 20px;
}
.action-buttons {
display: flex;
gap: 10px;
}
</style>
\ No newline at end of file
<template>
<div class="encryption-tab">
<!-- 搜索区域 -->
<div class="filter-section">
<el-form :inline="true" :model="filterForm" class="filter-form">
<el-form-item label="字段名过滤:">
<el-input
v-model="filterForm.fieldName"
placeholder="字段名模糊搜索"
clearable
style="width: 200px;"
/>
</el-form-item>
<el-form-item label="状态:">
<el-select style="width: 200px;" v-model="filterForm.status" placeholder="全部" clearable>
<el-option label="已加密" value="encrypted" />
<el-option label="未加密" value="unencrypted" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSearch">搜索</el-button>
</el-form-item>
</el-form>
</div>
<!-- 表格区域 -->
<el-table
:data="filteredTableData"
border
style="width: 100%; margin-top: 15px;"
:row-class-name="tableRowClassName"
>
<!-- 左浮动列 -->
<el-table-column prop="pk" label="主键" width="60" align="left" fixed />
<el-table-column prop="fieldName" label="字段名" width="150" align="left" fixed/>
<el-table-column prop="fieldType" label="字段类型" width="120" align="center" />
<el-table-column prop="length" label="长度" width="80" align="center" />
<el-table-column prop="algorithm" label="加密算法" width="120" align="center" />
<el-table-column label="密钥" width="120" align="center">
<template #default="{ row }">
<el-button type="text" @click="openEncryptionDialog(row)">设置</el-button>
</template>
</el-table-column>
<el-table-column prop="status" label="状态" width="100" align="center">
<template #default="{ row }">
<el-tag v-if="row.status === 'encrypted'" type="success" size="small">已加密</el-tag>
<el-tag v-else type="info" size="small">未加密</el-tag>
</template>
</el-table-column>
<!-- 右浮动列 -->
<el-table-column prop="comment" label="注释" align="left" min-width="200" />
<el-table-column label="描述信息" align="left" fixed="right">
<template #default="{ row }">
<el-input
v-model="row.description"
size="small"
:disabled="!isEditing"
@change="handleDescriptionChange(row)"
/>
</template>
</el-table-column>
<el-table-column prop="plaintextProcess" label="明文处理" align="left" fixed="right"/>
</el-table>
<!-- 操作按钮区域 -->
<div class="action-buttons">
<el-button type="primary" @click="handleBatchEncrypt">批量加密</el-button>
<el-button type="primary" @click="handleBatchDecrypt">批量解密</el-button>
<el-button type="primary" @click="handleDeleteColumns">删除多余列</el-button>
<el-button
type="primary"
@click="toggleEditMode"
>
{{ isEditing ? '保存' : '编辑' }}
</el-button>
</div>
<EncryptionRuleDialog
v-model:visible="showEncryptionDialog"
:field-data="currentField"
@confirm="handleRuleConfirm"
/>
</div>
</template>
<script setup>
import { ref, computed, reactive } from 'vue'
import EncryptionRuleDialog from './EncryptionRuleDialog.vue'
const props = defineProps({
tableData: {
type: Array,
required: true,
default: () => [
{
pk: 'PK',
fieldName: 'table_id',
fieldType: 'BIGINT',
length: '19',
algorithm: '',
status: 'unencrypted',
comment: '编号',
description: '主键ID',
plaintextProcess: ''
},
// 其他数据行...
]
}
})
const emit = defineEmits(['batch-encrypt', 'batch-decrypt', 'delete-columns', 'edit-field', 'update-field'])
// 搜索表单
const filterForm = reactive({
fieldName: '',
status: ''
})
// 编辑模式
const isEditing = ref(false)
// 表格行样式
const tableRowClassName = ({ row, rowIndex }) => {
return row.status === 'encrypted' ? 'encrypted-row' : ''
}
// 处理搜索
const handleSearch = () => {
// 搜索逻辑已在计算属性中实现
}
// 切换编辑模式
const toggleEditMode = () => {
isEditing.value = !isEditing.value
}
// 处理描述信息变更
const handleDescriptionChange = (row) => {
emit('update-field', row)
}
// 批量加密
const handleBatchEncrypt = () => {
emit('batch-encrypt')
}
// 批量解密
const handleBatchDecrypt = () => {
emit('batch-decrypt')
}
// 删除多余列
const handleDeleteColumns = () => {
emit('delete-columns')
}
// 过滤表格数据
const filteredTableData = computed(() => {
return props.tableData.filter(item => {
const matchesName = filterForm.fieldName
? item.fieldName.includes(filterForm.fieldName)
: true
const matchesStatus = filterForm.status
? item.status === filterForm.status
: true
return matchesName && matchesStatus
})
})
const showEncryptionDialog = ref(false)
const currentField = ref({})
// 处理设置密钥
const openEncryptionDialog = (row) => {
if(!isEditing.value) return
currentField.value = row
showEncryptionDialog.value = true
}
const handleRuleConfirm = (ruleData) => {
console.log('确认加密规则:', ruleData)
// 更新表格数据或调用API
}
</script>
<style scoped>
.encryption-tab {
padding: 15px;
background-color: white;
}
.filter-section {
margin-bottom: 15px;
}
.filter-form {
display: flex;
align-items: center;
}
.action-buttons {
margin-top: 20px;
text-align: center;
}
:deep(.el-table .encrypted-row) {
background-color: #f0f9eb;
}
:deep(.el-table td) {
padding: 8px 0;
}
:deep(.el-table th) {
background-color: #f5f7fa;
}
:deep(.el-form-item) {
margin-bottom: 0;
}
:deep(.el-form-item__label) {
color: #333;
font-weight: normal;
}
</style>
\ No newline at end of file
<template>
<div class="structure-tab">
<el-table
:data="tableData"
border
style="width: 100%"
:header-cell-style="{ background: '#f5f7fa', color: '#333' }"
:row-style="{ height: '40px' }"
:cell-style="{ padding: '8px 0', textAlign: 'center' }"
>
<el-table-column prop="pk" label="主键" width="120" fixed/>
<el-table-column prop="fieldName" label="字段名" width="180" />
<el-table-column prop="comment" label="注释" min-width="200" />
<el-table-column prop="fieldType" label="类型" width="120" />
<el-table-column prop="length" label="长度" width="120" />
<el-table-column prop="precision" label="精度" width="120" />
</el-table>
</div>
</template>
<script setup>
const props = defineProps({
tableData: {
type: Array,
required: true,
default: () => [
{ pk: '', fieldName: 'business_name', comment: '生成业务名', fieldType: 'VARCHAR', length: '30', precision: '' },
{ pk: '', fieldName: 'business_name_...', comment: '', fieldType: 'TEXT', length: '65535', precision: '' },
{ pk: '', fieldName: 'class_name', comment: '实体类名称', fieldType: 'VARCHAR', length: '100', precision: '' },
{ pk: '', fieldName: 'create_by', comment: '创建者', fieldType: 'VARCHAR', length: '64', precision: '' },
{ pk: '', fieldName: 'create_time', comment: '创建时间', fieldType: 'DATETIME', length: '19', precision: '' },
{ pk: '', fieldName: 'function_author', comment: '生成功能作者', fieldType: 'VARCHAR', length: '50', precision: '' },
{ pk: '', fieldName: 'function_name', comment: '生成功能名', fieldType: 'VARCHAR', length: '50', precision: '' },
{ pk: '', fieldName: 'gen_path', comment: '生成路径(不填...', fieldType: 'VARCHAR', length: '200', precision: '' },
{ pk: '', fieldName: 'gen_type', comment: '生成代码方式(0...', fieldType: 'CHAR', length: '1', precision: '' },
{ pk: '', fieldName: 'module_name', comment: '生成模块名', fieldType: 'VARCHAR', length: '30', precision: '' }
]
}
})
</script>
<style scoped>
.structure-tab {
padding: 10px;
background-color: white;
}
:deep(.el-table) {
font-size: 14px;
}
:deep(.el-table--striped .el-table__body tr.el-table__row--striped td) {
background-color: #fafafa;
}
</style>
\ No newline at end of file
<template>
<div class="table-info-tab">
<el-descriptions
title="表基本信息"
border
:column="1"
size="medium"
>
<el-descriptions-item label="字段数">224</el-descriptions-item>
<el-descriptions-item label="加密表数量">0</el-descriptions-item>
<el-descriptions-item label="加密字段数量">0</el-descriptions-item>
<el-descriptions-item label="未加密表数量">20</el-descriptions-item>
<el-descriptions-item label="未加密字段数量">224</el-descriptions-item>
<el-descriptions-item label="操作人">admin</el-descriptions-item>
<el-descriptions-item label="操作时间">2023-05-15 14:30:22</el-descriptions-item>
</el-descriptions>
</div>
</template>
<script setup>
// 可以留空,因为数据是静态的
</script>
<style scoped>
.table-info-tab {
padding: 20px;
background-color: white;
}
:deep(.el-descriptions__title) {
font-size: 16px;
font-weight: bold;
margin-bottom: 16px;
}
:deep(.el-descriptions__header) {
margin-bottom: 16px;
}
:deep(.el-descriptions__label) {
width: 180px;
background-color: #f5f7fa;
font-weight: bold;
color: #333;
}
:deep(.el-descriptions__content) {
background-color: white;
}
</style>
\ No newline at end of file
<template>
<div class="tree-filter-container">
<!-- 搜索框 -->
<div class="search-box">
<el-input
v-model="filterText"
placeholder="输入关键字过滤"
clearable
prefix-icon="el-icon-search"
/>
</div>
<!-- 树形结构 -->
<el-tree
ref="treeRef"
class="filter-tree"
:data="treeData"
:props="defaultProps"
:filter-node-method="filterNode"
:expand-on-click-node="false"
node-key="id"
highlight-current
default-expand-all
@node-click="handleNodeClick"
>
<template #default="{ node, data }">
<span class="custom-tree-node">
<i :class="getNodeIcon(data.type)" class="node-icon"></i>
<span>{{ node.label }}</span>
</span>
</template>
</el-tree>
</div>
</template>
<script setup>
import { ref, watch } from 'vue'
const emit = defineEmits(['node-click'])
const props = defineProps({
treeData: {
type: Array,
default: () => [
{
id: 'system',
label: '若依配测系统',
type: 'system',
children: [
{
id: 'database',
label: 'ry',
type: 'database',
children: [
{
id: 'tables',
label: '表',
type: 'category',
children: [
{ id: 'gen_table', label: 'gen_table', type: 'table' },
{ id: 'gen_table_column', label: 'gen_table_column', type: 'table' },
{ id: 'sys_config', label: 'sys_config', type: 'table' },
{ id: 'sys_dept', label: 'sys_dept', type: 'table' },
{ id: 'sys_dict_data', label: 'sys_dict_data', type: 'table' },
{ id: 'sys_dict_type', label: 'sys_dict_type', type: 'table' },
{ id: 'sys_job', label: 'sys_job', type: 'table' },
{ id: 'sys_job_log', label: 'sys_job_log', type: 'table' },
{ id: 'sys_logininfor', label: 'sys_logininfor', type: 'table' },
{ id: 'sys_menu', label: 'sys_menu', type: 'table' },
{ id: 'sys_notice', label: 'sys_notice', type: 'table' },
{ id: 'sys_oper_log', label: 'sys_oper_log', type: 'table' },
{ id: 'sys_post', label: 'sys_post', type: 'table' },
{ id: 'sys_role', label: 'sys_role', type: 'table' },
{ id: 'sys_role_dept', label: 'sys_role_dept', type: 'table' },
{ id: 'sys_role_menu', label: 'sys_role_menu', type: 'table' },
{ id: 'sys_user', label: 'sys_user', type: 'table' },
{ id: 'sys_user_online', label: 'sys_user_online', type: 'table' },
{ id: 'sys_user_post', label: 'sys_user_post', type: 'table' },
{ id: 'sys_user_role', label: 'sys_user_role', type: 'table' }
]
}
]
}
]
}
]
}
})
const filterText = ref('')
const treeRef = ref(null)
const defaultProps = {
children: 'children',
label: 'label'
}
// 根据节点类型获取图标
const getNodeIcon = (type) => {
const iconMap = {
system: 'el-icon-s-platform',
database: 'el-icon-s-data',
category: 'el-icon-folder-opened',
table: 'el-icon-s-grid'
}
return iconMap[type] || 'el-icon-document'
}
// 过滤树节点
const filterNode = (value, data) => {
if (!value) return true
return data.label.toLowerCase().includes(value.toLowerCase())
}
// 监听过滤文本变化
watch(filterText, (val) => {
treeRef.value.filter(val)
})
// 节点点击事件
const handleNodeClick = (data) => {
emit('node-click', data)
}
</script>
<style scoped>
.tree-filter-container {
width: 100%;
height: 100%;
background-color: #f5f5f5;
padding: 10px;
border-right: 1px solid #e6e6e6;
}
.search-box {
margin-bottom: 10px;
}
.filter-tree {
background-color: transparent;
}
.custom-tree-node {
flex: 1;
display: flex;
align-items: center;
font-size: 14px;
}
.node-icon {
margin-right: 6px;
color: #606266;
}
:deep(.el-tree-node__content) {
height: 36px;
}
</style>
\ No newline at end of file
<script setup lang="ts" name="QueryForm">
import { computed,ref,watch } from 'vue'
import type { FormInstance } from 'element-plus'
import PageWrapperSearch from '@/components/search/PageWrapperSearch.vue'
// import { useDict } from '@/utils/dict'
// import { listDept } from '@/api/system/dept'// 部门
// const { approve_status, invoice_status} = useDict('approve_status', 'invoice_status')
const emit = defineEmits(['update:modelValue', 'query', 'reset'])
const invoice_status_filter = ref([])
const employeesList = ref([])
const props = defineProps<{
modelValue: any
}>()
const queryForm = computed({
get() {
return props.modelValue
},
set(val: any) {
console.log('query computed', val)
emit('update:modelValue', val)
}
})
// 搜索
function onSearch() {
emit('query')
}
// 重置
function onReset(formRef: FormInstance) {
queryForm.value.datasource = ''
queryForm.value.schema = ''
queryForm.value.tableName = ''
queryForm.value.fieldName = ''
queryForm.value.status = ''
emit('reset', formRef)
}
</script>
<template>
<!-- el-form -->
<page-wrapper-search
:model="queryForm"
@search="onSearch"
@reset="onReset">
<el-form-item label="数据源名称" prop="datasource">
<el-input
v-model="queryForm.datasource"
placeholder="请输入数据源名称"
clearable
/>
</el-form-item>
<el-form-item label="SCHEMA" prop="schema">
<el-input
v-model="queryForm.schema"
placeholder="请输入SCHEMA"
clearable
/>
</el-form-item>
<el-form-item label="表名称" prop="tableName">
<el-input
v-model="queryForm.tableName"
placeholder="请输入表名称"
clearable
/>
</el-form-item>
<el-form-item label="字段名称" prop="fieldName">
<el-input
v-model="queryForm.fieldName"
placeholder="请输入字段名称"
clearable
/>
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select
v-model="queryForm.status"
placeholder="请选择状态"
clearable
>
<el-option label="启用" value="active" />
<el-option label="禁用" value="inactive" />
</el-select>
</el-form-item>
</page-wrapper-search>
</template>
<style scoped>
</style>
<script setup lang="ts" name="projectManageIndex">
import { ref } from 'vue'
import list from './list.vue'
const widget = {
list: list
}
const page = ref('list')
const params = ref({})
function onChangePage(val: string, param?: any) {
page.value = val
params.value = param ?? {}
}
</script>
<template>
<component :is="widget[page]" v-bind="params" @page="onChangePage" />
</template>
<script setup name="ProjectManageList">
import { getCurrentInstance, reactive, ref, toRefs } from 'vue'
import { ElMessage } from 'element-plus'
import QueryForm from './QueryForm.vue'
// import ProjectEditDialog from './ProjectEditDialog.vue'
// import DownloadPluginDialog from './DownloadPluginDialog.vue'
// import ExportDialog from './ExportDialog.vue'
import { useRouter } from 'vue-router'
const router = useRouter()
const emit = defineEmits(['page'])
const { proxy } = getCurrentInstance()
function onReset(formQuery) {
console.log('onReset')
formQuery.resetFields()
handleQuery()
}
function onQuery() {
handleQuery()
}
// 搜索按钮操作
function handleQuery() {
console.log('queryParams',queryParams.value)
queryParams.value.pageNum = 1
getList()
}
const data = reactive({
queryParams: {
pageNum: 1,
pageSize: 8
}
})
// 表格数据
const { queryParams } = toRefs(data)
const total = ref(3)
const loading = ref(false)
// 查询列表
function getList() {
loading.value = true
setTimeout(() => {
loading.value = false
}, 3000);
}
function pageProjectManage() {
router.push({
path: '/projectManage'
})
}
/**
* 删除多余列
*/
function deleteUnnecessaryColumns(){
console.log('删除多余列')
}
/**
* 批量加密
*/
function bulkEncryption(){
console.log('批量加密')
}
/**
* 批量解密
*/
function batchDecryption() {
console.log('批量解密')
}
</script>
<template>
<div class="app-container scroller">
<PageTitle :back="true" @back="pageProjectManage">
<template #title>
返回项目管理
</template>
<template #buttons>
<el-button
type="primary"
@click="deleteUnnecessaryColumns"
>
删除多余列
</el-button>
<el-button
type="primary"
@click="bulkEncryption"
>
批量加密
</el-button>
<el-button
type="primary"
@click="batchDecryption"
>
批量解密
</el-button>
</template>
</PageTitle>
<div class="app-container__body">
<div>
<query-form
ref="QueryFormRef"
v-model="queryParams"
@query="onQuery"
@reset="onReset"/>
<pagination
v-show="total > 0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</div>
</div>
</div>
</template>
<style lang="scss" scoped>
.table-item {
margin-top: var(--container-pd);
margin-bottom: 20px;
}
.item-content {
margin: 8px 0;
.label {
width: 80px;
color: #888;
}
}
.flex-container {
display: flex;
}
.align-center {
align-items: center;
}
.justify-between {
justify-content: space-between;
}
.flex1 {
flex: 1;
}
</style>
\ No newline at end of file
......@@ -67,10 +67,7 @@ const onConfirm = () => {
<div class="app-container scroller">
<PageTitle>
<template #title>
<span id="badDebt" class="title-icon">
资产库
<PageTour tourType="badDebt" />
</span>
资产库
</template>
<template #buttons>
<el-button type="primary" icon="Plus" @click="handleAdd">新增数据源</el-button>
......
++ "b/src/views/assetLibrary/\350\265\204\344\272\247\345\272\223"
......@@ -25,7 +25,7 @@
<template #prefix><svg-icon icon-class="password" class="el-input__icon input-icon" /></template>
</el-input>
</el-form-item>
<el-form-item prop="code" v-if="captchaEnabled">
<!-- <el-form-item prop="code" v-if="captchaEnabled">
<el-input
v-model="loginForm.code"
size="large"
......@@ -39,8 +39,8 @@
<div class="login-code">
<img :src="codeUrl" @click="getCode" class="login-code-img"/>
</div>
</el-form-item>
<el-checkbox v-model="loginForm.rememberMe" style="margin:0px 0px 25px 0px;">记住密码</el-checkbox>
</el-form-item> -->
<!-- <el-checkbox v-model="loginForm.rememberMe" style="margin:0px 0px 25px 0px;">记住密码</el-checkbox> -->
<el-form-item style="width:100%;">
<el-button
:loading="loading"
......@@ -67,13 +67,17 @@
<script setup>
import { getCodeImg } from "@/api/login"
import Cookies from "js-cookie"
import { sm4 as SM4 } from 'gm-crypt'
import { encrypt, decrypt } from "@/utils/jsencrypt"
import useUserStore from '@/store/modules/user'
const title = import.meta.env.VITE_APP_TITLE
const userStore = useUserStore()
import useAppStore from '@/store/modules/app'
import { useRoute, useRouter } from 'vue-router'
const route = useRoute()
const router = useRouter()
const title = import.meta.env.VITE_APP_TITLE
const userStore = useUserStore()
const appStore = useAppStore()
const { proxy } = getCurrentInstance()
const loginForm = ref({
......@@ -106,38 +110,123 @@ function handleLogin() {
proxy.$refs.loginRef.validate(valid => {
if (valid) {
loading.value = true
// 勾选了需要记住密码设置在 cookie 中设置记住用户名和密码
if (loginForm.value.rememberMe) {
Cookies.set("username", loginForm.value.username, { expires: 30 })
Cookies.set("password", encrypt(loginForm.value.password), { expires: 30 })
Cookies.set("rememberMe", loginForm.value.rememberMe, { expires: 30 })
} else {
// 否则移除
Cookies.remove("username")
Cookies.remove("password")
Cookies.remove("rememberMe")
}
// // 勾选了需要记住密码设置在 cookie 中设置记住用户名和密码
// if (loginForm.value.rememberMe) {
// Cookies.set("username", loginForm.value.username, { expires: 30 })
// Cookies.set("password", encrypt(loginForm.value.password), { expires: 30 })
// Cookies.set("rememberMe", loginForm.value.rememberMe, { expires: 30 })
// } else {
// // 否则移除
// Cookies.remove("username")
// Cookies.remove("password")
// Cookies.remove("rememberMe")
// }
// 调用action的登录方法
userStore.login(loginForm.value).then(() => {
const query = route.query
const otherQueryParams = Object.keys(query).reduce((acc, cur) => {
if (cur !== "redirect") {
acc[cur] = query[cur]
userStore.login(loginForm.value).then(async (res) => {
console.log('res123',res)
let data = res
let menus = data.menus.map(item => {
item.children = item.submenu.map(child => {
return {
children: [],
iconCls: child.icon,
id: child.id,
keepAlive: false,
name: child.menuname,
path: child.url,
requireAuth: false
}
})
return {
children: item.children,
name: item.menuname,
id: item.id,
path: item.url,
type: item.type,
iconCls: item.icon
}
})
// 用户数据整理
let user = data.tsysUser;
let loginBlankMenus = false
if(data.menus.length === 0) {
loginBlankMenus = true
}
return acc
}, {})
router.push({ path: redirect.value || "/", query: otherQueryParams })
try {
await appStore.signOut('login')
// 设置导航菜单
appStore.setNav({ menus: data.menus })
// 设置用户信息
appStore.setUserInfo({user, licenseRst: data.licenseRst, loginBlankMenus})
// 存储token和登录信息
window.sessionStorage.setItem('__token', data.token)
const loginStr = JSON.stringify({
username: loginForm.value.username,
password: encryptPassword(loginForm.value.password)
})
window.sessionStorage.setItem('login', loginStr)
const query = route.query
const otherQueryParams = Object.keys(query).reduce((acc, cur) => {
if (cur !== "redirect") {
acc[cur] = query[cur]
}
return acc
}, {})
console.log('路由配置:', router.getRoutes())
router.push({ path:"/index", query: otherQueryParams })
// router.push({ path: redirect.value || "/index", query: otherQueryParams })
// if(redirect.value){
// console.log('redirect',redirect.value)
// router.push({ path: redirect.value, query: otherQueryParams })
// router.push({ path: redirect.value, query: otherQueryParams })
// console.log(2134)
// }else{
// router.push({ path: "/", query: otherQueryParams })
// }
// console.log('redirect',redirect.value)
// 跳转页面或其他操作
// router.push('/dashboard')
} catch (error) {
console.error('登录处理失败:', error)
}
}).catch(() => {
loading.value = false
// 重新获取验证码
if (captchaEnabled.value) {
getCode()
// getCode()
}
})
}
})
}
function encryptPassword(plainPassword) {
const sm4Config = {
key: "GJstSK_YBD=gSOFT",
mode: 'ecb',
cipherType: 'base64'
}
const sm4 = new SM4(sm4Config)
try {
return sm4.encrypt(plainPassword)
} catch (error) {
console.error('加密失败:', error)
throw new Error('密码加密处理失败')
}
}
function getCode() {
getCodeImg().then(res => {
captchaEnabled.value = res.captchaEnabled === undefined ? true : res.captchaEnabled
......@@ -159,8 +248,8 @@ function getCookie() {
}
}
getCode()
getCookie()
// getCode()
// getCookie()
</script>
<style lang='scss' scoped>
......
......@@ -117,7 +117,7 @@ const handleEnterProject = (project) => {
console.log('进入项目:', project)
// // ElMessage.success(`进入项目 ${project.projectName}`)
// emit('page', 'detail', { projectId: project.id})
router.push({ path:'/project',query: { projectId: project.id } })
router.push({ path:'/projectHome',query: { projectId: project.id } })
}
......
<script setup name="Algorithm">
import { onMounted, ref, toRefs } from 'vue'
import { Split } from 'view-ui-plus';
import CollapseView from '@/components/CollapseView/index.vue'
import formModule from './modules/formModule.vue'
import ModalPop from "@/components/EditPop/ModalPop.vue"
const splitNum = ref(0.31) // 左右分割比例
const collapseList = ref([])
const data = reactive({
formEdit: {
name: "",
},
formAdd: {
name: "",
}
});
const { formEdit, formAdd } = toRefs(data);
const modalData = reactive({
show: false,
text: '',
icon: 'error',
cancel: true
})
const modalPopShow = ref(false)
// 获取数据
const getCollapse = () => {
const data = [
{
name: '测试数据域',
list: [
{
name: 'aaaaa'
},
{
name: 'bbbbb'
}
]
},
{
name: '通用规则',
list: [
{
name: 'aaaaa'
},
{
name: 'bbbbb'
}
]
},
{
name: '解密数据',
list: []
}
]
collapseList.value = data.map(item => {
const list = item.list.map(itemTwo => {
return {
...itemTwo,
isCheck: false,
isDelete: true,
}
})
return {
...item,
list: list,
isAdd: true,
isView: false,
isDelete: false,
}
})
}
// 新增算法关闭
const modalPopCancel = () => {
modalPopShow.value = false
}
// 新增算法
const collapseAdd = (item) => {
console.log('添加事件', item)
formAdd.value = {}
modalPopShow.value = true
}
// 删除事件
const collapseDelete = (item) => {
console.log('删除事件', item)
modalData.show = true
modalData.icon = 'error'
modalData.text = '删除后无法恢复,是否确认删除[' + item.name + ']?'
}
// 删除回调
const modalConfirm = () => {
modalData.show = false
}
// 点击监听
const collapseChange = (item) => {
formEdit.value = item
}
// 算法确认
const formModuleConfirm = (item) => {
console.log('新增算法', item)
modalPopShow.value = false
}
onMounted(() => {
getCollapse()
})
</script>
<template>
<div class="app-container scroller">
<PageTitle>
<template #title>
脱敏算法
</template>
</PageTitle>
<div class="app-container__body">
<Split v-model="splitNum">
<template #left>
<div class="demo-split-pane" style="padding: 0 38px 10px 0;width: 100%;overflow: auto;height: 100%;">
<el-input class="mb20" placeholder="脱敏规则搜索">
<template #suffix>
<el-icon style="vertical-align: middle;">
<search />
</el-icon>
</template>
</el-input>
<CollapseView :list="collapseList" @add="collapseAdd" @childDelete="collapseDelete" @change="collapseChange" />
</div>
</template>
<template #right>
<div class="demo-split-pane">
<div class="right">
<div class="right-title">
<el-icon>
<info-filled />
</el-icon>
<span style="margin-left: 5px;">脱敏规则</span>
</div>
<div class="right-content">
<formModule v-model="formEdit" type="edit" />
</div>
</div>
</div>
</template>
</Split>
</div>
<!-- 提示框 -->
<Modal v-model="modalData.show" :icon="modalData.icon" :cancel="modalData.cancel" :text="modalData.text"
@confirm="modalConfirm"></Modal>
<!-- 新增算法 -->
<ModalPop v-model="modalPopShow" title="编辑器" @cancel="modalPopCancel">
<template #content>
<formModule v-model="formAdd" type="add" @cancel="modalPopCancel" @confirm="formModuleConfirm" />
</template>
</ModalPop>
</div>
</template>
<style lang="scss" scoped>
.app-container__body {
height: calc(
100vh - var(--navbar-height) - var(--container-pd) - var(--container-pd)
) !important;
.right {
padding: 6px 10px 10px;
height: 100%;
&-title {
padding: 0px 0px 10px 10px;
display: flex;
align-items: center;
font-weight: 700;
color: #515a6e;
}
&-content {
padding: 10px 0px;
width: 80%;
margin: 20px auto;
.openEditor {
position: absolute;
bottom: 0;
right: 0;
}
}
&-btn {
padding: 20px 20px 20px 100px;
display: flex;
justify-content: center;
}
}
}
</style>
\ No newline at end of file
<script setup lang="ts" name="Form">
import { onMounted, reactive, ref, toRefs, watch } from "vue";
import { Switch } from "view-ui-plus";
import ExpressionEditor from '@/components/EditPop/ExpressionEditor.vue'
const props = defineProps<{
modelValue: boolean;
type: string;
}>();
const emit = defineEmits(["update:modelValue", 'cancel','confirm']);
const data = reactive({
rules: {},
});
const { rules } = toRefs(data);
const editorShow = ref(false);
const readOnly = ref(true);
const editor = ref({});
watch(
() => props.type,
(newVal) => {
readOnly.value = props.type === 'edit' ? true : false;
},
{ deep: true, immediate: true }
);
// 打开编辑器
const openEditor = () => {
editorShow.value = true
editor.value = props.modelValue
}
// 取消
const cancel = () => {
if (props.type === 'edit') {
readOnly.value = true
}else if (props.type === 'add') {
emit('cancel')
}
}
const confirm = () => {
if (props.type === 'edit') {
readOnly.value = true
}
emit('confirm')
}
// 编辑器确认
const formConfirm = (val) => {
editorShow.value = false
props.modelValue.name = val
}
</script>
<template>
<div>
<el-form ref="formRef" :model="modelValue" label-width="100px" :disabled="readOnly">
<el-form-item label="算法名称" required>
<el-input v-model="modelValue.name"></el-input>
</el-form-item>
<el-form-item label="表达式" required>
<el-input type="textarea" rows="8" v-model="modelValue.name"></el-input>
<el-button class="openEditor" type="primary" @click="openEditor">打开编辑器</el-button>
</el-form-item>
<el-form-item label="默认规则">
<Switch :disabled="readOnly">
<template #open>
<span></span>
</template>
<template #close>
<span></span>
</template>
</Switch>
</el-form-item>
</el-form>
<div class="btn">
<el-button type="primary" style="width: 150px;" @click="readOnly = false" v-if="readOnly">编辑</el-button>
<el-button type="info" style="width: 150px;" @click="cancel" v-if="!readOnly">取消</el-button>
<el-button type="primary" style="width: 150px;" @click="confirm" v-if="!readOnly">确认</el-button>
</div>
<ExpressionEditor v-model="editorShow" :data="editor" @cancel=" editorShow = false" @confirm="formConfirm" />
</div>
</template>
<style lang="scss" scoped>
.openEditor {
position: absolute;
bottom: 0;
right: 0;
}
.btn {
padding: 20px 20px 20px 100px;
display: flex;
justify-content: center;
}
</style>
\ No newline at end of file
++ "b/src/views/ruleConfig/Algorithm/\350\204\261\346\225\217\347\256\227\346\263\225.md"
<script setup name="DiscoveryRule">
import { onMounted, ref, toRefs } from 'vue'
</script>
<template>
<div class="app-container scroller">
<PageTitle>
<template #title>
发现规则
</template>
</PageTitle>
<div class="app-container__body"></div>
</div>
</template>
<style lang="scss" scoped>
.app-container__body {
height: calc(
100vh - var(--navbar-height) - var(--container-pd) - var(--container-pd)
) !important;
}
</style>
\ No newline at end of file
++ "b/src/views/ruleConfig/discoveryRule/\345\217\221\347\216\260\350\247\204\345\210\231.md"
......@@ -252,7 +252,7 @@ const upload = reactive({
// 是否更新已经存在的用户数据
updateSupport: 0,
// 设置上传的请求头部
headers: { Authorization: "Bearer " + getToken() },
headers: { Token: getToken() },
// 上传的地址
url: import.meta.env.VITE_APP_BASE_API + "/system/user/importData"
})
......
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论