Browse Source

海绵城市-多系统修改

zhiyuan-007 4 months ago
parent
commit
56dcb7c866

+ 5 - 1
src/api/menu.ts

@@ -1,11 +1,15 @@
 import {request} from '@/utils/request';
 import { AxiosPromise } from 'axios';
 import { RouteRecordRaw } from 'vue-router';
+import useSettingsStore from "@/store/modules/settings";
 import menuJson from '@/assets/json/menu.json';
 // 获取路由
 export function getRouters(): AxiosPromise<RouteRecordRaw[]> {
   return request({
     url: '/system/menu/getRouters',
-    method: 'get'
+    method: 'get',
+    params:{
+      systemType:useSettingsStore().systemType
+    }
   });
 }

BIN
src/assets/images/login_bg.jpg


BIN
src/assets/images/title_bg.png


+ 7 - 1
src/enums/SettingTypeEnum.ts

@@ -11,5 +11,11 @@ export enum SettingTypeEnum {
   LAYOUT = 'layout',
   DARK = 'dark',
 
-  LAYOUT_SETTING = 'layout-setting'
+  LAYOUT_SETTING = 'layout-setting',
+
+  SYSTEM_TYPE =  'systemType',
+
+  SYSTEM_NAME =  'systemName',
+
+  SYSTEM_LOGIN_URL =  'systemLoginUrl',
 }

+ 6 - 3
src/layout/components/Header/index.vue

@@ -1,6 +1,6 @@
 <template>
   <div class="header-box" :style="{ background: theme }">
-    <div class="left-title">上海市海绵城市信息系统</div>
+    <div class="left-title">{{settingsStore.systemName}}</div>
     <TopNav v-if="settingsStore.topNav" class="top-nav"></TopNav>
     <div class="right-menu flex align-center">
       <template v-if="appStore.device !== 'mobile'">
@@ -144,8 +144,11 @@ const logout = async () => {
     cancelButtonText: '取消',
     type: 'warning'
   });
-  userStore.logout()
-  router.push('/login');
+  userStore.logout();
+  settingsStore.setSystemType(null);
+  settingsStore.setSystemName(null);
+  router.push(settingsStore.systemLoginUrl);
+  settingsStore.setSystemLoginUrl(null);
 };
 
 const emits = defineEmits(['setLayout']);

+ 12 - 2
src/permission.ts

@@ -9,19 +9,29 @@ import useUserStore from '@/store/modules/user';
 import useSettingsStore from '@/store/modules/settings';
 import usePermissionStore from '@/store/modules/permission';
 NProgress.configure({ showSpinner: false });
