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 @@ + + + + + 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 @@ + + + + + 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()); } +}