1
0

8 Коммиты 59c828c92f ... 8b87a676a1

Автор SHA1 Сообщение Дата
  wangshun 8b87a676a1 perf(views): 修复"算法管理"模块时间段显示异常问题 4 дней назад
  wangshun ac705a209b chore(utils): 暂时注释tolen获取逻辑 4 дней назад
  wangshun b0e33d2319 perf(views): 完善“报警管理”模块执行动作联系人添加功能 4 дней назад
  wangshun 2c22d8f527 chore(utils): 添加Cookie管理与Pinia状态管理 4 дней назад
  wangshun 7a446534f5 chore(config): 新增依赖"js-cookie“,"@types/js-cookie" 4 дней назад
  wangshun 4edda4c9f7 perf(views): 完成"登陆页"编写 4 дней назад
  wangcong 2dfccfd207 perf(views): 优化实时监测页面设备分组及其列表的获取 4 дней назад
  wangshun ef5d676f80 perf(views): 完成"用户管理"模块多语言添加 4 дней назад

+ 2 - 0
package.json

@@ -28,6 +28,7 @@
     "ant-design-vue": "^4.2.6",
     "dayjs": "^1.11.13",
     "echarts": "^5.6.0",
+    "js-cookie": "^3.0.5",
     "libpag": "^4.2.84",
     "lodash-es": "^4.17.21",
     "pinia": "^2.3.0",
@@ -48,6 +49,7 @@
     "@intlify/unplugin-vue-i18n": "^6.0.3",
     "@meta2d/core": "^1.0.76",
     "@tsconfig/node22": "^22.0.0",
+    "@types/js-cookie": "^3.0.6",
     "@types/jsdom": "^21.1.7",
     "@types/lodash-es": "^4.17.12",
     "@types/node": "^22.10.4",

+ 11 - 0
pnpm-lock.yaml

@@ -31,6 +31,9 @@ importers:
       echarts:
         specifier: ^5.6.0
         version: 5.6.0
+      js-cookie:
+        specifier: ^3.0.5
+        version: 3.0.5
       libpag:
         specifier: ^4.2.84
         version: 4.2.84
@@ -86,6 +89,9 @@ importers:
       '@tsconfig/node22':
         specifier: ^22.0.0
         version: 22.0.0
+      '@types/js-cookie':
+        specifier: ^3.0.6
+        version: 3.0.6
       '@types/jsdom':
         specifier: ^21.1.7
         version: 21.1.7
@@ -1376,6 +1382,9 @@ packages:
   '@types/glob@7.2.0':
     resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==}
 
+  '@types/js-cookie@3.0.6':
+    resolution: {integrity: sha512-wkw9yd1kEXOPnvEeEV1Go1MmxtBJL0RR79aOTAApecWFVu7w0NNXNqhcWgvw2YgZDYadliXkl14pa3WXw5jlCQ==}
+
   '@types/jsdom@21.1.7':
     resolution: {integrity: sha512-yOriVnggzrnQ3a9OKOCxaVuSug3w3/SbOj5i7VwXWZEyUNl3bLF9V3MfxGbZKuwqJOQyRfqXyROBB1CoZLFWzA==}
 
@@ -6813,6 +6822,8 @@ snapshots:
       '@types/minimatch': 5.1.2
       '@types/node': 22.10.4
 
+  '@types/js-cookie@3.0.6': {}
+
   '@types/jsdom@21.1.7':
     dependencies:
       '@types/node': 22.10.4

+ 10 - 1
src/api/index.ts

