大屏项目初始化
This commit is contained in:
@@ -10,3 +10,26 @@ export function getHomeMenuList(params) {
|
||||
params: params
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 获取指标信息列表
|
||||
*/
|
||||
export function getUserMenuList(params) {
|
||||
return request({
|
||||
url: '/biz/homeMenu/userList',
|
||||
method: 'get',
|
||||
params: params
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
export function getTreeMenuList(params) {
|
||||
return request({
|
||||
url: '/biz/homeMenu/treeList',
|
||||
method: 'get',
|
||||
params: params
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import request from '@/utils/request'
|
||||
*/
|
||||
export function login(data) {
|
||||
return request({
|
||||
url: 'userLogin',
|
||||
url: '/userLogin',
|
||||
method: 'post',
|
||||
data
|
||||
})
|
||||
@@ -30,3 +30,15 @@ export function getUserInfo() {
|
||||
method: 'get'
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 修改密码
|
||||
*/
|
||||
export function updatePasswd(params) {
|
||||
return request({
|
||||
url: '/editPasswd',
|
||||
method: 'post',
|
||||
params: params
|
||||
})
|
||||
}
|
||||
|
Before Width: | Height: | Size: 1.4 MiB After Width: | Height: | Size: 1.4 MiB |
BIN
screen-vue/src/assets/login-box.png
Normal file
BIN
screen-vue/src/assets/login-box.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.8 MiB |
@@ -17,6 +17,9 @@
|
||||
<script setup>
|
||||
import { ref, reactive, onUnmounted, nextTick } from 'vue'
|
||||
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||
import { updatePasswd } from '@/api/user'
|
||||
|
||||
const LoginUser = ref(JSON.parse(localStorage.getItem("loginUser")) || {});
|
||||
|
||||
const emit = defineEmits(['success'])
|
||||
const pwdFormRef = ref(null)
|
||||
@@ -98,16 +101,20 @@ const submitForm = async () => {
|
||||
submitPromise = null
|
||||
return resolve(false)
|
||||
}
|
||||
|
||||
const reqParams = {
|
||||
oldPasswd :pwdForm.oldPassword,
|
||||
password : pwdForm.newPassword,
|
||||
userId : LoginUser.value?.userId || '',
|
||||
}
|
||||
const res = await updatePasswd(reqParams);
|
||||
await new Promise(res => setTimeout(res, 800))
|
||||
if (isMounted.value) {
|
||||
ElMessage.success('密码修改成功')
|
||||
emit('success')
|
||||
resolve(true)
|
||||
}
|
||||
} catch (error) {
|
||||
if (isMounted.value) {
|
||||
ElMessage.error(error.message || '密码修改失败,请稍后重试')
|
||||
console.log(error)
|
||||
}
|
||||
resolve(false)
|
||||
} finally {
|
||||
|
||||
@@ -167,7 +167,7 @@ import {
|
||||
ArrowDown, Lock, SwitchButton, User, FullScreen, Monitor,
|
||||
Expand, Fold, Close, ArrowLeft, ArrowRight, Menu, House
|
||||
} from '@element-plus/icons-vue'
|
||||
import { getHomeMenuList } from '@/api/bizMenu'
|
||||
import { getUserMenuList } from '@/api/bizMenu'
|
||||
import LogoImg from '@/assets/logo.png'
|
||||
import EditPswd from './components/editPswd.vue'
|
||||
|
||||
@@ -190,7 +190,7 @@ const editPwdRef = ref(null)
|
||||
const getMenuList = async () => {
|
||||
if (!isMounted.value) return
|
||||
try {
|
||||
const res = await getHomeMenuList();
|
||||
const res = await getUserMenuList();
|
||||
menuList.value = res || [];
|
||||
const setMenuNameMap = (menus) => {
|
||||
menus.forEach(menu => {
|
||||
@@ -381,6 +381,9 @@ const handlePwdModifySuccess = () => {
|
||||
if (!isMounted.value) return
|
||||
closeEditPwdDialog();
|
||||
ElMessage.success('密码修改成功,请重新登录');
|
||||
localStorage.removeItem('loginUser');
|
||||
localStorage.removeItem('token');
|
||||
router.push('/login');
|
||||
}
|
||||
|
||||
onUnmounted(() => {
|
||||
@@ -423,6 +426,25 @@ onMounted(() => {
|
||||
--tab-padding: 0 12px;
|
||||
--divider-color: #e6e6e6;
|
||||
--header-bg-color: #c6e2ff;
|
||||
background: url('@/assets/desktop.png') no-repeat center center fixed;
|
||||
background-size: cover;
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.app-container::before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-color: rgba(255, 255, 255, 0.85);
|
||||
z-index: 0;
|
||||
}
|
||||
|
||||
.app-container > * {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
|
||||
.app-header {
|
||||
@@ -431,7 +453,7 @@ onMounted(() => {
|
||||
align-items: center;
|
||||
padding: 0 20px;
|
||||
height: 60px !important;
|
||||
background: var(--header-bg-color);
|
||||
background: rgba(198, 226, 255, 0.9) !important;
|
||||
border-bottom: 1px solid #e6e6e6;
|
||||
}
|
||||
|
||||
@@ -489,7 +511,7 @@ onMounted(() => {
|
||||
}
|
||||
|
||||
.app-aside {
|
||||
background: var(--primary-dark) !important;
|
||||
background: rgba(31, 45, 61, 0.95) !important;
|
||||
height: calc(100vh - 60px) !important;
|
||||
transition: width 0.2s ease;
|
||||
}
|
||||
@@ -568,13 +590,13 @@ onMounted(() => {
|
||||
height: calc(100vh - 60px);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background: #f8f9fa;
|
||||
background: rgba(248, 249, 250, 0.9) !important;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.tabs-container {
|
||||
padding: 4px 8px;
|
||||
background: #f1f3f4;
|
||||
background: rgba(241, 243, 244, 0.95) !important;
|
||||
border-bottom: 1px solid #e6e6e6;
|
||||
}
|
||||
|
||||
@@ -707,7 +729,7 @@ onMounted(() => {
|
||||
.content-wrapper {
|
||||
margin: 8px;
|
||||
padding: 2px;
|
||||
background: #fff;
|
||||
background: rgba(255, 255, 255, 0.95) !important;
|
||||
border: 1px solid #e6e6e6;
|
||||
border-radius: 8px;
|
||||
flex: 1 !important;
|
||||
|
||||
@@ -31,7 +31,7 @@ import { ref, watch, defineProps, defineEmits, withDefaults, onMounted, onUnmoun
|
||||
import type { ElTree } from 'element-plus'
|
||||
|
||||
export interface FilterTreeNode {
|
||||
id: string | number
|
||||
id: string
|
||||
label: string
|
||||
children?: FilterTreeNode[]
|
||||
[key: string]: any
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
:tree-props="treeProps"
|
||||
lazy
|
||||
:load="loadChildren"
|
||||
row-key="id"
|
||||
:row-key="rowKey"
|
||||
>
|
||||
<slot name="columns" />
|
||||
</el-table>
|
||||
@@ -22,7 +22,7 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { defineProps, defineEmits, defineOptions } from 'vue'
|
||||
import { defineProps, defineEmits } from 'vue'
|
||||
|
||||
// 定义组件属性
|
||||
const props = defineProps({
|
||||
@@ -40,6 +40,10 @@ const props = defineProps({
|
||||
children: 'children',
|
||||
hasChildren: 'hasChildren'
|
||||
})
|
||||
},
|
||||
rowKey: {
|
||||
type: String,
|
||||
default: 'menuId'
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -115,7 +115,7 @@ const handleLogin = async () => {
|
||||
left: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: url('@/assets/login-bg.png') no-repeat center center;
|
||||
background: url('@/assets/login-box.png') no-repeat center center;
|
||||
background-size: cover;
|
||||
opacity: 0.95;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,184 @@
|
||||
<template>
|
||||
<el-form
|
||||
:model="formData"
|
||||
:rules="formRules"
|
||||
ref="formRef"
|
||||
label-width="100px"
|
||||
class="dialog-form-container"
|
||||
>
|
||||
<div class="form-row">
|
||||
<div class="form-col">
|
||||
<el-form-item label="父级菜单" prop="parentId">
|
||||
<el-select
|
||||
v-model="formData.parentId"
|
||||
placeholder="请选择父级菜单"
|
||||
clearable
|
||||
>
|
||||
<el-option label="根目录" value="0" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div class="form-col">
|
||||
<el-form-item label="菜单名称" prop="menuName">
|
||||
<el-input
|
||||
v-model="formData.menuName"
|
||||
placeholder="请输入用户名称"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-col">
|
||||
<el-form-item label="菜单类型" prop="menuType">
|
||||
<el-select
|
||||
v-model="formData.menuType"
|
||||
placeholder="请选择菜单类型"
|
||||
clearable
|
||||
>
|
||||
<el-option label="目录" value="M" />
|
||||
<el-option label="菜单" value="C" />
|
||||
<el-option label="按钮" value="F" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div class="form-col">
|
||||
<el-form-item label="路由地址" prop="path">
|
||||
<el-input
|
||||
v-model="formData.path"
|
||||
placeholder="请输入路由地址"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-col">
|
||||
<el-form-item label="菜单序号" prop="sort">
|
||||
<el-input
|
||||
v-model="formData.sort"
|
||||
placeholder="请输入菜单序号"
|
||||
clearable
|
||||
/>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div class="form-col">
|
||||
<el-form-item label="外链类别" prop="isIframe">
|
||||
<el-select
|
||||
v-model="formData.isIframe"
|
||||
placeholder="请选择外链类别"
|
||||
clearable
|
||||
>
|
||||
<el-option label="否" value="0" />
|
||||
<el-option label="是" value="1" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="form-row">
|
||||
<div class="form-col">
|
||||
<el-form-item label="菜单图标" prop="menuIcon">
|
||||
<el-select
|
||||
v-model="formData.menuIcon"
|
||||
placeholder="请选择菜单图标"
|
||||
clearable
|
||||
>
|
||||
<el-option label="模块A" value="0" />
|
||||
<el-option label="模块B" value="1" />
|
||||
<el-option label="模块C" value="2" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</div>
|
||||
<div class="form-col">
|
||||
<el-form-item label="菜单状态" prop="ustatus">
|
||||
<el-select
|
||||
v-model="formData.ustatus"
|
||||
placeholder="请选择菜单状态"
|
||||
clearable
|
||||
>
|
||||
<el-option label="停用" value="0" />
|
||||
<el-option label="在用" value="1" />
|
||||
<el-option label="锁定" value="2" />
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</div>
|
||||
</div>
|
||||
</el-form>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
|
||||
const props = defineProps({
|
||||
formData: {
|
||||
type: Object,
|
||||
required: true,
|
||||
default: () => ({
|
||||
parentId: '',
|
||||
menuName: '',
|
||||
menuType: '',
|
||||
path: '',
|
||||
sort: '',
|
||||
isIframe : '',
|
||||
menuIcon: '',
|
||||
ustatus: '1',
|
||||
})
|
||||
},
|
||||
isEdit: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
})
|
||||
|
||||
const formRef = ref(null)
|
||||
|
||||
const formRules = {
|
||||
parentId: [ { required: true, message: '请选择父级菜单', trigger: 'blur' } ],
|
||||
menuName: [ { required: true, message: '请输入菜单名称', trigger: 'blur' } ],
|
||||
menuType: [ { required: true, message: '请选择菜单类型', trigger: 'change' } ],
|
||||
path: [ { required: true, message: '请输入路由地址', trigger: 'change' } ],
|
||||
isIframe: [ { required: true, message: '请选择外链类型', trigger: 'change' } ],
|
||||
ustatus: [ { required: true, message: '请选择菜单状态', trigger: 'change' } ],
|
||||
}
|
||||
|
||||
const validate = async () => {
|
||||
if (!formRef.value) return false
|
||||
try {
|
||||
const valid = await formRef.value.validate()
|
||||
return valid
|
||||
} catch (error) {
|
||||
console.error('表单验证失败:', error)
|
||||
return false
|
||||
}
|
||||
}
|
||||
|
||||
const resetForm = () => {
|
||||
if (formRef.value) formRef.value.resetFields()
|
||||
}
|
||||
|
||||
defineExpose({ validate, resetForm })
|
||||
</script>
|
||||
|
||||
<style>
|
||||
<style scoped>
|
||||
.dialog-form-container {
|
||||
width: 100%;
|
||||
padding: 16px;
|
||||
margin: 0;
|
||||
border: 1px solid rgba(64, 158, 255, 0.15);
|
||||
border-radius: 8px;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.form-row {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
width: 100%;
|
||||
margin: 0 0 16px 0;
|
||||
gap: 20px;
|
||||
}
|
||||
.form-row:last-child { margin-bottom: 0; }
|
||||
.form-col { flex: 1; min-width: 180px; }
|
||||
</style>
|
||||
@@ -8,19 +8,17 @@
|
||||
<template #sidebar>
|
||||
<div class="sidebar-content">
|
||||
<FilterTree
|
||||
ref="filterTreeRef"
|
||||
:tree-data="treeData"
|
||||
:default-expand-all="false"
|
||||
:auto-height="true"
|
||||
:min-height="'100%'"
|
||||
@node-click="handleNodeClick"
|
||||
@search-change="handleSearchChange"
|
||||
/>
|
||||
</div>
|
||||
</template>
|
||||
<template #main>
|
||||
<div class="main-content">
|
||||
<vMeun />
|
||||
<vMeun :formParams="FormValues" />
|
||||
</div>
|
||||
</template>
|
||||
</ResizablePage>
|
||||
@@ -30,65 +28,30 @@
|
||||
import { ref, watch, onMounted, onUnmounted } from 'vue'
|
||||
import ResizablePage from '@/components/Layout/proResizable.vue'
|
||||
import FilterTree, { type FilterTreeNode } from '@/components/Table/proFilterTree.vue'
|
||||
import { getTreeMenuList } from '@/api/bizMenu'
|
||||
import vMeun from './list.vue'
|
||||
|
||||
const treeData = ref<FilterTreeNode[]>([
|
||||
{
|
||||
id: 1,
|
||||
label: 'Level one 1',
|
||||
children: [
|
||||
{
|
||||
id: 4,
|
||||
label: 'Level two 1-1',
|
||||
children: [
|
||||
{ id: 9, label: 'Level three 1-1-1' },
|
||||
{ id: 10, label: 'Level three 1-1-2' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
label: 'Level one 2',
|
||||
children: [
|
||||
{ id: 5, label: 'Level two 2-1' },
|
||||
{ id: 6, label: 'Level two 2-2' },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
label: 'Level one 3',
|
||||
children: [
|
||||
{ id: 7, label: 'Level two 3-1' },
|
||||
{ id: 8, label: 'Level two 3-2' },
|
||||
],
|
||||
},
|
||||
])
|
||||
const FormValues = ref({
|
||||
menuId: ''
|
||||
});
|
||||
|
||||
const filterTreeRef = ref<InstanceType<typeof FilterTree> | null>(null)
|
||||
const treeData = ref();
|
||||
|
||||
const handleNodeClick = (data: FilterTreeNode) => {
|
||||
const nodeInfo = {
|
||||
id: data.id,
|
||||
label: data.label,
|
||||
isLeaf: !data.children || data.children.length === 0,
|
||||
const getTreeListData = async () => {
|
||||
try {
|
||||
const res = await getTreeMenuList();
|
||||
treeData.value = res || []
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
console.log('=== 点击节点信息 ===', nodeInfo)
|
||||
}
|
||||
|
||||
let searchDebounceTimer: number | null = null
|
||||
const handleSearchChange = (value: string) => {
|
||||
if (searchDebounceTimer) clearTimeout(searchDebounceTimer)
|
||||
searchDebounceTimer = window.setTimeout(() => {
|
||||
console.log('搜索关键词(防抖后):', value.trim())
|
||||
}, 300)
|
||||
const handleNodeClick = (data: FilterTreeNode) => {
|
||||
FormValues.value.menuId = data.id;
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
const cleanup = () => {
|
||||
if (searchDebounceTimer) clearTimeout(searchDebounceTimer)
|
||||
}
|
||||
onUnmounted(cleanup)
|
||||
getTreeListData();
|
||||
})
|
||||
</script>
|
||||
|
||||
|
||||
@@ -4,26 +4,26 @@
|
||||
@search="handleSearch"
|
||||
@reset="handleReset"
|
||||
>
|
||||
<el-form-item label="用户名称:" class="search-item">
|
||||
<el-form-item label="菜单名称:" class="search-item">
|
||||
<el-input
|
||||
v-model="searchForm.uname"
|
||||
placeholder="请输入用户名称"
|
||||
v-model="searchForm.menuName"
|
||||
placeholder="请输入菜单名称"
|
||||
clearable
|
||||
class="search-input"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="登录账户:" class="search-item">
|
||||
<el-form-item label="路由地址:" class="search-item">
|
||||
<el-input
|
||||
v-model="searchForm.userName"
|
||||
placeholder="请输入登录账户"
|
||||
v-model="searchForm.path"
|
||||
placeholder="请输入路由地址"
|
||||
clearable
|
||||
class="search-input"
|
||||
/>
|
||||
</el-form-item>
|
||||
<el-form-item label="用户状态:" class="search-item">
|
||||
<el-form-item label="菜单状态:" class="search-item">
|
||||
<el-select
|
||||
v-model="searchForm.ustatus"
|
||||
placeholder="请选择用户状态"
|
||||
placeholder="请选择菜单状态"
|
||||
clearable
|
||||
class="search-select"
|
||||
>
|
||||
@@ -33,8 +33,6 @@
|
||||
</el-select>
|
||||
</el-form-item>
|
||||
</CSearch>
|
||||
|
||||
|
||||
<div class="main-wrapper">
|
||||
<div class="main-section">
|
||||
<div class="action-section">
|
||||
@@ -48,129 +46,193 @@
|
||||
<TreeTable
|
||||
:table-data="tableData"
|
||||
:loading="loading"
|
||||
@load-children="handleLoadChildren"
|
||||
:tree-props="treeProps"
|
||||
row-key="menuId"
|
||||
>
|
||||
<template #columns>
|
||||
<el-table-column prop="name" label="名称" width="350" :tree-node="true" />
|
||||
<el-table-column prop="desc" label="描述" width="400" />
|
||||
<el-table-column prop="createTime" label="创建时间" width="200" />
|
||||
<el-table-column prop="createTime" label="创建时间" width="200" />
|
||||
<el-table-column prop="createTime" label="创建时间" width="200" />
|
||||
<el-table-column prop="createTime" label="创建时间" width="200" />
|
||||
<el-table-column prop="createTime" label="创建时间" width="200" fixed="right" />
|
||||
<el-table-column prop="menuName" label="菜单名称" width="225" :tree-node="true" />
|
||||
<el-table-column prop="menuType" label="菜单类型" />
|
||||
<el-table-column prop="path" label="路由地址" width="225" />
|
||||
<el-table-column prop="sort" label="序号" sortable />
|
||||
<el-table-column prop="isIframe" label="是否外链" />
|
||||
<el-table-column prop="menuIcon" label="图标" />
|
||||
<el-table-column prop="ustatus" label="状态" sortable />
|
||||
<el-table-column label="操作" width="260" fixed="right" align="center">
|
||||
<template #default="scope">
|
||||
<el-button type="primary" size="small" icon="Edit"
|
||||
@click="handleEdit(scope.row)"
|
||||
class="operate-btn">
|
||||
编辑
|
||||
</el-button>
|
||||
<el-button type="danger" size="small" icon="Delete"
|
||||
@click="handleDelete(scope.row)"
|
||||
class="operate-btn">
|
||||
删除
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</template>
|
||||
</TreeTable>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<PDialog
|
||||
v-model="dialogVisible"
|
||||
:title="isEdit ? '编辑数据' : '新增数据'"
|
||||
@close="handleDialogClose"
|
||||
@reset="handleDialogReset"
|
||||
@confirm="handleSave"
|
||||
>
|
||||
<VForm ref="formComponentRef" :form-data="formData" :is-edit="isEdit" />
|
||||
</PDialog>
|
||||
</div>
|
||||
|
||||
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, reactive, onMounted } from 'vue'
|
||||
import { ref, reactive, watch , onMounted } from 'vue'
|
||||
import CSearch from '@/components/Search/proSearch.vue'
|
||||
import PDialog from '@/components/Dialog/proDialog.vue'
|
||||
import TreeTable from '@/components/Table/proTreeTable.vue'
|
||||
import { ElMessageBox, ElMessage } from 'element-plus'
|
||||
import { getHomeMenuList } from '@/api/bizMenu'
|
||||
import VForm from './form.vue'
|
||||
|
||||
|
||||
const searchForm = reactive({
|
||||
uname: '',
|
||||
ustatus: '',
|
||||
userName: ''
|
||||
const props = defineProps({
|
||||
formParams: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
}
|
||||
})
|
||||
|
||||
// 模拟树形表格数据(一级节点)
|
||||
const tableData = ref([
|
||||
{
|
||||
id: 1,
|
||||
name: '一级节点1',
|
||||
desc: '这是一级节点1(可展开到二级)',
|
||||
createTime: '2026-03-04',
|
||||
hasChildren: true // 标记有子节点
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: '一级节点2',
|
||||
desc: '这是一级节点2(无下级)',
|
||||
createTime: '2026-03-04',
|
||||
hasChildren: false
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: '一级节点3',
|
||||
desc: '这是一级节点3(可展开到二级,二级可展开到三级)',
|
||||
createTime: '2026-03-04',
|
||||
hasChildren: true
|
||||
}
|
||||
])
|
||||
const searchForm = reactive({
|
||||
menuName: '',
|
||||
path: '',
|
||||
ustatus: '',
|
||||
})
|
||||
|
||||
const dialogVisible = ref(false)
|
||||
const isEdit = ref(false)
|
||||
const formData = ref({})
|
||||
const formComponentRef = ref(null)
|
||||
|
||||
const tableData = ref()
|
||||
const loading = ref(false)
|
||||
const handleLoadChildren = async ({ row, resolve }) => {
|
||||
loading.value = true
|
||||
const treeProps = ref({
|
||||
children: 'children',
|
||||
hasChildren: false
|
||||
})
|
||||
|
||||
const handleSearch = () => {
|
||||
getTreeListData()
|
||||
}
|
||||
|
||||
const handleReset = () => {
|
||||
Object.assign(searchForm, {
|
||||
menuName: '',
|
||||
path: '',
|
||||
ustatus: '',
|
||||
})
|
||||
getTreeListData()
|
||||
}
|
||||
|
||||
const getTreeListData = async () => {
|
||||
try {
|
||||
// 模拟接口请求延迟
|
||||
await new Promise(resolve => setTimeout(resolve, 800))
|
||||
|
||||
const nodeId = row.id.toString()
|
||||
const level = nodeId.split('-').length // 通过ID分割判断层级:1=一级,2=二级,3=三级...
|
||||
|
||||
let childrenData = []
|
||||
// 一级节点的子节点(二级)
|
||||
if (level === 1) {
|
||||
childrenData = [
|
||||
{
|
||||
id: `${row.id}-1`,
|
||||
name: `二级节点-${row.id}-1`,
|
||||
desc: `这是【${row.name}】的二级子节点1`,
|
||||
createTime: '2026-03-04',
|
||||
// 只有一级节点3的二级子节点1可以展开到三级
|
||||
hasChildren: row.id === 3 ? true : false
|
||||
},
|
||||
{
|
||||
id: `${row.id}-2`,
|
||||
name: `二级节点-${row.id}-2`,
|
||||
desc: `这是【${row.name}】的二级子节点2`,
|
||||
createTime: '2026-03-04',
|
||||
hasChildren: false
|
||||
},
|
||||
{
|
||||
id: `${row.id}-3`,
|
||||
name: `二级节点-${row.id}-3`,
|
||||
desc: `这是【${row.name}】的二级子节点3`,
|
||||
createTime: '2026-03-04',
|
||||
hasChildren: false
|
||||
loading.value = true
|
||||
const reqParams = {
|
||||
...searchForm,
|
||||
...props.formParams,
|
||||
}
|
||||
]
|
||||
}
|
||||
// 二级节点的子节点(三级)
|
||||
else if (level === 2) {
|
||||
childrenData = [
|
||||
{
|
||||
id: `${row.id}-1`,
|
||||
name: `三级节点-${row.id}-1`,
|
||||
desc: `这是【${row.name}】的三级子节点1`,
|
||||
createTime: '2026-03-04',
|
||||
hasChildren: false
|
||||
},
|
||||
{
|
||||
id: `${row.id}-2`,
|
||||
name: `三级节点-${row.id}-2`,
|
||||
desc: `这是【${row.name}】的三级子节点2`,
|
||||
createTime: '2026-03-04',
|
||||
hasChildren: false
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
// 通过 resolve 回调返回子节点数据
|
||||
resolve(childrenData)
|
||||
const res = await getHomeMenuList(reqParams);
|
||||
tableData.value = res || []
|
||||
} catch (error) {
|
||||
console.error('加载子节点失败:', error)
|
||||
resolve([]) // 加载失败返回空数组
|
||||
console.log(error);
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
const handleEdit = (row) => {
|
||||
isEdit.value = true
|
||||
formData.value = { ...row }
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
const handleDelete = async (row) => {
|
||||
try {
|
||||
await ElMessageBox.confirm(
|
||||
"确定要删除此菜单吗?",
|
||||
'删除确认',
|
||||
{
|
||||
confirmButtonText: '确定',
|
||||
cancelButtonText: '取消',
|
||||
type: 'warning'
|
||||
}
|
||||
);
|
||||
|
||||
// 这里调用删除接口
|
||||
// const res = await deleteMenu(row.menuId);
|
||||
// if (res.success) {
|
||||
ElMessage.success('删除成功!');
|
||||
// 删除后重新加载列表
|
||||
getTreeListData();
|
||||
// }
|
||||
} catch (error) {
|
||||
console.error('删除失败', error);
|
||||
}
|
||||
}
|
||||
|
||||
const handleAdd = () => {
|
||||
isEdit.value = false
|
||||
formData.value = {}
|
||||
dialogVisible.value = true
|
||||
}
|
||||
|
||||
const handleExport = () => {
|
||||
console.log('导出菜单数据');
|
||||
// 导出逻辑
|
||||
}
|
||||
|
||||
|
||||
const handleDialogClose = () => {
|
||||
formData.value = {}
|
||||
isEdit.value = false
|
||||
dialogVisible.value = false
|
||||
}
|
||||
|
||||
const handleDialogReset = () => {
|
||||
if (formComponentRef.value) {
|
||||
formComponentRef.value.resetForm()
|
||||
ElMessage.info('表单已重置')
|
||||
}
|
||||
}
|
||||
|
||||
const handleSave = async () => {
|
||||
if (formComponentRef.value) {
|
||||
const isValid = await formComponentRef.value.validate()
|
||||
if (!isValid) {
|
||||
ElMessage.warning('表单验证失败,请检查必填项')
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
ElMessage.success(isEdit.value ? '编辑成功' : '新增成功')
|
||||
dialogVisible.value = false
|
||||
getDataList()
|
||||
}, 500)
|
||||
}
|
||||
|
||||
watch(
|
||||
() => props.formParams,
|
||||
() => {
|
||||
getTreeListData();
|
||||
},
|
||||
{ deep: true, immediate: true }
|
||||
)
|
||||
|
||||
onMounted(() => {
|
||||
getTreeListData();
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@@ -215,4 +277,9 @@ const handleLoadChildren = async ({ row, resolve }) => {
|
||||
:deep(.el-button .el-icon) {
|
||||
margin-right: 4px;
|
||||
}
|
||||
|
||||
/* 新增:操作按钮样式优化 */
|
||||
:deep(.operate-btn) {
|
||||
margin: 0 4px;
|
||||
}
|
||||
</style>
|
||||
@@ -67,15 +67,13 @@
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="180" align="center" fixed="right">
|
||||
<el-table-column label="操作" width="260" align="center" fixed="right">
|
||||
<template #default="scope">
|
||||
<!-- 编辑按钮添加 Edit 图标 -->
|
||||
<el-button size="small" type="primary" link @click="handleEdit(scope.row)">
|
||||
<el-button size="small" type="primary" @click="handleEdit(scope.row)">
|
||||
<el-icon><Edit /></el-icon>
|
||||
编辑
|
||||
</el-button>
|
||||
<!-- 删除按钮添加 Delete 图标 -->
|
||||
<el-button size="small" type="danger" link @click="handleDelete(scope.row)">
|
||||
<el-button size="small" type="danger" @click="handleDelete(scope.row)">
|
||||
<el-icon><Delete /></el-icon>
|
||||
删除
|
||||
</el-button>
|
||||
|
||||
@@ -52,13 +52,13 @@
|
||||
@current-change="handleCurrentChange"
|
||||
>
|
||||
<template #columns>
|
||||
<el-table-column prop="createTime" label="记录日期" />
|
||||
<el-table-column prop="createTime" label="记录日期" sortable fixed="left" />
|
||||
<el-table-column prop="userName" label="登录账户" />
|
||||
<el-table-column prop="uname" label="用户名称" />
|
||||
<el-table-column prop="sex" label="性别" />
|
||||
<el-table-column prop="sex" label="性别" sortable />
|
||||
<el-table-column prop="email" label="电子邮箱" />
|
||||
<el-table-column prop="phone" label="联系电话" />
|
||||
<el-table-column prop="ustatus" label="状态" min-width="100" align="center">
|
||||
<el-table-column prop="ustatus" label="状态" min-width="100" align="center" sortable>
|
||||
<template #default="scope">
|
||||
<el-tag
|
||||
:type="scope.row.ustatus === '1' ? 'success' : scope.row.ustatus === '2' ? 'warning' : 'danger'"
|
||||
@@ -67,15 +67,13 @@
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="操作" width="180" align="center" fixed="right">
|
||||
<el-table-column label="操作" width="260" align="center" fixed="right">
|
||||
<template #default="scope">
|
||||
<!-- 编辑按钮添加 Edit 图标 -->
|
||||
<el-button size="small" type="primary" link @click="handleEdit(scope.row)">
|
||||
<el-button size="small" type="primary" @click="handleEdit(scope.row)">
|
||||
<el-icon><Edit /></el-icon>
|
||||
编辑
|
||||
</el-button>
|
||||
<!-- 删除按钮添加 Delete 图标 -->
|
||||
<el-button size="small" type="danger" link @click="handleDelete(scope.row)">
|
||||
<el-button size="small" type="danger" @click="handleDelete(scope.row)">
|
||||
<el-icon><Delete /></el-icon>
|
||||
删除
|
||||
</el-button>
|
||||
|
||||
@@ -12,6 +12,7 @@ import com.mini.mybigscreen.utils.KeyUtil;
|
||||
import jakarta.annotation.Resource;
|
||||
import jakarta.servlet.http.HttpServletRequest;
|
||||
import jakarta.servlet.http.HttpSession;
|
||||
import org.apache.commons.lang3.ObjectUtils;
|
||||
import org.springframework.web.bind.annotation.PostMapping;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
@@ -42,4 +43,22 @@ public class userController {
|
||||
}
|
||||
return Result.error("账号或密码错误");
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改密码
|
||||
*/
|
||||
|
||||
@PostMapping("/editPasswd")
|
||||
public Result<?> getUserSave(String userId, String oldPasswd, String password) {
|
||||
QueryWrapper<HomeUser> query = new QueryWrapper<>();
|
||||
query.eq("user_id", userId)
|
||||
.eq("password", AesUtil.encrypt(oldPasswd));
|
||||
HomeUser user = userService.getOne(query, true);
|
||||
if (ObjectUtils.isEmpty(user)) {
|
||||
return Result.error("旧密码输入错误");
|
||||
}
|
||||
user.setPassword(AesUtil.encrypt(password));
|
||||
userService.update(user, query);
|
||||
return Result.success();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -4,7 +4,6 @@ import com.mini.mybigscreen.biz.domain.HomeMenu;
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@@ -18,11 +17,12 @@ public class Menu implements Serializable {
|
||||
private String menuIcon;
|
||||
private Integer sort;
|
||||
private Integer isIframe;
|
||||
private String ustatus;
|
||||
// 子菜单列表
|
||||
private List<HomeMenu> children;
|
||||
|
||||
|
||||
public Menu(String menuId,String parentId, String menuName, String menuType, String path,String menuIcon, Integer sort, Integer isIframe,List<HomeMenu> children) {
|
||||
public Menu(String menuId, String parentId, String menuName, String menuType, String path, String menuIcon, Integer sort, Integer isIframe, String ustatus, List<HomeMenu> children) {
|
||||
this.menuId = menuId;
|
||||
this.parentId = parentId;
|
||||
this.menuName = menuName;
|
||||
@@ -31,6 +31,7 @@ public class Menu implements Serializable {
|
||||
this.menuIcon = menuIcon;
|
||||
this.sort = sort;
|
||||
this.isIframe = isIframe;
|
||||
this.ustatus = ustatus;
|
||||
this.children = children;
|
||||
}
|
||||
}
|
||||
|
||||
24
src/main/java/com/mini/mybigscreen/Model/TreeMenu.java
Normal file
24
src/main/java/com/mini/mybigscreen/Model/TreeMenu.java
Normal file
@@ -0,0 +1,24 @@
|
||||
package com.mini.mybigscreen.Model;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class TreeMenu implements Serializable {
|
||||
|
||||
private String id;
|
||||
private String label;
|
||||
|
||||
private List<TreeMenu> children;
|
||||
|
||||
public TreeMenu(){
|
||||
|
||||
}
|
||||
public TreeMenu(String id, String label, List<TreeMenu> children) {
|
||||
this.id = id;
|
||||
this.label = label;
|
||||
this.children = children;
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
package com.mini.mybigscreen.biz.controller;
|
||||
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
|
||||
import com.mini.mybigscreen.Model.Menu;
|
||||
import com.mini.mybigscreen.Model.Result;
|
||||
import com.mini.mybigscreen.Model.TreeMenu;
|
||||
import com.mini.mybigscreen.biz.domain.HomeMenu;
|
||||
import com.mini.mybigscreen.biz.service.HomeMenuService;
|
||||
import jakarta.annotation.Resource;
|
||||
@@ -10,8 +12,8 @@ import org.springframework.web.bind.annotation.GetMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.*;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* <p>
|
||||
@@ -31,7 +33,88 @@ public class HomeMenuController {
|
||||
|
||||
|
||||
@GetMapping("list")
|
||||
public Result<?> getList() {
|
||||
public Result<?> getList(String menuName, String path, String ustatus, String menuId) {
|
||||
QueryWrapper<HomeMenu> query = new QueryWrapper<>();
|
||||
query.like(StrUtil.isNotBlank(menuName), "menu_name", menuName)
|
||||
.like(StrUtil.isNotBlank(path), "path", path)
|
||||
.eq(StrUtil.isNotBlank(ustatus), "ustatus", ustatus)
|
||||
.orderByAsc("sort").orderByDesc("create_time");
|
||||
List<HomeMenu> allHomeMenus = menuService.list(query);
|
||||
if (StrUtil.isNotBlank(menuId)) {
|
||||
Map<String, HomeMenu> menuMap = allHomeMenus.stream()
|
||||
.collect(Collectors.toMap(HomeMenu::getMenuId, menu -> menu));
|
||||
Set<String> targetIds = new HashSet<>();
|
||||
HomeMenu targetMenu = menuMap.get(menuId);
|
||||
if (targetMenu != null) {
|
||||
targetIds.add(menuId);
|
||||
boolean hasNew = true;
|
||||
while (hasNew) {
|
||||
hasNew = false;
|
||||
for (HomeMenu menu : allHomeMenus) {
|
||||
if (!targetIds.contains(menu.getMenuId()) && targetIds.contains(menu.getParentId())) {
|
||||
targetIds.add(menu.getMenuId());
|
||||
hasNew = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
allHomeMenus = allHomeMenus.stream()
|
||||
.filter(menu -> targetIds.contains(menu.getMenuId()))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
Map<String, List<HomeMenu>> parentIdToChildrenMap = new HashMap<>();
|
||||
for (HomeMenu menu : allHomeMenus) {
|
||||
parentIdToChildrenMap.computeIfAbsent(menu.getParentId(), k -> new ArrayList<>())
|
||||
.add(menu);
|
||||
}
|
||||
List<Menu> menuList = allHomeMenus.stream()
|
||||
.filter(homeMenu -> StrUtil.isNotBlank(menuId)
|
||||
? menuId.equals(homeMenu.getMenuId())
|
||||
: "0".equals(homeMenu.getParentId()))
|
||||
.map(topHomeMenu -> new Menu(
|
||||
topHomeMenu.getMenuId(),
|
||||
topHomeMenu.getParentId(),
|
||||
topHomeMenu.getMenuName(),
|
||||
topHomeMenu.getMenuType(),
|
||||
topHomeMenu.getPath(),
|
||||
topHomeMenu.getMenuIcon(),
|
||||
topHomeMenu.getSort(),
|
||||
topHomeMenu.getIsIframe(),
|
||||
topHomeMenu.getUstatus(),
|
||||
parentIdToChildrenMap.getOrDefault(topHomeMenu.getMenuId(), new ArrayList<>())
|
||||
))
|
||||
.collect(Collectors.toList());
|
||||
return Result.success(menuList);
|
||||
}
|
||||
|
||||
@GetMapping("treeList")
|
||||
public Result<?> getTreeList() {
|
||||
List<TreeMenu> menuList = new ArrayList<>();
|
||||
QueryWrapper<HomeMenu> query = new QueryWrapper<>();
|
||||
query.eq("ustatus", "1")
|
||||
.eq("parent_id", "0")
|
||||
.orderByAsc("sort");
|
||||
List<HomeMenu> pMenus = menuService.list(query);
|
||||
for (HomeMenu menu : pMenus) {
|
||||
QueryWrapper<HomeMenu> childQuery = new QueryWrapper<>();
|
||||
childQuery.eq("parent_id", menu.getMenuId());
|
||||
List<HomeMenu> childMenus = menuService.list(childQuery);
|
||||
List<TreeMenu> treeMenus = childMenus.stream()
|
||||
.map(homeMenu -> {
|
||||
TreeMenu treeMenu = new TreeMenu();
|
||||
treeMenu.setId(homeMenu.getMenuId());
|
||||
treeMenu.setLabel(homeMenu.getMenuName());
|
||||
return treeMenu;
|
||||
})
|
||||
.collect(Collectors.toList());
|
||||
menuList.add(new TreeMenu(menu.getMenuId(), menu.getMenuName(), treeMenus));
|
||||
}
|
||||
return Result.success(menuList);
|
||||
}
|
||||
|
||||
|
||||
@GetMapping("userList")
|
||||
public Result<?> getUserList() {
|
||||
List<Menu> menuList = new ArrayList<>();
|
||||
QueryWrapper<HomeMenu> query = new QueryWrapper<>();
|
||||
query.eq("ustatus", "1")
|
||||
@@ -51,6 +134,7 @@ public class HomeMenuController {
|
||||
menu.getMenuIcon(),
|
||||
menu.getSort(),
|
||||
menu.getIsIframe(),
|
||||
menu.getUstatus(),
|
||||
childMenus
|
||||
));
|
||||
}
|
||||
|
||||
@@ -73,5 +73,5 @@ public class HomeMenu implements Serializable {
|
||||
private String menuIcon;
|
||||
|
||||
@TableField("ustatus")
|
||||
private Integer ustatus;
|
||||
private String ustatus;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user