-const whiteList = ['/login', '/register', '/social-callback'];
+const whiteList = ['/login','/login_hmcs','/login_cstj', '/register', '/social-callback'];
 
 router.beforeEach(async (to, from, next) => {
   NProgress.start();
   if (getToken()) {
     to.meta.title && useSettingsStore().setTitle(to.meta.title);
     /* has token*/
-    if (to.path === '/login') {
+    if (to.path === '/login'||to.path === '/login_hxcs'||to.path === '/login_cstj') {
       next();
       NProgress.done();
     } else if (whiteList.indexOf(to.path as string) !== -1) {
       next();
     } else {
+      if(from.path === '/home'){
+        const accessRoutes = await usePermissionStore().generateRoutes();
+        // 根据roles权限生成可访问的路由表
+        accessRoutes.forEach((route) => {
+          if (!isHttp(route.path)) {
+            router.addRoute(route); // 动态添加可访问路由表
+          }
+        });
+      }
+
       if (useUserStore().roles.length === 0) {
         isRelogin.show = true;
         // 判断当前用户是否已拉取完user_info信息

+ 33 - 5
src/router/index.ts

@@ -31,6 +31,16 @@ export const constantRoutes: RouteRecordRaw[] = [
     component: () => import('@/views/login.vue'),
     hidden: true
   },
+  {
+    path: '/login_hmcs',
+    component: () => import('@/views/login_hmcs.vue'),
+    hidden: true
+  },
+  {
+    path: '/login_cstj',
+    component: () => import('@/views/login_cstj.vue'),
+    hidden: true
+  },
   {
     path: '/:pathMatch(.*)*',
     component: () => import('@/views/error/404.vue'),
@@ -42,14 +52,32 @@ export const constantRoutes: RouteRecordRaw[] = [
     hidden: true
   },
   {
-    path: '',
+    path: '/home',
+    component: () => import('@/views/home.vue'),
+    hidden: true
+  },
+  {
+    path: '/index_cstj',
+    component: Layout,
+    redirect: '/index_cstj',
+    children: [
+      {
+        path: '/index_cstj',
+        component: () => import('@/views/index_cstj.vue'),
+        name: 'IndexCstj',
+        meta: { title: '首页', icon: 'dashboard', affix: true }
+      }
+    ]
+  },
+  {
+    path: '/index_hmcs',
     component: Layout,
-    redirect: '/index',
+    redirect: '/index_hmcs',
     children: [
       {
-        path: '/index',
-        component: () => import('@/views/index.vue'),
-        name: 'Index',
+        path: '/index_hmcs',
+        component: () => import('@/views/index_hmcs.vue'),
+        name: 'IndexHmcs',
         meta: { title: '首页', icon: 'dashboard', affix: true }
       }
     ]

+ 17 - 1
src/settings.ts

@@ -56,6 +56,22 @@ const setting: DefaultSettings = {
 
   size: 'default',
 
-  layout: ''
+  layout: '',
+
+  /**
+   * 子系统参数-系统类型
+   */
+  systemType:null,
+  /**
+   * 子系统参数-系统名称
+   */
+  systemName:null,
+  /**
+   * 子系统参数-系统登录页
+   */
+  systemLoginUrl:null,
+
+
+
 };
 export default setting;

+ 23 - 2
src/store/modules/settings.ts

@@ -9,7 +9,10 @@ export const useSettingsStore = defineStore('setting', () => {
     sidebarLogo: defaultSettings.sidebarLogo,
     dynamicTitle: defaultSettings.dynamicTitle,
     sideTheme: defaultSettings.sideTheme,
-    theme: defaultSettings.theme
+    theme: defaultSettings.theme,
+    systemType: defaultSettings.systemType,
+    systemName: defaultSettings.systemName,
+    systemLoginUrl: defaultSettings.systemLoginUrl,
   });
   const title = ref<string>(defaultSettings.title);
   const theme = ref<string>(storageSetting.value.theme);
@@ -22,10 +25,22 @@ export const useSettingsStore = defineStore('setting', () => {
   const dark = ref<boolean>(defaultSettings.dark);
   const topNav = ref<boolean>(storageSetting.value.topNav);
   const navbarView = ref<boolean>(defaultSettings.navbarView);
+  const systemType = ref<string>(storageSetting.value.systemType);
+  const systemName = ref<string>(storageSetting.value.systemName);
+  const systemLoginUrl = ref<string>(storageSetting.value.systemLoginUrl);
   const setTitle = (value: string) => {
     title.value = value;
     useDynamicTitle();
   };
+  const setSystemType = (value: string) => {
+    systemType.value = value;
+  };
+  const setSystemName = (value: string) => {
+    systemName.value = value;
+  };
+  const setSystemLoginUrl =  (value: string) => {
+    systemLoginUrl.value = value;
+  };
   return {
     title,
     theme,
@@ -38,7 +53,13 @@ export const useSettingsStore = defineStore('setting', () => {
     animationEnable,
     dark,
     navbarView,
-    setTitle
+    setTitle,
+    systemType,
+    systemName,
+    systemLoginUrl,
+    setSystemType,
+    setSystemName,
+    setSystemLoginUrl
   };
 });
 

+ 22 - 0
src/types/global.d.ts

@@ -117,6 +117,15 @@ declare global {
      * 主题模式
      */
     theme: string;
+    /**
+     * 系统类型
+     */
+    systemType: string;
+    /**
+     * 系统名称
+     */
+    systemName: string;
+
   }
 
   declare interface DefaultSettings extends LayoutSetting {
@@ -163,6 +172,19 @@ declare global {
     dark: boolean;
 
     errorLog: string;
+
+    /**
+     * 子系统参数-系统类型
+     */
+    systemType: string;
+    /**
+     * 子系统参数-系统名称
+     */
+    systemName: string;
+    /**
+     * 子系统参数-系统登录地址
+     */
+    systemLoginUrl: string;
   }
 }
 export {};

+ 102 - 0
src/views/home.vue

@@ -0,0 +1,102 @@
+<template>
+  <div id="home">
+    <div class="top">
+      <div class="title">
+        <span>上海市CIM平台</span>
+      </div>
+    </div>
+    <div class="middle">
+      <div v-for="(item, index) in systemList" :key="index" class="single-item" @click="goToItem(item)">
+        <span>{{ item.name }}</span>
+      </div>
+    </div>
+    <div class="bottom">
+
+    </div>
+  </div>
+</template>
+
+<script setup name="Home" lang="ts">
+import useSettingsStore from "@/store/modules/settings";
+const settingsStore = useSettingsStore();
+const router = useRouter();
+const systemList = ref([
+  {
+    id:"1",
+    name:"上海市海绵城市信息系统",
+    homeUrl:"/index_hmcs",
+  },
+  {
+    id:"2",
+    name:"上海市城市体检信息系统",
+    homeUrl:"/index_cstj",
+  }
+]);
+function goToItem(item: any){
+  useSettingsStore().setSystemType(item.id);
+  useSettingsStore().setSystemName(item.name);
+  useSettingsStore().setSystemLoginUrl('/login');
+  router.push(item.homeUrl);
+}
+</script>
+
+<style scoped lang="scss">
+#home{
+  width: 100%;
+  height: 100%;
+  display: flex;
+  flex-direction: column;
+  background: url("../assets/images/login_bg.jpg");
+  background-size: cover;
+  .top{
+    width: 100%;
+    height: 15%;
+    .title{
+      position: absolute;
+      top: 50px;
+      left: 40px;
+      width: 600px;
+      height: 80px;
+      background: url("../assets/images/title_bg.png");
+      background-size: cover;
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      span{
+        font-size: 5vh;
+        color: #eff8fc;
+        line-height: 8.557vh;
+        text-transform: uppercase;
+        letter-spacing: 1.031vh;
+        background: linear-gradient(0deg, #EFF8FC 0%, #E9F8FF 0%, #59C8FF 100%);
+        -webkit-background-clip: text;
+        animation: wave 30s linear infinite;
+        -webkit-text-fill-color: transparent;
+        font-family: 'heitao';
+      }
+    }
+  }
+  .middle{
+    width: 100%;
+    height: 70%;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    .single-item{
+      cursor: pointer;
+      width: 400px;
+      height: 400px;
+      font-size: 5vh;
+      color: #eff8fc;
+      font-family: 'heitao';
+      display: flex;
+      justify-content: center;
+      align-items: center;
+    }
+  }
+  .bottom{
+    width: 100%;
+    height: 15%;
+  }
+}
+</style>

+ 1 - 1
src/views/index.vue

@@ -56,7 +56,7 @@
   </div>
 </template>
 
-<script setup name="Index" lang="ts">
+<script setup name="IndexCstj" lang="ts">
 import {nextTick} from "vue";
 import {ref} from "vue";
 import usePermissionStore from '@/store/modules/permission';

+ 322 - 0
src/views/index_hmcs.vue

@@ -0,0 +1,322 @@
+<template>
+  <div class="app-container home">
+    <div class="main-page-top">
+      <div class="content-wrapper">
+        <div class="content">
+          <div class="image-container">
+            <img src="../assets/images/home/shanghai.png" alt="上海天际线"/>
+          </div>
+          <div class="right-part">
+            <div class="side-bar" v-for="(item,index) in firstRouteData" :key="index" >
+              <div
+                @click="!item.disabled && goToPage(item.path)"
+                @mouseover="!item.disabled && (item.isHovered = true)"
+                @mouseleave="!item.disabled && (item.isHovered = false)"
+                :style="dynamicStyle(item)"
+                :disabled="item.disabled"
+                class="button-with-arrow"
+              >{{ item.name }}</div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+    <div class="main-page-lower">
+      <div class="recent-news">
+        <h2>最新成效</h2>
+        <ul>
+          <li>
+            <div class="number-square">1</div>
+            <span class="news-title">上海市人民政府办公厅关于印发《本市系统化全域推进海绵城市建设的实施意见》的通知</span>
+            <span class="news-date">2024-06-18</span>
+          </li>
+          <li>
+            <div class="number-square">2</div>
+            <span class="news-title">沪府办〔2016〕165号-关于印发《上海市海绵城市规划建设技术导则(试行)》的通知</span>
+            <span class="news-date">2024-06-18</span>
+          </li>
+          <li>
+            <div class="number-square">3</div>
+            <span class="news-title">上海市人民政府办公厅转发市水务局、市环保局制订的《关于加快本市城乡小河...</span>
+            <span class="news-date">2024-06-18</span>
+          </li>
+          <li>
+            <div class="number-square">4</div>
+            <span class="news-title">沪府办〔2017〕80号上海市人民政府办公厅关于转发市水务局制订的《苏州河环...</span>
+            <span class="news-date">2024-06-18</span>
+          </li>
+          <li>
+            <div class="number-square">5</div>
+            <span class="news-title">关于印发《开展上海市雨污混接综合整治攻坚战的实施意见》的通知</span>
+            <span class="news-date">2024-06-18</span>
+          </li>
+        </ul>
+      </div>
+    </div>
+  </div>
+</template>
+
+<script setup name="IndexHmcs" lang="ts">
+import {nextTick} from "vue";
+import {ref} from "vue";
+import usePermissionStore from '@/store/modules/permission';
+import { RouteRecordRaw } from 'vue-router';
+import {func} from "vue-types";
+const router = useRouter();
+const permissionStore = usePermissionStore();
+const routes = computed<RouteRecordRaw[]>(() => permissionStore.getRoutes());
+let firstRouteData = ref([
+  {
+    name:'规划计划',
+    disabled:true,
+    isHovered: false, // 初始悬浮状态
+    bgUrl:'../assets/images/home/ghjh1.png',
+    hoverUrl:'../assets/images/home/ghjh2.png'
+  },
+  {
+    name:'项目管理',
+    disabled:true,
+    isHovered: false,
+    bgUrl:'../assets/images/home/xmgl1.png',
+    hoverUrl:'../assets/images/home/xmgl2.png'
+  },
+  {
+    name:'设备设施监测',
+    disabled:true,
+    isHovered: false,
+    bgUrl:'../assets/images/home/sbss1.png',
+    hoverUrl:'../assets/images/home/sbss2.png'
+  },
+  {
+    name:'成效评估',
+    disabled:true,
+    isHovered: false,
+    bgUrl:'../assets/images/home/cxpg1.png',
+    hoverUrl:'../assets/images/home/cxpg2.png'
+  },
+  {
+    name:'政策公开',
+    disabled:true,
+    isHovered: false,
+    bgUrl:'../assets/images/home/zzk1.png',
+    hoverUrl:'../assets/images/home/zzs2.png'
+  }
+]);
+function dynamicStyle(item) {
+  const bgUrl = getBackgroundUrl(item);
+  return {
+    //backgroundImage: `url(${bgUrl})`,
+    pointerEvents: item.disabled ? 'none' : 'auto',
+    opacity: item.disabled ? 0.5 : 1
+  };
+}
+function getBackgroundUrl(item) {
+  // 自定义逻辑生成 URL
+  if (item.isHovered) {
+    return new URL(item.hoverUrl, import.meta.url).href;
+  } else {
+    return new URL(item.bgUrl, import.meta.url).href;
+  }
+}
+function goToPage(path) {
+  router.push({ path: path});
+}
+onMounted(() => {
+  nextTick(() => {
+    routes.value.forEach(bigItem => {
+      firstRouteData.value.forEach(smallItem => {
+        if (bigItem?.meta?.title === smallItem.name) {
+          // 递归拼接每一层的路径
+          const buildFullPath = (item, currentPath = "") => {
+            let fullPath = `${currentPath}/${item.path}`.replace(/\/+/, "/"); // 拼接路径并去掉多余的 "/"
+
+            if (item.children && item.children.length > 0) {
+              // 递归处理子项,继续拼接路径
+              return buildFullPath(item.children[0], fullPath);
+            }
+
+            // 去掉末尾的 /index
+            return fullPath.replace(/\/index$/, "");
+          };
+
+          smallItem.disabled = false;
+          smallItem.path = buildFullPath(bigItem); // 获取完整路径
+        }
+      });
+    });
+  })
+})
+
+</script>
+
+<style scoped lang="scss">
+.home {
+  box-sizing: border-box;
+  height: calc(100%);
+  overflow-y: auto;
+  .main-page-top {
+    display: flex;
+    flex-direction: column;
+    //background-color: #F5F5F5;
+    padding-top: 30px;
+    padding-bottom: 30px;
+    height: 60%;
+    .content-wrapper {
+      display: flex;
+      width: 90%;
+      justify-content: center; /* 居中对齐 */
+      .content {
+        width: 100%;
+        display: flex;
+        justify-content: center;
+        align-items: flex-start;
+        .image-container {
+          flex-shrink: 0;
+          width: 60%;
+          display: flex;
+          justify-content: center;
+          align-items: center;
+          img {
+            width: 920px; /* 设定图片的最大宽度 */
+            height: 520px;
+          }
+        }
+        .right-part{
+          .side-bar {
+            display: flex;
+            flex-direction: column;
+            width: 350px;
+            margin-left: 20px;
+            div {
+              display: block;
+              height:  96px;
+              padding: 29px;
+              margin-bottom: 10px;
+              border: 2px solid var(--commonTextColor);
+              cursor: pointer;
+              border-radius: 4px;
+              font-weight: bold;
+              font-family: "MicrosoftYaHei-Bold", sans-serif;
+              text-align: left;
+              font-size: 20px;
+              //background-color: #FFFFFF;
+              color:var(--commonTextColor);
+              /* 其他样式 */
+              box-shadow: none; /* 移除阴影效果 */
+            }
+            div:focus {
+              outline: none; /* 移除聚焦时的默认边框 */
+            }
+          }
+          .button-with-arrow {
+            position: relative;
+            padding-right: 10px; /* 调整箭头与按钮文本之间的间距 */
+            background-size: cover;
+            color:var(--commonTextColor)
+          }
+          .button-with-arrow:disabled {
+            cursor: not-allowed; /* 禁用时鼠标样式 */
+            color: gray !important; /* 禁用时字体颜色 */
+          }
+          .button-with-arrow::after {
+            content: '→'; /* 使用Unicode编码或者直接使用箭头字符 */
+            position: absolute;
+            top: 50%;
+            right: 30px; /* 箭头与按钮右侧的距离 */
+            transform: translateY(-50%);
+            font-size: 28px; /* 调整箭头的大小 */
+            color: #B3B3B3; /* 箭头的颜色 */
+          }
+
+          .button-with-arrow:hover{
+            color: #0080FF; /* 箭头的颜色 */
+          }
+
+          .button-with-arrow:hover::after {
+            color: #0080FF; /* 箭头悬停时的颜色 */
+          }
+        }
+      }
+    }
+  }
+  .main-page-lower {
+    height: 40%;
+    display: flex;
+    padding: 20px;
+    //background-color: #FFFFFF;
+    .recent-news {
+      flex-grow: 1;
+      padding: 20px;
+      border-radius: 5px;
+      h2 {
+        margin-top: 0;
+        font-weight: bold; /* 标题加粗 */
+        font-size: 24px;
+        text-align: center; /* 文本居中对齐 */
+        display: inline-block; /* 将h2设为内联块级元素 */
+        position: relative; /* 相对定位,以便于伪元素的绝对定位 */
+        margin-left: 350px;
+      }
+      h2::before {
+        content: '';
+        position: absolute;
+        top: 0;
+        left: -20px;
+        height: 100%;
+        width: 10%; /* 控制背景色占据的宽度 */
+        background-color: #0080FF; /* 与h2背景色一致 */
+        z-index: -1; /* 确保伪元素在文本下面 */
+      }
+      ul {
+        list-style: none;
+        padding: 0;
+        li {
+          display: flex;
+          align-items: center; /* 垂直居中 */
+          justify-content: space-between;
+          padding: 10px 350px;
+          //background-color: rgba(31, 98, 167, 0.1); /* 半透明背景色 */
+          .number-square {
+            width: 30px;
+            height: 30px;
+            opacity: 0.35;
+            background-color: #0080FF; /* 数字方块背景色 */
+            color: white;
+            font-weight: bold;
+            font-size: 16px;
+            display: flex;
+            justify-content: center;
+            align-items: center;
+            border-radius: 4px;
+            margin-right: 23px;
+          }
+          .news-title {
+            flex-grow: 1; /* 让标题占据剩余空间 */
+            text-align: left; /* 文本向左对齐 */
+            font-size: 20px;
+            font-family: "MicrosoftYaHei-Bold", sans-serif; /* 设置字体为 MicrosoftYaHei-Bold */
+            font-weight: bold; /* 字重加粗 */
+          }
+          .news-title:hover {
+            color: #0080FF;
+          }
+          .news-date {
+            font-weight: bold; /* 日期加粗 */
+            text-align: right; /* 文本向右对齐 */
+            font-size: 16px;
+            color: #595959;
+            opacity: 0.7;
+          }
+        }
+        li:hover  {
+          //background-color: #eff6ff;
+          cursor: pointer; /* 鼠标悬停时显示手型 */
+          .number-square {
+            opacity: 1; /* 鼠标悬浮时完全不透明 */
+          }
+        }
+      }
+    }
+  }
+}
+</style>

+ 4 - 3
src/views/login.vue

@@ -1,7 +1,7 @@
 <template>
   <div class="login">
     <el-form ref="loginRef" :model="loginForm" :rules="loginRules" class="login-form">
-      <h3 class="title">上海市海绵城市信息系统</h3>
+      <h3 class="title">上海市CIM+应用</h3>
 <!--      <el-form-item v-if="tenantEnabled" prop="tenantId">-->
 <!--        <el-select v-model="loginForm.tenantId" filterable placeholder="请选择/输入公司名称" style="width: 100%">-->
 <!--          <el-option v-for="item in tenantList" :key="item.tenantId" :label="item.companyName" :value="item.tenantId"></el-option>-->
@@ -113,7 +113,8 @@ const handleLogin = () => {
       // 调用action的登录方法
       const [err] = await to(userStore.login(loginForm.value));
       if (!err) {
-        const redirectUrl = redirect.value || '/';
+        //const redirectUrl = redirect.value || '/home';
+        const redirectUrl = '/home';
         await router.push(redirectUrl);
         loading.value = false;
       } else {
@@ -182,7 +183,7 @@ onMounted(() => {
   justify-content: center;
   align-items: center;
   height: 100%;
-  background-image: url('../assets/images/login-background.jpg');
+  background-image: url('../assets/images/login_bg.jpg');
   background-size: cover;
 }
 

+ 255 - 0
src/views/login_cstj.vue

@@ -0,0 +1,255 @@
+<template>
+  <div class="login">
+    <el-form ref="loginRef" :model="loginForm" :rules="loginRules" class="login-form">
+      <h3 class="title">上海市城市体检信息系统</h3>
+      <!--      <el-form-item v-if="tenantEnabled" prop="tenantId">-->
+      <!--        <el-select v-model="loginForm.tenantId" filterable placeholder="请选择/输入公司名称" style="width: 100%">-->
+      <!--          <el-option v-for="item in tenantList" :key="item.tenantId" :label="item.companyName" :value="item.tenantId"></el-option>-->
+      <!--          <template #prefix><svg-icon icon-class="company" class="el-input__icon input-icon" /></template>-->
+      <!--        </el-select>-->
+      <!--      </el-form-item>-->
+      <el-form-item prop="username">
+        <el-input v-model="loginForm.username" type="text" size="large" auto-complete="off" placeholder="账号">
+          <template #prefix><svg-icon icon-class="user" class="el-input__icon input-icon" /></template>
+        </el-input>
+      </el-form-item>
+      <el-form-item prop="password">
+        <el-input v-model="loginForm.password" type="password" size="large" auto-complete="off" placeholder="密码" @keyup.enter="handleLogin">
+          <template #prefix><svg-icon icon-class="password" class="el-input__icon input-icon" /></template>
+        </el-input>
+      </el-form-item>
+      <el-form-item v-if="captchaEnabled" prop="code">
+        <el-input v-model="loginForm.code" size="large" auto-complete="off" placeholder="验证码" style="width: 63%" @keyup.enter="handleLogin">
+          <template #prefix><svg-icon icon-class="validCode" class="el-input__icon input-icon" /></template>
+        </el-input>
+        <div class="login-code">
+          <img :src="codeUrl" class="login-code-img" @click="getCode" />
+        </div>
+      </el-form-item>
+      <el-checkbox v-model="loginForm.rememberMe" style="margin: 0 0 25px 0">记住密码</el-checkbox>
+      <el-form-item style="width: 100%">
+        <el-button :loading="loading" size="large" type="primary" style="width: 100%" @click.prevent="handleLogin">
+          <span v-if="!loading">登 录</span>
+          <span v-else>登 录 中...</span>
+        </el-button>
+        <div v-if="register" style="float: right">
+          <router-link class="link-type" :to="'/register'">立即注册</router-link>
+        </div>
+      </el-form-item>
+    </el-form>
+    <!--  底部  -->
+    <div class="el-login-footer">
+      <!-- <span>Copyright © 2018-2024 疯狂的狮子Li All Rights Reserved.</span> -->
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { getCodeImg, getTenantList } from '@/api/login';
+import { authBinding } from '@/api/system/social/auth';
+import { useUserStore } from '@/store/modules/user';
+import { LoginData, TenantVO } from '@/api/types';
+import { to } from 'await-to-js';
+import { HttpStatus } from '@/enums/RespEnum';
+import useSettingsStore from "@/store/modules/settings";
+
+const userStore = useUserStore();
+const router = useRouter();
+
+const loginForm = ref<LoginData>({
+  tenantId: '000000',
+  username: '',
+  password: '',
+  rememberMe: false,
+  code: '',
+  uuid: ''
+} as LoginData);
+
+const loginRules: ElFormRules = {
+  tenantId: [{ required: true, trigger: 'blur', message: '请输入您的租户编号' }],
+  username: [{ required: true, trigger: 'blur', message: '请输入您的账号' }],
+  password: [{ required: true, trigger: 'blur', message: '请输入您的密码' }],
+  code: [{ required: true, trigger: 'change', message: '请输入验证码' }]
+};
+
+const codeUrl = ref('');
+const loading = ref(false);
+// 验证码开关
+const captchaEnabled = ref(true);
+// 租户开关
+const tenantEnabled = ref(true);
+
+// 注册开关
+const register = ref(false);
+const redirect = ref(undefined);
+const loginRef = ref<ElFormInstance>();
+// 租户列表
+const tenantList = ref<TenantVO[]>([]);
+
+watch(
+  () => router.currentRoute.value,
+  (newRoute: any) => {
+    redirect.value = newRoute.query && newRoute.query.redirect;
+  },
+  { immediate: true }
+);
+
+const handleLogin = () => {
+  loginRef.value?.validate(async (valid: boolean, fields: any) => {
+    if (valid) {
+      loading.value = true;
+      // 勾选了需要记住密码设置在 localStorage 中设置记住用户名和密码
+      if (loginForm.value.rememberMe) {
+        localStorage.setItem('tenantId', String(loginForm.value.tenantId));
+        localStorage.setItem('username', String(loginForm.value.username));
+        localStorage.setItem('password', String(loginForm.value.password));
+        localStorage.setItem('rememberMe', String(loginForm.value.rememberMe));
+      } else {
+        // 否则移除
+        localStorage.removeItem('tenantId');
+        localStorage.removeItem('username');
+        localStorage.removeItem('password');
+        localStorage.removeItem('rememberMe');
+      }
+      // 调用action的登录方法
+      const [err] = await to(userStore.login(loginForm.value));
+      if (!err) {
+        useSettingsStore().setSystemType("2");
+        useSettingsStore().setSystemName("上海市城市体检信息系统");
+        useSettingsStore().setSystemLoginUrl('/login_cstj');
+
+        const redirectUrl = '/index_cstj';
+        await router.push(redirectUrl);
+        loading.value = false;
+      } else {
+        loading.value = false;
+        // 重新获取验证码
+        if (captchaEnabled.value) {
+          await getCode();
+        }
+      }
+    } else {
+      console.log('error submit!', fields);
+    }
+  });
+};
+
+/**
+ * 获取验证码
+ */
+const getCode = async () => {
+  const res = await getCodeImg();
+  const { data } = res;
+  captchaEnabled.value = data.captchaEnabled === undefined ? true : data.captchaEnabled;
+  if (captchaEnabled.value) {
+    codeUrl.value = 'data:image/gif;base64,' + data.img;
+    loginForm.value.uuid = data.uuid;
+  }
+};
+
+const getLoginData = () => {
+  const tenantId = localStorage.getItem('tenantId');
+  const username = localStorage.getItem('username');
+  const password = localStorage.getItem('password');
+  const rememberMe = localStorage.getItem('rememberMe');
+  loginForm.value = {
+    tenantId: tenantId === null ? String(loginForm.value.tenantId) : tenantId,
+    username: username === null ? String(loginForm.value.username) : username,
+    password: password === null ? String(loginForm.value.password) : String(password),
+    rememberMe: rememberMe === null ? false : Boolean(rememberMe)
+  } as LoginData;
+};
+
+/**
+ * 获取租户列表
+ */
+const initTenantList = async () => {
+  const { data } = await getTenantList();
+  tenantEnabled.value = data.tenantEnabled === undefined ? true : data.tenantEnabled;
+  if (tenantEnabled.value) {
+    tenantList.value = data.voList;
+    if (tenantList.value != null && tenantList.value.length !== 0) {
+      loginForm.value.tenantId = tenantList.value[0].tenantId;
+    }
+  }
+};
+
+onMounted(() => {
+  getCode();
+  initTenantList();
+  getLoginData();
+});
+</script>
+
+<style lang="scss" scoped>
+.login {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  height: 100%;
+  background-image: url('../assets/images/login-background.jpg');
+  background-size: cover;
+}
+
+.title {
+  margin: 0px auto 30px auto;
+  text-align: center;
+  color: #707070;
+}
+
+.login-form {
+  border-radius: 6px;
+  background: #ffffff;
+  width: 400px;
+  padding: 25px 25px 5px 25px;
+
+  .el-input {
+    height: 40px;
+
+    input {
+      height: 40px;
+    }
+  }
+
+  .input-icon {
+    height: 39px;
+    width: 14px;
+    margin-left: 0px;
+  }
+}
+
+.login-tip {
+  font-size: 13px;
+  text-align: center;
+  color: #bfbfbf;
+}
+
+.login-code {
+  width: 33%;
+  height: 40px;
+  float: right;
+
+  img {
+    cursor: pointer;
+    vertical-align: middle;
+  }
+}
+
+.el-login-footer {
+  height: 40px;
+  line-height: 40px;
+  position: fixed;
+  bottom: 0;
+  width: 100%;
+  text-align: center;
+  color: #fff;
+  font-family: Arial, serif;
+  font-size: 12px;
+  letter-spacing: 1px;
+}
+
+.login-code-img {
+  height: 40px;
+  padding-left: 12px;
+}
+</style>

+ 255 - 0
src/views/login_hmcs.vue

@@ -0,0 +1,255 @@
+<template>
+  <div class="login">
+    <el-form ref="loginRef" :model="loginForm" :rules="loginRules" class="login-form">
+      <h3 class="title">上海市海绵城市信息系统</h3>
+      <!--      <el-form-item v-if="tenantEnabled" prop="tenantId">-->
+      <!--        <el-select v-model="loginForm.tenantId" filterable placeholder="请选择/输入公司名称" style="width: 100%">-->
+      <!--          <el-option v-for="item in tenantList" :key="item.tenantId" :label="item.companyName" :value="item.tenantId"></el-option>-->
+      <!--          <template #prefix><svg-icon icon-class="company" class="el-input__icon input-icon" /></template>-->
+      <!--        </el-select>-->
+      <!--      </el-form-item>-->
+      <el-form-item prop="username">
+        <el-input v-model="loginForm.username" type="text" size="large" auto-complete="off" placeholder="账号">
+          <template #prefix><svg-icon icon-class="user" class="el-input__icon input-icon" /></template>
+        </el-input>
+      </el-form-item>
+      <el-form-item prop="password">
+        <el-input v-model="loginForm.password" type="password" size="large" auto-complete="off" placeholder="密码" @keyup.enter="handleLogin">
+          <template #prefix><svg-icon icon-class="password" class="el-input__icon input-icon" /></template>
+        </el-input>
+      </el-form-item>
+      <el-form-item v-if="captchaEnabled" prop="code">
+        <el-input v-model="loginForm.code" size="large" auto-complete="off" placeholder="验证码" style="width: 63%" @keyup.enter="handleLogin">
+          <template #prefix><svg-icon icon-class="validCode" class="el-input__icon input-icon" /></template>
+        </el-input>
+        <div class="login-code">
+          <img :src="codeUrl" class="login-code-img" @click="getCode" />
+        </div>
+      </el-form-item>
+      <el-checkbox v-model="loginForm.rememberMe" style="margin: 0 0 25px 0">记住密码</el-checkbox>
+      <el-form-item style="width: 100%">
+        <el-button :loading="loading" size="large" type="primary" style="width: 100%" @click.prevent="handleLogin">
+          <span v-if="!loading">登 录</span>
+          <span v-else>登 录 中...</span>
+        </el-button>
+        <div v-if="register" style="float: right">
+          <router-link class="link-type" :to="'/register'">立即注册</router-link>
+        </div>
+      </el-form-item>
+    </el-form>
+    <!--  底部  -->
+    <div class="el-login-footer">
+      <!-- <span>Copyright © 2018-2024 疯狂的狮子Li All Rights Reserved.</span> -->
+    </div>
+  </div>
+</template>
+
+<script setup lang="ts">
+import { getCodeImg, getTenantList } from '@/api/login';
+import { authBinding } from '@/api/system/social/auth';
+import { useUserStore } from '@/store/modules/user';
+import { LoginData, TenantVO } from '@/api/types';
+import { to } from 'await-to-js';
+import { HttpStatus } from '@/enums/RespEnum';
+import useSettingsStore from "@/store/modules/settings";
+
+const userStore = useUserStore();
+const router = useRouter();
+
+const loginForm = ref<LoginData>({
+  tenantId: '000000',
+  username: '',
+  password: '',
+  rememberMe: false,
+  code: '',
+  uuid: ''
+} as LoginData);
+
+const loginRules: ElFormRules = {
+  tenantId: [{ required: true, trigger: 'blur', message: '请输入您的租户编号' }],
+  username: [{ required: true, trigger: 'blur', message: '请输入您的账号' }],
+  password: [{ required: true, trigger: 'blur', message: '请输入您的密码' }],
+  code: [{ required: true, trigger: 'change', message: '请输入验证码' }]
+};
+
+const codeUrl = ref('');
+const loading = ref(false);
+// 验证码开关
+const captchaEnabled = ref(true);
+// 租户开关
+const tenantEnabled = ref(true);
+
+// 注册开关
+const register = ref(false);
+const redirect = ref(undefined);
+const loginRef = ref<ElFormInstance>();
+// 租户列表
+const tenantList = ref<TenantVO[]>([]);
+
+watch(
+  () => router.currentRoute.value,
+  (newRoute: any) => {
+    redirect.value = newRoute.query && newRoute.query.redirect;
+  },
+  { immediate: true }
+);
+
+const handleLogin = () => {
+  loginRef.value?.validate(async (valid: boolean, fields: any) => {
+    if (valid) {
+      loading.value = true;
+      // 勾选了需要记住密码设置在 localStorage 中设置记住用户名和密码
+      if (loginForm.value.rememberMe) {
+        localStorage.setItem('tenantId', String(loginForm.value.tenantId));
+        localStorage.setItem('username', String(loginForm.value.username));
+        localStorage.setItem('password', String(loginForm.value.password));
+        localStorage.setItem('rememberMe', String(loginForm.value.rememberMe));
+      } else {
+        // 否则移除
+        localStorage.removeItem('tenantId');
+        localStorage.removeItem('username');
+        localStorage.removeItem('password');
+        localStorage.removeItem('rememberMe');
+      }
+      // 调用action的登录方法
+      const [err] = await to(userStore.login(loginForm.value));
+      if (!err) {
+        useSettingsStore().setSystemType("1");
+        useSettingsStore().setSystemName("上海市海绵城市信息系统");
+        useSettingsStore().setSystemLoginUrl('/login_hmcs');
+
+        const redirectUrl = '/index_hmcs';
+        await router.push(redirectUrl);
+        loading.value = false;
+      } else {
+        loading.value = false;
+        // 重新获取验证码
+        if (captchaEnabled.value) {
+          await getCode();
+        }
+      }
+    } else {
+      console.log('error submit!', fields);
+    }
+  });
+};
+
+/**
+ * 获取验证码
+ */
+const getCode = async () => {
+  const res = await getCodeImg();
+  const { data } = res;
+  captchaEnabled.value = data.captchaEnabled === undefined ? true : data.captchaEnabled;
+  if (captchaEnabled.value) {
+    codeUrl.value = 'data:image/gif;base64,' + data.img;
+    loginForm.value.uuid = data.uuid;
+  }
+};
+
+const getLoginData = () => {
+  const tenantId = localStorage.getItem('tenantId');
+  const username = localStorage.getItem('username');
+  const password = localStorage.getItem('password');
+  const rememberMe = localStorage.getItem('rememberMe');
+  loginForm.value = {
+    tenantId: tenantId === null ? String(loginForm.value.tenantId) : tenantId,
+    username: username === null ? String(loginForm.value.username) : username,
+    password: password === null ? String(loginForm.value.password) : String(password),
+    rememberMe: rememberMe === null ? false : Boolean(rememberMe)
+  } as LoginData;
+};
+
+/**
+ * 获取租户列表
+ */
+const initTenantList = async () => {
+  const { data } = await getTenantList();
+  tenantEnabled.value = data.tenantEnabled === undefined ? true : data.tenantEnabled;
+  if (tenantEnabled.value) {
+    tenantList.value = data.voList;
+    if (tenantList.value != null && tenantList.value.length !== 0) {
+      loginForm.value.tenantId = tenantList.value[0].tenantId;
+    }
+  }
+};
+
+onMounted(() => {
+  getCode();
+  initTenantList();
+  getLoginData();
+});
+</script>
+
+<style lang="scss" scoped>
+.login {
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  height: 100%;
+  background-image: url('../assets/images/login-background.jpg');
+  background-size: cover;
+}
+
+.title {
+  margin: 0px auto 30px auto;
+  text-align: center;
+  color: #707070;
+}
+
+.login-form {
+  border-radius: 6px;
+  background: #ffffff;
+  width: 400px;
+  padding: 25px 25px 5px 25px;
+
+  .el-input {
+    height: 40px;
+
+    input {
+      height: 40px;
+    }
+  }
+
+  .input-icon {
+    height: 39px;
+    width: 14px;
+    margin-left: 0px;
+  }
+}
+
+.login-tip {
+  font-size: 13px;
+  text-align: center;
+  color: #bfbfbf;
+}
+
+.login-code {
+  width: 33%;
+  height: 40px;
+  float: right;
+
+  img {
+    cursor: pointer;
+    vertical-align: middle;
+  }
+}
+
+.el-login-footer {
+  height: 40px;
+  line-height: 40px;
+  position: fixed;
+  bottom: 0;
+  width: 100%;
+  text-align: center;
+  color: #fff;
+  font-family: Arial, serif;
+  font-size: 12px;
+  letter-spacing: 1px;
+}
+
+.login-code-img {
+  height: 40px;
+  padding-left: 12px;
+}
+</style>

File diff suppressed because it is too large
+ 271 - 0
vite.config.ts.timestamp-1733470278444-ab92163e8f6d2.mjs