登录
BIN
frontend/src/assets/system/nav1.png
Normal file
After Width: | Height: | Size: 741 B |
BIN
frontend/src/assets/system/nav2.png
Normal file
After Width: | Height: | Size: 525 B |
BIN
frontend/src/assets/system/nav3.png
Normal file
After Width: | Height: | Size: 443 B |
BIN
frontend/src/assets/system/nav4.png
Normal file
After Width: | Height: | Size: 470 B |
BIN
frontend/src/assets/system/navpermission.png
Normal file
After Width: | Height: | Size: 539 B |
BIN
frontend/src/assets/system/password.png
Normal file
After Width: | Height: | Size: 268 B |
BIN
frontend/src/assets/system/splitline1.png
Normal file
After Width: | Height: | Size: 124 B |
BIN
frontend/src/assets/system/splitline2.png
Normal file
After Width: | Height: | Size: 120 B |
BIN
frontend/src/assets/system/titleline.png
Normal file
After Width: | Height: | Size: 234 B |
BIN
frontend/src/assets/system/username.png
Normal file
After Width: | Height: | Size: 332 B |
@ -59,14 +59,14 @@
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<el-card class="action-card" @click="$router.push('/roles')">
|
||||
<el-card class="action-card" @click="routerclick('/UserLogin')">
|
||||
<div class="action-content">
|
||||
<el-icon class="action-icon"><UserFilled /></el-icon>
|
||||
<span>角色管理</span>
|
||||
</div>
|
||||
</el-card>
|
||||
|
||||
<el-card class="action-card" @click="$router.push('/organizations')">
|
||||
<el-card class="action-card" >
|
||||
<div class="action-content">
|
||||
<el-icon class="action-icon"><OfficeBuilding /></el-icon>
|
||||
<span>组织管理</span>
|
||||
@ -185,12 +185,20 @@ onMounted(() => {
|
||||
loadStats()
|
||||
})
|
||||
function routerclick(path) {
|
||||
if(path == '/Permission'){
|
||||
router.push({
|
||||
path: path,
|
||||
query: {
|
||||
id: '1927554158852841473'
|
||||
}
|
||||
})
|
||||
}else if(path == '/UserLogin'){
|
||||
const route = router.resolve({
|
||||
path: path,
|
||||
query: { id: '1927554158852841473' ,name:'测试项目'}
|
||||
});
|
||||
window.open(route.href, '_blank');
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
|
@ -107,6 +107,24 @@ const routes:any = [
|
||||
title: '权限管理',
|
||||
requiresAuth: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/UserLogin',
|
||||
name: 'UserLogin',
|
||||
component: () => import('@/views/system/userlogin/login_container.vue'),
|
||||
meta: {
|
||||
title: '登录',
|
||||
requiresAuth: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: '/UserNavbar',
|
||||
name: 'UserNavbar',
|
||||
component: () => import('@/views/system/userlogin/appframe_container.vue'),
|
||||
meta: {
|
||||
title: '导航',
|
||||
requiresAuth: true
|
||||
}
|
||||
}
|
||||
]
|
||||
|
||||
|
@ -64,7 +64,7 @@ const submitForm = (formEl: FormInstance | undefined) => {
|
||||
preventcombo.value = true
|
||||
if(ruleForm.value.id !== ''){
|
||||
updateOrganizationById(ruleForm.value).then((res:any) => {
|
||||
if(res &&res.data.code === '0'){
|
||||
if(res &&res.code === '0'){
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '修改成功',
|
||||
@ -85,7 +85,7 @@ const submitForm = (formEl: FormInstance | undefined) => {
|
||||
})
|
||||
}else{
|
||||
addOrganization(ruleForm.value).then((res:any) => {
|
||||
if(res && res.data.code === '0'){
|
||||
if(res && res.code === '0'){
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
message: '新增成功',
|
||||
@ -187,7 +187,6 @@ function gettree(){
|
||||
orgName:''
|
||||
}
|
||||
getOrganizations(params).then((res:any) => {
|
||||
console.log(res,8798798)
|
||||
treeData.value = res.data
|
||||
treeloading.value = false
|
||||
})
|
||||
@ -374,13 +373,9 @@ function depthandleClose() {
|
||||
deptFormRef.value?.resetFields();
|
||||
deptdialog.value = false
|
||||
}
|
||||
function formatDateTime(dateArray:any){
|
||||
if (!Array.isArray(dateArray) || dateArray.length < 6) return 'Invalid Date'
|
||||
|
||||
const pad = (n: number) => n.toString().padStart(2, '0')
|
||||
|
||||
return `${dateArray[0]}-${pad(dateArray[1])}-${pad(dateArray[2])} ` +
|
||||
`${pad(dateArray[3])}:${pad(dateArray[4])}:${pad(dateArray[5])}`
|
||||
function formatDateTime(time:any){
|
||||
let converted = time.replace("T", " ");
|
||||
return converted
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
|
@ -208,13 +208,9 @@ function handleClose() {
|
||||
ruleFormRef.value?.resetFields();
|
||||
dialogVisible.value = false;
|
||||
}
|
||||
function formatDateTime(dateArray:any){
|
||||
if (!Array.isArray(dateArray) || dateArray.length < 6) return 'Invalid Date'
|
||||
|
||||
const pad = (n: number) => n.toString().padStart(2, '0')
|
||||
|
||||
return `${dateArray[0]}-${pad(dateArray[1])}-${pad(dateArray[2])} ` +
|
||||
`${pad(dateArray[3])}:${pad(dateArray[4])}:${pad(dateArray[5])}`
|
||||
function formatDateTime(time:any){
|
||||
let converted = time.replace("T", " ");
|
||||
return converted
|
||||
}
|
||||
function setisValid(row:any){
|
||||
tableloading.value = true
|
||||
|
@ -99,8 +99,8 @@ onMounted(() => {
|
||||
function gettree(){
|
||||
treeloading.value = true
|
||||
getOrganizationById(props.applicationId).then((res:any) => {
|
||||
if(res.data.data && res.data.data.length>0){
|
||||
treeData.value = gettreeData(res.data.data)
|
||||
if(res.data && res.data.length>0){
|
||||
treeData.value = gettreeData(res.data)
|
||||
}else{
|
||||
treeData.value= []
|
||||
}
|
||||
@ -140,8 +140,8 @@ function getuserinfo(){
|
||||
size:10
|
||||
}
|
||||
queryUsers(params).then((res:any) => {
|
||||
tableData.value = res.data.data.records
|
||||
total.value = Number(res.data.data.total)
|
||||
tableData.value = res.data.records
|
||||
total.value = Number(res.data.total)
|
||||
tableloading.value = false
|
||||
})
|
||||
}
|
||||
@ -155,8 +155,8 @@ function queryuserinfo(){
|
||||
size:pageSize.value
|
||||
}
|
||||
queryUsers(params).then((res:any) => {
|
||||
tableData.value = res.data.data.records
|
||||
total.value = Number(res.data.data.total)
|
||||
tableData.value = res.data.records
|
||||
total.value = Number(res.data.total)
|
||||
tableloading.value = false
|
||||
})
|
||||
}
|
||||
@ -259,12 +259,9 @@ function handleCurrentChange(val: number){
|
||||
currentPage.value = val
|
||||
queryuserinfo()
|
||||
}
|
||||
function formatDateTime(dateArray:any){
|
||||
if (!Array.isArray(dateArray) || dateArray.length < 6) return 'Invalid Date'
|
||||
const pad = (n: number) => n.toString().padStart(2, '0')
|
||||
|
||||
return `${dateArray[0]}-${pad(dateArray[1])}-${pad(dateArray[2])} ` +
|
||||
`${pad(dateArray[3])}:${pad(dateArray[4])}:${pad(dateArray[5])}`
|
||||
function formatDateTime(time:any){
|
||||
let converted = time.replace("T", " ");
|
||||
return converted
|
||||
}
|
||||
function setisValid(row:any){
|
||||
const params = {
|
||||
|
@ -1,8 +1,8 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted } from 'vue'
|
||||
import { updateUser } from '@/api/permission/user'
|
||||
import { updateUser } from '@/api/data-visualization/manage/user'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { useCache } from '@/hooks/web/useCache'
|
||||
import { useCache } from '@/data-visualization/hooks/web/useCache'
|
||||
const { wsCache } = useCache()
|
||||
const props = defineProps({
|
||||
applicationId: String,
|
||||
@ -20,7 +20,7 @@ onMounted(() => {
|
||||
})
|
||||
function saveinfo() {
|
||||
//保存
|
||||
updateUser(userlist.value).then(res => {
|
||||
updateUser(userlist.value).then((res:any) => {
|
||||
if(res.data.code == '0'){
|
||||
ElMessage({
|
||||
type: 'success',
|
||||
|
@ -0,0 +1,217 @@
|
||||
<template>
|
||||
<div class="editor">
|
||||
<div ref="previewContainer" class="preview"></div>
|
||||
</div>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref, onMounted, onUnmounted } from 'vue'
|
||||
import { loadModule } from 'vue3-sfc-loader'
|
||||
import * as Vue from 'vue/dist/vue.esm-bundler.js'
|
||||
import * as ElementPlus from 'element-plus'
|
||||
import 'element-plus/dist/index.css'
|
||||
import less from 'less'
|
||||
import * as VueRouter from 'vue-router'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { i18n } from '@/data-visualization/plugins/vue-i18n'
|
||||
import defaultTemplate from '@/views/system/userlogin/frame.vue?raw'
|
||||
// import { moduleUpdate, moduleById } from '@/api/application/module'
|
||||
import { useCache } from '@/data-visualization/hooks/web/useCache'
|
||||
import { getMenuTree } from '@/api/permission/menu'
|
||||
import { moduleList } from '@/api/application/module'
|
||||
import { any } from 'vue-types'
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
const sfcCode = ref(defaultTemplate)
|
||||
const previewContainer = ref(null)
|
||||
let prevApp:any = null
|
||||
const projectName:any = ref('')
|
||||
const applicationId:any = ref('')
|
||||
const menuList:any= ref([])
|
||||
// 增强的Base64转换函数
|
||||
const convertToBase64 = async (imagePath:any) => {
|
||||
try {
|
||||
// 处理路径别名
|
||||
const resolvedPath = imagePath.replace('@/', '/src/')
|
||||
const response = await fetch(resolvedPath)
|
||||
if (!response.ok) throw new Error(`图片加载失败: ${response.status}`)
|
||||
const blob = await response.blob()
|
||||
return new Promise((resolve) => {
|
||||
const reader = new FileReader()
|
||||
reader.onloadend = () => resolve(reader.result)
|
||||
reader.readAsDataURL(blob)
|
||||
})
|
||||
} catch (error) {
|
||||
console.error('Base64转换失败:', error.message)
|
||||
return ''
|
||||
}
|
||||
}
|
||||
const runCode = async () => {
|
||||
try {
|
||||
if (prevApp) {
|
||||
prevApp._container.innerHTML = ''
|
||||
prevApp.unmount()
|
||||
prevApp._container.remove()
|
||||
prevApp = null
|
||||
await Vue.nextTick()
|
||||
}
|
||||
const options = {
|
||||
moduleCache: {
|
||||
vue: Vue,
|
||||
'element-plus': ElementPlus,
|
||||
'vue/dist/vue.esm-bundler.js': Vue,
|
||||
'vue-router': VueRouter,
|
||||
// '@/viewsnew/application/SfcEditor/NavbarEditor/assocPage.vue': Vue.defineAsyncComponent(() =>
|
||||
// import('@/viewsnew/application/SfcEditor/NavbarEditor/assocPage.vue')
|
||||
// ),
|
||||
// '@/viewsnew/application/permissionset/index.vue': Vue.defineAsyncComponent(() =>
|
||||
// import('@/viewsnew/application/permissionset/index.vue')
|
||||
// ),
|
||||
// '@/viewsnew/application/permissionset/user/userinfo.vue': Vue.defineAsyncComponent(() =>
|
||||
// import('@/viewsnew/application/permissionset/user/userinfo.vue')
|
||||
// ),
|
||||
'@/data-visualization/hooks/web/useCache': { useCache }
|
||||
},
|
||||
getFile: async (fileName) => {
|
||||
if (fileName === 'dynamic.vue') {
|
||||
let code = sfcCode.value
|
||||
|
||||
// 增强的Less处理(支持scoped)
|
||||
const lessRegex = /<style\s+.*?lang="less".*?>(.*?)<\/style>/gis
|
||||
let match
|
||||
while ((match = lessRegex.exec(code)) !== null) {
|
||||
const [full, content] = match
|
||||
try {
|
||||
const { css } = await less.render(content, {
|
||||
paths: ['.'], // 设置解析路径
|
||||
filename: 'dynamic.less'
|
||||
})
|
||||
code = code.replace(full, `<style>${css}</style>`)
|
||||
} catch (e) {
|
||||
console.error('Less编译错误:', e.message)
|
||||
code = code.replace(full, `<style>/* Less Error: ${e.message} */</style>`)
|
||||
}
|
||||
}
|
||||
|
||||
// 处理图片资源(增强路径处理)
|
||||
const imgRegex = /src=['"](.+?\.(png|jpg|jpeg))['"]/gi
|
||||
let imgMatch
|
||||
while ((imgMatch = imgRegex.exec(code)) !== null) {
|
||||
const [full, path] = imgMatch
|
||||
const base64 = await convertToBase64(path)
|
||||
code = code.replace(full, `src="${base64}"`)
|
||||
}
|
||||
|
||||
return code
|
||||
.replace(/<\/script>/g, '<\\/script>')
|
||||
.replace(/\\\//g, '/')
|
||||
}
|
||||
return ''
|
||||
},
|
||||
//样式
|
||||
addStyle: (css) => {
|
||||
const style = document.createElement('style')
|
||||
style.textContent = css
|
||||
document.head.appendChild(style)
|
||||
},
|
||||
compiledCache: new Map(),
|
||||
compileTemplate: true
|
||||
}
|
||||
const componentModule = await loadModule('dynamic.vue', options)
|
||||
const component = componentModule.default || componentModule
|
||||
const dynamicProps = Vue.reactive({
|
||||
menuList: props.menuList,
|
||||
isFixed: props.isFixed,
|
||||
projectName: props.projectName,
|
||||
applicationId: props.applicationId,
|
||||
isExecuteEvent: props.isExecuteEvent
|
||||
})
|
||||
// 修改createApp方式
|
||||
prevApp = Vue.createApp({
|
||||
render: () => Vue.h(component, {
|
||||
...dynamicProps,
|
||||
router: router,
|
||||
route: route
|
||||
})
|
||||
})
|
||||
prevApp.use(router)
|
||||
prevApp.use(route)
|
||||
prevApp.use(i18n)
|
||||
prevApp.use(ElementPlus)
|
||||
prevApp.mount(previewContainer.value)
|
||||
|
||||
} catch (error) {
|
||||
console.error('运行时错误:', error)
|
||||
previewContainer.value.innerHTML = `
|
||||
<div class="error">
|
||||
<h3>错误</h3>
|
||||
<pre>${error.message}</pre>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
}
|
||||
const processMenuTree = (menuNodes: any[], moduleData: any[]) => {
|
||||
const typeMap = new Map(
|
||||
moduleData.map(({ id, type }) => [id, type])
|
||||
);
|
||||
const traverseMenu = (nodes: any[]) => {
|
||||
for (const node of nodes) {
|
||||
if (node.module_id && typeMap.has(node.module_id)) {
|
||||
node.module_type = typeMap.get(node.module_id);
|
||||
}
|
||||
if (node.children?.length) {
|
||||
traverseMenu(node.children);
|
||||
}
|
||||
}
|
||||
};
|
||||
traverseMenu(menuNodes);
|
||||
return menuNodes;
|
||||
};
|
||||
onMounted(() => {
|
||||
if (route.query.id) {
|
||||
applicationId.value = route.query.id
|
||||
projectName.value = route.query.name
|
||||
getmenuinfo()
|
||||
}
|
||||
})
|
||||
function getmenuinfo() {
|
||||
const params = {
|
||||
appId: applicationId.value,
|
||||
name: '',
|
||||
isdisplay: ''
|
||||
}
|
||||
getMenuTree(params).then((res:any) => {
|
||||
menuList.value = res.data
|
||||
const paramss = { appId: applicationId.value }
|
||||
moduleList(paramss).then((ress:any) => {
|
||||
var arr = ress.data.data
|
||||
menuList.value = processMenuTree(menuList.value, arr)
|
||||
})
|
||||
})
|
||||
|
||||
}
|
||||
</script>
|
||||
<style>
|
||||
/* 保留原有样式并补充 */
|
||||
.editor {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
textarea {
|
||||
flex: 1;
|
||||
margin-bottom: 10px;
|
||||
font-family: Monaco, monospace;
|
||||
padding: 10px;
|
||||
}
|
||||
|
||||
.preview {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
flex: 1;
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,325 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch, onMounted, onBeforeUnmount, shallowRef } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
// import Assocmodule from '@/viewsnew/application/SfcEditor/NavbarEditor/assocPage.vue'
|
||||
// import PermissionSet from '@/viewsnew/application/permissionset/index.vue'
|
||||
// import UserInfoSet from '@/viewsnew/application/permissionset/user/userinfo.vue'
|
||||
import {useRoute, useRouter } from 'vue-router'
|
||||
import { useCache } from '@/hooks/web/useCache'
|
||||
const { wsCache } = useCache()
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const props = defineProps({
|
||||
menuList: {
|
||||
type: Array, // 根据实际数据结构调整
|
||||
required: true, // 强制必须传递
|
||||
default: () => [] // 设置安全默认值
|
||||
},
|
||||
isFixed: Boolean,
|
||||
projectName: String,
|
||||
applicationId: String,
|
||||
isExecuteEvent: Boolean
|
||||
})
|
||||
const navtitle:any = ref('')
|
||||
const navmenulist: any = ref([
|
||||
])
|
||||
const checkindex = ref(null)
|
||||
const showmodule = ref(false)
|
||||
const currentMoudleId = ref('')
|
||||
const showermission = ref(false)
|
||||
const showUserInfo = ref(false)
|
||||
const currentrow = ref({})
|
||||
const userList:any = ref({})
|
||||
watch(
|
||||
() => props.menuList,
|
||||
(newVal) => {
|
||||
if (newVal) {
|
||||
navmenulist.value = newVal
|
||||
}
|
||||
},
|
||||
{ immediate: true } // 立即执行一次
|
||||
)
|
||||
function menuclick(index: any) {
|
||||
if (navmenulist.value[index].children.length == 0 || navmenulist.value[index].children == undefined) {
|
||||
checkindex.value = index
|
||||
if (props.isExecuteEvent) {
|
||||
return
|
||||
}
|
||||
currentMoudleId.value = navmenulist.value[index].module_id
|
||||
if (navmenulist.value[index].module_id && navmenulist.value[index].module_id !== '') {
|
||||
currentMoudleId.value = navmenulist.value[index].module_id
|
||||
currentrow.value = navmenulist.value[index]
|
||||
showmodule.value = true
|
||||
showermission.value = false
|
||||
showUserInfo.value = false
|
||||
}
|
||||
}
|
||||
}
|
||||
function childmenuclick(item: any) {
|
||||
if (props.isExecuteEvent) {
|
||||
return
|
||||
}
|
||||
showermission.value = false
|
||||
showmodule.value = false
|
||||
showUserInfo.value = false
|
||||
if (item.module_id && item.module_id !== '') {
|
||||
currentrow.value = item
|
||||
currentMoudleId.value = item.module_id
|
||||
showmodule.value = true
|
||||
} else {
|
||||
showmodule.value = false
|
||||
}
|
||||
}
|
||||
// 时间相关逻辑移动到独立组件中
|
||||
const TimeDisplay = {
|
||||
template: `
|
||||
<div class="Navbar-menu-time">
|
||||
<div class="Navbar-menu-time-text">{{ currentTime }}</div>
|
||||
<div class="Navbar-menu-time-text2">{{ currentDate }}</div>
|
||||
</div>
|
||||
`,
|
||||
setup() {
|
||||
const currentTime = shallowRef('')
|
||||
const currentDate = shallowRef('')
|
||||
let timer: number
|
||||
|
||||
function updateTime() {
|
||||
const now = new Date();
|
||||
const hours = String(now.getHours()).padStart(2, '0');
|
||||
const minutes = String(now.getMinutes()).padStart(2, '0');
|
||||
const seconds = String(now.getSeconds()).padStart(2, '0');
|
||||
const year = now.getFullYear();
|
||||
const month = String(now.getMonth() + 1).padStart(2, '0');
|
||||
const day = String(now.getDate()).padStart(2, '0');
|
||||
const weekdays = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'];
|
||||
const weekday = weekdays[now.getDay()];
|
||||
currentTime.value = `${hours}:${minutes}:${seconds}`;
|
||||
currentDate.value = `${year}-${month}-${day} ${weekday}`;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
updateTime()
|
||||
timer = setInterval(updateTime, 1000)
|
||||
})
|
||||
|
||||
onBeforeUnmount(() => {
|
||||
clearInterval(timer)
|
||||
})
|
||||
|
||||
return { currentTime, currentDate }
|
||||
}
|
||||
}
|
||||
onMounted(() => {
|
||||
navmenulist.value = props.menuList
|
||||
navtitle.value = props.projectName
|
||||
userList.value = wsCache.get('Permission-userinfo')
|
||||
})
|
||||
onBeforeUnmount(() => {
|
||||
|
||||
});
|
||||
function permissionClick() {
|
||||
if (props.isExecuteEvent) {
|
||||
return
|
||||
}
|
||||
showmodule.value = false
|
||||
showUserInfo.value = false
|
||||
showermission.value = true
|
||||
}
|
||||
function logout(){
|
||||
ElMessageBox.confirm(
|
||||
'确定退出登录?',
|
||||
'提示',
|
||||
{
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning',
|
||||
}
|
||||
)
|
||||
.then(() => {
|
||||
wsCache.delete('Permission-userinfo')
|
||||
router.push({
|
||||
path: '/SystemLogin',
|
||||
query: { id: props.applicationId, name: props.projectName }
|
||||
})
|
||||
})
|
||||
.catch(() => {})
|
||||
}
|
||||
function userdetails(){
|
||||
showmodule.value = false
|
||||
showermission.value = false
|
||||
showUserInfo.value = true
|
||||
}
|
||||
</script>
|
||||
<template>
|
||||
<div class="system-box">
|
||||
<div class="Navbar-box">
|
||||
<div class="Navbar-box-titie">
|
||||
<div class="Navbar-box-titie-text">
|
||||
{{ navtitle }}
|
||||
</div>
|
||||
</div>
|
||||
<div class="Navbar-menu-box">
|
||||
<div class="Navbar-menu-content">
|
||||
<TimeDisplay />
|
||||
<div class="Navbar-menu-item"
|
||||
:class="{ 'Navbar-menu-items': checkindex == index && item.children.length == 0 }"
|
||||
v-for="(item, index) in navmenulist" :key="index" @click="menuclick(index)">
|
||||
<div v-if="!item.children || item.children.length == 0">{{ item.name }}</div>
|
||||
<el-popover v-else :show-arrow="false" class="tsmenu" placement="bottom" popper-class="tsmenu-popover">
|
||||
<div v-for="(items, indexs) in item.children" :key="indexs" class="Navbar-menu-item-child-item"
|
||||
@click="() => childmenuclick(items)">
|
||||
{{ items.name }}
|
||||
</div>
|
||||
<template #reference>
|
||||
<div v-if="item.children.length > 0">{{ item.name }}</div>
|
||||
</template>
|
||||
</el-popover>
|
||||
</div>
|
||||
</div>
|
||||
<div class="Navbar-box-right">
|
||||
<div style="margin-right: 10px;cursor: pointer;" @click="permissionClick"><img
|
||||
src="@/assets/img/navpermission.png" alt=""></div>
|
||||
<div style="margin-right: 10px;cursor: pointer;"><img src="@/assets/img/nav1.png" alt=""></div>
|
||||
<div style="margin-right: 15px;min-width: 45px;cursor: pointer;" @click="userdetails">{{ props.isExecuteEvent == false?userList.nickname:'admin' }}</div>
|
||||
<div style="margin-right: 15px;cursor: pointer;" @click="userdetails"><img src="@/assets/img/nav3.png" alt=""></div>
|
||||
<div @click="logout" style="cursor: pointer;"><img src="@/assets/img/nav4.png" alt=""></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="!props.isExecuteEvent" class="system-box-content"
|
||||
:style="{ height: props.isFixed ? 'calc(100vh - 65px)' : 'calc(100vh - 125px)' }">
|
||||
<!-- <Assocmodule v-if="showmodule" :applicationId="props.applicationId"
|
||||
:moduleinfo="currentrow" />
|
||||
<PermissionSet v-if="showermission" />
|
||||
<UserInfoSet v-if="showUserInfo" :userList="userList" :applicationId="props.applicationId" /> -->
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.system-box {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgb(0, 52, 102);
|
||||
|
||||
.Navbar-box {
|
||||
width: 100%;
|
||||
height: 65px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #fff;
|
||||
|
||||
.Navbar-box-titie {
|
||||
width: 400px;
|
||||
height: 65px;
|
||||
line-height: 65px;
|
||||
background: url(/images/navleft.png) no-repeat;
|
||||
background-size: 100% 100%;
|
||||
padding-left: 20px;
|
||||
padding-right: 15px;
|
||||
|
||||
.Navbar-box-titie-text {
|
||||
font-size: 25px;
|
||||
font-weight: 700;
|
||||
background: linear-gradient(to bottom, #ffffff, #0089ff);
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
color: transparent;
|
||||
}
|
||||
}
|
||||
|
||||
.Navbar-menu-box {
|
||||
width: calc(100% - 400px);
|
||||
height: 63px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: 20px;
|
||||
padding-right: 20px;
|
||||
justify-content: space-between;
|
||||
background: url(/images/navright.png) no-repeat;
|
||||
background-size: 100% 100%;
|
||||
|
||||
.Navbar-menu-content {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
.Navbar-menu-time {
|
||||
width: 150px;
|
||||
|
||||
.Navbar-menu-time-text {
|
||||
font-size: 16px;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.Navbar-menu-time-text2 {
|
||||
font-size: 13px;
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
|
||||
.Navbar-menu-item {
|
||||
font-size: 14px;
|
||||
padding: 5px 15px;
|
||||
background: url(/images/navmenu.png) no-repeat;
|
||||
background-size: 100% 100%;
|
||||
margin-left: 15px;
|
||||
cursor: pointer;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.Navbar-menu-items {
|
||||
background: url(/images/navmenucheck.png) no-repeat;
|
||||
background-size: 100% 100%;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.Navbar-box-right {
|
||||
width: 210px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
font-size: 14px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.system-box-content {
|
||||
width: 100%;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style>
|
||||
.tsmenu-popover.el-popover.el-popper {
|
||||
background: linear-gradient(to bottom, rgba(0, 52, 102, 1), rgba(0, 55, 110, 0.5));
|
||||
;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 0px;
|
||||
padding: 5px 0px;
|
||||
z-index: 9001 !important;
|
||||
}
|
||||
|
||||
.tsmenu-popover.ed-popover.ed-popper {
|
||||
background: linear-gradient(to bottom, rgba(0, 52, 102, 1), rgba(0, 55, 110, 0.5));
|
||||
;
|
||||
color: #fff;
|
||||
border: none;
|
||||
border-radius: 0px;
|
||||
padding: 5px 0px;
|
||||
z-index: 9001 !important;
|
||||
}
|
||||
|
||||
.tsmenu-popover .Navbar-menu-item-child-item {
|
||||
padding: 5px 15px;
|
||||
cursor: pointer;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.tsmenu-popover .Navbar-menu-item-child-item:hover {
|
||||
color: rgb(0, 255, 254);
|
||||
background: rgb(2, 88, 161);
|
||||
font-weight: 700;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,241 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch, onMounted, onBeforeUnmount, shallowRef } from 'vue'
|
||||
import type { FormInstance, FormRules } from 'element-plus'
|
||||
import { ElMessage } from 'element-plus'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import { userLogin } from '@/api/data-visualization/manage/user'
|
||||
import { useCache } from '@/data-visualization/hooks/web/useCache'
|
||||
const { wsCache } = useCache()
|
||||
const router = useRouter()
|
||||
const route = useRoute()
|
||||
const props = defineProps({
|
||||
id: String,
|
||||
name: String,
|
||||
isExecuteEvent: Boolean,
|
||||
})
|
||||
const form = ref({
|
||||
username: '',
|
||||
password: ''
|
||||
})
|
||||
const loginRules = ref({
|
||||
username: [
|
||||
{ required: true, message: '请输入用户名', trigger: 'blur' }
|
||||
],
|
||||
password: [
|
||||
{ required: true, message: '请输入密码', trigger: 'blur' }
|
||||
]
|
||||
})
|
||||
const ruleFormRef = ref<FormInstance>()
|
||||
const loginTitle: any = ref('')
|
||||
const submitForm = async (formEl: FormInstance | undefined) => {
|
||||
if (props.isExecuteEvent) {
|
||||
return
|
||||
}
|
||||
if (!formEl) return
|
||||
await formEl.validate((valid, fields) => {
|
||||
if (valid) {
|
||||
const params = {
|
||||
appid:props.id,
|
||||
username: form.value.username,
|
||||
password: form.value.password
|
||||
}
|
||||
userLogin(params).then((res:any) => {
|
||||
if(res.code == '0'){
|
||||
let userInfo = {
|
||||
nickname: res.data.nickname,
|
||||
username: res.data.username,
|
||||
id: res.data.id,
|
||||
phone: res.data.phone,
|
||||
email: res.data.email,
|
||||
}
|
||||
wsCache.set('Permission-userinfo', userInfo)
|
||||
ElMessage.success('登录成功')
|
||||
router.push({
|
||||
path: '/UserNavbar',
|
||||
query: { id: props.id, name: props.name }
|
||||
})
|
||||
}else{
|
||||
ElMessage.error(res.msg)
|
||||
return
|
||||
}
|
||||
})
|
||||
} else {
|
||||
|
||||
}
|
||||
})
|
||||
}
|
||||
onMounted(() => {
|
||||
loginTitle.value = props.name
|
||||
})
|
||||
onBeforeUnmount(() => {
|
||||
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<div class="login-box">
|
||||
<div class="login-container">
|
||||
<div class="login-title">
|
||||
<div class="login-title-left"><img src="@/assets/system/titleline.png" alt=""></div>
|
||||
<div class="login-title-text">{{ loginTitle }}</div>
|
||||
<div class="login-title-right"><img src="@/assets/system/titleline.png" alt=""></div>
|
||||
</div>
|
||||
<div class="login-form">
|
||||
<div class="login-form-title">欢迎登录</div>
|
||||
<el-form ref="ruleFormRef" :model="form" :rules="loginRules" label-width="0px" style="margin:0px 20px;">
|
||||
<el-form-item label="" prop="username">
|
||||
<div class="logininput">
|
||||
<el-input v-model="form.username" placeholder="请输入用户名">
|
||||
<template #prepend>
|
||||
<img src="@/assets/system/username.png" alt="">
|
||||
</template>
|
||||
</el-input>
|
||||
<img class="login-splitline" src="@/assets/system/splitline2.png"
|
||||
style="width:100%;height: 1px;" alt="">
|
||||
</div>
|
||||
</el-form-item>
|
||||
<el-form-item label="" prop="password">
|
||||
<div class="logininput">
|
||||
<el-input v-model="form.password" show-password placeholder="请输入密码" type="password">
|
||||
<template #prepend>
|
||||
<img src="@/assets/system/password.png" alt="">
|
||||
</template>
|
||||
</el-input>
|
||||
<img class="login-splitline" src="@/assets/system/splitline1.png"
|
||||
style="width:100%;height: 1px;" alt="">
|
||||
</div>
|
||||
</el-form-item>
|
||||
<div class="login-button" @click="submitForm(ruleFormRef)">登录</div>
|
||||
</el-form>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.login-box {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
background: url(/images/loginbg.png) no-repeat;
|
||||
background-size: 100% 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
padding-top: 8%;
|
||||
|
||||
.login-container {
|
||||
|
||||
.login-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
height: 60px;
|
||||
margin-bottom: 100px;
|
||||
|
||||
.login-title-left {
|
||||
transform: rotate(-180deg);
|
||||
margin-top: 10px;
|
||||
}
|
||||
|
||||
.login-title-text {
|
||||
text-align: center;
|
||||
font-size: 35px;
|
||||
font-weight: bold;
|
||||
color: #fff;
|
||||
margin: 0px 30px;
|
||||
background: linear-gradient(to bottom, #ffffff, #0089ff);
|
||||
-webkit-background-clip: text;
|
||||
background-clip: text;
|
||||
color: transparent;
|
||||
}
|
||||
|
||||
.login-title-right {
|
||||
margin-bottom: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
.login-form {
|
||||
width: 400px;
|
||||
height: 350px;
|
||||
margin: 0 auto;
|
||||
background: #fff;
|
||||
border-radius: 5px;
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
background: url(/images/loginform.png) no-repeat;
|
||||
background-size: 100% 100%;
|
||||
padding: 40px 20px;
|
||||
|
||||
.login-form-title {
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
color: #fff;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.logininput {
|
||||
position: relative;
|
||||
height: 40px;
|
||||
width: 100%;
|
||||
|
||||
.login-splitline {
|
||||
position: absolute;
|
||||
bottom: 0px;
|
||||
left: 0px;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.login-button {
|
||||
height: 40px;
|
||||
line-height: 40px;
|
||||
font-size: 16px;
|
||||
color: #fff;
|
||||
background: url(/images/loginbtn.png) no-repeat;
|
||||
background-size: 100% 100%;
|
||||
margin-top: 40px;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<style>
|
||||
.logininput .el-input__wrapper {
|
||||
background-color: transparent;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.logininput .el-input-group__prepend {
|
||||
box-shadow: none;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
.logininput .el-input__inner {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
.logininput .el-form-item.is-error .el-input-tag__wrapper,
|
||||
.el-form-item.is-error .el-input-tag__wrapper.is-focus,
|
||||
.el-form-item.is-error .el-input-tag__wrapper:focus,
|
||||
.el-form-item.is-error .el-input-tag__wrapper:hover,
|
||||
.el-form-item.is-error .el-input__wrapper,
|
||||
.el-form-item.is-error .el-input__wrapper.is-focus,
|
||||
.el-form-item.is-error .el-input__wrapper:focus,
|
||||
.el-form-item.is-error .el-input__wrapper:hover,
|
||||
.el-form-item.is-error .el-select__wrapper,
|
||||
.el-form-item.is-error .el-select__wrapper.is-focus,
|
||||
.el-form-item.is-error .el-select__wrapper:focus,
|
||||
.el-form-item.is-error .el-select__wrapper:hover,
|
||||
.el-form-item.is-error .el-textarea__inner,
|
||||
.el-form-item.is-error .el-textarea__inner.is-focus,
|
||||
.el-form-item.is-error .el-textarea__inner:focus,
|
||||
.el-form-item.is-error .el-textarea__inner:hover {
|
||||
box-shadow: none;
|
||||
border: none;
|
||||
box-shadow: none;
|
||||
}
|
||||
|
||||
.login-form .el-form-item__error {
|
||||
padding-top: 4px;
|
||||
}
|
||||
</style>
|
@ -0,0 +1,205 @@
|
||||
<script lang="ts" setup>
|
||||
import { ref, watch, onMounted, onBeforeUnmount } from 'vue'
|
||||
import { useRoute, useRouter } from 'vue-router'
|
||||
import * as VueRouter from 'vue-router'
|
||||
import { loadModule } from 'vue3-sfc-loader'
|
||||
import * as Vue from 'vue/dist/vue.esm-bundler.js'
|
||||
import { i18n } from '@/data-visualization/plugins/vue-i18n'
|
||||
import * as ElementPlus from 'element-plus'
|
||||
import less from 'less'
|
||||
import defaultTemplate from '@/views/system/userlogin/login.vue?raw'
|
||||
// import { moduleList, moduleById } from '@/api/application/module'
|
||||
import { userLogin } from '@/api/data-visualization/manage/user'
|
||||
import { useCache } from '@/data-visualization/hooks/web/useCache'
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
const sfcCode = ref(defaultTemplate)
|
||||
const previewContainer:any = ref(null)
|
||||
let prevApp:any = null
|
||||
// 增强的Base64转换函数
|
||||
const convertToBase64 = async (imagePath:any) => {
|
||||
try {
|
||||
// 处理路径别名
|
||||
const resolvedPath = imagePath.replace('@/', '/src/')
|
||||
const response = await fetch(resolvedPath)
|
||||
if (!response.ok) throw new Error(`图片加载失败: ${response.status}`)
|
||||
const blob = await response.blob()
|
||||
return new Promise((resolve) => {
|
||||
const reader = new FileReader()
|
||||
reader.onloadend = () => resolve(reader.result)
|
||||
reader.readAsDataURL(blob)
|
||||
})
|
||||
} catch (error:any) {
|
||||
console.error('Base64转换失败:', error.message)
|
||||
return ''
|
||||
}
|
||||
}
|
||||
const runCode = async () => {
|
||||
try {
|
||||
if (prevApp) {
|
||||
prevApp.unmount()
|
||||
previewContainer.value.innerHTML = ''
|
||||
}
|
||||
const options:any = {
|
||||
moduleCache: {
|
||||
vue: Vue,
|
||||
'element-plus': ElementPlus,
|
||||
'vue/dist/vue.esm-bundler.js': Vue,
|
||||
'vue-router': VueRouter,
|
||||
'@/api/data-visualization/manage/user': { userLogin },
|
||||
'@/data-visualization/hooks/web/useCache': { useCache }
|
||||
},
|
||||
getFile: async (fileName:any) => {
|
||||
if (fileName.startsWith('@/')) {
|
||||
const resolvedPath = fileName.replace('@/', '/src/')
|
||||
try {
|
||||
const response = await fetch(resolvedPath)
|
||||
return { content: await response.text() }
|
||||
} catch (e) {
|
||||
console.error(`文件加载失败: ${resolvedPath}`, e)
|
||||
return { content: '<!-- 文件加载失败 -->' }
|
||||
}
|
||||
}
|
||||
if (fileName === 'dynamic.vue') {
|
||||
let code = sfcCode.value
|
||||
// 增强的Less处理(支持scoped)
|
||||
const lessRegex = /<style\s+.*?lang="less".*?>(.*?)<\/style>/gis
|
||||
let match
|
||||
while ((match = lessRegex.exec(code)) !== null) {
|
||||
const [full, content] = match
|
||||
try {
|
||||
const { css } = await less.render(content, {
|
||||
paths: ['.'], // 设置解析路径
|
||||
filename: 'dynamic.less'
|
||||
})
|
||||
code = code.replace(full, `<style>${css}</style>`)
|
||||
} catch (e) {
|
||||
console.error('Less编译错误:', e.message)
|
||||
code = code.replace(full, `<style>/* Less Error: ${e.message} */</style>`)
|
||||
}
|
||||
}
|
||||
|
||||
// 处理图片资源(增强路径处理)
|
||||
const imgRegex = /src=['"](.+?\.(png|jpg|jpeg))['"]/gi
|
||||
let imgMatch
|
||||
while ((imgMatch = imgRegex.exec(code)) !== null) {
|
||||
const [full, path] = imgMatch
|
||||
const base64 = await convertToBase64(path)
|
||||
code = code.replace(full, `src="${base64}"`)
|
||||
}
|
||||
return code
|
||||
.replace(/<\/script>/g, '<\\/script>')
|
||||
.replace(/\\\//g, '/')
|
||||
}
|
||||
|
||||
// 处理外部Less文件(支持别名)
|
||||
if (fileName.endsWith('.less')) {
|
||||
const resolvedPath = fileName.replace('@/', '/src/')
|
||||
try {
|
||||
const response = await fetch(resolvedPath)
|
||||
const content = await response.text()
|
||||
const { css } = await less.render(content, { filename: fileName })
|
||||
return { content: css, mediaType: 'text/css' }
|
||||
} catch (e) {
|
||||
return { content: `/* Less Error: ${e.message} */`, mediaType: 'text/css' }
|
||||
}
|
||||
}
|
||||
|
||||
return ''
|
||||
},
|
||||
addStyle: (css:any) => {
|
||||
const style = document.createElement('style')
|
||||
style.textContent = css
|
||||
document.head.appendChild(style)
|
||||
|
||||
},
|
||||
compiledCache: new Map(),
|
||||
compileTemplate: true
|
||||
}
|
||||
const componentModule:any = await loadModule('dynamic.vue', options)
|
||||
const component = componentModule.default || componentModule
|
||||
const dynamicProps = ref({
|
||||
id: route.query.id,
|
||||
name: route.query.name,
|
||||
isExecuteEvent: false
|
||||
})
|
||||
// prevApp = Vue.createApp({
|
||||
// render: () => Vue.h(component, dynamicProps.value)
|
||||
// })
|
||||
prevApp = Vue.createApp({
|
||||
render: () => Vue.h(component, {
|
||||
...dynamicProps.value,
|
||||
router: router,
|
||||
route: route
|
||||
})
|
||||
})
|
||||
|
||||
// 显式提供路由实例
|
||||
prevApp.use(router)
|
||||
prevApp.use(ElementPlus)
|
||||
prevApp.use(i18n)
|
||||
prevApp.mount(previewContainer.value)
|
||||
|
||||
} catch (error:any) {
|
||||
console.error('运行时错误:', error)
|
||||
previewContainer.value.innerHTML = `
|
||||
<div class="error">
|
||||
<h3>错误</h3>
|
||||
<pre>${error.message}</pre>
|
||||
</div>
|
||||
`
|
||||
}
|
||||
}
|
||||
onMounted(() => {
|
||||
init()
|
||||
runCode()
|
||||
})
|
||||
function init() {
|
||||
// const paramss = { appId: route.query.id }
|
||||
// moduleList(paramss).then(ress => {
|
||||
// var arr = ress.data.data
|
||||
// let list: any = {}
|
||||
// arr.forEach((item: any) => {
|
||||
// if (item.type == '01' && item.node_type == '02') {
|
||||
// list = item
|
||||
// }
|
||||
// })
|
||||
// if (list.id) {
|
||||
// moduleById(list.id).then(res2 => {
|
||||
// if (res2.data.data.canvas_style_data) {
|
||||
// sfcCode.value = res2.data.data.canvas_style_data
|
||||
// runCode()
|
||||
// }
|
||||
// })
|
||||
// }else{
|
||||
// sfcCode.value = defaultTemplate
|
||||
// runCode()
|
||||
// }
|
||||
// })
|
||||
}
|
||||
onBeforeUnmount(() => {
|
||||
|
||||
});
|
||||
</script>
|
||||
<template>
|
||||
<div class="loginBox">
|
||||
<div ref="previewContainer" class="preview"></div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style lang="less" scoped>
|
||||
.loginBox {
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
}
|
||||
.no-login{
|
||||
width: 100%;
|
||||
height: 100vh;
|
||||
background-color: #151515;
|
||||
color: #787878;
|
||||
font-size: 20px;
|
||||
line-height: 100vh;
|
||||
text-align: center;
|
||||
font-weight: 700;
|
||||
}
|
||||
</style>
|