提交 80a23c4d authored 作者: zhuyongshuai's avatar zhuyongshuai

灭火2025/8/19

上级 7701ae0c
# # 页面标题
VITE_APP_TITLE = '卫星仿真系统平台'
# # # 页面标题
# VITE_APP_TITLE = '卫星仿真系统平台'
# # 开发环境配置
# VITE_APP_ENV = 'development'
# # # 开发环境配置
# # VITE_APP_ENV = 'development'
# # 若依管理系统/开发环境
# VITE_API_URL = '/dev-api'
# # # 若依管理系统/开发环境
# # VITE_API_URL = '/dev-api'
# NODE_ENV="development"
# VITE_API_URL = http://211.149.190.82:18080
# # NODE_ENV="development"
VITE_API_URL = http://211.149.190.82:18080
# ENV = development # 本地环境
# VITE_API_URL = http://localhost:8888/ # 本地环境接口地址
# # ENV = development # 本地环境
# # VITE_API_URL = http://localhost:8888/ # 本地环境接口地址
# 本地环境
MODE_ENV = development
# # 本地环境
# MODE_ENV = development
# 本地环境接口地址
# # 本地环境接口地址
VITE_API_URL = /api
\ No newline at end of file
......@@ -21,7 +21,9 @@
"qs": "^6.13.1",
"vue": "^3.5.13",
"vue-router": "^4.5.0",
"vuedraggable": "4.1.0"
"vuedraggable": "4.1.0",
"vxe-pc-ui": "^4.8.20",
"vxe-table": "^4.15.9"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.2.1",
......@@ -1151,6 +1153,18 @@
}
}
},
"node_modules/@vxe-ui/core": {
"version": "4.2.11",
"resolved": "https://registry.npmmirror.com/@vxe-ui/core/-/core-4.2.11.tgz",
"integrity": "sha512-Cv0XKTGgFD+CD2MGixxM+k60Y3tFEJYErQj59tuPd0y0HuSFc7sQk0fuXdbcNgxZxtAi4zX3nsDk3kY+/HeAgQ==",
"dependencies": {
"dom-zindex": "^1.0.6",
"xe-utils": "^3.7.8"
},
"peerDependencies": {
"vue": "^3.2.0"
}
},
"node_modules/acorn": {
"version": "8.14.1",
"resolved": "https://registry.npmmirror.com/acorn/-/acorn-8.14.1.tgz",
......@@ -2043,6 +2057,11 @@
"url": "https://github.com/fb55/entities?sponsor=1"
}
},
"node_modules/dom-zindex": {
"version": "1.0.6",
"resolved": "https://registry.npmmirror.com/dom-zindex/-/dom-zindex-1.0.6.tgz",
"integrity": "sha512-FKWIhiU96bi3xpP9ewRMgANsoVmMUBnMnmpCT6dPMZOunVYJQmJhSRruoI0XSPoHeIif3kyEuiHbFrOJwEJaEA=="
},
"node_modules/domelementtype": {
"version": "1.3.1",
"resolved": "https://registry.npmmirror.com/domelementtype/-/domelementtype-1.3.1.tgz",
......@@ -6114,6 +6133,22 @@
"vue": "^3.0.1"
}
},
"node_modules/vxe-pc-ui": {
"version": "4.8.20",
"resolved": "https://registry.npmmirror.com/vxe-pc-ui/-/vxe-pc-ui-4.8.20.tgz",
"integrity": "sha512-//Ixq6Q1iBPZua4ia8/DjF7ZoWG2rb10mr2PuQHTJHsbI3I0mXhq6PyouMnMehjfFBhSyLMl9nrQbtIh/lFFeg==",
"dependencies": {
"@vxe-ui/core": "^4.2.11"
}
},
"node_modules/vxe-table": {
"version": "4.15.9",
"resolved": "https://registry.npmmirror.com/vxe-table/-/vxe-table-4.15.9.tgz",
"integrity": "sha512-zWFyW3Oi4bXQ5J2oiaaa+lXDbo4EBzX2EyjQKagBQYS4zaL8V5mzV4fxG4xWiW8V86pojbubSIVaiSKSaMj4tg==",
"dependencies": {
"vxe-pc-ui": "^4.8.0"
}
},
"node_modules/warning": {
"version": "4.0.3",
"resolved": "https://registry.npmmirror.com/warning/-/warning-4.0.3.tgz",
......@@ -6219,6 +6254,11 @@
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/xe-utils": {
"version": "3.7.8",
"resolved": "https://registry.npmmirror.com/xe-utils/-/xe-utils-3.7.8.tgz",
"integrity": "sha512-V/k6B/ASYir6yLYhp62DnM17po9u1N9mou/rn4if5WoFCsAO49JpCiVpkDpwCv4zxGfWmhWgzmz4FytWF+pDVw=="
},
"node_modules/zrender": {
"version": "5.6.0",
"resolved": "https://registry.npmmirror.com/zrender/-/zrender-5.6.0.tgz",
......
......@@ -25,7 +25,9 @@
"qs": "^6.13.1",
"vue": "^3.5.13",
"vue-router": "^4.5.0",
"vuedraggable": "4.1.0"
"vuedraggable": "4.1.0",
"vxe-pc-ui": "~4.8.20",
"vxe-table": "~4.15.9"
},
"devDependencies": {
"@vitejs/plugin-vue": "^5.2.1",
......
//建筑管理
import qs from 'qs'
import request from '@/utils/request'
// 人员添加接口
export function staffAddApi(data) {
/**
* 建筑添加接口
* @param {Object} data - 请求数据
* @returns {Promise} 返回请求结果
*/
export function buildingAddApi(data) {
return request({
url: '/user/add',
url: '/build/add',
method: 'post',
data,
})
}
// 人员修改
export function staffEditApi(data) {
/**
* 获取建筑信息接口
* @param {Object} data - 请求数据
* @returns {Promise} 返回请求结果
*/
export function buildingGetInfoApi(data) {
return request({
url: '/user/update',
url: '/build/getInfo',
method: 'post',
data,
})
}
// 详情
export function staffDetailApi(data) {
return request({
url: `/user/getInfo`,
method: 'get',
params: data
})
}
//分页
export function staffListApi(data) {
/**
* 建筑分页查询接口
* @param {Object} data - 请求数据
* @returns {Promise} 返回请求结果
*/
export function buildingPageApi(data) {
return request({
url: '/user/page',
url: '/build/page',
method: 'post',
data,
})
}
// 删除
export function staffDelApi(data) {
/**
* 删除建筑接口
* @param {Object} data - 请求数据
* @returns {Promise} 返回请求结果
*/
export function buildingRemoveApi(data) {
return request({
url: `/user/remove`,
method: 'get',
params: data
url: '/build/remove',
method: 'post',
data,
headers: { // 请求头
'Content-Type': 'multipart/form-data'
}
})
}
export function staffAerialViewApi(data) {
/**
* 更新建筑信息接口
* @param {Object} data - 请求数据
* @returns {Promise} 返回请求结果
*/
export function buildingUpdateApi(data) {
return request({
url: `/project/list`,
method: 'get',
params: data
url: '/build/update',
method: 'post',
data,
})
}
\ No newline at end of file
import qs from 'qs'
import request from '@/utils/request'
export function floorAddInfoAPI(data) {
return request({
url: '/build/floor/add',
method: 'post',
data,
})
}
export function floorGetDownFloorSortAPI(data) {
return request({
url: '/build/floor/getDownFloorSort',
method: 'GET',
data,
})
}
export function floorGetInfoAPI(data) {
return request({
url: '/build/floor/getInfo',
method: 'GET',
data,
})
}
export function floorGetUpFloorSortAPI(data) {
return request({
url: '/build/floor/getUpFloorSort',
method: 'GET',
data,
})
}
export function floorPageAPI(data) {
return request({
url: '/build/floor/page',
method: 'POST',
data,
})
}
export function floorRemoveAPI(data) {
return request({
url: '/build/floor/remove',
method: 'POST',
data,
headers: { // 请求头
'Content-Type': 'multipart/form-data'
}
})
}
export function floorUpdateAPI(data) {
return request({
url: '/build/floor/update',
method: 'POST',
data,
})
}
\ No newline at end of file
import qs from 'qs'
import request from '@/utils/request'
// 新增天线组
export function addcommonAntennaGroup(data) {
return request({
url: '/simu/commonAntenna',
method: 'post',
data,
})
}
// 修改
export function upcommonAntennaGroup(data) {
return request({
url: '/simu/commonAntenna',
method: 'put',
data,
})
}
// 详情
export function getantenna(data) {
return request({
url: `/simu/commonAntenna/${data.id}`,
method: 'get',
})
}
// 列表
export function getlist(data) {
return request({
url: `/simu/commonAntenna/list?${qs.stringify(data)}`,
method: 'get',
})
}
// 删除
export function deleantenna(data) {
return request({
url: `/simu/commonAntenna/${data.id}`,
method: 'delete',
})
}
\ No newline at end of file
import axios from 'axios'
import request from '../../utils/request'
//建筑管理
import qs from 'qs'
export default {
// 保留请求取消
cancels: {},
// 话务模型----------------------------------------------------------------------------------------------------------------
// 话务列表
simu_voiceslist(params) {
this.cancels['simu_voiceslist']?.()
return request.get(
`/simu/voice/list?${qs.stringify(params)}`,
params,
{
cancelToken: new axios.CancelToken(c => {
this.cancels['simu_voiceslist'] = c
})
}
)
},
//新增话务
simu_voicesAdd(params) {
this.cancels['simu_voicesAdd']?.()
return request.post(
`/simu/voice`,
params,
{
cancelToken: new axios.CancelToken(c => {
this.cancels['simu_voicesAdd'] = c
})
}
)
},
//修改话务
simu_voicesput(params) {
this.cancels['simu_voicesput']?.()
return request.put(
`/simu/voice`,
params,
{
cancelToken: new axios.CancelToken(c => {
this.cancels['simu_voicesput'] = c
})
}
)
},
//删除话务
simu_voicesRemo(data) {
this.cancels['simu_voicesRemo']?.()
return request.delete(
`/simu/voice/${data.ids}`,
{
cancelToken: new axios.CancelToken(c => {
this.cancels['simu_voicesRemo'] = c
})
}
)
},
//话务详细信息
simu_voicesId(params) {
this.cancels['simu_voicesId']?.()
return request.get(
`/simu/voice/${params.id}`,
params,
{
cancelToken: new axios.CancelToken(c => {
this.cancels['simu_voicesId'] = c
})
}
)
},
// 话务模型结束----------------------------------------------------------------------------------------------------------------
// 业务模型----------------------------------------------------------------------------------------------------------------
// 业务列表
simu_businesslist(params) {
this.cancels['simu_businesslist']?.()
return request.get(
`/simu/business/list?${qs.stringify(params)}`,
params,
{
cancelToken: new axios.CancelToken(c => {
this.cancels['simu_businesslist'] = c
})
}
)
},
//新增业务
simu_businessAdd(params) {
this.cancels['simu_businessAdd']?.()
return request.post(
`/simu/business`,
params,
{
cancelToken: new axios.CancelToken(c => {
this.cancels['simu_businessAdd'] = c
})
}
)
},
//修改业务
simu_businessput(params) {
this.cancels['simu_businessput']?.()
return request.put(
`/simu/business`,
params,
{
cancelToken: new axios.CancelToken(c => {
this.cancels['simu_businessput'] = c
})
}
)
import request from '@/utils/request'
// 图片上传
export function staffAvatorApi(data) {
return request({
url: '/common/image/upload',
method: 'post',
headers: { // 请求头
'Content-Type': 'multipart/form-data;'
},
//删除业务
simu_businessRemo(data) {
this.cancels['simu_businessRemo']?.()
return request.delete(
`/simu/business/${data.ids}`,
{
cancelToken: new axios.CancelToken(c => {
this.cancels['simu_businessRemo'] = c
data,
})
}
)
},
//业务详细信息
simu_businessId(params) {
this.cancels['simu_businessId']?.()
return request.get(
`/simu/business/${params.id}`,
params,
{
cancelToken: new axios.CancelToken(c => {
this.cancels['simu_businessId'] = c
})
}
)
},
// 业务模型结束------------------------------------------------------------------------------------------------------------- ----
//接口调节-------------------------------------------------------------------------------------------------------------
simu_rdFileInsList(params) {
this.cancels['simu_rdFileInsList']?.()
return request.get(
`/simu/rdFileIns/list?${qs.stringify(params)}`,
params,
{
cancelToken: new axios.CancelToken(c => {
this.cancels['simu_rdFileInsList'] = c
})
}
)
},
}
//人员管理
import qs from 'qs'
import request from '@/utils/request'
import { he } from 'element-plus/es/locales.mjs'
import { helper } from 'echarts'
// 人员添加接口
export function staffAddApi(data) {
return request({
......@@ -78,7 +80,9 @@ export function staffAddInfoApi(data) {
url: `/user/add`, // 请求的URL路径
method: 'POST', // 请求方法为POST
data, // 请求携带的数据
})
}
export function staffGetInfoApi(data) {
return request({ // 调用request方法发送HTTP请求
......@@ -93,12 +97,27 @@ export function staffRemoveInfoApi(data) {
url: `/user/remove`, // 请求的URL路径
method: 'POST', // 请求方法为POST
data, // 请求携带的数据
headers: { // 请求头
'Content-Type': 'multipart/form-data;'
}
})
}
export function staffResetPasswordApi(data) {
return request({ // 调用request方法发送HTTP请求
url: `/user/resetPassword`, // 请求的URL路径
method: 'POST', // 请求方法为POST
data, // 请求携带的数据
data
})
}
export function staffSetAbleApi(data) {
return request({ // 调用request方法发送HTTP请求
url: `/user/enable`, // 请求的URL路径
method: 'POST', // 请求方法为POST
data,
headers: { // 请求头
'Content-Type': 'multipart/form-data'
}
})
}
\ No newline at end of file
......@@ -5,7 +5,7 @@
<div class="tip2">国际花园小区</div>
<div class="avatar-container" v-if="useAppStore.isCurUserLogin">
<el-dropdown>
<el-avatar shape="square" size="large" src="https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png"/>
<el-avatar shape="square" size="large" :src="useAppStoreInstance.userInfo.avatar?useAppStoreInstance.userInfo.avatar: useAppStoreInstance.showavatarUrl"/>
<template #dropdown>
<el-dropdown-menu>
<el-dropdown-item @click="toSatffManage">人员管理</el-dropdown-item>
......@@ -56,7 +56,8 @@
<div class="breadcrumb-container">
<el-breadcrumb separator=">">
<el-breadcrumb-item v-for="(crumb, index) in breadcrumbs" :key="index"
@click="handleBreadcrumbClick(index)">
>
{{ crumb.name }}
</el-breadcrumb-item>
</el-breadcrumb>
......@@ -96,28 +97,26 @@
</template>
<script setup>
import { onMounted, ref, watch } from 'vue';
import { nextTick, onMounted, ref, watch } from 'vue';
import { useRoute, useRouter } from 'vue-router';
import useAppStore from '../store/module/app';
import { getToken, removeToken } from '../utils/auth';
import { pa } from 'element-plus/es/locales.mjs';
const router = useRouter();
const route = useRoute();
const useAppStoreInstance = useAppStore();
// 1.(根据菜单结构)
// 菜单结构
const menuList = ref([
{
name: '仪表盘',
id: 'dashboard',
path: '/analySisPage',
icon: new URL('../static/image/huo.png', import.meta.url).href,
children:[
children: [
{ name: '分析页', id: 'analysis', path: '/analysisPage' },
{ name: '工作台', id: 'workbench', path: '/workBenchPage'},
{ name: '工作台', id: 'workbench', path: '/workBenchPage' },
]
},
{
......@@ -127,7 +126,6 @@ const menuList = ref([
icon: new URL('../static/image/huo.png', import.meta.url).href,
children: [
{ name: '用户管理', id: 'user', path: '/usermanage' },
]
},
{
......@@ -137,7 +135,6 @@ const menuList = ref([
icon: new URL('../static/image/huo.png', import.meta.url).href,
children: [
{ name: '区域建筑管理', id: 'areaBuilding', path: '/areabuildmanage' },
]
},
{
......@@ -147,30 +144,29 @@ const menuList = ref([
icon: new URL('../static/image/huo.png', import.meta.url).href,
children: [
{ name: '无人机调度', id: 'droneDispatch', path: '/uavdispatch' },
{ name: '无人机监控', id: 'droneMonitor', path: '/drone/monitor' }
{ name: '无人机监控', id: 'droneMonitor', path: '/uavdetaildate' }
]
},
{
name: '火情管理',
id: 'fire',
path: '/fireSurveillance',
path: '/firesurveillance',
icon: new URL('../static/image/huo.png', import.meta.url).href,
children: [
{ name: '当前火情', id: 'fireMonitor', path: '/firesurveillance' },
{ name: '历史火情', id: 'fireHistory', path: '/historyfire' }
{ name: '当前火情', id: 'fireHistory', path: '/historyfire' },
{ name: '历史火情', id: 'fireMonitor', path: '/firesurveillance' },
]
},
]);
const openMenus = ref([]);
const breadcrumbs = ref([]);
const tabs = ref([]);
const activeTabKey = ref('');
const lastValidMenuPath = ref('/historyfire'); // 记录最后一个有效的菜单路径
// --------------------------
// 2. 工具函数
// --------------------------
// 获取菜单项名称
const getMenuNameByPath = (path) => {
for (const item of router.options.routes[4].children) {
if (item.path === path) {
......@@ -196,40 +192,25 @@ const getMenuNameByPath = (path) => {
return '未知页面';
};
// 3. 核心函数
const addTab = () => {
const path = route.path;
const existingTabIndex = tabs.value.findIndex(tab => tab.key === path);
const preTabs = ref(tabs.value);
console.log("existingTabIndexpath----------------12121----------",preTabs,"----------",path,'----------',tabs.value)
if (existingTabIndex === -1) {
// console.log("existingTabIndexpath--------------------------",tabs.value)
tabs.value.push({
key: path,
label: getMenuNameByPath(path),
path: path
});
}else {
tabs.value=preTabs.value;
// 检查路径是否在菜单中
const isPathInMenu = (path) => {
for (const item of menuList.value) {
if (item.path === path) return true;
if (item.children && item.children.some(c => c.path === path)) return true;
}
activeTabKey.value = path;
console.log("existingTabIndexpath---------56555555555555555555-----------------",activeTabKey.value)
return false;
};
const saveMenuState = () => {
const menuState = {
openMenus: openMenus.value
};
localStorage.setItem('menuState', JSON.stringify(menuState));
// 获取当前有效路径
const getCurrentValidPath = () => {
return isPathInMenu(route.path) ? route.path : lastValidMenuPath.value;
};
// 更新菜单激活状态
const updateMenuActiveState = () => {
useAppStoreInstance.num = route.path;
const currentPath = getCurrentValidPath();
useAppStoreInstance.num = currentPath;
const currentPath = route.path;
menuList.value.forEach((item, index) => {
if (item.children && item.children.length) {
const hasActiveChild = item.children.some(child => child.path === currentPath);
......@@ -238,102 +219,98 @@ const updateMenuActiveState = () => {
}
}
});
};
if (isPathInMenu(route.path)) {
lastValidMenuPath.value = route.path;
}
};
// 检查路径是否存在
function isPathInMenu(path) {
// 扁平化所有菜单项
const flattenMenu = (menus) => {
return menus.reduce((acc, item) => {
acc.push(item.path);
if (item.children) {
acc.push(...flattenMenu(item.children));
// 检查菜单项是否激活
const isActive = (item) => {
const currentPath = getCurrentValidPath();
if (currentPath === item.path) return true;
if (item.children && item.children.length) {
return item.children.some(child => child.path === currentPath);
}
return acc;
}, []);
};
return false;
};
const allPaths = flattenMenu(menuList.value);
return allPaths.includes(path);
}
// 检查子菜单项是否激活
const isSubMenuActive = (child) => {
const currentPath = getCurrentValidPath();
return currentPath === child.path;
};
// 更新面包屑导航
const updateBreadcrumbs = () => {
const path = route.path;
const crumbs = [];
crumbs.push({ name: '首页', path: '/' });
let preCrumbsDate = breadcrumbs.value;
// if (path === '/') {
// }
if (isPathInMenu(path)) {
menuList.value.forEach(mainItem => {
if (mainItem.path === path) {
crumbs.push({ name: mainItem.name, path: mainItem.path });
} else if (mainItem.children && mainItem.children.length) {
const matchedChild = mainItem.children.find(child => child.path === path);
if (matchedChild) {
crumbs.push({ name: mainItem.name, path: mainItem.path });
crumbs.push({ name: matchedChild.name, path: matchedChild.path });
const path = getCurrentValidPath();
const crumbs = [{ name: '首页', path: '/' }];
for (const item of menuList.value) {
if (item.path === path) {
crumbs.push({ name: item.name, path: item.path });
break;
}
if (item.children && item.children.length) {
const child = item.children.find(c => c.path === path);
if (child) {
crumbs.push({ name: item.name, path: item.path });
crumbs.push({ name: child.name, path: child.path });
break;
}
}
});
breadcrumbs.value = crumbs;
}else {
// 匹配不到时,返回首页
breadcrumbs.value = preCrumbsDate
console.log('匹配不到时,返回首页',breadcrumbs.value.length)
// useAppStoreInstance.num =
}
breadcrumbs.value = crumbs;
};
const initTabs = () => {
const initialTab = {
key: route.path,
label: getMenuNameByPath(route.path),
path: route.path
};
console.log("初始化标签页",initialTab);
tabs.value = [initialTab];
activeTabKey.value = route.path;
};
// 添加标签页
const addTab = () => {
const path = route.path;
const existingTabIndex = tabs.value.findIndex(tab => tab.key === path);
// --------------------------
// 4. 生命周期钩子
// --------------------------
onMounted(() => {
menuList.value.forEach((item, index) => {
if (item.children && item.children.length) {
openMenus.value[index] = false;
const preTabs = ref(tabs.value);
console.log("existingTabIndexpath----------------12121----------",preTabs,"----------",path,'----------',tabs.value)
if (existingTabIndex === -1) {
// console.log("existingTabIndexpath--------------------------",tabs.value)
tabs.value.push({
key: path,
label: getMenuNameByPath(path),
path: path
});
}else {
tabs.value=preTabs.value;
}
const savedState = localStorage.getItem('menuState');
if (savedState) {
const parsedState = JSON.parse(savedState);
openMenus.value = parsedState.openMenus || openMenus.value;
}
});
activeTabKey.value = path;
console.log("existingTabIndexpath---------56555555555555555555-----------------",activeTabKey.value)
};
initTabs();
updateBreadcrumbs();
updateMenuActiveState();
});
// 初始化标签页
const initTabs = () => {
const initialPath = isPathInMenu(route.path) ? route.path : lastValidMenuPath.value;
tabs.value = [{
key: initialPath,
label: getMenuNameByPath(initialPath),
path: initialPath
}];
activeTabKey.value = initialPath;
};
watch(route, () => {
updateMenuActiveState();
updateBreadcrumbs();
addTab();
saveMenuState();
}, { immediate: true });
// 保存菜单状态
const saveMenuState = () => {
console.log('保存菜单状态',router.options.history.state.back)
if (router.options.history.state.back !== null) {
const menuState = {
openMenus: openMenus.value,
lastValidMenuPath: isPathInMenu(router.options.history.state.back)? router.options.history.state.back:(localStorage.getItem('lastValidMenuPath')),
};
localStorage.setItem('menuState', JSON.stringify(menuState));}
};
// --------------------------
// 5. 其他辅助函数
// --------------------------
// 移除标签页
const removeTab = (key) => {
const index = tabs.value.findIndex(tab => tab.key === key);
if (index === -1) return;
if (key === activeTabKey.value) {
......@@ -346,47 +323,28 @@ const removeTab = (key) => {
tabs.value.splice(index, 1);
};
// 标签页点击处理
const handleTabClick = (tab) => {
const tabItem = tabs.value.find(item => item.key === tab.name);
console.log(tab,tab.props.name,"-------999999999999999999-------------");
router.push(tab.props.name);
const tabItem = tabs.value.find(item => item.key === tab.paneName);
if (tabItem) {
router.push(tabItem.path);
}
};
// 切换子菜单展开状态
const toggleSubMenu = (index) => {
openMenus.value[index] = !openMenus.value[index];
saveMenuState();
};
// 子菜单项点击处理
const handleSubMenuClick = (child) => {
useAppStoreInstance.num = child.path;
localStorage.setItem('menu', child.path);
router.push(child.path);
};
const menuclick = (item) => {
if (!item.children || !item.children.length) {
useAppStoreInstance.num = item.id;
localStorage.setItem('menu', item.id);
router.push(item.path);
}
};
const isActive = (item) => {
if (route.path === item.path) return true;
if (item.children && item.children.length) {
return item.children.some(child => child.path === route.path);
}
return false;
};
const isSubMenuActive = (child) => {
return route.path === child.path;
};
// 面包屑点击处理
const handleBreadcrumbClick = (index) => {
if (index < breadcrumbs.value.length - 1) {
const crumb = breadcrumbs.value[index];
......@@ -394,16 +352,75 @@ const handleBreadcrumbClick = (index) => {
}
};
// 跳转到人员管理
const toSatffManage = () => {
router.push({ path: '/staff' });
useAppStoreInstance.num = '/staff';
};
const noremoveItemFn = (data) => {
const valueToKeep = localStorage.getItem(data);
console.log("sdsadadddddd",valueToKeep)
localStorage.clear();
if (valueToKeep !== null) {
localStorage.setItem(data, valueToKeep);
}
};
// 用户退出
const curUserLogout = () => {
removeToken();
router.push('/login');
useAppStoreInstance.isCurUserLogin = false;
noremoveItemFn('rememberedAccount');
};
const showavatarUrl = ref('https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png');
// 初始化
onMounted(() => {
nextTick (()=>{
useAppStoreInstance.initavatarUrlFn()
})
// 初始化菜单展开状态
menuList.value.forEach((_, index) => {
openMenus.value[index] = false;
});
// 从本地存储恢复状态
const savedState = localStorage.getItem('menuState');
if (savedState) {
try {
const parsedState = JSON.parse(savedState);
openMenus.value = parsedState.openMenus || openMenus.value;
lastValidMenuPath.value = parsedState.lastValidMenuPath || '/historyfire';
} catch (e) {
console.error('Failed to parse menu state', e);
}
}
// console.log("currentDisplayPath.value--------------------------",isPathInMenu(route.path))
if (!isPathInMenu(route.path)) {
console.log("currentDisplayPath.value--------------------------",isPathInMenu(route.path),getCurrentValidPath(),JSON.parse(localStorage.getItem('menuState')).lastValidMenuPath)
router.replace(JSON.parse(localStorage.getItem('menuState')).lastValidMenuPath);
}
// console.log("currentDisplayPath.value--------------------------",isPathInMenu(route.path),getCurrentValidPath())
initTabs();
updateBreadcrumbs();
updateMenuActiveState();
});
// 监听路由变化
watch(route, () => {
useAppStoreInstance.initavatarUrlFn()
updateMenuActiveState();
updateBreadcrumbs();
addTab();
saveMenuState();
}, { immediate: true });
</script>
......
......@@ -21,6 +21,10 @@ import store from './store/index.js'
import router from './router/index.js'
import SvgIcon from '@/components/SvgIcon/index.vue' //
import elementIcons from '@/components/SvgIcon/svgicon.js'
import VxeUIAll from 'vxe-pc-ui'
import 'vxe-pc-ui/es/style.css'
import VxeUITable from 'vxe-table'
import 'vxe-table/es/style.css'
import './permission' // 路由拦截和加载页面进度条
// 全局配置组件
......@@ -40,7 +44,7 @@ const app = createApp(App)
const pinia = createPinia();
// 全局方法挂载
app.config.globalProperties.useDict = useDict
localStorage.setItem('lastValidMenuPath','/home')
// 全局组件挂载
......@@ -65,6 +69,6 @@ app.use(ElementPlus, {
size: Cookies.get('size') || 'default'
})
app.use(VxeUIAll).use(VxeUITable)
app.mount('#app')
import router from './router'
import { ElMessage } from 'element-plus'
import NProgress from 'nprogress'
import 'nprogress/nprogress.css'
import { getToken } from '@/utils/auth'
import useAppStore from './store/module/app'
......@@ -41,8 +40,8 @@ router.beforeEach((to, from, next) => {
} else {
localStorage.clear()//清空所有本地储存
// 重定向到首页
// next(`/login`) // 否则全部重定向到登录页
next()
next(`/login`) // 否则全部重定向到登录页
// next()
}
}
......
......@@ -57,6 +57,7 @@ export const constantRoutes = [
component: () => import('@/views/error/401.vue'),
hidden: true
},
{
path: '',
component: Layout,
......@@ -135,25 +136,44 @@ export const constantRoutes = [
path: '/firesurveillance',
component: () => import('../views/areafiremanage/firesurveillance/index.vue'),
name: 'fireSurveillance',
meta: { title: '当前火情', icon: 'dashboard', affix: true }
meta: { title: '历史火情', icon: 'dashboard', affix: true }
},
{
path: '/historyfire',
component: () => import('../views/areafiremanage/historyfire/index.vue'),
name: 'historyFire',
meta: { title: '历史火情', icon: 'dashboard', affix: true },
children:[
meta: { title: '当前火情', icon: 'dashboard', affix: true },
},
{
path: '/historyfire/floordetaildate',
path: '/floordetaildate',
component: () => import('../views/areafiremanage/historyfire/components/floordetail.vue'),
name: 'floorDetailDate',
meta: { title: '详细楼层火情', icon: 'dashboard', affix: true }
},
]
{
path: '/buildingdetaildate',
component: () => import('../views/areabuildmanage/components/showdetaildate.vue'),
name: 'buildingDetailDate',
meta: { title: '楼详细建筑数据', icon: 'dashboard', affix: true }
},
{
path: '/onefloorbuildingdetaildate',
component: () => import('../views/areabuildmanage/onefloor/index.vue'),
name: 'oneFloorBuildingDetailDate',
meta: { title: '层详细建筑数据', icon: 'dashboard', affix: true }
},
{
path: '/uavdetaildate',
component: () => import('../views/uavshowdate/uavmanage/index.vue'),
name: 'uavDetailDate',
meta: { title: '无人机管理', icon: 'dashboard', affix: true }
},
]
},
]
const router = createRouter({
......
import Cookies from 'js-cookie'
import { ElMessage } from 'element-plus';
import { tr } from 'element-plus/es/locales.mjs';
const useAppStore = defineStore(
'app',
{
......@@ -12,7 +13,14 @@ const useAppStore = defineStore(
device: 'desktop',
size: Cookies.get('size') || 'default',
num:1,
isCurUserLogin: false
isCurUserLogin: false,
userInfo: {},
showavatarUrl :'https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png',
position:{
10:'超级管理员',
1:'巡检人员',
0:"管理员"
}
}),
actions: {
toggleSideBar(withoutAnimation) {
......@@ -43,7 +51,32 @@ const useAppStore = defineStore(
this.sidebar.hide = status
},
initavatarUrlFn () {
try {
if (!(this.userInfo)) {
this.userInfo = JSON.parse(localStorage.getItem('user'))
console.log('initavatarUrlFn-----我是空------------',this.userInfo)
}else{
(this.userInfo).avatarUrl = (this.userInfo).avatarUrl ? (this.userInfo).avatarUrl : 'https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png';
console.log('initavatarUrlFn-----我不是空------------',this.userInfo)
}
} catch (error) {
ElMessage.error('avatarUrl is not valid',error);
}
},
chackCurPersionOpition() {
try {
// console.log(this.userInfo,'********************************************');
return this.userInfo.status === 0 ? true : false;
} catch (error) {
ElMessage.error('this.userInfo.state is not valid');
}
}
}
})
......
......@@ -22,9 +22,8 @@ const service = axios.create({
// request拦截器
service.interceptors.request.use(
config => {
console.log('--------------config', config.url)
service.interceptors.request.use(config => {
console.log('--------------config', config)
// 是否需要设置 token
const isToken = (config.headers || {}).isToken === false
console.log('--------------isToken', isToken)
......@@ -42,6 +41,7 @@ service.interceptors.request.use(
config.url = url;
}
if (!isRepeatSubmit && (config.method === 'post' || config.method === 'put')) {
const requestObj = {
url: config.url,
data: typeof config.data === 'object' ? JSON.stringify(config.data) : config.data,
......
......@@ -19,9 +19,9 @@
status-icon
>
<!-- 建筑编号 -->
<el-form-item label="建筑编号" prop="buildingNo" class="form-item">
<el-form-item label="建筑编号" prop="buildCode" class="form-item">
<el-input
v-model="form.buildingNo"
v-model="form.buildCode"
placeholder="请输入建筑编号"
class="input-field"
clearable
......@@ -29,9 +29,9 @@
</el-form-item>
<!-- 建筑名称 -->
<el-form-item label="建筑名称" prop="buildingName" class="form-item">
<el-form-item label="建筑名称" prop="buildName" class="form-item">
<el-input
v-model="form.buildingName"
v-model="form.buildName"
placeholder="请输入建筑名称"
class="input-field"
clearable
......@@ -39,9 +39,9 @@
</el-form-item>
<!-- 建筑层数 -->
<el-form-item label="建筑层数(地上)" prop="floorCount" class="form-item">
<el-form-item label="建筑层数(地上)" prop="floorNum" class="form-item">
<el-input
v-model="form.floorCount"
v-model="form.floorNum"
placeholder="请输入地上层数"
type="number"
min="0"
......@@ -61,13 +61,13 @@
<!-- 地下室层数 -->
<el-form-item
label="地下室层数"
prop="basementCount"
prop="undergroundFloorNum"
class="form-item"
:rules="form.hasBasement === 1 ? rules.basementCount : []"
>
<el-input
v-model="form.basementCount"
placeholder="请输入地下室层数"
v-model="form.undergroundFloorNum"
:placeholder ="`${form.hasBasement === 1 ? '请输入地下室层数' : '无地下室'}`"
type="number"
min="0"
class="input-field"
......@@ -110,35 +110,35 @@
import { ref, reactive } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import { Close } from '@element-plus/icons-vue';
import { buildingAddApi } from '../../../api/build';
// 表单引用
const addBuildingFormRef = ref(null);
// 提交加载状态
const submitting = ref(false);
const emit = defineEmits(['closeAddDrawer']);
const emit = defineEmits(['closeAddDrawer','handleRefreshInfoFn']);
// 表单数据
const form = reactive({
buildingNo: '', // 建筑编号
buildingName: '', // 建筑名称
floorCount: null, // 地上层数
buildCode: '', // 建筑编号
buildName: '', // 建筑名称
floorNum: null, // 地上层数
hasBasement: 1, // 是否含有地下室 (1:是, 2:否)
basementCount: null, // 地下室层数
undergroundFloorNum: null, // 地下室层数
description: '' // 建筑功能描述
});
// 表单验证规则
const rules = reactive({
buildingNo: [
buildCode: [
{ required: true, message: '请输入建筑编号', trigger: ['blur', 'change'] },
{ min: 2, max: 20, message: '编号长度在2-20个字符之间', trigger: ['blur', 'change'] }
],
buildingName: [
buildName: [
{ required: true, message: '请输入建筑名称', trigger: ['blur', 'change'] },
{ message: '名称长度在2-50个字符之间', trigger: ['blur', 'change'] }
],
floorCount: [
floorNum: [
{ required: true, message: '请输入地上层数', trigger: ['blur', 'change'] },
{
validator: (rule, value, callback) => {
......@@ -154,52 +154,39 @@ const rules = reactive({
},
trigger: ['blur', 'change']
}
],
basementCount: [
{ required: true, message: '请输入地下室层数', trigger: ['blur', 'change'] },
{
validator: (rule, value, callback) => {
if (form.hasBasement === 1) {
if (value === null || value === '') {
callback(new Error('请输入地下室层数'));
} else if (value < 0) {
callback(new Error('层数不能为负数'));
} else {
callback();
}
} else {
callback();
}
},
trigger: ['blur', 'change']
}
]
]
});
// 提交表单
const handleSubmit = async () => {
const handleSubmit = () => {
try {
submitting.value = true;
if (form.hasBasement === 2) {
rules.basementCount = [];
}
await addBuildingFormRef.value.validate();
// 准备提交数据
const submitData = {
...form,
hasBasement: form.hasBasement === 1
undergroundFloorNum:form.hasBasement === 1 ? form.undergroundFloorNum : 0
};
console.log('提交新增建筑数据:', submitData);
await new Promise(resolve => setTimeout(resolve, 1000));
buildingAddApi(submitData).then(res => {
if (res.code === 200) {
ElMessage.success('建筑信息新增成功!');
addBuildingFormRef.value.resetFields();
emit('closeAddDrawer');
emit('tableDataRefresh');
}else{
ElMessage.error('建筑信息新增响应代码错误!');
}
}).catch(err => {
ElMessage.error('添加建筑错误',err);
});
} catch (error) {
console.error('表单验证失败:', error);
ElMessage.error('请完善表单信息后重试');
// ElMessage.error('请完善表单信息后重试');
} finally {
submitting.value = false;
}
......@@ -213,8 +200,9 @@ const handleCancel = () => {
type: 'warning',
center: true
}).then(() => {
addBuildingFormRef.value.resetFields();
emit('closeAddDrawer');
addBuildingFormRef.value.resetFields();
}).catch(() => {});
};
......
<template>
<div class="add-building-modal">
<!-- 标题栏 -->
<div class="modal-header">
<h2 class="title">新增建筑</h2>
<el-icon class="close-icon" @click="handleClose" size="20">
<Close />
</el-icon>
</div>
<!-- 表单主体 -->
<el-form
ref="addBuildingFormRef"
:model="form"
:rules="rules"
label-width="120px"
label-position="left"
class="add-building-form"
status-icon
>
<!-- 建筑编号 -->
<el-form-item label="楼层类型" prop="buildingStye" class="form-item">
<el-radio-group v-model="form.buildingStye">
<el-radio :value="1">地上</el-radio>
<el-radio :value="0">地下</el-radio>
</el-radio-group>
</el-form-item>
<!-- 建筑名称 -->
<el-form-item label="楼层号" prop="buildingNo" class="form-item">
<el-select v-model="form.buildingNo" placeholder="选择楼层号" style="width: 240px">
<el-option
v-for="item in form.buildingNo"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<!-- 建筑层数 -->
<el-form-item label="楼层平面图" prop="floorPlan" class="form-item">
<!-- 上传图片 -->
<UpdataImg ref="floorUpdateloader" @getAvatar="getfloorPlan" :updataImageCount="1"></UpdataImg>
</el-form-item>
<!-- 是否有地下室 -->
<el-form-item label="航线绑定" prop="pathBinding" class="form-item">
<el-select v-model="form.pathBinding" placeholder="选择可绑定的航线" style="width: 240px">
<el-option
v-for="item in form.pathBinding"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<!-- 建筑功能描述 -->
<el-form-item label="楼层描述" prop="description" class="form-item">
<el-input
v-model="form.description"
type="textarea"
placeholder="请输入建筑功能描述"
:rows="3"
class="textarea-field"
show-word-limit
maxlength="200"
/>
</el-form-item>
</el-form>
<!-- 底部按钮 -->
<div class="modal-footer">
<el-button @click="handleCancel" class="cancel-btn" size="default">取消</el-button>
<el-button
@click="handleSubmit"
type="primary"
class="confirm-btn"
size="default"
:loading="submitting"
>
确认
</el-button>
</div>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import { Close } from '@element-plus/icons-vue';
import UpdataImg from '../../systemmanage/components/updataImg.vue';
// 表单引用
const addBuildingFormRef = ref(null);
// 提交加载状态
const submitting = ref(false);
const emit = defineEmits(['closeAddDrawer']);
// 表单数据
const form = reactive({
buildingStye:'',
// 楼层编号初始的时候要获取,数据类型 buildingNo:{label: '1层', value: '1'}
buildingNo:'',
// 航线编号初始的时候要获取,数据类型floorPlan:{label: '航线1', value: '1'}
floorPlan:'',
pathBinding:'',
description:'',
description: "",
});
const getfloorPlan = (url) => {
console.log(url,"const url = URL.createObjectURL(url);");
form.floorPlan = url;
};
// 表单验证规则
const rules = reactive({
});
// 提交表单
const handleSubmit = async () => {
try {
submitting.value = true;
await addBuildingFormRef.value.validate();
// 准备提交数据
const submitData = {
...form,
};
console.log('提交新增建筑数据:', submitData);
await new Promise(resolve => setTimeout(resolve, 1000));
ElMessage.success('建筑信息新增成功!');
addBuildingFormRef.value.resetFields();
emit('closeAddDrawer');
} catch (error) {
console.error('表单验证失败:', error);
ElMessage.error('请完善表单信息后重试');
} finally {
submitting.value = false;
}
};
const floorUpdateloader = ref(null);
// 取消操作
const handleCancel = () => {
ElMessageBox.confirm('确定要取消新增吗?已填写内容将不保存', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning',
center: true
}).then(() => {
console.log('取消新增');
addBuildingFormRef.value.resetFields();
floorUpdateloader.value.handleRemove()
emit('closeAddDrawer');
}).catch(() => {
console.log('报错了');
});
};
// 关闭弹窗
const handleClose = () => {
addBuildingFormRef.value.resetFields();
handleCancel();
};
</script>
<style scoped lang="scss">
.add-building-modal {
width: 100%;
max-width: 520px;
padding: 15px;
box-sizing: border-box;
background-color: #fff;
border-radius: 8px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.08);
// 标题栏样式
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
padding-bottom: 16px;
border-bottom: 1px solid #f0f0f0;
.title {
margin: 0;
font-size: 18px;
font-weight: 600;
color: #303133;
}
.close-icon {
cursor: pointer;
color: #909399;
transition: all 0.2s;
padding: 4px;
border-radius: 50%;
&:hover {
color: #f56c6c;
background-color: #fef0f0;
}
}
}
// 表单样式
.add-building-form {
.form-item {
margin-bottom: 18px;
:deep(.el-form-item__label) {
color: #606266;
font-weight: normal;
}
}
.input-field, .textarea-field {
width: 100%;
max-width: 380px;
:deep(.el-input__inner) {
height: 36px;
line-height: 36px;
border-radius: 4px;
}
:deep(.el-textarea__inner) {
min-height: 100px !important;
border-radius: 4px;
}
}
// 单选框样式
.radio-group {
display: flex;
gap: 20px;
.radio-btn {
margin: 0;
:deep(.el-radio__label) {
padding-left: 6px;
font-size: 14px;
}
}
}
}
// 底部按钮样式
.modal-footer {
display: flex;
justify-content: flex-end;
gap: 16px;
margin-top: 24px;
padding-top: 20px;
border-top: 1px solid #f0f0f0;
.cancel-btn {
width: 90px;
height: 36px;
border-radius: 4px;
color: #606266;
border-color: #dcdfe6;
&:hover {
color: #409eff;
border-color: #c6e2ff;
background-color: #ecf5ff;
}
}
.confirm-btn {
width: 90px;
height: 36px;
border-radius: 4px;
background-color: #409eff;
border-color: #409eff;
&:hover {
background-color: #66b1ff;
border-color: #66b1ff;
}
&:active {
background-color: #3a8ee6;
border-color: #3a8ee6;
}
}
}
// 响应式适配
@media (max-width: 768px) {
padding: 16px;
max-width: 100%;
.add-building-form {
.input-field, .textarea-field {
max-width: 100% !important;
}
.radio-group {
gap: 16px;
}
}
.modal-footer {
justify-content: center;
gap: 12px;
}
}
}
</style>
\ No newline at end of file
<template>
<div class="building-detail-container">
<!-- 页面标题 -->
<div class="page-title">
XX 单元 XX 号楼 XX 层详情信息
</div>
<!-- 折叠面板容器 -->
<el-collapse v-model="activeNames" class="detail-collapse" accordion>
<!-- 楼栋信息详情面板 -->
<el-collapse-item title="楼栋信息详情" name="buildingInfo">
<searchtop :searchShowData="searchShowData"></searchtop>
</el-collapse-item>
<!-- 楼层平面结构图面板 -->
<el-collapse-item title="楼层平面结构图" name="floorPlan">
<div class="floor-plan-container">
<el-empty
v-if="!floorPlanImage"
description="暂无楼层平面图"
class="empty-state"
/>
<el-image
v-else
style="width: 100px; height: 100px;background-color: aquamarine;"
:src="floorPlanImage[0]"
:preview-src-list="floorPlanImage"
show-progress
hide-on-click-modal="true"
fit="cover"
alt="楼层平面结构图"
/>
</div>
</el-collapse-item>
<!-- 航线列表面板 -->
<el-collapse-item title="航线列表" name="routeList">
<div class="table-container">
<el-table
:data="routeTableData"
stripe
border
class="custom-table"
:header-cell-style="headerCellStyle"
>
<el-table-column prop="id" label="序号" align="center" />
<el-table-column prop="routeId" label="航线ID" align="center" />
<el-table-column prop="takeoffPoint" label="起飞点" align="center" />
<el-table-column prop="task" label="任务点" align="center" />
<el-table-column prop="updateTime" label="更新时间" align="center">
<template #default="scope">
<el-popover
placement="top"
width="200"
trigger="hover"
content="具体于安防信息库内容填写<br>Zhen.2025.08.13"
>
<template #reference>
<span class="time-link">{{ scope.row.updateTime }}</span>
</template>
</el-popover>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<div class="pagination-container">
<el-pagination
layout="total, sizes, prev, pager, next"
:total="routeTotal"
:page-size="routePageSize"
:page-sizes="[5, 10, 20, 50]"
v-model:current-page="routeCurrentPage"
@size-change="handleRouteSizeChange"
@current-change="handleRouteCurrentChange"
/>
</div>
</div>
</el-collapse-item>
<!-- 火情信息列表面板 -->
<el-collapse-item title="火情信息列表" name="fireInfo">
<div class="table-container">
<el-table
:data="fireTableData"
stripe
border
class="custom-table"
:header-cell-style="headerCellStyle"
>
<el-table-column prop="id" label="序号" width="80" align="center" />
<el-table-column prop="fireTime" label="火情时间" width="180" align="center" />
<el-table-column prop="location" label="着火点" width="150" align="center" />
<el-table-column prop="description" label="火情描述" min-width="200" />
<el-table-column prop="recorder" label="记录人" width="120" align="center" />
<el-table-column prop="image" label="火情图片" width="120" align="center">
<template #default="scope">
<el-image @click="handleFireImageClick(scope.row)" style="width: 100px; height: 100px" :src="scope.row.image[0]" fit="fill" />
<!-- <el-image
v-if="scope.row.image"
:src="scope.row.image[0]"
alt="火情图片"
:preview-src-list="scope.row.image"
class="fire-image"
hide-on-click-modal = true
:initial-index="400000000000000"
>
<template #error>
<div class="image-error">
<el-icon><PictureFilled /></el-icon>
</div>
</template>
</el-image>
<span v-else class="no-image">暂无</span> -->
</template>
</el-table-column>
<el-table-column prop="level" label="火情等级" width="120" align="center">
<template #default="scope">
<el-tag
:type="getTagType(scope.row.level)"
size="small"
>
{{ scope.row.level }}
</el-tag>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<div class="pagination-container">
<el-pagination
layout="total, sizes, prev, pager, next"
:total="fireTotal"
:page-size="firePageSize"
:page-sizes="[5, 10, 20, 50]"
v-model:current-page="fireCurrentPage"
@size-change="handleFireSizeChange"
@current-change="handleFireCurrentChange"
/>
</div>
</div>
</el-collapse-item>
</el-collapse>
<el-dialog
v-model="isFireImagedialogVisible"
title="火情照片"
width="800"
:before-close="handleClose"
>
<div class="image-grid">
<div
v-for="(img, index) in fireImagedialogDate.image"
:key="index"
class="image-thumbnail"
@click="openPreview(index)"
>
<el-image
:src="img"
fit="cover"
class="thumbnail-image"
/>
</div>
</div>
<!-- 图片预览组件 -->
<el-image-viewer
v-if="previewVisible"
:url-list="fireImagedialogDate.image"
:initial-index="previewIndex"
@close="previewVisible = false"
/>
</el-dialog>
</div>
</template>
<script setup>
import { ref, reactive } from 'vue';
import searchtop from '../../commentcomponents/searchtop/index.vue'
// 折叠面板状态 - 默认展开第一个面板
const activeNames = ref(['floorPlan']);
// 楼栋信息
const buildingInfo = reactive({
buildingNo: '',
buildingName: '',
floor: ''
});
const searchShowData = ref([
{ label: '楼栋编号', placeholder: "请输入", type: 'input',content:'' },
{ label: '楼栋名称', placeholder: "请输入", type: 'input',content:'' },
{ label: '对应楼层', placeholder: "请输入", type: 'input',content:'' },
])
// 楼层平面图
const floorPlanImage = ref(['https://fuss10.elemecdn.com/a/3f/3302e58f9a181d2509f3dc0fa68b0jpeg.jpeg']); // 初始为空,实际项目中可替换为真实图片URL
const handleImageError = () => {
floorPlanImage.value = ''; // 图片加载失败时显示空状态
};
// 航线表格数据
const routeTableData = ref([
{ id: 1, routeId: '', takeoffPoint: '', task: '', updateTime: '2025-08-13' },
{ id: 2, routeId: '', takeoffPoint: '', task: '', updateTime: '2025-08-13' },
{ id: 3, routeId: '', takeoffPoint: '', task: '', updateTime: '2025-08-13' },
{ id: 4, routeId: '', takeoffPoint: '', task: '', updateTime: '2025-08-13' },
{ id: 5, routeId: '', takeoffPoint: '', task: '', updateTime: '2025-08-13' },
{ id: 6, routeId: '', takeoffPoint: '', task: '', updateTime: '2025-08-13' },
{ id: 7, routeId: '', takeoffPoint: '', task: '', updateTime: '2025-08-13' },
{ id: 8, routeId: '', takeoffPoint: '', task: '', updateTime: '2025-08-13' },
{ id: 9, routeId: '', takeoffPoint: '', task: '', updateTime: '2025-08-13' },
{ id: 10, routeId: '', takeoffPoint: '', task: '', updateTime: '2025-08-13' },
{ id: 11, routeId: '', takeoffPoint: '', task: '', updateTime: '2025-08-13' },
{ id: 12, routeId: '', takeoffPoint: '', task: '', updateTime: '2025-08-13' },
{ id: 13, routeId: '', takeoffPoint: '', task: '', updateTime: '2025-08-13' },
{ id: 14, routeId: '', takeoffPoint: '', task: '', updateTime: '2025-08-13' },
{ id: 15, routeId: '', takeoffPoint: '', task: '', updateTime: '2025-08-13' },
{ id: 16, routeId: '', takeoffPoint: '', task: '', updateTime: '2025-08-13' },
{ id: 17, routeId: '', takeoffPoint: '', task: '', updateTime: '2025-08-13' },
{ id: 18, routeId: '', takeoffPoint: '', task: '', updateTime: '2025-08-13' },
]);
const routeTotal = ref(routeTableData.value.length);
const routeCurrentPage = ref(1);
const routePageSize = ref(10);
// 火情表格数据
const fireTableData = ref([
{
id: 1,
fireTime: 'fsaf ',
location: 'fa',
description: 'gtr',
recorder: 'gts',
image: ['https://fuss10.elemecdn.com/a/3f/3302e58f9a181d2509f3dc0fa68b0jpeg.jpeg',
'https://fuss10.elemecdn.com/1/34/19aa98b1fcb2781c4fba33d850549jpeg.jpeg',
'https://fuss10.elemecdn.com/0/6f/e35ff375812e6b0020b6b4e8f9583jpeg.jpeg',
'https://fuss10.elemecdn.com/9/bb/e27858e973f5d7d3904835f46abbdjpeg.jpeg',
'https://fuss10.elemecdn.com/d/e6/c4d93a3805b3ce3f323f7974e6f78jpeg.jpeg',
'https://fuss10.elemecdn.com/3/28/bbf893f792f03a54408b3b7a7ebf0jpeg.jpeg',
'https://fuss10.elemecdn.com/2/11/6535bcfb26e4c79b48ddde44f4b6fjpeg.jpeg',],
level: ''
},
{
id: 1,
fireTime: '',
location: '',
description: '',
recorder: '',
image: ['https://fuss10.elemecdn.com/a/3f/3302e58f9a181d2509f3dc0fa68b0jpeg.jpeg',
'https://fuss10.elemecdn.com/1/34/19aa98b1fcb2781c4fba33d850549jpeg.jpeg',
],
level: ''
},
{
id: 1,
fireTime: '',
location: '',
description: '',
recorder: '',
image: ['https://fuss10.elemecdn.com/a/3f/3302e58f9a181d2509f3dc0fa68b0jpeg.jpeg',
'https://fuss10.elemecdn.com/1/34/19aa98b1fcb2781c4fba33d850549jpeg.jpeg',
'https://fuss10.elemecdn.com/0/6f/e35ff375812e6b0020b6b4e8f9583jpeg.jpeg',
'https://fuss10.elemecdn.com/9/bb/e27858e973f5d7d3904835f46abbdjpeg.jpeg',
'https://fuss10.elemecdn.com/d/e6/c4d93a3805b3ce3f323f7974e6f78jpeg.jpeg',
'https://fuss10.elemecdn.com/3/28/bbf893f792f03a54408b3b7a7ebf0jpeg.jpeg',
'https://fuss10.elemecdn.com/2/11/6535bcfb26e4c79b48ddde44f4b6fjpeg.jpeg',],
level: ''
},
{
id: 1,
fireTime: '',
location: '',
description: '',
recorder: '',
image: ['https://fuss10.elemecdn.com/a/3f/3302e58f9a181d2509f3dc0fa68b0jpeg.jpeg',
'https://fuss10.elemecdn.com/1/34/19aa98b1fcb2781c4fba33d850549jpeg.jpeg',
'https://fuss10.elemecdn.com/0/6f/e35ff375812e6b0020b6b4e8f9583jpeg.jpeg',
'https://fuss10.elemecdn.com/9/bb/e27858e973f5d7d3904835f46abbdjpeg.jpeg',
'https://fuss10.elemecdn.com/d/e6/c4d93a3805b3ce3f323f7974e6f78jpeg.jpeg',
'https://fuss10.elemecdn.com/3/28/bbf893f792f03a54408b3b7a7ebf0jpeg.jpeg',
'https://fuss10.elemecdn.com/2/11/6535bcfb26e4c79b48ddde44f4b6fjpeg.jpeg',],
level: ''
},
{
id: 2,
fireTime: '',
location: '',
description: '',
recorder: '',
image: '',
level: ''
},
{
id: 3,
fireTime: '',
location: '',
description: '',
recorder: '',
image: '',
level: ''
}
]);
const fireTotal = ref(fireTableData.value.length);
const fireCurrentPage = ref(1);
const firePageSize = ref(10);
const isFireImagedialogVisible = ref(false);
const fireImagedialogDate = ref([]);
const handleFireImageClick = (info) => {
console.log('点击图片索引:', info);
isFireImagedialogVisible.value = true;
fireImagedialogDate.value = info
};
// 图片预览状态
const previewVisible = ref(false);
const previewIndex = ref(0);
// 打开图片预览
const openPreview = (index) => {
previewIndex.value = index;
previewVisible.value = true;
};
// 表格头部样式
const headerCellStyle = {
'background-color': '#f5f7fa',
'color': '#1f2937',
'font-weight': '500'
};
// 获取火情等级标签样式
const getTagType = (level) => {
switch(level) {
case '严重':
return 'danger';
case '中等':
return 'warning';
case '轻微':
return 'info';
default:
return 'default';
}
};
// 航线分页事件
const handleRouteSizeChange = (val) => {
routePageSize.value = val;
console.log('航线当前页个数:', val);
// 实际项目中这里应该调用API加载数据
};
const handleRouteCurrentChange = (val) => {
routeCurrentPage.value = val;
console.log('航线当前页码:', val);
// 实际项目中这里应该调用API加载数据
};
// 火情分页事件
const handleFireSizeChange = (val) => {
firePageSize.value = val;
console.log('火情当前页个数:', val);
// 实际项目中这里应该调用API加载数据
};
const handleFireCurrentChange = (val) => {
fireCurrentPage.value = val;
console.log('火情当前页码:', val);
// 实际项目中这里应该调用API加载数据
};
</script>
<style scoped>
.building-detail-container {
padding: 20px;
background-color: #fff;
border-radius: 8px;
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.05);
}
.page-title {
font-size: 18px;
font-weight: 500;
color: #1f2937;
margin-bottom: 20px;
padding-bottom: 10px;
border-bottom: 1px solid #e5e7eb;
}
/* 折叠面板样式 */
.detail-collapse {
border: none;
}
:deep(.el-collapse-item) {
border: 1px solid #e5e7eb;
border-radius: 6px;
margin-bottom: 15px;
overflow: hidden;
}
:deep(.el-collapse-item__header) {
padding: 12px 20px;
background-color: #f9fafb;
border-bottom: 1px solid #e5e7eb;
font-size: 15px;
font-weight: 500;
}
:deep(.el-collapse-item__content) {
padding: 15px 20px;
}
/* 表格容器样式 */
.table-container {
width: 100%;
/* z-index: 0; */
/* background-color: #1f2937; */
overflow-x: auto;
}
.custom-table {
width: 100%;
height: 380px;
margin-bottom: 16px;
}
.pagination-container {
display: flex;
justify-content: flex-end;
margin-top: 16px;
}
/* 楼栋信息表单样式 */
.info-form {
padding: 5px 0;
}
.form-row {
display: flex;
flex-wrap: wrap;
gap: 20px;
}
.form-item {
display: flex;
align-items: center;
margin-bottom: 15px;
min-width: 250px;
flex: 1;
}
.form-label {
width: 100px;
color: #6b7280;
text-align: right;
margin-right: 12px;
white-space: nowrap;
}
.form-input {
flex: 1;
min-width: 150px;
}
/* 楼层平面图样式 */
.floor-plan-container {
display: flex;
justify-content: center;
align-items: center;
height: 220px;
border: 1px dashed #e5e7eb;
border-radius: 4px;
padding: 20px;
}
.floor-plan-image {
max-width: 100%;
max-height: 500px;
object-fit: contain;
border-radius: 4px;
}
.empty-state {
width: 100%;
}
/* 火情信息列表样式 */
.fire-image {
/* width: 80px;
height: 60px; */
object-fit: cover;
cursor: pointer;
border-radius: 4px;
}
.image-error {
width: 80px;
height: 60px;
display: flex;
justify-content: center;
align-items: center;
background-color: #f5f5f5;
color: #909399;
}
.no-image {
color: #909399;
font-size: 14px;
}
/* 航线列表样式 */
.time-link {
color: #409eff;
cursor: pointer;
text-decoration: underline;
}
.fire-image-dialog {
--el-dialog-padding-primary: 20px;
}
.image-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
gap: 15px;
max-height: 70vh;
overflow-y: auto;
padding: 10px;
}
.image-thumbnail {
position: relative;
height: 150px;
border-radius: 4px;
overflow: hidden;
cursor: pointer;
transition: all 0.3s ease;
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
}
.image-thumbnail:hover {
transform: translateY(-3px);
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
}
.thumbnail-image {
width: 100%;
height: 100%;
object-fit: cover;
}
/* 响应式调整 */
@media (max-width: 768px) {
.image-grid {
grid-template-columns: repeat(auto-fill, minmax(120px, 1fr));
}
.image-thumbnail {
height: 120px;
}
}
@media (max-width: 480px) {
.image-grid {
grid-template-columns: repeat(auto-fill, minmax(100px, 1fr));
}
.image-thumbnail {
height: 100px;
}
}
/* 响应式样式 */
@media (max-width: 768px) {
.form-row {
flex-direction: column;
gap: 0;
}
.form-item {
width: 100%;
}
.page-title {
font-size: 16px;
}
.pagination-container {
justify-content: center;
}
:deep(.el-collapse-item__header) {
padding: 10px 15px;
font-size: 14px;
}
:deep(.el-collapse-item__content) {
padding: 10px 15px;
}
}
</style>
\ No newline at end of file
......@@ -19,9 +19,9 @@
status-icon
>
<!-- 建筑编号 -->
<el-form-item label="建筑编号" prop="buildingNo" class="form-item">
<el-form-item label="建筑编号" prop="buildCode" class="form-item">
<el-input
v-model="form.buildingNo"
v-model="form.buildCode"
placeholder="请输入建筑编号"
class="input-field"
clearable
......@@ -29,9 +29,9 @@
</el-form-item>
<!-- 建筑名称 -->
<el-form-item label="建筑名称" prop="buildingName" class="form-item">
<el-form-item label="建筑名称" prop="buildName" class="form-item">
<el-input
v-model="form.buildingName"
v-model="form.buildName"
placeholder="请输入建筑名称"
class="input-field"
clearable
......@@ -39,9 +39,9 @@
</el-form-item>
<!-- 建筑层数 -->
<el-form-item label="建筑层数(地上)" prop="buildingFloorCount" class="form-item">
<el-form-item label="建筑层数(地上)" prop="floorNum" class="form-item">
<el-input
v-model="form.buildingFloorCount"
v-model="form.floorNum"
placeholder="请输入地上层数"
type="number"
min="0"
......@@ -58,10 +58,10 @@
label="地下室层数"
prop="basementCount"
class="form-item"
:rules=" rules.buildingBasementCount "
>
<el-input
v-model="form.buildingBasementCount"
v-model="form.undergroundFloorNum"
placeholder="请输入地下室层数"
type="number"
min="0"
......@@ -72,9 +72,9 @@
</el-form-item>
<!-- 建筑功能描述 -->
<el-form-item label="建筑功能描述" prop="buildingDescribe" class="form-item">
<el-form-item label="建筑功能描述" prop="description" class="form-item">
<el-input
v-model="form.buildingDescribe"
v-model="form.description"
type="textarea"
placeholder="请输入建筑功能描述"
:rows="3"
......@@ -105,7 +105,7 @@
import { ref, reactive, onMounted, onActivated } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import { Close } from '@element-plus/icons-vue';
import { buildingUpdateApi } from '@/api/build';
// 表单引用
const addBuildingFormRef = ref(null);
// 提交加载状态
......@@ -122,12 +122,12 @@ watch(() => props.isBuildingEditDrawer, (newVal) => {
});
// 表单数据
const form = reactive({
buildingNo: '', // 建筑编号
buildingName: '', // 建筑名称
buildingFloorCount: null, // 地上层数
buildCode: '', // 建筑编号
buildName: '', // 建筑名称
floorNum: null, // 地上层数
buildingBasementCount: null, // 地下室层数
buildingDescribe: '' // 建筑功能描述
undergroundFloorNum: null, // 地下室层数
description: '' // 建筑功能描述
});
// 初始化表单数据
......@@ -146,11 +146,11 @@ const initFormData = () => {
// 表单验证规则
const rules = reactive({
buildingNo: [
buildCode: [
{ required: true, message: '请输入建筑编号', trigger: ['blur', 'change'] },
{ min: 2, max: 20, message: '编号长度在2-20个字符之间', trigger: ['blur', 'change'] }
],
buildingName: [
buildName: [
{ required: true, message: '请输入建筑名称', trigger: ['blur', 'change'] },
{ message: '名称长度在2-50个字符之间', trigger: ['blur', 'change'] }
],
......@@ -193,27 +193,36 @@ const rules = reactive({
});
// 提交表单
const handleSubmit = async () => {
const handleSubmit = () => {
try {
submitting.value = true;
if (form.hasBasement === 2) {
rules.basementCount = [];
}
await addBuildingFormRef.value.validate();
const {buildCode,buildId, buildName,description} = form;
// 准备提交数据
const submitData = {
...form,
hasBasement: form.hasBasement === 1
buildCode,buildId, buildName,description
};
console.log('提交新增建筑数据:', submitData);
await new Promise(resolve => setTimeout(resolve, 1000));
ElMessage.success('建筑信息新增成功!');
// addBuildingFormRef.value.resetFields();
buildingUpdateApi(submitData).then(res => {
if (res.code === 200) {
ElMessage.success('建筑信息更新成功!');
emit('closeEditDrawer');
emit('tableDataRefresh');
}else{
ElMessage.error('建筑信息更新响应代码错误!');
}
}).catch(err => {
})
// console.log('提交新增建筑数据:', submitData);
// ElMessage.success('建筑信息新增成功!');
// // addBuildingFormRef.value.resetFields();
// emit('closeEditDrawer');
// emit('tableDataRefresh');
} catch (error) {
console.error('表单验证失败:', error);
ElMessage.error('请完善表单信息后重试');
......
<template>
<div class="dashboard-container" :style="containerStyle">
<main class="main-content">
<!-- 左侧区域 -->
<div class="left-section">
<!-- 左侧上部分 -->
<div class="left-top">
<!-- 区域信息概览 -->
<div class="info-card region-overview">
<searchtop :searchShowData="searchShowData"></searchtop>
</div>
</div>
<!-- 左侧下部分:统计区域 -->
<div class="info-card stats-alerts">
<div class="card-tabs">
<span class="title">建筑列表</span>
<div class="card-actions">
<el-button class="operation-button" type="primary" size="small">导入</el-button>
<el-button class="operation-button" type="primary" size="small">导出</el-button>
<el-button class="operation-button" type="primary" size="small" @click="handleAddInfoFn">新增建筑</el-button>
<el-button class="operation-button" type="primary" size="small" @click="handleRefreshInfoFn"><el-icon ><RefreshRight /></el-icon></el-button>
<el-button class="operation-button" type="primary" size="small"><el-icon><FullScreen /></el-icon></el-button>
</div>
</div>
<div class="card-table">
<tabledata :tableShowData="tableShowData"></tabledata>
</div>
</div>
</div>
</main>
<!-- 新增建筑抽屉 -->
<el-drawer v-model="isAddDrawer" title="添加建筑信息" :with-header="false" :before-close="beforeCloseAddBuildDrawer">
<Flooraddbuilding @closeAddDrawer="closeAddDrawer" @tableDataRefresh="handleRefreshInfoFn"></Flooraddbuilding>
</el-drawer>
<!-- 修改建筑抽屉 -->
<el-drawer v-model="isBuildingEditDrawer" title="修改用户信息" :with-header="false" :before-close="beforeCloseAddBuildDrawer">
<Flooraddbuilding :rowData="rowData" :isBuildingEditDrawer="isBuildingEditDrawer" @closeEditDrawer="closeBuildingEditDrawer" @tableDataRefresh="handleRefreshInfoFn"></Flooraddbuilding>
</el-drawer>
<!-- 图片预览组件 -->
<el-image-viewer
v-if="previewVisible && previewVisibledialogDate.length > 0"
:url-list="previewVisibledialogDate"
@close="previewVisible = false"
/>
</div>
</template>
<script setup>
import { reactive, computed, ref } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import searchtop from '../../commentcomponents/searchtop/index.vue';
import tabledata from '../../commentcomponents/tabledata/index.vue';
import router from '../../../router';
import Flooraddbuilding from './flooraddbuilding.vue';
import { useRoute,useRouter } from 'vue-router';
const route = useRoute();
// 搜索栏配置
const searchShowData = ref([
{ label: '楼层描述', placeholder: "请输入", type: 'input', content: '' },
{ label: '楼层号', placeholder: "请选择", type: 'select', content: '', options: [
{label:'火灾', value:'烟雾'},
{label:'烟雾', value:'火灾'}
] },
]);
// 布局配置
const layoutConfig = reactive({
containerMinHeight: '100vh',
cardSpacing: '1rem'
});
// 抽屉控制
const isAddDrawer = ref(false);
const isBuildingEditDrawer = ref(false);
const rowData = ref([]);
// 方法定义
const handleAddInfoFn = () => {
console.log("打开新增信息");
isAddDrawer.value = true;
}
const handleRefreshInfoFn = () => {
console.log("刷新数据");
}
const closeAddDrawer = () => {
console.log("000000-------1-----0000000000",isAddDrawer.value)
isAddDrawer.value = false;
}
const beforeCloseAddBuildDrawer = () => {
// 关闭前的处理逻辑
}
const oneBuildingSeeDetails = (data) => {
console.log("查看详情", data);
router.push({
path: '/onefloorbuildingdetaildate',
// params: { data: JSON.stringify(data) }
});
}
const oneBuildingDdeleteData = (data) => {
ElMessageBox.confirm('是否删除该建筑?此操作将无法撤销', '删除', {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
}).then(() => {
handleRefreshInfoFn()
ElMessage.success('已删除');
}).catch(() => {
ElMessage.info('取消删除');
});
}
const oneResetBuildingShowInfo = (data) => {
isBuildingEditDrawer.value = true;
rowData.value = data;
}
const closeBuildingEditDrawer = () => {
isBuildingEditDrawer.value = false;
console.log("000000------------0000000000")
}
// 图片预览状态
const previewVisible = ref(false);
const previewVisibledialogDate = ref([]);
const seeBuildingFloorPlan = (data) => {
console.log("查看楼层平面图", data);
previewVisible.value = true;
if(data.buildingFloorPlan.length > 0) {
previewVisibledialogDate.value = ['https://fuss10.elemecdn.com/a/3f/3302e58f9a181d2509f3dc0fa68b0jpeg.jpeg',
'https://fuss10.elemecdn.com/1/34/19aa98b1fcb2781c4fba33d850549jpeg.jpeg',]
return;
}else {
ElMessage.warning({
message: '该层暂无楼层平面图',
duration: 2000
})
}
}
// 表格数据
const tableShowData = ref([{
tableHeader: [
{label: '建筑编号', prop: 'buildingNo'},
{label: '建筑名称', prop: 'buildingName'},
{label: '建筑层数', prop: 'buildingFloorCount'},
{label: '楼层平面图', prop: 'buildingFloorPlan'},
{label: '更新时间', prop: 'buildingUpdataTime'},
{label: '楼层描述', prop: 'buildingDescription'},
{label: '操作', prop: 'buildingOperation'}
],
tableBody: [
{buildingNo: '张三', buildingName:"123456", buildingFloorCount: 11, buildingFloorPlan:
[
{label:'查看', type:'primary', icon:'EditPen', click: seeBuildingFloorPlan},
],
buildingUpdataTime: Date.now(),buildingDescription:' TEST ',
buildingOperation: [
{label:'查看详情', type:'primary', icon:'EditPen', click: oneBuildingSeeDetails},
{label:'删除', type:'danger', icon:'Delete', click: oneBuildingDdeleteData},
{label:'修改', type:'primary', icon:'EditPen', click: oneResetBuildingShowInfo}
]},
{buildingNo: '张89三', buildingName:"123456", buildingFloorCount: 11, buildingFloorPlan:
[
{label:'查看', type:'primary', icon:'EditPen', click: seeBuildingFloorPlan},
],
buildingUpdataTime: Date.now(),buildingDescription:' TEST ',
buildingOperation: [
{label:'查看详情', type:'primary', icon:'EditPen', click: oneBuildingSeeDetails},
{label:'删除', type:'danger', icon:'Delete', click: oneBuildingDdeleteData},
{label:'修改', type:'primary', icon:'EditPen', click: oneResetBuildingShowInfo}
]},
{buildingNo: '张45三', buildingName:"123456", buildingFloorCount: 11, buildingFloorPlan:
[
{label:'查看', type:'primary', icon:'EditPen', click: seeBuildingFloorPlan},
],
buildingUpdataTime: Date.now(),buildingDescription:' TEST ',
buildingOperation: [
{label:'查看详情', type:'primary', icon:'EditPen', click: oneBuildingSeeDetails},
{label:'删除', type:'danger', icon:'Delete', click: oneBuildingDdeleteData},
{label:'修改', type:'primary', icon:'EditPen', click: oneResetBuildingShowInfo}
]},
]
}]);
// 计算属性
const containerStyle = computed(() => ({
minHeight: layoutConfig.containerMinHeight,
'--card-spacing': layoutConfig.cardSpacing,
}));
</script>
<style scoped>
.dashboard-container {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: rgba(0, 0, 0, 0.05);
color: #333;
overflow: hidden;
}
.main-content {
display: flex;
gap: var(--card-spacing);
padding: 0.4rem;
height: 100vh;
box-sizing: border-box;
}
.left-section {
flex: 2;
display: flex;
flex-direction: column;
gap: var(--card-spacing);
height: 100%;
}
.left-top {
height: auto;
}
.info-card {
background-color: #fff;
padding: 1rem;
border-radius: 8px;
box-sizing: border-box;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
overflow: auto;
}
.region-overview {
flex: 1;
min-width: 200px;
}
.stats-alerts {
display: flex;
flex-direction: column;
gap: 1rem;
height: 67%;
}
.card-tabs {
display: flex;
border-bottom: 1px solid #292828;
font-size: 1.2rem;
color: #000000;
height: 8%;
align-items: center;
width: 100%;
padding: 10px;
}
.title {
width: 70%;
}
.card-actions {
display: flex;
flex: 1;
justify-content: space-between;
align-items: center;
height: 8%;
}
.operation-button {
width: 15%;
}
.card-table {
width: 100%;
flex:1;
overflow: hidden;
border-radius: 8px;
}
@media (max-width: 1024px) {
.left-top {
flex-direction: column;
}
.region-overview {
width: 100%;
flex: none;
}
}
@media (max-width: 768px) {
.stats-alerts {
flex-direction: column;
}
}
</style>
\ No newline at end of file
......@@ -23,7 +23,7 @@
</div>
</div>
<div class="card-table">
<tabledata :tableShowData="tableShowData"></tabledata>
<tabledata :tableShowData="tableShowData" @tableDataRefresh="handleRefreshInfoFn"></tabledata>
</div>
</div>
</div>
......@@ -44,13 +44,16 @@
</template>
<script setup>
import { reactive, computed, ref } from 'vue';
import { reactive, computed, ref, onMounted, watch } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import searchtop from '../commentcomponents/searchtop/index.vue';
import tabledata from '../commentcomponents/tabledata/index.vue';
import showResetBuildingInfo from './components/showResetBuildingInfo.vue';
import Addbuild from './components/addbuild.vue';
import router from '../../router';
import { buildingPageApi,buildingRemoveApi } from '../../api/build';
import { useRoute,useRouter } from 'vue-router';
const route = useRoute();
// 搜索栏配置
const searchShowData = ref([
{ label: '功能描述', placeholder: "请输入", type: 'input', content: '' },
......@@ -84,6 +87,7 @@ const handleAddInfoFn = () => {
const handleRefreshInfoFn = () => {
console.log("刷新数据");
initTableDateFn()
}
const closeAddDrawer = () => {
isAddDrawer.value = false;
......@@ -97,6 +101,10 @@ const beforeCloseAddBuildDrawer = () => {
const buildingSeeDetails = (data) => {
console.log("查看详情", data);
router.push({
path: '/buildingdetaildate',
params: { data: JSON.stringify(data) },
});
}
const buildingDdeleteData = (data) => {
......@@ -104,13 +112,34 @@ const buildingDdeleteData = (data) => {
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
}).then(() => {
handleRefreshInfoFn()
ElMessage.success('已删除');
}).catch(() => {
ElMessage.info('取消删除');
})
.then(() => {
try {
// 用户点击「确认」
const {buildId,buildName} = data;
buildingRemoveApi({ buildId: buildId })
.then(res => {
if (res.code === 200) {
ElMessage.success("`${buildName}已删除`");
handleRefreshInfoFn();
} else {
ElMessage.error(res.message || '删除失败'); // 更明确的错误提示
}
})
.catch(err => {
// API 请求失败
ElMessage.error(err?.message || '删除请求失败');
});
}
} catch (error) {
console.error(error,'API 请求失败');
}
})
.catch(() => {
// 用户点击「取消」
ElMessage.info('已取消删除'); // 移除多余的 `err` 参数
});
};
const resetBuildingShowInfo = (data) => {
......@@ -121,31 +150,62 @@ const resetBuildingShowInfo = (data) => {
const closeBuildingEditDrawer = () => {
isBuildingEditDrawer.value = false;
// console.log("000000------------0000000000",isBuildingEditDrawer.value)
}
// 表格数据
const tableShowData = ref([{
tableHeader: [
{label: '建筑编号', prop: 'buildingNo'},
{label: '建筑名称', prop: 'buildingName'},
{label: '建筑层数', prop: 'buildingFloorCount'},
{label: '火情次数', prop: 'buildingFireCount'},
{label: '更新时间', prop: 'buildingUpdataTime'},
{label: '操作', prop: 'buildingOperation'}
{label: '建筑编号', prop: 'buildCode'},
{label: '建筑名称', prop: 'buildName'},
{label: '建筑层数', prop: 'totalFloorNum'},
{label: '火情次数', prop: 'fireNum'},
{label: '更新时间', prop: 'updateTime'},
{label: '操作', prop: 'Operation'}
],
tableBody: [
{buildingNo: '张三', buildingName:"123456", buildingFloorCount: 11, buildingFireCount:3, buildingUpdataTime: Date.now(), buildingOperation: [
{label:'查看详情', type:'primary', icon:'EditPen', click: buildingSeeDetails},
{label:'删除', type:'danger', icon:'Delete', click: buildingDdeleteData},
{label:'修改', type:'primary', icon:'EditPen', click: resetBuildingShowInfo}
]},
{buildingNo: '张s三', buildingName:"123456", buildingFloorCount: 11, buildingFireCount:3, buildingUpdataTime: Date.now(), buildingOperation: [
{label:'查看详情', type:'primary', icon:'EditPen', click: buildingSeeDetails},
{label:'删除', type:'danger', icon:'Delete', click: buildingDdeleteData},
{label:'修改', type:'primary', icon:'EditPen', click: resetBuildingShowInfo}
]},
]
tableBody: []
}]);
const Operation = ref([
{ label: '查看详情', type: 'primary', icon: 'EditPen', click: buildingSeeDetails },
{ label: '删除', type: 'danger', icon: 'Delete', click: buildingDdeleteData },
{ label: '修改', type: 'primary', icon: 'EditPen', click: resetBuildingShowInfo }
]);
// onMounted(() => {
// initTableDateFn()
// });
const initTableDateFn = () => {
console.log('初始化数据');
buildingPageApi({currentPageNum: 1, currentPageSize: 10}).then(res => {
if(res.code === 200){
let {list ,total,pageSize} = res.data;
if(!list){
ElMessage.warning('建筑列表暂无数据,请先添加建筑数据');
}else{
tableShowData.value[0].tableBody = []
list = list.filter(element => {
return element.isDeleted === 0;
});
list.forEach(element => {
element.Operation = [...Operation.value]
});
tableShowData.value[0].tableBody = list
tableShowData.value[0].total = total
tableShowData.value[0].pageSize = pageSize
console.log(tableShowData.value, '展示数据');
}
}
}).catch(err => {
ElMessage.error(err?.message || "建筑列表请求失败")
})
};
// 监听路由变化
watch(route, () => {
initTableDateFn()
}, { immediate: true });
// 计算属性
const containerStyle = computed(() => ({
minHeight: layoutConfig.containerMinHeight,
......
<template>
<div class="dashboard-container" :style="containerStyle">
<main class="main-content">
<!-- 左侧区域 -->
<div class="left-section">
<!-- 左侧下部分:统计区域 -->
<div class="info-card stats-alerts">
<div class="card-table">
<onefloorbuildingdetaildate></onefloorbuildingdetaildate>
</div>
</div>
</div>
</main>
</div>
</template>
<script setup>
import { reactive, computed, ref, onMounted } from 'vue';
// import searchtop from '../../commentcomponents/searchtop/index.vue';
import onefloorbuildingdetaildate from '../components/onefloorbuildingdetaildate.vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { staffGetPageInfoApi } from "../../../api/staff.js"
import { message } from 'ant-design-vue';
// import Operation from 'ant-design-vue/es/transfer/operation';
// 定义一个名为searchShowData的ref变量,它是一个数组,数组中的每个元素都是一个对象,对象中包含了label、placeholder、type、content和options等属性
const searchShowData = ref([
{ label: '用户姓名', placeholder: "请输入", type: 'input',content:'' },
{ label: '用户状态', placeholder: "请选择", type: 'select',content:'' , options: [{label:'启用', value:'启用'}, {label:'禁用', value:'禁用'}] },
{ label: '用户角色', placeholder: "请选择", type: 'select', content:'' ,options: [{label:'管理员', value:'管理员'}, {label:'巡查员', value:'巡查员'}]},
])
// 响应式布局配置
const layoutConfig = reactive({
containerMinHeight: '100vh',
cardSpacing: '1rem',
statsMinHeight: '300px'
});
const isUserInfoDialogVisible = ref(false); // 控制用户信息弹窗的显示状态
/**
* 刷新表格数据的方法
* 当调用此方法时,会触发表格数据的重新加载
*/
const tableDataRefresh = () => {
initTableData()
console.log("000000-------2-----0000000000,这里获取到最新的数据")
}
/**
* 控制编辑抽屉的显示状态
* @type {Ref<boolean>} - 响应式布尔值,用于控制编辑抽屉的显示
*/
const isEditDrawer = ref(false)
/**
* 存储行数据
* @type {Ref<Array>} - 响应式数组,用于存储当前选中的行数据
*/
const rowData = ref([])
/**
* 编辑数据的方法
* @param {Object} data - 要编辑的数据对象
*/
const editData = (data) => {
// 打开编辑抽屉
isEditDrawer.value = true
// 设置要编辑的数据
rowData.value = data
console.log("000000-------1-----0000000000",data)
}
/**
* 删除数据的方法
* @param {Object} data - 要删除的数据对象
* 显示确认对话框,用户确认后执行删除操作
*/
const deleteData = (data) => {
console.log("000000---2---------0000000000",data)
ElMessageBox.confirm(
'删除操作不可逆,是否继续?',
'删除',
{
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
draggable: true,
}
)
.then(() => {
console.log("000000---1-456464564------0000000000","我是确认")
try {
const res = staffRemoveInfoApi({id:data.id});
if (res.code === 200) {
ElMessage({ type: 'success', message: '已删除' });
tableDataRefresh();
} else {
ElMessage({ type: 'error', message: '删除失败:' + res.msg });
}
} catch (err) {
// API 请求失败时在此捕获,不向上冒泡
ElMessage({ type: 'error', message: '删除失败(网络错误)' });
// console.error('删除接口失败:', err);
}
})
.catch(() => {
ElMessage({
type: 'info',
message: '取消删除',
})
})
}
// const deleteUserDate = (data) => {
// staffRemoveInfoApi(data).then((res) => {
// console.log(res,'--------------8--');
// if(res.code==200){
// ElMessage({
// type: 'success',
// message: '已删除',
// })
// tableDataRefresh()
// }
// }).catch(() => {
// ElMessage({
// type: 'info',
// message: '删除shibai',
// })
// })
// }
const isResetPasswordDialogVisible = ref(false); // 控制重置密码弹窗的显示状态
const resetPasswordData = ref({});
const resetData = (data) => {
console.log("000000-----3-------0000000000",data)
isResetPasswordDialogVisible.value = true;
resetPasswordData.value = data;
}
const showuserinfodata = ref({})
const userData = (data) => {
console.log("000000------4------0000000000",data)
isUserInfoDialogVisible.value = true
showuserinfodata.value = data
}
const tableShowData = ref([{
tableHeader:[
{label: '姓名', prop: 'name'},
{label: '电话', prop: 'phone'},
{label: '是否可用', prop: 'status'},
{label: '用户权限', prop: 'role'},
{label: '创建时间', prop: 'createTime'},
{label: '更新时间', prop: 'updateTime'},
{label: '操作', prop: 'Operation'}
],
tableBody:[
]
}
])
const Operation = ref( [ { label: '编辑', type: 'primary', icon: 'EditPen', click: editData },
{ label: '删除', type: 'danger', icon: 'Delete', click: deleteData },
{ label: '用户信息', type: 'primary', icon: 'EditPen', click: userData },
{ label: '重置密码', type: 'primary', icon: 'EditPen', click: resetData }]
)
const initTableData = () => {
staffGetPageInfoApi({
"currentPageNum": 1,
"currentPageSize": 10,
}).then(res => {
if(res.code === 200){
let tempData = res.data.list
tableShowData.value[0].tableBody = []
//获取未逻辑删除的数据
console.log(res.data,"--------999999999----")
tempData = tempData.filter(element => {
return element.isDeleted === 0;
});
tempData.forEach(element => {
element.Operation = [...Operation.value]
});
tableShowData.value[0].tableBody = tempData
tableShowData.value[0].total = res.data.total
tableShowData.value[0].pageSize = res.data.pageSize
console.log(tableShowData.value, '展示数据');
}
}).catch(err => {
ElMessage.error(err.message)
})
}
onMounted(() => {
// console.log(tableShowData.value, '错误');
initTableData()
})
// 计算容器样式
const containerStyle = computed(() => ({
minHeight: layoutConfig.containerMinHeight,
'--card-spacing': layoutConfig.cardSpacing,
}));
</script>
<style scoped>
.dashboard-container {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: rgba(0, 0, 0, 0.05);
color: #333;
}
.main-content {
display: flex;
gap: var(--card-spacing);
padding: 0.4rem;
height: 100vh;
box-sizing: border-box;
}
.left-section {
flex: 2;
display: flex;
flex-direction: column;
gap: var(--card-spacing);
height: 100%;
}
.left-top {
height: auto;
}
/* 通用卡片样式 */
.info-card {
background-color: #fff;
padding: 1rem;
border-radius: 8px;
box-sizing: border-box;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
overflow: auto;
}
.region-overview {
flex: 1;
min-width: 200px;
}
/* 统计区域 */
.stats-alerts {
display: flex;
flex-direction: column;
gap: 1rem;
height: 79.5%;
overflow: auto;
}
.card-tabs {
display: flex;
border-bottom: 1px solid #292828;
/* margin-bottom: 1rem; */
font-size: 1.2rem;
color: #000000;
/* background-color: #002aff; */
height: 8%;
display: flex;
align-items: center;
width: 100%;
padding: 10px;
}
.title{
width: 70%;
}
.card-actions {
display: flex;
flex: 1;
justify-content: space-between;
align-items: center;
/* margin-bottom: 1rem; */
height: 8%;
padding: 1.5rem;
}
.operation-button {
width: 15%;
}
.card-tabs-footer {
display: flex;
/* margin-bottom: 1rem; */
font-size: 1.2rem;
color: #000000;
/* background-color: #002aff; */
height: 8%;
align-items: center;
width: 100%;
padding: 10px 0;
justify-content: flex-end;
align-items: center;
}
/* 文本样式 */
.page-text {
color: #606266;
font-size: 14px;
white-space: nowrap; /* 防止文本换行 */
}
/* 修复 Element Plus 分页组件默认的块级布局问题 */
:deep .el-pagination {
display: flex;
align-items: center;
margin: 0; /* 清除默认外边距 */
}
.pagination-block{
height: 8%;
}
.card-table{
width: 100%;
/* display: flex; */
/* background-color: #000000; */
flex:1;
overflow: hidden;
border-radius: 8PX;
}
/* 响应式适配 */
@media (max-width: 1024px) {
.left-top {
flex-direction: column;
}
.region-overview {
width: 100%;
flex: none;
}
}
@media (max-width: 768px) {
.stats-alerts {
flex-direction: column;
}
}
</style>
\ No newline at end of file
<template>
<div class="add-user-modal">
<!-- 标题栏 -->
<div class="modal-header">
<h2 class="title">新增用户</h2>
<el-icon class="close-icon" @click="handleClose" size="20">
<Close />
</el-icon>
</div>
<!-- 表单主体 -->
<el-form
ref="addUserFormRef"
:model="form"
:rules="rules"
label-width="100px"
label-position="left"
class="add-user-form"
status-icon
>
<!-- 用户账号 -->
<el-form-item label="用户账号" prop="name" class="form-item">
<el-input
v-model="form.name"
placeholder="请输入用户账号"
class="input-field"
prefix-icon="User"
clearable
/>
</el-form-item>
<!-- 用户密码 -->
<el-form-item label="用户密码" prop="password" class="form-item">
<el-input
v-model="form.password"
placeholder="请输入用户密码"
type="password"
class="input-field"
prefix-icon="Lock"
show-password
clearable
/>
</el-form-item>
<!-- 手机号 -->
<el-form-item label="手机号" prop="phone" class="form-item">
<el-input
v-model="form.phone"
placeholder="请输入手机号"
class="input-field"
prefix-icon="Phone"
maxlength="11"
clearable
/>
</el-form-item>
<!-- 性别 -->
<el-form-item label="性别" prop="gender" class="form-item">
<el-radio-group v-model="form.gender" class="radio-group">
<el-radio :label="1" class="radio-btn" size="large"></el-radio>
<el-radio :label="2" class="radio-btn" size="large"></el-radio>
</el-radio-group>
</el-form-item>
<!-- 状态 -->
<el-form-item label="状态" prop="status" class="form-item">
<el-radio-group v-model="form.status" class="radio-group">
<el-radio :label="0" class="radio-btn" size="large">正常</el-radio>
<el-radio :label="1" class="radio-btn" size="large">停用</el-radio>
</el-radio-group>
</el-form-item>
<!-- 角色 -->
<el-form-item label="角色" prop="role" class="form-item">
<el-select
v-model="form.role"
placeholder="请选择角色"
class="select-field"
prefix-icon="UserFilled"
>
<el-option
v-for="item in roleOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</el-form-item>
<!-- 头像上传 -->
<el-form-item label="头像上传" prop="avatar" class="form-item">
<div class="avatar-uploader">
<UpdataImg @getAvatar="getAvatar" :updataImageCount="1" ref="avatarUploader"></UpdataImg>
</div>
</el-form-item>
<!-- 备注 -->
<el-form-item label="备注" prop="remark" class="form-item">
<el-input
v-model="form.remark"
type="textarea"
placeholder="请输入备注信息"
:rows="3"
class="textarea-field"
show-word-limit
maxlength="200"
/>
</el-form-item>
</el-form>
<!-- 底部按钮 -->
<div class="modal-footer">
<el-button @click="handleCancel" class="cancel-btn" size="default">取消</el-button>
<el-button
@click="handleSubmit"
type="primary"
class="confirm-btn"
size="default"
:loading="submitting"
>
确认
</el-button>
</div>
</div>
</template>
<script setup>
import { ref, reactive, onMounted } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus';
import { Close, } from '@element-plus/icons-vue';
import UpdataImg from './updataImg.vue';
import {staffAddInfoApi} from '../../../api/staff'
// 表单引用
const addUserFormRef = ref(null);
// 提交加载状态
const submitting = ref(false);
const avatarUploader = ref(null);
const emit = defineEmits(['closeAddDrawer','tableDataRefresh']);
// 角色选项数据
const roleOptions = ref([
{ label: '管理员', value: 0 },
{ label: '巡察员', value: 1 }
]);
// 表单数据
const form = reactive({
name: '', // 用户账号
password: '', // 用户密码
phone: '', // 手机号
gender: 1, // 性别:1-男,2-女
status: 0, // 状态:0-正常,1-停用
role: '', // 角色
avatar: '', // 头像URL
remark: '' // 备注
});
const getAvatar = (data) => {
console.log("000000000000000000000")
form.avatar = data;
}
// 表单验证规则
const rules = reactive({
name: [
{ required: true, message: '请输入正确的汉字', trigger: 'blur' },
{ min: 2, max: 10, message: '账号长度在4-20个字符之间', trigger: 'blur' },
{ pattern: /^[\u4e00-\u9fa5]+$/, message: '账号只能包含汉字', trigger: 'blur' }
],
password: [
{ required: true, message: '请输入用户密码', trigger: 'blur' },
{ min: 6, max: 30, message: '密码长度在6-30个字符之间', trigger: 'blur' },
{ pattern: /^(?=.*[a-zA-Z])(?=.*\d).+$/, message: '密码需包含字母和数字', trigger: 'blur' }
],
phone: [
{ required: true, message: '请输入手机号', trigger: 'blur' },
{ pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号格式', trigger: 'blur' }
],
role: [
{ required: true, message: '请选择角色', trigger: 'change' }
]
});
// // 头像上传前验证
// const beforeAvatarUpload = (rawFile) => {
// const isJpgOrPng = rawFile.type === 'image/jpeg' || rawFile.type === 'image/png';
// const isLt2M = rawFile.size / 1024 / 1024 < 2;
// if (!isJpgOrPng) {
// ElMessage.error('上传头像图片只能是 JPG/PNG 格式!');
// }
// if (!isLt2M) {
// ElMessage.error('上传头像图片大小不能超过 2MB!');
// }
// return isJpgOrPng && isLt2M;
// };
// // 头像上传处理(自定义上传)
// const handleAvatarUpload = async (options) => {
// try {
// const formData = new FormData();
// formData.append('file', options.file);
// // 模拟上传请求
// await new Promise(resolve => setTimeout(resolve, 1000));
// // 模拟返回的头像URL
// const avatarUrl = `https://example.com/avatars/${Date.now()}.jpg`;
// form.avatar = avatarUrl;
// options.onSuccess({ data: { url: avatarUrl } });
// } catch (error) {
// options.onError(error);
// ElMessage.error('头像上传失败,请重试');
// }
// };
// // 头像上传成功回调
// const handleAvatarSuccess = (response) => {
// form.avatar = response.data.url;
// ElMessage.success('头像上传成功');
// };
// 提交表单
const handleSubmit = () => {
try {
submitting.value = true;
// 准备提交数据
const submitData = {
...form,
avatar:form.avatar,
};
// console.log('提交新增用户数据:', submitData);
// 模拟API请求
console.log('提交新增用户数据:', submitData,form.avatar);
staffAddInfoApi(submitData).then(res => {
console.log('用户新增成功', res);
if (res.code === 200) {
// 成功后关闭抽屉
emit('closeAddDrawer');
// 通知父组件刷新列表
emit('tableDataRefresh');
ElMessage.success('用户新增成功!');
addUserFormRef.value.resetFields();
// avatarUploader.value.handleRemove(form.avatar[0])
}
}).catch(err => {
ElMessage.error('用户新增失败');
});
} catch (error) {
console.error('表单验证失败:', error);
} finally {
submitting.value = false;
}
};
// 取消操作
const handleCancel = () => {
ElMessageBox.confirm('确定要取消新增吗?已填写内容将不保存', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type:'warning',
center: true
}).then(() => {
addUserFormRef.value.resetFields();
emit('closeAddDrawer');
}).catch(() => {});
};
// 关闭弹窗
const handleClose = () => {
// 通知父组件关闭弹窗
// emit('close');
addUserFormRef.value.resetFields();
handleCancel()
};
onMounted(() => {
console.log('新增组件已挂载');
});
</script>
<style scoped lang="scss">
.add-user-modal {
width: 100%;
max-width: 550px;
padding: 24px;
box-sizing: border-box;
background-color: #fff;
border-radius: 8px;
// 标题栏样式
.modal-header {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 24px;
padding-bottom: 16px;
border-bottom: 1px solid #f0f0f0;
.title {
margin: 0;
font-size: 18px;
font-weight: 500;
color: #1f2937;
}
.close-icon {
cursor: pointer;
color: #6b7280;
transition: all 0.2s;
&:hover {
color:#ef4444;
background-color: #f8fafc;
border-radius: 50%;
}
}
}
// 表单样式
.add-user-form {
.form-item {
margin-bottom: 20px;
}
.input-field, .select-field {
width: 100%;
max-width: 320px;
transition: all 0.2s;
.el-input__prefix-inner {
margin-right:.5rem;
}
}
.textarea-field {
width: 100%;
max-width: 320px !important;
resize: vertical;
}
// 单选框样式
.radio-group {
display: flex;
gap: 24px;
padding: 4px 0;
.radio-btn {
padding: 6px 16px !important;
border-radius: 4px !important;
transition: all 0.2s;
& .el-radio__label {
font-size: 14px;
}
&:hover:not(.is-checked) {
background-color: #f5f5f5;
}
}
}
// 选中状态样式
:deep(.el-radio.is-checked .el-radio__inner) {
border-color: #4096ff;
background-color: #4096ff;
}
:deep(.el-radio__input.is-checked + .el-radio__label) {
color: #4096ff;
}
}
// 头像上传样式
.avatar-uploader {
margin-bottom: 12px;
.avatar-upload {
width: 120px !important;
height: 120px !important;
border: 1px dashed #d1d5db;
border-radius: 8px;
cursor:pointer;
position: relative;
overflow: hidden;
transition: all 0.3s;
&:hover {
border-color: #4096ff;
background-color: #f0f9ff;
}
.avatar-img {
width: 100%;
height: 100%;
object-fit: cover;
transition: transform 0.3s;
&:hover {
transform: scale(1.02);
}
}
.avatar-placeholder {
width: 100%;
height: 100%;
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
background-color: #f9fafb;
color: #6b7280;
gap: 8px;
.avatar-icon {
font-size: 28px;
}
.upload-text {
font-size: 14px;
}
}
}
.upload-tip {
margin: 8px 0 0;
font-size: 12px;
color: #9ca3af;
line-height: 1.5;
}
}
// 底部按钮样式
.modal-footer {
display: flex;
justify-content: flex-end;
gap: 16px;
margin-top: 30px;
padding-top: 20px;
border-top: 1px solid #f0f0f0;
.cancel-btn, .confirm-btn {
width: 110px;
height: 40px;
transition: all 0.2s;
&:hover {
transform: translateY(-1px);
}
}
.confirm-btn {
box-shadow: 0 2px 8px rgba(64, 158, 255, 0.2);
}
}
// 响应式适配
@media (max-width: 768px) {
padding: 16px;
max-width: 100%;
.add-user-form {
.input-field, .select-field, .textarea-field {
max-width: 100% !important;
}
.radio-group {
gap: 16px;
flex-wrap: wrap;
}
}
.modal-footer {
justify-content: center;
gap: 12px;
margin-top: 24px;
}
}
}
</style>
\ No newline at end of file
<template>
<div>
<el-table
<vxe-table
style="width: 100%;"
ref="tableRef"
@checkbox-change="selectTableDateFn"
@checkbox-all="selectTableDateFn"
@edit-closed="handleAfterEdit"
show-overflow
:edit-config="{trigger: 'click', mode: 'cell'}"
:data="tableShowData">
<vxe-column field="checkbox" type="checkbox" width="50"></vxe-column>
<vxe-column field="id" title="火情编号" min-width="80" ></vxe-column>
<vxe-column field="name" title="建筑名称" min-width="150" ></vxe-column>
<vxe-column field="floorcount" title="火情楼层" min-width="150" ></vxe-column>
<vxe-column field="reporter" title="上报人" min-width="150" ></vxe-column>
<vxe-column field="fireLevel" title="火情等级" width="150" :edit-render="fireLevelEditRender" ></vxe-column>
<vxe-column field="updateTime" title="上报时间" min-width="150" ></vxe-column>
<vxe-column field="discription" title="火情描述" :edit-render="fireDescripEditRender" min-width="150" ></vxe-column>
<vxe-column title="详情查看" min-width="220" >
<template #default="scope">
<el-button @click="handleClick(scope.row)">图片查看</el-button>
<el-button @click="handleVideoDialogFn(scope.row)">视频查看</el-button>
</template>
</vxe-column>
<vxe-column title="操作" min-width="220" >
<template #default="scope">
<el-button @click="taskSchedulingFn(scope.row)">任务调度</el-button>
</template>
</vxe-column>
</vxe-table>
</div>
<div>
<!-- <el-table
:data="tableShowData"
style="width: 100%"
@select="selectTableDateFn"
......@@ -13,11 +55,21 @@
<el-table-column prop="floorcount" label="火情楼层" width="120" />
<el-table-column prop="reporter" label="上报人" width="120" />
<el-table-column prop="updateTime" label="上报时间" width="220" />
<el-table-column prop="fireLevel" label="火情等级" width="220" />
<el-table-column label="火情等级" width="220" >
<template #default="scope">
<el-select v-model="scope.row.fireLevel" placeholder="Select" >
<el-option
v-for="item in fireLevelOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</template>
</el-table-column>
<el-table-column prop="discription" label="火情描述" />
<el-table-column
label="详情查看"
show-overflow-tooltip
>
<template #default="scope">
......@@ -37,7 +89,7 @@
</template>
</el-table-column>
</el-table>
</el-table> -->
<!-- 分页区域(固定在右下角) -->
<div class="card-tabs-footer" >
<div class="pagination-block">
......@@ -136,7 +188,46 @@
</template>
<script setup>
import { message } from 'ant-design-vue'
const fireLevelEditRender = reactive({
name: 'select',
options: [
{ label: '一级火情', value: '1' },
{ label: '二级火情', value: '2' },
{ label: '三级火情', value: '3' },
{ label: '四级火情', value: '4' },
]
})
const fireDescripEditRender = reactive({
name: 'input',
})
const handleAfterEdit = ({ row, column }) => {
console.log('编辑完成后的行数据:', row);
// 可以在这里提交数据或做其他操作
return true; // 返回 true 表示编辑成功,false 会阻止修改
};
const fireLevelOptions = [
{ label: '一级火情', value: '1' },
{ label: '二级火情', value: '2' },
{ label: '三级火情', value: '3' },
{ label: '四级火情', value: '4' },
{ label: '五级火情', value: '5' },
]
const emit = defineEmits(['archiveInfoDateBackcallFn'])
const props = defineProps({
tableShowData: {
......@@ -185,9 +276,18 @@ const taskSchedulingFn = (info) => {
console.log("任务调度",info)
isTaskSchedulingDialogVisible.value = true
}
const selectTableDateFn = (selectedRows) => {
console.log('selectTableDateFn选择的数据信息',selectedRows)
emit('archiveInfoDateBackcallFn',selectedRows)
const tableRef = ref() // 确保这里声明了 tableRef
const selectTableDateFn = () => {
const $table = tableRef.value
if ($table) {
emit('archiveInfoDateBackcallFn',$table.getCheckboxRecords())
}else{
console.log('未找到表格实例')
message.error('未找到表格实例')
}
// console.log('selectTableDateFn选择的数据信息',tableRef.value)
// emit('archiveInfoDateBackcallFn',selectedRows)
}
// 分页数据
......
......@@ -14,9 +14,10 @@
<!-- 左侧下部分:统计区域 -->
<div class="info-card stats-alerts">
<div class="card-tabs">
<span class="title">当前火情</span>
<span class="title">历史火情</span>
<div class="card-actions">
<el-button class="operation-button" type="primary" @cliick="archiveInfoFn" >归档</el-button>
<el-button class="operation-button" type="primary" @cliick="archiveInfoFn1" >归档1</el-button>
</div>
</div>
<div class="card-table">
......@@ -25,6 +26,15 @@
</div>
</div>
</main>
<!-- 归档窗口 -->
<el-drawer
v-model="archiveInfoDrawer"
direction="rtl"
:before-close="handleClose"
>
asdada
</el-drawer>
</div>
</template>
......@@ -73,11 +83,24 @@ const archiveInfoDateBackcallFn = (row) => {
archiveInfoDate.value = row
console.log('archiveInfoFn需要归档的数据',archiveInfoDate.value)
};
const archiveInfoDrawer = ref(false)
const archiveInfoFn = () => {
if ( archiveInfoDate ===undefined ||archiveInfoDate.value===null || archiveInfoDate.value.length<=0 ) {
ElMessage.warning("没有勾选数据")
}else {
console.log('archiveInfoFn归档的数据',archiveInfoDate.value)
};
// archiveInfoDrawer.value = true
fireInfoDataImport.value = null
}
}
const archiveInfoFn1 = () => {
ElMessage.warning("没有勾选数据")
}
const handleAddInfoFn = () => {
isAddDrawer.value = true;
}
......@@ -166,7 +189,7 @@ const containerStyle = computed(() => ({
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: rgba(0, 0, 0, 0.05);
color: #333;
overflow: hidden;
/* overflow: hidden; */
}
.main-content {
......@@ -206,6 +229,7 @@ const containerStyle = computed(() => ({
.stats-alerts {
display: flex;
flex-direction: column;
flex: 1;
gap: 1rem;
height: 67%;
}
......@@ -218,6 +242,7 @@ const containerStyle = computed(() => ({
height: 8%;
align-items: center;
width: 100%;
/* background-color: aqua; */
padding: 10px;
}
......@@ -243,7 +268,7 @@ const containerStyle = computed(() => ({
.card-table {
width: 100%;
flex: 1;
overflow: hidden;
/* overflow: hidden; */
border-radius: 8px;
}
......
<template>
<div class="fire-report-detail">
<!-- 页面标题 -->
<div class="report-header">
<h2>火情上报详情</h2>
<span class="close-btn" @click="handleClose"> <el-icon><Close /></el-icon></span>
</div>
<!-- 表单内容 -->
<div class="report-content">
<!-- 基本信息区域 -->
<div class="info-section">
<div class="info-item">
<label class="info-label">上报人:</label>
<div class="info-value">
<input type="text" placeholder="请输入" class="info-value" disabled :value="`${userAppStoreInstance.userInfo.name}`">
</div>
</div>
<div class="info-item">
<label class="info-label">着火建筑:</label>
<div class="info-value"><input type="text" placeholder="请输入" class="info-value" ></div>
</div>
<div class="info-item">
<label class="info-label">着火楼层:</label>
<div class="info-value"><input type="text" placeholder="请输入" class="info-value" ></div>
</div>
</div>
<!-- 图片区域 -->
<div class="media-section">
<h3 class="section-title">现场图片</h3>
<div class="image-container">
<updataImg :updataImageCount="1"></updataImg>
</div>
</div>
<!-- 视频区域 -->
<div class="media-section">
<h3 class="section-title">现场视频</h3>
<div class="video-container">
<video src="" controls></video>
</div>
</div>
<!-- 描述区域 -->
<div class="description-section">
<h3 class="section-title">描述</h3>
<el-input
type="textarea"
placeholder="请输入"
class="description-input"
:rows="4"
/>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, onUnmounted } from 'vue';
import useAppStore from '../../../../store/module/app';
import updataImg from '../../../systemmanage/components/updataImg.vue';
const userAppStoreInstance = useAppStore();
// 关闭按钮事件
const handleClose = () => {
// 这里可以添加关闭当前页面或弹窗的逻辑
console.log('关闭火情上报详情');
};
// 监听窗口大小变化,实现自适应
const handleResize = () => {
// 可以在这里添加需要响应式调整的逻辑
console.log('窗口大小变化');
};
onMounted(() => {
window.addEventListener('resize', handleResize);
});
onUnmounted(() => {
window.removeEventListener('resize', handleResize);
});
</script>
<style scoped lang="scss">
.fire-report-detail {
width: 100%;
height: 90%;
min-height: 100vh;
padding: 10px;
box-sizing: border-box;
background-color: #f9f9f9;
.report-header {
display: flex;
justify-content: space-between;
align-items: center;
// margin-bottom: 20px;
padding-bottom: 15px;
border-bottom: 1px solid #e0e0e0;
h2 {
margin: 0;
font-size: 1.5rem;
color: #333;
}
.close-btn {
padding: 5px 10px;
}
.close-btn:hover{
background-color: rgb(118, 143, 168,0.1);
cursor: pointer
}
}
.report-content {
max-width: 1200px;
margin: 0 auto;
}
.info-section {
display: flex;
flex-direction: column; /* 子元素竖直排列 */
gap: 16px; /* info-item 之间的间距(竖直方向) */
padding: 10px; /* 可选:整体内边距 */
.info-item {
display: flex; /* 内部元素水平排列 */
align-items: center; /* 垂直居中对齐 */
gap: 12px; /* label 和 value 之间的间距(水平方向) */
.info-label {
width: 80px; /* 固定宽度,确保所有 label 对齐 */
font-weight: 500; /* 可选:加粗标签文字 */
color: #666; /* 可选:标签文字颜色 */
}
.info-value {
flex: 1; /* 占满剩余宽度 */
padding: 0px 4px; /* 内边距,增强可读性 */
border-radius: 4px; /* 可选:圆角边框 */
min-height: 36px; /* 可选:固定高度,确保对齐 */
display: flex; /* 内容垂直居中 */
align-items: center;
}
}
}
.section-title {
font-size: 1.1rem;
color: #333;
margin-bottom: 15px;
padding-left: 5px;
border-left: 3px solid #409eff;
}
.media-section {
margin-bottom: 30px;
.image-container {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 20px;
.image-item {
aspect-ratio: 4/3;
background-color: #f0f0f0;
border-radius: 4px;
display: flex;
align-items: center;
justify-content: center;
overflow: hidden;
.placeholder-image {
width: 100%;
height: 100%;
display: flex;
align-items: center;
justify-content: center;
color: #999;
.image-icon {
font-size: 2rem;
}
}
}
}
.video-container {
width: 100%;
.video-placeholder {
width: 100%;
aspect-ratio: 16/9;
background: linear-gradient(to bottom, #444, #222);
border-radius: 4px;
position: relative;
overflow: hidden;
.video-overlay {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
color: #fff;
font-size: 1rem;
text-align: center;
}
.video-controls {
position: absolute;
bottom: 0;
left: 0;
right: 0;
padding: 10px;
background: linear-gradient(transparent, rgba(0,0,0,0.7));
color: #fff;
display: flex;
align-items: center;
gap: 15px;
.play-btn {
background: rgba(0,0,0,0.5);
color: #fff;
width: 36px;
height: 36px;
border-radius: 50%;
padding: 0;
display: flex;
align-items: center;
justify-content: center;
}
.video-time {
font-size: 0.8rem;
min-width: 80px;
}
.video-progress {
flex: 1;
height: 4px;
background-color: rgba(255,255,255,0.3);
border-radius: 2px;
position: relative;
.progress-bar {
position: absolute;
left: 0;
top: 0;
height: 100%;
width: 30%;
background-color: #409eff;
border-radius: 2px;
.progress-indicator {
position: absolute;
right: 0;
top: 50%;
transform: translate(50%, -50%);
width: 12px;
height: 12px;
background-color: #fff;
border-radius: 50%;
box-shadow: 0 0 5px rgba(0,0,0,0.3);
}
}
}
.volume-btn, .fullscreen-btn {
color: #fff;
background: transparent;
padding: 5px;
}
}
}
}
}
.description-section {
.description-input {
width: 100%;
min-height: 120px;
}
}
}
/* 响应式调整 */
@media (max-width: 768px) {
.fire-report-detail {
padding: 15px 10px;
.info-section {
grid-template-columns: 1fr;
}
.image-container {
grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));
}
.video-controls {
gap: 10px;
.video-time {
display: none;
}
}
}
}
</style>
\ No newline at end of file
......@@ -19,10 +19,12 @@
<!-- 左侧下部分:统计区域 -->
<div class="info-card stats-alerts">
<div class="card-tabs">
<span class="title" >单次火情汇报列表</span>
<span class="title" >火情汇报列表</span>
<div class="card-actions">
<el-button class="operation-button" type="primary" size="small" @click="fireInfoDataImportFn" >导入</el-button>
<!-- <el-button type="danger" plain>Danger</el-button> -->
<el-button class="operation-button" type="danger" plain @click="fireInfoDataDeleteFn" >撤销归档</el-button>
<el-button class="operation-button" type="danger" plain @click="fireInfoDataDeleteFn" >删除</el-button>
<el-button class="operation-button" type="primary" @click="addFireInfoDataImportFn" >新增</el-button>
</div>
</div>
<div class="card-table">
......@@ -33,8 +35,13 @@
</div>
</div>
</main>
<!-- 新增添加框 -->
<el-drawer
v-model="addFireInfoDataImportFndrawer"
:with-header="false"
>
<addFireInfo></addFireInfo>
</el-drawer>
</div>
</template>
......@@ -45,21 +52,35 @@ import { reactive, computed, ref, onMounted } from 'vue';
import { ElMessage, ElMessageBox } from 'element-plus'
import addFireInfo from './addFireInfo.vue';
import Tableshowdetail from './tableshowdetail.vue';
const addFireInfoDataImportFndrawer = ref(false);
const addFireInfoDataImportFn = () => {
addFireInfoDataImportFndrawer.value = true
}
const fireInfoDataImport = ref(null);
const fireInfoDataImportBackcallFn = (info) => {
fireInfoDataImport.value = info
}
const fireInfoDataDeleteFn = () => {
if ( fireInfoDataImport ===undefined ||fireInfoDataImport.value===null || fireInfoDataImport.value.length<=0 ) {
ElMessage.warning("没有勾选数据")
}else {
console.log("删除数据",fireInfoDataImport.value)
fireInfoDataImport.value = null
}
}
const fireInfoDataImportFn = () => {
// if ( fireInfoDataImport.value===undefined ||fireInfoDataImport.value===null) {
// ElMessage.warning("没有勾选数据")
// }
if ( fireInfoDataImport ===undefined ||fireInfoDataImport.value===null || fireInfoDataImport.value.length<=0 ) {
ElMessage.warning("没有勾选数据")
}else {
console.log("导入数据",fireInfoDataImport.value)
fireInfoDataImport.value = null
}
}
// import Operation from 'ant-design-vue/es/transfer/operation';
......@@ -301,6 +322,7 @@ const containerStyle = computed(() => ({
border-radius: 8px;
box-sizing: border-box;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
/* overflow: auto; */
}
......@@ -320,11 +342,11 @@ const containerStyle = computed(() => ({
.card-tabs {
display: flex;
border-bottom: 1px solid #8a8787;
/* border-bottom: 1px solid #8a8787; */
font-size: 1.2rem;
color: #000000;
padding: 1.3rem;
height: 8%;
display: flex;
align-items: center;
......
......@@ -47,9 +47,48 @@
/>
</div>
</div>
<!-- 火情模拟对话框 -->
<el-dialog
v-model="isFireSimulationDialogVisible"
width="800px"
:before-close="handleClose"
custom-class="task-scheduling-dialog"
>
<!-- 标题区域 -->
<template #header>
<div class="dialog-header">
<span class="dialog-title">火情模拟</span>
</div>
</template>
<!-- 内容区域 -->
<div class="dialog-content">
<div class="notification-box">
<el-icon class="notification-icon"><Bell /></el-icon>
<span class="notification-text">系统已开始进行火情模拟,点击跳转按钮可以进入大屏观察火情模拟</span>
</div>
</div>
<!-- 底部按钮 -->
<template #footer>
<div class="dialog-footer">
<el-button type="primary" class="action-button">
<template #icon><VideoPlay /></template>
前往
</el-button>
<el-button class="action-button">
确认
</el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup>
......@@ -62,13 +101,14 @@ const props = defineProps({
},
})
const isFireSimulationDialogVisible = ref(false)
const route = useRoute()
const router = useRouter()
const imageRef = ref(null)
const showPreview = ref(false)
const fireSimulationFn = (info) => {
isFireSimulationDialogVisible.value = true
console.log("火情模拟",info)
}
......@@ -81,7 +121,8 @@ const exportReportFn = (info) => {
const detailViewsFn = (info) => {
console.log("详情查看",info,route)
router.push({
path: '/historyfire/floordetaildate',
name: 'floorDetailDate',
})
......
......@@ -14,7 +14,18 @@
<el-table-column prop="reporter" label="上报人员" width="120" />
<el-table-column prop="updateTime" label="上报时间" width="120" />
<el-table-column prop="discription" label="火情描述" width="220" show-overflow-tooltip/>
<el-table-column prop="fireLevel" label="火情等级" width="120" />
<el-table-column prop="fireLevel" label="火情等级" width="120" show-overflow-tooltip>
<template #default="scope">
<el-select v-model="scope.row.fireLevel" >
<el-option
v-for="item in fireOptions"
:key="item.value"
:label="item.label"
:value="item.value"
/>
</el-select>
</template>
</el-table-column>
<el-table-column prop="firePoint" label="着火点" width="220" show-overflow-tooltip />
<el-table-column
label="详情查看"
......@@ -98,7 +109,14 @@ const emit = defineEmits(['fireInfoDataImportBackcallFn'])
// const props = defineProps(['tableShowData'])
const router = useRouter()
const route = useRoute()
const fireOptions = [
{ label: '一级火情', value: '1' },
{ label: '二级火情', value: '2' },
{ label: '三级火情', value: '3' },
{ label: '四级火情', value: '4' },
{ label: '五级火情', value: '5' },
{ label: '六级火情', value: '6' },
]
const tableShowData =[{
id: 1,
fireNo:12,
......@@ -108,7 +126,7 @@ const tableShowData =[{
reporter:"张三",
updateTime:"2022-05-04",
discription: 'Lorem ipsum dolor sit ametisl nisl nisl nisl nisl nisl nisl nisl nis',
fireLevel: '1',
fireLevel: '1',
firePoint: '厨房',
},
......@@ -121,7 +139,7 @@ const tableShowData =[{
reporter:"张三",
updateTime:"2022-05-04",
discription: 'Lorem ipsum dolor sit ametisl nisl nisl nisl nisl nisl nisl nisl nis',
fireLevel: '1级',
fireLevel: '2',
firePoint: '厨房',
}]
......
......@@ -14,11 +14,8 @@
<!-- 左侧下部分:统计区域 -->
<div class="info-card stats-alerts">
<div class="card-tabs">
<span class="title">历史火情</span>
<!-- <div class="card-actions">
<el-button class="operation-button" type="primary" @cliick="archiveInfoFn" >归档</el-button>
<span class="title">当前火情</span>
</div> -->
</div>
<div class="card-table">
<tableshow :tableShowData="tableShowData" @archiveInfoDateBackcallFn="archiveInfoDateBackcallFn"></tableshow>
......
......@@ -167,7 +167,7 @@
:before-close="handleClose"
>
<updataImgInfo :file="aerialViewUrl" @changeAerialView="changeAerialView" ></updataImgInfo>
<updataImgInfo :file="aerialViewUrl" @changeAerialView="changeAerialView" :updataImageCount="1" ></updataImgInfo>
</el-dialog>
<!-- 展示鸟瞰图 -->
<el-dialog
......
......@@ -82,8 +82,9 @@ const resetSearchForm = () => {
.search-row {
/* background-color: #606266; */
width: 80%;
display: flex;
flex: 1;
align-items: center;
justify-items: center;
justify-content: space-between;
......
......@@ -4,6 +4,8 @@
<el-table
:data="curTableShowData[0].tableBody"
style="width: 100%;height: 100%;"
fixed
@load="handleTableLoad"
>
<template v-for="(items, index) in curTableShowData[0].tableHeader" :key="index">
......@@ -33,23 +35,19 @@
<div v-else-if="items.prop == 'status'">
<el-switch
v-model="scope.row[items.prop]"
active-color="#13ce66"
inactive-color="#ff4949"
disabled
class="mt-2"
style="margin-left: 24px"
inline-prompt
:active-icon="Check"
:inactive-icon="Close"
@change="changeStatusFn(scope.row)"
/>
</div>
<div v-else-if="items.prop == 'role'">
<span class="role-tag" :class="scope.row[items.prop]==='0'|| scope.row[items.prop] ==='10' ? 'admin' : 'inspector'" >
<span >{{ useAppStoreInstance.position[scope.row[items.prop]] }}</span>
</span>
<el-button
type="primary"
round
:style="{
backgroundColor: scope.row[items.prop] ==='0' ? '#67C23A' : '#F56C6C' // 0→绿色(管理员),1→红色(巡查人员)
}"
>
{{ scope.row[items.prop] === "0" ? "管理员" : "巡查人员" }} <!-- 文本逻辑不变 -->
</el-button>
</div>
<div v-else>
......@@ -84,14 +82,19 @@
<script setup>
import { onMounted, ref, toRef } from 'vue';
import { staffGetPageInfoApi } from '../../../api/staff';
import { staffGetPageInfoApi ,staffSetAbleApi} from '../../../api/staff';
import { message } from 'ant-design-vue';
import { pa } from 'element-plus/es/locales.mjs';
import { color } from 'echarts';
import { Check, Close } from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus';
import useAppStore from '../../../store/module/app';
const useAppStoreInstance = useAppStore();
// const value = ref(true);
const props = defineProps(["tableShowData",'Operation'])
const emits = defineEmits(["tableDataRefresh"])
const curTableShowData = ref(props.tableShowData)
console.log("11111111111111111111111props.tableShowData-----------------",props.Operation)
......@@ -99,6 +102,25 @@ console.log("11111111111111111111111props.tableShowData-----------------",props.
const currentPage2 = ref(1)
const pageSize2 = ref(10)
const changeStatusFn = (info) => {
console.log("changeStatusFn-----------------",info)
const submitDate = {
"id":info.id,
"status": info.status?0:1
}
console.log("submitDate-----------------",submitDate)
if (submitDate.id ==='' || submitDate.status ==='') {
ElMessage.warning('缺少操作的行数据!')
}else {
(staffSetAbleApi(submitDate)) .then(res => {
emits('tableDataRefresh')
ElMessage.success(`${submitDate.status===1?'禁用':'启用'} 操作成功!`)
}).catch(err => {
ElMessage.error("changeStatusFn err",err.message)
})
}
}
// 处理按钮点击事件
const handleButtonClick = (value, row) => {
// console.log('点击了按钮:', value)
......@@ -159,6 +181,29 @@ const getNewTableData = () => {
</script>
<style scoped>
.role-tag {
font-weight: 500;
}
.admin {
background-color: #ecf5ff;
color: #409eff;
}
/* 巡查员标签 */
.inspector {
background-color: #f0f9eb;
color: #67c23a;
}
.gradient-text {
background: linear-gradient(90deg, #ff3366, #ffcc00, #33cc33, #3399ff, #cc33ff);
-webkit-background-clip: text; /* 关键属性 */
background-clip: text;
color: transparent; /* 文字透明显露出背景 */
font-size: 1em;
font-weight: bold;
}
/* 容器样式 */
.table-container {
position: relative; /* 为分页定位提供基准 */
......
......@@ -2,7 +2,7 @@
<div class="sidebar-container">
<div class="nav-grid">
<!-- 首页 -->
<div class="nav-item" v-for="(items, index) in menuItems" :key="index" @click="handleNavClick(value)">
<div class="nav-item" v-for="(items, index) in menuItems" :key="index" @click="handleNavClick(items)">
<div class="nav-icon">
<img style=" width:100%;" :src="items.icon">
</div>
......@@ -17,29 +17,28 @@
</template>
<script setup>
import { message } from 'ant-design-vue';
import { it } from 'element-plus/es/locales.mjs';
import { ref } from 'vue';
import { useRouter } from 'vue-router';
import { useRouter, useRoute } from 'vue-router';
const router = useRouter();
const route = useRoute();
const activeItem = ref('首页'); // 默认激活首页
const props = defineProps(['menuItems']);
// 导航菜单数据
// const navItems = ref([
// { name: '首页', icon: 'home', path: '/' },
// { name: '系统管理', icon: 'settings', path: '/system' },
// { name: '建筑管理', icon: 'building', path: '/buildings' },
// { name: '无人机管理', icon: 'drone', path: '/drones' },
// { name: '火情管理', icon: 'fire', path: '/fires' },
// { name: '数字孪生管理', icon: 'digital-twin', path: '/digital-twin' },
// { name: '个人信息', icon: 'profile', path: '/profile' }
// ]);
// 导航点击处理
const handleNavClick = (item) => {
activeItem.value = item.name;
console.log('点击了导航项:', item,route);
try {
router.push(item.path);
message.success('跳转:' + item.name);
} catch (error) {
console.log('导航项 错误 error', error);
}
};
</script>
......
......@@ -9,10 +9,10 @@
<!-- 区域信息概览 -->
<div class="info-card region-overview">
<div class="info-grid">
<el-avatar size="large" src="https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png"/>
<el-avatar shape="square" size="large" :src="useAppStoreInstance.userInfo.avatar?useAppStoreInstance.userInfo.avatar: useAppStoreInstance.showavatarUrl"/>
<div class="info-item">
<span>张明</span>
<span>高级产品经理</span>
<span>{{useAppStoreInstance.userInfo.name}}</span>
<span>{{useAppStoreInstance.position[useAppStoreInstance.userInfo.role]}}</span>
</div>
</div>
</div>
......@@ -42,11 +42,25 @@
</template>
<script setup>
import { reactive, computed } from 'vue';
import { reactive, computed, onMounted, watch } from 'vue';
import { ChatDotSquare } from '@element-plus/icons-vue';
import currentFire from '../components/currentFire.vue';
import uav from '../components/uav.vue';
import meanList from '../components/menuList.vue';
import useAppStore from '../../../store/module/app';
import { useRouter,useRoute } from 'vue-router';
const useAppStoreInstance = useAppStore();
const curruserInfo = computed(() => (useAppStoreInstance.userInfo));
const router = useRouter();
const route = useRoute();
// const initUserInfoFn = () => {
// return JSON.parse(curruserInfo).avatarUrl = JSON.parse(curruserInfo).avatarUrl?JSON.parse(curruserInfo).avatarUrl:"https://cube.elemecdn.com/0/88/03b0d39583f48206768a7534e55bcpng.png";
// }
// console.log(curruserInfo.value,'89898989898989',);
// 定义一个ref变量,用于存储当前的火灾数据
const currentFireData = ref([
// 火灾数据1
......@@ -119,13 +133,13 @@ const uavRepairData = ref([
const menuItems = ref([
{ name: '首页', icon: new URL('../../../static/image/huo.png', import.meta.url), path: '/' },
{ name: '系统管理', icon: new URL('../../../static/image/huo.png', import.meta.url), path: '/system' },
{ name: '建筑管理', icon: new URL('../../../static/image/huo.png', import.meta.url), path: '/buildings' },
{ name: '无人机管理', icon: new URL('../../../static/image/huo.png', import.meta.url), path: '/drones' },
{ name: '火情管理', icon: new URL('../../../static/image/huo.png', import.meta.url), path: '/fires' },
{ name: '首页', icon: new URL('../../../static/image/huo.png', import.meta.url), path: '/analysisPage' },
{ name: '系统管理', icon: new URL('../../../static/image/huo.png', import.meta.url), path: '/usermanage' },
{ name: '建筑管理', icon: new URL('../../../static/image/huo.png', import.meta.url), path: '/areabuildmanage' },
{ name: '无人机管理', icon: new URL('../../../static/image/huo.png', import.meta.url), path: '/uavdispatch' },
{ name: '火情管理', icon: new URL('../../../static/image/huo.png', import.meta.url), path: '/firesurveillance' },
{ name: '数字孪生管理', icon: new URL('../../../static/image/huo.png', import.meta.url), path: '/digital-twin' },
{ name: '个人信息', icon: new URL('../../../static/image/huo.png', import.meta.url), path: '/profile' }
{ name: '个人信息', icon: new URL('../../../static/image/huo.png', import.meta.url), path: '/usermanage' }
]);
// 响应式配置
const layoutConfig = reactive({
......@@ -140,6 +154,18 @@ const containerStyle = computed(() => ({
minHeight: layoutConfig.containerMinHeight,
'--card-spacing': layoutConfig.cardSpacing,
}));
onMounted(() => {
nextTick (()=>{
useAppStoreInstance.initavatarUrlFn()
})
});
// 监听路由变化
watch(route, () => {
useAppStoreInstance.initavatarUrlFn()
}, { immediate: true });
</script>
<style scoped>
......
<template>
<div class="login">
<div class="login_main">
<div class="title1">无人灭火机器人数字孪生后台管理系统</div>
<div class="title2">国际花园小区</div>
<el-form class="fromclass" :model="form" :rules="rules" ref="formref"
style="width: 16.2vw;margin: 0 auto;margin-top: 4vh;">
<div class="login-container">
<!-- 左侧标题区域 -->
<div class="left-panel">
<img style="width: 100%;height: 100%;" src="../../static/image/login_bg.png" alt="logo"></img>
<!-- <div class="title-group">
<h1 class="main-title">智能消防灭火信息管控平台</h1>
<p class="sub-title">高效·智能·安全</p>
</div> -->
</div>
<!-- 右侧登录表单区域 -->
<div class="right-panel">
<div class="login-card">
<h2 class="welcome-text">欢迎回来</h2>
<p class="login-desc">请输入您的账号和密码登录</p>
<el-form
ref="formref"
:model="form"
:rules="rules"
class="login-form"
>
<el-form-item prop="phone">
<el-input v-model="form.phone" placeholder="请输入您的账号" clearable class="widthClass" />
<el-input
v-model="form.phone"
placeholder="输入手机号码"
prefix-icon="Phone"
clearable
class="custom-input"
@keyup.enter="commit"
/>
</el-form-item>
<el-form-item prop="password">
<el-input v-model="form.password" placeholder="请输入您的密码" clearable class="widthClass" @keyup.enter="commit" />
<el-input
v-model="form.password"
placeholder="输入密码"
type="password"
prefix-icon="Lock"
clearable
class="custom-input"
@keyup.enter="commit"
/>
</el-form-item>
<div class="form-options">
<el-checkbox v-model="rememberMe" class="remember-checkbox">记住账号</el-checkbox>
<el-button type="text" class="forgot-password">忘记密码?</el-button>
</div>
<el-button
type="primary"
class="login-button"
@click="commit"
:loading="loginLoading"
>
登录
</el-button>
</el-form>
<div class="logingo" @click="commit">登录</div>
</div>
</div>
</div>
</div>
</template>
<script setup>
import { ref, onMounted, reactive } from 'vue';
import { ref, reactive } from 'vue';
import { useRouter } from 'vue-router';
import { ElMessage, ElLoading } from 'element-plus';
import { useRoute, useRouter } from 'vue-router';
import { loginApi } from "../../api/login.js";
import { setToken } from '@/utils/auth.js'
const router = useRouter();
const form = ref({
import { setToken } from '@/utils/auth.js';
import useAppStore from '../../store/module/app.js';
const useAppStoreInstance = useAppStore();
// 登录表单数据
const form = reactive({
phone: '',
password: ''
})
const formref = ref(null)
});
// 状态管理
const formref = ref(null);
const rememberMe = ref(false);
const loginLoading = ref(false);
const router = useRouter();
// 表单验证规则
const rules = {
phone: [{ required: true, trigger: "blur", message: "请输入您的账号" }],
password: [{ required: true, trigger: "blur", message: "请输入您的密码" }],
phone: [
{ required: true, message: "请输入您的账号", trigger: "blur" },
{ pattern: /^1[3-9]\d{9}$/, message: "请输入正确的手机号码", trigger: "blur" }
],
password: [
{ required: true, message: "请输入您的密码", trigger: "blur" },
{ min: 6, message: "密码长度不能少于6位", trigger: "blur" }
]
};
// 登录
// 登录处理函数
const commit = () => {
formref.value.validate(valid => {
if (valid) {
loginApi(form.value).then(res => {
console.log(res, '登录');
if (res.code == 200) {
setToken(res.data.token)//储存token
localStorage.setItem('menu', 1)//默认选中工作台
router.replace({ path: '/home' })
loginLoading.value = true;
// 登录请求
loginApi(form).then(res => {
loginLoading.value = false;
console.log(res, '测试');
if (res.code === 200) {
setToken(res.data.token);
let { phone, id,name,avatarUrl ,role,status} = res.data.user;
// console.log('phone, id,name,avatarUrl ,role',useAppStoreInstance.userInfo);
localStorage.setItem('user', JSON.stringify({ phone, id,name,avatarUrl ,role,status}));
useAppStoreInstance.userInfo = ( {phone, id,name,avatarUrl ,role,status})
console.log('phone, id,name,avatarUrl ,role',useAppStoreInstance.userInfo);
localStorage.setItem('lastValidMenuPath','/home')
// localStorage.setItem('saveMenuState',JSON.stringify({lastValidMenuPath:'/home',openMenus:[true,null,null,null,null]}))
if (rememberMe.value) {
localStorage.setItem('rememberedAccount', form.phone);
} else {
localStorage.removeItem('rememberedAccount');
}
localStorage.setItem('menu', 1); // 默认选中工作台
router.replace({ path: '/analysisPage' });
ElMessage.success('登录成功!');
} else {
ElMessage.error(res.message || '登录失败,请检查账号密码');
}
})
}).catch(err => {
loginLoading.value = false;
ElMessage.error('网络错误,请稍后重试');
});
}
});
};
// 初始化表单数据
const initFormData = () => {
const rememberedAccount = localStorage.getItem('rememberedAccount');
if (rememberedAccount) {
form.phone = rememberedAccount;
rememberMe.value = true;
}
})
}
};
// 页面加载时初始化
initFormData();
</script>
<style scoped>
<style scoped lang="scss">
.login {
padding: 10rem;
width: 100vw;
height: 100vh;
overflow: hidden;
position: relative;
background-color: #f5f7fa;
}
.login-bg-decoration {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
background-image: url(../../static/image/loginback.png);
background-repeat: no-repeat;
background-size: 100% 100%;
background-size: cover;
background-position: center;
z-index: 0;
}
.login-container {
display: flex;
width: 100%;
height: 100%;
position: relative;
overflow: auto;
z-index: 1;
}
.login_main {
width: 30vw;
height: 25vw;
margin: 0 auto;
margin-top: calc((100vh - 25vw)/2);
background-image: url(../../static/image/loginmain.png);
background-repeat: no-repeat;
background-size: 100% 100%;
padding: 50px;
.left-panel {
flex: 1;
display: flex;
flex-direction: column;
justify-content: center;
// padding: 0 5vw;
color: #fff;
// background-image: url(../../static/image/loginback.png);
// background-color: #1d2129;
// background: linear-gradient(90deg, rgba(0, 47, 108, 0.8), rgba(0, 47, 108, 0.6) 70%, transparent);
}
.title-group {
// background-color: #165dff;
max-width: 500px;
}
.title1 {
font-family: SourceHanSansCN, SourceHanSansCN;
font-weight: bold;
line-height: 48px;
text-align: right;
font-style: normal;
background: linear-gradient(to bottom, #FFFFFF 50%, #96CBFF 100%);
.main-title {
font-size: 2.5rem;
font-weight: 700;
margin-bottom: 1rem;
line-height: 1.3;
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);
}
.sub-title {
font-size: 1rem;
opacity: 0.9;
}
width: 100%;
text-align: center;
font-size: 26px;
color: #D5F6FF;
opacity: 0.79;
background-clip: text;
-webkit-background-clip: text;
color: transparent;
.right-panel {
width: 380px;
display: flex;
justify-content: center;
align-items: center;
background-color: #fff;
box-shadow: -5px 0 25px rgba(0, 0, 0, 0.05);
}
.title2 {
.login-card {
width: 100%;
text-align: center;
font-size: 20px;
color: #D5F6FF;
opacity: 0.79;
line-height: 48px;
padding: 2.5rem;
max-width: 320px;
}
.widthClass {
.welcome-text {
font-size: 1.5rem;
font-weight: 600;
color: #1d2129;
margin-bottom: 0.5rem;
}
.login-desc {
color: #86909c;
margin-bottom: 2rem;
font-size: 0.9rem;
}
.login-form {
width: 100%;
height: 2.2vw;
margin: 0 auto;
margin-top: 1vh;
}
.custom-input {
margin-bottom: 1rem;
}
.logingo {
width: 16.5vw;
height: 2.2vw;
text-align: center;
line-height: 2.2vw;
margin: 0 auto;
margin-top: 3vh;
color: #ffffff;
background-image: url(../../static/image/loginbutton.png);
background-repeat: no-repeat;
background-size: 100% 100%;
cursor: pointer;
.form-options {
display: flex;
justify-content: space-between;
align-items: center;
margin: 0.5rem 0 1.5rem;
}
:deep().widthClass .el-input__wrapper {
background-color: #06325c;
/* opacity: 0.25; */
box-shadow: none;
border: 1px solid #064a74;
/* color: #06325c; */
.remember-checkbox {
color: #4e5969;
font-size: 0.85rem;
}
:deep().widthClass .el-input__inner {
color: #ffffff !important;
.forgot-password {
color: #165dff;
font-size: 0.85rem;
padding: 0;
}
:deep().widthClass .el-input__inner::placeholder {
color: #ffffff !important;
opacity: 1;
.login-button {
width: 100%;
height: 48px;
font-size: 1rem;
background-color: #165dff;
border-radius: 6px;
}
/* 自定义输入框样式 */
:deep(.custom-input .el-input__wrapper) {
height: 48px;
border-radius: 6px;
border-color: #e5e6eb;
box-shadow: none;
transition: all 0.2s ease;
&:focus-within {
border-color: #165dff;
box-shadow: 0 0 0 2px rgba(22, 93, 255, 0.1);
}
}
/* 响应式调整 */
@media (max-width: 768px) {
.login-container {
flex-direction: column;
}
.left-panel {
height: 200px;
width: 100%;
background: linear-gradient(180deg, rgba(0, 47, 108, 0.9), rgba(0, 47, 108, 0.7));
padding: 0 2rem;
}
.main-title {
font-size: 1.8rem;
}
.right-panel {
width: 100%;
height: calc(100vh - 200px);
}
}
</style>
\ No newline at end of file
......@@ -65,8 +65,8 @@
<!-- 状态 -->
<el-form-item label="状态" prop="status" class="form-item">
<el-radio-group v-model="form.status" class="radio-group">
<el-radio :label="1" class="radio-btn" size="large">正常</el-radio>
<el-radio :label="0" class="radio-btn" size="large">停用</el-radio>
<el-radio :label="0" class="radio-btn" size="large">正常</el-radio>
<el-radio :label="1" class="radio-btn" size="large">停用</el-radio>
</el-radio-group>
</el-form-item>
......@@ -90,7 +90,7 @@
<!-- 头像上传 -->
<el-form-item label="头像上传" prop="avatar" class="form-item">
<div class="avatar-uploader">
<UpdataImg @getAvatar="getAvatar"></UpdataImg>
<UpdataImg @getAvatar="getAvatar" ref="avatarUploader" :updataImageCount="1"></UpdataImg>
</div>
</el-form-item>
......@@ -134,13 +134,13 @@ import {staffAddInfoApi} from '../../../api/staff'
const addUserFormRef = ref(null);
// 提交加载状态
const submitting = ref(false);
const avatarUploader = ref(null);
const emit = defineEmits(['closeAddDrawer','tableDataRefresh']);
// 角色选项数据
const roleOptions = ref([
{ label: '管理员', value: 'admin' },
{ label: '巡察员', value: 'operator' }
{ label: '管理员', value: 0 },
{ label: '巡察员', value: 1 }
]);
// 表单数据
......@@ -149,22 +149,25 @@ const form = reactive({
password: '', // 用户密码
phone: '', // 手机号
gender: 1, // 性别:1-男,2-女
status: 1, // 状态:1-正常,0-停用
status: 0, // 状态:0-正常,1-停用
role: '', // 角色
avatar: '', // 头像URL
remark: '' // 备注
});
const getAvatar = (data) => {
console.log("000000000000000000000",data)
console.log("000000000000000000000")
form.avatar = data;
}
// 表单验证规则
const rules = reactive({
name: [
{ required: true, message: '请输入用户账号', trigger: 'blur' },
{ min: 4, max: 20, message: '账号长度在4-20个字符之间', trigger: 'blur' },
{ pattern: /^[a-zA-Z0-9_]+$/, message: '账号只能包含字母、数字和下划线', trigger: 'blur' }
{ required: true, message: '请输入正确的汉字', trigger: 'blur' },
{ min: 2, max: 10, message: '账号长度在4-20个字符之间', trigger: 'blur' },
{ pattern: /^[\u4e00-\u9fa5]+$/, message: '账号只能包含汉字', trigger: 'blur' }
],
password: [
{ required: true, message: '请输入用户密码', trigger: 'blur' },
......@@ -221,22 +224,21 @@ const rules = reactive({
// };
// 提交表单
const handleSubmit = async () => {
const handleSubmit = () => {
try {
submitting.value = true;
await addUserFormRef.value.validate();
// 准备提交数据
const submitData = {
...form,
gender: form.gender ==='male' ? 0 : 1,
status: form.status ? 0 : 1
avatar:form.avatar,
};
addUserFormRef.value.resetFields();
console.log('提交新增用户数据:', submitData);
// 模拟API请求
// console.log('提交新增用户数据:', submitData);
// await new Promise(resolve => setTimeout(resolve, 1000));
// 模拟API请求
console.log('提交新增用户数据:', submitData,form.avatar);
staffAddInfoApi(submitData).then(res => {
console.log('用户新增成功', res);
if (res.code === 200) {
......@@ -245,14 +247,18 @@ const handleSubmit = async () => {
// 通知父组件刷新列表
emit('tableDataRefresh');
ElMessage.success('用户新增成功!');
addUserFormRef.value.resetFields();
// avatarUploader.value.handleRemove(form.avatar[0])
}
}).catch(err => {
console.error('重置密码失败', err);
ElMessage.error('用户新增失败');
});
} catch (error) {
console.error('表单验证失败:', error);
......
......@@ -22,7 +22,7 @@
<el-form-item label="用户号" prop="name">
<el-input
v-model="form.name"
disabled
placeholder="请输入"
class="form-input"
/>
......@@ -39,37 +39,25 @@
/>
</el-form-item>
<!-- 时间信息 -->
<div class="time-group">
<!-- 创建时间 -->
<el-form-item label="创建时间" prop="createTime">
<el-input
v-model="form.createTime"
placeholder="-"
disabled
class="time-input"
/>
</el-form-item>
<!-- 修改时间 -->
<el-form-item label="修改时间" prop="updateTime">
<el-input
v-model="form.updateTime"
placeholder="-"
disabled
class="time-input"
/>
<el-form-item label="性别" prop="gender">
<el-radio-group v-model="form.gender" class="status-radio">
<el-radio :label="1" size="large">
<span class="radio-label"></span>
</el-radio>
<el-radio :label="2" size="large">
<span class="radio-label"></span>
</el-radio>
</el-radio-group>
</el-form-item>
</div>
<!-- 状态 -->
<el-form-item label="状态" prop="status">
<el-radio-group v-model="form.status" class="status-radio">
<el-radio :label="1" size="large">
<span class="radio-label">正常</span>
</el-radio>
<el-radio :label="0" size="large">
<span class="radio-label">停用</span>
<span class="radio-label">启用</span>
</el-radio>
<el-radio :label="1" size="large">
<span class="radio-label">禁用</span>
</el-radio>
</el-radio-group>
</el-form-item>
......@@ -125,7 +113,7 @@
<script setup>
import { ref, reactive, onMounted, watch } from 'vue';
import { dayjs, ElMessage, ElMessageBox } from 'element-plus';
import { dayjs, ElMessage, ElMessageBox, namespaceContextKey } from 'element-plus';
import {staffUpdateInfoApi } from '../../../api/staff';
const props = defineProps(["rowData"]);
......@@ -144,8 +132,10 @@ const submitting = ref(false);
// 角色选项
const roleOptions = ref([
{ label: '管理员', value: 'admin' },
{ label: '巡察员', value: 'operator' }
{ label: '管理员', value: '0' },
{ label: '巡察员', value: '1' },
{ label: '超级管理员', value: '10' },
]);
// 表单数据
......@@ -182,28 +172,27 @@ const rules = reactive({
const initFormData = () => {
if (!props.rowData) return;
const { status, role, ...rest } = props.rowData;
console.log(props.rowData,"8888888888888888888888");
const { status, ...rest } = props.rowData;
// console.log(props.rowData,"8888888888888888888888");
// 映射状态值
Object.assign(form, {
...rest,
status: status ? 1 : 0 ,
role: role===0 ? 'admin' : 'operator'
status: status ? 0 : 1
});
console.log("645555555555",form)
console.log("645555--------------555555",form)
};
// 提交表单
const handleSubmit = async () => {
const handleSubmit = () => {
try {
submitting.value = true;
await editFormRef.value.validate();
let { status, role,gender, id,phone,remark } = form;
let { status, role,name, gender,id,phone,remark } = form;
// 准备提交数据
const submitData = {
status,
role:role ==="operator" ? 1 : 0,
role:role ,
gender,
name,
id,
phone,
remark
......@@ -212,10 +201,11 @@ const handleSubmit = async () => {
// 模拟API请求
console.log('提交数据:', submitData);
staffUpdateInfoApi(submitData).then(res => {
submitting.value = true;
console.log(res,"--------898989---------------");
if(res.code === 200){
ElMessage.success('用户信息修改成功');
// submitting.value = true;
emit('tableDataRefresh');
emit('closeEditDrawer');
}else{
......
......@@ -22,8 +22,8 @@
<div class="info-row">
<div class="info-label">用户角色</div>
<div class="info-value">
<span class="role-tag" :class="from.role ? 'admin' : 'inspector'">
{{ from.role ? "管理员" : "巡查员" }}
<span class="role-tag" :class="from.role==='0'|| from.role ==='10' ? 'admin' : 'inspector'">
{{ userappstoreinstance.position[from.role] }}
</span>
</div>
</div>
......@@ -36,6 +36,7 @@
type="password"
placeholder="请输入新密码"
show-password
/>
</div>
......@@ -54,12 +55,17 @@
</template>
<script setup>
import { onMounted, ref } from 'vue';
import { onMounted, ref, watch } from 'vue';
import {staffResetPasswordApi} from '../../../api/staff'
import { ElMessageBox } from 'element-plus';
import useAppStore from '../../../store/module/app';
import { useRouter,useRoute } from 'vue-router';
const userappstoreinstance = useAppStore();
const route = useRoute();
// 接收父组件传入的用户信息
const props = defineProps(['userInfo','isResetPasswordDialogVisible']);
const emits = defineEmits(['closeResetPasswordDialogFn']);
console.log(props.isResetPasswordDialogVisible,"00000000000000000000000000");
const from = ref({
id: '',
password: '',
......@@ -67,15 +73,14 @@ const from = ref({
phone: '',
role: ''
});
watch(() => props.isResetPasswordDialogVisible, (newVal) => {
if (newVal) {
// console.log('抽屉已打开,执行初始化代码');
watch(() => props.isResetPasswordDialogVisible,
(newVal) => {
initFormData();
}
});
);
onMounted(() => {
// 在这里处理初始化逻辑
console.log('组件已挂载',props.userInfo);
initFormData();
});
const closeResetPasswordDialogFn = () => {
......@@ -104,11 +109,14 @@ const initFormData = () => {
phone: props.userInfo.phone,
role: props.userInfo.role
};
console.log(from.value,"----------------------56565656---------------------");
}
const resetPasswordFn = () => {
// 在这里处理密码重置的逻辑
console.log('重置密码', userInfo.value);
const { id, password } = from.value;
const submitData = { id, password }; // 发送请求到后端进行密码重置
// console.log('提交数据', submitData);
staffResetPasswordApi(submitData).then(res => {
......@@ -125,6 +133,8 @@ const resetPasswordFn = () => {
};
// 定义一个响应式变量来存储用户信息
const userInfo = ref(props.userInfo);
......
......@@ -29,7 +29,7 @@
<div class="info-row">
<div class="info-label">用户角色</div>
<div class="info-value">
<span class="role-tag">{{ form.role==='0' ? "管理员": "巡查员" }}</span>
<span class="role-tag">{{ userappstoreinstance.position[form.role] }}</span>
</div>
</div>
......@@ -46,13 +46,13 @@
<div class="info-row">
<div class="info-label">启用状态</div>
<div class="info-value">
<span class="status-tag" :class=" form.status ? 'status-active' : 'status-inactive'">{{form.status ? '启用' : '禁用' || '-' }}</span>
<span class="status-tag" :class=" form.status ? 'status-active' : 'status-inactive'"> {{form.status ? '启用' : '禁用' || '-' }}</span>
</div>
</div>
<div class="info-row">
<div class="info-label">备注</div>
<div class="info-value">{{ (form.remarks) || '-' }}</div>
<div class="info-value">{{ (form.remark) || '-' }}</div>
</div>
</div>
</div>
......@@ -63,8 +63,8 @@
<script setup>
import { ref, computed, onActivated, onMounted } from 'vue';
import useAppStore from '../../../store/module/app';
const userappstoreinstance = useAppStore();
// 接收父组件传入的用户信息和显示状态
const props = defineProps({
userInfo: {
......@@ -206,7 +206,7 @@ const formatDate = (timestamp) => {
.info-value {
width: 70%;
color: #1f2937;
color: #6b7280;
font-size: 14px;
}
......
......@@ -10,7 +10,7 @@
:file-list="fileList"
:on-change="handleFileChange"
:before-upload="beforeUpload"
:limit="1"
:limit="updataImageCount"
:on-exceed="handleExceed"
accept="image/*"
class="custom-upload"
......@@ -70,12 +70,14 @@
<script setup>
import { ref,defineEmits } from 'vue'
import {staffAvatorApi} from '../../../api/other'
import { Delete, Plus, ZoomIn, InfoFilled } from '@element-plus/icons-vue'
import { ElMessage } from 'element-plus'
import { message } from 'ant-design-vue'
// 获取父组件传递的值
const emits = defineEmits(['getAvatar'])
const props = defineProps(['updataImageCount'])
// 组件引用
const uploadRef = ref(null)
......@@ -96,7 +98,16 @@ const handleFileChange = (file, files) => {
if (file.status === 'ready') {
fileList.value = files
emits('getAvatar', files)
// console.log('文件列表', fileList.value[0].url)
try {
staffAvatorApi({file:fileList.value[0].raw}).then(res => {
emits('getAvatar', res.fileAddress)
})
} catch (error) {
message.error('上传失败',err)
}
}
}
......@@ -104,10 +115,21 @@ const handleFileChange = (file, files) => {
* 删除文件处理
*/
const handleRemove = (file) => {
console.log('删除文件',file)
if (file) {
fileList.value = fileList.value.filter(item => item.uid !== file.uid)
ElMessage.success(`已移除 ${file.name}`)
}
}else {
fileList.value = []
}
}
// 暴露方法给父组件
defineExpose({
handleRemove,
});
/**
* 预览图片处理
*/
......
......@@ -25,7 +25,7 @@
</div>
</div>
<div class="card-table">
<tabledata :tableShowData="tableShowData" :Operation="Operation" ></tabledata>
<tabledata :tableShowData="tableShowData" :Operation="Operation" @tableDataRefresh="tableDataRefresh"></tabledata>
</div>
</div>
......@@ -57,13 +57,13 @@
:close-on-click-modal = false
:show-close = 'false'
>
<resetpassword :userInfo="resetPasswordData" @closeResetPasswordDialogFn="closeResetPasswordDialogFn"></resetpassword>
<resetpassword :userInfo="resetPasswordData" @closeResetPasswordDialogFn="closeResetPasswordDialogFn" :isResetPasswordDialogVisible="isResetPasswordDialogVisible"></resetpassword>
</el-dialog>
</div>
</template>
<script setup>
import { reactive, computed, ref, onMounted } from 'vue';
import { reactive, computed, ref, onMounted, watch } from 'vue';
import searchtop from '../../commentcomponents/searchtop/index.vue';
import tabledata from '../../commentcomponents/tabledata/index.vue';
import userinfo from '../components/showuserinfo.vue'
......@@ -71,9 +71,13 @@ import resetpassword from '../components/resetpassword.vue'
import editinfo from '../components/editinfo.vue';
import { ElMessage, ElMessageBox } from 'element-plus'
import AddInfo from '../components/addInfo.vue';
import { staffGetPageInfoApi } from "../../../api/staff.js"
import { staffGetPageInfoApi,staffRemoveInfoApi } from "../../../api/staff.js"
import { message } from 'ant-design-vue';
import { useRoute } from 'vue-router';
import useAppStore from '../../../store/module/app.js';
import { el } from 'element-plus/es/locales.mjs';
const useAppStoreInstance = useAppStore()
const route = useRoute()
// import Operation from 'ant-design-vue/es/transfer/operation';
// 定义一个名为searchShowData的ref变量,它是一个数组,数组中的每个元素都是一个对象,对象中包含了label、placeholder、type、content和options等属性
const searchShowData = ref([
......@@ -132,18 +136,27 @@ const rowData = ref([])
* @param {Object} data - 要编辑的数据对象
*/
const editData = (data) => {
try {
if(useAppStoreInstance.chackCurPersionOpition()){
// 打开编辑抽屉
isEditDrawer.value = true
// 设置要编辑的数据
rowData.value = data
console.log("000000-------1-----0000000000",data)
}else{
ElMessage.error("没有权限打开编辑抽屉")
}
} catch (error) {
ElMessage.error("打开编辑抽屉错")
}
}
/**
* 关闭编辑抽屉的方法
* 调用后将关闭编辑抽屉并重置相关状态
*/
const closeEditDrawer = () => {
console.log("000000-------23-----0000000000")
// 关闭编辑抽屉
isEditDrawer.value = false
......@@ -155,6 +168,9 @@ const closeEditDrawer = () => {
* 显示确认对话框,用户确认后执行删除操作
*/
const deleteData = (data) => {
try {
if(useAppStoreInstance.chackCurPersionOpition()){
console.log("000000---2---------0000000000",data)
ElMessageBox.confirm(
'删除操作不可逆,是否继续?',
......@@ -167,57 +183,64 @@ const deleteData = (data) => {
}
)
.then(() => {
console.log("000000---1-456464564------0000000000","我是确认")
// console.log("000000---1-456464564------0000000000","我是确认",data.id)
try {
const res = staffRemoveInfoApi({id:data.id});
let submitData = {id:data.id};
staffRemoveInfoApi(submitData).then(res => {
if (res.code === 200) {
ElMessage({ type: 'success', message: '已删除' });
tableDataRefresh();
} else {
ElMessage({ type: 'error', message: '删除失败:' + res.msg });
}
})
} catch (err) {
// API 请求失败时在此捕获,不向上冒泡
ElMessage({ type: 'error', message: '删除失败(网络错误)' });
// console.error('删除接口失败:', err);
ElMessage({ type: 'error', message: `删除失败(网络错误)err:${err}` });
}
})
.catch(() => {
}).catch(() => {
ElMessage({
type: 'info',
message: '取消删除',
})
})
}else{
ElMessage.error("没有权限打开编辑抽屉")
}
} catch (error) {
ElMessage.error("打开编辑抽屉错")
}
}
// const deleteUserDate = (data) => {
// staffRemoveInfoApi(data).then((res) => {
// console.log(res,'--------------8--');
// if(res.code==200){
// ElMessage({
// type: 'success',
// message: '已删除',
// })
// tableDataRefresh()
// }
// }).catch(() => {
// ElMessage({
// type: 'info',
// message: '删除shibai',
// })
// })
// }
const isResetPasswordDialogVisible = ref(false); // 控制重置密码弹窗的显示状态
const closeResetPasswordDialogFn = () => {
isResetPasswordDialogVisible.value = false
}
const resetPasswordData = ref({});
const resetData = (data) => {
if(useAppStoreInstance.userInfo.id === data.id){
ElMessage.error("不能重置自己的密码")
}else{
try {
if(useAppStoreInstance.chackCurPersionOpition() ){
console.log("000000-----3-------0000000000",data)
isResetPasswordDialogVisible.value = true;
resetPasswordData.value = data;
}else{
ElMessage.error("没有权限打开重置密码抽屉")
}
} catch (error) {
ElMessage.error("打开重置密码抽屉错误")
}
}
}
const showuserinfodata = ref({})
......@@ -264,10 +287,10 @@ const initTableData = () => {
});
tempData.forEach(element => {
element.status = element.status === 0 ? true : false;
element.Operation = [...Operation.value]
});
tableShowData.value[0].tableBody = tempData
tableShowData.value[0].total = res.data.total
tableShowData.value[0].pageSize = res.data.pageSize
......@@ -285,11 +308,20 @@ onMounted(() => {
// console.log(tableShowData.value, '错误');
initTableData()
})
// 计算容器样式
const containerStyle = computed(() => ({
minHeight: layoutConfig.containerMinHeight,
'--card-spacing': layoutConfig.cardSpacing,
}));
// 监听路由变化
watch(route, () => {
useAppStoreInstance.initavatarUrlFn()
initTableData()
}, { immediate: true });
</script>
<style scoped>
......
......@@ -101,10 +101,10 @@
</div>
</div>
<div class="drone-info-footer">
<el-steps style="width: 100%;" :active="0">
<el-steps style="width: 100%;" :active="2">
<el-step >
<template #icon>
<el-icon style="background-color: rgba(0, 0, 0, 0);"><Edit style="background-color: rgba(0, 0, 0, 0);"/></el-icon>
<el-icon style="padding: 0;margin: 0;"><img style="width: 150%;height: 150%;" src="https://fuss10.elemecdn.com/e/5d/4a731a90594a4af544c0c25941171jpeg.jpeg" alt="" srcset=""></el-icon>
</template>
<template #title>
<span>待命</span>
......@@ -115,7 +115,7 @@
<el-icon style="background-color: rgba(0, 0, 0, 0);"><Edit style="background-color: rgba(0, 0, 0, 0);"/></el-icon>
</template>
<template #title>
<span>待命</span>
<span>飞行中</span>
</template>
</el-step>
<el-step >
......@@ -123,7 +123,7 @@
<el-icon style="background-color: rgba(0, 0, 0, 0);"><Edit style="background-color: rgba(0, 0, 0, 0);"/></el-icon>
</template>
<template #title>
<span>待命</span>
<span >任务完成</span>
</template>
</el-step>
<el-step >
......@@ -131,7 +131,7 @@
<el-icon style="background-color: rgba(0, 0, 0, 0);"><Edit style="background-color: rgba(0, 0, 0, 0);"/></el-icon>
</template>
<template #title>
<span>待命</span>
<span>返航</span>
</template>
</el-step>
......@@ -245,6 +245,12 @@ const getBatteryColor = (level) => {
width: 100%;
/* background-color: #165DFF; */
}
.drone-info-footer span{
font-size: small;
/* background-color: #165DFF; */
}
.data-column {
display: flex;
flex-direction: column;
......
<template>
<div class="dashboard-container" :style="containerStyle">
<main class="main-content">
<!-- 左侧区域 -->
<div class="left-section">
<!-- 左侧上部分 -->
<div class="left-top">
<!-- 区域信息概览 -->
<div class="info-card region-overview">
<searchtop :searchShowData="searchShowData" ></searchtop>
</div>
</div>
<!-- 左侧下部分:统计区域 -->
<div class="info-card stats-alerts">
<div class="card-tabs">
<span class="title" >用户列表</span>
<div class="card-actions">
<el-button class="operation-button" type="primary" size="small" style="width: 15%;">导入</el-button>
<el-button class="operation-button" type="primary" size="small">导出</el-button>
<el-button class="operation-button" type="primary" size="small" @click="handleAddInfoFn">新增</el-button>
<el-button class="operation-button" type="primary" size="small" @click="tableDataRefresh"><el-icon><RefreshRight /></el-icon></el-button>
<el-button class="operation-button" type="primary" size="small"><el-icon><FullScreen /></el-icon></el-button>
</div>
</div>
<div class="card-table">
<tableshow :tableShowData="tableShowData" @editData="uavseedetails" @deleteData="deleteData"></tableshow>
</div>
</div>
</div>
</main>
</div>
</template>
<script setup>
import { reactive, computed, ref, onMounted } from 'vue';
import searchtop from '../../commentcomponents/searchtop/index.vue';
import { ElMessage, ElMessageBox } from 'element-plus'
import tableshow from '../../commentcomponents/tabledata/index.vue';
import { staffGetPageInfoApi } from "../../../api/staff.js"
import { message } from 'ant-design-vue';
// import Operation from 'ant-design-vue/es/transfer/operation';
// 定义一个名为searchShowData的ref变量,它是一个数组,数组中的每个元素都是一个对象,对象中包含了label、placeholder、type、content和options等属性
const searchShowData = ref([
{ label: '用户姓名', placeholder: "请输入", type: 'input',content:'' },
{ label: '用户状态', placeholder: "请选择", type: 'select',content:'' , options: [{label:'启用', value:'启用'}, {label:'禁用', value:'禁用'}] },
{ label: '用户角色', placeholder: "请选择", type: 'select', content:'' ,options: [{label:'管理员', value:'管理员'}, {label:'巡查员', value:'巡查员'}]},
])
// 响应式布局配置
const layoutConfig = reactive({
containerMinHeight: '100vh',
cardSpacing: '1rem',
statsMinHeight: '300px'
});
const isUserInfoDialogVisible = ref(false); // 控制用户信息弹窗的显示状态
/**
* 控制添加抽屉的显示状态
* @type {Ref<boolean>} - 响应式布尔值,用于控制添加抽屉的显示
*/
const isAddDrawer= ref(false)
const closeAddDrawer = () => {
console.log("000000-------3-----0000000000")
// 关闭添加抽屉
isAddDrawer.value = false
}
const handleAddInfoFn = () => {
isAddDrawer.value = true
}
/**
* 刷新表格数据的方法
* 当调用此方法时,会触发表格数据的重新加载
*/
const tableDataRefresh = () => {
initTableData()
console.log("000000-------2-----0000000000,这里获取到最新的数据")
}
/**
* 控制编辑抽屉的显示状态
* @type {Ref<boolean>} - 响应式布尔值,用于控制编辑抽屉的显示
*/
const isEditDrawer = ref(false)
/**
* 存储行数据
* @type {Ref<Array>} - 响应式数组,用于存储当前选中的行数据
*/
const rowData = ref([])
/**
* 编辑数据的方法
* @param {Object} data - 要编辑的数据对象
*/
const editData = (data) => {
// 打开编辑抽屉
isEditDrawer.value = true
// 设置要编辑的数据
rowData.value = data
console.log("000000-------1-----0000000000",data)
}
/**
* 关闭编辑抽屉的方法
* 调用后将关闭编辑抽屉并重置相关状态
*/
const closeEditDrawer = () => {
console.log("000000-------23-----0000000000")
// 关闭编辑抽屉
isEditDrawer.value = false
}
/**
* 删除数据的方法
* @param {Object} data - 要删除的数据对象
* 显示确认对话框,用户确认后执行删除操作
*/
const deleteData = (data) => {
console.log("000000---2---------0000000000",data)
ElMessageBox.confirm(
'删除操作不可逆,是否继续?',
'删除',
{
confirmButtonText: '确认',
cancelButtonText: '取消',
type: 'warning',
draggable: true,
}
)
.then(() => {
console.log("000000---1-456464564------0000000000","我是确认")
try {
const res = staffRemoveInfoApi({id:data.id});
if (res.code === 200) {
ElMessage({ type: 'success', message: '已删除' });
tableDataRefresh();
} else {
ElMessage({ type: 'error', message: '删除失败:' + res.msg });
}
} catch (err) {
// API 请求失败时在此捕获,不向上冒泡
ElMessage({ type: 'error', message: '删除失败(网络错误)' });
// console.error('删除接口失败:', err);
}
})
.catch(() => {
ElMessage({
type: 'info',
message: '取消删除',
})
})
}
// const deleteUserDate = (data) => {
// staffRemoveInfoApi(data).then((res) => {
// console.log(res,'--------------8--');
// if(res.code==200){
// ElMessage({
// type: 'success',
// message: '已删除',
// })
// tableDataRefresh()
// }
// }).catch(() => {
// ElMessage({
// type: 'info',
// message: '删除shibai',
// })
// })
// }
const isResetPasswordDialogVisible = ref(false); // 控制重置密码弹窗的显示状态
const closeResetPasswordDialogFn = () => {
isResetPasswordDialogVisible.value = false
}
const resetPasswordData = ref({});
const resetData = (data) => {
console.log("000000-----3-------0000000000",data)
isResetPasswordDialogVisible.value = true;
resetPasswordData.value = data;
}
const showuserinfodata = ref({})
const userData = (data) => {
console.log("000000------4------0000000000",data)
isUserInfoDialogVisible.value = true
showuserinfodata.value = data
}
const uavseedetails = (data) => {
console.log(data,'---------5---------')
// router.push({
// path: '/dialog_infoShow_img_vid/uavseedetails',
// query: { data: JSON.stringify(data) },
// })
}
const tableShowData = ref([{
tableHeader:[
{label: '序号', prop: 'id'},
{label: '无人机编号', prop: 'uavid'},
{label: '无人机机型', prop: 'uavtype'},
{label: '无人机状态', prop: 'uavstate'},
{label: '无人机载弹数量', prop: 'uavpayloadcapacity'},
{label: '上次维护时间', prop: 'premaintenancetime'},
{label: '弹药信息', prop: 'ammunitioninfo'},
{label: '操作', prop: 'Operation'}
],
tableBody:[
{id: 1, uavid: 'UAV-0001', uavtype: 'UAV-M100', uavstate: '正常', uavpayloadcapacity: '10', premaintenancetime: '2023-05-01', ammunitioninfo: '弹药数量:1000',Operation:[ { label: '查看详情', type: 'primary', icon: 'EditPen', click: uavseedetails }]}
]
}
])
const Operation = ref( [ { label: '查看详情', type: 'primary', icon: 'EditPen', click: uavseedetails }])
const initTableData = () => {
// 这里要使用无人机列表接口
// staffGetPageInfoApi({
// "currentPageNum": 1,
// "currentPageSize": 10,
// }).then(res => {
// if(res.code === 200){
// let tempData = res.data.list
// tableShowData.value[0].tableBody = []
// //获取未逻辑删除的数据
// console.log(res.data,"--------999999999----")
// tempData = tempData.filter(element => {
// return element.isDeleted === 0;
// });
// tempData.forEach(element => {
// element.Operation = [...Operation.value]
// });
// tableShowData.value[0].tableBody = tempData
// tableShowData.value[0].total = res.data.total
// tableShowData.value[0].pageSize = res.data.pageSize
// console.log(tableShowData.value, '展示数据');
// }
// }).catch(err => {
// message.error(err.message)
// })
}
onMounted(() => {
// console.log(tableShowData.value, '错误');
initTableData()
})
// 计算容器样式
const containerStyle = computed(() => ({
minHeight: layoutConfig.containerMinHeight,
'--card-spacing': layoutConfig.cardSpacing,
}));
</script>
<style scoped>
.dashboard-container {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
background-color: rgba(0, 0, 0, 0.05);
color: #333;
overflow: hidden;
}
.main-content {
display: flex;
gap: var(--card-spacing);
padding: 0.4rem;
height: 100vh;
box-sizing: border-box;
}
.left-section {
flex: 2;
display: flex;
flex-direction: column;
gap: var(--card-spacing);
height: 100%;
}
.left-top {
height: auto;
}
/* 通用卡片样式 */
.info-card {
background-color: #fff;
padding: 1rem;
border-radius: 8px;
box-sizing: border-box;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.05);
overflow: auto;
}
.region-overview {
flex: 1;
min-width: 200px;
}
/* 统计区域 */
.stats-alerts {
display: flex;
flex-direction: column;
gap: 1rem;
height: 67%;
}
.card-tabs {
display: flex;
border-bottom: 1px solid #292828;
/* margin-bottom: 1rem; */
font-size: 1.2rem;
color: #000000;
/* background-color: #002aff; */
height: 8%;
display: flex;
align-items: center;
width: 100%;
padding: 10px;
}
.title{
width: 70%;
}
.card-actions {
display: flex;
flex: 1;
justify-content: space-between;
align-items: center;
/* margin-bottom: 1rem; */
height: 8%;
padding: 1.5rem;
}
.operation-button {
width: 15%;
}
.card-tabs-footer {
display: flex;
/* margin-bottom: 1rem; */
font-size: 1.2rem;
color: #000000;
/* background-color: #002aff; */
height: 8%;
align-items: center;
width: 100%;
padding: 10px 0;
justify-content: flex-end;
align-items: center;
}
/* 文本样式 */
.page-text {
color: #606266;
font-size: 14px;
white-space: nowrap; /* 防止文本换行 */
}
/* 修复 Element Plus 分页组件默认的块级布局问题 */
:deep .el-pagination {
display: flex;
align-items: center;
margin: 0; /* 清除默认外边距 */
}
.pagination-block{
height: 8%;
}
.card-table{
width: 100%;
/* display: flex; */
/* background-color: #000000; */
flex:1;
overflow: hidden;
border-radius: 8PX;
}
/* 响应式适配 */
@media (max-width: 1024px) {
.left-top {
flex-direction: column;
}
.region-overview {
width: 100%;
flex: none;
}
}
@media (max-width: 768px) {
.stats-alerts {
flex-direction: column;
}
}
</style>
\ No newline at end of file
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论