添加系统绑的功能

This commit is contained in:
guanj
2026-01-05 16:34:42 +08:00
parent 75987c0c6f
commit d25f16bcc7
13 changed files with 502 additions and 401 deletions

View File

@@ -2,7 +2,14 @@
<div> <div>
<!--指标越限时间分布 <!--指标越限时间分布
--> -->
<TableHeader :showReset="false" :timeKeyList="prop.timeKey" ref="TableHeaderRef" @selectChange="selectChange" datePicker v-if="fullscreen"> <TableHeader
:showReset="false"
:timeKeyList="prop.timeKey"
ref="TableHeaderRef"
@selectChange="selectChange"
datePicker
v-if="fullscreen"
>
<template v-slot:select> <template v-slot:select>
<el-form-item label="监测点"> <el-form-item label="监测点">
<el-select size="small" filterable v-model="tableStore.table.params.lineId"> <el-select size="small" filterable v-model="tableStore.table.params.lineId">
@@ -22,7 +29,7 @@
:options="echartList1" :options="echartList1"
:style="{ :style="{
width: prop.width, width: prop.width,
height: `calc(${prop.height} - ${headerHeight}px + ${fullscreen ? 0 : 56}px)` height: `calc(${prop.height} - ${headerHeight}px + ${fullscreen ? 0 : 56}px)`
}" }"
/> />
<!-- <my-echart <!-- <my-echart
@@ -49,7 +56,7 @@ const prop = defineProps({
h: { type: [String, Number] }, h: { type: [String, Number] },
width: { type: [String, Number] }, width: { type: [String, Number] },
height: { type: [String, Number] }, height: { type: [String, Number] },
timeKey: { type: Array as () => string[] }, timeKey: { type: Array as () => string[] },
timeValue: { type: Object }, timeValue: { type: Object },
interval: { type: Number } interval: { type: Number }
}) })
@@ -146,6 +153,9 @@ const initProbabilityData = () => {
const yAxisData = sortedData.map(item => item.indexName) const yAxisData = sortedData.map(item => item.indexName)
echartList.value = { echartList.value = {
title: {
text: '指标越限概率分布'
},
options: { options: {
backgroundColor: '#fff', backgroundColor: '#fff',
tooltip: { tooltip: {
@@ -166,14 +176,7 @@ const initProbabilityData = () => {
return tips return tips
} }
}, },
title: {
text: '指标越限概率分布',
x: 'center',
textStyle: {
fontSize: 16,
fontWeight: 'normal'
}
},
// 移除或隐藏 visualMap 组件 // 移除或隐藏 visualMap 组件
visualMap: { visualMap: {
show: false, // 设置为 false 隐藏右侧颜色条 show: false, // 设置为 false 隐藏右侧颜色条

View File

@@ -1,7 +1,14 @@
<template> <template>
<div> <div>
<!--指标越限概率分布 --> <!--指标越限概率分布 -->
<TableHeader :showReset="false" :timeKeyList="prop.timeKey" ref="TableHeaderRef" @selectChange="selectChange" datePicker v-if="fullscreen"> <TableHeader
:showReset="false"
:timeKeyList="prop.timeKey"
ref="TableHeaderRef"
@selectChange="selectChange"
datePicker
v-if="fullscreen"
>
<template v-slot:select> <template v-slot:select>
<el-form-item label="监测点"> <el-form-item label="监测点">
<el-select size="small" filterable v-model="tableStore.table.params.lineId"> <el-select size="small" filterable v-model="tableStore.table.params.lineId">
@@ -21,7 +28,7 @@
:options="echartList" :options="echartList"
:style="{ :style="{
width: prop.width, width: prop.width,
height: `calc(${prop.height} - ${headerHeight}px + ${fullscreen ? 0 : 56}px)` height: `calc(${prop.height} - ${headerHeight}px + ${fullscreen ? 0 : 56}px)`
}" }"
/> />
<!-- <my-echart <!-- <my-echart
@@ -48,7 +55,7 @@ const prop = defineProps({
h: { type: [String, Number] }, h: { type: [String, Number] },
width: { type: [String, Number] }, width: { type: [String, Number] },
height: { type: [String, Number] }, height: { type: [String, Number] },
timeKey: { type: Array as () => string[] }, timeKey: { type: Array as () => string[] },
timeValue: { type: Object }, timeValue: { type: Object },
interval: { type: Number } interval: { type: Number }
}) })
@@ -145,6 +152,9 @@ const initProbabilityData = () => {
const yAxisData = sortedData.map(item => item.indexName) const yAxisData = sortedData.map(item => item.indexName)
echartList.value = { echartList.value = {
title: {
text: '指标越限概率分布'
},
options: { options: {
backgroundColor: '#fff', backgroundColor: '#fff',
tooltip: { tooltip: {
@@ -165,14 +175,7 @@ const initProbabilityData = () => {
return tips return tips
} }
}, },
title: {
text: '指标越限概率分布',
x: 'center',
textStyle: {
fontSize: 16,
fontWeight: 'normal'
}
},
// 移除或隐藏 visualMap 组件 // 移除或隐藏 visualMap 组件
visualMap: { visualMap: {
show: false, // 设置为 false 隐藏右侧颜色条 show: false, // 设置为 false 隐藏右侧颜色条

View File

@@ -5,7 +5,7 @@
datePicker datePicker
@selectChange="selectChange" @selectChange="selectChange"
v-if="fullscreen" v-if="fullscreen"
ref="TableHeaderRef" ref="TableHeaderRef"
:timeKeyList="prop.timeKey" :timeKeyList="prop.timeKey"
> >
<template v-slot:select> <template v-slot:select>
@@ -100,7 +100,7 @@ const prop = defineProps({
h: { type: [String, Number] }, h: { type: [String, Number] },
width: { type: [String, Number] }, width: { type: [String, Number] },
height: { type: [String, Number] }, height: { type: [String, Number] },
timeKey: { type: Array as () => string[] }, timeKey: { type: Array as () => string[] },
timeValue: { type: Object }, timeValue: { type: Object },
interval: { type: Number } interval: { type: Number }
}) })
@@ -432,10 +432,9 @@ watch(
} }
) )
onMounted(() => { onMounted(async () => {
initLineList().then(() => { await initLineList()
initCode() await initCode()
})
}) })
watch( watch(

View File

@@ -1,6 +1,6 @@
<template> <template>
<div :style="{ width: menuCollapse ? '40px' : props.width }" style="transition: all 0.3s; overflow: hidden"> <div :style="{ width: menuCollapse ? '40px' : props.width }" style="transition: all 0.3s; overflow: hidden">
<div class="mt15 mr10" style="display: flex; justify-content: end"> <div class="mt10 mr10" style="display: flex; justify-content: end">
<el-button type="primary" icon="el-icon-Select" @click="save" :loading="loading">保存</el-button> <el-button type="primary" icon="el-icon-Select" @click="save" :loading="loading">保存</el-button>
</div> </div>
<Icon <Icon
@@ -39,7 +39,7 @@
</div> </div>
<el-tree <el-tree
:style="{ height: 'calc(100vh - 235px)' }" :style="{ height: 'calc(100vh - 267px)' }"
style="overflow: auto" style="overflow: auto"
ref="treeRef" ref="treeRef"
:props="defaultProps" :props="defaultProps"
@@ -183,5 +183,7 @@ defineExpose({ treeRef, loading })
.custom-tree-node { .custom-tree-node {
display: flex; display: flex;
align-items: center; align-items: center;
} }
</style> </style>

View File

@@ -1,81 +1,81 @@
<template> <template>
<el-scrollbar ref="verticalMenusRef" class="vertical-menus-scrollbar"> <el-scrollbar ref="verticalMenusRef" class="vertical-menus-scrollbar">
<el-menu <el-menu
class="layouts-menu-vertical" class="layouts-menu-vertical"
:collapse-transition="false" :collapse-transition="false"
:unique-opened="config.layout.menuUniqueOpened" :unique-opened="config.layout.menuUniqueOpened"
:default-active="state.defaultActive" :default-active="state.defaultActive"
:collapse="config.layout.menuCollapse" :collapse="config.layout.menuCollapse"
> >
<MenuTree :menus="navTabs.state.tabsViewRoutes" /> <MenuTree :menus="navTabs.state.tabsViewRoutes" />
</el-menu> </el-menu>
</el-scrollbar> </el-scrollbar>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { computed, nextTick, onMounted, reactive, ref } from 'vue' import { computed, nextTick, onMounted, reactive, ref } from 'vue'
import MenuTree from '@/layouts/admin/components/menus/menuTree.vue' import MenuTree from '@/layouts/admin/components/menus/menuTree.vue'
import { useRoute, onBeforeRouteUpdate, type RouteLocationNormalizedLoaded } from 'vue-router' import { useRoute, onBeforeRouteUpdate, type RouteLocationNormalizedLoaded } from 'vue-router'
import type { ScrollbarInstance } from 'element-plus' import type { ScrollbarInstance } from 'element-plus'
import { useConfig } from '@/stores/config' import { useConfig } from '@/stores/config'
import { useNavTabs } from '@/stores/navTabs' import { useNavTabs } from '@/stores/navTabs'
const config = useConfig() const config = useConfig()
const navTabs = useNavTabs() const navTabs = useNavTabs()
const route = useRoute() const route = useRoute()
const verticalMenusRef = ref<ScrollbarInstance>() const verticalMenusRef = ref<ScrollbarInstance>()
const state = reactive({ const state = reactive({
defaultActive: '', defaultActive: '',
}) })
const verticalMenusScrollbarHeight = computed(() => { const verticalMenusScrollbarHeight = computed(() => {
let menuTopBarHeight = 0 let menuTopBarHeight = 0
if (config.layout.menuShowTopBar) { if (config.layout.menuShowTopBar) {
menuTopBarHeight = 50 menuTopBarHeight = 50
} }
if (config.layout.layoutMode == 'Default') { if (config.layout.layoutMode == 'Default') {
return 'calc(100vh - ' + (32 + menuTopBarHeight) + 'px)' return 'calc(100vh - ' + (32 + menuTopBarHeight) + 'px)'
} else { } else {
return 'calc(100vh - ' + menuTopBarHeight + 'px)' return 'calc(100vh - ' + menuTopBarHeight + 'px)'
} }
}) })
// 激活当前路由的菜单 // 激活当前路由的菜单
const currentRouteActive = (currentRoute: RouteLocationNormalizedLoaded) => { const currentRouteActive = (currentRoute: RouteLocationNormalizedLoaded) => {
state.defaultActive = currentRoute.path state.defaultActive = currentRoute.path
} }
// 滚动条滚动到激活菜单所在位置 // 滚动条滚动到激活菜单所在位置
const verticalMenusScroll = () => { const verticalMenusScroll = () => {
nextTick(() => { nextTick(() => {
let activeMenu: HTMLElement | null = document.querySelector('.el-menu.layouts-menu-vertical li.is-active') let activeMenu: HTMLElement | null = document.querySelector('.el-menu.layouts-menu-vertical li.is-active')
if (!activeMenu) return false if (!activeMenu) return false
verticalMenusRef.value?.setScrollTop(activeMenu.offsetTop) verticalMenusRef.value?.setScrollTop(activeMenu.offsetTop)
}) })
} }
onMounted(() => { onMounted(() => {
currentRouteActive(route) currentRouteActive(route)
verticalMenusScroll() verticalMenusScroll()
}) })
onBeforeRouteUpdate((to) => { onBeforeRouteUpdate((to) => {
currentRouteActive(to) currentRouteActive(to)
}) })
</script> </script>
<style> <style>
.vertical-menus-scrollbar { .vertical-menus-scrollbar {
height: v-bind(verticalMenusScrollbarHeight); height: v-bind(verticalMenusScrollbarHeight);
background-color: v-bind('config.getColorVal("menuBackground")'); background-color: v-bind('config.getColorVal("menuBackground")');
} }
.layouts-menu-vertical { .layouts-menu-vertical {
border: 0; border: 0;
padding-bottom: 30px; padding-bottom: 30px;
--el-menu-bg-color: v-bind('config.getColorVal("menuBackground")'); --el-menu-bg-color: v-bind('config.getColorVal("menuBackground")');
--el-menu-text-color: v-bind('config.getColorVal("menuColor")'); --el-menu-text-color: v-bind('config.getColorVal("menuColor")');
--el-menu-active-color: v-bind('config.getColorVal("menuActiveColor")'); --el-menu-active-color: v-bind('config.getColorVal("menuActiveColor")');
--el-menu-hover-color: v-bind('config.getColorVal("menuActiveBackground")'); --el-menu-hover-color: v-bind('config.getColorVal("menuActiveBackground")');
} }
</style> </style>

View File

@@ -24,7 +24,7 @@
size="18" size="18"
/> />
</div> </div>
<el-dropdown style="height: 100%" @command="handleCommand"> <el-dropdown style="height: 100%" @command="handleCommand">
<div class="admin-info" :class="state.currentNavMenu == 'adminInfo' ? 'hover' : ''"> <div class="admin-info" :class="state.currentNavMenu == 'adminInfo' ? 'hover' : ''">
<el-avatar :size="25" fit="fill"> <el-avatar :size="25" fit="fill">
@@ -47,8 +47,9 @@
name="fa fa-cogs" name="fa fa-cogs"
size="18" size="18"
/> />
</div> -->" </div> -->
"
<Config /> <Config />
<PopupPwd ref="popupPwd" /> <PopupPwd ref="popupPwd" />
<AdminInfo ref="popupAdminInfo" /> <AdminInfo ref="popupAdminInfo" />
@@ -71,7 +72,6 @@ import PopupPwd from './popup/password.vue'
import AdminInfo from './popup/adminInfo.vue' import AdminInfo from './popup/adminInfo.vue'
import { useNavTabs } from '@/stores/navTabs' import { useNavTabs } from '@/stores/navTabs'
const adminInfo = useAdminInfo() const adminInfo = useAdminInfo()
const navTabs = useNavTabs() const navTabs = useNavTabs()
const configStore = useConfig() const configStore = useConfig()
@@ -106,7 +106,7 @@ const onFullScreen = () => {
}) })
} }
const handleCommand = (key: string) => { const handleCommand = async (key: string) => {
// console.log(key) // console.log(key)
switch (key) { switch (key) {
case 'adminInfo': case 'adminInfo':
@@ -116,10 +116,13 @@ const handleCommand = (key: string) => {
popupPwd.value.open() popupPwd.value.open()
break break
case 'layout': case 'layout':
navTabs.closeTabs() await window.location.reload()
window.localStorage.clear() setTimeout(() => {
adminInfo.reset() navTabs.closeTabs()
router.push({ name: 'login' }) window.localStorage.clear()
adminInfo.reset()
router.push({ name: 'login' })
}, 0)
break break
default: default:
break break

View File

@@ -25,7 +25,6 @@ export const useNavTabs = defineStore(
}) })
function addTab(route: RouteLocationNormalized) { function addTab(route: RouteLocationNormalized) {
console.log('🚀 ~ addTab ~ route:', route)
if (!route.meta.addtab) return if (!route.meta.addtab) return
for (const key in state.tabsView) { for (const key in state.tabsView) {
if (state.tabsView[key].path === route.path) { if (state.tabsView[key].path === route.path) {
@@ -69,7 +68,7 @@ export const useNavTabs = defineStore(
} }
const setTabsViewRoutes = (data: RouteRecordRaw[]): void => { const setTabsViewRoutes = (data: RouteRecordRaw[]): void => {
state.tabsViewRoutes = encodeRoutesURI(data) state.tabsViewRoutes = encodeRoutesURI(JSON.parse(JSON.stringify(data)))
} }
const setAuthNode = (key: string, data: string[]) => { const setAuthNode = (key: string, data: string[]) => {
@@ -87,9 +86,9 @@ export const useNavTabs = defineStore(
const refresh = () => { const refresh = () => {
// setTimeout(() => { // setTimeout(() => {
// console.log(123, state.tabsViewRoutes) // console.log(123, state.tabsViewRoutes)
let list = matchAndReturnRouteData(state.tabsViewRoutes, state.tabsView) let list = matchAndReturnRouteData(state.tabsViewRoutes, state.tabsView)
state.tabsView = [] state.tabsView = []
list.forEach(item => { list.forEach(item => {
addTab(item) addTab(item)
}) })

View File

@@ -7,16 +7,34 @@
</div> </div>
<Table ref="tableRef" :row-config="{ isCurrent: true, isHover: true }" @currentChange="currentChange" /> <Table ref="tableRef" :row-config="{ isCurrent: true, isHover: true }" @currentChange="currentChange" />
</div> </div>
<Tree <div>
v-if="menuListId" <el-tabs type="border-card">
ref="treeRef" <el-tab-pane label="菜单">
show-checkbox <Tree
width="350px" v-if="menuListId"
:data="menuTree" ref="treeRef"
:checkStrictly="false" show-checkbox
@checkChange="checkChange" width="350px"
></Tree> :data="menuTree"
<el-empty style="width: 350px; padding-top: 300px; box-sizing: border-box" description="请选择角色" v-else /> :checkStrictly="false"
@checkChange="checkChange"
></Tree>
<el-empty
style="width: 350px; padding-top: 300px; box-sizing: border-box"
description="请选择角色"
v-else
/>
</el-tab-pane>
<el-tab-pane label="系统">
<div class="mt10 mr10" style="display: flex; justify-content: end">
<el-button type="primary" icon="el-icon-Select" @click="saveSystem">保存</el-button>
</div>
<el-checkbox-group v-model="systemIds" class="md10 system" >
<el-checkbox v-for="item in systemList" :label="item.name" :value="item.id" :key="item.id" />
</el-checkbox-group>
</el-tab-pane>
</el-tabs>
</div>
<PopupForm ref="popupRef"></PopupForm> <PopupForm ref="popupRef"></PopupForm>
</div> </div>
</template> </template>
@@ -34,11 +52,14 @@ import { del } from '@/api/user-boot/role'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import PopupForm from './popupForm.vue' import PopupForm from './popupForm.vue'
import { useAdminInfo } from '@/stores/adminInfo' import { useAdminInfo } from '@/stores/adminInfo'
import { useDictData } from '@/stores/dictData'
const dictData = useDictData()
const systemList = dictData.getBasicData('System_Type')
const adminInfo = useAdminInfo() const adminInfo = useAdminInfo()
defineOptions({ defineOptions({
name: 'auth/role' name: 'auth/role'
}) })
const systemIds = ref([])
const height = mainHeight(20).height const height = mainHeight(20).height
const treeRef = ref() const treeRef = ref()
const menuTree = ref<treeData[]>([]) const menuTree = ref<treeData[]>([])
@@ -163,6 +184,19 @@ const checkChange = (data: any) => {
treeRef.value.loading = false treeRef.value.loading = false
}) })
} }
const saveSystem = () => {
// updateRoleMenu({
// id: menuListId.value,
// idList: systemIds.value
// })
// .then(() => {
// ElMessage.success('操作成功!')
// treeRef.value.loading = false
// })
// .catch(() => {
// treeRef.value.loading = false
// })
}
onMounted(() => { onMounted(() => {
tableStore.index() tableStore.index()
}) })
@@ -170,3 +204,17 @@ const addRole = () => {
popupRef.value.open('新增角色') popupRef.value.open('新增角色')
} }
</script> </script>
<style lang="scss" scoped>
:deep(.el-tabs--border-card > .el-tabs__content) {
padding: 0px !important;
}
.system{
width: 330px;
height: calc(100vh - 225px);
border: 1px solid var(--el-border-color);
padding: 5px 10px;
display: flex;
flex-direction: column;
}
</style>

View File

@@ -95,6 +95,7 @@ const tableStore = new TableStore({
}, },
click: row => { click: row => {
popupEditRef.value.open('编辑用户', row) popupEditRef.value.open('编辑用户', row)
console.log("🚀 ~ row:", row)
} }
}, },
{ {

View File

@@ -1,245 +1,271 @@
<template> <template>
<el-dialog class="cn-operate-dialog" v-model.trim="dialogVisible" :title="title"> <el-dialog class="cn-operate-dialog" v-model.trim="dialogVisible" :title="title">
<el-form :model="form" label-width="auto" class="form-two" :rules="rules">
<el-form :model="form" label-width="auto" class="form-two" :rules="rules"> <el-form-item label="用户名" prop="name">
<el-form-item label="用户名" prop="name"> <el-input maxlength="32" show-word-limit v-model.trim="form.name" placeholder="请输入昵称" />
<el-input maxlength="32" show-word-limit v-model.trim="form.name" placeholder="请输入昵称" /> </el-form-item>
</el-form-item> <el-form-item label="登录名" prop="loginName">
<el-form-item label="登录名" prop="loginName"> <el-input maxlength="32" show-word-limit v-model.trim="form.loginName" placeholder="请输入登录名" />
<el-input maxlength="32" show-word-limit v-model.trim="form.loginName" placeholder="请输入登录名" /> </el-form-item>
</el-form-item> <el-form-item label="默认密码" prop="password" v-if="title === '新增用户'">
<el-form-item label="默认密码" prop="password" v-if="title === '新增用户'"> <el-input
<el-input maxlength="32" show-word-limit v-model.trim="form.password" placeholder="请输入密码" disabled /> maxlength="32"
</el-form-item> show-word-limit
<el-form-item label="权限类型" prop="type"> v-model.trim="form.password"
<el-select v-model.trim="form.type" @change="changeValue" disabled placeholder="请选择权限类型"> placeholder="请输入密码"
<el-option v-for="(item, index) in UserTypeOption" :label="item.label" :value="item.value" disabled
:key="index"></el-option> />
</el-select> </el-form-item>
</el-form-item> <el-form-item label="权限类型" prop="type">
<el-form-item label="用户类型" prop="casualUser"> <el-select v-model.trim="form.type" @change="changeValue" disabled placeholder="请选择权限类型">
<el-select v-model.trim="form.casualUser" placeholder="请选择权限类型"> <el-option
<el-option v-for="(item, index) in TypeOptions" :label="item.label" :value="item.value" v-for="(item, index) in UserTypeOption"
:key="index"></el-option> :label="item.label"
</el-select> :value="item.value"
</el-form-item> :key="index"
<!-- <el-form-item label="所属部门" prop="deptId"> ></el-option>
<Area v-model.trim="form.deptId" /> </el-select>
</el-form-item> --> </el-form-item>
<el-form-item label="角色" prop="role"> <el-form-item label="用户类型" prop="casualUser">
<el-select v-model.trim="form.role" placeholder="请选择角色" multiple collapse-tags> <el-select v-model.trim="form.casualUser" placeholder="请选择权限类型">
<el-option v-for="(item, index) in roleOptions" :label="item.label" :value="item.value" <el-option
:key="index"></el-option> v-for="(item, index) in TypeOptions"
</el-select> :label="item.label"
</el-form-item> :value="item.value"
:key="index"
<el-form-item label="手机号" prop="phone"> ></el-option>
<el-input maxlength="32" show-word-limit v-model.trim="form.phone" placeholder="请输入手机号" /> </el-select>
</el-form-item> </el-form-item>
<el-form-item label="邮箱" prop="email"> <!-- <el-form-item label="所属部门" prop="deptId">
<el-input maxlength="32" show-word-limit v-model.trim="form.email" placeholder="请输入描述" /> <Area v-model.trim="form.deptId" />
</el-form-item> </el-form-item> -->
<el-form-item label="时间段" prop="limitTime"> <el-form-item label="角色" prop="role">
<el-slider v-model.trim="form.limitTime" style="width: 95%" range show-stops :max="24" /> <el-select v-model.trim="form.role" placeholder="请选择角色" multiple collapse-tags>
</el-form-item> <el-option
<el-form-item label="起始IP" prop="limitIpStart"> v-for="(item, index) in roleOptions"
<el-input maxlength="32" show-word-limit v-model.trim="form.limitIpStart" placeholder="请输入描述" /> :label="item.label"
</el-form-item> :value="item.value"
<el-form-item label="结束IP" prop="limitIpEnd"> :key="index"
<el-input maxlength="32" show-word-limit v-model.trim="form.limitIpEnd" placeholder="请输入描述" /> ></el-option>
</el-form-item> </el-select>
</el-form-item>
<el-form-item label="短信通知" prop="smsNotice">
<el-radio-group v-model.trim="form.smsNotice" style="width: 200px"> <el-form-item label="手机号" prop="phone">
<el-radio-button :label="0"></el-radio-button> <el-input maxlength="32" show-word-limit v-model.trim="form.phone" placeholder="请输入手机号" />
<el-radio-button :label="1"></el-radio-button> </el-form-item>
</el-radio-group> <el-form-item label="邮箱" prop="email">
</el-form-item> <el-input maxlength="32" show-word-limit v-model.trim="form.email" placeholder="请输入描述" />
<el-form-item label="邮件通知" prop="emailNotice"> </el-form-item>
<el-radio-group v-model.trim="form.emailNotice" style="width: 200px"> <el-form-item label="时间段" prop="limitTime">
<el-radio-button :label="0"></el-radio-button> <el-slider v-model.trim="form.limitTime" style="width: 95%" range show-stops :max="24" />
<el-radio-button :label="1"></el-radio-button> </el-form-item>
</el-radio-group> <el-form-item label="起始IP" prop="limitIpStart">
</el-form-item> <el-input maxlength="32" show-word-limit v-model.trim="form.limitIpStart" placeholder="请输入描述" />
<el-form-item label="用户ID"> </el-form-item>
<div style="display: flex; width: 100%"> <el-form-item label="结束IP" prop="limitIpEnd">
<el-radio-group v-model.trim="useId"> <el-input maxlength="32" show-word-limit v-model.trim="form.limitIpEnd" placeholder="请输入描述" />
<el-radio-button :label="1"></el-radio-button> </el-form-item>
<el-radio-button :label="0"></el-radio-button>
</el-radio-group> <el-form-item label="短信通知" prop="smsNotice">
<el-input maxlength="32" show-word-limit :disabled="title !== '新增用户'" v-model.trim="form.id" <el-radio-group v-model.trim="form.smsNotice" style="width: 200px">
placeholder="请输入用户id" v-if="useId" style="flex: 1;" class="ml10"></el-input> <el-radio-button :label="0"></el-radio-button>
</div> <el-radio-button :label="1"></el-radio-button>
</el-form-item> </el-radio-group>
</el-form> </el-form-item>
<el-form-item label="邮件通知" prop="emailNotice">
<template #footer> <el-radio-group v-model.trim="form.emailNotice" style="width: 200px">
<span class="dialog-footer"> <el-radio-button :label="0"></el-radio-button>
<el-button @click="dialogVisible = false">取消</el-button> <el-radio-button :label="1"></el-radio-button>
<el-button type="primary" @click="submit">确认</el-button> </el-radio-group>
</span> </el-form-item>
</template> <el-form-item label="用户ID">
</el-dialog> <div style="display: flex; width: 100%">
</template> <el-radio-group v-model.trim="useId">
<script lang="ts" setup> <el-radio-button :label="1"></el-radio-button>
import { ref, inject } from 'vue' <el-radio-button :label="0"></el-radio-button>
import { reactive } from 'vue' </el-radio-group>
import TableStore from '@/utils/tableStore' <el-input
import { ElMessage, FormItemRule } from 'element-plus' maxlength="32"
import { roleList } from '@/api/user-boot/role' show-word-limit
import { add, edit } from '@/api/user-boot/user' :disabled="title !== '新增用户'"
import { useAdminInfo } from '@/stores/adminInfo' v-model.trim="form.id"
import Area from '@/components/form/area/index.vue' placeholder="请输入用户id"
import { Arrayable } from 'element-plus/es/utils' v-if="useId"
style="flex: 1"
const adminInfo = useAdminInfo() class="ml10"
const tableStore = inject('tableStore') as TableStore ></el-input>
// do not use same name with ref </div>
const form = reactive({ </el-form-item>
id: '', </el-form>
name: '',
password: '123456', <template #footer>
email: '', <span class="dialog-footer">
limitIpStart: '', <el-button @click="dialogVisible = false">取消</el-button>
deptId: '', <el-button type="primary" @click="submit">确认</el-button>
deptName: '', </span>
casualUser: 1, </template>
loginName: '', </el-dialog>
phone: '', </template>
limitIpEnd: '', <script lang="ts" setup>
limitTime: [1, 2], import { ref, inject } from 'vue'
role: [], import { reactive } from 'vue'
smsNotice: 0, import TableStore from '@/utils/tableStore'
emailNotice: 0, import { ElMessage, FormItemRule } from 'element-plus'
type: 0 import { roleList } from '@/api/user-boot/role'
}) import { add, edit } from '@/api/user-boot/user'
const rules: Partial<Record<string, Arrayable<FormItemRule>>> = { import { useAdminInfo } from '@/stores/adminInfo'
name: [{ required: true, message: '用户名不能为空', trigger: 'blur' }], import Area from '@/components/form/area/index.vue'
role: [{ required: true, message: '角色不能为空', trigger: 'blur' }], import { Arrayable } from 'element-plus/es/utils'
password: [{ required: true, message: '用户密码不能为空', trigger: 'blur' }],
loginName: [{ required: true, message: '登录名不能为空', trigger: 'blur' }], const adminInfo = useAdminInfo()
casualUser: [{ required: true, message: '用户类型不能为空', trigger: 'blur' }], const tableStore = inject('tableStore') as TableStore
smsNotice: [{ required: true, message: '短信通知不能为空', trigger: 'blur' }], // do not use same name with ref
emailNotice: [{ required: true, message: '邮件通知不能为空', trigger: 'blur' }], const form = reactive({
email: [ id: '',
{ required: false, message: '邮箱不能为空', trigger: 'blur' }, name: '',
{ password: '123456',
type: 'email', email: '',
message: "'请输入正确的邮箱地址", limitIpStart: '',
trigger: ['blur', 'change'] deptId: '',
} deptName: '',
], casualUser: 1,
phone: [ loginName: '',
{ required: false, message: '手机号不能为空', trigger: 'blur' }, phone: '',
{ limitIpEnd: '',
pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, limitTime: [1, 2],
message: '请输入正确的手机号码', role: [],
trigger: 'blur' smsNotice: 0,
} emailNotice: 0,
], type: 0
limitTime: [{ required: true, message: '时间段不能为空', trigger: 'blur' }], })
limitIpStart: [ const rules: Partial<Record<string, Arrayable<FormItemRule>>> = {
{ required: true, message: '起始IP不能为空', trigger: 'blur' }, name: [{ required: true, message: '用户名不能为空', trigger: 'blur' }],
{ role: [{ required: true, message: '角色不能为空', trigger: 'blur' }],
required: true, password: [{ required: true, message: '用户密码不能为空', trigger: 'blur' }],
validator: (rule: any, value: string, callback: any) => { loginName: [{ required: true, message: '登录名不能为空', trigger: 'blur' }],
let regexp = /^((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})(\.((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})){3}$/ casualUser: [{ required: true, message: '用户类型不能为空', trigger: 'blur' }],
let isCorrect = regexp.test(value) smsNotice: [{ required: true, message: '短信通知不能为空', trigger: 'blur' }],
if (value == '') { emailNotice: [{ required: true, message: '邮件通知不能为空', trigger: 'blur' }],
return callback(new Error('请输入IP地址')) email: [
} else if (!isCorrect) { { required: false, message: '邮箱不能为空', trigger: 'blur' },
callback(new Error('请输入正确的IP地址')) {
} else { type: 'email',
callback() message: "'请输入正确的邮箱地址",
} trigger: ['blur', 'change']
}, }
trigger: 'blur' ],
} phone: [
], { required: false, message: '手机号不能为空', trigger: 'blur' },
limitIpEnd: [ {
{ required: true, message: '结束IP不能为空', trigger: 'blur' }, pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/,
{ message: '请输入正确的手机号码',
required: true, trigger: 'blur'
validator: (rule: any, value: string, callback: any) => { }
let regexp = /^((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})(\.((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})){3}$/ ],
let isCorrect = regexp.test(value) limitTime: [{ required: true, message: '时间段不能为空', trigger: 'blur' }],
if (value == '') { limitIpStart: [
return callback(new Error('请输入IP地址')) { required: true, message: '起始IP不能为空', trigger: 'blur' },
} else if (!isCorrect) { {
callback(new Error('请输入正确的IP地址')) required: true,
} else { validator: (rule: any, value: string, callback: any) => {
callback() let regexp = /^((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})(\.((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})){3}$/
} let isCorrect = regexp.test(value)
}, if (value == '') {
trigger: 'blur' return callback(new Error('请输入IP地址'))
} } else if (!isCorrect) {
] callback(new Error('请输入正确的IP地址'))
} } else {
const UserTypeOption = [ callback()
{ label: '管理员', value: 1 }, }
{ label: '普通用户', value: 2 } },
] trigger: 'blur'
const TypeOptions = [ }
{ label: '临时用户', value: 0 }, ],
{ label: '长期用户', value: 1 } limitIpEnd: [
] { required: true, message: '结束IP不能为空', trigger: 'blur' },
const useId = ref(1) {
const roleOptions = ref<treeData>() required: true,
const queryRole = () => { validator: (rule: any, value: string, callback: any) => {
roleList(adminInfo.$state.userType).then((res: any) => { let regexp = /^((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})(\.((2(5[0-5]|[0-4]\d))|[0-1]?\d{1,2})){3}$/
roleOptions.value = res.data.map((item: any) => { let isCorrect = regexp.test(value)
return { if (value == '') {
label: item.name, return callback(new Error('请输入IP地址'))
value: item.id } else if (!isCorrect) {
} callback(new Error('请输入正确的IP地址'))
}) } else {
}) callback()
} }
queryRole() },
const dialogVisible = ref(false) trigger: 'blur'
const title = ref('新增菜单') }
const open = (text: string, data?: anyObj) => { ]
title.value = text }
dialogVisible.value = true const UserTypeOption = [
if (data) { { label: '管理员', value: 1 },
for (let key in form) { { label: '普通用户', value: 2 }
form[key] = data[key] ]
} const TypeOptions = [
form.limitTime = data.limitTime.split('-') { label: '临时用户', value: 0 },
form.role = data.roleList { label: '长期用户', value: 1 }
} else { ]
for (let key in form) { const useId = ref(1)
form[key] = '' const roleOptions = ref<treeData>()
} const queryRole = () => {
form.casualUser = 1 roleList(adminInfo.$state.userType).then((res: any) => {
form.limitTime = [0, 24] roleOptions.value = res.data.map((item: any) => {
form.role = [] return {
form.smsNotice = 0 label: item.name,
form.emailNotice = 0 value: item.id
useId.value = 1 }
form.id = '' })
form.limitIpStart = '0.0.0.0' })
form.limitIpEnd = '255.255.255.255' }
form.password = '123456' queryRole()
} const dialogVisible = ref(false)
form.type = adminInfo.$state.userType + 1 const title = ref('新增菜单')
} const open = (text: string, data?: anyObj) => {
const submit = async () => { title.value = text
let obj = JSON.parse(JSON.stringify(form)) dialogVisible.value = true
obj.limitTime = obj.limitTime.join('-') console.log("🚀 ~ open ~ data:", data)
delete obj.password if (data) {
if (form.id) { for (let key in form) {
await edit(obj) form[key] = data[key]
ElMessage.success('修改成功') }
} else { form.limitTime = data.limitTime.split('-')
form.type = adminInfo.$state.userType + 1 form.role = data.roleList
await add(obj) } else {
ElMessage.success('新增成功') for (let key in form) {
} form[key] = ''
tableStore.index() }
dialogVisible.value = false form.casualUser = 1
} form.limitTime = [0, 24]
form.role = []
const changeValue = () => { } form.smsNotice = 0
defineExpose({ open }) form.emailNotice = 0
</script> useId.value = 1
form.id = ''
form.limitIpStart = '0.0.0.0'
form.limitIpEnd = '255.255.255.255'
form.password = '123456'
form.type = adminInfo.$state.userType + 1
}
}
const submit = async () => {
let obj = JSON.parse(JSON.stringify(form))
obj.limitTime = obj.limitTime.join('-')
delete obj.password
if (form.id) {
await edit(obj)
ElMessage.success('修改成功')
} else {
form.type = adminInfo.$state.userType + 1
await add(obj)
ElMessage.success('新增成功')
}
tableStore.index()
dialogVisible.value = false
}
const changeValue = () => {}
defineExpose({ open })
</script>

View File

@@ -380,10 +380,11 @@ const drag = throttle(row => {
} catch (e) {} } catch (e) {}
Object.assign(item.state, { Object.assign(item.state, {
top: mouseAt.y - parentRect.top, top: mouseAt.y - parentRect.top - 300,
left: mouseAt.x - parentRect.left left: mouseAt.x - parentRect.left - 180
}) })
const newPos = item.calcXY(mouseAt.y - parentRect.top, mouseAt.x - parentRect.left)
const newPos = item.calcXY(mouseAt.y - parentRect.top - 300, mouseAt.x - parentRect.left - 180)
if (mouseInGrid) { if (mouseInGrid) {
gridLayout.value.dragEvent('dragstart', dropId, newPos.x, newPos.y, dragItem.h, dragItem.w) gridLayout.value.dragEvent('dragstart', dropId, newPos.x, newPos.y, dragItem.h, dragItem.w)
@@ -441,7 +442,7 @@ const onSubmit = () => {
console.log(123, findDuplicateNames(layout.value)) console.log(123, findDuplicateNames(layout.value))
let repeat = findDuplicateNames(layout.value) || [] let repeat = findDuplicateNames(layout.value) || []
if (repeat.length > 0) { if (repeat.length > 0) {
return ElMessage.warning(repeat.join('、')+' 组件重复,请删除重复组件!') return ElMessage.warning(repeat.join('、') + ' 组件重复,请删除重复组件!')
} }
// const maxValue = Math.max(...layout.value.map(item => item.y + item.h)) // const maxValue = Math.max(...layout.value.map(item => item.y + item.h))

View File

@@ -31,13 +31,6 @@
<el-input v-model="form.path" placeholder="请输入组件路径"></el-input> <el-input v-model="form.path" placeholder="请输入组件路径"></el-input>
</el-form-item> </el-form-item>
<el-form-item class="top" label="组件查询时间" prop="timeKeys"> <el-form-item class="top" label="组件查询时间" prop="timeKeys">
<!-- <el-radio-group v-model="form.timeKeys" style="width: 100%">
<el-radio-button label="年" value="1" />
<el-radio-button label="季" value="2" />
<el-radio-button label="月" value="3" />
<el-radio-button label="周" value="4" />
<el-radio-button label="日" value="5" />
</el-radio-group> -->
<el-checkbox-group v-model="form.timeKeys"> <el-checkbox-group v-model="form.timeKeys">
<el-checkbox-button value="1"></el-checkbox-button> <el-checkbox-button value="1"></el-checkbox-button>
<el-checkbox-button value="2"></el-checkbox-button> <el-checkbox-button value="2"></el-checkbox-button>
@@ -46,6 +39,17 @@
<el-checkbox-button value="5"></el-checkbox-button> <el-checkbox-button value="5"></el-checkbox-button>
</el-checkbox-group> </el-checkbox-group>
</el-form-item> </el-form-item>
<el-form-item class="top" label="组件绑定系统" prop="systemIds">
<el-select
v-model="form.systemIds"
multiple
collapse-tags
collapse-tags-tooltip
placeholder="请选择组件绑定系统"
>
<el-option v-for="item in systemList" :key="item.id" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item class="top" label="组件排序" prop="sort"> <el-form-item class="top" label="组件排序" prop="sort">
<el-input v-model.number="form.sort" placeholder="请输入组件排序"></el-input> <el-input v-model.number="form.sort" placeholder="请输入组件排序"></el-input>
</el-form-item> </el-form-item>
@@ -70,7 +74,7 @@
<template #footer> <template #footer>
<span class="dialog-footer"> <span class="dialog-footer">
<el-button @click="cancel">取消</el-button> <el-button @click="cancel">取消</el-button>
<el-button type="primary" @click="submit">保存</el-button> <el-button type="primary" @click="submit" :loading="loading">保存</el-button>
</span> </span>
</template> </template>
</el-dialog> </el-dialog>
@@ -88,6 +92,7 @@ const dictData = useDictData()
const dialogVisible = ref(false) const dialogVisible = ref(false)
const title = ref('') const title = ref('')
const formRef = ref() const formRef = ref()
const loading = ref(false)
// 注意不要和表单ref的命名冲突 // 注意不要和表单ref的命名冲突
const form = ref<anyObj>({ const form = ref<anyObj>({
name: '', name: '',
@@ -95,6 +100,7 @@ const form = ref<anyObj>({
system: [], system: [],
timeKeys: ['1', '2', '3', '4', '5'], timeKeys: ['1', '2', '3', '4', '5'],
code: '', code: '',
systemIds: [],
path: '' path: ''
}) })
const props = { label: 'name', value: 'id' } const props = { label: 'name', value: 'id' }
@@ -105,11 +111,14 @@ const rules = {
icon: [{ required: true, message: '请先择组件图标', trigger: 'change' }], icon: [{ required: true, message: '请先择组件图标', trigger: 'change' }],
path: [{ required: true, message: '请输入组件路径', trigger: 'blur' }], path: [{ required: true, message: '请输入组件路径', trigger: 'blur' }],
sort: [{ required: true, message: '请输入排序', trigger: 'blur' }], sort: [{ required: true, message: '请输入排序', trigger: 'blur' }],
timeKeys: [{ required: true, message: '请选择组件查询时间', trigger: 'change' }] timeKeys: [{ required: true, message: '请选择组件查询时间', trigger: 'change' }],
systemIds: [{ required: true, message: '请选择组件绑定系统', trigger: 'change' }]
} }
const customDeptOption: any = ref([]) const customDeptOption: any = ref([])
const systemList = dictData.getBasicData('System_Type')
onMounted(() => { onMounted(() => {
customDeptOption.value = dictData.getBasicData('System_Type') customDeptOption.value = dictData.getBasicData('Component_Type')
customDeptOption.value.forEach((item: any) => { customDeptOption.value.forEach((item: any) => {
getFatherComponent({ systemType: item.id }).then(res => { getFatherComponent({ systemType: item.id }).then(res => {
item.children = res.data.filter(item => item.name != '无') item.children = res.data.filter(item => item.name != '无')
@@ -134,6 +143,7 @@ const submit = () => {
if (valid) { if (valid) {
let url = '' let url = ''
ElMessage.info('正在保存请稍等!') ElMessage.info('正在保存请稍等!')
loading.value = true
setTimeout(async () => { setTimeout(async () => {
await html2canvas(document.querySelector('.GridLayout'), { await html2canvas(document.querySelector('.GridLayout'), {
// scale: 2 // scale: 2
@@ -173,6 +183,7 @@ const submit = () => {
cancel() cancel()
}) })
} }
loading.value = false
}, 500) }, 500)
} }
}) })

View File

@@ -13,11 +13,11 @@
/> --> /> -->
<el-tabs v-model="tableName" type="border-card" @tab-change="changeTab"> <el-tabs v-model="tableName" type="border-card" @tab-change="changeTab">
<el-tab-pane v-for="item in tableStore.table.data" :key="item.name" :label="item.name" :name="item.name"> <el-tab-pane v-for="item in tableStore.table.data" :key="item.name" :label="item.name" :name="item.name">
<el-tabs v-model="tableName1" tab-position="left"> <el-tabs v-model="tableName1" tab-position="left" class="componentList">
<el-tab-pane v-for="k in item?.children" :key="k.name" :label="k.name" :name="k.name"> <el-tab-pane v-for="k in item?.children" :key="k.name" :label="k.name" :name="k.name">
<template #label> <template #label>
<span class="custom-tabs-label"> <span class="custom-tabs-label">
<span>{{ k.name }}</span> <p>{{ k.name }}</p>
<!-- <el-icon><Edit /></el-icon> --> <!-- <el-icon><Edit /></el-icon> -->
<el-button <el-button
@@ -25,21 +25,21 @@
icon="el-icon-Edit" icon="el-icon-Edit"
link link
class="ml10" class="ml10"
@click="editTree(k, 0)" @click.stop="editTree(k, 0)"
></el-button> ></el-button>
<el-button <el-button
type="danger" type="danger"
icon="el-icon-Delete" icon="el-icon-Delete"
link link
class="ml0" class="ml0"
@click="del(k)" @click.stop="del(k)"
></el-button> ></el-button>
</span> </span>
</template> </template>
<div :style="height" style="overflow-y: auto; overflow-x: hidden"> <div :style="height" style="overflow-y: auto; overflow-x: hidden">
<el-row :gutter="10" class="pl5 pr5 pt5"> <el-row :gutter="10" class="pl5 pr5 pt5">
<el-col :span="6" v-for="component in k.children" :key="component.id" class="mb10"> <el-col :span="6" v-for="component in k.children" :key="component.id" class="mb10">
<el-card class="box-card" shadow="hover"> <el-card class="box-card" shadow="hover">
<div slot="header" class="clearfix"> <div slot="header" class="clearfix">
<span style="display: flex; align-items: center"> <span style="display: flex; align-items: center">
{{ component.name }} {{ component.name }}
@@ -50,7 +50,7 @@
icon="el-icon-Edit" icon="el-icon-Edit"
style="padding: 3px 0; color: blue" style="padding: 3px 0; color: blue"
type="text" type="text"
@click="editTree(component, 1)" @click.stop="editTree(component, 1)"
> >
编辑 编辑
</el-button> </el-button>
@@ -58,7 +58,7 @@
icon="el-icon-Delete" icon="el-icon-Delete"
style="padding: 3px 0; color: red" style="padding: 3px 0; color: red"
type="text" type="text"
@click="del(component)" @click.stop="del(component)"
> >
删除 删除
</el-button> </el-button>
@@ -356,4 +356,9 @@ span {
min-width: 128px; min-width: 128px;
} }
} }
:deep(.componentList){
.el-tabs__header{
height: calc(100vh - 250px)!important;
}
}
</style> </style>