大屏项目初始化

This commit is contained in:
2026-03-04 23:42:19 +08:00
parent e23a1f3965
commit b9dbd542e7
19 changed files with 641 additions and 243 deletions

View File

@@ -9,4 +9,27 @@ export function getHomeMenuList(params) {
method: 'get',
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
})
}

View File

@@ -5,7 +5,7 @@ import request from '@/utils/request'
*/
export function login(data) {
return request({
url: 'userLogin',
url: '/userLogin',
method: 'post',
data
})
@@ -29,4 +29,16 @@ export function getUserInfo() {
url: '/userInfo',
method: 'get'
})
}
/**
* 修改密码
*/
export function updatePasswd(params) {
return request({
url: '/editPasswd',
method: 'post',
params: params
})
}

View File

Before

Width:  |  Height:  |  Size: 1.4 MiB

After

Width:  |  Height:  |  Size: 1.4 MiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 MiB

View File

@@ -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 {

View File

@@ -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;

View File

@@ -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

View File

@@ -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'
}
})

View File

@@ -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;
}

View File

@@ -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>

View File

@@ -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>

View File

@@ -1,176 +1,238 @@
<template>
<div class="data-manage-page">
<CSearch
@search="handleSearch"
@reset="handleReset"
<div class="data-manage-page">
<CSearch
@search="handleSearch"
@reset="handleReset"
>
<el-form-item label="菜单名称:" class="search-item">
<el-input
v-model="searchForm.menuName"
placeholder="请输入菜单名称"
clearable
class="search-input"
/>
</el-form-item>
<el-form-item label="路由地址:" class="search-item">
<el-input
v-model="searchForm.path"
placeholder="请输入路由地址"
clearable
class="search-input"
/>
</el-form-item>
<el-form-item label="菜单状态:" class="search-item">
<el-select
v-model="searchForm.ustatus"
placeholder="请选择菜单状态"
clearable
class="search-select"
>
<el-form-item label="用户名称:" class="search-item">
<el-input
v-model="searchForm.uname"
placeholder="请输入用户名称"
clearable
class="search-input"
/>
</el-form-item>
<el-form-item label="登录账户:" class="search-item">
<el-input
v-model="searchForm.userName"
placeholder="请输入登录账户"
clearable
class="search-input"
/>
</el-form-item>
<el-form-item label="用户状态:" class="search-item">
<el-select
v-model="searchForm.ustatus"
placeholder="请选择用户状态"
clearable
class="search-select"
>
<el-option label="停用" value="0" />
<el-option label="在用" value="1" />
<el-option label="锁定" value="2" />
</el-select>
</el-form-item>
</CSearch>
<div class="main-wrapper">
<div class="main-section">
<div class="action-section">
<el-button type="primary" icon="Plus" @click="handleAdd">
新增
</el-button>
<el-button type="success" icon="Download" @click="handleExport">
导出
</el-button>
</div>
<TreeTable
:table-data="tableData"
:loading="loading"
@load-children="handleLoadChildren"
>
<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" />
</template>
</TreeTable>
</div>
<el-option label="停用" value="0" />
<el-option label="在用" value="1" />
<el-option label="锁定" value="2" />
</el-select>
</el-form-item>
</CSearch>
<div class="main-wrapper">
<div class="main-section">
<div class="action-section">
<el-button type="primary" icon="Plus" @click="handleAdd">
新增
</el-button>
<el-button type="success" icon="Download" @click="handleExport">
导出
</el-button>
</div>
<TreeTable
:table-data="tableData"
:loading="loading"
:tree-props="treeProps"
row-key="menuId"
>
<template #columns>
<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
}
]
}
// 二级节点的子节点(三级)
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)
loading.value = true
const reqParams = {
...searchForm,
...props.formParams,
}
const res = await getHomeMenuList(reqParams);
tableData.value = res || []
} catch (error) {
console.error('加载子节点失败:', error)
resolve([]) // 加载失败返回空数组
console.log(error);
} finally {
loading.value = false
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>

View File

@@ -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>

View File

@@ -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>