6 Commits 3d40839fcc ... 57a2250f27

Author SHA1 Message Date
  liujing 57a2250f27 commit message 2 weeks ago
  liujing c529802b61 commit message 2 weeks ago
  liujing cd57c441e1 commit message 2 weeks ago
  liujing 277304a236 首页代码提交 2 weeks ago
  wanghao 02c2757732 merge 2 weeks ago
  liujing e1c5a28270 feat: 更新代码 2 weeks ago
41 changed files with 1867 additions and 282 deletions
  1. 2 1
      .env.test
  2. 1 1
      eslint.config.js
  3. 0 1
      public/favicon.svg
  4. BIN
      public/logo.png
  5. BIN
      src/assets/home/app_logo.png
  6. BIN
      src/assets/home/home.mp4
  7. 2 1
      src/enum/index.ts
  8. 31 34
      src/hooks/common/table.ts
  9. 2 1
      src/layouts/modules/global-header/components/user-avatar.vue
  10. 2 2
      src/locales/langs/zh-cn.ts
  11. 2 0
      src/router/elegant/imports.ts
  12. 30 0
      src/router/elegant/routes.ts
  13. 3 0
      src/router/elegant/transform.ts
  14. 6 0
      src/service/api/auth.ts
  15. 51 0
      src/service/api/home.ts
  16. 1 0
      src/service/api/index.ts
  17. 102 2
      src/service/request/index.ts
  18. 22 0
      src/store/modules/alias/index.ts
  19. 15 7
      src/store/modules/auth/index.ts
  20. 1 0
      src/store/modules/auth/shared.ts
  21. 1 1
      src/store/modules/route/custom.ts
  22. 0 2
      src/store/modules/route/index.ts
  23. 24 0
      src/typings/api.d.ts
  24. 1 1
      src/typings/app.d.ts
  25. 4 0
      src/typings/components.d.ts
  26. 7 0
      src/typings/elegant-router.d.ts
  27. 26 1
      src/typings/storage.d.ts
  28. 12 10
      src/views/admin/sys-dept/modules/user-resetpwd-dialog.vue
  29. 49 29
      src/views/admin/sys-role/modules/role-modifypermis-modal.vue
  30. 12 10
      src/views/admin/sys-user/modules/user-resetpwd-dialog.vue
  31. 494 0
      src/views/business-analysis/system-space/index.vue
  32. 52 32
      src/views/manage/menu/index.vue
  33. 36 32
      src/views/manage/role/index.vue
  34. 1 1
      src/views/manage/role/modules/role-modifypermis-modal.vue
  35. 60 29
      src/views/manage/role/modules/role-operate-drawer.vue
  36. 41 31
      src/views/manage/user/index.vue
  37. 78 51
      src/views/manage/user/modules/user-operate-drawer.vue
  38. 1 1
      src/views/manage/user/modules/user-resetpwd-dialog.vue
  39. 542 0
      src/views/new-home/index.vue
  40. 153 0
      src/views/new-home/modules/add-modal.vue
  41. 0 1
      vite.config.ts

+ 2 - 1
.env.test

@@ -3,7 +3,8 @@ VITE_SERVICE_BASE_URL=https://mock.apifox.cn/m1/3109515-0-default
 
 # other backend service base url, test environment
 VITE_OTHER_SERVICE_BASE_URL= `{
-  "demo": "http://observe-server.cestong.com.cn"
+  "demo": "http://observe-server.cestong.com.cn",
+  "core": "http://observe-front.cestong.com.cn/core"
 }`
 
 # whether to enable http proxy when is dev mode

+ 1 - 1
eslint.config.js

@@ -22,6 +22,6 @@ export default defineConfig(
     }
   },
   {
-    ignores: ['src/views/admin/**']
+    ignores: ['src']
   }
 );

+ 0 - 1
public/favicon.svg

@@ -1 +0,0 @@
-<svg viewBox="0 0 160 160" xmlns="http://www.w3.org/2000/svg"><path d="M81.28 55.9c-.1-11.67-2.93-22.55-9.37-32.38-1-1.5-2.14-2.86-2.5-4.71a8.1 8.1 0 014-8.61 7.89 7.89 0 019.3 1.23 35.999 35.999 0 015.9 8.83 75.18 75.18 0 018.44 28.58 83.211 83.211 0 01-5.23 36.74 102.983 102.983 0 01-3 7.28 1.2 1.2 0 000 1.41c9.58 13.3 21.76 23 37.85 27.24a54.37 54.37 0 0019.68 1.57 7.72 7.72 0 018.36 6.9 7.903 7.903 0 01-6.7 9 64.744 64.744 0 01-23-1.33 77.68 77.68 0 01-36.93-19.88 93.628 93.628 0 01-11.91-13.71 2.18 2.18 0 00-2.3-1.06 72.744 72.744 0 00-27.38 7.55c-11.6 6-20.67 14.58-26.4 26.45a10.134 10.134 0 01-3.7 4.7 8 8 0 01-9.19-.7 7.86 7.86 0 01-2.36-9.28 60.324 60.324 0 018.72-14.52c12.2-15.43 28.21-24.59 47.32-28.57A85.085 85.085 0 0173.07 87c.524.015 1-.307 1.18-.8a76.06 76.06 0 006.53-22.3c.351-2.652.518-5.325.5-8z" fill="#646cff"/><path d="M136.26 108.34a44.742 44.742 0 01-11.13-2.87 46.108 46.108 0 01-19.66-13.76 8 8 0 015.72-13.22 7.93 7.93 0 016.54 2.93 33.27 33.27 0 0018.87 10.75c1.546.155 3.058.553 4.48 1.18a8.08 8.08 0 013.84 9.21c-.92 3.52-4.13 5.81-8.66 5.78zm-80.6-75.02a7.61 7.61 0 016.64 5 49.139 49.139 0 013.64 17 46.33 46.33 0 01-2.46 17.28c-2 5.77-8.24 7.79-12.89 4.15a8.1 8.1 0 01-2.39-9 31.679 31.679 0 001.68-12.36 35.77 35.77 0 00-2.43-11c-2.1-5.45 1.75-11.07 8.21-11.07zm22.26 93.25a8 8 0 01-6.68 7.86 32.88 32.88 0 00-19.7 12.19 8.13 8.13 0 01-11.21 1.62 8 8 0 01-1.41-11.58A51.043 51.043 0 0154 123.81a45.842 45.842 0 0114-5.1c5.35-1.04 9.91 2.56 9.92 7.86z" fill="#646cff"/></svg>

BIN
public/logo.png


BIN
src/assets/home/app_logo.png


BIN
src/assets/home/home.mp4


+ 2 - 1
src/enum/index.ts

@@ -3,5 +3,6 @@ export enum SetupStoreId {
   Theme = 'theme-store',
   Auth = 'auth-store',
   Route = 'route-store',
-  Tab = 'tab-store'
+  Tab = 'tab-store',
+  Alias = 'alias-store'
 }

+ 31 - 34
src/hooks/common/table.ts

@@ -1,5 +1,3 @@
-import { $t } from '@/locales';
-import { useAppStore } from '@/store/modules/app';
 import { useBoolean, useHookTable } from '@sa/hooks';
 import { useElementSize } from '@vueuse/core';
 import type { TablePaginationConfig } from 'ant-design-vue';
@@ -7,6 +5,8 @@ import type { TableRowSelection } from 'ant-design-vue/es/table/interface';
 import { cloneDeep } from 'lodash-es';
 import type { MaybeRef, Ref } from 'vue';
 import { computed, effectScope, onScopeDispose, reactive, ref, shallowRef, toValue, watch } from 'vue';
+import { useAppStore } from '@/store/modules/app';
+import { $t } from '@/locales';
 
 type TableData = AntDesign.TableData;
 type GetTableData<A extends AntDesign.TableApiFn> = AntDesign.GetTableData<A>;
