diff --git a/frontend/index.html b/frontend/index.html
new file mode 100644
index 00000000..e1190ebb
--- /dev/null
+++ b/frontend/index.html
@@ -0,0 +1,12 @@
+
+
+
+
+
+ 智慧水务管理系统
+
+
+
+
+
+
diff --git a/frontend/package.json b/frontend/package.json
new file mode 100644
index 00000000..c9719fc9
--- /dev/null
+++ b/frontend/package.json
@@ -0,0 +1,31 @@
+{
+ "name": "water-management-frontend",
+ "version": "1.0.0",
+ "private": true,
+ "scripts": {
+ "dev": "vite",
+ "build": "vue-tsc && vite build",
+ "preview": "vite preview"
+ },
+ "dependencies": {
+ "vue": "^3.5.0",
+ "vue-router": "^4.4.0",
+ "pinia": "^2.2.0",
+ "axios": "^1.7.0",
+ "element-plus": "^2.8.0",
+ "@element-plus/icons-vue": "^2.3.0",
+ "echarts": "^5.5.0",
+ "leaflet": "^1.9.0",
+ "cesium": "^1.120.0"
+ },
+ "devDependencies": {
+ "typescript": "^5.5.0",
+ "vite": "^5.4.0",
+ "vue-tsc": "^2.0.0",
+ "@types/leaflet": "^1.9.0",
+ "sass": "^1.77.0",
+ "unplugin-auto-import": "^0.17.0",
+ "unplugin-vue-components": "^0.27.0",
+ "vite-plugin-compression": "^0.5.0"
+ }
+}
\ No newline at end of file
diff --git a/frontend/src/App.vue b/frontend/src/App.vue
new file mode 100644
index 00000000..98240aef
--- /dev/null
+++ b/frontend/src/App.vue
@@ -0,0 +1,3 @@
+
+
+
diff --git a/frontend/src/api/auth.ts b/frontend/src/api/auth.ts
new file mode 100644
index 00000000..49a798e2
--- /dev/null
+++ b/frontend/src/api/auth.ts
@@ -0,0 +1,13 @@
+import request from './request'
+
+export function login(username: string, password: string) {
+ return request.post('/base/auth/login', { username, password })
+}
+
+export function getUserInfo() {
+ return request.get('/base/auth/user-info')
+}
+
+export function logout() {
+ return request.post('/base/auth/logout')
+}
diff --git a/frontend/src/api/request.ts b/frontend/src/api/request.ts
new file mode 100644
index 00000000..45202689
--- /dev/null
+++ b/frontend/src/api/request.ts
@@ -0,0 +1,21 @@
+import axios from 'axios'
+import { ElMessage } from 'element-plus'
+
+const request = axios.create({ baseURL: '/api', timeout: 15000 })
+
+request.interceptors.request.use(config => {
+ const token = localStorage.getItem('token')
+ if (token) config.headers.Authorization = token
+ return config
+})
+
+request.interceptors.response.use(
+ res => {
+ const data = res.data
+ if (data.code !== 200) { ElMessage.error(data.message); return Promise.reject(data) }
+ return data
+ },
+ err => { ElMessage.error(err.message); return Promise.reject(err) }
+)
+
+export default request
diff --git a/frontend/src/assets/style.scss b/frontend/src/assets/style.scss
new file mode 100644
index 00000000..92d7694c
--- /dev/null
+++ b/frontend/src/assets/style.scss
@@ -0,0 +1,2 @@
+body { margin:0; padding:0; font-family:"Microsoft YaHei","PingFang SC",sans-serif }
+#app { height:100vh }
diff --git a/frontend/src/components/layout/MainLayout.vue b/frontend/src/components/layout/MainLayout.vue
new file mode 100644
index 00000000..194d05a2
--- /dev/null
+++ b/frontend/src/components/layout/MainLayout.vue
@@ -0,0 +1,53 @@
+
+
+
+
+
+ 精河县供水工程综合管理平台
+
+ {{ userStore.realName || '管理员' }}
+
+
+ 个人中心
+ 退出登录
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/main.ts b/frontend/src/main.ts
new file mode 100644
index 00000000..8753073e
--- /dev/null
+++ b/frontend/src/main.ts
@@ -0,0 +1,14 @@
+import { createApp } from 'vue'
+import ElementPlus from 'element-plus'
+import 'element-plus/dist/index.css'
+import App from './App.vue'
+import router from './router'
+import { createPinia } from 'pinia'
+import * as ElementPlusIconsVue from '@element-plus/icons-vue'
+import './assets/style.scss'
+
+const app = createApp(App)
+for (const [key, comp] of Object.entries(ElementPlusIconsVue)) {
+ app.component(key, comp)
+}
+app.use(ElementPlus).use(router).use(createPinia()).mount('#app')
diff --git a/frontend/src/router/index.ts b/frontend/src/router/index.ts
new file mode 100644
index 00000000..996badad
--- /dev/null
+++ b/frontend/src/router/index.ts
@@ -0,0 +1,27 @@
+import { createRouter, createWebHistory } from 'vue-router'
+
+const routes = [
+ { path: '/login', name: 'login', component: () => import('@/views/login/LoginView.vue') },
+ {
+ path: '/', component: () => import('@/components/layout/MainLayout.vue'),
+ redirect: '/dashboard',
+ children: [
+ { path: 'dashboard', name: 'dashboard', component: () => import('@/views/dashboard/DashboardView.vue') },
+ { path: 'system/user', name: 'user', component: () => import('@/views/system/user/UserList.vue') },
+ { path: 'system/role', name: 'role', component: () => import('@/views/system/role/RoleList.vue') },
+ { path: 'system/menu', name: 'menu', component: () => import('@/views/system/menu/MenuList.vue') },
+ { path: 'system/dept', name: 'dept', component: () => import('@/views/system/dept/DeptList.vue') },
+ ]
+ },
+ { path: '/:pathMatch(.*)*', redirect: '/dashboard' }
+]
+
+const router = createRouter({ history: createWebHistory(), routes })
+
+router.beforeEach((to, _from, next) => {
+ const token = localStorage.getItem('token')
+ if (to.path !== '/login' && !token) { next('/login') }
+ else { next() }
+})
+
+export default router
diff --git a/frontend/src/store/user.ts b/frontend/src/store/user.ts
new file mode 100644
index 00000000..2407cb7f
--- /dev/null
+++ b/frontend/src/store/user.ts
@@ -0,0 +1,10 @@
+import { defineStore } from 'pinia'
+import { ref } from 'vue'
+
+export const useUserStore = defineStore('user', () => {
+ const token = ref(localStorage.getItem('token') || '')
+ const realName = ref('')
+ const setToken = (t: string) => { token.value = t; localStorage.setItem('token', t) }
+ const logout = () => { token.value = ''; localStorage.removeItem('token') }
+ return { token, realName, setToken, logout }
+})
diff --git a/frontend/src/views/dashboard/DashboardView.vue b/frontend/src/views/dashboard/DashboardView.vue
new file mode 100644
index 00000000..dbc973e7
--- /dev/null
+++ b/frontend/src/views/dashboard/DashboardView.vue
@@ -0,0 +1,62 @@
+
+
+
供水总览
+
+
+
+
+
{{ card.value }}
+
{{ card.title }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/views/login/LoginView.vue b/frontend/src/views/login/LoginView.vue
new file mode 100644
index 00000000..d66cb3dd
--- /dev/null
+++ b/frontend/src/views/login/LoginView.vue
@@ -0,0 +1,52 @@
+
+
+
+
智慧水务管理系统
+
+
+
+
+
+
+
+
+ 登 录
+
+
+
+
+
+
+
+
+
diff --git a/frontend/src/views/system/dept/部门List.vue b/frontend/src/views/system/dept/部门List.vue
new file mode 100644
index 00000000..2083db17
--- /dev/null
+++ b/frontend/src/views/system/dept/部门List.vue
@@ -0,0 +1,22 @@
+
+
+ 新增部门
+
+
+
+
+
+ 编辑
+ 删除
+
+
+
+ 表单内容待实现
+
+
+
+
diff --git a/frontend/src/views/system/menu/菜单List.vue b/frontend/src/views/system/menu/菜单List.vue
new file mode 100644
index 00000000..651199bc
--- /dev/null
+++ b/frontend/src/views/system/menu/菜单List.vue
@@ -0,0 +1,22 @@
+
+
+ 新增菜单
+
+
+
+
+
+ 编辑
+ 删除
+
+
+
+ 表单内容待实现
+
+
+
+
diff --git a/frontend/src/views/system/role/角色List.vue b/frontend/src/views/system/role/角色List.vue
new file mode 100644
index 00000000..9654cff9
--- /dev/null
+++ b/frontend/src/views/system/role/角色List.vue
@@ -0,0 +1,22 @@
+
+
+ 新增角色
+
+
+
+
+
+ 编辑
+ 删除
+
+
+
+ 表单内容待实现
+
+
+
+
diff --git a/frontend/src/views/system/user/用户List.vue b/frontend/src/views/system/user/用户List.vue
new file mode 100644
index 00000000..80483475
--- /dev/null
+++ b/frontend/src/views/system/user/用户List.vue
@@ -0,0 +1,22 @@
+
+
+ 新增用户
+
+
+
+
+
+ 编辑
+ 删除
+
+
+
+ 表单内容待实现
+
+
+
+
diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json
new file mode 100644
index 00000000..ff99a337
--- /dev/null
+++ b/frontend/tsconfig.json
@@ -0,0 +1,20 @@
+{
+ "compilerOptions": {
+ "target": "ESNext",
+ "module": "ESNext",
+ "moduleResolution": "bundler",
+ "strict": true,
+ "jsx": "preserve",
+ "baseUrl": ".",
+ "paths": {
+ "@/*": [
+ "src/*"
+ ]
+ }
+ },
+ "include": [
+ "src/**/*.ts",
+ "src/**/*.d.ts",
+ "src/**/*.vue"
+ ]
+}
\ No newline at end of file
diff --git a/frontend/vite.config.ts b/frontend/vite.config.ts
new file mode 100644
index 00000000..e548f386
--- /dev/null
+++ b/frontend/vite.config.ts
@@ -0,0 +1,14 @@
+import { defineConfig } from 'vite'
+import vue from '@vitejs/plugin-vue'
+import path from 'path'
+
+export default defineConfig({
+ plugins: [vue()],
+ resolve: { alias: { '@': path.resolve(__dirname, 'src') } },
+ server: {
+ port: 3000,
+ proxy: {
+ '/api': { target: 'http://localhost:8080', changeOrigin: true }
+ }
+ }
+})
diff --git a/wm-common/src/main/java/com/water/common/core/annotation/DataScope.java b/wm-common/src/main/java/com/water/common/core/annotation/DataScope.java
new file mode 100644
index 00000000..cba52a78
--- /dev/null
+++ b/wm-common/src/main/java/com/water/common/core/annotation/DataScope.java
@@ -0,0 +1,11 @@
+package com.water.common.core.annotation;
+
+import java.lang.annotation.*;
+
+@Target(ElementType.METHOD)
+@Retention(RetentionPolicy.RUNTIME)
+@Documented
+public @interface DataScope {
+ String deptAlias() default "d";
+ String userAlias() default "u";
+}
diff --git a/wm-common/src/main/java/com/water/common/core/config/SwaggerCommonConfig.java b/wm-common/src/main/java/com/water/common/core/config/SwaggerCommonConfig.java
new file mode 100644
index 00000000..cb37523c
--- /dev/null
+++ b/wm-common/src/main/java/com/water/common/core/config/SwaggerCommonConfig.java
@@ -0,0 +1,15 @@
+package com.water.common.core.config;
+
+import io.swagger.v3.oas.models.OpenAPI;
+import io.swagger.v3.oas.models.info.Info;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+
+@Configuration
+public class SwaggerCommonConfig {
+ @Bean
+ public OpenAPI commonOpenAPI() {
+ return new OpenAPI().info(new Info().title("智慧水务管理系统 API")
+ .description("精河县供水工程综合管理平台").version("1.0.0"));
+ }
+}
diff --git a/wm-common/src/main/java/com/water/common/core/entity/BaseEntity.java b/wm-common/src/main/java/com/water/common/core/entity/BaseEntity.java
new file mode 100644
index 00000000..7d93a33a
--- /dev/null
+++ b/wm-common/src/main/java/com/water/common/core/entity/BaseEntity.java
@@ -0,0 +1,16 @@
+package com.water.common.core.entity;
+
+import com.baomidou.mybatisplus.annotation.*;
+import lombok.Data;
+import java.time.LocalDateTime;
+
+@Data
+public abstract class BaseEntity {
+ @TableId(type = IdType.AUTO)
+ private Long id;
+ @TableLogic
+ private Integer deleted;
+ private LocalDateTime createdAt;
+ @TableField(fill = FieldFill.INSERT_UPDATE)
+ private LocalDateTime updatedAt;
+}
diff --git a/wm-common/src/main/java/com/water/common/core/storage/MinioService.java b/wm-common/src/main/java/com/water/common/core/storage/MinioService.java
new file mode 100644
index 00000000..1aa4172e
--- /dev/null
+++ b/wm-common/src/main/java/com/water/common/core/storage/MinioService.java
@@ -0,0 +1,41 @@
+package com.water.common.core.storage;
+
+import io.minio.*;
+import lombok.extern.slf4j.Slf4j;
+import org.springframework.beans.factory.annotation.Value;
+import org.springframework.stereotype.Service;
+import org.springframework.web.multipart.MultipartFile;
+import java.io.InputStream;
+import java.util.UUID;
+
+@Slf4j
+@Service
+public class MinioService {
+ private final MinioClient client;
+ @Value("${minio.bucket:water-management}") private String bucket;
+
+ public MinioService(@Value("${minio.endpoint:http://127.0.0.1:9000}") String endpoint,
+ @Value("${minio.access-key:minioadmin}") String accessKey,
+ @Value("${minio.secret-key:minioadmin}") String secretKey) {
+ this.client = MinioClient.builder().endpoint(endpoint).credentials(accessKey, secretKey).build();
+ }
+
+ public String upload(MultipartFile file, String module) throws Exception {
+ String objectName = module + "/" + UUID.randomUUID() + "_" + file.getOriginalFilename();
+ ensureBucket();
+ client.putObject(PutObjectArgs.builder().bucket(bucket).object(objectName)
+ .stream(file.getInputStream(), file.getSize(), -1)
+ .contentType(file.getContentType()).build());
+ return objectName;
+ }
+
+ public InputStream download(String objectName) throws Exception {
+ return client.getObject(GetObjectArgs.builder().bucket(bucket).object(objectName).build());
+ }
+
+ private void ensureBucket() throws Exception {
+ if (!client.bucketExists(BucketExistsArgs.builder().bucket(bucket).build())) {
+ client.makeBucket(MakeBucketArgs.builder().bucket(bucket).build());
+ }
+ }
+}
diff --git a/wm-common/src/main/java/com/water/common/core/util/ExcelUtils.java b/wm-common/src/main/java/com/water/common/core/util/ExcelUtils.java
new file mode 100644
index 00000000..c3563c09
--- /dev/null
+++ b/wm-common/src/main/java/com/water/common/core/util/ExcelUtils.java
@@ -0,0 +1,23 @@
+package com.water.common.core.util;
+
+import com.alibaba.excel.EasyExcel;
+import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
+import jakarta.servlet.http.HttpServletResponse;
+import java.net.URLEncoder;
+import java.util.List;
+
+public class ExcelUtils {
+ public static void export(HttpServletResponse resp, String fileName, String sheetName, Class clazz, List data) {
+ try {
+ resp.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
+ resp.setCharacterEncoding("utf-8");
+ resp.setHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(fileName, "UTF-8") + ".xlsx");
+ EasyExcel.write(resp.getOutputStream(), clazz)
+ .sheet(sheetName)
+ .registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
+ .doWrite(data);
+ } catch (Exception e) {
+ throw new RuntimeException("导出Excel失败", e);
+ }
+ }
+}
diff --git a/wm-common/src/main/java/com/water/common/core/util/IdUtils.java b/wm-common/src/main/java/com/water/common/core/util/IdUtils.java
new file mode 100644
index 00000000..480540d9
--- /dev/null
+++ b/wm-common/src/main/java/com/water/common/core/util/IdUtils.java
@@ -0,0 +1,10 @@
+package com.water.common.core.util;
+
+import cn.hutool.core.lang.Snowflake;
+import cn.hutool.core.util.IdUtil;
+
+public class IdUtils {
+ private static final Snowflake SNOWFLAKE = IdUtil.getSnowflake(1, 1);
+ public static long nextId() { return SNOWFLAKE.nextId(); }
+ public static String nextIdStr() { return String.valueOf(SNOWFLAKE.nextId()); }
+}