@@ -81,6 +81,7 @@ import type {
   ListInfo,
   ListInterfaces,
   ListPhysicalInterfaces,
+  loginForm,
   LoginUser,
   MonitoringForm,
   MonitorPointInfo,
@@ -132,6 +133,7 @@ import type {
   TempHumidityControlSettings,
   TreeStructure,
   UploadLogo,
+  UserPageItem,
   UserPageItemData,
   UserPageParams,
   VerificationAgreement,
@@ -168,12 +170,13 @@ const apiBiz = (path: string, params?: unknown) => {
 // 登录和注销
 
 export const loginUser = async (params: LoginUser) => {
-  await request(apiUaa('/oauth/token', params), {
+  const data = await request<loginForm>(apiUaa('/oauth/token', params), {
     method: 'POST',
     headers: {
       Authorization: 'Basic ' + btoa('unimat:unimat'),
     },
   });
+  return data;
 };
 
 export const logoutUser = async () => {
@@ -362,6 +365,12 @@ export const getUserPageList = async (params: UserPageParams) => {
   return data;
 };
 
+// 内部用户表
+export const getOrgUsers = async () => {
+  const data = await request<UserPageItem[]>(apiSys('/inner/sysUser/orgUsers'));
+  return data;
+};
+
 // ----- 业务服务 -----
 
 // 报警事件

BIN
src/assets/img/login-view.png


+ 33 - 3
src/i18n/locales/zh.json

@@ -93,8 +93,10 @@
     "weighted": "加权"
   },
   "common": {
+    "activating": "启用中",
     "add": "添加",
     "addNew": "新增",
+    "addSuccess": "添加成功!",
     "advancedSettings": "高级设置",
     "aiCtrl": "AI智控",
     "all": "所有",
@@ -123,10 +125,13 @@
     "custom": "自定义",
     "dataCenter": "数据中心",
     "date": "日",
+    "deactivated": "停用",
     "delete": "删除",
     "deleteConfirmation": "删除确定",
+    "deleteSuccess": "删除成功!",
     "device": "设备",
     "disable": "禁用",
+    "editSuccess": "编辑成功!",
     "editor": "编辑",
     "emptyData": "请求数据为空",
     "enable": "启用",
@@ -566,7 +571,12 @@
     "userManage": "用户管理",
     "userPermission": "账号管理"
   },
-  "organizationManage": {},
+  "organizationManage": {
+    "organizationDescription": "组织描述",
+    "organizationName": "组织名称",
+    "parentOrganization": "上级组织",
+    "pleaseEnterOrganizationName": "请输入组织名称"
+  },
   "realTimeMonitor": {
     "activePower": "有功功率",
     "addSubtractLoadParameters": "加减载参数",
@@ -730,7 +740,16 @@
     "verificationEquipment": "验证设备",
     "verificationSuccessful": "验证成功"
   },
-  "roleManage": {},
+  "roleManage": {
+    "dataPermission": "数据权限",
+    "deviceGroupPermission": "设备组权限",
+    "functionalPermission": "功能权限",
+    "nameCannotBeEmpty": "名称不能为空!",
+    "nameCannotContainSpaces": "名称不能包含空格!",
+    "operationPermission": "操作权限",
+    "permission": "权限",
+    "viewPermission": "查看权限"
+  },
   "setupProtocol": {
     "addCustomParams": "新增自定义参数",
     "addCustomParamsSuccessful": "新增自定义参数成功",
@@ -854,5 +873,16 @@
     "wrongProtocolFileType": "协议文件格式错误,请上传 .xls,xlsx 格式的文件"
   },
   "standardProtocolLibrary": {},
-  "userManage": {}
+  "userManage": {
+    "accountManagement": "账号管理",
+    "createDate": "创建日期",
+    "defaultPassword": "默认密码8个0",
+    "expiryDate": "到期日期",
+    "fullName": "姓名",
+    "mobileNumberFormatError": "手机号格式错误",
+    "mobilePhoneNumber": "手机号",
+    "password": "密码",
+    "pleaseEnterMobileNumber": "请输入手机号、姓名",
+    "role": "角色"
+  }
 }

+ 20 - 0
src/stores/user-info.ts

@@ -0,0 +1,20 @@
+import { ref } from 'vue';
+import { defineStore } from 'pinia';
+
+export const useUserInfoStore = defineStore('userInfo', () => {
+  const token = ref<string>();
+
+  const saveToken = (val: string) => {
+    token.value = val;
+  };
+
+  const resetToken = () => {
+    token.value = undefined;
+  };
+
+  return {
+    token,
+    saveToken,
+    resetToken,
+  };
+});

+ 7 - 0
src/types/index.ts

@@ -2668,3 +2668,10 @@ export interface LoginUser {
   mobile: string;
   password: string;
 }
+
+export interface loginForm {
+  access_token: string;
+  expires_in: number;
+  refresh_token: string;
+  token_type: string;
+}

+ 15 - 0
src/utils/auth.ts

@@ -0,0 +1,15 @@
+import Cookies from 'js-cookie';
+
+export const TOKEN_KEY = 'X-Token';
+
+export const setToken = (token: string) => {
+  Cookies.set(TOKEN_KEY, token);
+};
+
+export const getToken = () => {
+  return Cookies.get(TOKEN_KEY);
+};
+
+export const removeToken = () => {
+  Cookies.remove(TOKEN_KEY);
+};

+ 4 - 15
src/utils/index.ts

@@ -1,9 +1,11 @@
 import dayjs from 'dayjs';
 import { kebabCase } from 'lodash-es';
 
+// import { useUserInfoStore } from '@/stores/user-info';
 import { t } from '@/i18n';
 import { fileContentTypeRegExp, TimeScaleType } from '@/constants';
 
+// import { removeToken } from './auth';
 import { fetchWithTimeout } from './fetch';
 
 import type { SorterResult } from 'ant-design-vue/es/table/interface';
@@ -17,13 +19,10 @@ export const request = async <T>(url: string, init: RequestInit = {}, timeout?:
   if (typeof init?.body === 'string') {
     headers['Content-Type'] = 'application/json;charset=UTF-8';
   }
-
-  // const { token, saveToken, resetToken } = useUserInfoStore();
-  // const { resetDevInfo } = useDeviceInfoStore();
-
+  // const { token, resetToken } = useUserInfoStore();
   // if (token) {
   //   Object.assign(headers, {
-  //     [TOKEN_KEY]: token,
+  //     Authorization: 'Bearer ' + token,
   //   });
   // }
 
@@ -43,9 +42,7 @@ export const request = async <T>(url: string, init: RequestInit = {}, timeout?:
   if (res.status === 401) {
     // removeToken();
     // resetToken();
-    // resetDevInfo();
     // location.replace('/login');
-
     throw new Error(t('common.reLogin'));
   }
 
@@ -55,14 +52,6 @@ export const request = async <T>(url: string, init: RequestInit = {}, timeout?:
     throw new Error(`HTTP Error: ${res.status} ${errMsg}`);
   }
 
-  // if (res.url.includes('/Uboxlogin')) {
-  //   const token = res.headers.get(TOKEN_KEY);
-
-  //   if (token) {
-  //     setToken(token);
-  //     saveToken(token);
-  //   }
-  // }
   const contentType = res.headers.get('content-type');
   const isBlob = fileContentTypeRegExp.test(contentType || '');
 

+ 6 - 8
src/views/alarm-manage/AlarmExecution.vue

@@ -5,13 +5,14 @@ import DataSelection from '@/components/DataSelection.vue';
 import { t } from '@/i18n';
 
 import type { FormInstance, Rule } from 'ant-design-vue/es/form';
-import type { DataSelectionItem, DictValue, ExecutionAction } from '@/types';
+import type { DataSelectionItem, DictValue, ExecutionAction, UserPageItem } from '@/types';
 
 interface Props {
   index: number;
   form: ExecutionAction;
   executionAction: DictValue[];
   alarmNotifyMethod: DictValue[];
+  orgUsersList: UserPageItem[];
 }
 
 const props = defineProps<Props>();
@@ -102,13 +103,10 @@ defineExpose({
             <ASelect
               class="select-input select-right"
               v-model:value="form.alarmContact"
-              :placeholder="$t('common.plzSelect')"
-            >
-              <ASelectOption value="0">王某某</ASelectOption>
-              <ASelectOption value="1">汪某某</ASelectOption>
-              <ASelectOption value="2">何某某</ASelectOption>
-              <ASelectOption value="3">黄某某</ASelectOption>
-            </ASelect>
+              :options="orgUsersList"
+              :field-names="{ label: 'userName', value: 'id' }"
+              :placeholder="t('common.plzSelect')"
+            />
           </AFormItem>
           <br />
           <AFormItem :label="t('alarmManage.alarmContent')" name="alarmAlertContent" v-if="form.subType === '3'">

+ 19 - 0
src/views/alarm-manage/AlarmManage.vue

@@ -15,6 +15,7 @@ import {
   getAlarmEventGetPageList,
   getAlarmEventInfo,
   getAlarmHistoryList,
+  getOrgUsers,
   updateAlarmEvent,
 } from '@/api';
 import { DictCode } from '@/constants';
@@ -33,6 +34,7 @@ import type {
   EventTrigger,
   ExecutionAction,
   TriggerConditionItem,
+  UserPageItem,
 } from '@/types';
 
 const { handleRequest } = useRequest();
@@ -49,6 +51,7 @@ const triggerConditionRefs = ref<InstanceType<typeof AlarmConditions>[]>([]);
 const judgmentConditionRefs = ref<InstanceType<typeof AlarmConditions>[]>([]);
 const executionActionRefs = ref<InstanceType<typeof AlarmConditions>[]>([]);
 const alarmConditionList = ref<DictValue[]>([]);
+const orgUsersList = ref<UserPageItem[]>([]);
 const alarmPageParams = ref<AlarmPageParams>({
   pageIndex: 1,
   pageSize: 10,
@@ -263,6 +266,14 @@ const convertBtoA = (arr: AlarmEventItem[]): TriggerConditionItem[] | ExecutionA
     // 处理动态字段
     const dynamicFields = item.dataList.reduce(
       (acc, { code, value }) => {
+        if (code === 'alarmContact') {
+          if (value) {
+            acc[code] = Number(value);
+          } else {
+            acc[code] = undefined;
+          }
+          return acc;
+        }
         // 特殊处理时间字段
         if (code === 'alarmScheduledTime') {
           const timeStr = dayjs(value, 'HH:mm');
@@ -593,6 +604,12 @@ const okConfirm = async () => {
   }
 };
 
+const getOrgUsersList = () => {
+  handleRequest(async () => {
+    orgUsersList.value = await getOrgUsers();
+  });
+};
+
 onMounted(() => {
   handleRequest(async () => {
     await getAlarmCondition();
@@ -604,6 +621,7 @@ onMounted(() => {
     await getAlarmNotifyMethod();
   });
   getAlarmEventGetPageData();
+  getOrgUsersList();
 });
 </script>
 
@@ -744,6 +762,7 @@ onMounted(() => {
             :form="item"
             :execution-action="executionAction"
             :alarm-notify-method="alarmNotifyMethod"
+            :org-users-list="orgUsersList"
           />
         </div>
 

+ 2 - 2
src/views/algorithm-manage/AlgorithmManage.vue

@@ -118,9 +118,9 @@ const cancelClick = () => {
                   <AFlex align="center" class="interval-width">
                     <div>{{ t('algorithmManage.periodTime') }}:</div>
                     <AFlex align="center">
-                      <div>{{ item.endTime }}</div>
-                      <div>-</div>
                       <div>{{ item.startTime }}</div>
+                      <div>-</div>
+                      <div>{{ item.endTime }}</div>
                     </AFlex>
                   </AFlex>
                   <AFlex align="center" class="interval-width">

+ 38 - 5
src/views/login-component/LoginView.vue

@@ -1,14 +1,20 @@
 <script setup lang="ts">
 import { ref } from 'vue';
+import { useRouter } from 'vue-router';
 
 import SvgIcon from '@/components/SvgIcon.vue';
 import { useRequest } from '@/hooks/request';
+import { useUserInfoStore } from '@/stores/user-info';
 import { t } from '@/i18n';
 import { loginUser } from '@/api';
 
+import { setToken } from '../../utils/auth';
+
 import type { Rule } from 'ant-design-vue/es/form';
 import type { LoginUser } from '@/types';
 
+const router = useRouter();
+const { saveToken } = useUserInfoStore();
 const { handleRequest } = useRequest();
 const loginForm = ref<LoginUser>({
   mobile: '',
@@ -33,10 +39,15 @@ const isValidPhone = (phone: string): boolean => {
 };
 const addLog = () => {
   handleRequest(async () => {
-    await loginUser({
+    const { access_token } = await loginUser({
       grant_type: 'mobile_password',
       ...loginForm.value,
     });
+    if (access_token) {
+      saveToken(access_token);
+      setToken(access_token);
+      router.push('/env-monitor/index');
+    }
   });
 };
 </script>
@@ -44,11 +55,13 @@ const addLog = () => {
 <template>
   <div>
     <AFlex>
-      <div class="login-div">
-        <AButton type="text" @click="$router.go(-1)">返回</AButton>
+      <div class="login-bgc">
+        <AButton type="text" @click="$router.go(-1)">
+          <AFlex align="center"> <SvgIcon class="icon-left" name="left" /> 返回 </AFlex>
+        </AButton>
       </div>
 
-      <AFlex class="login-div" justify="center" align="center">
+      <AFlex class="login-div" align="center">
         <div>
           <div class="login-title">暖通智控平台</div>
           <AForm
@@ -82,6 +95,12 @@ const addLog = () => {
 </template>
 
 <style lang="scss" scoped>
+.icon-left {
+  margin-right: 12px;
+  font-size: 14px;
+  color: #999;
+}
+
 .icon-style {
   margin-right: 10px;
 }
@@ -108,8 +127,22 @@ const addLog = () => {
   text-align: left;
 }
 
+.login-bgc {
+  width: 710px;
+  height: 100vh;
+  padding-top: 32px;
+  padding-left: 30px;
+  zoom: 1;
+  background: url('../../assets/img/login-view.png') no-repeat center 0;
+  background-repeat: no-repeat;
+  background-attachment: fixed;
+  background-position: 0, 0;
+  background-size: 710px 100vh;
+}
+
 .login-div {
-  width: 50vw;
+  width: calc(100vw - 710px);
   height: 100vh;
+  padding-left: 154px;
 }
 </style>

+ 28 - 18
src/views/organization-manage/OrganizationManage.vue

@@ -1,5 +1,6 @@
 <script setup lang="ts">
 import { computed, onMounted, reactive, ref, useTemplateRef } from 'vue';
+import { message } from 'ant-design-vue';
 import dayjs from 'dayjs';
 
 import ConfirmModal from '@/components/ConfirmModal.vue';
@@ -36,37 +37,37 @@ const useForm = reactive<CreateCustomer>({
 
 const organizationColumns = [
   {
-    title: '组织名称',
+    title: t('organizationManage.organizationName'),
     dataIndex: 'orgName',
     key: 'orgName',
     ellipsis: true,
   },
   {
-    title: '上级组织',
+    title: t('organizationManage.parentOrganization'),
     dataIndex: 'parentOrgName',
     key: 'parentOrgName',
     ellipsis: true,
   },
   {
-    title: '组织描述',
+    title: t('organizationManage.organizationDescription'),
     dataIndex: 'remark',
     key: 'remark',
     ellipsis: true,
   },
   {
-    title: '创建日期',
+    title: t('userManage.createDate'),
     dataIndex: 'startTenancy',
     key: 'startTenancy',
     ellipsis: true,
   },
   {
-    title: '到期日期',
+    title: t('userManage.expiryDate'),
     dataIndex: 'endTenancy',
     key: 'endTenancy',
     ellipsis: true,
   },
   {
-    title: '操作',
+    title: t('common.operation'),
     dataIndex: 'action',
     key: 'action',
     width: 80,
@@ -77,28 +78,28 @@ const rules = computed<FormRules<RegisterGatewayForm>>(() => {
     orgName: [
       {
         required: true,
-        message: '不能为空',
+        message: t('common.cannotEmpty'),
         trigger: 'change',
       },
     ],
     leaseTerm: [
       {
         required: true,
-        message: '不能为空',
+        message: t('common.cannotEmpty'),
         trigger: 'change',
       },
     ],
     stationsNumber: [
       {
         required: true,
-        message: '不能为空',
+        message: t('common.cannotEmpty'),
         trigger: 'change',
       },
     ],
     dataValidityPeriod: [
       {
         required: true,
-        message: '不能为空',
+        message: t('common.cannotEmpty'),
         trigger: 'change',
       },
     ],
@@ -164,6 +165,11 @@ const saveOrganization = async () => {
   try {
     await Promise.all([organizationRefs.value?.validate() || Promise.resolve()]);
     await establishOrganizationRef.value?.finish?.();
+    if (organizationTitle.value) {
+      message.success(t('common.addSuccess'));
+    } else {
+      message.success(t('common.editSuccess'));
+    }
     organizationOpen.value = false;
     getOrganizationAll();
     // eslint-disable-next-line @typescript-eslint/no-unused-vars
@@ -193,13 +199,13 @@ onMounted(() => {
 <template>
   <div>
     <AFlex justify="space-between" class="div-bottom">
-      <div class="text-top">组织管理</div>
+      <div class="text-top">{{ t('navigation.organizationManage') }}</div>
       <AFlex align="center">
         <!--<div class="text-restriction">还可创建1个</div> -->
         <AButton type="primary" class="icon-button" @click="addOrganization">
           <AFlex align="center">
             <SvgIcon name="plus" />
-            <span> 添加 </span>
+            <span> {{ t('common.add') }} </span>
           </AFlex>
         </AButton>
       </AFlex>
@@ -209,12 +215,16 @@ onMounted(() => {
       <div class="organization-div">
         <AFlex justify="space-between" class="div-bottom">
           <AFlex align="center">
-            <div class="organization-content-text">搜索</div>
-            <AInput v-model:value="orgName" class="input-width" placeholder="请输入组织名称" />
+            <div class="organization-content-text">{{ t('common.search') }}</div>
+            <AInput
+              v-model:value="orgName"
+              class="input-width"
+              :placeholder="t('organizationManage.pleaseEnterOrganizationName')"
+            />
           </AFlex>
           <div>
-            <AButton type="primary" @click="addQuery"> 查询 </AButton>
-            <AButton class="button-left" type="primary" ghost @click="addReset"> 重置 </AButton>
+            <AButton type="primary" @click="addQuery"> {{ t('common.query') }} </AButton>
+            <AButton class="button-left" type="primary" ghost @click="addReset"> {{ t('common.reset') }} </AButton>
           </div>
         </AFlex>
         <div class="organization-table">
@@ -232,7 +242,7 @@ onMounted(() => {
     <AModal
       class="organization-modal"
       v-model:open="organizationOpen"
-      :title="organizationTitle ? '添加' : '编辑'"
+      :title="organizationTitle ? t('common.add') : t('common.editor')"
       width="920px"
       :mask-closable="false"
       :footer="null"
@@ -250,7 +260,7 @@ onMounted(() => {
 
       <AFlex justify="flex-end" class="footer">
         <AButton type="primary" ghost @click="cancelOrganization">{{ $t('common.cancel') }}</AButton>
-        <AButton type="primary" class="dev-left" @click="saveOrganization">保存</AButton>
+        <AButton type="primary" class="dev-left" @click="saveOrganization">{{ t('common.save') }}</AButton>
       </AFlex>
     </AModal>
 

+ 35 - 33
src/views/real-time-monitor/RealTimeMonitor.vue

@@ -69,39 +69,41 @@ const transformGroupsAndDevices = (groups: DeviceGroupItem[], devices: AllDevice
   });
 
   // 转换顶级分组
-  return groups.map((group) => {
-    // 转换当前组
-    const transformed: DeviceGroupTree = {
-      ...group,
-      label: group.groupName,
-      children: [],
-    };
-
-    // 转换子分组(二级分组),并过滤掉没有设备的分组
-    if (group.deviceGroupChilds && group.deviceGroupChilds.length > 0) {
-      transformed.children = group.deviceGroupChilds
-        .map((child) => {
-          const transformedChild: DeviceGroupTreeChild = {
-            ...child,
-            label: child.groupName,
-            children: [],
-          };
-
-          // 查找并添加属于这个二级分组的设备
-          const childDevices = deviceMap.get(child.id) || [];
-
-          transformedChild.children = childDevices.map((device) => ({
-            ...device,
-            label: device.deviceName,
-          }));
-
-          return transformedChild;
-        })
-        .filter((child) => child.children.length > 0); // 过滤掉没有设备的二级分组
-    }
-
-    return transformed;
-  });
+  return groups
+    .map((group) => {
+      // 转换当前组
+      const transformed: DeviceGroupTree = {
+        ...group,
+        label: group.groupName,
+        children: [],
+      };
+
+      // 转换子分组(二级分组),并过滤掉没有设备的分组
+      if (group.deviceGroupChilds && group.deviceGroupChilds.length > 0) {
+        transformed.children = group.deviceGroupChilds
+          .map((child) => {
+            const transformedChild: DeviceGroupTreeChild = {
+              ...child,
+              label: child.groupName,
+              children: [],
+            };
+
+            // 查找并添加属于这个二级分组的设备
+            const childDevices = deviceMap.get(child.id) || [];
+
+            transformedChild.children = childDevices.map((device) => ({
+              ...device,
+              label: device.deviceName,
+            }));
+
+            return transformedChild;
+          })
+          .filter((child) => child.children.length > 0); // 过滤掉没有设备的二级分组
+      }
+
+      return transformed;
+    })
+    .filter((parent) => parent.children.length > 0); // 过滤掉没有二级分组的一级分组
 };
 
 const checkModuleInfo = () => {

+ 17 - 14
src/views/role-manage/RoleManage.vue

@@ -110,6 +110,7 @@ const confirm = () => {
   handleRequest(async () => {
     if (characterId.value) {
       await deleteCharacter(characterId.value);
+      message.success(t('common.deleteSuccess'));
       if (orgId.value) {
         getFindRolesByOrg(orgId.value);
       }
@@ -141,8 +142,10 @@ const editorCharacter = (index: number, name: string) => {
       handleRequest(async () => {
         if (id) {
           await updateCharacter({ roleName: name, orgId: orgId.value, id });
+          message.success(t('common.editSuccess'));
         } else {
           await addCharacter({ roleName: name, orgId: orgId.value });
+          message.success(t('common.addSuccess'));
         }
         enterShow.value = false;
         if (orgId.value) {
@@ -150,10 +153,10 @@ const editorCharacter = (index: number, name: string) => {
         }
       });
     } else {
-      return message.warning('名称中不能包含空格!');
+      return message.warning(t('roleManage.nameCannotContainSpaces'));
     }
   } else {
-    return message.warning('名称不能为空!');
+    return message.warning(t('roleManage.nameCannotBeEmpty'));
   }
 };
 const editorPermission = () => {
@@ -363,16 +366,16 @@ onMounted(() => {
 
 <template>
   <div>
-    <div class="text-top">角色管理</div>
+    <div class="text-top">{{ t('navigation.roleManage') }}</div>
     <AFlex>
       <OrganizationalStructure @change="clickOrganizationChange" />
       <div class="content">
         <AFlex justify="space-between" align="center" class="content-top">
-          <div class="content-text">角色</div>
+          <div class="content-text">{{ t('userManage.role') }}</div>
           <div class="icon-style pointer" @click="addCharacterName">
             <AFlex align="center"
               ><SvgIcon name="plus" />
-              <div class="text-left">添加</div>
+              <div class="text-left">{{ t('common.add') }}</div>
             </AFlex>
           </div>
         </AFlex>
@@ -406,32 +409,32 @@ onMounted(() => {
 
       <div class="permission-management">
         <AFlex justify="space-between" align="center" class="content-top">
-          <div class="content-text">权限</div>
+          <div class="content-text">{{ t('roleManage.permission') }}</div>
           <div class="pointer" @click="editorPermission" v-if="editorChecked">
             <AFlex align="center"
               ><SvgIcon name="edit-o" />
-              <div class="text-left">编辑</div>
+              <div class="text-left">{{ t('common.editor') }}</div>
             </AFlex>
           </div>
           <AFlex v-else>
             <div class="pointer" @click="cancelPermission">
               <AFlex align="center"
                 ><SvgIcon name="close" />
-                <div class="text-left">取消</div>
+                <div class="text-left">{{ t('common.cancel') }}</div>
               </AFlex>
             </div>
             <div class="pointer pointer-left" @click="savePermission">
               <AFlex align="center"
                 ><SvgIcon name="close" />
-                <div class="text-left">保存</div>
+                <div class="text-left">{{ t('common.save') }}</div>
               </AFlex>
             </div>
           </AFlex>
         </AFlex>
 
         <ARadioGroup v-model:value="permissions" button-style="solid" size="large" @change="addRadioGroup">
-          <ARadioButton value="dataPermissions">数据权限</ARadioButton>
-          <ARadioButton value="functionPermissions">功能权限</ARadioButton>
+          <ARadioButton value="dataPermissions">{{ t('roleManage.dataPermission') }}</ARadioButton>
+          <ARadioButton value="functionPermissions">{{ t('roleManage.functionalPermission') }}</ARadioButton>
         </ARadioGroup>
         <div v-if="permissions === 'dataPermissions'">
           <AFlex align="center" class="device-permissions">
@@ -441,7 +444,7 @@ onMounted(() => {
               :indeterminate="indeterminate"
               :disabled="editorChecked"
               @change="selectAll"
-              >设备组权限</ACheckbox
+              >{{ t('roleManage.deviceGroupPermission') }}</ACheckbox
             >
           </AFlex>
           <div class="permission-div">
@@ -471,7 +474,7 @@ onMounted(() => {
         </div>
         <div v-if="permissions === 'functionPermissions'">
           <AFlex align="center" class="device-permissions">
-            <div>查看权限</div>
+            <div>{{ t('roleManage.viewPermission') }}</div>
           </AFlex>
           <div class="check-div">
             <ATree
@@ -488,7 +491,7 @@ onMounted(() => {
           </div>
 
           <AFlex align="center" class="device-permissions div-top">
-            <div>操作权限</div>
+            <div>{{ $t('roleManage.operationPermission') }}</div>
           </AFlex>
           <div class="operation">
             <AFlex align="center" v-for="(item, index) in operationpermissions" :key="index" class="operation-div">

+ 47 - 31
src/views/user-manage/UserManage.vue

@@ -47,37 +47,37 @@ const accountForm = ref<AccountForm>({
 const accountList = ref<UserPageItem[]>([]);
 const accountColumns = [
   {
-    title: '手机号',
+    title: t('userManage.mobilePhoneNumber'),
     dataIndex: 'mobile',
     key: 'mobile',
     ellipsis: true,
   },
   {
-    title: '姓名',
+    title: t('userManage.fullName'),
     dataIndex: 'userName',
     key: 'userName',
     ellipsis: true,
   },
   {
-    title: '角色',
+    title: t('userManage.role'),
     dataIndex: 'roleName',
     key: 'roleName',
     ellipsis: true,
   },
   {
-    title: '创建日期',
+    title: t('userManage.createDate'),
     dataIndex: 'startTenancy',
     key: 'startTenancy',
     ellipsis: true,
   },
   {
-    title: '到期日期',
+    title: t('userManage.expiryDate'),
     dataIndex: 'endTenancy',
     key: 'endTenancy',
     ellipsis: true,
   },
   {
-    title: '状态',
+    title: t('common.status'),
     dataIndex: 'enabled',
     key: 'enabled',
     ellipsis: true,
@@ -90,7 +90,7 @@ const rules: Record<string, Rule[]> = {
     {
       validator: (_rule: unknown, value: string) => {
         if (!isValidPhone(value)) {
-          return Promise.reject('手机号格式错误');
+          return Promise.reject(t('userManage.mobileNumberFormatError'));
         }
         return Promise.resolve();
       },
@@ -111,6 +111,8 @@ const accountChange = (selectedRowKeys: Key[]) => {
 const confirm = () => {
   handleRequest(async () => {
     await batchDeleteAccount(accountKeys.value as number[]);
+
+    message.success(t('common.deleteSuccess'));
     getUserList();
     modalComponentRef.value?.hideView();
   });
@@ -196,6 +198,7 @@ const bindingAccount = () => {
             endTenancy: accountTerm![1].format('YYYY-MM-DD'),
             orgId: orgId.value as number,
           });
+          message.success(t('common.addSuccess'));
         } else {
           await updateAccount({
             id: accountId.value,
@@ -208,6 +211,7 @@ const bindingAccount = () => {
             endTenancy: accountTerm![1].format('YYYY-MM-DD'),
             orgId: orgId.value as number,
           });
+          message.success(t('common.editSuccess'));
         }
         getUserList();
 
@@ -244,18 +248,18 @@ const getUserList = () => {
 <template>
   <div>
     <AFlex justify="space-between" class="account-header">
-      <div class="text-top">账号管理</div>
+      <div class="text-top">{{ t('userManage.accountManagement') }}</div>
       <div>
         <AButton class="icon-button default-button" @click="addDelete">
           <AFlex align="center">
             <SvgIcon name="delete" />
-            <span>删除</span>
+            <span>{{ t('common.delete') }}</span>
           </AFlex>
         </AButton>
         <AButton type="primary" class="icon-button button-monitoring" @click="addOpenAccount">
           <AFlex align="center">
             <SvgIcon name="plus" />
-            <span> 添加 </span>
+            <span> {{ t('common.add') }} </span>
           </AFlex>
         </AButton>
       </div>
@@ -265,11 +269,15 @@ const getUserList = () => {
       <div class="account-content">
         <AFlex wrap="wrap" justify="space-between">
           <AFlex align="center" class="margin-bottom">
-            <div class="account-content-text">搜索</div>
-            <AInput v-model:value="searchContent" class="input-width" placeholder="请输入手机号、姓名" />
+            <div class="account-content-text">{{ $t('common.search') }}</div>
+            <AInput
+              v-model:value="searchContent"
+              class="input-width"
+              :placeholder="t('userManage.pleaseEnterMobileNumber')"
+            />
           </AFlex>
           <AFlex align="center" class="margin-bottom">
-            <div class="account-content-text">角色</div>
+            <div class="account-content-text">{{ t('userManage.role') }}</div>
 
             <ASelect
               class="input-width"
@@ -281,7 +289,7 @@ const getUserList = () => {
             />
           </AFlex>
           <AFlex align="center" class="margin-bottom">
-            <div class="account-content-text">创建日期</div>
+            <div class="account-content-text">{{ t('userManage.createDate') }}</div>
             <ARangePicker
               v-model:value="accountTerm"
               class="input-width"
@@ -291,21 +299,21 @@ const getUserList = () => {
           </AFlex>
 
           <AFlex align="center" class="margin-bottom">
-            <div class="account-content-text">状态</div>
+            <div class="account-content-text">{{ $t('common.status') }}</div>
             <ASelect
               class="input-width"
               v-model:value="accountPageParam.enabled"
               :placeholder="$t('common.plzSelect')"
               :allow-clear="true"
             >
-              <ASelectOption value="1">正常</ASelectOption>
-              <ASelectOption value="0">停用</ASelectOption>
+              <ASelectOption value="1">{{ t('envMonitor.normal') }}</ASelectOption>
+              <ASelectOption value="0">{{ t('common.deactivated') }}</ASelectOption>
             </ASelect>
           </AFlex>
         </AFlex>
         <AFlex class="margin-bottom" justify="flex-end">
-          <AButton type="primary" @click="addQuery"> 查询 </AButton>
-          <AButton class="default-button margin-left" @click="addReset"> 重置 </AButton>
+          <AButton type="primary" @click="addQuery"> {{ t('common.query') }} </AButton>
+          <AButton class="default-button margin-left" @click="addReset"> {{ t('common.reset') }} </AButton>
         </AFlex>
         <ATable
           :row-selection="{
@@ -323,8 +331,8 @@ const getUserList = () => {
               <div @click="addCheck(record as UserPageItem)" class="mobile-phone">{{ record.mobile }}</div>
             </template>
             <template v-if="column.key === 'enabled'">
-              <div v-if="record.enabled == 1" class="tag-style success">启用中</div>
-              <div v-else class="tag-style default">停用</div>
+              <div v-if="record.enabled == 1" class="tag-style success">{{ t('common.activating') }}</div>
+              <div v-else class="tag-style default">{{ t('common.deactivated') }}</div>
             </template>
           </template>
         </ATable>
@@ -345,7 +353,7 @@ const getUserList = () => {
     </AFlex>
     <AModal
       v-model:open="accountOpen"
-      :title="titleAccount ? '添加' : '编辑'"
+      :title="titleAccount ? t('common.add') : t('common.editor')"
       width="460px"
       :footer="null"
       :mask-closable="false"
@@ -362,13 +370,17 @@ const getUserList = () => {
         class="form-style"
       >
         <AFlex :vertical="true">
-          <AFormItem label="姓名" name="userName">
-            <AInput class="input-width" v-model:value="accountForm.userName" placeholder="请输入" />
+          <AFormItem :label="t('userManage.fullName')" name="userName">
+            <AInput class="input-width" v-model:value="accountForm.userName" :placeholder="t('common.pleaseEnter')" />
           </AFormItem>
-          <AFormItem label="手机号" name="mobile">
-            <AInputNumber class="input-width" v-model:value="accountForm.mobile" placeholder="请输入" />
+          <AFormItem :label="t('userManage.mobilePhoneNumber')" name="mobile">
+            <AInputNumber
+              class="input-width"
+              v-model:value="accountForm.mobile"
+              :placeholder="t('common.pleaseEnter')"
+            />
           </AFormItem>
-          <AFormItem label="角色" name="roleId">
+          <AFormItem :label="t('userManage.role')" name="roleId">
             <ASelect
               class="input-width"
               v-model:value="accountForm.roleId"
@@ -377,7 +389,7 @@ const getUserList = () => {
               :placeholder="t('common.plzSelect')"
             />
           </AFormItem>
-          <AFormItem label="到期日期" name="accountTerm">
+          <AFormItem :label="t('userManage.expiryDate')" name="accountTerm">
             <ARangePicker
               v-model:value="accountForm.accountTerm"
               class="input-width"
@@ -385,10 +397,14 @@ const getUserList = () => {
               :separator="$t('common.to')"
             />
           </AFormItem>
-          <AFormItem label="密码" name="password">
-            <AInput class="input-width" v-model:value="accountForm.password" placeholder="默认密码8个0" />
+          <AFormItem :label="t('userManage.password')" name="password">
+            <AInput
+              class="input-width"
+              v-model:value="accountForm.password"
+              :placeholder="t('userManage.defaultPassword')"
+            />
           </AFormItem>
-          <AFormItem label="启用" name="enabled">
+          <AFormItem :label="t('alarmManage.enable')" name="enabled">
             <ASwitch v-model:checked="accountForm.enabled" />
           </AFormItem>
         </AFlex>