@@ -34,31 +34,31 @@ export function useTable<A extends AntDesign.TableApiFn>(config: AntDesign.AntDe
     apiParams,
     columns: config.columns,
     transformer: res => {
-      const { list = [], pageSize = 10, total = 0, count = 0, pageIndex =1  } = res.data || {};
-      //此处为处理菜单列表数据格式不统一的问题,详见todoList
-      const data  = res.data
-      let recordsWithIndex = []
-      if(list.length > 0){
-        recordsWithIndex = list.map((item:any, index:any) => {
+      const { list = [], pageSize = 10, total = 0, count = 0, pageIndex = 1 } = res.data || {};
+      // 此处为处理菜单列表数据格式不统一的问题,详见todoList
+      const data = res.data;
+      let recordsWithIndex = [];
+      if (list.length > 0) {
+        recordsWithIndex = list.map((item: any, index: any) => {
           return {
             ...item,
-            index: (pageIndex - 1) * pageSize + index + 1,
-          };
-        });
-      }else{
-        //此处为处理菜单列表数据格式不统一的问题,详见todoList
-        if(Array.isArray(data))
-        recordsWithIndex = data.map((item:any, index:any) => {
-          return {
-            ...item,
-            index: (pageIndex - 1) * pageSize + index + 1,
+            index: (pageIndex - 1) * pageSize + index + 1
           };
         });
+      } else {
+        // 此处为处理菜单列表数据格式不统一的问题,详见todoList
+        if (Array.isArray(data))
+          recordsWithIndex = data.map((item: any, index: any) => {
+            return {
+              ...item,
+              index: (pageIndex - 1) * pageSize + index + 1
+            };
+          });
       }
       return {
         data: recordsWithIndex,
-        pageIndex:  pageIndex,
-        pageSize: pageSize,
+        pageIndex,
+        pageSize,
         total: total || count
       };
     },
@@ -92,9 +92,9 @@ export function useTable<A extends AntDesign.TableApiFn>(config: AntDesign.AntDe
       return filteredColumns;
     },
     onFetched: async transformed => {
-      const {  pageSize, total, pageIndex } = transformed;
+      const { pageSize, total, pageIndex } = transformed;
       updatePagination({
-        pageIndex: pageIndex,
+        pageIndex,
         pageSize,
         total
       });
@@ -104,19 +104,19 @@ export function useTable<A extends AntDesign.TableApiFn>(config: AntDesign.AntDe
 
   const pagination: TablePaginationConfig = reactive({
     pageIndex: 1,
-    current:1,
+    current: 1,
     pageSize: 10,
     showSizeChanger: true,
     pageSizeOptions: ['10', '15', '20', '25', '30'],
     total: 0,
-    showTotal:total => `共 ${total} 条`,
+    showTotal: total => `共 ${total} 条`,
     onChange: async (current: number, size: number) => {
-      pagination.pageIndex = current
-      pagination.pageSize = size
-      pagination.current = current
+      pagination.pageIndex = current;
+      pagination.pageSize = size;
+      pagination.current = current;
       updateSearchParams({
         pageIndex: pagination.pageIndex,
-        pageSize: pagination.pageSize,
+        pageSize: pagination.pageSize
       });
 
       getData();
@@ -143,18 +143,16 @@ export function useTable<A extends AntDesign.TableApiFn>(config: AntDesign.AntDe
    * @param pageNum the page number. default is 1
    */
   async function getDataByPage(pageNum: number = 1, isNeedPageParams: boolean = true) {
-
     updatePagination({
       pageIndex: pageNum || pagination.current
     });
-    if (isNeedPageParams){
+    if (isNeedPageParams) {
       updateSearchParams({
         pageIndex: pagination.current || pageNum,
         pageSize: pagination.pageSize!
       });
     }
 
-
     await getData();
   }
 
@@ -203,9 +201,9 @@ export function useTableOperate<T extends TableData = TableData>(data: Ref<T[]>,
   /** the editing row data */
   const editingData: Ref<T | null> = ref(null);
 
-  function handleEdit(id: T['id'], keyName:string) {
+  function handleEdit(id: T['id'], keyName: string) {
     operateType.value = 'edit';
-    const findItem = data.value.find((item:any) => item[keyName] === id) || null;
+    const findItem = data.value.find((item: any) => item[keyName] === id) || null;
     editingData.value = cloneDeep(findItem);
     openDrawer();
   }
@@ -239,7 +237,6 @@ export function useTableOperate<T extends TableData = TableData>(data: Ref<T[]>,
   async function onDeleted() {
     window.$message?.success($t('common.deleteSuccess'));
 
-
     await getData();
   }
 

+ 2 - 1
src/layouts/modules/global-header/components/user-avatar.vue

@@ -22,7 +22,8 @@ function logout() {
     okText: $t('common.confirm'),
     cancelText: $t('common.cancel'),
     onOk: () => {
-      authStore.resetStore();
+      // authStore.resetStore();
+      authStore.logout();
     }
   });
 }

+ 2 - 2
src/locales/langs/zh-cn.ts

@@ -1,6 +1,6 @@
 const local: App.I18n.Schema = {
   system: {
-    title: 'Observe 管理系统',
+    title: '可观测平台',
     updateTitle: '系统版本更新通知',
     updateContent: '检测到系统有新版本发布,是否立即刷新页面?',
     updateConfirm: '立即刷新',
@@ -17,7 +17,7 @@ const local: App.I18n.Schema = {
     check: '勾选',
     columnSetting: '列设置',
     config: '配置',
-    confirm: '确',
+    confirm: '确',
     delete: '删除',
     deleteSuccess: '删除成功',
     confirmDelete: '确认删除吗?',

+ 2 - 0
src/router/elegant/imports.ts

@@ -25,6 +25,7 @@ export const views: Record<LastLevelRouteKey, RouteComponent | (() => Promise<Ro
   "admin_sys-menu": () => import("@/views/admin/sys-menu/index.vue"),
   "admin_sys-role": () => import("@/views/admin/sys-role/index.vue"),
   "admin_sys-user": () => import("@/views/admin/sys-user/index.vue"),
+  "business-analysis_system-space": () => import("@/views/business-analysis/system-space/index.vue"),
   "function_hide-child_one": () => import("@/views/function/hide-child/one/index.vue"),
   "function_hide-child_three": () => import("@/views/function/hide-child/three/index.vue"),
   "function_hide-child_two": () => import("@/views/function/hide-child/two/index.vue"),
@@ -40,5 +41,6 @@ export const views: Record<LastLevelRouteKey, RouteComponent | (() => Promise<Ro
   manage_user: () => import("@/views/manage/user/index.vue"),
   "multi-menu_first_child": () => import("@/views/multi-menu/first_child/index.vue"),
   "multi-menu_second_child_home": () => import("@/views/multi-menu/second_child_home/index.vue"),
+  "new-home": () => import("@/views/new-home/index.vue"),
   "user-center": () => import("@/views/user-center/index.vue"),
 };

+ 30 - 0
src/router/elegant/routes.ts

@@ -97,6 +97,26 @@ export const generatedRoutes: GeneratedRoute[] = [
       }
     ]
   },
+  {
+    name: 'business-analysis',
+    path: '/business-analysis',
+    component: 'layout.base',
+    meta: {
+      title: 'business-analysis',
+      i18nKey: 'route.business-analysis'
+    },
+    children: [
+      {
+        name: 'business-analysis_system-space',
+        path: '/business-analysis/system-space',
+        component: 'view.business-analysis_system-space',
+        meta: {
+          title: 'business-analysis_system-space',
+          i18nKey: 'route.business-analysis_system-space'
+        }
+      }
+    ]
+  },
   {
     name: 'function',
     path: '/function',
@@ -377,6 +397,16 @@ export const generatedRoutes: GeneratedRoute[] = [
       }
     ]
   },
+  {
+    name: 'new-home',
+    path: '/new-home',
+    component: 'layout.blank$view.new-home',
+    meta: {
+      title: 'new-home',
+      constant: true,
+      i18nKey: 'route.new-home'
+    }
+  },
   {
     name: 'user-center',
     path: '/user-center',

+ 3 - 0
src/router/elegant/transform.ts

@@ -182,6 +182,8 @@ const routeMap: RouteMap = {
   "admin_sys-menu": "/admin/sys-menu",
   "admin_sys-role": "/admin/sys-role",
   "admin_sys-user": "/admin/sys-user",
+  "business-analysis": "/business-analysis",
+  "business-analysis_system-space": "/business-analysis/system-space",
   "function": "/function",
   "function_hide-child": "/function/hide-child",
   "function_hide-child_one": "/function/hide-child/one",
@@ -206,6 +208,7 @@ const routeMap: RouteMap = {
   "multi-menu_second": "/multi-menu/second",
   "multi-menu_second_child": "/multi-menu/second/child",
   "multi-menu_second_child_home": "/multi-menu/second/child/home",
+  "new-home": "/new-home",
   "user-center": "/user-center"
 };
 

+ 6 - 0
src/service/api/auth.ts

@@ -32,6 +32,12 @@ export function fetchLoginDemo(data: object) {
     data
   });
 }
+export function fetchLoginOut(data: string | null) {
+  return baseRequest<Api.Auth.LoginToken>({
+    url: '/api/v1/logout',
+    method: 'post'
+  });
+}
 
 /** Get user info */
 export function fetchGetUserInfo() {

+ 51 - 0
src/service/api/home.ts

@@ -0,0 +1,51 @@
+import { baseRequest, coreRequest } from '../request';
+export function fetchListApps(query?: Api.Home.commonQuery) {
+  return baseRequest({
+    url: '/api/v1/ot-apps',
+    method: 'get',
+    params: query
+  });
+}
+// 首页-业务搜索
+export function searchBizName(query?: Api.Home.bizSearchQuery) {
+  return coreRequest({
+    url: '/v1/biz/search',
+    method: 'get',
+    params: query
+  });
+}
+// 首页- 搜索url
+export function searchServiceUrl(query?: Api.Home.urlSearchQuery) {
+  return baseRequest({
+    url: '/api/v1/url-mapping/match',
+    method: 'get',
+    params: query
+  });
+}
+// 修改应用
+export function updateOtApps(id: any, data: Api.Home.addAliasParams) {
+  return baseRequest({
+    url: `/api/v1/ot-apps/${id}`,
+    method: 'put',
+    data
+  });
+}
+// 新增应用
+export function addOtApps(data: Api.Home.addAliasParams) {
+  return baseRequest({
+    url: '/api/v1/ot-apps',
+    method: 'post',
+    data
+  });
+}
+// 上传照片
+export function fetchUploadFile(data: any) {
+  return baseRequest({
+    headers: {
+      'Content-Type': 'multipart/form-data'
+    },
+    url: '/api/v1/public/uploadFile',
+    method: 'post',
+    data
+  });
+}

+ 1 - 0
src/service/api/index.ts

@@ -1,3 +1,4 @@
 export * from './auth';
 export * from './route';
 export * from './system-manage';
+export * from './home';

+ 102 - 2
src/service/request/index.ts

@@ -8,8 +8,8 @@ import { handleRefreshToken, showErrorMsg } from './shared';
 import type { RequestInstanceState } from './type';
 
 const isHttpProxy = import.meta.env.DEV && import.meta.env.VITE_HTTP_PROXY === 'Y';
-const { baseURL, otherBaseURL } = getServiceBaseURL(import.meta.env, isHttpProxy);
 
+const { baseURL, otherBaseURL } = getServiceBaseURL(import.meta.env, isHttpProxy);
 export const request = createFlatRequest<App.Service.Response, RequestInstanceState>(
   {
     baseURL,
@@ -126,7 +126,6 @@ export const request = createFlatRequest<App.Service.Response, RequestInstanceSt
     }
   }
 );
-
 export const baseRequest = createRequest<App.Service.DemoResponse>(
   {
     baseURL: otherBaseURL.demo
@@ -150,6 +149,107 @@ export const baseRequest = createRequest<App.Service.DemoResponse>(
     async onBackendFail(response: { data: { msg: any; code: string }; config: AxiosRequestConfig<any> }, instance) {
       const authStore = useAuthStore();
 
+      const responseCode = `${response.data.code}`;
+
+      function handleLogout() {
+        authStore.resetStore();
+      }
+
+      function logoutAndCleanup() {
+        handleLogout();
+        window.removeEventListener('beforeunload', handleLogout);
+
+        request.state.errMsgStack = request.state.errMsgStack.filter((msg: any) => msg !== response.data.msg);
+      }
+
+      // when the backend response code is in `logoutCodes`, it means the user will be logged out and redirected to login page
+      const logoutCodes = import.meta.env.VITE_SERVICE_LOGOUT_CODES?.split(',') || [];
+      if (logoutCodes.includes(responseCode)) {
+        handleLogout();
+        return null;
+      }
+
+      // when the backend response code is in `modalLogoutCodes`, it means the user will be logged out by displaying a modal
+      const modalLogoutCodes = import.meta.env.VITE_SERVICE_MODAL_LOGOUT_CODES?.split(',') || [];
+      if (modalLogoutCodes.includes(responseCode) && !request.state.errMsgStack?.includes(response.data.msg)) {
+        request.state.errMsgStack = [...(request.state.errMsgStack || []), response.data.msg];
+
+        // prevent the user from refreshing the page
+        window.addEventListener('beforeunload', handleLogout);
+
+        window.$modal?.error({
+          title: $t('common.error'),
+          content: response.data.msg,
+          okText: $t('common.confirm'),
+          maskClosable: false,
+          onOk() {
+            logoutAndCleanup();
+          },
+          onCancel() {
+            logoutAndCleanup();
+          }
+        });
+
+        return null;
+      }
+
+      // when the backend response code is in `expiredTokenCodes`, it means the token is expired, and refresh token
+      // the api `refreshToken` can not return error code in `expiredTokenCodes`, otherwise it will be a dead loop, should return `logoutCodes` or `modalLogoutCodes`
+      const expiredTokenCodes = import.meta.env.VITE_SERVICE_EXPIRED_TOKEN_CODES?.split(',') || [];
+      if (expiredTokenCodes.includes(responseCode) && !request.state.isRefreshingToken) {
+        request.state.isRefreshingToken = true;
+
+        const refreshConfig = await handleRefreshToken(response.config);
+
+        request.state.isRefreshingToken = false;
+
+        if (refreshConfig) {
+          return instance.request(refreshConfig) as Promise<AxiosResponse>;
+        }
+      }
+
+      return null;
+    },
+    transformBackendResponse(response: { data: any }) {
+      return response.data;
+    },
+    onError(error: { message: any; code: any; response: { data: { message: any } } }) {
+      // when the request is fail, you can show error message
+
+      let message = error.message;
+
+      // show backend error message
+      if (error.code === BACKEND_ERROR_CODE) {
+        message = error.response?.data?.message || message;
+      }
+
+      window.$message?.error(message);
+    }
+  }
+);
+export const coreRequest = createRequest<App.Service.DemoResponse>(
+  {
+    baseURL: otherBaseURL.core
+  },
+  {
+    async onRequest(config: { headers: any }) {
+      const { headers } = config;
+
+      // set token
+      const token = localStg.get('token');
+      const Authorization = token ? `Bearer ${token}` : null;
+      Object.assign(headers, { Authorization });
+
+      return config;
+    },
+    isBackendSuccess(response: { data: { code: number } }) {
+      // when the backend response code is "200", it means the request is success
+      // you can change this logic by yourself
+      return response.data.code === 200;
+    },
+    async onBackendFail(response: { data: { msg: any; code: string }; config: AxiosRequestConfig<any> }, instance) {
+      const authStore = useAuthStore();
+
       console.log('onBackendFail', response.data.code);
       const responseCode = `${response.data.code}`;
 

+ 22 - 0
src/store/modules/alias/index.ts

@@ -0,0 +1,22 @@
+import { defineStore } from 'pinia';
+import { SetupStoreId } from '@/enum';
+import { localStg } from '@/utils/storage';
+import { reactive } from 'vue';
+export const useAliasStore = defineStore(SetupStoreId.Alias, ()=>{
+  const aliasState = reactive({
+    currentAlias: '',
+    allAliasArr: []
+  })
+  function setCurrentAlias(data: string) {
+    aliasState.currentAlias = data
+    localStg.set('currentAlias', data)
+  }
+  function setAllAliasArr(data:any){
+    aliasState.allAliasArr = data
+  }
+  return {
+    aliasState,
+    setCurrentAlias,
+    setAllAliasArr
+  }
+});

+ 15 - 7
src/store/modules/auth/index.ts

@@ -5,7 +5,7 @@ import { useRoute } from 'vue-router';
 import { SetupStoreId } from '@/enum';
 import { useRouterPush } from '@/hooks/common/router';
 import { $t } from '@/locales';
-import { fetchGetUserInfoDemo, fetchLoginDemo } from '@/service/api';
+import { fetchGetUserInfoDemo, fetchLoginDemo, fetchLoginOut } from '@/service/api';
 // import { fetchLogin, fetchGetUserInfo } from '@/service/api';
 import { localStg } from '@/utils/storage';
 import { useRouteStore } from '../route';
@@ -53,7 +53,14 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
     tabStore.cacheTabs();
     routeStore.resetStore();
   }
-
+  async function logout() {
+    const token = localStg.get('token');
+    const res = await fetchLoginOut(token)
+    if (res && res.code == 200){
+      toLogin();
+      resetStore();
+    }
+  }
   /**
    * Login
    *
@@ -63,17 +70,17 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
    * @param uuid uuid
    * @param [redirect=true] Whether to redirect after login. Default is `true`
    */
-  async function login(params: object, redirect = true) {
+  async function login(params: any, redirect = true) {
     startLoading();
     // 待完善 此处和目前业务逻辑不同,关于refreshToken需后续处理
     const { token: currentToken, currentAuthority: refreshToken, error } = await fetchLoginDemo(params);
     // const { data: loginToken, error } = await fetchLogin('Soybean', '123456');
-    console.log('error-----', error);
     if (!error) {
-      const pass = await loginByToken({ token: currentToken, refreshToken, data: null, code: 200 });
+      const pass = await loginByToken({ token: currentToken, refreshToken, data: null, code: 200, userName: params.username });
       // const pass = await loginByToken(loginToken);
 
       if (pass) {
+
         await routeStore.initAuthRoute();
 
         if (redirect) {
@@ -98,7 +105,7 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
     // 1. stored in the localStorage, the later requests need it in headers
     localStg.set('token', loginToken.token);
     localStg.set('refreshToken', loginToken.refreshToken);
-
+    localStg.set('userName', loginToken.userName);
     // 2. get user info
     const pass = await getUserInfo();
 
@@ -147,6 +154,7 @@ export const useAuthStore = defineStore(SetupStoreId.Auth, () => {
     loginLoading,
     resetStore,
     login,
-    initUserInfo
+    initUserInfo,
+    logout
   };
 });

+ 1 - 0
src/store/modules/auth/shared.ts

@@ -9,4 +9,5 @@ export function getToken() {
 export function clearAuthStorage() {
   localStg.remove('token');
   localStg.remove('refreshToken');
+  localStg.remove('userName');
 }

+ 1 - 1
src/store/modules/route/custom.ts

@@ -59,5 +59,5 @@ export function transformToTargetData(data: DataItem[]): {
   routes: ElegantConstRoute[];
   home: LastLevelRouteKey;
 } {
-  return { routes: transData(data), home: 'admin_sys-user' };
+  return { routes: transData(data), home: 'new-home' };
 }

+ 0 - 2
src/store/modules/route/index.ts

@@ -340,9 +340,7 @@ export const useRouteStore = defineStore(SetupStoreId.Route, () => {
       const { authRoutes: staticAuthRoutes } = createStaticRoutes();
       return isRouteExistByRouteName(routeName, staticAuthRoutes);
     }
-
     const { data } = await fetchIsRouteExist(routeName);
-
     return data;
   }
 

+ 24 - 0
src/typings/api.d.ts

@@ -60,6 +60,7 @@ declare namespace Api {
       refreshToken: string;
       data: any;
       code: number;
+      userName: string;
     }
 
     interface UserInfo {
@@ -263,4 +264,27 @@ declare namespace Api {
       children?: MenuTree[];
     };
   }
+  namespace Home {
+    interface commonQuery {
+      pageIndex: number;
+      pageSize: number;
+    }
+    interface bizSearchQuery {
+      name: string;
+      pageIndex: number;
+      pageSize: number;
+    }
+    interface urlSearchQuery extends Omit<bizSearchQuery, 'name'> {
+      url: string;
+    }
+    interface addAliasParams {
+      alias?: string;
+      contractInfo?: string;
+      contractPhone?: string;
+      desc?: string;
+      imgUrl?: string;
+      name?: string;
+      // id?: number | string | null;
+    }
+  }
 }

+ 1 - 1
src/typings/app.d.ts

@@ -652,7 +652,7 @@ declare namespace App {
   /** Service namespace */
   namespace Service {
     /** Other baseURL key */
-    type OtherBaseURLKey = 'demo';
+    type OtherBaseURLKey = 'demo' | 'core';
 
     interface ServiceConfigItem {
       /** The backend service base url */

+ 4 - 0
src/typings/components.d.ts

@@ -7,6 +7,7 @@ export {}
 /* prettier-ignore */
 declare module 'vue' {
   export interface GlobalComponents {
+    AAutoComplete: typeof import('ant-design-vue/es')['AutoComplete']
     ABreadcrumb: typeof import('ant-design-vue/es')['Breadcrumb']
     ABreadcrumbItem: typeof import('ant-design-vue/es')['BreadcrumbItem']
     AButton: typeof import('ant-design-vue/es')['Button']
@@ -50,14 +51,17 @@ declare module 'vue' {
     ATransfer: typeof import('ant-design-vue/es')['Transfer']
     ATree: typeof import('ant-design-vue/es')['Tree']
     ATreeSelect: typeof import('ant-design-vue/es')['TreeSelect']
+    AUpload: typeof import('ant-design-vue/es')['Upload']
     BetterScroll: typeof import('./../components/custom/better-scroll.vue')['default']
     ButtonIcon: typeof import('./../components/custom/button-icon.vue')['default']
     CountTo: typeof import('./../components/custom/count-to.vue')['default']
     DarkModeContainer: typeof import('./../components/common/dark-mode-container.vue')['default']
     ExceptionBase: typeof import('./../components/common/exception-base.vue')['default']
     FullScreen: typeof import('./../components/common/full-screen.vue')['default']
+    IconAntDesignCaretDownOutlined: typeof import('~icons/ant-design/caret-down-outlined')['default']
     IconAntDesignReloadOutlined: typeof import('~icons/ant-design/reload-outlined')['default']
     IconAntDesignSettingOutlined: typeof import('~icons/ant-design/setting-outlined')['default']
+    IconAntDesignSyncOutlined: typeof import('~icons/ant-design/sync-outlined')['default']
     IconCarbonAdd: typeof import('~icons/carbon/add')['default']
     IconGridiconsFullscreen: typeof import('~icons/gridicons/fullscreen')['default']
     IconGridiconsFullscreenExit: typeof import('~icons/gridicons/fullscreen-exit')['default']

+ 7 - 0
src/typings/elegant-router.d.ts

@@ -38,6 +38,8 @@ declare module "@elegant-router/types" {
     "admin_sys-menu": "/admin/sys-menu";
     "admin_sys-role": "/admin/sys-role";
     "admin_sys-user": "/admin/sys-user";
+    "business-analysis": "/business-analysis";
+    "business-analysis_system-space": "/business-analysis/system-space";
     "function": "/function";
     "function_hide-child": "/function/hide-child";
     "function_hide-child_one": "/function/hide-child/one";
@@ -62,6 +64,7 @@ declare module "@elegant-router/types" {
     "multi-menu_second": "/multi-menu/second";
     "multi-menu_second_child": "/multi-menu/second/child";
     "multi-menu_second_child_home": "/multi-menu/second/child/home";
+    "new-home": "/new-home";
     "user-center": "/user-center";
   };
 
@@ -111,12 +114,14 @@ declare module "@elegant-router/types" {
     | "500"
     | "about"
     | "admin"
+    | "business-analysis"
     | "function"
     | "home"
     | "iframe-page"
     | "login"
     | "manage"
     | "multi-menu"
+    | "new-home"
     | "user-center"
   >;
 
@@ -146,6 +151,7 @@ declare module "@elegant-router/types" {
     | "admin_sys-menu"
     | "admin_sys-role"
     | "admin_sys-user"
+    | "business-analysis_system-space"
     | "function_hide-child_one"
     | "function_hide-child_three"
     | "function_hide-child_two"
@@ -161,6 +167,7 @@ declare module "@elegant-router/types" {
     | "manage_user"
     | "multi-menu_first_child"
     | "multi-menu_second_child_home"
+    | "new-home"
     | "user-center"
   >;
 

+ 26 - 1
src/typings/storage.d.ts

@@ -8,7 +8,22 @@ declare namespace StorageType {
     //  */
     // themeSettings: App.Theme.ThemeSetting;
   }
-
+  interface appsItem {
+    alias: string;
+    id: number;
+    contractInfo?: string;
+    contractPhone?: string;
+    createBy?: number | string;
+    createdAt?: string;
+    desc?: string;
+    end_time?: number;
+    imgUrl?: string;
+    live?: boolean;
+    name?: string;
+    start_time?: number;
+    updateBy?: number;
+    updatedAt?: string;
+  }
   interface Local {
     /** The i18n language */
     lang: App.I18n.LangType;
@@ -35,5 +50,15 @@ declare namespace StorageType {
       layout: UnionKey.ThemeLayoutMode;
       siderCollapse: boolean;
     };
+    userName: string;
+    appsItem: appsItem;
+    row: {
+      method?: string;
+      service_name?: string;
+      kind?: string;
+      name?: string;
+      route?: string;
+    };
+    currentAlias: string;
   }
 }

+ 12 - 10
src/views/admin/sys-dept/modules/user-resetpwd-dialog.vue

@@ -1,8 +1,8 @@
 /* eslint-disable */
 <script setup lang="ts">
+import { reactive, watch } from 'vue';
 import { useAntdForm, useFormRules } from '@/hooks/common/form';
 import { fetchRestPwsUser } from '@/service/api';
-import { reactive, watch } from 'vue';
 defineOptions({
   name: 'UserResetPwdDialog'
 });
@@ -10,21 +10,21 @@ const visible = defineModel<boolean>('dialogVisible', {
   default: false
 });
 interface Props {
-  userId: number | null,
-  userName: string | null
+  userId: number | null;
+  userName: string | null;
 }
 const props = defineProps<Props>();
 const model = reactive(createDefaultModel());
 const { formRef, validate, resetFields } = useAntdForm();
 const { formRules } = useFormRules();
-function createDefaultModel(){
+function createDefaultModel() {
   return {
     password: '',
     userId: props.userId
   };
 }
 const rules: Record<'password', App.Global.FormRule> = {
-  password: formRules.pwd,
+  password: formRules.pwd
 };
 
 function closeDrawer() {
@@ -32,10 +32,10 @@ function closeDrawer() {
 }
 async function handleSubmit() {
   await validate();
-  model.userId = props.userId
+  model.userId = props.userId;
 
-  const res = await fetchRestPwsUser(model)
-  if (res.code === 200){
+  const res = await fetchRestPwsUser(model);
+  if (res.code === 200) {
     window.$message?.success(res.msg);
   }
   closeDrawer();
@@ -46,12 +46,13 @@ watch(visible, () => {
   }
 });
 </script>
+
 <template>
-  <AModal v-model:open="visible" title="重置密码" width="420px">
+  <AModal v-model:open="visible" title="重置密码" class="w-480px">
     <!-- <p style="margin-bottom: 10px;"></p> -->
     <AForm ref="formRef" layout="vertical" :model="model" :rules="rules">
       <AFormItem :label="`请输入${userName}的新密码`" name="password">
-        <AInput v-model:value="model.password" placeholder="请输入新密码" allowClear/>
+        <AInput v-model:value="model.password" placeholder="请输入新密码" allow-clear />
       </AFormItem>
     </AForm>
     <template #footer>
@@ -62,4 +63,5 @@ watch(visible, () => {
     </template>
   </AModal>
 </template>
+
 <style scoped></style>

+ 49 - 29
src/views/admin/sys-role/modules/role-modifypermis-modal.vue

@@ -1,9 +1,9 @@
 /* eslint-disable */
 <script setup lang="ts">
+import { reactive, ref, watch } from 'vue';
 import { useAntdForm, useFormRules } from '@/hooks/common/form';
 import { $t } from '@/locales';
 import { fetchDeptTreeByRole, fetchModifyRolePersion } from '@/service/api';
-import { reactive, ref, watch } from 'vue';
 defineOptions({
   name: 'RoleModifyPermisModal'
 });
@@ -31,7 +31,7 @@ const dataScopeOptions = ref([
     value: '5',
     label: '仅本人数据权限'
   }
-])
+]);
 interface Emits {
   (e: 'submitted'): void;
 }
@@ -44,8 +44,8 @@ const props = defineProps({
 });
 const { formRef, validate } = useAntdForm();
 const { defaultRequiredRule } = useFormRules();
-const treeData = ref([])
-type Model = Pick<Api.SystemManage.Role,  'roleName'| 'roleKey'| 'dataScope'| 'deptIds'>;
+const treeData = ref([]);
+type Model = Pick<Api.SystemManage.Role, 'roleName' | 'roleKey' | 'dataScope' | 'deptIds'>;
 let model: Model = reactive(createDefaultModel());
 function createDefaultModel(): Model {
   return {
@@ -62,63 +62,82 @@ const rules: Record<RuleKey, App.Global.FormRule> = {
   dataScope: defaultRequiredRule
 };
 
-
-
 function closeDrawer() {
   visible.value = false;
 }
-async function handleSubmit(){
-  await validate()
-  const res = await fetchModifyRolePersion(model)
-  if (res.code === 200){
+async function handleSubmit() {
+  await validate();
+  const res = await fetchModifyRolePersion(model);
+  if (res.code === 200) {
     window.$message?.success($t('common.updateSuccess'));
   } else {
     window.$message?.error(res.msg);
   }
-  closeDrawer()
+  closeDrawer();
   emit('submitted');
 }
-function handelChange(){
+function handelChange() {
   if (treeData.value.length === 0) {
-    getDeptTreeselect()
+    getDeptTreeselect();
   }
 }
 /** 查询部门树结构 */
 async function getDeptTreeselect() {
-  const res = await fetchDeptTreeByRole(props.rowData.roleId)
+  const res = await fetchDeptTreeByRole(props.rowData.roleId);
   if (res.code === 200) {
-    treeData.value = res.data.depts || []
-    model.deptIds = res.data.checkedKeys || []
+    treeData.value = res.data.depts || [];
+    model.deptIds = res.data.checkedKeys || [];
   }
 }
 watch(visible, () => {
   if (visible.value) {
-    model = props.rowData
-    if(model.dataScope === '2') {
-      getDeptTreeselect()
+    model = props.rowData;
+    if (model.dataScope === '2') {
+      getDeptTreeselect();
     }
   }
 });
 </script>
+
 <template>
-  <AModal v-model:open="visible" title="分配数据权限" width="420px">
+  <AModal v-model:open="visible" title="分配数据权限" class="w-480px">
     <AForm ref="formRef" layout="vertical" :model="model" :rules="rules">
       <AFormItem :label="$t('page.manage.role.roleName')" name="roleName">
-        <AInput v-model:value="model.roleName" :placeholder="$t('page.manage.role.form.roleName')" :disabled="true" allowClear/>
+        <AInput
+          v-model:value="model.roleName"
+          :placeholder="$t('page.manage.role.form.roleName')"
+          :disabled="true"
+          allow-clear
+        />
       </AFormItem>
-      <AFormItem :label="$t('page.manage.role.roleKey')" name="roleKey" >
-        <AInput v-model:value.trim="model.roleKey" :placeholder="$t('page.manage.role.form.roleKey')" :disabled="true" allowClear/>
+      <AFormItem :label="$t('page.manage.role.roleKey')" name="roleKey">
+        <AInput
+          v-model:value.trim="model.roleKey"
+          :placeholder="$t('page.manage.role.form.roleKey')"
+          :disabled="true"
+          allow-clear
+        />
       </AFormItem>
-      <AFormItem label="权限范围" name="dataScope" >
-        <ASelect v-model:value="model.dataScope"   :placeholder="$t('page.manage.role.form.roleStatus')" allowClear @change="handelChange">
-          <ASelectOption v-for="option in dataScopeOptions" :key="option.value" :value="option.value" >
+      <AFormItem label="权限范围" name="dataScope">
+        <ASelect
+          v-model:value="model.dataScope"
+          :placeholder="$t('page.manage.role.form.roleStatus')"
+          allow-clear
+          @change="handelChange"
+        >
+          <ASelectOption v-for="option in dataScopeOptions" :key="option.value" :value="option.value">
             {{ option.label }}
           </ASelectOption>
         </ASelect>
       </AFormItem>
-      <AFormItem label="数据权限" name="dataScope" v-if="model.dataScope === '2'">
-        <ATree v-model:checkedKeys="model.deptIds" :field-names="{children:'children', title:'label', key:'id' }" :tree-data="treeData" allowClear
-        checkable/>
+      <AFormItem v-if="model.dataScope === '2'" label="数据权限" name="dataScope">
+        <ATree
+          v-model:checkedKeys="model.deptIds"
+          :field-names="{ children: 'children', title: 'label', key: 'id' }"
+          :tree-data="treeData"
+          allow-clear
+          checkable
+        />
       </AFormItem>
     </AForm>
     <template #footer>
@@ -129,4 +148,5 @@ watch(visible, () => {
     </template>
   </AModal>
 </template>
+
 <style scoped></style>

+ 12 - 10
src/views/admin/sys-user/modules/user-resetpwd-dialog.vue

@@ -1,8 +1,8 @@
 /* eslint-disable */
 <script setup lang="ts">
+import { reactive, watch } from 'vue';
 import { useAntdForm, useFormRules } from '@/hooks/common/form';
 import { fetchRestPwsUser } from '@/service/api';
-import { reactive, watch } from 'vue';
 defineOptions({
   name: 'UserResetPwdDialog'
 });
@@ -10,21 +10,21 @@ const visible = defineModel<boolean>('dialogVisible', {
   default: false
 });
 interface Props {
-  userId: number | null,
-  userName: string | null
+  userId: number | null;
+  userName: string | null;
 }
 const props = defineProps<Props>();
 const model = reactive(createDefaultModel());
 const { formRef, validate, resetFields } = useAntdForm();
 const { formRules } = useFormRules();
-function createDefaultModel(){
+function createDefaultModel() {
   return {
     password: '',
     userId: props.userId
   };
 }
 const rules: Record<'password', App.Global.FormRule> = {
-  password: formRules.pwd,
+  password: formRules.pwd
 };
 
 function closeDrawer() {
@@ -32,10 +32,10 @@ function closeDrawer() {
 }
 async function handleSubmit() {
   await validate();
-  model.userId = props.userId
+  model.userId = props.userId;
 
-  const res = await fetchRestPwsUser(model)
-  if (res.code === 200){
+  const res = await fetchRestPwsUser(model);
+  if (res.code === 200) {
     window.$message?.success(res.msg);
   }
   closeDrawer();
@@ -46,12 +46,13 @@ watch(visible, () => {
   }
 });
 </script>
+
 <template>
-  <AModal v-model:open="visible" title="重置密码" width="420px">
+  <AModal v-model:open="visible" title="重置密码" class="w-480px">
     <!-- <p style="margin-bottom: 10px;"></p> -->
     <AForm ref="formRef" layout="vertical" :model="model" :rules="rules">
       <AFormItem :label="`请输入${userName}的新密码`" name="password">
-        <AInput v-model:value="model.password" placeholder="请输入新密码" allowClear/>
+        <AInput v-model:value="model.password" placeholder="请输入新密码" allow-clear />
       </AFormItem>
     </AForm>
     <template #footer>
@@ -62,4 +63,5 @@ watch(visible, () => {
     </template>
   </AModal>
 </template>
+
 <style scoped></style>

+ 494 - 0
src/views/business-analysis/system-space/index.vue

@@ -0,0 +1,494 @@
+<script setup lang="ts">
+import { reactive, ref } from 'vue';
+</script>
+
+<template>
+  <div class="ob-basic-layout">
+    <div class="system-content">
+      <div class="system-left-content">
+        <!-- 基本信息 -->
+        <div class="system-basic-info">
+          <span class="fontSize16">基本信息</span>
+          <div>
+            <p class="service-header-p">
+              <span class="service-span-right">应用:</span>
+              <ElTooltip class="item" effect="dark" :content="appItem.name + '(' + appItem.alias + ')'" placement="top">
+                <span class="service-span-left">{{ appItem.name }} ({{ appItem.alias }})</span>
+              </ElTooltip>
+            </p>
+          </div>
+        </div>
+        <!-- 性能指标概述 -->
+        <div class="performance-box">
+          <div class="performance-title">
+            <div class="title-info">整体性能</div>
+            <div class="title-info-icon-box">
+              <ElTooltip placement="top">
+                <template #content>
+                  <div>
+                    <div style="text-align: center; padding: 1px 0 6px">性能说明</div>
+                    <div>
+                      <div>
+                        <span>性能极佳:</span>
+                        <span>应用分数 >= 0.94</span>
+                      </div>
+                      <div>
+                        <span>性能较好:</span>
+                        <span>0.85 <= 应用分数 < 0.94</span>
+                      </div>
+                      <div>
+                        <span>性能一般:</span>
+                        <span>0.50 <= 应用分数 < 0.85</span>
+                      </div>
+                      <div>
+                        <span>性能未知:</span>
+                        <span>应用分数 < 0.50</span>
+                      </div>
+                    </div>
+                  </div>
+                </template>
+                <!-- <i class="el-icon-question" /> -->
+                <img class="icon-help" src="../../../assets/icons/help.svg" />
+              </ElTooltip>
+            </div>
+          </div>
+          <div class="performance-content">
+            <div class="performance-content-left">
+              <div class="apdex-box">
+                <div
+                  class="apdex-con"
+                  :class="
+                    ApdexNum >= 0.94
+                      ? 'gc'
+                      : ApdexNum >= 0.85 && ApdexNum < 0.94
+                        ? 'bc'
+                        : ApdexNum >= 0.7 && ApdexNum < 0.85
+                          ? 'grc'
+                          : ApdexNum >= 0.5 && ApdexNum < 0.7
+                            ? 'oc'
+                            : 'rc'
+                  "
+                >
+                  {{
+                    ApdexNum >= 0.94
+                      ? '性能极佳'
+                      : ApdexNum >= 0.85 && ApdexNum < 0.94
+                        ? '性能较好'
+                        : ApdexNum >= 0.7 && ApdexNum < 0.85
+                          ? '性能一般'
+                          : ApdexNum >= 0.5 && ApdexNum < 0.7
+                            ? '性能一般'
+                            : '性能未知'
+                  }}
+                </div>
+              </div>
+            </div>
+            <div class="performance-content-right">
+              <div class="formula-title">
+                <div style="padding-bottom: 2px">计算公式:</div>
+                {{
+                  truncateNumber(
+                    (performanceObj.satisfied + performanceObj.tolerable / 2) /
+                      (performanceObj.satisfied + performanceObj.tolerable + performanceObj.highLatencyNum),
+                    2
+                  )
+                }}=({{ performanceObj.satisfied }}+{{ performanceObj.tolerable }}/2)/{{
+                  performanceObj.satisfied + performanceObj.tolerable + performanceObj.highLatencyNum
+                }}
+              </div>
+              <div class="formula-content">
+                <div class="item">
+                  <span class="item-name">较满意请求:</span>
+                  <span class="item-info">{{ performanceObj.satisfied }}</span>
+                </div>
+                <div class="item">
+                  <span class="item-name">可容忍请求:</span>
+                  <span class="item-info">{{ performanceObj.tolerable }}</span>
+                </div>
+                <div class="item">
+                  <span class="item-name">高延迟请求:</span>
+                  <span class="item-info">{{ performanceObj.highLatencyNum }}</span>
+                </div>
+              </div>
+            </div>
+          </div>
+        </div>
+        <!-- 面板数据-->
+        <div class="panel-box">
+          <div class="panel-item">
+            <ElTooltip placement="bottom" effect="dark" :visible-arrow="false">
+              <template #content>
+                <div>
+                  <i class="dot" />
+                  <span>自己关注业务总数</span>
+                </div>
+              </template>
+              <div class="icon-box">
+                <img class="icon-help" src="../../../assets/icons/help.svg" />
+              </div>
+            </ElTooltip>
+            <div>
+              <div class="panel-item-tit">重点业务总数</div>
+              <div class="panel-item-con">{{ digitsObj.vip_biz != undefined ? digitsObj.vip_biz : 0 }}</div>
+            </div>
+          </div>
+          <div class="panel-item">
+            <ElTooltip effect="dark" :visible-arrow="false">
+              <template #content>
+                <div>
+                  <i class="dot" />
+                  <span>当前应用所包含的服务包总数</span>
+                </div>
+              </template>
+              <div class="icon-box">
+                <img class="icon-help" src="../../../assets/icons/help.svg" />
+              </div>
+            </ElTooltip>
+            <div>
+              <div class="panel-item-tit">部署包总数</div>
+              <div class="panel-item-con">{{ digitsObj.service || 0 }}</div>
+            </div>
+          </div>
+          <div class="panel-item">
+            <ElTooltip placement="bottom" effect="dark" :visible-arrow="false">
+              <template #content>
+                <div>
+                  <i class="dot" />
+                  <span>当前应用所涉及的消息系统总数</span>
+                </div>
+              </template>
+              <div class="icon-box">
+                <img class="icon-help" src="../../../assets/icons/help.svg" />
+              </div>
+            </ElTooltip>
+            <div>
+              <div class="panel-item-tit">中间件总数</div>
+              <div class="panel-item-con">{{ digitsObj.messaging != undefined ? Number(digitsObj.messaging) : 0 }}</div>
+            </div>
+          </div>
+          <div class="panel-item">
+            <ElTooltip placement="bottom" effect="dark" :visible-arrow="false">
+              <template #content>
+                <div>
+                  <i class="dot" />
+                  <span>当前应用所包含的数据库类型总数</span>
+                </div>
+              </template>
+              <div class="icon-box">
+                <img class="icon-help" src="../../../assets/icons/help.svg" />
+              </div>
+            </ElTooltip>
+            <div>
+              <div class="panel-item-tit">数据库总数</div>
+              <div class="panel-item-con">{{ digitsObj.database != undefined ? Number(digitsObj.database) : 0 }}</div>
+            </div>
+          </div>
+        </div>
+        <!-- 性能指标面板 -->
+        <div class="performance-panel-box">
+          <div class="performance-panel-item">
+            <ElTooltip placement="bottom" effect="dark" :visible-arrow="false">
+              <template #content>
+                <div>
+                  <i class="dot" />
+                  <span>当日应用所涉及的业务请求总数</span>
+                </div>
+              </template>
+              <div class="icon-box">
+                <img class="icon-help" src="../../../assets/icons/help.svg" />
+              </div>
+            </ElTooltip>
+            <div>
+              <div class="performance-panel-item-tit">业务请求总数</div>
+              <div class="performance-panel-item-con">{{ digitsObj.request_total || 0 }}</div>
+              <div class="performance-panel-item-day-rate">
+                日同比
+                <i
+                  v-show="processNumber(digitsObj.request_total_dod).sign"
+                  :class="
+                    processNumber(digitsObj.request_total_dod).sign == 'up'
+                      ? 'el-icon-top goUpColor'
+                      : processNumber(digitsObj.request_total_dod).sign == 'down'
+                        ? 'el-icon-bottom goDownColor'
+                        : 'el-icon-minus equalColor'
+                  "
+                />
+                <span>
+                  {{
+                    digitsObj.request_total_dod == undefined || digitsObj.request_total_dod == '0'
+                      ? ''
+                      : getErrRate(processNumber(digitsObj.request_total_dod).value)
+                  }}
+                </span>
+              </div>
+              <div class="inchart-box">
+                <RequestInChart />
+              </div>
+            </div>
+          </div>
+          <div class="performance-panel-item">
+            <ElTooltip placement="bottom" effect="dark" :visible-arrow="false">
+              <template #content>
+                <div>
+                  <i class="dot" />
+                  <span>当日应用所涉及的业务请求错误总数</span>
+                </div>
+              </template>
+              <div class="icon-box">
+                <img class="icon-help" src="../../../assets/icons/help.svg" />
+              </div>
+            </ElTooltip>
+            <div @click="goPage">
+              <div class="performance-panel-item-tit">业务错误数</div>
+              <div class="performance-panel-item-con">{{ digitsObj.error_num || 0 }}</div>
+              <div class="performance-panel-item-day-rate">
+                日同比
+                <i
+                  v-show="processNumber(digitsObj.error_num_dod).sign"
+                  :class="
+                    processNumber(digitsObj.error_num_dod).sign == 'up'
+                      ? 'el-icon-top goUpColor'
+                      : processNumber(digitsObj.error_num_dod).sign == 'down'
+                        ? 'el-icon-bottom goDownColor'
+                        : 'el-icon-minus equalColor'
+                  "
+                />
+                <span>
+                  {{
+                    digitsObj.error_num_dod == undefined || digitsObj.error_num_dod == '0'
+                      ? ''
+                      : getErrRate(processNumber(digitsObj.error_num_dod).value)
+                  }}
+                </span>
+              </div>
+              <div class="inchart-box">
+                <RequestInChart />
+              </div>
+            </div>
+          </div>
+        </div>
+      </div>
+    </div>
+    <div></div>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.ob-basic-layout {
+  padding: 10px;
+  box-sizing: border-box;
+  .system-content {
+    display: flex;
+    background: #f0f1f5;
+    font-family: Roboto, sans-serif;
+    .system-left-content {
+      position: relative;
+      height: calc(100vh - 78px);
+      padding: 20px 10px;
+      box-sizing: border-box;
+      width: 30%; /* 设置左边盒子的初始宽度 */
+      min-width: 350px;
+      background-color: #fff;
+      margin-right: 16px;
+      border: 1px solid #f5f5f5;
+      overflow-y: auto;
+      .system-basic-info {
+        height: 100px;
+        border-radius: 8px;
+        color: #4e5969;
+        padding: 16px;
+        box-sizing: border-box;
+        background: url('../../../assets/pic-bg.png');
+        background-position: right;
+        background-size: cover;
+        background-repeat: no-repeat;
+        .service-header-p {
+          font-weight: bold;
+          font-size: 16px;
+          margin-bottom: 0;
+          margin-top: 20px;
+          display: flex;
+          align-items: center;
+          span {
+            display: inline-block;
+          }
+          .service-span-right {
+            text-align: left;
+            width: 58px;
+          }
+          .service-span-left {
+            text-align: left;
+            cursor: pointer;
+            white-space: nowrap;
+            overflow: hidden;
+            text-overflow: ellipsis;
+          }
+        }
+      }
+      .panel-box {
+        width: 100%;
+        // font-size: 0;
+        padding-top: 10px;
+        .panel-item {
+          position: relative;
+          display: inline-flex;
+          width: calc(50% - 5px);
+          box-sizing: border-box;
+          height: 100px;
+          background: linear-gradient(155deg, #ffffff 0%, #ffffff 50%, #eff4ff 100%);
+          border: 1px solid rgba(96, 97, 112, 0.07);
+          margin: 0 10px 10px 0;
+          justify-content: center;
+          align-items: center;
+          padding: 0 10px;
+          border-radius: 8px;
+          .icon-box {
+            position: absolute;
+            right: 10px;
+            top: 10px;
+            i {
+              font-size: 16px;
+            }
+          }
+          &:nth-child(even) {
+            margin-right: 0;
+          }
+          .panel-item-tit {
+            font-weight: bold;
+            font-size: 16px;
+            color: #4e5969;
+            text-align: center;
+          }
+          .panel-item-con {
+            font-weight: bold;
+            font-size: 36px;
+            color: #1a2233;
+            margin-top: 10px;
+            text-align: center;
+          }
+          .panel-item-day-rate {
+            font-size: 10px;
+            opacity: 0.9;
+          }
+        }
+      }
+      .performance-box {
+        width: 100%;
+        padding-top: 10px;
+        border: 1px solid rgba(96, 97, 112, 0.07);
+        border-radius: 8px;
+        margin-top: 10px;
+        .performance-title {
+          display: flex;
+          justify-content: space-between;
+          align-items: center;
+          height: 30px;
+          padding: 0 10px;
+          .title-info {
+            font-size: 16px;
+            font-weight: bold;
+            // color: '#1a2233';
+          }
+          .title-info-icon-box {
+            font-size: 16px;
+          }
+        }
+        .performance-content {
+          display: flex;
+          justify-content: space-between;
+          .performance-content-left {
+            align-self: center;
+            min-width: 110px;
+            padding-left: 10px;
+            flex: 1;
+            .apdex-box {
+              box-sizing: border-box;
+              color: #fff;
+              .apdex-con {
+                height: 72px;
+                line-height: 72px;
+                text-align: center;
+                font-size: 16px;
+                font-weight: bold;
+                border-radius: 3px;
+              }
+            }
+          }
+          .performance-content-right {
+            font-size: 10px;
+            padding: 10px;
+            // min-width: 200px;
+            .formula-title {
+              line-height: 14px;
+              font-weight: bold;
+              margin-bottom: 2px;
+              text-align: center;
+            }
+            .formula-content {
+              line-height: 14px;
+              .item {
+                display: flex;
+                justify-content: space-between;
+                .item-info {
+                  align-self: flex-start;
+                }
+              }
+            }
+          }
+        }
+      }
+    }
+    .performance-panel-box {
+      margin: 10px 0;
+      .performance-panel-item {
+        position: relative;
+        display: inline-flex;
+        width: calc(50% - 5px);
+        box-sizing: border-box;
+        // height: 160px;
+        background: linear-gradient(155deg, #ffffff 0%, #ffffff 50%, #eff4ff 100%);
+        border: 1px solid rgba(96, 97, 112, 0.07);
+        margin: 0 10px 10px 0;
+        justify-content: space-between;
+        align-items: center;
+        padding: 0 10px;
+        border-radius: 8px;
+        cursor: pointer;
+        .icon-box {
+          position: absolute;
+          right: 10px;
+          top: 10px;
+          i {
+            font-size: 16px;
+          }
+        }
+        &:nth-child(even) {
+          margin-right: 0;
+        }
+        .performance-panel-item-tit {
+          font-weight: bold;
+          font-size: 16px;
+          color: #4e5969;
+          padding: 10px 0px 10px;
+          // text-align: center;
+        }
+        .performance-panel-item-con {
+          font-weight: bold;
+          font-size: 36px;
+          color: #1a2233;
+          // margin-top:10px;
+          // text-align: center;
+        }
+        .performance-panel-item-day-rate {
+          font-size: 12px;
+          opacity: 0.9;
+        }
+        .inchart-box {
+          width: 100%;
+          margin: 20px 0;
+        }
+      }
+    }
+  }
+}
+</style>

+ 52 - 32
src/views/manage/menu/index.vue

@@ -1,17 +1,17 @@
 <script setup lang="tsx">
-import SvgIcon from '@/components/custom/svg-icon.vue';
-import { useTable, useTableOperate, useTableScroll } from '@/hooks/common/table';
-import { $t } from '@/locales';
-import { fetchDeleteMenu, fetchGetAllPages, fetchGetMenuList, getDicts } from '@/service/api';
 import { useBoolean } from '@sa/hooks';
 import { Button, Popconfirm } from 'ant-design-vue';
 import type { Ref } from 'vue';
 import { onMounted, ref } from 'vue';
+import SvgIcon from '@/components/custom/svg-icon.vue';
+import { useTable, useTableOperate, useTableScroll } from '@/hooks/common/table';
+import { $t } from '@/locales';
+import { fetchDeleteMenu, fetchGetAllPages, fetchGetMenuList, getDicts } from '@/service/api';
 import MenuOperateModal, { type OperateType } from './modules/menu-operate-modal.vue';
 import MenuSearch from './modules/menu-search.vue';
 const { bool: visible, setTrue: openModal } = useBoolean();
 const { tableWrapperRef, scrollConfig } = useTableScroll();
-const visibleOptions = ref([])
+const visibleOptions = ref([]);
 const { columns, columnChecks, data, loading, getData, getDataByPage, searchParams, resetSearchParams } = useTable({
   apiFn: fetchGetMenuList,
   apiParams: {
@@ -40,7 +40,7 @@ const { columns, columnChecks, data, loading, getData, getDataByPage, searchPara
       customRender: ({ record }) => {
         return (
           <div class="flex-center">
-            <SvgIcon localIcon={record.icon}  class="text-icon" />
+            <SvgIcon localIcon={record.icon} class="text-icon" />
           </div>
         );
       }
@@ -50,11 +50,18 @@ const { columns, columnChecks, data, loading, getData, getDataByPage, searchPara
       title: '组件路径',
       align: 'center',
       customRender: ({ record }) => {
-        if (record.menuType=='A') {
-          return <a-tooltip placement="top" title={record.path}>{record.path}</a-tooltip>;
-        } else {
-          return <a-tooltip placement="top" title={record.component}>{record.component}</a-tooltip>
+        if (record.menuType == 'A') {
+          return (
+            <a-tooltip placement="top" title={record.path}>
+              {record.path}
+            </a-tooltip>
+          );
         }
+        return (
+          <a-tooltip placement="top" title={record.component}>
+            {record.component}
+          </a-tooltip>
+        );
       }
     },
     {
@@ -63,7 +70,11 @@ const { columns, columnChecks, data, loading, getData, getDataByPage, searchPara
       dataIndex: 'permission',
       align: 'center',
       customRender: ({ record }) => {
-        return  <a-tooltip placement="top" title={record.permission}><span >{record.permission || '--' }</span></a-tooltip>
+        return (
+          <a-tooltip placement="top" title={record.permission}>
+            <span>{record.permission || '--'}</span>
+          </a-tooltip>
+        );
       }
     },
     {
@@ -80,7 +91,11 @@ const { columns, columnChecks, data, loading, getData, getDataByPage, searchPara
       width: 90,
       align: 'center',
       customRender: ({ record }) => {
-        return  <a-tag color={record.visible === '1'? 'error' : 'success'}><span >{visibleFormat(record) }</span></a-tag>
+        return (
+          <a-tag color={record.visible === '1' ? 'error' : 'success'}>
+            <span>{visibleFormat(record)}</span>
+          </a-tag>
+        );
       }
     },
     {
@@ -122,14 +137,14 @@ function handleAdd() {
 
 async function handleBatchDelete() {
   // request
-  handleDelete(checkedRowKeys.value)
+  handleDelete(checkedRowKeys.value);
 }
 
 async function handleDelete(id: any) {
   // request
-  let ids = []
-  typeof id === 'number' ? ids.push(id) : ids = [].concat(id)
-  const res = await fetchDeleteMenu({ids})
+  let ids = [];
+  typeof id === 'number' ? ids.push(id) : (ids = [].concat(id));
+  const res = await fetchDeleteMenu({ ids });
   if (res.code === 200) {
     onDeleted();
   } else {
@@ -164,28 +179,28 @@ async function getAllPages() {
 function init() {
   getAllPages();
 }
-async function getOptions (){
-  const res  = await getDicts('sys_show_hide')
+async function getOptions() {
+  const res = await getDicts('sys_show_hide');
   if (res.code == 200) {
-    visibleOptions.value = res.data
+    visibleOptions.value = res.data;
   }
 }
 // 菜单显示状态字典翻译
-function visibleFormat(row:any) {
+function visibleFormat(row: any) {
   if (row.menuType === 'F') {
-    return '--'
+    return '--';
   }
-  return selectDictLabel(visibleOptions.value, row.visible)
+  return selectDictLabel(visibleOptions.value, row.visible);
 }
-function selectDictLabel(datas:any, value:any) {
-  var actions:any = []
-  Object.keys(datas).map((key) => {
-    if (datas[key].value === ('' + value)) {
-      actions.push(datas[key].label)
-      return false
+function selectDictLabel(datas: any, value: any) {
+  const actions: any = [];
+  Object.keys(datas).map(key => {
+    if (datas[key].value === `${value}`) {
+      actions.push(datas[key].label);
+      return false;
     }
-  })
-  return actions.join('')
+  });
+  return actions.join('');
 }
 // init
 init();
@@ -193,7 +208,12 @@ init();
 
 <template>
   <div class="min-h-500px flex-col-stretch gap-16px overflow-hidden lt-sm:overflow-auto">
-    <MenuSearch v-model:model="searchParams" @reset="resetSearchParams" @search="getDataByPage(1, false)"  :visibleOptions="visibleOptions"/>
+    <MenuSearch
+      v-model:model="searchParams"
+      :visible-options="visibleOptions"
+      @reset="resetSearchParams"
+      @search="getDataByPage(1, false)"
+    />
     <ACard
       :title="$t('page.manage.menu.title')"
       :bordered="false"
@@ -227,7 +247,7 @@ init();
         :operate-type="operateType"
         :row-data="editingData"
         :all-pages="allPages"
-        :visibleOptions="visibleOptions"
+        :visible-options="visibleOptions"
         @submitted="getDataByPage"
       />
     </ACard>

+ 36 - 32
src/views/manage/role/index.vue

@@ -1,19 +1,19 @@
 /* eslint-disable */
 <script setup lang="tsx">
+import { Button, Popconfirm } from 'ant-design-vue';
+import { ref } from 'vue';
 import { useTable, useTableOperate, useTableScroll } from '@/hooks/common/table';
 import { $t } from '@/locales';
 import { fetchChangeRoleStatus, fetchDeleteRole, fetchGetRoleList } from '@/service/api';
-import { Button, Popconfirm } from 'ant-design-vue';
-import { ref } from 'vue';
 import RuleModifypermisModal from './modules/role-modifypermis-modal.vue';
 import RoleOperateDrawer from './modules/role-operate-drawer.vue';
 import RoleSearch from './modules/role-search.vue';
 const { tableWrapperRef, scrollConfig } = useTableScroll();
-const modalVisible = ref(false)
-const rowRoleKey = ref('')
-const rowRoleName = ref('')
-const rowRoleId = ref()
-const rowData = ref()
+const modalVisible = ref(false);
+const rowRoleKey = ref('');
+const rowRoleName = ref('');
+const rowRoleId = ref();
+const rowData = ref();
 const {
   columns,
   columnChecks,
@@ -76,11 +76,16 @@ const {
       align: 'center',
       minWidth: 100,
       customRender: ({ record }) => {
-        let showStatus = record.status === '2'? true : false
-        const text = record.status === '2' ? '停用' : '启用'
-        return <Popconfirm title={`确定要${text}${record.roleName}角色吗?`} onConfirm={() => handleChangeStatus(record, text)}>
-          <a-switch v-model:checked={showStatus} />
-        </Popconfirm>
+        const showStatus = record.status === '2';
+        const text = record.status === '2' ? '停用' : '启用';
+        return (
+          <Popconfirm
+            title={`确定要${text}${record.roleName}角色吗?`}
+            onConfirm={() => handleChangeStatus(record, text)}
+          >
+            <a-switch v-model:checked={showStatus} />
+          </Popconfirm>
+        );
       }
     },
     {
@@ -93,7 +98,7 @@ const {
           <Button type="primary" ghost size="small" onClick={() => handleRowEdit(record.roleId)}>
             {$t('common.edit')}
           </Button>
-          <Button  block size="small" onClick={() => handleModifyPermi(record)}>
+          <Button block size="small" onClick={() => handleModifyPermi(record)}>
             数据权限
           </Button>
           <Popconfirm onConfirm={() => handleDelete(record.roleId)} title={$t('common.confirmDelete')}>
@@ -121,14 +126,14 @@ const {
 
 async function handleBatchDelete() {
   // request
-  handleDelete(checkedRowKeys.value)
+  handleDelete(checkedRowKeys.value);
 }
 
 async function handleDelete(id: any) {
   // request
-  let ids = []
-  typeof id === 'number' ? ids.push(id) : ids = [].concat(id)
-  const res = await fetchDeleteRole({ids})
+  let ids = [];
+  typeof id === 'number' ? ids.push(id) : (ids = [].concat(id));
+  const res = await fetchDeleteRole({ ids });
   if (res.code === 200) {
     onDeleted();
   } else {
@@ -140,21 +145,20 @@ function handleRowEdit(id: number) {
   handleEdit(id, 'roleId');
 }
 // 数据权限
-function handleModifyPermi(row:any){
-  modalVisible.value = true
-  rowData.value = row
-  rowRoleId.value = row.roleId
-  rowRoleKey.value = row.roleKey
-  rowRoleName.value = row.roleName
-
+function handleModifyPermi(row: any) {
+  modalVisible.value = true;
+  rowData.value = row;
+  rowRoleId.value = row.roleId;
+  rowRoleKey.value = row.roleKey;
+  rowRoleName.value = row.roleName;
 }
-async function handleChangeStatus(row:any, text:string){
-  let paras = {
-    roleId:row.roleId,
-    status: text === '停用'? '1' : '2'
-  }
-  const res:any = await fetchChangeRoleStatus(paras)
-  if (res.code === 200 ){
+async function handleChangeStatus(row: any, text: string) {
+  const paras = {
+    roleId: row.roleId,
+    status: text === '停用' ? '1' : '2'
+  };
+  const res: any = await fetchChangeRoleStatus(paras);
+  if (res.code === 200) {
     window.$message?.success($t('common.updateSuccess'));
   } else {
     window.$message?.error('更新失败!');
@@ -199,7 +203,7 @@ async function handleChangeStatus(row:any, text:string){
         :row-data="editingData"
         @submitted="getDataByPage"
       />
-      <RuleModifypermisModal v-model:visible="modalVisible"  :rowData="rowData"  @submitted="getDataByPage"/>
+      <RuleModifypermisModal v-model:visible="modalVisible" :row-data="rowData" @submitted="getDataByPage" />
     </ACard>
   </div>
 </template>

+ 1 - 1
src/views/manage/role/modules/role-modifypermis-modal.vue

@@ -101,7 +101,7 @@ watch(visible, () => {
 });
 </script>
 <template>
-  <AModal v-model:open="visible" title="分配数据权限" width="420px">
+  <AModal v-model:open="visible" title="分配数据权限" class="w-480px">
     <AForm ref="formRef" layout="vertical" :model="model" :rules="rules">
       <AFormItem :label="$t('page.manage.role.roleName')" name="roleName">
         <AInput v-model:value="model.roleName" :placeholder="$t('page.manage.role.form.roleName')" :disabled="true" allowClear/>

+ 60 - 29
src/views/manage/role/modules/role-operate-drawer.vue

@@ -2,12 +2,12 @@
 <script setup lang="ts">
 import { computed, nextTick, reactive, ref, watch } from 'vue';
 // import { useBoolean } from '@sa/hooks';
+import type { DataNode } from 'ant-design-vue/es/tree';
 import { enableStatusOptions } from '@/constants/business';
 import { useAntdForm, useFormRules } from '@/hooks/common/form';
 import { $t } from '@/locales';
 // import MenuAuthModal from './menu-auth-modal.vue';
 import { fetchAddRole, fetchEditRole, fetchGetMenuTree } from '@/service/api';
-import type { DataNode } from 'ant-design-vue/es/tree';
 defineOptions({
   name: 'RoleOperateDrawer'
 });
@@ -72,8 +72,7 @@ const isEdit = computed(() => props.operateType === 'edit');
 
 async function handleInitModel() {
   Object.assign(model, createDefaultModel());
-  if (isEdit && props.rowData) {
-
+  if (isEdit.value && props.rowData) {
     await nextTick();
     Object.assign(model, props.rowData);
   }
@@ -87,8 +86,8 @@ async function getTree() {
 
   if (code === 200) {
     tree.value = recursiveTransform(data.menus);
-    if(isEdit.value){
-      model.menuIds=  data.checkedKeys || []
+    if (isEdit.value) {
+      model.menuIds = data.checkedKeys || [];
     }
   }
 }
@@ -112,17 +111,19 @@ function recursiveTransform(data: Api.SystemManage.MenuTree[]): DataNode[] {
 }
 async function handleSubmit() {
   await validate();
-  let res = null
-  if (isEdit.value) { // 编辑
-    res = await fetchEditRole(model, roleId.value)
-  } else {// 新增
-    res = await fetchAddRole(model)
+  let res = null;
+  if (isEdit.value) {
+    // 编辑
+    res = await fetchEditRole(model, roleId.value);
+  } else {
+    // 新增
+    res = await fetchAddRole(model);
   }
   if (res.code === 200) {
-    if(isEdit.value){
+    if (isEdit.value) {
       window.$message?.success($t('common.updateSuccess'));
     } else {
-      window.$message?.success($t('common.addSuccess'))
+      window.$message?.success($t('common.addSuccess'));
     }
   } else {
     window.$message?.error(res.msg);
@@ -136,30 +137,44 @@ watch(visible, () => {
   if (visible.value) {
     handleInitModel();
     resetFields();
-    getTree()
+    getTree();
   }
 });
 </script>
 
 <template>
   <AModal v-model:open="visible" :title="title" width="800px">
-    <AForm ref="formRef"  :model="model" :rules="rules" :label-col="{ lg: 8, xs: 4 }" label-wrap class="pr-20px">
+    <AForm ref="formRef" :model="model" :rules="rules" :label-col="{ lg: 8, xs: 4 }" label-wrap class="pr-20px">
       <ARow wrap>
         <ACol :span="12" :md="12" :xs="24">
           <AFormItem :label="$t('page.manage.role.roleName')" name="roleName">
-            <AInput v-model:value="model.roleName" :disabled="isEdit" :placeholder="$t('page.manage.role.form.roleName')" allowClear />
+            <AInput
+              v-model:value="model.roleName"
+              :disabled="isEdit"
+              :placeholder="$t('page.manage.role.form.roleName')"
+              allow-clear
+            />
           </AFormItem>
         </ACol>
         <ACol :span="12" :md="12" :xs="24">
           <AFormItem :label="$t('page.manage.role.roleKey')" name="roleKey">
-            <AInput v-model:value="model.roleKey" :disabled="isEdit" :placeholder="$t('page.manage.role.form.roleKey')" allowClear/>
+            <AInput
+              v-model:value="model.roleKey"
+              :disabled="isEdit"
+              :placeholder="$t('page.manage.role.form.roleKey')"
+              allow-clear
+            />
           </AFormItem>
         </ACol>
       </ARow>
       <ARow wrap>
         <ACol :span="12" :md="12" :xs="24">
           <AFormItem :label="$t('page.manage.role.roleSort')" name="roleSort">
-            <AInputNumber v-model:value="model.roleSort" :placeholder="$t('page.manage.role.form.roleSort')"  style="width:100%"/>
+            <AInputNumber
+              v-model:value="model.roleSort"
+              :placeholder="$t('page.manage.role.form.roleSort')"
+              style="width: 100%"
+            />
           </AFormItem>
         </ACol>
         <ACol :span="12" :md="12" :xs="24">
@@ -172,27 +187,43 @@ watch(visible, () => {
           </AFormItem>
         </ACol>
       </ARow>
-      <ARow wrap >
+      <ARow wrap>
         <ACol :span="24" :md="24" :xs="24">
           <AFormItem :label-col="{ lg: 4, xs: 2 }" :label="$t('page.manage.user.remark')" name="remark">
-          <a-textarea v-model:value="model.remark" :placeholder="$t('page.manage.user.form.remark')" allowClear
-            :auto-size="{ minRows: 2, maxRows: 5 }" />
-        </AFormItem>
+            <ATextarea
+              v-model:value="model.remark"
+              :placeholder="$t('page.manage.user.form.remark')"
+              allow-clear
+              :auto-size="{ minRows: 2, maxRows: 5 }"
+            />
+          </AFormItem>
         </ACol>
       </ARow>
-      <ARow wrap >
+      <ARow wrap>
         <ACol :span="24" :md="24" :xs="24">
           <AFormItem :label-col="{ lg: 4, xs: 2 }" :label="$t('page.manage.role.menuAuth')" name="remark">
-          <ATree :label-col="{ lg: 4, xs: 2 }" v-model:checked-keys="model.menuIds" :tree-data="tree" checkable :height="280" class="h-280px" allowClear/>
-        </AFormItem>
+            <ATree
+              v-model:checked-keys="model.menuIds"
+              :label-col="{ lg: 4, xs: 2 }"
+              :tree-data="tree"
+              checkable
+              :height="280"
+              class="h-280px"
+              allow-clear
+            />
+          </AFormItem>
         </ACol>
       </ARow>
     </AForm>
-    <ASpace >
-      <!-- <AButton @click="openMenuAuthModal">{{ $t('page.manage.role.menuAuth') }}</AButton>
-      <MenuAuthModal v-model:visible="menuAuthVisible" :role-id="roleId" :isEdit="isEdit" @submitted="getMenuIds"/> -->
-      <!-- <AButton @click="openButtonAuthModal">{{ $t('page.manage.role.buttonAuth') }}</AButton>
-      <ButtonAuthModal v-model:visible="buttonAuthVisible" :role-id="roleId" /> -->
+    <ASpace>
+      <!--
+ <AButton @click="openMenuAuthModal">{{ $t('page.manage.role.menuAuth') }}</AButton>
+      <MenuAuthModal v-model:visible="menuAuthVisible" :role-id="roleId" :isEdit="isEdit" @submitted="getMenuIds"/> 
+-->
+      <!--
+ <AButton @click="openButtonAuthModal">{{ $t('page.manage.role.buttonAuth') }}</AButton>
+      <ButtonAuthModal v-model:visible="buttonAuthVisible" :role-id="roleId" /> 
+-->
     </ASpace>
     <template #footer>
       <div class="flex-y-center justify-end gap-12px">

+ 41 - 31
src/views/manage/user/index.vue

@@ -1,10 +1,10 @@
 <script setup lang="tsx">
-import { useTable, useTableOperate, useTableScroll } from '@/hooks/common/table';
-import { $t } from '@/locales';
-import { changeUserStatus, fetchDelteUser, fetchGetUserList } from '@/service/api';
 import { Button, Popconfirm } from 'ant-design-vue';
 import dayjs from 'dayjs';
 import { ref } from 'vue';
+import { useTable, useTableOperate, useTableScroll } from '@/hooks/common/table';
+import { $t } from '@/locales';
+import { changeUserStatus, fetchDelteUser, fetchGetUserList } from '@/service/api';
 import UserOperateDrawer from './modules/user-operate-drawer.vue';
 import UserResetPwdDialog from './modules/user-resetpwd-dialog.vue';
 import UserSearch from './modules/user-search.vue';
@@ -13,9 +13,9 @@ const apiParams = ref({
   pageIndex: 1,
   pageSize: 10
 });
-let transUserId:number | null = null
-let userName = ''
-let resetDialogVisible = ref(false)
+let transUserId: number | null = null;
+let userName = '';
+const resetDialogVisible = ref(false);
 
 const {
   columns,
@@ -29,7 +29,7 @@ const {
   resetSearchParams
 } = useTable({
   apiFn: fetchGetUserList,
-  apiParams:apiParams.value,
+  apiParams: apiParams.value,
   showTotal: true,
   columns: () => [
     {
@@ -76,12 +76,17 @@ const {
       title: $t('page.manage.user.userStatus'),
       align: 'center',
       minWidth: 100,
-      customRender: ({ record}) => {
-        let showStatus:boolean = record.status == '2' ? true : false;
-        const text:string = record.status == '2' ? '停用' : '启用';
-        return <Popconfirm title={`确定要${text}${record.username}的用户吗?`} onConfirm={() => handleChangeStatus(record, text)} >
-          <a-switch v-model:checked={showStatus} ></a-switch>
-        </Popconfirm>;
+      customRender: ({ record }) => {
+        const showStatus: boolean = record.status == '2';
+        const text: string = record.status == '2' ? '停用' : '启用';
+        return (
+          <Popconfirm
+            title={`确定要${text}${record.username}的用户吗?`}
+            onConfirm={() => handleChangeStatus(record, text)}
+          >
+            <a-switch v-model:checked={showStatus}></a-switch>
+          </Popconfirm>
+        );
       }
     },
     {
@@ -90,8 +95,8 @@ const {
       title: '创建时间',
       align: 'center',
       minWidth: 150,
-      customRender:({record})=>{
-        return dayjs(record.createdAt).format('YYYY-MM-DD HH:mm:ss')
+      customRender: ({ record }) => {
+        return dayjs(record.createdAt).format('YYYY-MM-DD HH:mm:ss');
       }
     },
     {
@@ -102,7 +107,13 @@ const {
       width: 180,
       customRender: ({ record }) => (
         <div class="flex-center gap-0.1rem">
-          <Button type="primary" ghost size="small" onClick={() => handleRowEdit(record.userId)} style="margin-right:5px">
+          <Button
+            type="primary"
+            ghost
+            size="small"
+            onClick={() => handleRowEdit(record.userId)}
+            style="margin-right:5px"
+          >
             {$t('common.edit')}
           </Button>
           <Popconfirm title={$t('common.confirmDelete')} onConfirm={() => handleDelete(record.userId)}>
@@ -132,28 +143,27 @@ const {
 
 async function handleBatchDelete() {
   // request
-  handleDelete(checkedRowKeys.value)
+  handleDelete(checkedRowKeys.value);
 }
-async function handleChangeStatus(row:any, text:string){
+async function handleChangeStatus(row: any, text: string) {
   const data = {
     userId: row.userId,
-    status: text === '停用'? '1' : '2'
-  }
-  const res = await changeUserStatus(data)
+    status: text === '停用' ? '1' : '2'
+  };
+  const res = await changeUserStatus(data);
   if (res.code === 200) {
     window.$message?.success('更新成功!');
   } else {
     window.$message?.error('更新失败!');
   }
-  await getData()
-
+  await getData();
 }
 // 删除
 async function handleDelete(id: any) {
   // request
-  let ids = []
-  typeof id === 'number' ? ids.push(id) : ids = [].concat(id)
-  const res = await fetchDelteUser({ids})
+  let ids = [];
+  typeof id === 'number' ? ids.push(id) : (ids = [].concat(id));
+  const res = await fetchDelteUser({ ids });
   if (res.code === 200) {
     onDeleted();
   } else {
@@ -162,10 +172,10 @@ async function handleDelete(id: any) {
 }
 
 // 重置
-function handleResetPwd(row:any) {
-  resetDialogVisible.value = true
-  userName = row.username
-  transUserId = row.userId
+function handleResetPwd(row: any) {
+  resetDialogVisible.value = true;
+  userName = row.username;
+  transUserId = row.userId;
 }
 
 // 编辑
@@ -212,7 +222,7 @@ function handleRowEdit(id: number) {
         :row-data="editingData"
         @submitted="getDataByPage"
       />
-      <UserResetPwdDialog v-model:dialogVisible="resetDialogVisible" :userId="transUserId" :user-name="userName"/>
+      <UserResetPwdDialog v-model:dialogVisible="resetDialogVisible" :user-id="transUserId" :user-name="userName" />
     </ACard>
   </div>
 </template>

+ 78 - 51
src/views/manage/user/modules/user-operate-drawer.vue

@@ -1,9 +1,9 @@
 /* eslint-disable */
 <script setup lang="ts">
+import { computed, nextTick, reactive, ref, watch } from 'vue';
 import { useAntdForm, useFormRules } from '@/hooks/common/form';
 import { $t } from '@/locales';
-import { fetchAddUser, fetchGetAllRoles, fetchGetdeptTreeList, fetchGetPostList, getDicts } from '@/service/api';
-import { computed, nextTick, reactive, ref, watch } from 'vue';
+import { fetchAddUser, fetchGetAllRoles, fetchGetPostList, fetchGetdeptTreeList, getDicts } from '@/service/api';
 defineOptions({
   name: 'UserOperateDrawer'
 });
@@ -40,7 +40,17 @@ const title = computed(() => {
 
 type Model = Pick<
   Api.SystemManage.User,
-  'username' | 'sex' | 'nickName' | 'phone' | 'email' | 'status' | 'deptId' | 'password' | 'postId' | 'roleId' | 'remark'
+  | 'username'
+  | 'sex'
+  | 'nickName'
+  | 'phone'
+  | 'email'
+  | 'status'
+  | 'deptId'
+  | 'password'
+  | 'postId'
+  | 'roleId'
+  | 'remark'
 >;
 
 const model: Model = reactive(createDefaultModel());
@@ -61,7 +71,7 @@ function createDefaultModel(): Model {
   };
 }
 
-type RuleKey = Extract<keyof Model, 'username' | 'status'| 'nickName'| 'deptId' | 'password'| 'email' | 'phone'>;
+type RuleKey = Extract<keyof Model, 'username' | 'status' | 'nickName' | 'deptId' | 'password' | 'email' | 'phone'>;
 
 const rules: Record<RuleKey, App.Global.FormRule> = {
   username: defaultRequiredRule,
@@ -69,45 +79,45 @@ const rules: Record<RuleKey, App.Global.FormRule> = {
   deptId: defaultRequiredRule,
   password: defaultRequiredRule,
   phone: formRules.phone,
-  email:formRules.email
+  email: formRules.email
 };
 
 /** the enabled role options */
 const roleOptions = ref<CommonType.Option<string>[]>([]);
 const userGenderOptions = ref<CommonType.Option<string>[]>([
-  {label: "男", value: "0"},
-  {label: "女", value: "1"},
-  {label: "未知", value: "2"}
+  { label: '男', value: '0' },
+  { label: '女', value: '1' },
+  { label: '未知', value: '2' }
 ]);
 const enableStatusOptions = ref<CommonType.Option<string>[]>([]);
 const postOptions = ref<CommonType.Option<string>[]>([]);
 const departOptions = ref<CommonType.Option<string>[]>([]);
 
-async function getSixOptions(){
-  const { data } = await getDicts('sys_user_sex')
-  userGenderOptions.value = data || []
+async function getSixOptions() {
+  const { data } = await getDicts('sys_user_sex');
+  userGenderOptions.value = data || [];
 }
-async function getStatusOptions(){
-  const { data } = await getDicts('sys_normal_disable')
-  enableStatusOptions.value = data || []
+async function getStatusOptions() {
+  const { data } = await getDicts('sys_normal_disable');
+  enableStatusOptions.value = data || [];
 }
-async function getPostOptions(){
-  const { data } = await fetchGetPostList({ pageSize: 1000 })
-  const options = data?.list.map((item:any) => ({
+async function getPostOptions() {
+  const { data } = await fetchGetPostList({ pageSize: 1000 });
+  const options = data?.list.map((item: any) => ({
     label: item.postName,
     value: item.postId,
     postCode: item.postCode
   }));
-  postOptions.value = [...options] || []
+  postOptions.value = [...options] || [];
 }
-async function getDeptTree(){
-  const { data } = await fetchGetdeptTreeList()
-  departOptions.value = data
+async function getDeptTree() {
+  const { data } = await fetchGetdeptTreeList();
+  departOptions.value = data;
 }
 
 async function getRoleOptions() {
   const { data } = await fetchGetAllRoles({ pageSize: 1000 });
-  const options = data?.list.map((item:any) => ({
+  const options = data?.list.map((item: any) => ({
     label: item.roleName,
     value: item.roleId,
     status: item.status
@@ -131,14 +141,14 @@ function closeDrawer() {
 async function handleSubmit() {
   await validate();
   // request 新增method 是post 修改是put
-  let method = ''
+  let method = '';
   if (props.operateType === 'add') {
-    method = 'post'
+    method = 'post';
   } else {
-    method = 'put'
+    method = 'put';
   }
-  const res = await fetchAddUser(model, method)
-  if (res.code === 200){
+  const res = await fetchAddUser(model, method);
+  if (res.code === 200) {
     window.$message?.success(res.msg);
   }
   closeDrawer();
@@ -150,59 +160,64 @@ watch(visible, () => {
     handleInitModel();
     resetFields();
     getRoleOptions();
-    getSixOptions()
-    getStatusOptions()
-    getPostOptions()
-    getDeptTree()
+    getSixOptions();
+    getStatusOptions();
+    getPostOptions();
+    getDeptTree();
   }
 });
 </script>
 
 <template>
   <AModal v-model:open="visible" :title="title" width="800px">
-    <AForm ref="formRef"  :model="model" :rules="rules" :label-col="{ lg: 8, xs: 4 }" label-wrap class="pr-20px">
-      <ARow  wrap>
+    <AForm ref="formRef" :model="model" :rules="rules" :label-col="{ lg: 8, xs: 4 }" label-wrap class="pr-20px">
+      <ARow wrap>
         <ACol :span="12" :md="12" :xs="24">
           <AFormItem :label="$t('page.manage.user.nickName')" name="nickName">
-            <AInput v-model:value="model.nickName" :placeholder="$t('page.manage.user.form.nickName')" allowClear/>
+            <AInput v-model:value="model.nickName" :placeholder="$t('page.manage.user.form.nickName')" allow-clear />
           </AFormItem>
         </ACol>
         <ACol :span="12" :md="12" :xs="24">
           <AFormItem :label="$t('page.manage.user.deptId')" name="roleId">
-            <ATreeSelect v-model:value="model.deptId" show-search  :tree-data="departOptions"
-            :fieldNames="{value: 'id', children: 'children', label: 'label'}"
-              :placeholder="$t('page.manage.user.form.deptId')" allowClear/>
+            <ATreeSelect
+              v-model:value="model.deptId"
+              show-search
+              :tree-data="departOptions"
+              :field-names="{ value: 'id', children: 'children', label: 'label' }"
+              :placeholder="$t('page.manage.user.form.deptId')"
+              allow-clear
+            />
           </AFormItem>
         </ACol>
       </ARow>
       <ARow wrap>
         <ACol :span="12" :md="12" :xs="24">
           <AFormItem :label="$t('page.manage.user.userPhone')" name="phone">
-            <AInput v-model:value="model.phone" :placeholder="$t('page.manage.user.form.userPhone')" allowClear/>
+            <AInput v-model:value="model.phone" :placeholder="$t('page.manage.user.form.userPhone')" allow-clear />
           </AFormItem>
         </ACol>
         <ACol :span="12" :md="12" :xs="24">
           <AFormItem :label="$t('page.manage.user.userEmail')" name="email">
-            <AInput v-model:value="model.email" :placeholder="$t('page.manage.user.form.userEmail')" allowClear/>
+            <AInput v-model:value="model.email" :placeholder="$t('page.manage.user.form.userEmail')" allow-clear />
           </AFormItem>
         </ACol>
       </ARow>
       <ARow wrap>
         <ACol :span="12" :md="12" :xs="24">
           <AFormItem :label="$t('page.manage.user.userName')" name="username">
-            <AInput v-model:value="model.username" :placeholder="$t('page.manage.user.form.userName')" allowClear/>
+            <AInput v-model:value="model.username" :placeholder="$t('page.manage.user.form.userName')" allow-clear />
           </AFormItem>
         </ACol>
-        <ACol :span="12" :md="12" :xs="24" v-if="props.operateType === 'add'">
-          <AFormItem :label="$t('page.manage.user.password')" name="password" >
-            <AInput v-model:value="model.password" :placeholder="$t('page.manage.user.form.password')" allowClear/>
+        <ACol v-if="props.operateType === 'add'" :span="12" :md="12" :xs="24">
+          <AFormItem :label="$t('page.manage.user.password')" name="password">
+            <AInput v-model:value="model.password" :placeholder="$t('page.manage.user.form.password')" allow-clear />
           </AFormItem>
         </ACol>
         <ACol :span="12" :md="12" :xs="24">
           <AFormItem :label="$t('page.manage.user.userGender')" name="sex">
             <ARadioGroup v-model:value="model.sex">
               <ARadio v-for="item in userGenderOptions" :key="item.value" :value="item.value">
-                {{item.label }}
+                {{ item.label }}
               </ARadio>
             </ARadioGroup>
           </AFormItem>
@@ -218,20 +233,32 @@ watch(visible, () => {
         </ACol>
         <ACol :span="12" :md="12" :xs="24">
           <AFormItem :label="$t('page.manage.user.postId')" name="postId">
-            <ASelect v-model:value="model.postId" :options="postOptions"
-              :placeholder="$t('page.manage.user.form.postId')" allowClear/>
+            <ASelect
+              v-model:value="model.postId"
+              :options="postOptions"
+              :placeholder="$t('page.manage.user.form.postId')"
+              allow-clear
+            />
           </AFormItem>
         </ACol>
-        <ACol :span="12" :md="props.operateType === 'add'? 12: 24" :xs="24">
+        <ACol :span="12" :md="props.operateType === 'add' ? 12 : 24" :xs="24">
           <AFormItem :label="$t('page.manage.user.userRole')" name="roleId">
-            <ASelect v-model:value="model.roleId"  :options="roleOptions"
-              :placeholder="$t('page.manage.user.form.userRole')" allowClear/>
+            <ASelect
+              v-model:value="model.roleId"
+              :options="roleOptions"
+              :placeholder="$t('page.manage.user.form.userRole')"
+              allow-clear
+            />
           </AFormItem>
         </ACol>
         <ACol :span="24" :md="24" :xs="24">
           <AFormItem :label-col="{ lg: 4, xs: 2 }" :label="$t('page.manage.user.remark')" name="remark">
-            <a-textarea v-model:value="model.remark" :placeholder="$t('page.manage.user.form.remark')"
-              :auto-size="{ minRows: 2, maxRows: 5 }" allowClear/>
+            <ATextarea
+              v-model:value="model.remark"
+              :placeholder="$t('page.manage.user.form.remark')"
+              :auto-size="{ minRows: 2, maxRows: 5 }"
+              allow-clear
+            />
           </AFormItem>
         </ACol>
       </ARow>

+ 1 - 1
src/views/manage/user/modules/user-resetpwd-dialog.vue

@@ -47,7 +47,7 @@ watch(visible, () => {
 });
 </script>
 <template>
-  <AModal v-model:open="visible" title="重置密码" width="420px">
+  <AModal v-model:open="visible" title="重置密码" class="w-480px">
     <!-- <p style="margin-bottom: 10px;"></p> -->
     <AForm ref="formRef" layout="vertical" :model="model" :rules="rules">
       <AFormItem :label="`请输入${userName}的新密码`" name="password">

+ 542 - 0
src/views/new-home/index.vue

@@ -0,0 +1,542 @@
+<script setup lang="ts">
+import { onMounted, reactive, ref } from 'vue';
+import { useRouter } from 'vue-router';
+import { debounce } from 'lodash-es';
+import { localStg } from '@/utils/storage';
+import { fetchListApps, searchBizName, searchServiceUrl } from '@/service/api';
+import { useAuthStore } from '@/store/modules/auth';
+import { useAliasStore } from '@/store/modules/alias';
+import { useAntdForm } from '@/hooks/common/form';
+import AddModal from './modules/add-modal.vue'
+import { Modal } from 'ant-design-vue';
+import { $t } from '@/locales';
+const authStore = useAuthStore();
+const aliasStore = useAliasStore();
+const { resetFields } = useAntdForm();
+interface urlOption {
+  AppAlias: string;
+  Method: string;
+  Route: string;
+  ServiceName: string;
+}
+interface bizOption {
+  active: boolean;
+  app_alias: string;
+  app_name: string;
+  favor: boolean;
+  hash: string;
+  id: string;
+  name: string;
+  node_num: string;
+  service_name: string;
+}
+
+const openMonitor = ref(false);
+
+const router = useRouter();
+const queryParams = reactive({
+  name: ''
+});
+const selectOption = ref('1');
+let appList = ref<StorageType.appsItem[]>([]);
+const stateurl = ref('');
+const urlLoading = ref(false);
+const suppilerListQuery = reactive({
+  url: '',
+  pageIndex: 1,
+  pageSize: 100
+});
+let urlSuggestions = reactive<urlOption[]>([]);
+const bizCompleteRef = ref();
+const stateBiz = ref('');
+const bizLoading = ref(false);
+const bizSearchQuery = reactive({
+  name: stateBiz.value,
+  pageIndex: 1,
+  pageSize: 100
+});
+let bizSuggestions = reactive<bizOption[]>([]);
+let bizPageTotal = 0;
+let pageTotal = 0;
+let dialogFlag = ref(false)
+function handChange(val: any) {
+  if (val) {
+    const obj = val;
+    localStg.set('appsItem', obj);
+    aliasStore.setCurrentAlias(val.alias);
+
+    router
+      .push({
+        path: '/business-analysis/system-space/index',
+        query: {
+          id: val.id,
+          app_alias: val.alias,
+          name: val.name,
+          item: val,
+          live: val.live
+        }
+      })
+      .catch(err => err);
+  }
+}
+function getList() {
+  fetchListApps({ pageIndex: 0, pageSize: 200 }).then((res: any) => {
+    if (res.code === 200) {
+      appList.value = res.data.list || [];
+    } else {
+      appList.value = [];
+    }
+  });
+}
+// url-滚到底部执行的方法
+async function handelScroll(e: any) {
+  if (Number(pageTotal) <= urlSuggestions.length) {
+    // 全部已经展示完了
+    suppilerListQuery.pageIndex = 1;
+    return;
+  }
+  const { scrollHeight, scrollTop, clientHeight } = e.target;
+  if (clientHeight + scrollTop + 2 >= scrollHeight) {
+    // 到底部
+    console.log('到底了~~~~~~');
+    suppilerListQuery.pageIndex += 1;
+    urlLoading.value = true;
+    const result = await searchServiceUrl(suppilerListQuery);
+    // 根据实际接口返回数据格式将数据插入到选项中
+    urlSuggestions = urlSuggestions.concat(result.data.list || []);
+    pageTotal = result.data.count || 0;
+    urlLoading.value = false;
+  }
+}
+// url-查询
+const querySearchAsync = debounce(async value => {
+  // 300ms 防抖延迟
+  suppilerListQuery.url = value && value.trim();
+  stateurl.value = value && value.trim();
+  if (stateurl.value === '') {
+    suppilerListQuery.pageIndex = 1;
+  }
+  urlLoading.value = true;
+  try {
+    const res = await searchServiceUrl(suppilerListQuery);
+    if (res && res.code === 200) {
+      urlSuggestions = res.data.list || [];
+      pageTotal = res.data.count || 0;
+    } else {
+      urlSuggestions = [];
+      pageTotal = 0;
+    }
+  } catch (error) {
+    console.error('搜索失败:', error);
+  } finally {
+    urlLoading.value = false;
+  }
+}, 100);
+function urlHandleFocus() {
+  querySearchAsync('');
+}
+// url-选中
+function handleSelect(item: urlOption) {
+  // 处理选中项)
+  const obj = appList.value.filter((m: any) => m.alias === item.AppAlias)[0];
+  localStg.set('appsItem', obj);
+  const rowItem = {
+    method: item.Method,
+    service_name: item.ServiceName,
+    kind: item.Route,
+    name: item.Route,
+    route: item.Route
+  };
+  localStg.set('row', rowItem);
+  router.push({
+    path: '/service/InterfaceDetail/index',
+    query: {
+      kind: item.Route,
+      method: item.Method,
+      name: item.Route,
+      service_name: item.ServiceName,
+      route: item.Route
+    }
+  });
+}
+
+// 业务查询
+const bizSearchAsync = debounce(async value => {
+  // if (!value) {
+  //   bizSuggestions = [];
+  //   return;
+  // }
+  bizSearchQuery.name = value && value.trim();
+  stateBiz.value = value && value.trim();
+  if (stateBiz.value === '') {
+    bizSearchQuery.pageIndex = 1;
+  }
+  bizLoading.value = true;
+
+  try {
+    const res = await searchBizName(bizSearchQuery);
+    if (res && res.code === 200) {
+      bizSuggestions = res.data.list || [];
+      bizPageTotal = res.data.total || 0;
+    } else {
+      bizSuggestions = [];
+      bizPageTotal = 0;
+    }
+  } catch (error) {
+    console.error('搜索请求失败', error);
+  } finally {
+    bizLoading.value = false;
+  }
+}, 10); // 300ms 防抖延迟
+function bizHandleFocus() {
+  bizSearchAsync('');
+}
+// 业务-滚到底部执行的方法
+async function bizHandelScroll(e: any) {
+  const { scrollHeight, scrollTop, clientHeight } = e.target;
+  if (Number(bizPageTotal) <= bizSuggestions.length) {
+    // 全部已经展示完了
+    bizSearchQuery.pageIndex = 1;
+    return;
+  }
+  if (clientHeight + scrollTop + 2 >= scrollHeight) {
+    // 到底部
+    console.log('到底了~~~~~~');
+    bizSearchQuery.pageIndex += 1;
+    bizLoading.value = true;
+    const result = await searchBizName(bizSearchQuery);
+    // 根据实际接口返回数据格式将数据插入到选项中
+    bizSuggestions = bizSuggestions.concat(result.data.list || []);
+    bizPageTotal = result.data.total || 0;
+    bizLoading.value = false;
+  }
+}
+// 业务-下拉选中
+function bizHandleSelect(item: any) {
+  // 处理选中项
+  const obj = appList.value.filter((m: any) => m.alias === item.app_alias)[0];
+  localStg.set('appsItem', obj);
+  router.push({
+    path: '/business-analysis/business-topics/index',
+    query: {
+      rowHash: item.hash,
+      rowId: item.id,
+      service_name: item.service_name,
+      name: item.name,
+      subconditionValue: item.name
+    }
+  });
+}
+// 表单重置
+async function reset() {
+  await resetFields();
+}
+ /** 新增按钮操作 */
+function handleAdd() {
+  reset()
+  dialogFlag.value = true
+}
+function toggle() {
+  openMonitor.value = !openMonitor.value
+  // router.push({ // todo
+  //   path: '/homeNewIndex',
+  //   query: {
+  //     servicesNumber: this.statusData.services
+  //   }
+  // })
+}
+function logout (){
+  Modal.confirm({
+    title: $t('common.tip'),
+    content: $t('common.logoutConfirm'),
+    okText: $t('common.confirm'),
+    cancelText: $t('common.cancel'),
+    onOk: () => {
+      authStore.logout();
+    }
+  });
+}
+function submitted(){
+  dialogFlag.value = false
+  getList();
+}
+onMounted(() => {
+  getList();
+});
+</script>
+
+<template>
+  <div class="index-wrap">
+    <video
+      v-if="!openMonitor"
+      class="bg_video"
+      data-v-93f40d6c=""
+      data-v-45e53fd3=""
+      autoplay="true"
+      loop="true"
+      muted="true"
+    >
+      <source src="@/assets/home/home.mp4" data-v-93f40d6c="" data-v-45e53fd3="" type="video/mp4" />
+    </video>
+    <div class="index">
+      <div class="header">
+        <div class="header-right">
+          <AButton type="primary" size="small" style="margin-right:12px;vertical-align: middle;" round @click="handleAdd">{{ $t('common.add') }}</AButton>
+          <IconAntDesignSyncOutlined style="font-size:20px;vertical-align: middle;margin-right:12px;" @click="toggle"/>
+          <ADropdown class="avatar-container right-menu-item hover-effect" style="font-size:16px;" trigger="hover">
+            <div class="avatar-wrapper">
+              <span class="user-name">{{ authStore.userInfo.userName || localStg.get('userName') }}</span>
+              <IconAntDesignCaretDownOutlined />
+            </div>
+            <template #overlay>
+              <AMenu>
+                <router-link to="/profile/index">
+                  <AMenuItem key="0">
+                    {{ $t('common.userCenter') }}
+                  </AMenuItem>
+                </router-link>
+                <AMenuItem key="1">
+                  <span style="display:block;" @click="logout">{{ $t('common.logout') }}</span>
+                </AMenuItem>
+              </AMenu>
+            </template>
+          </ADropdown>
+        </div>
+      </div>
+      <div v-if="!openMonitor" class="content">
+        <div class="content-form">
+          <div class="lg-logo">
+            <img src="@/assets/home/app_logo.png" class="lg-img" width="32" height="32" />
+          </div>
+          <AForm ref="queryForm" :model="queryParams" class="content-form-content">
+            <div class="content-form-flex">
+              <ASelect
+                v-model:value="selectOption"
+                placeholder="请选择"
+                class="content-select-left"
+                size="large"
+                :bordered="false"
+              >
+                <ASelectOption value="1">应用系统</ASelectOption>
+                <ASelectOption value="2">接口URL</ASelectOption>
+                <ASelectOption value="3">业务功能</ASelectOption>
+              </ASelect>
+              <AFormItem v-if="selectOption == '1'" label="" name="name">
+                <ASelect
+                  v-model="queryParams.name"
+                  class="content-select-right"
+                  placeholder="请选择要访问的业务系统"
+                  clearable
+                  filterable
+                  value-key="id"
+                  size="large"
+                  :bordered="false"
+                  @change="handChange"
+                >
+                  <ASelectOption v-for="app in appList" :key="app.id" :title="app.name" :value="app.name" />
+                </ASelect>
+              </AFormItem>
+              <!-- 接口URL -->
+              <AFormItem v-if="selectOption == '2'" label="" name="url">
+                <ASelect
+                  v-model:value="stateurl"
+                  :filter-option="false"
+                  class="content-select-right"
+                  placeholder="请输入url"
+                  :bordered="false"
+                  :show-search="true"
+                  :loading="urlLoading"
+                  @search="querySearchAsync"
+                  @focus="urlHandleFocus"
+                  @popup-scroll="handelScroll"
+                  @select="handleSelect"
+                >
+                  <ASelectOption v-for="(item,index) in urlSuggestions" :key="index + item.Route + item.Method + item.ServiceName" :value="item.Method">
+                    <div
+                      v-show="item.Route"
+                      class="ob-select-box"
+                      style="display: flex; align-items: center; justify-content: space-between"
+                    >
+                      <ATooltip placement="bottom" effect="dark" :content="item.Route">
+                        <span class="ob-select-title">{{ item.Route }}</span>
+                      </ATooltip>
+                      <div>
+                        <ATag size="mini" color="success" class="ob-tag-right">{{ item.Method }}</ATag>
+                        <ATag size="mini" color="blue">{{ item.ServiceName }}</ATag>
+                      </div>
+                    </div>
+                  </ASelectOption>
+                </ASelect>
+              </AFormItem>
+
+              <!-- 业务 -->
+              <AFormItem v-if="selectOption == '3'" label="">
+                <ASelect
+                  ref="bizCompleteRef"
+                  v-model:value="stateBiz"
+                  :filter-option="false"
+                  class="content-select-right"
+                  placeholder="请输入业务"
+                  :bordered="false"
+                  :show-search="true"
+                  autofocus
+                  :loading="bizLoading"
+                  @search="bizSearchAsync"
+                  @focus="bizHandleFocus"
+                  @popup-scroll="bizHandelScroll"
+                  @select="bizHandleSelect"
+                >
+                  <ASelectOption v-for="item,index in bizSuggestions" :key="index + item.app_name + item.name" :value="item.name">
+                    <div
+                      v-show="item.name"
+                      class="ob-select-box"
+                      style="display: flex; align-items: center; justify-content: space-between"
+                    >
+                      <ATooltip placement="bottom" effect="dark" :content="item.name">
+                        <span ref="bussinessContent" class="ob-select-title">{{ item.name }}</span>
+                      </ATooltip>
+                      <div>
+                        <ATag size="mini" color="blue">{{ item.app_name }}</ATag>
+                      </div>
+                    </div>
+                  </ASelectOption>
+                </ASelect>
+              </AFormItem>
+            </div>
+          </AForm>
+        </div>
+      </div>
+      <!-- <Monitoring v-if="openMonitor" :services-number="servicesNumber" /> -->
+    </div>
+    <!-- 添加或修改应用配置对话框 -->
+    <AddModal v-model:visible="dialogFlag" @submitted="submitted"/>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.index-wrap {
+  // min-width: 1250px;
+  width: 100%;
+  height: 100%;
+  // min-height: 600px;
+  min-height: 100vh;
+  font-family: Arial, sans-serif;
+  // background: #fff;
+  position: relative;
+  .index {
+    position: relative;
+    padding-bottom: 0;
+    height: 100%;
+    min-height: 600px;
+    text-align: center;
+  }
+  .header {
+    position: absolute;
+    z-index: 99;
+    min-width: 1000px;
+    width: 100%;
+    border-bottom: 0;
+    height: 60px;
+    line-height: 60px;
+    // background: #fff;
+    left: 0px;
+    .header-right {
+      position: absolute;
+      display: flex;
+      align-items: center;
+      right: 0;
+      top: 0;
+      z-index: 100;
+      height: 60px;
+      padding-right: 24px;
+      padding-left: 200px;
+      cursor: pointer;
+      -webkit-tap-highlight-color: transparent;
+    }
+  }
+  .content {
+    position: absolute;
+    top: 50%;
+    left: 50%;
+    transform: translate(-50%, -50%);
+    width: 1000px;
+    margin: 0 auto;
+    .content-form {
+      margin-top: 20px;
+      width: 850px;
+      height: 100%;
+      margin: 0 auto;
+      text-align: left;
+      display: flex;
+      justify-content: center;
+      align-items: center;
+      margin-bottom: 20px;
+    }
+    .content-form-content {
+      flex: 1;
+    }
+    .content-form-flex {
+      width: 100%;
+      display: flex;
+      align-items: center;
+      justify-content: flex-start;
+      height: 60px;
+    }
+    .content-form-flex ::v-deep .ant-select-selector {
+      background: white !important;
+      width: 100%;
+    }
+    .content-select-left ::v-deep .ant-select-selector {
+      width: 120px !important;
+      height: 44px;
+      border-radius: 8px 0 0 8px;
+    }
+    .content-select-right ::v-deep .ant-select-selector {
+      width: 100% !important;
+      height: 44px;
+      border-radius: 0 8px 8px 0;
+      margin-top: 24px;
+    }
+    /* 在这里添加上面的CSS规则 */
+    .content-select-right ::v-deep .ant-select-dropdown {
+      display: block !important;
+    }
+    .content-select-right ::v-deep.ant-select .ant-select-arrow {
+      top: 70%;
+    }
+    .content-form-flex ::v-deep .ant-form-item {
+      width: 100%;
+    }
+
+    .lg-logo {
+      width: 32px;
+      height: 32px;
+      margin-right: 16px;
+    }
+  }
+}
+.bg_video {
+  height: 100vh;
+  width: 100vw;
+  position: absolute;
+  top: 0;
+  left: 0;
+  -o-object-fit: cover;
+  object-fit: cover;
+}
+::v-deep .ob-select-box {
+  display: flex;
+  align-items: center;
+  justify-content: space-between;
+}
+::v-deep .ob-tag-right {
+  margin-right: 10px;
+}
+::v-deep .ob-select-title {
+  overflow: hidden;
+  white-space: nowrap;
+  text-overflow: ellipsis;
+  display: inline-block;
+  max-width: 70%;
+}
+</style>

+ 153 - 0
src/views/new-home/modules/add-modal.vue

@@ -0,0 +1,153 @@
+<script setup lang="ts">
+import { nextTick, reactive, watch } from 'vue';
+import type { UploadProps } from 'ant-design-vue';
+import { useAntdForm, useFormRules } from '@/hooks/common/form';
+import { getToken } from '@/store/modules/auth/shared';
+import { addOtApps, fetchUploadFile } from '@/service/api/home';
+const { formRef, validate, resetFields } = useAntdForm();
+const { defaultRequiredRule, patternRules } = useFormRules();
+defineOptions({
+  name: 'AddModal'
+});
+interface Emits {
+  (e: 'submitted'): void;
+}
+const emit = defineEmits<Emits>();
+const visible = defineModel<boolean>('visible', {
+  default: false
+});
+const labelCol = { style: { width: '100px' } };
+type RuleKey = 'name' | 'alias' | 'desc' | 'contractPhone';
+const rules: Record<RuleKey, App.Global.FormRule> = {
+  name: defaultRequiredRule,
+  alias: defaultRequiredRule,
+  desc: defaultRequiredRule,
+  contractPhone: patternRules.phone
+};
+
+function createDefaultModel(): Api.Home.addAliasParams {
+  return {
+    name: '',
+    alias: '',
+    desc: '',
+    imgUrl: '',
+    contractInfo: '',
+    contractPhone: ''
+  };
+}
+const model: Api.Home.addAliasParams = reactive(createDefaultModel());
+const stateForm = reactive({
+  headers: { Authorization: `Bearer ${getToken()}` },
+  appsfileList: []
+});
+/** 提交按钮 */
+async function submitForm() {
+  await validate();
+  addOtApps(model).then(response => {
+    if (response.code === 200) {
+      window.$message?.success(response.msg);
+      visible.value = false;
+      emit('submitted');
+    } else {
+      window.$message?.error(response.msg);
+    }
+  });
+}
+// 取消按钮
+async function cancel() {
+  visible.value = false;
+  await resetFields();
+}
+function beforeUpload(file: UploadProps['fileList'][number]) {
+  const isRightSize = file.size / 1024 / 1024 < 2;
+  if (!isRightSize) {
+    window.$message?.error('文件大小超过 2MB');
+  }
+  const isJPG = ['image/jpeg', 'image/png', 'image/jpg'].includes(file.type);
+  if (!isJPG) {
+    window.$message?.error('上传头像图片只能是 JPG/PNG/JPEG 格式!');
+  }
+  return isRightSize && isJPG;
+}
+async function customUpload(options:any) {
+  const formdata = new FormData();
+  formdata.append("file",options.file);
+  const res = await fetchUploadFile(formdata);
+  if (res && res.code == 200) {
+    model.imgUrl = `http://${window.location.hostname == 'localhost' ? 'observe-front.cestong.com.cn/re':window.location.hostname}${res.data.full_path}`
+  } else {
+    model.imgUrl = '';
+  }
+}
+
+watch(visible, () => {
+  if (visible.value) {
+    resetFields();
+    nextTick(() => {
+      Object.assign(model, createDefaultModel());
+    });
+  }
+});
+</script>
+
+<template>
+  <div>
+    <!-- 添加或修改应用配置对话框 -->
+    <AModal v-model:open="visible" title="添加应用" :close-on-click-modal="false" class="w-480px">
+      <AForm ref="formRef" :model="model" :rules="rules" :label-col="labelCol" label-align="right">
+        <AFormItem label="应用名称" name="name">
+          <AInput v-model:value="model.name" placeholder="请输入应用名称" />
+        </AFormItem>
+        <AFormItem label="应用别名" name="alias">
+          <AInput v-model:value="model.alias" placeholder="请输入应用别名" />
+        </AFormItem>
+        <AFormItem label="应用备注" name="desc">
+          <ATextarea v-model:value="model.desc" placeholder="请输入应用备注" />
+        </AFormItem>
+        <AFormItem label="应用图片" name="imgUrl" >
+          <div style="display: flex;">
+            <img v-if="model.imgUrl" :src="model.imgUrl" class="upload--picture-card" style="float: left" />
+            <AUpload
+              ref="sys_app_logo"
+              accept=".png,.jpg,.jpeg"
+              :file-list="stateForm.appsfileList"
+              :custom-request="customUpload"
+              :before-upload="beforeUpload"
+              list-type="picture-card"
+              :show-file-list="false"
+            >
+              <IconIcRoundPlus />
+            </AUpload>
+          </div>
+        </AFormItem>
+        <AFormItem label="负责人" name="contractInfo">
+          <AInput v-model:value="model.contractInfo" placeholder="请输入负责人" />
+        </AFormItem>
+        <AFormItem label="手机号码" name="contractPhone">
+          <AInput v-model:value="model.contractPhone" placeholder="请输入合作手机号" />
+        </AFormItem>
+      </AForm>
+      <template #footer>
+        <div class="dialog-footer">
+          <AButton type="primary" @click="submitForm">{{ $t('common.confirm') }}</AButton>
+          <AButton @click="cancel">{{ $t('common.cancel') }}</AButton>
+        </div>
+      </template>
+    </AModal>
+  </div>
+</template>
+
+<style lang="scss"scoped>
+.upload--picture-card{
+  background-color: #fbfdff;
+  border: 1px dashed #c0ccda;
+  border-radius: 6px;
+  -webkit-box-sizing: border-box;
+  box-sizing: border-box;
+  width: 104px;
+  height: 104px;
+  cursor: pointer;
+  line-height: 146px;
+  vertical-align: top;
+}
+</style>

+ 0 - 1
vite.config.ts

@@ -8,7 +8,6 @@ export default defineConfig(configEnv => {
   const viteEnv = loadEnv(configEnv.mode, process.cwd()) as unknown as Env.ImportMeta;
 
   const buildTime = getBuildTime();
-
   return {
     base: viteEnv.VITE_BASE_URL,
     resolve: {