优化了路由配置
This commit is contained in:
parent
85dcc74e11
commit
c59e2cf505
22
.env
Normal file
22
.env
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
# 所有环境都会加载的配置
|
||||||
|
|
||||||
|
# 项目名称
|
||||||
|
VITE_APP_TITLE=智慧湿地管理平台
|
||||||
|
|
||||||
|
# 项目描述
|
||||||
|
VITE_APP_DESC=科技赋能生态保护 智慧守护绿色家园
|
||||||
|
|
||||||
|
# 版本号
|
||||||
|
VITE_APP_VERSION=1.0.0
|
||||||
|
|
||||||
|
# 版权信息
|
||||||
|
VITE_APP_COPYRIGHT=Copyright © 2025 智慧湿地管理平台 All Rights Reserved.
|
||||||
|
|
||||||
|
# 是否启用 gzip 压缩
|
||||||
|
VITE_BUILD_GZIP=false
|
||||||
|
|
||||||
|
# 是否启用 brotli 压缩
|
||||||
|
VITE_BUILD_BROTLI=false
|
||||||
|
|
||||||
|
# 是否删除 console
|
||||||
|
VITE_DROP_CONSOLE=true
|
||||||
14
.env.development
Normal file
14
.env.development
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
# 开发环境
|
||||||
|
VITE_NODE_ENV=development
|
||||||
|
|
||||||
|
# API 基础路径
|
||||||
|
VITE_API_BASE_URL=http://localhost:3000
|
||||||
|
|
||||||
|
# 项目基础路径
|
||||||
|
VITE_BASE_URL=/
|
||||||
|
|
||||||
|
# Mock API 路径
|
||||||
|
VITE_MOCK_API=true
|
||||||
|
|
||||||
|
# 是否开启调试工具
|
||||||
|
VITE_DEV_TOOLS=true
|
||||||
14
.env.production
Normal file
14
.env.production
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
# 生产环境
|
||||||
|
VITE_NODE_ENV=production
|
||||||
|
|
||||||
|
# API 基础路径 - 实际项目中替换为真实的后端接口地址
|
||||||
|
VITE_API_BASE_URL=https://api.your-domain.com
|
||||||
|
|
||||||
|
# 项目基础路径
|
||||||
|
VITE_BASE_URL=/
|
||||||
|
|
||||||
|
# Mock API 路径
|
||||||
|
VITE_MOCK_API=false
|
||||||
|
|
||||||
|
# 是否开启调试工具
|
||||||
|
VITE_DEV_TOOLS=false
|
||||||
14
.env.test
Normal file
14
.env.test
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
# 测试环境
|
||||||
|
VITE_NODE_ENV=test
|
||||||
|
|
||||||
|
# API 基础路径
|
||||||
|
VITE_API_BASE_URL=https://test-api.your-domain.com
|
||||||
|
|
||||||
|
# 项目基础路径
|
||||||
|
VITE_BASE_URL=/
|
||||||
|
|
||||||
|
# Mock API 路径
|
||||||
|
VITE_MOCK_API=false
|
||||||
|
|
||||||
|
# 是否开启调试工具
|
||||||
|
VITE_DEV_TOOLS=true
|
||||||
@ -8,6 +8,6 @@
|
|||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
<script type="module" src="/src/main.ts"></script>
|
<script type="module" src="/src/main.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|||||||
7
package-lock.json
generated
7
package-lock.json
generated
@ -15,7 +15,8 @@
|
|||||||
"json-server": "^1.0.0-beta.3",
|
"json-server": "^1.0.0-beta.3",
|
||||||
"pinia": "^2.3.1",
|
"pinia": "^2.3.1",
|
||||||
"vue": "^3.5.13",
|
"vue": "^3.5.13",
|
||||||
"vue-router": "^4.5.0"
|
"vue-router": "^4.5.0",
|
||||||
|
"wetlandguard-admin": "file:"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^22.13.0",
|
"@types/node": "^22.13.0",
|
||||||
@ -2750,6 +2751,10 @@
|
|||||||
"typescript": ">=5.0.0"
|
"typescript": ">=5.0.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/wetlandguard-admin": {
|
||||||
|
"resolved": "",
|
||||||
|
"link": true
|
||||||
|
},
|
||||||
"node_modules/zrender": {
|
"node_modules/zrender": {
|
||||||
"version": "5.6.1",
|
"version": "5.6.1",
|
||||||
"resolved": "https://registry.npmmirror.com/zrender/-/zrender-5.6.1.tgz",
|
"resolved": "https://registry.npmmirror.com/zrender/-/zrender-5.6.1.tgz",
|
||||||
|
|||||||
@ -16,7 +16,8 @@
|
|||||||
"json-server": "^1.0.0-beta.3",
|
"json-server": "^1.0.0-beta.3",
|
||||||
"pinia": "^2.3.1",
|
"pinia": "^2.3.1",
|
||||||
"vue": "^3.5.13",
|
"vue": "^3.5.13",
|
||||||
"vue-router": "^4.5.0"
|
"vue-router": "^4.5.0",
|
||||||
|
"wetlandguard-admin": "file:"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^22.13.0",
|
"@types/node": "^22.13.0",
|
||||||
|
|||||||
20
src/api/login/index.js
Normal file
20
src/api/login/index.js
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
import request from '@/utils/request'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 用户登录
|
||||||
|
* @param {Object} data - 登录参数
|
||||||
|
* @param {string} data.username - 用户名
|
||||||
|
* @param {string} data.password - 密码
|
||||||
|
* @returns {Promise} 返回包含token和用户信息的Promise
|
||||||
|
*/
|
||||||
|
export function login(data) {
|
||||||
|
return request.post('/api/users/login', data)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 退出登录
|
||||||
|
* @returns {Promise}
|
||||||
|
*/
|
||||||
|
export function logout() {
|
||||||
|
return request.post('/api/users/logout')
|
||||||
|
}
|
||||||
@ -1,49 +0,0 @@
|
|||||||
import request from './request';
|
|
||||||
|
|
||||||
// 日常报告接口
|
|
||||||
export const dailyReportApi = {
|
|
||||||
// 获取日常报告列表
|
|
||||||
getList: (params: any) => request.get('/report/daily/list', { params }),
|
|
||||||
|
|
||||||
// 获取日常报告详情
|
|
||||||
getDetail: (id: number) => request.get(`/report/daily/${id}`),
|
|
||||||
|
|
||||||
// 创建日常报告
|
|
||||||
create: (data: any) => request.post('/report/daily', data),
|
|
||||||
|
|
||||||
// 更新日常报告
|
|
||||||
update: (id: number, data: any) => request.put(`/report/daily/${id}`, data),
|
|
||||||
|
|
||||||
// 删除日常报告
|
|
||||||
delete: (id: number) => request.delete(`/report/daily/${id}`),
|
|
||||||
|
|
||||||
// 导出日常报告
|
|
||||||
export: (id: number) => request.get(`/report/daily/export/${id}`, { responseType: 'blob' })
|
|
||||||
};
|
|
||||||
|
|
||||||
// 分析报告接口
|
|
||||||
export const analysisReportApi = {
|
|
||||||
// 获取分析报告列表
|
|
||||||
getList: (params: any) => request.get('/report/analysis/list', { params }),
|
|
||||||
|
|
||||||
// 获取分析报告详情
|
|
||||||
getDetail: (id: number) => request.get(`/report/analysis/${id}`),
|
|
||||||
|
|
||||||
// 创建分析报告
|
|
||||||
create: (data: any) => request.post('/report/analysis', data),
|
|
||||||
|
|
||||||
// 更新分析报告
|
|
||||||
update: (id: number, data: any) => request.put(`/report/analysis/${id}`, data),
|
|
||||||
|
|
||||||
// 删除分析报告
|
|
||||||
delete: (id: number) => request.delete(`/report/analysis/${id}`),
|
|
||||||
|
|
||||||
// 导出分析报告
|
|
||||||
export: (id: number) => request.get(`/report/analysis/export/${id}`, { responseType: 'blob' }),
|
|
||||||
|
|
||||||
// 获取监测数据统计
|
|
||||||
getMonitorStats: (params: any) => request.get('/report/analysis/monitor-stats', { params }),
|
|
||||||
|
|
||||||
// 获取物种数据统计
|
|
||||||
getSpeciesStats: (params: any) => request.get('/report/analysis/species-stats', { params })
|
|
||||||
};
|
|
||||||
@ -1,35 +0,0 @@
|
|||||||
import axios from 'axios'
|
|
||||||
import { ElMessage } from 'element-plus'
|
|
||||||
|
|
||||||
const request = axios.create({
|
|
||||||
baseURL: '/api',
|
|
||||||
timeout: 5000
|
|
||||||
})
|
|
||||||
|
|
||||||
// 请求拦截器
|
|
||||||
request.interceptors.request.use(
|
|
||||||
(config) => {
|
|
||||||
const token = localStorage.getItem('token')
|
|
||||||
if (token) {
|
|
||||||
config.headers.Authorization = `Bearer ${token}`
|
|
||||||
}
|
|
||||||
return config
|
|
||||||
},
|
|
||||||
(error) => {
|
|
||||||
return Promise.reject(error)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
// 响应拦截器
|
|
||||||
request.interceptors.response.use(
|
|
||||||
(response) => {
|
|
||||||
const { data } = response
|
|
||||||
return data
|
|
||||||
},
|
|
||||||
(error) => {
|
|
||||||
ElMessage.error(error.message || '请求失败')
|
|
||||||
return Promise.reject(error)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
export default request
|
|
||||||
0
src/api/user/index.js
Normal file
0
src/api/user/index.js
Normal file
@ -1,41 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { ref } from 'vue'
|
|
||||||
|
|
||||||
defineProps<{ msg: string }>()
|
|
||||||
|
|
||||||
const count = ref(0)
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<h1>{{ msg }}</h1>
|
|
||||||
|
|
||||||
<div class="card">
|
|
||||||
<button type="button" @click="count++">count is {{ count }}</button>
|
|
||||||
<p>
|
|
||||||
Edit
|
|
||||||
<code>components/HelloWorld.vue</code> to test HMR
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<p>
|
|
||||||
Check out
|
|
||||||
<a href="https://vuejs.org/guide/quick-start.html#local" target="_blank"
|
|
||||||
>create-vue</a
|
|
||||||
>, the official Vue + Vite starter
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Learn more about IDE Support for Vue in the
|
|
||||||
<a
|
|
||||||
href="https://vuejs.org/guide/scaling-up/tooling.html#ide-support"
|
|
||||||
target="_blank"
|
|
||||||
>Vue Docs Scaling up Guide</a
|
|
||||||
>.
|
|
||||||
</p>
|
|
||||||
<p class="read-the-docs">Click on the Vite and Vue logos to learn more</p>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<style scoped>
|
|
||||||
.read-the-docs {
|
|
||||||
color: #888;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
@ -1,15 +1,23 @@
|
|||||||
<script setup lang="ts">
|
<script setup>
|
||||||
const props = defineProps<{
|
const props = defineProps({
|
||||||
data: {
|
data: {
|
||||||
qualityTrend: string;
|
type: Object,
|
||||||
indicators: {
|
required: true,
|
||||||
name: string;
|
validator: (value) => {
|
||||||
value: number;
|
return (
|
||||||
threshold: number;
|
value.qualityTrend &&
|
||||||
status: string;
|
Array.isArray(value.indicators) &&
|
||||||
}[];
|
value.indicators.every(
|
||||||
};
|
(indicator) =>
|
||||||
}>();
|
indicator.name &&
|
||||||
|
typeof indicator.value === 'number' &&
|
||||||
|
typeof indicator.threshold === 'number' &&
|
||||||
|
indicator.status
|
||||||
|
)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
@ -19,7 +27,7 @@ const props = defineProps<{
|
|||||||
:type="data.qualityTrend === '改善' ? 'success' : data.qualityTrend === '恶化' ? 'warning' : 'info'"
|
:type="data.qualityTrend === '改善' ? 'success' : data.qualityTrend === '恶化' ? 'warning' : 'info'"
|
||||||
show-icon
|
show-icon
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<div class="indicators-table">
|
<div class="indicators-table">
|
||||||
<el-table :data="data.indicators" border style="width: 100%">
|
<el-table :data="data.indicators" border style="width: 100%">
|
||||||
<el-table-column prop="name" label="监测指标" />
|
<el-table-column prop="name" label="监测指标" />
|
||||||
@ -43,4 +51,4 @@ const props = defineProps<{
|
|||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@ -1,19 +1,29 @@
|
|||||||
<script setup lang="ts">
|
<script setup>
|
||||||
import { ref, onMounted, onUnmounted } from 'vue';
|
import { ref, onMounted, onUnmounted } from 'vue';
|
||||||
import * as echarts from 'echarts';
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps({
|
||||||
data: any;
|
data: {
|
||||||
type: 'line' | 'bar';
|
type: [Object, Array],
|
||||||
title?: string;
|
required: true
|
||||||
}>();
|
},
|
||||||
|
type: {
|
||||||
|
type: String,
|
||||||
|
validator: (value) => ['line', 'bar'].includes(value),
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
required: false
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
let chart: echarts.ECharts | null = null;
|
let chart = null;
|
||||||
|
|
||||||
const initChart = () => {
|
const initChart = () => {
|
||||||
const chartDom = document.getElementById('monitorChart');
|
const chartDom = document.getElementById('monitorChart');
|
||||||
if (!chartDom) return;
|
if (!chartDom) return;
|
||||||
|
|
||||||
chart = echarts.init(chartDom);
|
chart = echarts.init(chartDom);
|
||||||
const option = {
|
const option = {
|
||||||
title: {
|
title: {
|
||||||
@ -24,7 +34,7 @@ const initChart = () => {
|
|||||||
},
|
},
|
||||||
// ... 其他配置
|
// ... 其他配置
|
||||||
};
|
};
|
||||||
|
|
||||||
chart.setOption(option);
|
chart.setOption(option);
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -45,4 +55,4 @@ const handleResize = () => {
|
|||||||
|
|
||||||
<template>
|
<template>
|
||||||
<div id="monitorChart" style="width: 100%; height: 400px"></div>
|
<div id="monitorChart" style="width: 100%; height: 400px"></div>
|
||||||
</template>
|
</template>
|
||||||
@ -1,13 +1,19 @@
|
|||||||
<script setup lang="ts">
|
<script setup>
|
||||||
import { computed } from 'vue';
|
import { computed } from 'vue';
|
||||||
|
|
||||||
const props = defineProps<{
|
const props = defineProps({
|
||||||
data: {
|
data: {
|
||||||
diversity: number;
|
type: Object,
|
||||||
richness: number;
|
required: true,
|
||||||
distribution: any[];
|
validator: (value) => {
|
||||||
};
|
return (
|
||||||
}>();
|
typeof value.diversity === 'number' &&
|
||||||
|
typeof value.richness === 'number' &&
|
||||||
|
Array.isArray(value.distribution)
|
||||||
|
);
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
const diversityLevel = computed(() => {
|
const diversityLevel = computed(() => {
|
||||||
const value = props.data.diversity;
|
const value = props.data.diversity;
|
||||||
@ -30,7 +36,7 @@ const diversityLevel = computed(() => {
|
|||||||
{{ data.richness }}
|
{{ data.richness }}
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
</el-descriptions>
|
</el-descriptions>
|
||||||
|
|
||||||
<!-- 分布情况图表 -->
|
<!-- 分布情况图表 -->
|
||||||
<div class="distribution-chart">
|
<div class="distribution-chart">
|
||||||
<!-- 这里可以添加物种分布的图表展示 -->
|
<!-- 这里可以添加物种分布的图表展示 -->
|
||||||
@ -44,4 +50,4 @@ const diversityLevel = computed(() => {
|
|||||||
margin-top: 20px;
|
margin-top: 20px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@ -1,4 +1,4 @@
|
|||||||
<script setup lang="ts">
|
<script setup>
|
||||||
import { ref, watch } from "vue";
|
import { ref, watch } from "vue";
|
||||||
import { useRouter, useRoute } from "vue-router";
|
import { useRouter, useRoute } from "vue-router";
|
||||||
import { ElMessageBox } from 'element-plus';
|
import { ElMessageBox } from 'element-plus';
|
||||||
@ -45,7 +45,7 @@ watch(
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const handleSelect = (key: string) => {
|
const handleSelect = (key) => {
|
||||||
router.push(key);
|
router.push(key);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@ -14,4 +14,4 @@ export const menuData = [
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
@ -17,4 +17,4 @@ app.use(createPinia())
|
|||||||
app.use(router)
|
app.use(router)
|
||||||
app.use(ElementPlus)
|
app.use(ElementPlus)
|
||||||
|
|
||||||
app.mount('#app')
|
app.mount('#app')
|
||||||
@ -5,6 +5,14 @@ import { useUserStore } from '../stores/user'
|
|||||||
const router = createRouter({
|
const router = createRouter({
|
||||||
history: createWebHistory(),
|
history: createWebHistory(),
|
||||||
routes: [
|
routes: [
|
||||||
|
{
|
||||||
|
path: '/',
|
||||||
|
redirect: to => {
|
||||||
|
// 如果已登录,重定向到后台首页,否则重定向到登录页
|
||||||
|
const userStore = useUserStore()
|
||||||
|
return userStore.isLoggedIn ? '/dashboard' : '/login'
|
||||||
|
}
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: '/login',
|
path: '/login',
|
||||||
name: 'Login',
|
name: 'Login',
|
||||||
@ -104,22 +112,22 @@ const router = createRouter({
|
|||||||
// 路由守卫
|
// 路由守卫
|
||||||
router.beforeEach((to, from, next) => {
|
router.beforeEach((to, from, next) => {
|
||||||
const userStore = useUserStore()
|
const userStore = useUserStore()
|
||||||
|
|
||||||
// 如果访问登录页且已登录,重定向到首页
|
// 如果访问登录页且已登录,重定向到首页
|
||||||
if (to.path === '/login' && userStore.isLoggedIn) {
|
if (to.path === '/login' && userStore.isLoggedIn) {
|
||||||
next('/')
|
next('/')
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果需要认证但未登录,重定向到登录页
|
// 如果需要认证但未登录,重定向到登录页
|
||||||
if (to.matched.some(record => record.meta.requiresAuth) && !userStore.isLoggedIn) {
|
if (to.matched.some(record => record.meta.requiresAuth) && !userStore.isLoggedIn) {
|
||||||
next({
|
next({
|
||||||
path: '/login',
|
path: '/login',
|
||||||
query: { redirect: to.fullPath } // 保存原目标路径
|
query: { redirect: to.fullPath }
|
||||||
})
|
})
|
||||||
} else {
|
} else {
|
||||||
next()
|
next()
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
export default router
|
export default router
|
||||||
@ -1,25 +1,14 @@
|
|||||||
import { defineStore } from 'pinia'
|
import { defineStore } from 'pinia'
|
||||||
|
|
||||||
interface LogEntry {
|
|
||||||
id: number;
|
|
||||||
type: string;
|
|
||||||
user: string;
|
|
||||||
action: string;
|
|
||||||
ip: string;
|
|
||||||
status: string;
|
|
||||||
detail: string;
|
|
||||||
createTime: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useSystemLogStore = defineStore('systemLog', {
|
export const useSystemLogStore = defineStore('systemLog', {
|
||||||
state: () => ({
|
state: () => ({
|
||||||
logs: [] as LogEntry[]
|
logs: []
|
||||||
}),
|
}),
|
||||||
|
|
||||||
actions: {
|
actions: {
|
||||||
// 添加日志
|
// 添加日志
|
||||||
addLog(log: Omit<LogEntry, 'id' | 'createTime'>) {
|
addLog(log) {
|
||||||
const newLog: LogEntry = {
|
const newLog = {
|
||||||
...log,
|
...log,
|
||||||
id: Date.now(),
|
id: Date.now(),
|
||||||
createTime: new Date().toLocaleString('zh-CN', {
|
createTime: new Date().toLocaleString('zh-CN', {
|
||||||
@ -32,7 +21,7 @@ export const useSystemLogStore = defineStore('systemLog', {
|
|||||||
hour12: false
|
hour12: false
|
||||||
}).replace(/\//g, '-')
|
}).replace(/\//g, '-')
|
||||||
};
|
};
|
||||||
|
|
||||||
this.logs.unshift(newLog);
|
this.logs.unshift(newLog);
|
||||||
this.saveLogs();
|
this.saveLogs();
|
||||||
},
|
},
|
||||||
@ -60,4 +49,4 @@ export const useSystemLogStore = defineStore('systemLog', {
|
|||||||
getters: {
|
getters: {
|
||||||
getLogs: (state) => state.logs
|
getLogs: (state) => state.logs
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
53
src/stores/user.js
Normal file
53
src/stores/user.js
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import { defineStore } from 'pinia'
|
||||||
|
import { login as loginApi, logout as logoutApi } from '@/api/login'
|
||||||
|
|
||||||
|
export const useUserStore = defineStore('user', {
|
||||||
|
state: () => ({
|
||||||
|
token: localStorage.getItem('token'),
|
||||||
|
userInfo: JSON.parse(localStorage.getItem('userInfo')) || null
|
||||||
|
}),
|
||||||
|
|
||||||
|
actions: {
|
||||||
|
// 登录
|
||||||
|
async login(username, password) {
|
||||||
|
try {
|
||||||
|
// 调用登录接口
|
||||||
|
const { token, userInfo } = await loginApi({ username, password })
|
||||||
|
|
||||||
|
// 保存token和用户信息
|
||||||
|
this.token = token
|
||||||
|
this.userInfo = userInfo
|
||||||
|
|
||||||
|
// 持久化存储
|
||||||
|
localStorage.setItem('token', token)
|
||||||
|
localStorage.setItem('userInfo', JSON.stringify(userInfo))
|
||||||
|
|
||||||
|
return true
|
||||||
|
} catch (error) {
|
||||||
|
console.error('登录失败:', error)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
// 退出登录
|
||||||
|
async logout() {
|
||||||
|
try {
|
||||||
|
await logoutApi()
|
||||||
|
} catch (error) {
|
||||||
|
console.error('退出登录失败:', error)
|
||||||
|
} finally {
|
||||||
|
// 无论是否成功调用退出接口,都清除本地存储
|
||||||
|
this.token = null
|
||||||
|
this.userInfo = null
|
||||||
|
localStorage.removeItem('token')
|
||||||
|
localStorage.removeItem('userInfo')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
|
||||||
|
getters: {
|
||||||
|
isLoggedIn: (state) => !!state.token,
|
||||||
|
username: (state) => state.userInfo?.username,
|
||||||
|
userRole: (state) => state.userInfo?.role
|
||||||
|
}
|
||||||
|
})
|
||||||
@ -1,44 +0,0 @@
|
|||||||
import { defineStore } from 'pinia'
|
|
||||||
|
|
||||||
interface UserState {
|
|
||||||
token: string | null;
|
|
||||||
userInfo: {
|
|
||||||
username: string;
|
|
||||||
role: string;
|
|
||||||
} | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const useUserStore = defineStore('user', {
|
|
||||||
state: (): UserState => ({
|
|
||||||
token: localStorage.getItem('token'),
|
|
||||||
userInfo: null
|
|
||||||
}),
|
|
||||||
|
|
||||||
actions: {
|
|
||||||
// 登录
|
|
||||||
async login(username: string, password: string) {
|
|
||||||
if (username === 'admin' && password === '123456') {
|
|
||||||
this.token = 'demo-token';
|
|
||||||
this.userInfo = {
|
|
||||||
username: 'admin',
|
|
||||||
role: '管理员'
|
|
||||||
};
|
|
||||||
localStorage.setItem('token', this.token);
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
|
|
||||||
// 退出登录
|
|
||||||
logout() {
|
|
||||||
this.token = null;
|
|
||||||
this.userInfo = null;
|
|
||||||
localStorage.removeItem('token');
|
|
||||||
}
|
|
||||||
},
|
|
||||||
|
|
||||||
getters: {
|
|
||||||
isLoggedIn: (state) => !!state.token,
|
|
||||||
username: (state) => state.userInfo?.username
|
|
||||||
}
|
|
||||||
})
|
|
||||||
166
src/utils/request.js
Normal file
166
src/utils/request.js
Normal file
@ -0,0 +1,166 @@
|
|||||||
|
import axios from 'axios'
|
||||||
|
import { ElMessage } from 'element-plus'
|
||||||
|
import { useUserStore } from '../stores/user'
|
||||||
|
import router from '../router'
|
||||||
|
|
||||||
|
// 创建axios实例
|
||||||
|
const service = axios.create({
|
||||||
|
// 从环境变量获取基础URL,如果没有则使用默认值
|
||||||
|
baseURL: import.meta.env.VITE_API_BASE_URL || '/api',
|
||||||
|
timeout: 15000, // 请求超时时间
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json;charset=utf-8'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// 请求拦截器
|
||||||
|
service.interceptors.request.use(
|
||||||
|
(config) => {
|
||||||
|
const userStore = useUserStore()
|
||||||
|
const token = userStore.token
|
||||||
|
|
||||||
|
// 如果有token,添加到请求头
|
||||||
|
if (token) {
|
||||||
|
config.headers = config.headers || {}
|
||||||
|
config.headers['Authorization'] = `Bearer ${token}`
|
||||||
|
}
|
||||||
|
|
||||||
|
return config
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
console.error('请求错误:', error)
|
||||||
|
return Promise.reject(error)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// 响应拦截器
|
||||||
|
service.interceptors.response.use(
|
||||||
|
(response) => {
|
||||||
|
const res = response.data
|
||||||
|
|
||||||
|
// 模拟API响应
|
||||||
|
if (response.config.url.includes('/api/users/')) {
|
||||||
|
// 模拟登录接口
|
||||||
|
if (response.config.url.includes('/login')) {
|
||||||
|
return {
|
||||||
|
token: 'demo-token',
|
||||||
|
userInfo: {
|
||||||
|
id: 1,
|
||||||
|
username: 'admin',
|
||||||
|
role: '管理员',
|
||||||
|
email: 'admin@example.com',
|
||||||
|
status: true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 模拟退出登录接口
|
||||||
|
if (response.config.url.includes('/logout')) {
|
||||||
|
return { message: '退出成功' }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果响应成功
|
||||||
|
if (res.code === 200 || res.code === undefined) {
|
||||||
|
return res.data || res
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理特定错误码
|
||||||
|
switch (res.code) {
|
||||||
|
case 401:
|
||||||
|
handleUnauthorized()
|
||||||
|
break
|
||||||
|
case 403:
|
||||||
|
ElMessage.error('没有权限访问该资源')
|
||||||
|
break
|
||||||
|
case 500:
|
||||||
|
ElMessage.error('服务器错误,请稍后重试')
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
ElMessage.error(res.message || '请求失败')
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.reject(new Error(res.message || '请求失败'))
|
||||||
|
},
|
||||||
|
(error) => {
|
||||||
|
console.error('响应错误:', error)
|
||||||
|
|
||||||
|
// 处理网络错误
|
||||||
|
if (!error.response) {
|
||||||
|
ElMessage.error('网络错误,请检查您的网络连接')
|
||||||
|
return Promise.reject(error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理HTTP状态码错误
|
||||||
|
const status = error.response.status
|
||||||
|
switch (status) {
|
||||||
|
case 401:
|
||||||
|
handleUnauthorized()
|
||||||
|
break
|
||||||
|
case 403:
|
||||||
|
ElMessage.error('没有权限访问该资源')
|
||||||
|
break
|
||||||
|
case 404:
|
||||||
|
ElMessage.error('请求的资源不存在')
|
||||||
|
break
|
||||||
|
case 500:
|
||||||
|
ElMessage.error('服务器错误,请稍后重试')
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
ElMessage.error(`请求失败:${error.message}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
return Promise.reject(error)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// 处理未授权情况
|
||||||
|
const handleUnauthorized = () => {
|
||||||
|
const userStore = useUserStore()
|
||||||
|
userStore.logout() // 清除用户信息
|
||||||
|
|
||||||
|
// 跳转到登录页,并携带当前页面路径
|
||||||
|
const currentPath = router.currentRoute.value.fullPath
|
||||||
|
router.push({
|
||||||
|
path: '/login',
|
||||||
|
query: {
|
||||||
|
redirect: currentPath
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
ElMessage.error('登录已过期,请重新登录')
|
||||||
|
}
|
||||||
|
|
||||||
|
// 封装请求方法
|
||||||
|
export const request = {
|
||||||
|
get(url, config) {
|
||||||
|
return service.get(url, config)
|
||||||
|
},
|
||||||
|
|
||||||
|
post(url, data, config) {
|
||||||
|
return service.post(url, data, config)
|
||||||
|
},
|
||||||
|
|
||||||
|
put(url, data, config) {
|
||||||
|
return service.put(url, data, config)
|
||||||
|
},
|
||||||
|
|
||||||
|
delete(url, config) {
|
||||||
|
return service.delete(url, config)
|
||||||
|
},
|
||||||
|
|
||||||
|
// 上传文件的专用方法
|
||||||
|
upload(url, file, config) {
|
||||||
|
const formData = new FormData()
|
||||||
|
formData.append('file', file)
|
||||||
|
|
||||||
|
return service.post(url, formData, {
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'multipart/form-data'
|
||||||
|
},
|
||||||
|
...config
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export default request
|
||||||
@ -1,4 +1,4 @@
|
|||||||
<script setup lang="ts">
|
<script setup>
|
||||||
import { ref, onMounted } from "vue";
|
import { ref, onMounted } from "vue";
|
||||||
import * as echarts from "echarts";
|
import * as echarts from "echarts";
|
||||||
import { markRaw } from 'vue'
|
import { markRaw } from 'vue'
|
||||||
@ -377,8 +377,8 @@ onMounted(() => {
|
|||||||
<template>
|
<template>
|
||||||
<div class="dashboard-container">
|
<div class="dashboard-container">
|
||||||
<div class="stats-grid">
|
<div class="stats-grid">
|
||||||
<div v-for="card in statsCards"
|
<div v-for="card in statsCards"
|
||||||
:key="card.title"
|
:key="card.title"
|
||||||
class="stats-card"
|
class="stats-card"
|
||||||
:style="{ backgroundColor: card.bgColor }">
|
:style="{ backgroundColor: card.bgColor }">
|
||||||
<div class="card-header">
|
<div class="card-header">
|
||||||
@ -394,16 +394,16 @@ onMounted(() => {
|
|||||||
{{ card.value }}
|
{{ card.value }}
|
||||||
<span class="unit">{{ card.unit }}</span>
|
<span class="unit">{{ card.unit }}</span>
|
||||||
</div>
|
</div>
|
||||||
<div class="change-value"
|
<div class="change-value"
|
||||||
:style="{ color: card.change.value.includes('+') ? '#67C23A' :
|
:style="{ color: card.change.value.includes('+') ? '#67C23A' :
|
||||||
card.change.value.includes('%') ? card.color : '#F56C6C' }">
|
card.change.value.includes('%') ? card.color : '#F56C6C' }">
|
||||||
{{ card.change.value }}
|
{{ card.change.value }}
|
||||||
<span class="label">{{ card.change.label }}</span>
|
<span class="label">{{ card.change.label }}</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div class="card-footer">
|
<div class="card-footer">
|
||||||
<div v-for="feature in card.features"
|
<div v-for="feature in card.features"
|
||||||
:key="feature"
|
:key="feature"
|
||||||
class="feature-item">
|
class="feature-item">
|
||||||
{{ feature }}
|
{{ feature }}
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
<script setup lang="ts">
|
<script setup>
|
||||||
import { ref, onMounted, onUnmounted } from "vue";
|
import { ref, onMounted, onUnmounted } from "vue";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import * as echarts from "echarts";
|
import * as echarts from "echarts";
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
<script setup lang="ts">
|
<script setup>
|
||||||
import { ref, reactive } from "vue";
|
import { ref, reactive } from "vue";
|
||||||
import { useRouter } from "vue-router";
|
import { useRouter } from "vue-router";
|
||||||
import { ElMessage } from "element-plus";
|
import { ElMessage } from "element-plus";
|
||||||
@ -42,7 +42,7 @@ const getCurrentTime = () => {
|
|||||||
.replace(/\//g, "-");
|
.replace(/\//g, "-");
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleLogin = async (formEl: any) => {
|
const handleLogin = async (formEl) => {
|
||||||
if (!formEl) return;
|
if (!formEl) return;
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
try {
|
try {
|
||||||
@ -67,7 +67,7 @@ const handleLogin = async (formEl: any) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 获取重定向地址
|
// 获取重定向地址
|
||||||
const redirect = route.query.redirect as string;
|
const redirect = route.query.redirect;
|
||||||
router.push(redirect || "/dashboard");
|
router.push(redirect || "/dashboard");
|
||||||
} else {
|
} else {
|
||||||
// 记录失败日志
|
// 记录失败日志
|
||||||
@ -92,7 +92,7 @@ const handleLogin = async (formEl: any) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 添加输入框获得焦点时的处理函数
|
// 添加输入框获得焦点时的处理函数
|
||||||
const handleFocus = (prop: "username" | "password") => {
|
const handleFocus = (prop) => {
|
||||||
if (formRef.value) {
|
if (formRef.value) {
|
||||||
// 清除对应字段的验证错误
|
// 清除对应字段的验证错误
|
||||||
formRef.value.clearValidate(prop);
|
formRef.value.clearValidate(prop);
|
||||||
@ -161,7 +161,7 @@ const handleFocus = (prop: "username" | "password") => {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import "../../styles/variables.scss";
|
@use "../../styles/variables.scss" as *;
|
||||||
|
|
||||||
.login-container {
|
.login-container {
|
||||||
height: 100vh;
|
height: 100vh;
|
||||||
|
|||||||
@ -1,19 +1,10 @@
|
|||||||
<script setup lang="ts">
|
<script setup>
|
||||||
import { ref, reactive, onMounted } from "vue";
|
import { ref, reactive, onMounted } from "vue";
|
||||||
import * as echarts from "echarts";
|
import * as echarts from "echarts";
|
||||||
import { ElMessage } from "element-plus";
|
import { ElMessage } from "element-plus";
|
||||||
import { Monitor, Warning, TrendCharts } from "@element-plus/icons-vue";
|
import { Monitor, Warning, TrendCharts } from "@element-plus/icons-vue";
|
||||||
|
|
||||||
interface EnvData {
|
const tableData = ref([
|
||||||
id: number;
|
|
||||||
location: string;
|
|
||||||
temperature: number;
|
|
||||||
humidity: number;
|
|
||||||
waterQuality: string;
|
|
||||||
time: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const tableData = ref<EnvData[]>([
|
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
location: "A区监测点",
|
location: "A区监测点",
|
||||||
@ -90,9 +81,9 @@ const initChart = () => {
|
|||||||
textStyle: {
|
textStyle: {
|
||||||
color: "#666",
|
color: "#666",
|
||||||
},
|
},
|
||||||
formatter: function (params: any) {
|
formatter: function (params) {
|
||||||
let result = `${params[0].axisValue}<br/>`;
|
let result = `${params[0].axisValue}<br/>`;
|
||||||
params.forEach((item: any) => {
|
params.forEach((item) => {
|
||||||
result += `${item.marker} ${item.seriesName}: ${item.value}${
|
result += `${item.marker} ${item.seriesName}: ${item.value}${
|
||||||
item.seriesName.includes("温度")
|
item.seriesName.includes("温度")
|
||||||
? "°C"
|
? "°C"
|
||||||
@ -247,33 +238,30 @@ const initChart = () => {
|
|||||||
|
|
||||||
myChart.setOption(option);
|
myChart.setOption(option);
|
||||||
|
|
||||||
// 监听窗口大小变化
|
// 处理窗口大小变化
|
||||||
window.addEventListener("resize", () => {
|
window.addEventListener("resize", () => {
|
||||||
myChart.resize();
|
myChart.resize();
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 添加导出相关的数据和方法
|
// 导出相关数据
|
||||||
const exportDialogVisible = ref(false);
|
const exportDialogVisible = ref(false);
|
||||||
const exportForm = reactive({
|
const exportForm = reactive({
|
||||||
timeRange: [] as string[],
|
timeRange: [],
|
||||||
format: "excel",
|
|
||||||
dataType: ["temperature", "humidity", "waterQuality"],
|
dataType: ["temperature", "humidity", "waterQuality"],
|
||||||
|
format: "excel",
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleExport = () => {
|
// 处理导出确认
|
||||||
exportDialogVisible.value = true;
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleExportConfirm = () => {
|
const handleExportConfirm = () => {
|
||||||
console.log("导出数据:", exportForm);
|
ElMessage.success("数据导出成功");
|
||||||
exportDialogVisible.value = false;
|
exportDialogVisible.value = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 初始化迷你图表
|
// 初始化迷你图表
|
||||||
const initMiniChart = (el: HTMLElement, data: number[]) => {
|
const initMiniChart = (el, data) => {
|
||||||
const chart = echarts.init(el);
|
const chart = echarts.init(el);
|
||||||
chart.setOption({
|
const option = {
|
||||||
grid: {
|
grid: {
|
||||||
left: 0,
|
left: 0,
|
||||||
right: 0,
|
right: 0,
|
||||||
@ -357,7 +345,8 @@ const initMiniChart = (el: HTMLElement, data: number[]) => {
|
|||||||
},
|
},
|
||||||
padding: [4, 8],
|
padding: [4, 8],
|
||||||
},
|
},
|
||||||
});
|
};
|
||||||
|
chart.setOption(option);
|
||||||
|
|
||||||
// 监听卡片hover事件
|
// 监听卡片hover事件
|
||||||
const card = el.closest(".env-card");
|
const card = el.closest(".env-card");
|
||||||
@ -378,15 +367,12 @@ const initMiniChart = (el: HTMLElement, data: number[]) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
// 初始化所有迷你图表
|
|
||||||
envStats.value.forEach((stat, index) => {
|
|
||||||
const el = document.getElementById(`miniChart${index}`);
|
|
||||||
if (el) {
|
|
||||||
initMiniChart(el, stat.trend);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
// 原有的图表初始化
|
|
||||||
initChart();
|
initChart();
|
||||||
|
// 初始化所有迷你图表
|
||||||
|
const chartEls = document.querySelectorAll(".env-chart");
|
||||||
|
chartEls.forEach((el, index) => {
|
||||||
|
initMiniChart(el, envStats.value[index].trend);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@ -482,7 +468,7 @@ onMounted(() => {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import "../../../styles/variables.scss";
|
@use "./styles/variables" as v;
|
||||||
|
|
||||||
.env-container {
|
.env-container {
|
||||||
.env-card {
|
.env-card {
|
||||||
|
|||||||
@ -1,18 +1,9 @@
|
|||||||
<script setup lang="ts">
|
<script setup>
|
||||||
import { ref, reactive } from "vue";
|
import { ref, reactive } from "vue";
|
||||||
import * as echarts from "echarts";
|
import * as echarts from "echarts";
|
||||||
import { onMounted } from "vue";
|
import { onMounted } from "vue";
|
||||||
|
|
||||||
interface SpeciesData {
|
const tableData = ref([
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
type: string;
|
|
||||||
count: number;
|
|
||||||
location: string;
|
|
||||||
time: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const tableData = ref<SpeciesData[]>([
|
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
name: "东方白鹳",
|
name: "东方白鹳",
|
||||||
@ -148,7 +139,7 @@ const initChart = () => {
|
|||||||
label: {
|
label: {
|
||||||
show: true,
|
show: true,
|
||||||
position: 'outside',
|
position: 'outside',
|
||||||
formatter: function(params: any) {
|
formatter: function(params) {
|
||||||
return `${params.name}\n${params.percent.toFixed(1)}%`;
|
return `${params.name}\n${params.percent.toFixed(1)}%`;
|
||||||
},
|
},
|
||||||
color: '#666',
|
color: '#666',
|
||||||
@ -172,10 +163,10 @@ const initChart = () => {
|
|||||||
animationDurationUpdate: 500,
|
animationDurationUpdate: 500,
|
||||||
animationType: 'expansion',
|
animationType: 'expansion',
|
||||||
animationEasing: 'cubicInOut',
|
animationEasing: 'cubicInOut',
|
||||||
animationDelay: function(idx: number) {
|
animationDelay: function(idx) {
|
||||||
return idx * 100;
|
return idx * 100;
|
||||||
},
|
},
|
||||||
animationDelayUpdate: function(idx: number) {
|
animationDelayUpdate: function(idx) {
|
||||||
return idx * 100;
|
return idx * 100;
|
||||||
},
|
},
|
||||||
emphasis: {
|
emphasis: {
|
||||||
@ -201,7 +192,7 @@ const initChart = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
myChart.setOption(option);
|
myChart.setOption(option);
|
||||||
|
|
||||||
window.addEventListener('resize', () => {
|
window.addEventListener('resize', () => {
|
||||||
myChart.resize();
|
myChart.resize();
|
||||||
});
|
});
|
||||||
@ -214,7 +205,7 @@ onMounted(() => {
|
|||||||
// 添加导出相关的数据和方法
|
// 添加导出相关的数据和方法
|
||||||
const exportDialogVisible = ref(false);
|
const exportDialogVisible = ref(false);
|
||||||
const exportForm = reactive({
|
const exportForm = reactive({
|
||||||
timeRange: [] as string[],
|
timeRange: [],
|
||||||
format: "excel",
|
format: "excel",
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -306,7 +297,7 @@ const handleExportConfirm = () => {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import "../../../styles/variables.scss";
|
@use "./styles/variables" as v;
|
||||||
|
|
||||||
.species-container {
|
.species-container {
|
||||||
padding: 20px;
|
padding: 20px;
|
||||||
|
|||||||
@ -1,18 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup>
|
||||||
import { ref, reactive, computed } from "vue";
|
import { ref, reactive, computed } from "vue";
|
||||||
import { ElMessage, ElMessageBox } from "element-plus";
|
import { ElMessage, ElMessageBox } from "element-plus";
|
||||||
|
|
||||||
interface PointData {
|
const tableData = ref([
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
location: string;
|
|
||||||
type: string;
|
|
||||||
description: string;
|
|
||||||
status: string;
|
|
||||||
lastPatrolTime: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const tableData = ref<PointData[]>([
|
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
name: "A区-1号点位",
|
name: "A区-1号点位",
|
||||||
@ -56,25 +46,25 @@ const handleAdd = () => {
|
|||||||
|
|
||||||
// 添加搜索相关
|
// 添加搜索相关
|
||||||
const searchText = ref("");
|
const searchText = ref("");
|
||||||
const typeFilter = ref<string[]>([]);
|
const typeFilter = ref([]);
|
||||||
const statusFilter = ref<string[]>([]);
|
const statusFilter = ref([]);
|
||||||
|
|
||||||
// 过滤表格数据
|
// 过滤表格数据
|
||||||
const filteredTableData = computed(() => {
|
const filteredTableData = computed(() => {
|
||||||
return tableData.value.filter(point => {
|
return tableData.value.filter(point => {
|
||||||
// 文本搜索
|
// 文本搜索
|
||||||
const searchLower = searchText.value.toLowerCase();
|
const searchLower = searchText.value.toLowerCase();
|
||||||
const textMatch = !searchText.value ||
|
const textMatch = !searchText.value ||
|
||||||
point.name.toLowerCase().includes(searchLower) ||
|
point.name.toLowerCase().includes(searchLower) ||
|
||||||
point.location.toLowerCase().includes(searchLower) ||
|
point.location.toLowerCase().includes(searchLower) ||
|
||||||
point.description.toLowerCase().includes(searchLower);
|
point.description.toLowerCase().includes(searchLower);
|
||||||
|
|
||||||
// 类型筛选
|
// 类型筛选
|
||||||
const typeMatch = typeFilter.value.length === 0 ||
|
const typeMatch = typeFilter.value.length === 0 ||
|
||||||
typeFilter.value.includes(point.type);
|
typeFilter.value.includes(point.type);
|
||||||
|
|
||||||
// 状态筛选
|
// 状态筛选
|
||||||
const statusMatch = statusFilter.value.length === 0 ||
|
const statusMatch = statusFilter.value.length === 0 ||
|
||||||
statusFilter.value.includes(point.status);
|
statusFilter.value.includes(point.status);
|
||||||
|
|
||||||
return textMatch && typeMatch && statusMatch;
|
return textMatch && typeMatch && statusMatch;
|
||||||
@ -90,9 +80,9 @@ const resetFilters = () => {
|
|||||||
|
|
||||||
// 添加编辑相关
|
// 添加编辑相关
|
||||||
const isEdit = ref(false);
|
const isEdit = ref(false);
|
||||||
const currentPoint = ref<PointData | null>(null);
|
const currentPoint = ref(null);
|
||||||
|
|
||||||
const handleEdit = (row: PointData) => {
|
const handleEdit = (row) => {
|
||||||
isEdit.value = true;
|
isEdit.value = true;
|
||||||
currentPoint.value = row;
|
currentPoint.value = row;
|
||||||
formData.name = row.name;
|
formData.name = row.name;
|
||||||
@ -105,7 +95,7 @@ const handleEdit = (row: PointData) => {
|
|||||||
// 更新提交处理
|
// 更新提交处理
|
||||||
const handleSubmit = async () => {
|
const handleSubmit = async () => {
|
||||||
if (!formRef.value) return;
|
if (!formRef.value) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await formRef.value.validate();
|
await formRef.value.validate();
|
||||||
if (isEdit.value) {
|
if (isEdit.value) {
|
||||||
@ -145,16 +135,16 @@ const paginatedData = computed(() => {
|
|||||||
return filteredTableData.value.slice(start, end);
|
return filteredTableData.value.slice(start, end);
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleSizeChange = (val: number) => {
|
const handleSizeChange = (val) => {
|
||||||
pageSize.value = val;
|
pageSize.value = val;
|
||||||
currentPage.value = 1;
|
currentPage.value = 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCurrentChange = (val: number) => {
|
const handleCurrentChange = (val) => {
|
||||||
currentPage.value = val;
|
currentPage.value = val;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete = (row: PointData) => {
|
const handleDelete = (row) => {
|
||||||
ElMessageBox.confirm("确认删除该巡护点位?", "提示", {
|
ElMessageBox.confirm("确认删除该巡护点位?", "提示", {
|
||||||
confirmButtonText: "确定",
|
confirmButtonText: "确定",
|
||||||
cancelButtonText: "取消",
|
cancelButtonText: "取消",
|
||||||
@ -253,9 +243,9 @@ const pointTypes = [
|
|||||||
<el-table-column label="操作" width="150" fixed="right">
|
<el-table-column label="操作" width="150" fixed="right">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-button type="primary" size="small" link @click="handleEdit(row)">编辑</el-button>
|
<el-button type="primary" size="small" link @click="handleEdit(row)">编辑</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
type="danger"
|
type="danger"
|
||||||
size="small"
|
size="small"
|
||||||
link
|
link
|
||||||
@click="handleDelete(row)"
|
@click="handleDelete(row)"
|
||||||
>
|
>
|
||||||
@ -280,8 +270,8 @@ const pointTypes = [
|
|||||||
</el-card>
|
</el-card>
|
||||||
|
|
||||||
<!-- 新增点位弹窗 -->
|
<!-- 新增点位弹窗 -->
|
||||||
<el-dialog
|
<el-dialog
|
||||||
v-model="dialogVisible"
|
v-model="dialogVisible"
|
||||||
:title="isEdit ? '编辑巡护点位' : '新增巡护点位'"
|
:title="isEdit ? '编辑巡护点位' : '新增巡护点位'"
|
||||||
width="500px"
|
width="500px"
|
||||||
@close="handleDialogClose"
|
@close="handleDialogClose"
|
||||||
@ -328,7 +318,7 @@ const pointTypes = [
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import "../../../styles/variables.scss";
|
@use "./styles/variables" as v;
|
||||||
|
|
||||||
.point-container {
|
.point-container {
|
||||||
.el-card {
|
.el-card {
|
||||||
@ -418,4 +408,4 @@ const pointTypes = [
|
|||||||
width: 100%;
|
width: 100%;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@ -1,20 +1,9 @@
|
|||||||
<script setup lang="ts">
|
<script setup>
|
||||||
import { ref, computed, reactive } from "vue";
|
import { ref, computed, reactive } from "vue";
|
||||||
import { ElMessage } from "element-plus";
|
import { ElMessage } from "element-plus";
|
||||||
import { Search } from "@element-plus/icons-vue";
|
import { Search } from "@element-plus/icons-vue";
|
||||||
|
|
||||||
interface RecordData {
|
const tableData = ref([
|
||||||
id: number;
|
|
||||||
taskTitle: string;
|
|
||||||
patroller: string;
|
|
||||||
route: string;
|
|
||||||
findings: string;
|
|
||||||
images: string[];
|
|
||||||
status: string;
|
|
||||||
completedTime: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const tableData = ref<RecordData[]>([
|
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
taskTitle: "A区日常巡查",
|
taskTitle: "A区日常巡查",
|
||||||
@ -38,9 +27,9 @@ const tableData = ref<RecordData[]>([
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
const dialogVisible = ref(false);
|
const dialogVisible = ref(false);
|
||||||
const currentRecord = ref<RecordData | null>(null);
|
const currentRecord = ref(null);
|
||||||
|
|
||||||
const handleView = (row: RecordData) => {
|
const handleView = (row) => {
|
||||||
currentRecord.value = row;
|
currentRecord.value = row;
|
||||||
dialogVisible.value = true;
|
dialogVisible.value = true;
|
||||||
};
|
};
|
||||||
@ -61,7 +50,7 @@ const filteredTableData = computed(() => {
|
|||||||
|
|
||||||
const exportDialogVisible = ref(false);
|
const exportDialogVisible = ref(false);
|
||||||
const exportForm = reactive({
|
const exportForm = reactive({
|
||||||
timeRange: [] as string[],
|
timeRange: [],
|
||||||
format: "excel",
|
format: "excel",
|
||||||
types: ["basic", "findings", "images"],
|
types: ["basic", "findings", "images"],
|
||||||
});
|
});
|
||||||
@ -85,12 +74,12 @@ const paginatedData = computed(() => {
|
|||||||
return filteredTableData.value.slice(start, end);
|
return filteredTableData.value.slice(start, end);
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleSizeChange = (val: number) => {
|
const handleSizeChange = (val) => {
|
||||||
pageSize.value = val;
|
pageSize.value = val;
|
||||||
currentPage.value = 1;
|
currentPage.value = 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCurrentChange = (val: number) => {
|
const handleCurrentChange = (val) => {
|
||||||
currentPage.value = val;
|
currentPage.value = val;
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@ -242,7 +231,7 @@ const handleCurrentChange = (val: number) => {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import "../../../styles/variables.scss";
|
@use "./styles/variables" as v;
|
||||||
|
|
||||||
.record-container {
|
.record-container {
|
||||||
.el-card {
|
.el-card {
|
||||||
|
|||||||
@ -1,19 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup>
|
||||||
import { ref, reactive, computed } from "vue";
|
import { ref, reactive, computed } from "vue";
|
||||||
import { ElMessage, ElMessageBox } from "element-plus";
|
import { ElMessage, ElMessageBox } from "element-plus";
|
||||||
|
|
||||||
interface TaskData {
|
const tableData = ref([
|
||||||
id: number;
|
|
||||||
title: string;
|
|
||||||
area: string;
|
|
||||||
assignee: string;
|
|
||||||
status: string;
|
|
||||||
priority: string;
|
|
||||||
startTime: string;
|
|
||||||
endTime: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const tableData = ref<TaskData[]>([
|
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
title: "A区日常巡查",
|
title: "A区日常巡查",
|
||||||
@ -36,8 +25,8 @@ const tableData = ref<TaskData[]>([
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const getStatusType = (status: string) => {
|
const getStatusType = (status) => {
|
||||||
const map: Record<string, string> = {
|
const map = {
|
||||||
进行中: "success",
|
进行中: "success",
|
||||||
待开始: "info",
|
待开始: "info",
|
||||||
已完成: "primary",
|
已完成: "primary",
|
||||||
@ -46,8 +35,8 @@ const getStatusType = (status: string) => {
|
|||||||
return map[status] || "info";
|
return map[status] || "info";
|
||||||
};
|
};
|
||||||
|
|
||||||
const getPriorityType = (priority: string) => {
|
const getPriorityType = (priority) => {
|
||||||
const map: Record<string, string> = {
|
const map = {
|
||||||
高: "danger",
|
高: "danger",
|
||||||
中: "warning",
|
中: "warning",
|
||||||
低: "info",
|
低: "info",
|
||||||
@ -55,7 +44,7 @@ const getPriorityType = (priority: string) => {
|
|||||||
return map[priority] || "info";
|
return map[priority] || "info";
|
||||||
};
|
};
|
||||||
|
|
||||||
// 添加新建任务相关
|
// 新建任务相关
|
||||||
const newTaskDialogVisible = ref(false);
|
const newTaskDialogVisible = ref(false);
|
||||||
const newTaskForm = reactive({
|
const newTaskForm = reactive({
|
||||||
title: "",
|
title: "",
|
||||||
@ -67,24 +56,23 @@ const newTaskForm = reactive({
|
|||||||
description: "",
|
description: "",
|
||||||
});
|
});
|
||||||
|
|
||||||
// 添加任务详情相关
|
// 任务详情相关
|
||||||
const detailDialogVisible = ref(false);
|
const detailDialogVisible = ref(false);
|
||||||
const currentTask = ref<TaskData | null>(null);
|
const currentTask = ref(null);
|
||||||
|
|
||||||
// 添加任务状态更新相关
|
// 任务状态更新相关
|
||||||
const handleView = (row: TaskData) => {
|
const handleView = (row) => {
|
||||||
currentTask.value = row;
|
currentTask.value = row;
|
||||||
detailDialogVisible.value = true;
|
detailDialogVisible.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleComplete = (row: TaskData) => {
|
const handleComplete = (row) => {
|
||||||
ElMessageBox.confirm("确认完成该巡护任务?", "提示", {
|
ElMessageBox.confirm("确认完成该巡护任务?", "提示", {
|
||||||
confirmButtonText: "确定",
|
confirmButtonText: "确定",
|
||||||
cancelButtonText: "取消",
|
cancelButtonText: "取消",
|
||||||
type: "warning",
|
type: "warning",
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
// 这里添加完成任务的逻辑
|
|
||||||
ElMessage.success("任务已完成");
|
ElMessage.success("任务已完成");
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
@ -92,14 +80,13 @@ const handleComplete = (row: TaskData) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCancel = (row: TaskData) => {
|
const handleCancel = (row) => {
|
||||||
ElMessageBox.confirm("确认取消该巡护任务?", "提示", {
|
ElMessageBox.confirm("确认取消该巡护任务?", "提示", {
|
||||||
confirmButtonText: "确定",
|
confirmButtonText: "确定",
|
||||||
cancelButtonText: "取消",
|
cancelButtonText: "取消",
|
||||||
type: "warning",
|
type: "warning",
|
||||||
})
|
})
|
||||||
.then(() => {
|
.then(() => {
|
||||||
// 这里添加取消任务的逻辑
|
|
||||||
ElMessage.success("任务已取消");
|
ElMessage.success("任务已取消");
|
||||||
})
|
})
|
||||||
.catch(() => {
|
.catch(() => {
|
||||||
@ -107,14 +94,8 @@ const handleCancel = (row: TaskData) => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
// 添加任务时间线数据
|
// 任务时间线数据
|
||||||
interface TimelineItem {
|
const taskTimeline = ref([
|
||||||
type: 'primary' | 'success' | 'warning' | 'danger' | 'info';
|
|
||||||
timestamp: string;
|
|
||||||
content: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const taskTimeline = ref<TimelineItem[]>([
|
|
||||||
{
|
{
|
||||||
type: 'primary',
|
type: 'primary',
|
||||||
timestamp: '2024-03-20 09:00',
|
timestamp: '2024-03-20 09:00',
|
||||||
@ -137,8 +118,8 @@ const taskTimeline = ref<TimelineItem[]>([
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// 添加表单验证规则
|
// 表单验证规则
|
||||||
const validateTime = (rule: any, value: string, callback: Function) => {
|
const validateTime = (rule, value, callback) => {
|
||||||
if (!value) {
|
if (!value) {
|
||||||
callback(new Error('请选择时间'));
|
callback(new Error('请选择时间'));
|
||||||
return;
|
return;
|
||||||
@ -174,7 +155,7 @@ const handleNewTask = () => {
|
|||||||
|
|
||||||
const handleNewTaskSubmit = async () => {
|
const handleNewTaskSubmit = async () => {
|
||||||
if (!newTaskFormRef.value) return;
|
if (!newTaskFormRef.value) return;
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await newTaskFormRef.value.validate();
|
await newTaskFormRef.value.validate();
|
||||||
console.log("提交新任务:", newTaskForm);
|
console.log("提交新任务:", newTaskForm);
|
||||||
@ -199,18 +180,18 @@ const resetForm = () => {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// 添加关闭弹窗时重置表单
|
// 关闭弹窗时重置表单
|
||||||
const handleDialogClose = () => {
|
const handleDialogClose = () => {
|
||||||
resetForm();
|
resetForm();
|
||||||
};
|
};
|
||||||
|
|
||||||
// 添加搜索和筛选相关
|
// 搜索和筛选相关
|
||||||
const searchText = ref("");
|
const searchText = ref("");
|
||||||
const statusFilter = ref<string[]>([]);
|
const statusFilter = ref([]);
|
||||||
const priorityFilter = ref<string[]>([]);
|
const priorityFilter = ref([]);
|
||||||
const dateRange = ref<[string, string]>(['', '']);
|
const dateRange = ref(['', '']);
|
||||||
|
|
||||||
// 添加统计数据
|
// 统计数据
|
||||||
const statistics = computed(() => {
|
const statistics = computed(() => {
|
||||||
const total = tableData.value.length;
|
const total = tableData.value.length;
|
||||||
const statusCount = {
|
const statusCount = {
|
||||||
@ -226,8 +207,8 @@ const statistics = computed(() => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
tableData.value.forEach(task => {
|
tableData.value.forEach(task => {
|
||||||
statusCount[task.status as keyof typeof statusCount]++;
|
statusCount[task.status]++;
|
||||||
priorityCount[task.priority as keyof typeof priorityCount]++;
|
priorityCount[task.priority]++;
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -242,17 +223,17 @@ const filteredTableData = computed(() => {
|
|||||||
return tableData.value.filter(task => {
|
return tableData.value.filter(task => {
|
||||||
// 文本搜索
|
// 文本搜索
|
||||||
const searchLower = searchText.value.toLowerCase();
|
const searchLower = searchText.value.toLowerCase();
|
||||||
const textMatch = !searchText.value ||
|
const textMatch = !searchText.value ||
|
||||||
task.title.toLowerCase().includes(searchLower) ||
|
task.title.toLowerCase().includes(searchLower) ||
|
||||||
task.area.toLowerCase().includes(searchLower) ||
|
task.area.toLowerCase().includes(searchLower) ||
|
||||||
task.assignee.toLowerCase().includes(searchLower);
|
task.assignee.toLowerCase().includes(searchLower);
|
||||||
|
|
||||||
// 状态筛选
|
// 状态筛选
|
||||||
const statusMatch = statusFilter.value.length === 0 ||
|
const statusMatch = statusFilter.value.length === 0 ||
|
||||||
statusFilter.value.includes(task.status);
|
statusFilter.value.includes(task.status);
|
||||||
|
|
||||||
// 优先级筛选
|
// 优先级筛选
|
||||||
const priorityMatch = priorityFilter.value.length === 0 ||
|
const priorityMatch = priorityFilter.value.length === 0 ||
|
||||||
priorityFilter.value.includes(task.priority);
|
priorityFilter.value.includes(task.priority);
|
||||||
|
|
||||||
// 日期范围筛选
|
// 日期范围筛选
|
||||||
@ -489,9 +470,9 @@ const resetFilters = () => {
|
|||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
<!-- 修改新建任务弹窗,添加表单验证 -->
|
<!-- 修改新建任务弹窗,添加表单验证 -->
|
||||||
<el-dialog
|
<el-dialog
|
||||||
v-model="newTaskDialogVisible"
|
v-model="newTaskDialogVisible"
|
||||||
title="新建巡护任务"
|
title="新建巡护任务"
|
||||||
width="600px"
|
width="600px"
|
||||||
@close="handleDialogClose"
|
@close="handleDialogClose"
|
||||||
>
|
>
|
||||||
@ -546,7 +527,7 @@ const resetFilters = () => {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import "../../../styles/variables.scss";
|
@use "./styles/variables" as v;
|
||||||
|
|
||||||
.task-container {
|
.task-container {
|
||||||
.el-card {
|
.el-card {
|
||||||
@ -669,7 +650,7 @@ const resetFilters = () => {
|
|||||||
.el-radio-group {
|
.el-radio-group {
|
||||||
.el-radio {
|
.el-radio {
|
||||||
margin-right: 20px;
|
margin-right: 20px;
|
||||||
|
|
||||||
&:last-child {
|
&:last-child {
|
||||||
margin-right: 0;
|
margin-right: 0;
|
||||||
}
|
}
|
||||||
@ -692,7 +673,7 @@ const resetFilters = () => {
|
|||||||
.statistics-content {
|
.statistics-content {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: baseline;
|
align-items: baseline;
|
||||||
|
|
||||||
.number {
|
.number {
|
||||||
font-size: 28px;
|
font-size: 28px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
<script setup lang="ts">
|
<script setup>
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
import { markRaw } from "vue";
|
import { markRaw } from "vue";
|
||||||
import {
|
import {
|
||||||
|
|||||||
@ -1,43 +1,11 @@
|
|||||||
<script setup lang="ts">
|
<script setup>
|
||||||
import { ref, onMounted, onUnmounted, computed } from "vue";
|
import { ref, onMounted, onUnmounted, computed } from "vue";
|
||||||
import * as echarts from "echarts";
|
import * as echarts from "echarts";
|
||||||
import { ElMessageBox, ElMessage } from "element-plus";
|
import { ElMessageBox, ElMessage } from "element-plus";
|
||||||
import { Plus } from '@element-plus/icons-vue';
|
import { Plus } from "@element-plus/icons-vue";
|
||||||
|
|
||||||
interface AnalysisReport {
|
|
||||||
id: number;
|
|
||||||
title: string;
|
|
||||||
type: "species" | "environment"; // 分析类型:物种/环境
|
|
||||||
timeRange: {
|
|
||||||
// 分析时间范围
|
|
||||||
start: string;
|
|
||||||
end: string;
|
|
||||||
};
|
|
||||||
dataSource: {
|
|
||||||
// 数据来源
|
|
||||||
type: string;
|
|
||||||
points: string[]; // 监测点位
|
|
||||||
}[];
|
|
||||||
analysis: {
|
|
||||||
summary: string; // 分析总结
|
|
||||||
trends: {
|
|
||||||
// 趋势分析
|
|
||||||
indicator: string; // 指标
|
|
||||||
trend: string; // 变化趋势
|
|
||||||
data: any[]; // 数据
|
|
||||||
}[];
|
|
||||||
abnormal: {
|
|
||||||
// 异常分析
|
|
||||||
type: string;
|
|
||||||
description: string;
|
|
||||||
level: string;
|
|
||||||
}[];
|
|
||||||
};
|
|
||||||
recommendations: string[]; // 建议措施
|
|
||||||
}
|
|
||||||
|
|
||||||
// 示例数据
|
// 示例数据
|
||||||
const tableData = ref<AnalysisReport[]>([
|
const tableData = ref([
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
title: "2024年第一季度水质监测分析报告",
|
title: "2024年第一季度水质监测分析报告",
|
||||||
@ -87,15 +55,15 @@ const filterForm = ref({
|
|||||||
|
|
||||||
// 添加详情查看功能
|
// 添加详情查看功能
|
||||||
const detailVisible = ref(false);
|
const detailVisible = ref(false);
|
||||||
const currentReport = ref<AnalysisReport | null>(null);
|
const currentReport = ref(null);
|
||||||
|
|
||||||
const handleView = (row: AnalysisReport) => {
|
const handleView = (row) => {
|
||||||
currentReport.value = row;
|
currentReport.value = row;
|
||||||
detailVisible.value = true;
|
detailVisible.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 添加导出功能
|
// 添加导出功能
|
||||||
const handleExport = (row: AnalysisReport) => {
|
const handleExport = (row) => {
|
||||||
ElMessageBox.confirm("确认导出该分析报告?", "提示", {
|
ElMessageBox.confirm("确认导出该分析报告?", "提示", {
|
||||||
confirmButtonText: "确定",
|
confirmButtonText: "确定",
|
||||||
cancelButtonText: "取消",
|
cancelButtonText: "取消",
|
||||||
@ -110,7 +78,7 @@ const handleExport = (row: AnalysisReport) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 添加图表resize监听
|
// 添加图表resize监听
|
||||||
let myChart: echarts.ECharts | null = null;
|
let myChart = null;
|
||||||
|
|
||||||
const initChart = () => {
|
const initChart = () => {
|
||||||
const chartDom = document.getElementById("trendChart");
|
const chartDom = document.getElementById("trendChart");
|
||||||
@ -118,39 +86,125 @@ const initChart = () => {
|
|||||||
|
|
||||||
myChart = echarts.init(chartDom);
|
myChart = echarts.init(chartDom);
|
||||||
const option = {
|
const option = {
|
||||||
|
backgroundColor: 'transparent',
|
||||||
title: {
|
title: {
|
||||||
text: "监测指标趋势分析",
|
text: '近7天报告提交趋势',
|
||||||
|
textStyle: {
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: 500,
|
||||||
|
color: '#303133'
|
||||||
|
},
|
||||||
|
padding: [20, 0]
|
||||||
},
|
},
|
||||||
tooltip: {
|
tooltip: {
|
||||||
trigger: "axis",
|
trigger: 'axis',
|
||||||
|
backgroundColor: 'rgba(255, 255, 255, 0.95)',
|
||||||
|
borderColor: '#eee',
|
||||||
|
padding: [15, 20],
|
||||||
},
|
},
|
||||||
legend: {
|
legend: {
|
||||||
data: ["pH值", "溶解氧", "水温"],
|
data: ['巡护报告', '监测报告'],
|
||||||
|
right: 0,
|
||||||
|
top: 0,
|
||||||
|
itemWidth: 10,
|
||||||
|
itemHeight: 10,
|
||||||
|
textStyle: {
|
||||||
|
color: '#666',
|
||||||
|
fontSize: 12
|
||||||
|
}
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
left: '3%',
|
||||||
|
right: '4%',
|
||||||
|
top: '15%',
|
||||||
|
bottom: '3%',
|
||||||
|
containLabel: true
|
||||||
},
|
},
|
||||||
xAxis: {
|
xAxis: {
|
||||||
type: "category",
|
type: 'category',
|
||||||
data: ["1月", "2月", "3月", "4月"],
|
boundaryGap: false,
|
||||||
|
data: ['3-14', '3-15', '3-16', '3-17', '3-18', '3-19', '3-20'],
|
||||||
|
axisLine: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
axisTick: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
splitLine: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
color: '#999',
|
||||||
|
fontSize: 12
|
||||||
|
}
|
||||||
},
|
},
|
||||||
yAxis: {
|
yAxis: {
|
||||||
type: "value",
|
type: 'value',
|
||||||
|
min: 0,
|
||||||
|
max: 8,
|
||||||
|
interval: 2,
|
||||||
|
axisLine: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
axisTick: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
splitLine: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
color: '#999',
|
||||||
|
fontSize: 12
|
||||||
|
}
|
||||||
},
|
},
|
||||||
series: [
|
series: [
|
||||||
{
|
{
|
||||||
name: "pH值",
|
name: '巡护报告',
|
||||||
type: "line",
|
type: 'line',
|
||||||
data: [7.1, 7.2, 7.0, 7.3],
|
data: [5, 6, 4, 8, 7, 6, 5],
|
||||||
|
smooth: true,
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 6,
|
||||||
|
lineStyle: {
|
||||||
|
width: 2,
|
||||||
|
color: '#36A2EB'
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
color: '#36A2EB',
|
||||||
|
borderWidth: 2,
|
||||||
|
borderColor: '#fff'
|
||||||
|
},
|
||||||
|
areaStyle: {
|
||||||
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||||
|
{ offset: 0, color: 'rgba(54, 162, 235, 0.15)' },
|
||||||
|
{ offset: 1, color: 'rgba(54, 162, 235, 0.02)' }
|
||||||
|
])
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "溶解氧",
|
name: '监测报告',
|
||||||
type: "line",
|
type: 'line',
|
||||||
data: [6.5, 6.3, 6.0, 5.8],
|
data: [3, 4, 3, 5, 4, 5, 4],
|
||||||
},
|
smooth: true,
|
||||||
{
|
symbol: 'circle',
|
||||||
name: "水温",
|
symbolSize: 6,
|
||||||
type: "line",
|
lineStyle: {
|
||||||
data: [15, 16, 18, 21],
|
width: 2,
|
||||||
},
|
color: '#4BC0C0'
|
||||||
],
|
},
|
||||||
|
itemStyle: {
|
||||||
|
color: '#4BC0C0',
|
||||||
|
borderWidth: 2,
|
||||||
|
borderColor: '#fff'
|
||||||
|
},
|
||||||
|
areaStyle: {
|
||||||
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||||
|
{ offset: 0, color: 'rgba(75, 192, 192, 0.15)' },
|
||||||
|
{ offset: 1, color: 'rgba(75, 192, 192, 0.02)' }
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
};
|
};
|
||||||
|
|
||||||
myChart.setOption(option);
|
myChart.setOption(option);
|
||||||
@ -180,12 +234,12 @@ const paginatedData = computed(() => {
|
|||||||
return tableData.value.slice(start, end);
|
return tableData.value.slice(start, end);
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleSizeChange = (val: number) => {
|
const handleSizeChange = (val) => {
|
||||||
pageSize.value = val;
|
pageSize.value = val;
|
||||||
currentPage.value = 1;
|
currentPage.value = 1;
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleCurrentChange = (val: number) => {
|
const handleCurrentChange = (val) => {
|
||||||
currentPage.value = val;
|
currentPage.value = val;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -210,7 +264,7 @@ const handleSubmit = () => {
|
|||||||
|
|
||||||
// 刷新数据
|
// 刷新数据
|
||||||
const handleRefresh = () => {
|
const handleRefresh = () => {
|
||||||
ElMessage.success('数据已更新');
|
ElMessage.success("数据已更新");
|
||||||
// TODO: 实际刷新数据的逻辑
|
// TODO: 实际刷新数据的逻辑
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@ -218,8 +272,8 @@ const handleRefresh = () => {
|
|||||||
<template>
|
<template>
|
||||||
<div class="analysis-report">
|
<div class="analysis-report">
|
||||||
<!-- 趋势图表 -->
|
<!-- 趋势图表 -->
|
||||||
<el-card>
|
<el-card class="chart-card">
|
||||||
<div id="trendChart" style="height: 400px"></div>
|
<div id="trendChart"></div>
|
||||||
</el-card>
|
</el-card>
|
||||||
|
|
||||||
<!-- 报告列表 -->
|
<!-- 报告列表 -->
|
||||||
@ -259,7 +313,7 @@ const handleRefresh = () => {
|
|||||||
<el-table-column prop="type" label="类型" width="120">
|
<el-table-column prop="type" label="类型" width="120">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-tag :type="row.type === 'species' ? 'success' : 'primary'">
|
<el-tag :type="row.type === 'species' ? 'success' : 'primary'">
|
||||||
{{ row.type === 'species' ? '物种分析' : '环境分析' }}
|
{{ row.type === "species" ? "物种分析" : "环境分析" }}
|
||||||
</el-tag>
|
</el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
@ -394,7 +448,7 @@ const handleRefresh = () => {
|
|||||||
<el-button @click="detailVisible = false">关闭</el-button>
|
<el-button @click="detailVisible = false">关闭</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
type="warning"
|
type="warning"
|
||||||
@click="handleExport(currentReport!)"
|
@click="handleExport(currentReport)"
|
||||||
:disabled="!currentReport"
|
:disabled="!currentReport"
|
||||||
>
|
>
|
||||||
导出
|
导出
|
||||||
@ -406,7 +460,7 @@ const handleRefresh = () => {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import "../../../styles/variables.scss";
|
@use "./styles/variables" as v;
|
||||||
|
|
||||||
.analysis-report {
|
.analysis-report {
|
||||||
.card-header {
|
.card-header {
|
||||||
@ -458,22 +512,34 @@ const handleRefresh = () => {
|
|||||||
padding: 20px;
|
padding: 20px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
transform: translateY(-2px);
|
transform: translateY(-2px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.stat-value {
|
.stat-value {
|
||||||
font-size: 28px;
|
font-size: 28px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: $primary-color;
|
color: $primary-color;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stat-label {
|
.stat-label {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: $text-secondary;
|
color: $text-secondary;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.chart-card {
|
||||||
|
margin-bottom: 24px;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #fff;
|
||||||
|
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.04);
|
||||||
|
|
||||||
|
#trendChart {
|
||||||
|
height: 400px;
|
||||||
|
padding: 20px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@ -1,29 +1,11 @@
|
|||||||
<script setup lang="ts">
|
<script setup>
|
||||||
import { ref, onMounted } from 'vue';
|
import { ref, onMounted } from 'vue';
|
||||||
import { ElMessage, ElMessageBox, ElLoading } from 'element-plus';
|
import { ElMessage, ElMessageBox, ElLoading } from 'element-plus';
|
||||||
import { Plus, Document, Download, TrendCharts, Connection } from '@element-plus/icons-vue';
|
import { Plus, Document, Download, TrendCharts, Connection } from '@element-plus/icons-vue';
|
||||||
import * as echarts from 'echarts';
|
import * as echarts from 'echarts';
|
||||||
|
|
||||||
interface DailyReport {
|
|
||||||
id: number;
|
|
||||||
title: string;
|
|
||||||
reporter: string;
|
|
||||||
department: string;
|
|
||||||
type: string;
|
|
||||||
content: string;
|
|
||||||
summary: string;
|
|
||||||
data: {
|
|
||||||
type: string;
|
|
||||||
value: string | number;
|
|
||||||
trend?: string;
|
|
||||||
}[];
|
|
||||||
attachments: string[];
|
|
||||||
status: string;
|
|
||||||
createTime: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 示例数据
|
// 示例数据
|
||||||
const tableData = ref<DailyReport[]>([
|
const tableData = ref([
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
title: "A区巡护日报",
|
title: "A区巡护日报",
|
||||||
@ -60,9 +42,9 @@ const formData = ref({
|
|||||||
|
|
||||||
// 查看详情
|
// 查看详情
|
||||||
const detailVisible = ref(false);
|
const detailVisible = ref(false);
|
||||||
const currentReport = ref<DailyReport | null>(null);
|
const currentReport = ref(null);
|
||||||
|
|
||||||
const handleView = (row: DailyReport) => {
|
const handleView = (row) => {
|
||||||
currentReport.value = row;
|
currentReport.value = row;
|
||||||
detailVisible.value = true;
|
detailVisible.value = true;
|
||||||
};
|
};
|
||||||
@ -78,35 +60,150 @@ const handleSubmit = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 趋势图表
|
// 趋势图表
|
||||||
let trendChart: echarts.ECharts | null = null;
|
let trendChart = null;
|
||||||
|
|
||||||
// 初始化趋势图表
|
// 初始化趋势图表
|
||||||
const initTrendChart = () => {
|
const initTrendChart = () => {
|
||||||
const chartDom = document.getElementById('trendChart');
|
const chartDom = document.getElementById('trendChart');
|
||||||
if (!chartDom) return;
|
if (!chartDom) return;
|
||||||
|
|
||||||
trendChart = echarts.init(chartDom);
|
const myChart = echarts.init(chartDom);
|
||||||
const option = {
|
const option = {
|
||||||
title: { text: '近7天报告提交趋势' },
|
backgroundColor: 'transparent',
|
||||||
tooltip: { trigger: 'axis' },
|
title: {
|
||||||
xAxis: { type: 'category', data: ['3-14', '3-15', '3-16', '3-17', '3-18', '3-19', '3-20'] },
|
text: '近7天报告提交趋势',
|
||||||
yAxis: { type: 'value' },
|
textStyle: {
|
||||||
|
fontSize: 16,
|
||||||
|
fontWeight: 500,
|
||||||
|
color: '#303133'
|
||||||
|
},
|
||||||
|
padding: [20, 0]
|
||||||
|
},
|
||||||
|
tooltip: {
|
||||||
|
trigger: 'axis',
|
||||||
|
backgroundColor: 'rgba(255, 255, 255, 0.95)',
|
||||||
|
borderColor: '#eee',
|
||||||
|
padding: [10, 15],
|
||||||
|
textStyle: {
|
||||||
|
color: '#666'
|
||||||
|
},
|
||||||
|
axisPointer: {
|
||||||
|
type: 'line',
|
||||||
|
lineStyle: {
|
||||||
|
color: '#409EFF',
|
||||||
|
opacity: 0.2
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
grid: {
|
||||||
|
left: '3%',
|
||||||
|
right: '4%',
|
||||||
|
bottom: '3%',
|
||||||
|
containLabel: true
|
||||||
|
},
|
||||||
|
xAxis: {
|
||||||
|
type: 'category',
|
||||||
|
data: ['3-14', '3-15', '3-16', '3-17', '3-18', '3-19', '3-20'],
|
||||||
|
boundaryGap: false,
|
||||||
|
axisLine: {
|
||||||
|
lineStyle: {
|
||||||
|
color: '#E5E7EB'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
axisTick: {
|
||||||
|
show: false
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
color: '#666',
|
||||||
|
padding: [8, 0]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
yAxis: {
|
||||||
|
type: 'value',
|
||||||
|
splitLine: {
|
||||||
|
lineStyle: {
|
||||||
|
color: '#E5E7EB',
|
||||||
|
type: 'dashed'
|
||||||
|
}
|
||||||
|
},
|
||||||
|
axisLabel: {
|
||||||
|
color: '#666'
|
||||||
|
}
|
||||||
|
},
|
||||||
series: [
|
series: [
|
||||||
{
|
{
|
||||||
name: '巡护报告',
|
name: '巡护报告',
|
||||||
type: 'line',
|
type: 'line',
|
||||||
data: [5, 6, 4, 8, 7, 6, 5],
|
data: [5, 6, 4, 8, 7, 6, 5],
|
||||||
smooth: true
|
smooth: true,
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 8,
|
||||||
|
lineStyle: {
|
||||||
|
width: 3,
|
||||||
|
color: '#409EFF'
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
color: '#409EFF',
|
||||||
|
borderWidth: 2,
|
||||||
|
borderColor: '#fff'
|
||||||
|
},
|
||||||
|
areaStyle: {
|
||||||
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||||
|
{
|
||||||
|
offset: 0,
|
||||||
|
color: 'rgba(64, 158, 255, 0.2)'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
offset: 1,
|
||||||
|
color: 'rgba(64, 158, 255, 0.02)'
|
||||||
|
}
|
||||||
|
])
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: '监测报告',
|
name: '监测报告',
|
||||||
type: 'line',
|
type: 'line',
|
||||||
data: [3, 4, 3, 5, 4, 5, 4],
|
data: [3, 4, 3, 5, 4, 5, 4],
|
||||||
smooth: true
|
smooth: true,
|
||||||
|
symbol: 'circle',
|
||||||
|
symbolSize: 8,
|
||||||
|
lineStyle: {
|
||||||
|
width: 3,
|
||||||
|
color: '#67C23A'
|
||||||
|
},
|
||||||
|
itemStyle: {
|
||||||
|
color: '#67C23A',
|
||||||
|
borderWidth: 2,
|
||||||
|
borderColor: '#fff'
|
||||||
|
},
|
||||||
|
areaStyle: {
|
||||||
|
color: new echarts.graphic.LinearGradient(0, 0, 0, 1, [
|
||||||
|
{
|
||||||
|
offset: 0,
|
||||||
|
color: 'rgba(103, 194, 58, 0.2)'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
offset: 1,
|
||||||
|
color: 'rgba(103, 194, 58, 0.02)'
|
||||||
|
}
|
||||||
|
])
|
||||||
|
}
|
||||||
}
|
}
|
||||||
]
|
],
|
||||||
|
legend: {
|
||||||
|
data: ['巡护报告', '监测报告'],
|
||||||
|
right: 20,
|
||||||
|
top: 20,
|
||||||
|
itemWidth: 12,
|
||||||
|
itemHeight: 12,
|
||||||
|
textStyle: {
|
||||||
|
color: '#666'
|
||||||
|
}
|
||||||
|
}
|
||||||
};
|
};
|
||||||
trendChart.setOption(option);
|
|
||||||
|
myChart.setOption(option);
|
||||||
|
window.addEventListener('resize', () => myChart.resize());
|
||||||
};
|
};
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
@ -120,11 +217,10 @@ const handleGenerate = () => {
|
|||||||
cancelButtonText: '取消',
|
cancelButtonText: '取消',
|
||||||
type: 'info'
|
type: 'info'
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
// 模拟自动汇总数据过程
|
|
||||||
const loadingInstance = ElLoading.service({
|
const loadingInstance = ElLoading.service({
|
||||||
text: '正在汇总数据,请稍候...'
|
text: '正在汇总数据,请稍候...'
|
||||||
});
|
});
|
||||||
|
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
loadingInstance.close();
|
loadingInstance.close();
|
||||||
ElMessage.success('报告生成成功,已自动整合今日巡护记录和监测数据');
|
ElMessage.success('报告生成成功,已自动整合今日巡护记录和监测数据');
|
||||||
@ -300,8 +396,8 @@ const showTrendAnalysis = ref(false);
|
|||||||
{{ currentReport.content }}
|
{{ currentReport.content }}
|
||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
<el-descriptions-item label="附件" :span="2">
|
<el-descriptions-item label="附件" :span="2">
|
||||||
<el-link
|
<el-link
|
||||||
v-for="file in currentReport.attachments"
|
v-for="file in currentReport.attachments"
|
||||||
:key="file"
|
:key="file"
|
||||||
type="primary"
|
type="primary"
|
||||||
style="margin-right: 16px"
|
style="margin-right: 16px"
|
||||||
@ -328,7 +424,7 @@ const showTrendAnalysis = ref(false);
|
|||||||
<template #footer>
|
<template #footer>
|
||||||
<span class="dialog-footer">
|
<span class="dialog-footer">
|
||||||
<el-button @click="detailVisible = false">关闭</el-button>
|
<el-button @click="detailVisible = false">关闭</el-button>
|
||||||
<el-button type="warning" @click="handleExport(currentReport!)">导出</el-button>
|
<el-button type="warning" @click="handleExport(currentReport)">导出</el-button>
|
||||||
</span>
|
</span>
|
||||||
</template>
|
</template>
|
||||||
</el-dialog>
|
</el-dialog>
|
||||||
@ -366,7 +462,7 @@ const showTrendAnalysis = ref(false);
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import "../../../styles/variables.scss";
|
@use "./styles/variables" as v;
|
||||||
|
|
||||||
.daily-report {
|
.daily-report {
|
||||||
.card-header {
|
.card-header {
|
||||||
@ -383,18 +479,18 @@ const showTrendAnalysis = ref(false);
|
|||||||
padding: 20px;
|
padding: 20px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
transition: all 0.3s;
|
transition: all 0.3s;
|
||||||
|
|
||||||
&:hover {
|
&:hover {
|
||||||
transform: translateY(-2px);
|
transform: translateY(-2px);
|
||||||
}
|
}
|
||||||
|
|
||||||
.stat-value {
|
.stat-value {
|
||||||
font-size: 28px;
|
font-size: 28px;
|
||||||
font-weight: 600;
|
font-weight: 600;
|
||||||
color: $primary-color;
|
color: $primary-color;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stat-label {
|
.stat-label {
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
color: $text-secondary;
|
color: $text-secondary;
|
||||||
@ -414,19 +510,19 @@ const showTrendAnalysis = ref(false);
|
|||||||
|
|
||||||
.stat-grid {
|
.stat-grid {
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
|
|
||||||
.stat-item {
|
.stat-item {
|
||||||
text-align: center;
|
text-align: center;
|
||||||
padding: 12px;
|
padding: 12px;
|
||||||
background: #f8f9fa;
|
background: #f8f9fa;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
|
||||||
.stat-item-label {
|
.stat-item-label {
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
color: $text-secondary;
|
color: $text-secondary;
|
||||||
margin-bottom: 8px;
|
margin-bottom: 8px;
|
||||||
}
|
}
|
||||||
|
|
||||||
.stat-item-value {
|
.stat-item-value {
|
||||||
font-size: 16px;
|
font-size: 16px;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
@ -435,11 +531,11 @@ const showTrendAnalysis = ref(false);
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
gap: 4px;
|
gap: 4px;
|
||||||
|
|
||||||
.up {
|
.up {
|
||||||
color: $success-color;
|
color: $success-color;
|
||||||
}
|
}
|
||||||
|
|
||||||
.down {
|
.down {
|
||||||
color: $danger-color;
|
color: $danger-color;
|
||||||
}
|
}
|
||||||
@ -451,7 +547,7 @@ const showTrendAnalysis = ref(false);
|
|||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
gap: 8px;
|
gap: 8px;
|
||||||
|
|
||||||
.el-icon {
|
.el-icon {
|
||||||
color: $text-secondary;
|
color: $text-secondary;
|
||||||
cursor: help;
|
cursor: help;
|
||||||
@ -463,15 +559,15 @@ const showTrendAnalysis = ref(false);
|
|||||||
padding: 12px;
|
padding: 12px;
|
||||||
background: #f8f9fa;
|
background: #f8f9fa;
|
||||||
border-radius: 4px;
|
border-radius: 4px;
|
||||||
|
|
||||||
p {
|
p {
|
||||||
margin: 8px 0;
|
margin: 8px 0;
|
||||||
color: $text-regular;
|
color: $text-regular;
|
||||||
|
|
||||||
&:first-child {
|
&:first-child {
|
||||||
margin-top: 0;
|
margin-top: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
&:last-child {
|
&:last-child {
|
||||||
margin-bottom: 0;
|
margin-bottom: 0;
|
||||||
}
|
}
|
||||||
@ -479,4 +575,17 @@ const showTrendAnalysis = ref(false);
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
|
||||||
|
.chart-card {
|
||||||
|
height: 400px;
|
||||||
|
background: #fff;
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.04);
|
||||||
|
margin-bottom: 24px;
|
||||||
|
transition: all 0.3s;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
box-shadow: 0 4px 20px rgba(0, 0, 0, 0.08);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@ -1,21 +1,10 @@
|
|||||||
<script setup lang="ts">
|
<script setup>
|
||||||
import { ref, computed, reactive } from "vue";
|
import { ref, computed, reactive } from "vue";
|
||||||
import { ElMessage, ElMessageBox } from "element-plus";
|
import { ElMessage, ElMessageBox } from "element-plus";
|
||||||
import * as echarts from "echarts";
|
import * as echarts from "echarts";
|
||||||
import { onMounted } from "vue";
|
import { onMounted } from "vue";
|
||||||
|
|
||||||
interface AnalysisReport {
|
const tableData = ref([
|
||||||
id: number;
|
|
||||||
title: string;
|
|
||||||
type: string;
|
|
||||||
period: string;
|
|
||||||
author: string;
|
|
||||||
summary: string;
|
|
||||||
charts: string[];
|
|
||||||
createTime: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const tableData = ref<AnalysisReport[]>([
|
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
title: "2024年第一季度水质分析报告",
|
title: "2024年第一季度水质分析报告",
|
||||||
@ -42,7 +31,7 @@ const tableData = ref<AnalysisReport[]>([
|
|||||||
const initChart = () => {
|
const initChart = () => {
|
||||||
const chartDom = document.getElementById("analysisChart");
|
const chartDom = document.getElementById("analysisChart");
|
||||||
if (!chartDom) return;
|
if (!chartDom) return;
|
||||||
|
|
||||||
const myChart = echarts.init(chartDom);
|
const myChart = echarts.init(chartDom);
|
||||||
const option = {
|
const option = {
|
||||||
title: {
|
title: {
|
||||||
@ -107,7 +96,7 @@ const handleSubmit = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 添加导出处理函数
|
// 添加导出处理函数
|
||||||
const handleExport = (row: AnalysisReport) => {
|
const handleExport = (row) => {
|
||||||
ElMessageBox.confirm(
|
ElMessageBox.confirm(
|
||||||
'确认导出该分析报告?',
|
'确认导出该分析报告?',
|
||||||
'提示',
|
'提示',
|
||||||
@ -130,9 +119,9 @@ const handleExport = (row: AnalysisReport) => {
|
|||||||
|
|
||||||
// 添加查看报告功能
|
// 添加查看报告功能
|
||||||
const detailVisible = ref(false);
|
const detailVisible = ref(false);
|
||||||
const currentReport = ref<AnalysisReport | null>(null);
|
const currentReport = ref(null);
|
||||||
|
|
||||||
const handleView = (row: AnalysisReport) => {
|
const handleView = (row) => {
|
||||||
currentReport.value = row;
|
currentReport.value = row;
|
||||||
detailVisible.value = true;
|
detailVisible.value = true;
|
||||||
};
|
};
|
||||||
@ -168,8 +157,8 @@ const handleView = (row: AnalysisReport) => {
|
|||||||
<el-button type="primary" size="small" @click="handleView(row)">
|
<el-button type="primary" size="small" @click="handleView(row)">
|
||||||
查看
|
查看
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
type="warning"
|
type="warning"
|
||||||
size="small"
|
size="small"
|
||||||
@click="handleExport(row)"
|
@click="handleExport(row)"
|
||||||
>
|
>
|
||||||
@ -271,9 +260,9 @@ const handleView = (row: AnalysisReport) => {
|
|||||||
<template #footer>
|
<template #footer>
|
||||||
<span class="dialog-footer">
|
<span class="dialog-footer">
|
||||||
<el-button @click="detailVisible = false">关闭</el-button>
|
<el-button @click="detailVisible = false">关闭</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
type="warning"
|
type="warning"
|
||||||
@click="handleExport(currentReport!)"
|
@click="handleExport(currentReport)"
|
||||||
:disabled="!currentReport"
|
:disabled="!currentReport"
|
||||||
>
|
>
|
||||||
导出
|
导出
|
||||||
@ -285,7 +274,7 @@ const handleView = (row: AnalysisReport) => {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import "../../../styles/variables.scss";
|
@use "./styles/variables" as v;
|
||||||
|
|
||||||
.analysis-report-container {
|
.analysis-report-container {
|
||||||
.el-card {
|
.el-card {
|
||||||
@ -342,4 +331,4 @@ const handleView = (row: AnalysisReport) => {
|
|||||||
color: $text-secondary;
|
color: $text-secondary;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@ -1,21 +1,9 @@
|
|||||||
<script setup lang="ts">
|
<script setup>
|
||||||
import { ref, computed, reactive } from "vue";
|
import { ref, computed, reactive } from "vue";
|
||||||
import { ElMessage, ElMessageBox } from "element-plus";
|
import { ElMessage, ElMessageBox } from "element-plus";
|
||||||
import { Search } from "@element-plus/icons-vue";
|
import { Search } from "@element-plus/icons-vue";
|
||||||
|
|
||||||
interface DailyReport {
|
const tableData = ref([
|
||||||
id: number;
|
|
||||||
title: string;
|
|
||||||
reporter: string;
|
|
||||||
department: string;
|
|
||||||
type: string;
|
|
||||||
content: string;
|
|
||||||
attachments: string[];
|
|
||||||
status: string;
|
|
||||||
createTime: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const tableData = ref<DailyReport[]>([
|
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
title: "A区巡护日报",
|
title: "A区巡护日报",
|
||||||
@ -42,22 +30,22 @@ const tableData = ref<DailyReport[]>([
|
|||||||
|
|
||||||
// 搜索和筛选
|
// 搜索和筛选
|
||||||
const searchText = ref("");
|
const searchText = ref("");
|
||||||
const typeFilter = ref<string[]>([]);
|
const typeFilter = ref([]);
|
||||||
const statusFilter = ref<string[]>([]);
|
const statusFilter = ref([]);
|
||||||
const dateRange = ref<[string, string]>(["", ""]);
|
const dateRange = ref(["", ""]);
|
||||||
|
|
||||||
// 过滤数据
|
// 过滤数据
|
||||||
const filteredData = computed(() => {
|
const filteredData = computed(() => {
|
||||||
return tableData.value.filter(report => {
|
return tableData.value.filter(report => {
|
||||||
const searchLower = searchText.value.toLowerCase();
|
const searchLower = searchText.value.toLowerCase();
|
||||||
const textMatch = !searchText.value ||
|
const textMatch = !searchText.value ||
|
||||||
report.title.toLowerCase().includes(searchLower) ||
|
report.title.toLowerCase().includes(searchLower) ||
|
||||||
report.reporter.toLowerCase().includes(searchLower);
|
report.reporter.toLowerCase().includes(searchLower);
|
||||||
|
|
||||||
const typeMatch = typeFilter.value.length === 0 ||
|
const typeMatch = typeFilter.value.length === 0 ||
|
||||||
typeFilter.value.includes(report.type);
|
typeFilter.value.includes(report.type);
|
||||||
|
|
||||||
const statusMatch = statusFilter.value.length === 0 ||
|
const statusMatch = statusFilter.value.length === 0 ||
|
||||||
statusFilter.value.includes(report.status);
|
statusFilter.value.includes(report.status);
|
||||||
|
|
||||||
return textMatch && typeMatch && statusMatch;
|
return textMatch && typeMatch && statusMatch;
|
||||||
@ -70,7 +58,7 @@ const formData = reactive({
|
|||||||
title: "",
|
title: "",
|
||||||
type: "",
|
type: "",
|
||||||
content: "",
|
content: "",
|
||||||
attachments: [] as string[],
|
attachments: [],
|
||||||
});
|
});
|
||||||
|
|
||||||
const handleCreate = () => {
|
const handleCreate = () => {
|
||||||
@ -85,15 +73,15 @@ const handleSubmit = () => {
|
|||||||
|
|
||||||
// 查看报告
|
// 查看报告
|
||||||
const detailVisible = ref(false);
|
const detailVisible = ref(false);
|
||||||
const currentReport = ref<DailyReport | null>(null);
|
const currentReport = ref(null);
|
||||||
|
|
||||||
const handleView = (row: DailyReport) => {
|
const handleView = (row) => {
|
||||||
currentReport.value = row;
|
currentReport.value = row;
|
||||||
detailVisible.value = true;
|
detailVisible.value = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 添加导出处理函数
|
// 添加导出处理函数
|
||||||
const handleExport = (row: DailyReport) => {
|
const handleExport = (row) => {
|
||||||
ElMessageBox.confirm(
|
ElMessageBox.confirm(
|
||||||
'确认导出该报告?',
|
'确认导出该报告?',
|
||||||
'提示',
|
'提示',
|
||||||
@ -181,15 +169,15 @@ const handleExport = (row: DailyReport) => {
|
|||||||
<el-button type="primary" size="small" @click="handleView(row)">
|
<el-button type="primary" size="small" @click="handleView(row)">
|
||||||
查看
|
查看
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
type="success"
|
type="success"
|
||||||
size="small"
|
size="small"
|
||||||
:disabled="row.status === '已审核'"
|
:disabled="row.status === '已审核'"
|
||||||
>
|
>
|
||||||
审核
|
审核
|
||||||
</el-button>
|
</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
type="warning"
|
type="warning"
|
||||||
size="small"
|
size="small"
|
||||||
@click="handleExport(row)"
|
@click="handleExport(row)"
|
||||||
>
|
>
|
||||||
@ -264,8 +252,8 @@ const handleExport = (row: DailyReport) => {
|
|||||||
</el-descriptions-item>
|
</el-descriptions-item>
|
||||||
<el-descriptions-item label="附件" :span="2">
|
<el-descriptions-item label="附件" :span="2">
|
||||||
<div class="attachment-list">
|
<div class="attachment-list">
|
||||||
<el-link
|
<el-link
|
||||||
v-for="file in currentReport.attachments"
|
v-for="file in currentReport.attachments"
|
||||||
:key="file"
|
:key="file"
|
||||||
type="primary"
|
type="primary"
|
||||||
style="margin-right: 16px"
|
style="margin-right: 16px"
|
||||||
@ -279,9 +267,9 @@ const handleExport = (row: DailyReport) => {
|
|||||||
<template #footer>
|
<template #footer>
|
||||||
<span class="dialog-footer">
|
<span class="dialog-footer">
|
||||||
<el-button @click="detailVisible = false">关闭</el-button>
|
<el-button @click="detailVisible = false">关闭</el-button>
|
||||||
<el-button
|
<el-button
|
||||||
type="warning"
|
type="warning"
|
||||||
@click="handleExport(currentReport!)"
|
@click="handleExport(currentReport)"
|
||||||
:disabled="!currentReport"
|
:disabled="!currentReport"
|
||||||
>
|
>
|
||||||
导出
|
导出
|
||||||
@ -293,7 +281,7 @@ const handleExport = (row: DailyReport) => {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import "../../../styles/variables.scss";
|
@use "./styles/variables" as v;
|
||||||
|
|
||||||
.daily-report-container {
|
.daily-report-container {
|
||||||
.el-card {
|
.el-card {
|
||||||
@ -341,4 +329,4 @@ const handleExport = (row: DailyReport) => {
|
|||||||
justify-content: right;
|
justify-content: right;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
@ -1,22 +1,11 @@
|
|||||||
<script setup lang="ts">
|
<script setup>
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
import { ElMessage, ElMessageBox } from "element-plus";
|
import { ElMessage, ElMessageBox } from "element-plus";
|
||||||
import type { UploadProps } from "element-plus";
|
|
||||||
import { markRaw } from 'vue';
|
import { markRaw } from 'vue';
|
||||||
import { Upload, Download, Refresh } from "@element-plus/icons-vue";
|
import { Upload, Download, Refresh } from "@element-plus/icons-vue";
|
||||||
|
|
||||||
// 备份记录
|
|
||||||
interface BackupRecord {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
size: string;
|
|
||||||
type: string;
|
|
||||||
status: string;
|
|
||||||
createTime: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 模拟备份记录数据
|
// 模拟备份记录数据
|
||||||
const backupRecords = ref<BackupRecord[]>([
|
const backupRecords = ref([
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
name: "系统完整备份_20240320",
|
name: "系统完整备份_20240320",
|
||||||
@ -36,7 +25,7 @@ const backupRecords = ref<BackupRecord[]>([
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
// 数据导入上传配置
|
// 数据导入上传配置
|
||||||
const uploadConfig: UploadProps = {
|
const uploadConfig = {
|
||||||
action: "/api/upload",
|
action: "/api/upload",
|
||||||
multiple: true,
|
multiple: true,
|
||||||
accept: ".xlsx,.csv",
|
accept: ".xlsx,.csv",
|
||||||
@ -75,7 +64,7 @@ const handleBackup = () => {
|
|||||||
type: "warning",
|
type: "warning",
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
ElMessage.success("备份任务已启动");
|
ElMessage.success("备份任务已启动");
|
||||||
const newBackup: BackupRecord = {
|
const newBackup = {
|
||||||
id: Date.now(),
|
id: Date.now(),
|
||||||
name: `系统备份_${new Date().toISOString().split("T")[0].replace(/-/g, "")}`,
|
name: `系统备份_${new Date().toISOString().split("T")[0].replace(/-/g, "")}`,
|
||||||
size: "处理中",
|
size: "处理中",
|
||||||
@ -97,7 +86,7 @@ const handleBackup = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 恢复备份
|
// 恢复备份
|
||||||
const handleRestore = (record: BackupRecord) => {
|
const handleRestore = (record) => {
|
||||||
if (record.status !== "成功") {
|
if (record.status !== "成功") {
|
||||||
ElMessage.warning("只能恢复成功的备份");
|
ElMessage.warning("只能恢复成功的备份");
|
||||||
return;
|
return;
|
||||||
@ -113,7 +102,7 @@ const handleRestore = (record: BackupRecord) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 删除备份
|
// 删除备份
|
||||||
const handleDelete = (record: BackupRecord) => {
|
const handleDelete = (record) => {
|
||||||
ElMessageBox.confirm("确认删除该备份?", "提示", {
|
ElMessageBox.confirm("确认删除该备份?", "提示", {
|
||||||
confirmButtonText: "确定",
|
confirmButtonText: "确定",
|
||||||
cancelButtonText: "取消",
|
cancelButtonText: "取消",
|
||||||
@ -248,7 +237,7 @@ const icons = {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import "../../../styles/variables.scss";
|
@use "./styles/variables" as v;
|
||||||
|
|
||||||
.data-container {
|
.data-container {
|
||||||
.mb-20 {
|
.mb-20 {
|
||||||
|
|||||||
@ -1,22 +1,11 @@
|
|||||||
<script setup lang="ts">
|
<script setup>
|
||||||
import { ref, onUnmounted } from "vue";
|
import { ref, onUnmounted } from "vue";
|
||||||
import { ElMessage, ElMessageBox } from "element-plus";
|
import { ElMessage, ElMessageBox } from "element-plus";
|
||||||
import * as echarts from "echarts";
|
import * as echarts from "echarts";
|
||||||
import { Plus } from "@element-plus/icons-vue";
|
import { Plus } from "@element-plus/icons-vue";
|
||||||
|
|
||||||
interface Device {
|
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
type: string;
|
|
||||||
location: string;
|
|
||||||
status: string;
|
|
||||||
ip: string;
|
|
||||||
lastHeartbeat: string;
|
|
||||||
lastMaintenance: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
// 模拟设备数据
|
// 模拟设备数据
|
||||||
const tableData = ref<Device[]>([
|
const tableData = ref([
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
name: "A区-水质监测器-01",
|
name: "A区-水质监测器-01",
|
||||||
@ -50,7 +39,7 @@ const deviceTypes = [
|
|||||||
// 新增/编辑设备
|
// 新增/编辑设备
|
||||||
const dialogVisible = ref(false);
|
const dialogVisible = ref(false);
|
||||||
const isEdit = ref(false);
|
const isEdit = ref(false);
|
||||||
const currentDevice = ref<Device | null>(null);
|
const currentDevice = ref(null);
|
||||||
|
|
||||||
const formData = ref({
|
const formData = ref({
|
||||||
name: "",
|
name: "",
|
||||||
@ -78,7 +67,7 @@ const handleAdd = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 打开编辑弹窗
|
// 打开编辑弹窗
|
||||||
const handleEdit = (row: Device) => {
|
const handleEdit = (row) => {
|
||||||
isEdit.value = true;
|
isEdit.value = true;
|
||||||
currentDevice.value = row;
|
currentDevice.value = row;
|
||||||
formData.value = {
|
formData.value = {
|
||||||
@ -99,7 +88,7 @@ const handleSubmit = async () => {
|
|||||||
if (isEdit.value && currentDevice.value) {
|
if (isEdit.value && currentDevice.value) {
|
||||||
// 编辑模式
|
// 编辑模式
|
||||||
const index = tableData.value.findIndex(
|
const index = tableData.value.findIndex(
|
||||||
(item) => item.id === currentDevice.value!.id
|
(item) => item.id === currentDevice.value.id
|
||||||
);
|
);
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
tableData.value[index] = {
|
tableData.value[index] = {
|
||||||
@ -111,7 +100,7 @@ const handleSubmit = async () => {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 新增模式
|
// 新增模式
|
||||||
const newDevice: Device = {
|
const newDevice = {
|
||||||
id: tableData.value.length + 1,
|
id: tableData.value.length + 1,
|
||||||
...formData.value,
|
...formData.value,
|
||||||
status: "在线",
|
status: "在线",
|
||||||
@ -141,7 +130,7 @@ const resetForm = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 删除设备
|
// 删除设备
|
||||||
const handleDelete = (row: Device) => {
|
const handleDelete = (row) => {
|
||||||
ElMessageBox.confirm("确认删除该设备?", "提示", {
|
ElMessageBox.confirm("确认删除该设备?", "提示", {
|
||||||
confirmButtonText: "确定",
|
confirmButtonText: "确定",
|
||||||
cancelButtonText: "取消",
|
cancelButtonText: "取消",
|
||||||
@ -158,12 +147,12 @@ const handleDelete = (row: Device) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 远程配置
|
// 远程配置
|
||||||
const handleConfig = (row: Device) => {
|
const handleConfig = (row) => {
|
||||||
ElMessage.success("正在连接设备...");
|
ElMessage.success("正在连接设备...");
|
||||||
};
|
};
|
||||||
|
|
||||||
// 设备维护
|
// 设备维护
|
||||||
const handleMaintenance = (row: Device) => {
|
const handleMaintenance = (row) => {
|
||||||
ElMessage.success("已记录维护时间");
|
ElMessage.success("已记录维护时间");
|
||||||
const index = tableData.value.findIndex((item) => item.id === row.id);
|
const index = tableData.value.findIndex((item) => item.id === row.id);
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
@ -206,8 +195,8 @@ const defaultProps = {
|
|||||||
|
|
||||||
// 监控相关
|
// 监控相关
|
||||||
const monitorVisible = ref(false);
|
const monitorVisible = ref(false);
|
||||||
const currentMonitorDevice = ref<Device | null>(null);
|
const currentMonitorDevice = ref(null);
|
||||||
const monitorChart = ref<echarts.ECharts | null>(null);
|
const monitorChart = ref(null);
|
||||||
|
|
||||||
// 模拟实时数据
|
// 模拟实时数据
|
||||||
const realTimeData = ref({
|
const realTimeData = ref({
|
||||||
@ -257,13 +246,13 @@ const handleAddGroup = () => {
|
|||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleNodeClick = (data: any) => {
|
const handleNodeClick = (data) => {
|
||||||
console.log("选中分组:", data);
|
console.log("选中分组:", data);
|
||||||
// 这里可以根据分组筛选设备列表
|
// 这里可以根据分组筛选设备列表
|
||||||
};
|
};
|
||||||
|
|
||||||
// 监控处理
|
// 监控处理
|
||||||
const handleMonitor = (device: Device) => {
|
const handleMonitor = (device) => {
|
||||||
currentMonitorDevice.value = device;
|
currentMonitorDevice.value = device;
|
||||||
monitorVisible.value = true;
|
monitorVisible.value = true;
|
||||||
initMonitorChart();
|
initMonitorChart();
|
||||||
@ -330,7 +319,7 @@ const initMonitorChart = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 模拟实时数据更新
|
// 模拟实时数据更新
|
||||||
let dataTimer: number;
|
let dataTimer;
|
||||||
const startRealTimeData = () => {
|
const startRealTimeData = () => {
|
||||||
clearInterval(dataTimer);
|
clearInterval(dataTimer);
|
||||||
dataTimer = setInterval(() => {
|
dataTimer = setInterval(() => {
|
||||||
@ -376,7 +365,7 @@ const startRealTimeData = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 检查告警
|
// 检查告警
|
||||||
const checkAlarms = (data: any) => {
|
const checkAlarms = (data) => {
|
||||||
const { thresholds } = alarmConfig.value;
|
const { thresholds } = alarmConfig.value;
|
||||||
|
|
||||||
if (
|
if (
|
||||||
@ -610,7 +599,7 @@ onUnmounted(() => {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import "../../../styles/variables.scss";
|
@use "./styles/variables" as v;
|
||||||
|
|
||||||
.device-container {
|
.device-container {
|
||||||
.group-card {
|
.group-card {
|
||||||
|
|||||||
@ -1,21 +1,10 @@
|
|||||||
<script setup lang="ts">
|
<script setup>
|
||||||
import { ref, onMounted } from "vue";
|
import { ref, onMounted } from "vue";
|
||||||
import { ElMessage } from "element-plus";
|
import { ElMessage } from "element-plus";
|
||||||
import { useSystemLogStore } from '../../../stores/systemLog';
|
import { useSystemLogStore } from '../../../stores/systemLog';
|
||||||
import { markRaw } from 'vue';
|
import { markRaw } from 'vue';
|
||||||
import { Download, Delete } from "@element-plus/icons-vue";
|
import { Download, Delete } from "@element-plus/icons-vue";
|
||||||
|
|
||||||
interface LogEntry {
|
|
||||||
id: number;
|
|
||||||
type: string;
|
|
||||||
user: string;
|
|
||||||
action: string;
|
|
||||||
ip: string;
|
|
||||||
status: string;
|
|
||||||
detail: string;
|
|
||||||
createTime: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const systemLogStore = useSystemLogStore();
|
const systemLogStore = useSystemLogStore();
|
||||||
|
|
||||||
// 日志类型选项
|
// 日志类型选项
|
||||||
@ -34,7 +23,7 @@ const statusOptions = [
|
|||||||
];
|
];
|
||||||
|
|
||||||
// 获取状态标签类型
|
// 获取状态标签类型
|
||||||
const getStatusType = (status: string) => {
|
const getStatusType = (status) => {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case "成功":
|
case "成功":
|
||||||
return "success";
|
return "success";
|
||||||
@ -184,7 +173,7 @@ const icons = {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import "../../../styles/variables.scss";
|
@use "./styles/variables" as v;
|
||||||
|
|
||||||
.logs-container {
|
.logs-container {
|
||||||
.card-header {
|
.card-header {
|
||||||
|
|||||||
@ -1,18 +1,8 @@
|
|||||||
<script setup lang="ts">
|
<script setup>
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
import { ElMessage, ElMessageBox } from "element-plus";
|
import { ElMessage, ElMessageBox } from "element-plus";
|
||||||
|
|
||||||
interface Permission {
|
const tableData = ref([
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
code: string;
|
|
||||||
description: string;
|
|
||||||
type: string;
|
|
||||||
status: boolean;
|
|
||||||
createTime: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const tableData = ref<Permission[]>([
|
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
name: "用户管理",
|
name: "用户管理",
|
||||||
@ -45,7 +35,7 @@ const tableData = ref<Permission[]>([
|
|||||||
// 新增/编辑权限
|
// 新增/编辑权限
|
||||||
const dialogVisible = ref(false);
|
const dialogVisible = ref(false);
|
||||||
const isEdit = ref(false);
|
const isEdit = ref(false);
|
||||||
const currentPermission = ref<Permission | null>(null);
|
const currentPermission = ref(null);
|
||||||
|
|
||||||
const formData = ref({
|
const formData = ref({
|
||||||
name: "",
|
name: "",
|
||||||
@ -73,7 +63,7 @@ const handleAdd = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 打开编辑弹窗
|
// 打开编辑弹窗
|
||||||
const handleEdit = (row: Permission) => {
|
const handleEdit = (row) => {
|
||||||
isEdit.value = true;
|
isEdit.value = true;
|
||||||
currentPermission.value = row;
|
currentPermission.value = row;
|
||||||
formData.value = {
|
formData.value = {
|
||||||
@ -95,7 +85,7 @@ const handleSubmit = async () => {
|
|||||||
if (isEdit.value && currentPermission.value) {
|
if (isEdit.value && currentPermission.value) {
|
||||||
// 编辑模式:更新表格中的数据
|
// 编辑模式:更新表格中的数据
|
||||||
const index = tableData.value.findIndex(
|
const index = tableData.value.findIndex(
|
||||||
(item) => item.id === currentPermission.value!.id
|
(item) => item.id === currentPermission.value.id
|
||||||
);
|
);
|
||||||
if (index !== -1) {
|
if (index !== -1) {
|
||||||
tableData.value[index] = {
|
tableData.value[index] = {
|
||||||
@ -106,7 +96,7 @@ const handleSubmit = async () => {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// 新增模式:添加到表格
|
// 新增模式:添加到表格
|
||||||
const newPermission: Permission = {
|
const newPermission = {
|
||||||
id: tableData.value.length + 1,
|
id: tableData.value.length + 1,
|
||||||
...formData.value,
|
...formData.value,
|
||||||
createTime: new Date().toLocaleString(),
|
createTime: new Date().toLocaleString(),
|
||||||
@ -142,7 +132,7 @@ const handleDialogClose = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
// 删除权限
|
// 删除权限
|
||||||
const handleDelete = (row: Permission) => {
|
const handleDelete = (row) => {
|
||||||
ElMessageBox.confirm("确认删除该权限?", "提示", {
|
ElMessageBox.confirm("确认删除该权限?", "提示", {
|
||||||
confirmButtonText: "确定",
|
confirmButtonText: "确定",
|
||||||
cancelButtonText: "取消",
|
cancelButtonText: "取消",
|
||||||
@ -246,7 +236,7 @@ const handleDelete = (row: Permission) => {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import "../../../styles/variables.scss";
|
@use "./styles/variables" as v;
|
||||||
|
|
||||||
.permission-container {
|
.permission-container {
|
||||||
.el-card {
|
.el-card {
|
||||||
|
|||||||
@ -1,15 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup>
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
|
|
||||||
interface RoleData {
|
const tableData = ref([
|
||||||
id: number;
|
|
||||||
name: string;
|
|
||||||
description: string;
|
|
||||||
permissions: string[];
|
|
||||||
createTime: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const tableData = ref<RoleData[]>([
|
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
name: "超级管理员",
|
name: "超级管理员",
|
||||||
@ -26,11 +18,11 @@ const tableData = ref<RoleData[]>([
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const handleEdit = (row: RoleData) => {
|
const handleEdit = (row) => {
|
||||||
console.log("编辑角色:", row);
|
console.log("编辑角色:", row);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete = (row: RoleData) => {
|
const handleDelete = (row) => {
|
||||||
console.log("删除角色:", row);
|
console.log("删除角色:", row);
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@ -73,7 +65,7 @@ const handleDelete = (row: RoleData) => {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import "../../../styles/variables.scss";
|
@use "../../../styles/variables.scss" as *;
|
||||||
|
|
||||||
.role-container {
|
.role-container {
|
||||||
.el-card {
|
.el-card {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
<script setup lang="ts">
|
<script setup>
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
|
|
||||||
// 系统参数配置
|
// 系统参数配置
|
||||||
@ -31,7 +31,7 @@ const alertConfig = ref({
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 保存配置
|
// 保存配置
|
||||||
const handleSave = (type: string) => {
|
const handleSave = (type) => {
|
||||||
// 这里模拟保存配置
|
// 这里模拟保存配置
|
||||||
console.log(
|
console.log(
|
||||||
`保存${type}配置:`,
|
`保存${type}配置:`,
|
||||||
@ -174,7 +174,7 @@ const handleSave = (type: string) => {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import "../../../styles/variables.scss";
|
@use "../../../styles/variables.scss" as *;
|
||||||
|
|
||||||
.settings-container {
|
.settings-container {
|
||||||
.mb-20 {
|
.mb-20 {
|
||||||
|
|||||||
@ -1,16 +1,7 @@
|
|||||||
<script setup lang="ts">
|
<script setup>
|
||||||
import { ref } from "vue";
|
import { ref } from "vue";
|
||||||
|
|
||||||
interface UserData {
|
const tableData = ref([
|
||||||
id: number;
|
|
||||||
username: string;
|
|
||||||
role: string;
|
|
||||||
email: string;
|
|
||||||
status: boolean;
|
|
||||||
createTime: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
const tableData = ref<UserData[]>([
|
|
||||||
{
|
{
|
||||||
id: 1,
|
id: 1,
|
||||||
username: "admin",
|
username: "admin",
|
||||||
@ -29,11 +20,11 @@ const tableData = ref<UserData[]>([
|
|||||||
},
|
},
|
||||||
]);
|
]);
|
||||||
|
|
||||||
const handleEdit = (row: UserData) => {
|
const handleEdit = (row) => {
|
||||||
console.log("编辑用户:", row);
|
console.log("编辑用户:", row);
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleDelete = (row: UserData) => {
|
const handleDelete = (row) => {
|
||||||
console.log("删除用户:", row);
|
console.log("删除用户:", row);
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
@ -77,7 +68,7 @@ const handleDelete = (row: UserData) => {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
@import "../../../styles/variables.scss";
|
@use "../../../styles/variables.scss" as *;
|
||||||
|
|
||||||
.user-container {
|
.user-container {
|
||||||
.el-card {
|
.el-card {
|
||||||
|
|||||||
1
src/vite-env.d.ts
vendored
1
src/vite-env.d.ts
vendored
@ -1 +0,0 @@
|
|||||||
/// <reference types="vite/client" />
|
|
||||||
@ -1,14 +0,0 @@
|
|||||||
{
|
|
||||||
"extends": "@vue/tsconfig/tsconfig.dom.json",
|
|
||||||
"compilerOptions": {
|
|
||||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.app.tsbuildinfo",
|
|
||||||
|
|
||||||
/* Linting */
|
|
||||||
"strict": true,
|
|
||||||
"noUnusedLocals": true,
|
|
||||||
"noUnusedParameters": true,
|
|
||||||
"noFallthroughCasesInSwitch": true,
|
|
||||||
"noUncheckedSideEffectImports": true
|
|
||||||
},
|
|
||||||
"include": ["src/**/*.ts", "src/**/*.tsx", "src/**/*.vue"]
|
|
||||||
}
|
|
||||||
@ -1,13 +0,0 @@
|
|||||||
{
|
|
||||||
"files": [],
|
|
||||||
"references": [
|
|
||||||
{ "path": "./tsconfig.app.json" },
|
|
||||||
{ "path": "./tsconfig.node.json" }
|
|
||||||
],
|
|
||||||
"compilerOptions": {
|
|
||||||
"baseUrl": ".",
|
|
||||||
"paths": {
|
|
||||||
"@/*": ["src/*"]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,24 +0,0 @@
|
|||||||
{
|
|
||||||
"compilerOptions": {
|
|
||||||
"tsBuildInfoFile": "./node_modules/.tmp/tsconfig.node.tsbuildinfo",
|
|
||||||
"target": "ES2022",
|
|
||||||
"lib": ["ES2023"],
|
|
||||||
"module": "ESNext",
|
|
||||||
"skipLibCheck": true,
|
|
||||||
|
|
||||||
/* Bundler mode */
|
|
||||||
"moduleResolution": "bundler",
|
|
||||||
"allowImportingTsExtensions": true,
|
|
||||||
"isolatedModules": true,
|
|
||||||
"moduleDetection": "force",
|
|
||||||
"noEmit": true,
|
|
||||||
|
|
||||||
/* Linting */
|
|
||||||
"strict": true,
|
|
||||||
"noUnusedLocals": true,
|
|
||||||
"noUnusedParameters": true,
|
|
||||||
"noFallthroughCasesInSwitch": true,
|
|
||||||
"noUncheckedSideEffectImports": true
|
|
||||||
},
|
|
||||||
"include": ["vite.config.ts"]
|
|
||||||
}
|
|
||||||
@ -9,5 +9,12 @@ export default defineConfig({
|
|||||||
alias: {
|
alias: {
|
||||||
'@': path.resolve(__dirname, 'src')
|
'@': path.resolve(__dirname, 'src')
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
build: {
|
||||||
|
rollupOptions: {
|
||||||
|
input: {
|
||||||
|
main: path.resolve(__dirname, 'index.html')
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
Loading…
x
Reference in New Issue
Block a user