1
0

9 Коммиты c71dadcc4f ... 59c828c92f

Автор SHA1 Сообщение Дата
  wangshun 59c828c92f perf(views): 初步编写"登陆页" 1 неделя назад
  wangshun 3793bdd28b perf(views): 编写首页 1 неделя назад
  wangshun b6273de9cf chore(config): 新增依赖"libpag“,"rollup-plugin-copy" 1 неделя назад
  wangshun a922ff0331 perf(views): 修复"创建组织"步骤设备类型查询异常问题 1 неделя назад
  wangshun d670a70c88 perf(views): 完成"创建账号"步骤新增,查询功能 1 неделя назад
  wangshun 2614bff331 perf(views): 完成"创建角色"步骤查询,新增,编辑,删除功能 1 неделя назад
  wangshun f72dd24f94 perf(views): 完成"组织管理"页面编辑功能 1 неделя назад
  wangshun 98124b3252 perf(views): 完成"角色管理"页面功能权限功能 1 неделя назад
  wangshun 9797c490f0 chore(api): 添加用户管理模块相关接口 1 неделя назад

+ 2 - 0
package.json

@@ -28,9 +28,11 @@
     "ant-design-vue": "^4.2.6",
     "dayjs": "^1.11.13",
     "echarts": "^5.6.0",
+    "libpag": "^4.2.84",
     "lodash-es": "^4.17.21",
     "pinia": "^2.3.0",
     "qs": "^6.14.0",
+    "rollup-plugin-copy": "^3.5.0",
     "simplebar-vue": "^2.4.0",
     "vue": "^3.5.13",
     "vue-color-kit": "^1.0.6",

+ 87 - 1
pnpm-lock.yaml

@@ -31,6 +31,9 @@ importers:
       echarts:
         specifier: ^5.6.0
         version: 5.6.0
+      libpag:
+        specifier: ^4.2.84
+        version: 4.2.84
       lodash-es:
         specifier: ^4.17.21
         version: 4.17.21
@@ -40,6 +43,9 @@ importers:
       qs:
         specifier: ^6.14.0
         version: 6.14.0
+      rollup-plugin-copy:
+        specifier: ^3.5.0
+        version: 3.5.0
       simplebar-vue:
         specifier: ^2.4.0
         version: 2.4.0(vue@3.5.13(typescript@5.6.3))
@@ -1364,6 +1370,12 @@ packages:
   '@types/estree@1.0.6':
     resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==}
 
+  '@types/fs-extra@8.1.5':
+    resolution: {integrity: sha512-0dzKcwO+S8s2kuF5Z9oUWatQJj5Uq/iqphEtE3GQJVRRYm/tD1LglU2UnXi2A8jLq5umkGouOXOR9y0n613ZwQ==}
+
+  '@types/glob@7.2.0':
+    resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==}
+
   '@types/jsdom@21.1.7':
     resolution: {integrity: sha512-yOriVnggzrnQ3a9OKOCxaVuSug3w3/SbOj5i7VwXWZEyUNl3bLF9V3MfxGbZKuwqJOQyRfqXyROBB1CoZLFWzA==}
 
@@ -1379,6 +1391,9 @@ packages:
   '@types/lodash@4.17.15':
     resolution: {integrity: sha512-w/P33JFeySuhN6JLkysYUK2gEmy9kHHFN7E8ro0tkfmlDOgxBDzWEZ/J8cWA+fHqFevpswDTFZnDx+R9lbL6xw==}
 
+  '@types/minimatch@5.1.2':
+    resolution: {integrity: sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==}
+
   '@types/node@22.10.4':
     resolution: {integrity: sha512-99l6wv4HEzBQhvaU/UGoeBoCK61SCROQaCCGyQSgX2tEQ3rKkNZ2S7CEWnS/4s1LV+8ODdK21UeyR1fHP2mXug==}
 
@@ -2134,6 +2149,9 @@ packages:
   colord@2.9.3:
     resolution: {integrity: sha512-jeC1axXpnb0/2nn/Y1LPuLdgXBLH7aDcHu4KEKfqw3CUhX7ZpfBSlPKyqXE6btIgEzfWtrX3/tyBCaCvXvMkOw==}
 
+  colorette@1.4.0:
+    resolution: {integrity: sha512-Y2oEozpomLn7Q3HFP7dpww7AtMJplbM9lGZP6RDfHqmbeRjiwRg4n6VM6j4KLmRke85uWEI7JqF17f3pqdRA0g==}
+
   colorette@2.0.20:
     resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==}
 
@@ -2952,6 +2970,10 @@ packages:
     resolution: {integrity: sha512-PmDi3uwK5nFuXh7XDTlVnS17xJS7vW36is2+w3xcv8SVxiB4NyATf4ctkVY5bkSjX0Y4nbvZCq1/EjtEyr9ktw==}
     engines: {node: '>=14.14'}
 
+  fs-extra@8.1.0:
+    resolution: {integrity: sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==}
+    engines: {node: '>=6 <7 || >=8'}
+
   fs-extra@9.1.0:
     resolution: {integrity: sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==}
     engines: {node: '>=10'}
@@ -3110,6 +3132,10 @@ packages:
     resolution: {integrity: sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==}
     engines: {node: '>= 0.4'}
 
+  globby@10.0.1:
+    resolution: {integrity: sha512-sSs4inE1FB2YQiymcmTv6NWENryABjUNPeWhOvmn4SjtKybglsyPZxFB3U1/+L1bYi0rNZDqCLlHyLYDl1Pq5A==}
+    engines: {node: '>=8'}
+
   globby@11.1.0:
     resolution: {integrity: sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==}
     engines: {node: '>=10'}
@@ -3677,6 +3703,9 @@ packages:
     resolution: {integrity: sha512-WYDyuc/uFcGp6YtM2H0uKmUwieOuzeE/5YocFJLnLfclZ4inf3mRn8ZVy1s7Hxji7Jxm6Ss8gqpexD/GlKoGgg==}
     engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
 
+  jsonfile@4.0.0:
+    resolution: {integrity: sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==}
+
   jsonfile@6.1.0:
     resolution: {integrity: sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==}
 
@@ -3721,6 +3750,9 @@ packages:
     resolution: {integrity: sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==}
     engines: {node: '>= 0.8.0'}
 
+  libpag@4.2.84:
+    resolution: {integrity: sha512-5BTAyswCjPillWtgUx9QpiwEw030qMQLH+S0MWFT7J9LgYtEpFAICq8ycBFTmnIHW8m1fPXwTHFzMcV+Gmfw+Q==}
+
   lilconfig@3.1.3:
     resolution: {integrity: sha512-/vlFKAoH5Cgt3Ie+JLhRbwOsCQePABiU3tJ1egGvyQ+33R/vcwM2Zl2QR/LzjsBeItPt3oSVXapn+m4nQDvpzw==}
     engines: {node: '>=14'}
@@ -4604,6 +4636,10 @@ packages:
     engines: {node: 20 || >=22}
     hasBin: true
 
+  rollup-plugin-copy@3.5.0:
+    resolution: {integrity: sha512-wI8D5dvYovRMx/YYKtUNt3Yxaw4ORC9xo6Gt9t22kveWz1enG9QrhVlagzwrxSC455xD1dHMKhIJkbsQ7d48BA==}
+    engines: {node: '>=8.3'}
+
   rollup@4.29.1:
     resolution: {integrity: sha512-RaJ45M/kmJUzSWDs1Nnd5DdV4eerC98idtUOVr6FfKcgxqvjwHmxc5upLF9qZU9EpsVzzhleFahrT3shLuJzIw==}
     engines: {node: '>=18.0.0', npm: '>=8.0.0'}
@@ -5312,6 +5348,10 @@ packages:
     resolution: {integrity: sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w==}
     engines: {node: ^12.13.0 || ^14.15.0 || >=16.0.0}
 
+  universalify@0.1.2:
+    resolution: {integrity: sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==}
+    engines: {node: '>= 4.0.0'}
+
   universalify@2.0.1:
     resolution: {integrity: sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==}
     engines: {node: '>= 10.0.0'}
@@ -6764,6 +6804,15 @@ snapshots:
 
   '@types/estree@1.0.6': {}
 
+  '@types/fs-extra@8.1.5':
+    dependencies:
+      '@types/node': 22.10.4
+
+  '@types/glob@7.2.0':
+    dependencies:
+      '@types/minimatch': 5.1.2
+      '@types/node': 22.10.4
+
   '@types/jsdom@21.1.7':
     dependencies:
       '@types/node': 22.10.4
@@ -6780,6 +6829,8 @@ snapshots:
 
   '@types/lodash@4.17.15': {}
 
+  '@types/minimatch@5.1.2': {}
+
   '@types/node@22.10.4':
     dependencies:
       undici-types: 6.20.0
@@ -7797,6 +7848,8 @@ snapshots:
 
   colord@2.9.3: {}
 
+  colorette@1.4.0: {}
+
   colorette@2.0.20: {}
 
   colors-cli@1.0.33: {}
@@ -8853,6 +8906,12 @@ snapshots:
       jsonfile: 6.1.0
       universalify: 2.0.1
 
+  fs-extra@8.1.0:
+    dependencies:
+      graceful-fs: 4.2.11
+      jsonfile: 4.0.0
+      universalify: 0.1.2
+
   fs-extra@9.1.0:
     dependencies:
       at-least-node: 1.0.0
@@ -9036,6 +9095,17 @@ snapshots:
       define-properties: 1.2.1
       gopd: 1.2.0
 
+  globby@10.0.1:
+    dependencies:
+      '@types/glob': 7.2.0
+      array-union: 2.1.0
+      dir-glob: 3.0.1
+      fast-glob: 3.3.3
+      glob: 7.2.3
+      ignore: 5.3.2
+      merge2: 1.4.1
+      slash: 3.0.0
+
   globby@11.1.0:
     dependencies:
       array-union: 2.1.0
@@ -9568,6 +9638,10 @@ snapshots:
       espree: 9.6.1
       semver: 7.6.3
 
+  jsonfile@4.0.0:
+    optionalDependencies:
+      graceful-fs: 4.2.11
+
   jsonfile@6.1.0:
     dependencies:
       universalify: 2.0.1
@@ -9610,6 +9684,8 @@ snapshots:
       prelude-ls: 1.2.1
       type-check: 0.4.0
 
+  libpag@4.2.84: {}
+
   lilconfig@3.1.3: {}
 
   lines-and-columns@1.2.4: {}
@@ -10520,6 +10596,14 @@ snapshots:
       glob: 11.0.1
       package-json-from-dist: 1.0.1
 
+  rollup-plugin-copy@3.5.0:
+    dependencies:
+      '@types/fs-extra': 8.1.5
+      colorette: 1.4.0
+      fs-extra: 8.1.0
+      globby: 10.0.1
+      is-plain-object: 3.0.1
+
   rollup@4.29.1:
     dependencies:
       '@types/estree': 1.0.6
@@ -11384,7 +11468,7 @@ snapshots:
       acorn: 8.14.0
       escape-string-regexp: 5.0.0
       estree-walker: 3.0.3
-      fast-glob: 3.3.2
+      fast-glob: 3.3.3
       local-pkg: 0.5.1
       magic-string: 0.30.17
       mlly: 1.7.3
@@ -11412,6 +11496,8 @@ snapshots:
     dependencies:
       imurmurhash: 0.1.4
 
+  universalify@0.1.2: {}
+
   universalify@2.0.1: {}
 
   unocss@0.65.4(postcss@8.4.49)(rollup@4.29.1)(vite@6.0.7(@types/node@22.10.4)(jiti@2.4.2)(sass@1.83.0)(tsx@4.19.2)(yaml@2.7.0))(vue@3.5.13(typescript@5.6.3)):


BIN
public/libpag.wasm


+ 45 - 7
src/api/index.ts

@@ -81,6 +81,7 @@ import type {
   ListInfo,
   ListInterfaces,
   ListPhysicalInterfaces,
+  LoginUser,
   MonitoringForm,
   MonitorPointInfo,
   MonitorPointItem,
@@ -88,6 +89,7 @@ import type {
   OperateLogData,
   OperateLogQuery,
   Organization,
+  OrganizationInfo,
   OrganizationItem,
   OrganizationListItem,
   OrganizationListItemData,
@@ -119,6 +121,8 @@ import type {
   RegionQuery,
   RegionsPointsItem,
   RolePermissions,
+  RolePermissionsItem,
+  RolePermissionsParams,
   SerialNumberItem,
   SerialNumberItemData,
   SmartCtrlLogData,
@@ -163,13 +167,7 @@ const apiBiz = (path: string, params?: unknown) => {
 
 // 登录和注销
 
-export const loginUser = async () => {
-  const params = {
-    grant_type: 'password',
-    username: 'admin1',
-    password: 'admin',
-  };
-
+export const loginUser = async (params: LoginUser) => {
   await request(apiUaa('/oauth/token', params), {
     method: 'POST',
     headers: {
@@ -200,6 +198,19 @@ export const getSubPermList = async (id: number) => {
   return data;
 };
 
+export const getPermissionCheckTree = async (roleId: number) => {
+  const data = await request<TreeStructure[]>(apiSys(`/sysPermission/permissionCheckTree/${roleId}`));
+  return data;
+};
+
+// 角色和菜单关联表
+export const addGrantRolePermissions = async (params: RolePermissionsParams) => {
+  await request(apiSys('/sysRolePermission/grantRolePermissions'), {
+    method: 'POST',
+    body: JSON.stringify(params),
+  });
+};
+
 // 角色和组织关联表
 export const getFindRolesByOrgIds = async (params: number[]) => {
   const data = await request<CharacterPageItem[]>(apiSys('/sysRoleOrg/findRolesByOrgIds'), {
@@ -217,6 +228,13 @@ export const addRolePermissions = async (params: RolePermissions) => {
   });
 };
 
+export const getRolePermissions = async (id: number) => {
+  const data = await request<RolePermissionsItem>(apiSys(`/sysRole/getRolePermissions/${id}`), {
+    method: 'POST',
+  });
+  return data;
+};
+
 // 角色信息表
 
 export const addCharacter = async (params: CharacterParams) => {
@@ -247,6 +265,13 @@ export const deleteCharacter = async (id: number) => {
   });
 };
 
+export const addUpdateRolePermissions = async (params: RolePermissions) => {
+  await request(apiSys('/sysRole/updateRolePermissions'), {
+    method: 'POST',
+    body: JSON.stringify(params),
+  });
+};
+
 // 组织表
 export const addOrganization = async (params: OrganizationItem) => {
   const data = await request<number>(apiSys('/sysOrg/add'), {
@@ -294,6 +319,19 @@ export const getOrganizationAllList = async (params?: string) => {
   return data;
 };
 
+export const getDownloadLogo = async (fileName: string) => {
+  const data = await request(apiSys(`/sysOrg/downloadLogo/${fileName}`), {
+    method: 'POST',
+  });
+  return data;
+};
+
+// 菜单权限表
+export const getOrgInfo = async (id: number) => {
+  const data = await request<OrganizationInfo>(apiSys(`/sysOrg/info/${id}`));
+  return data;
+};
+
 // 用户信息表
 export const addAccount = async (params: AccountParams) => {
   await request(apiSys('/sysUser/add'), {

+ 41 - 1
src/types/index.ts

@@ -2412,7 +2412,8 @@ export interface TreeStructure {
   menuType: string;
   perms: string;
   icon: string;
-  enabled: string;
+  enabled: number;
+  checked: number;
   subPermissions: TreeStructure[];
 }
 
@@ -2508,6 +2509,7 @@ export interface AccountParams {
 }
 
 export interface AccountForm {
+  id?: number;
   userName: string;
   mobile: string;
   password: string;
@@ -2627,4 +2629,42 @@ export interface OrganizationListItem {
   degree: string;
   remark: string;
   enabled: string;
+  id: number;
+}
+
+export interface OrganizationInfo {
+  id: number;
+  createTime: string;
+  updateTime: string;
+  createUserId: number;
+  updateUserId: number;
+  parentId: number;
+  orgName: string;
+  orderNum: number;
+  logo: string;
+  themeColor: string;
+  startTenancy: string;
+  endTenancy: string;
+  dataValidityPeriod: string;
+  degree: string;
+  remark: string;
+  enabled: string;
+}
+
+export interface RolePermissionsParams {
+  roleId: number;
+  permissionIds: number[];
+}
+
+export interface RolePermissionsItem {
+  id: number;
+  remark: string;
+  roleName: string;
+  permissionCheckTreeVos: TreeStructure[];
+}
+
+export interface LoginUser {
+  grant_type?: string;
+  mobile: string;
+  password: string;
 }

+ 154 - 0
src/views/PortalIndex.vue

@@ -0,0 +1,154 @@
+<script setup lang="ts">
+import { onMounted, ref } from 'vue';
+import { useRouter } from 'vue-router';
+import { PAGInit } from 'libpag';
+
+const pagCanvas = ref<HTMLCanvasElement | null>(null);
+const router = useRouter();
+
+const addLog = () => {
+  router.push('/login');
+};
+
+onMounted(async () => {
+  if (!pagCanvas.value) {
+    console.error('Canvas element not found');
+    return;
+  }
+
+  // 初始化 libpag
+  const PAG = await PAGInit();
+
+  // 加载 PAG 文件
+  const pagUrl = '/portal.pag';
+  try {
+    const response = await fetch(pagUrl);
+    const buffer = await response.arrayBuffer();
+    const pagFile = await PAG.PAGFile.load(buffer);
+
+    // 创建 PAG 视图并初始化
+    const pagView = await PAG.PAGView.init(pagFile, pagCanvas.value);
+    if (pagView) {
+      pagView.setRepeatCount(0); // 设置循环播放
+
+      // 修复:使用直接属性访问或数值常量
+      // 方法1: 检查 PAGScaleMode 是否直接作为 PAG 的属性
+      if (PAG.PAGScaleMode) {
+        pagView.setScaleMode(PAG.PAGScaleMode.Stretch);
+      } else if (PAG.ScaleMode) {
+        // 有些版本可能使用 PAG.ScaleMode
+        pagView.setScaleMode(PAG.ScaleMode.Stretch);
+      } else {
+        // 方法2: 使用数值常量
+        pagView.setScaleMode(1);
+      }
+      await pagView.play(); // 播放动画
+    }
+  } catch (error) {
+    console.error('Failed to load or play PAG file:', error);
+  }
+});
+</script>
+
+<template>
+  <div class="fullscreen-container">
+    <div class="header-bar">
+      <AFlex justify="space-between" class="header-top" align="center">
+        <div>
+          <img class="aside-header-logo" src="@/assets/img/logo.png" />
+        </div>
+        <AFlex class="text-top">
+          <div class="text-color">首页</div>
+          <div>产品介绍</div>
+          <div>关于我们</div>
+        </AFlex>
+        <div>
+          <AButton type="primary" class="button-style" @click="addLog">立即体验</AButton>
+        </div>
+      </AFlex>
+      <AFlex justify="center">
+        <div class="portal-text">
+          <div>智能能耗指挥官</div>
+          <div>AI动态调控冷热平衡</div>
+          <AButton type="primary" @click="addLog" class="lord-button-style">立即体验</AButton>
+        </div>
+      </AFlex>
+    </div>
+    <canvas ref="pagCanvas" class="fullscreen-canvas"></canvas>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.text-top .text-color {
+  color: var(--antd-color-primary-hover);
+}
+
+.text-top > div {
+  margin-left: 56px;
+  font-size: 14px;
+  font-style: normal;
+  font-weight: 500;
+  line-height: 20px;
+  color: #333;
+  text-align: justify;
+  cursor: pointer;
+}
+
+.lord-button-style {
+  width: 172px;
+  height: 56px;
+  margin-top: 32px;
+  border-radius: 28px;
+}
+
+.portal-text {
+  margin-top: 85px;
+  font-size: 40px;
+  font-style: normal;
+  font-weight: 500;
+  line-height: 56px;
+  color: #333;
+  text-align: center;
+}
+
+.button-style {
+  width: 112px;
+  height: 48px;
+  border-radius: 24px;
+}
+
+.header-top {
+  width: 100%;
+  height: 80px;
+  padding: 0 80px;
+}
+
+.aside-header-logo {
+  width: 48px;
+  height: 48px;
+}
+
+.header-bar {
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100vw;
+  height: 269px;
+  background: linear-gradient(180deg, #fff 0%, rgb(230 229 228 / 0%) 100%);
+}
+
+.fullscreen-container {
+  position: fixed;
+  top: 0;
+  left: 0;
+  width: 100vw;
+  height: 100vh;
+  overflow: hidden;
+}
+
+.fullscreen-canvas {
+  display: block;
+  width: 100vw;
+  height: 100vh;
+}
+</style>

+ 52 - 16
src/views/create-customer/AddAccount.vue

@@ -4,23 +4,53 @@ import { ref } from 'vue';
 import SvgIcon from '@/components/SvgIcon.vue';
 import { t } from '@/i18n';
 
-import type { Rule } from 'ant-design-vue/es/form';
-import type { AccountItem } from '@/types';
+import type { FormInstance, Rule } from 'ant-design-vue/es/form';
+import type { AccountForm, CharacterPageItem } from '@/types';
 
 interface Props {
   index: number;
-  form: AccountItem;
+  form: AccountForm;
+  characterList: CharacterPageItem[];
 }
+
+const formRef = ref<FormInstance>();
 const emit = defineEmits(['deleteClick']);
 const props = defineProps<Props>();
 const rules: Record<string, Rule[]> = {
-  name: [{ required: true, message: t('common.cannotEmpty'), trigger: 'change' }],
+  userName: [{ required: true, message: t('common.cannotEmpty'), trigger: 'change' }],
+  mobile: [
+    {
+      validator: (_rule: unknown, value: string) => {
+        if (!isValidPhone(value)) {
+          return Promise.reject('手机号格式错误');
+        }
+        return Promise.resolve();
+      },
+    },
+  ],
+  password: [{ required: true, message: t('common.cannotEmpty'), trigger: 'change' }],
+  enabled: [{ required: true, message: t('common.cannotEmpty'), trigger: 'change' }],
+  roleId: [{ required: true, message: t('common.cannotEmpty'), trigger: 'change' }],
+  accountTerm: [{ required: true, message: t('common.cannotEmpty'), trigger: 'change' }],
+};
+// 手机号校验函数
+const isValidPhone = (phone: string): boolean => {
+  return /^1[3-9]\d{9}$/.test(phone);
+};
+const formResetFields = () => {
+  formRef.value?.resetFields();
+};
+const formRefSubmit = () => {
+  return formRef.value?.validate() || Promise.resolve();
 };
-
-const characterList = ref([]);
 const deleteAccount = () => {
   emit('deleteClick', props.index);
 };
+
+defineExpose({
+  formRefSubmit,
+  formResetFields,
+});
 </script>
 
 <template>
@@ -31,25 +61,31 @@ const deleteAccount = () => {
           <SvgIcon @click="deleteAccount" class="icon-delete" name="close-circle" />
         </template>
         <AFlex class="account-bgc" :vertical="true">
-          <AFormItem label="姓名" name="name">
-            <AInput class="input-width" v-model:value="form.name" placeholder="请输入" />
+          <AFormItem label="姓名" name="userName">
+            <AInput class="input-width" v-model:value="form.userName" placeholder="请输入" />
           </AFormItem>
-          <AFormItem label="手机号" name="name">
-            <AInput class="input-width" v-model:value="form.name" placeholder="请输入" />
+          <AFormItem label="手机号" name="mobile">
+            <AInput class="input-width" v-model:value="form.mobile" placeholder="请输入" />
           </AFormItem>
-          <AFormItem label="角色" name="name">
+          <AFormItem label="角色" name="roleId">
             <ASelect
+              v-model:value="form.roleId"
               class="input-width"
               :options="characterList"
-              :field-names="{ label: 'groupName', value: 'id' }"
+              :field-names="{ label: 'roleName', value: 'id' }"
               :placeholder="$t('common.plzSelect')"
             />
           </AFormItem>
-          <AFormItem label="到期时间">
-            <ADatePicker class="input-width" />
+          <AFormItem label="到期时间" name="accountTerm">
+            <ARangePicker
+              v-model:value="form.accountTerm"
+              class="input-width"
+              :allow-clear="false"
+              :separator="$t('common.to')"
+            />
           </AFormItem>
-          <AFormItem label="启用">
-            <ASwitch />
+          <AFormItem label="启用" name="enabled">
+            <ASwitch v-model:checked="form.enabled" />
           </AFormItem>
         </AFlex>
       </ABadge>

+ 69 - 7
src/views/create-customer/CreateAccount.vue

@@ -1,18 +1,36 @@
 <script setup lang="ts">
-import { ref, useTemplateRef } from 'vue';
+import { onMounted, ref, useTemplateRef } from 'vue';
 
 import ConfirmModal from '@/components/ConfirmModal.vue';
+import { useRequest } from '@/hooks/request';
 import { t } from '@/i18n';
+import { addAccount, getFindRolesByOrgIds } from '@/api';
 
 import AddAccount from './AddAccount.vue';
 
-import type { AccountItem } from '@/types';
+import type {
+  AccountForm,
+  CharacterPageItem,
+  CreateCustomer,
+  UseGuideStepItemExpose,
+  UseGuideStepItemProps,
+} from '@/types';
 
-const accountList = ref<AccountItem[]>([]);
+const addAccountRef = ref<InstanceType<typeof AddAccount>[]>([]);
+const props = defineProps<UseGuideStepItemProps<CreateCustomer>>();
+const characterList = ref<CharacterPageItem[]>([]);
+const accountList = ref<AccountForm[]>([]);
 const accountIndex = ref<number>(0);
-const addAccount = () => {
+
+const { handleRequest } = useRequest();
+const addAccountForm = () => {
   accountList.value.push({
-    name: '',
+    userName: '',
+    mobile: '',
+    password: '00000000',
+    enabled: true,
+    roleId: undefined,
+    accountTerm: undefined,
   });
 };
 const modalComponentRef = useTemplateRef('modalComponent');
@@ -24,6 +42,44 @@ const confirm = () => {
   modalComponentRef.value?.hideView();
   accountList.value.splice(accountIndex.value, 1);
 };
+
+const finish = async () => {
+  if (!accountList.value.length) {
+    throw new Error('请添加账号!');
+  }
+  // eslint-disable-next-line no-useless-catch
+  try {
+    const allTasks = [...addAccountRef.value.map((c) => c.formRefSubmit())];
+    await Promise.all(allTasks);
+
+    for (const item of accountList.value) {
+      const { userName, mobile, password, enabled, roleId, accountTerm } = item;
+
+      await addAccount({
+        userName,
+        mobile,
+        password,
+        roleId: roleId as number,
+        enabled: enabled ? '1' : '0',
+        startTenancy: accountTerm![0].format('YYYY-MM-DD'),
+        endTenancy: accountTerm![1].format('YYYY-MM-DD'),
+        orgId: props.form.id as number,
+      });
+    }
+  } catch (err) {
+    throw err;
+  }
+};
+defineExpose<UseGuideStepItemExpose>({
+  finish,
+});
+onMounted(() => {
+  handleRequest(async () => {
+    if (props.form.id) {
+      characterList.value = await getFindRolesByOrgIds([props.form.id]);
+    }
+  });
+});
 </script>
 
 <template>
@@ -31,11 +87,17 @@ const confirm = () => {
     <div class="account"><span class="account-text">*</span>创建账号</div>
     <AFlex wrap="wrap">
       <div v-for="(item, index) in accountList" :key="index" class="account-list">
-        <AddAccount :index="index" :form="item" @deleteClick="deleteClick" />
+        <AddAccount
+          ref="addAccountRef"
+          :index="index"
+          :form="item"
+          :character-list="characterList"
+          @deleteClick="deleteClick"
+        />
       </div>
     </AFlex>
 
-    <AButton type="primary" ghost class="icon-button button-style" @click="addAccount">
+    <AButton type="primary" ghost class="icon-button button-style" @click="addAccountForm">
       <AFlex align="center">
         <SvgIcon name="plus" />
         <span> 增加账号 </span>

+ 114 - 45
src/views/create-customer/CreateCharacter.vue

@@ -1,16 +1,23 @@
 <script setup lang="ts">
-import { onMounted, ref, useTemplateRef } from 'vue';
+import { onMounted, ref, useTemplateRef, watch } from 'vue';
 
 import ConfirmModal from '@/components/ConfirmModal.vue';
 import SvgIcon from '@/components/SvgIcon.vue';
 import { useRequest } from '@/hooks/request';
 import { t } from '@/i18n';
-import { addRolePermissions, getSubPermList } from '@/api';
+import {
+  addRolePermissions,
+  addUpdateRolePermissions,
+  deleteCharacter,
+  getFindRolesByOrgIds,
+  getRolePermissions,
+  getSubPermList,
+} from '@/api';
 
 import type { TreeProps } from 'ant-design-vue';
 import type { Rule } from 'ant-design-vue/es/form';
 import type { DataNode } from 'ant-design-vue/es/tree';
-import type { CharacterForm, CharacterItem, CreateCustomer, TreeStructure, UseGuideStepItemProps } from '@/types';
+import type { CharacterForm, CharacterPageItem, CreateCustomer, TreeStructure, UseGuideStepItemProps } from '@/types';
 
 const rules: Record<string, Rule[]> = {
   name: [{ required: true, message: t('common.cannotEmpty'), trigger: 'change' }],
@@ -18,12 +25,13 @@ const rules: Record<string, Rule[]> = {
 
 const { handleRequest } = useRequest();
 const modalComponentRef = useTemplateRef('modalComponent');
-const characterList = ref<CharacterItem[]>([]);
+const characterList = ref<CharacterPageItem[]>([]);
 const characterOpen = ref<boolean>(false);
+const characterTitle = ref<boolean>(true);
 const checked = ref<boolean>(false);
 const checkedAll = ref<number[]>([]);
 const indeterminate = ref<boolean>(false);
-const characterIndex = ref<number>(0);
+const characterId = ref<number>(0);
 const expandedKeys = ref<number[]>([]);
 const selectedKeys = ref<number[]>([]);
 const checkedKeys = ref<number[]>([]);
@@ -39,58 +47,87 @@ const characterForm = ref<CharacterForm>({
   roleName: '',
   remark: '',
 });
+const handleClose = () => {
+  characterForm.value.roleName = '';
+  characterForm.value.remark = '';
+  checkedKeys.value = [];
+  indeterminate.value = false;
+  checked.value = false;
+  expandedKeys.value = [];
+};
 const addCharacter = () => {
+  characterTitle.value = true;
   characterOpen.value = true;
 };
 const characterSave = () => {
   handleRequest(async () => {
-    await addRolePermissions({
-      ...characterForm.value,
-      orgId: props.form.id as number,
-      enabled: '1',
-      permissionIds: checkedKeys.value,
-    });
-  });
+    if (characterTitle.value) {
+      await addRolePermissions({
+        ...characterForm.value,
+        orgId: props.form.id as number,
+        enabled: '1',
+        permissionIds: checkedKeys.value,
+      });
+    } else {
+      await addUpdateRolePermissions({
+        id: characterId.value,
+        ...characterForm.value,
+        orgId: props.form.id as number,
+        enabled: '1',
+        permissionIds: checkedKeys.value,
+      });
+    }
 
-  characterList.value.push({
-    name: '',
-    id: undefined,
+    getFindRolesByOrg();
+    characterOpen.value = false;
   });
-  characterOpen.value = false;
 };
 const cancelSave = () => {
   checkedKeys.value = [];
   characterOpen.value = false;
 };
 const confirm = () => {
-  modalComponentRef.value?.hideView();
-  characterList.value.splice(characterIndex.value, 1);
+  handleRequest(async () => {
+    await deleteCharacter(characterId.value);
+    getFindRolesByOrg();
+    modalComponentRef.value?.hideView();
+  });
 };
-const characterEditor = () => {};
-const characterDelete = (index: number) => {
-  characterIndex.value = index;
-  modalComponentRef.value?.showView();
+const characterEditor = (id: number) => {
+  characterId.value = id;
+  characterTitle.value = false;
+  characterOpen.value = true;
+  handleRequest(async () => {
+    const { remark, roleName, permissionCheckTreeVos } = await getRolePermissions(id);
+    checkedKeys.value = [];
+    characterForm.value.roleName = roleName;
+    characterForm.value.remark = remark;
+
+    permissionCheckTreeVos[0].subPermissions[0].subPermissions.forEach((item) => {
+      if (item.subPermissions) {
+        item.subPermissions.forEach((i) => {
+          if (i.checked === 1) {
+            checkedKeys.value.push(i.id);
+          }
+        });
+        expandedKeys.value.push(item.id);
+      } else {
+        if (item.checked === 1) {
+          checkedKeys.value.push(item.id);
+        }
+      }
+    });
+  });
 };
-const addMenu = () => {
-  if (checkedKeys.value.length === 0) {
-    checked.value = false;
-    indeterminate.value = false;
-    return;
-  }
-  if (checkedAll.value.length !== checkedKeys.value.length) {
-    indeterminate.value = true;
-    checked.value = false;
-  } else {
-    indeterminate.value = false;
-    checked.value = true;
-  }
+const characterDelete = (id: number) => {
+  characterId.value = id;
+  modalComponentRef.value?.showView();
 };
+
 const selectAll = () => {
+  indeterminate.value = false;
   if (checked.value) {
-    checkedKeys.value = getAllKeys(treeStructure.value);
-    if (checkedKeys.value.length) {
-      checkedAll.value = checkedKeys.value;
-    }
+    checkedKeys.value = checkedAll.value;
   } else {
     checkedKeys.value = [];
   }
@@ -101,6 +138,7 @@ const getAllKeys = (data: DataNode[]) => {
   let keys: number[] = [];
   data.forEach((item) => {
     keys.push(item.id);
+    expandedKeys.value.push(item.id);
     if (item.subPermissions) {
       keys = keys.concat(getAllKeys(item.subPermissions));
     }
@@ -117,11 +155,36 @@ const transformTreeData = (data: TreeStructure[]): DataNode[] => {
   }));
 };
 
+const getFindRolesByOrg = () => {
+  handleRequest(async () => {
+    if (props.form.id) {
+      characterList.value = [];
+      const list = await getFindRolesByOrgIds([props.form.id]);
+      list.forEach((item) => {
+        if (item.roleName !== '管理员' && item.roleName !== '工程师') {
+          characterList.value.push(item);
+        }
+      });
+    }
+  });
+};
+
+watch(
+  () => checkedKeys.value,
+  (count) => {
+    if (count) {
+      indeterminate.value = !!count.length && count.length < checkedAll.value.length;
+      checked.value = count.length === checkedAll.value.length;
+    }
+  },
+);
+
 onMounted(() => {
   handleRequest(async () => {
+    getFindRolesByOrg();
     const data = await getSubPermList(0);
+
     treeStructure.value = transformTreeData(data[0].subPermissions[0].subPermissions);
-    // treeStructure.value = treeData[0].subPermissions;
     checkedAll.value = getAllKeys(treeStructure.value);
   });
 });
@@ -140,13 +203,13 @@ onMounted(() => {
         <div class="input-style-text">默认角色</div>
       </AFlex>
       <AFlex align="center" v-for="(item, index) in characterList" :key="index">
-        <AFlex class="input-style input-background" align="center"> 角色 </AFlex>
-        <div @click="characterEditor">
+        <AFlex class="input-style input-background" align="center"> {{ item.roleName }} </AFlex>
+        <div @click="characterEditor(item.id)">
           <AFlex class="editorial-role" align="center" justify="center">
             <SvgIcon name="edit-o" />
           </AFlex>
         </div>
-        <div @click="characterDelete(index)">
+        <div @click="characterDelete(item.id)">
           <AFlex class="editorial-role" align="center" justify="center">
             <SvgIcon class="icon-color" name="delete" />
           </AFlex>
@@ -159,7 +222,14 @@ onMounted(() => {
         <span> 增加角色 </span>
       </AFlex>
     </AButton>
-    <AModal v-model:open="characterOpen" title="添加角色" width="920px" :mask-closable="false" :footer="null">
+    <AModal
+      v-model:open="characterOpen"
+      :title="characterTitle ? '添加角色' : '编辑角色'"
+      width="920px"
+      :mask-closable="false"
+      :footer="null"
+      :after-close="handleClose"
+    >
       <AForm
         ref="formRef"
         class="alarm-modal"
@@ -191,7 +261,6 @@ onMounted(() => {
               :tree-data="treeStructure"
               checkable
               :field-names="fieldNames"
-              @check="addMenu"
             />
           </div>
         </AFormItem>

+ 25 - 7
src/views/create-customer/EstablishOrganization.vue

@@ -1,5 +1,5 @@
 <script setup lang="ts">
-import { inject, onMounted, ref } from 'vue';
+import { inject, onMounted, ref, watch } from 'vue';
 import { ColorPicker } from 'vue-color-kit';
 import { message } from 'ant-design-vue';
 
@@ -57,7 +57,7 @@ const colorStyle: ColorStyle[] = [
   {
     background: 'background:#32bac0',
     border: 'border:1px solid #32bac0',
-    color: '#32bac0',
+    color: '#32BAC0',
     index: 0,
   },
   {
@@ -197,16 +197,15 @@ defineExpose<UseGuideStepItemExpose>({
   finish,
 });
 
-onMounted(() => {
+const getInfoListByOrg = () => {
   handleRequest(async () => {
-    await getDataValidityPeriod();
-    equipmentType.value = await groupList({
-      dataType: 1,
-    });
     if (props.form.id) {
       equipmentLimitationsList.value = [];
       const data = await getInfoListByOrgId(props.form.id);
       data.forEach((item) => {
+        if (item.deviceGlobalId === -1) {
+          props.form.stationsNumber = item.upperLimit;
+        }
         if (item.deviceGlobalId !== -1) {
           equipmentLimitationsList.value.push({
             deviceGlobalId: item.deviceGlobalId,
@@ -216,6 +215,25 @@ onMounted(() => {
       });
     }
   });
+};
+
+watch(
+  () => props.form.id,
+  (count) => {
+    if (count) {
+      getInfoListByOrg();
+    }
+  },
+);
+
+onMounted(() => {
+  handleRequest(async () => {
+    await getDataValidityPeriod();
+    equipmentType.value = await groupList({
+      dataType: 1,
+    });
+    getInfoListByOrg();
+  });
 });
 </script>
 

+ 115 - 0
src/views/login-component/LoginView.vue

@@ -0,0 +1,115 @@
+<script setup lang="ts">
+import { ref } from 'vue';
+
+import SvgIcon from '@/components/SvgIcon.vue';
+import { useRequest } from '@/hooks/request';
+import { t } from '@/i18n';
+import { loginUser } from '@/api';
+
+import type { Rule } from 'ant-design-vue/es/form';
+import type { LoginUser } from '@/types';
+
+const { handleRequest } = useRequest();
+const loginForm = ref<LoginUser>({
+  mobile: '',
+  password: '',
+});
+const rules: Record<string, Rule[]> = {
+  password: [{ required: true, message: t('common.cannotEmpty'), trigger: 'change' }],
+  mobile: [
+    {
+      validator: (_rule: unknown, value: string) => {
+        if (!isValidPhone(value)) {
+          return Promise.reject('手机号格式错误');
+        }
+        return Promise.resolve();
+      },
+    },
+  ],
+};
+// 手机号校验函数
+const isValidPhone = (phone: string): boolean => {
+  return /^1[3-9]\d{9}$/.test(phone);
+};
+const addLog = () => {
+  handleRequest(async () => {
+    await loginUser({
+      grant_type: 'mobile_password',
+      ...loginForm.value,
+    });
+  });
+};
+</script>
+
+<template>
+  <div>
+    <AFlex>
+      <div class="login-div">
+        <AButton type="text" @click="$router.go(-1)">返回</AButton>
+      </div>
+
+      <AFlex class="login-div" justify="center" align="center">
+        <div>
+          <div class="login-title">暖通智控平台</div>
+          <AForm
+            ref="formRef"
+            :colon="false"
+            layout="vertical"
+            :model="loginForm"
+            :rules="rules"
+            :hide-required-mark="true"
+          >
+            <AFormItem label="账号" name="mobile">
+              <AInput class="input-width" v-model:value="loginForm.mobile" placeholder="请输入手机号">
+                <template #prefix>
+                  <SvgIcon name="calendar" class="icon-style" />
+                </template>
+              </AInput>
+            </AFormItem>
+            <AFormItem label="密码" name="password">
+              <AInput class="input-width" v-model:value="loginForm.password" placeholder="请输入密码">
+                <template #prefix>
+                  <SvgIcon name="calendar" class="icon-style" />
+                </template>
+              </AInput>
+            </AFormItem>
+          </AForm>
+          <AButton type="primary" class="button-style" @click="addLog">登陆</AButton>
+        </div>
+      </AFlex>
+    </AFlex>
+  </div>
+</template>
+
+<style lang="scss" scoped>
+.icon-style {
+  margin-right: 10px;
+}
+
+.button-style {
+  width: 422px;
+  height: 56px;
+  margin-top: 32px;
+  border-radius: 28px;
+}
+
+.input-width {
+  width: 422px;
+  height: 56px;
+}
+
+.login-title {
+  margin-bottom: 80px;
+  font-size: 32px;
+  font-style: normal;
+  font-weight: 500;
+  line-height: 44px;
+  color: #333;
+  text-align: left;
+}
+
+.login-div {
+  width: 50vw;
+  height: 100vh;
+}
+</style>

+ 40 - 12
src/views/organization-manage/OrganizationManage.vue

@@ -6,7 +6,7 @@ import ConfirmModal from '@/components/ConfirmModal.vue';
 import OrganizationalStructure from '@/components/OrganizationalStructure.vue';
 import { useRequest } from '@/hooks/request';
 import { t } from '@/i18n';
-import { getOrganizationAllList } from '@/api';
+import { getDownloadLogo, getOrganizationAllList, getOrgInfo } from '@/api';
 
 import EstablishOrganization from '../create-customer/EstablishOrganization.vue';
 
@@ -69,7 +69,7 @@ const organizationColumns = [
     title: '操作',
     dataIndex: 'action',
     key: 'action',
-    width: 152,
+    width: 80,
   },
 ];
 const rules = computed<FormRules<RegisterGatewayForm>>(() => {
@@ -107,6 +107,15 @@ const rules = computed<FormRules<RegisterGatewayForm>>(() => {
 const addOrganization = () => {
   organizationOpen.value = true;
   organizationTitle.value = true;
+  useForm.id = undefined;
+  useForm.orgName = '';
+  useForm.themeColor = '#32BAC0';
+  useForm.logo = '';
+  useForm.leaseTerm = [dayjs(), dayjs()];
+  useForm.dataValidityPeriod = '';
+  useForm.stationsNumber = 0;
+  useForm.selectedColor = '#e2550d';
+  useForm.imageUrl = '';
 };
 const addQuery = () => {
   getOrganizationAll();
@@ -115,18 +124,37 @@ const addReset = () => {
   orgName.value = '';
   getOrganizationAll();
 };
+
 const organizationEditor = (id: number) => {
   organizationOpen.value = true;
   organizationTitle.value = false;
-  console.log(id);
-};
-const organizationDelete = (id: number) => {
-  organizationId.value = id;
-  modalComponentRef.value?.showView();
-  console.log(id);
+  handleRequest(async () => {
+    const { id: OrgId, orgName, themeColor, logo, endTenancy, startTenancy, dataValidityPeriod } = await getOrgInfo(id);
+    if (logo) {
+      const data = await getDownloadLogo(logo);
+      useForm.imageUrl = URL.createObjectURL(data as Blob);
+    }
+    const colorList = ['#32BAC0', '#256AFE', '#00A94D', '#7866FF'];
+    if (colorList.includes(themeColor)) {
+      useForm.themeColor = themeColor;
+      useForm.selectedColor = '#e2550d';
+    } else {
+      useForm.selectedColor = themeColor;
+      useForm.themeColor = themeColor;
+    }
+    useForm.id = OrgId;
+    useForm.orgName = orgName;
+
+    useForm.logo = logo;
+    useForm.leaseTerm = [dayjs(startTenancy, 'YYYY-MM-DD'), dayjs(endTenancy, 'YYYY-MM-DD')];
+    useForm.dataValidityPeriod = dataValidityPeriod;
+  });
 };
-const clickOrganizationChange = (id: number) => {
-  console.log(id);
+// const organizationDelete = (id: number) => {
+//   organizationId.value = id;
+//   modalComponentRef.value?.showView();
+// };
+const clickOrganizationChange = () => {
   getOrganizationAll();
 };
 const cancelOrganization = () => {
@@ -167,7 +195,7 @@ onMounted(() => {
     <AFlex justify="space-between" class="div-bottom">
       <div class="text-top">组织管理</div>
       <AFlex align="center">
-        <div class="text-restriction">还可创建1个</div>
+        <!--<div class="text-restriction">还可创建1个</div> -->
         <AButton type="primary" class="icon-button" @click="addOrganization">
           <AFlex align="center">
             <SvgIcon name="plus" />
@@ -194,7 +222,7 @@ onMounted(() => {
             <template #bodyCell="{ column, record }">
               <template v-if="column.key === 'action'">
                 <SvgIcon @click="organizationEditor(record.id)" class="icon-style" name="edit-o" />
-                <SvgIcon @click="organizationDelete(record.id)" class="icon-style" name="delete" />
+                <!-- <SvgIcon @click="organizationDelete(record.id)" class="icon-style" name="delete" /> -->
               </template>
             </template>
           </ATable>

+ 57 - 13
src/views/role-manage/RoleManage.vue

@@ -10,11 +10,12 @@ import { t } from '@/i18n';
 import {
   addCharacter,
   addDevicePermissions,
+  addGrantRolePermissions,
   deleteCharacter,
   getAllGroupList,
   getFindRolesByOrgIds,
+  getPermissionCheckTree,
   getSubOrgsByToken,
-  getSubPermList,
   updateCharacter,
 } from '@/api';
 
@@ -159,7 +160,11 @@ const editorPermission = () => {
   editorChecked.value = false;
 };
 const cancelPermission = () => {
-  getDeviceGroupList();
+  if (permissions.value === 'dataPermissions') {
+    getDeviceGroupList();
+  } else {
+    getFunctionPermList();
+  }
   editorChecked.value = true;
 };
 
@@ -179,10 +184,23 @@ const savePermission = () => {
       getDeviceGroupList();
     });
   } else {
-    console.log(operationpermissions.value);
-    console.log(checkedFatherKeys.value);
-    console.log(checkedKeys.value);
-    console.log([...new Set([...checkedKeys.value, ...checkedFatherKeys.value])]);
+    let data: number[] = [];
+    operationpermissions.value.forEach((item) => {
+      if (item.list.length) {
+        data = [...new Set([...data, ...item.list])];
+      }
+    });
+
+    handleRequest(async () => {
+      if (!checkedKeys.value.length) {
+        checkedFatherKeys.value = [];
+      }
+      await addGrantRolePermissions({
+        roleId: characterListId.value,
+        permissionIds: [...new Set([...checkedKeys.value, ...checkedFatherKeys.value, ...data])],
+      });
+      getFunctionPermList();
+    });
   }
   editorChecked.value = true;
 };
@@ -207,7 +225,15 @@ const getFindRolesByOrg = (id: number) => {
       if (!characterListId.value) {
         characterListId.value = data[0].id;
         getDeviceGroupList();
+        getFunctionPermList();
+      } else {
+        getDeviceGroupList();
+        getFunctionPermList();
       }
+    } else {
+      pagePermissionsTree.value = [];
+      treeStructure.value = [];
+      operationpermissions.value = [];
     }
   });
 };
@@ -260,7 +286,8 @@ const transformData = (data: TreeStructure[]): OperationPermissions[] => {
     const transformed = {
       value: item.id,
       label: item.menuName,
-      list: [],
+      list: item.subPermissions?.filter((sub) => sub.checked === 1).map((sub) => sub.id) || [],
+      ...item,
       // 保留其他属性(如果需要)
       ...(item.subPermissions && {
         subPermissions: transformData(item.subPermissions),
@@ -273,12 +300,27 @@ const transformData = (data: TreeStructure[]): OperationPermissions[] => {
 
 const getFunctionPermList = () => {
   handleRequest(async () => {
-    const data = await getSubPermList(0);
-    if (data.length) {
-      treeStructure.value = transformTreeData(data[0].subPermissions[0].subPermissions);
-      operationpermissions.value = transformData(data[0].subPermissions[1].subPermissions);
-      console.log(operationpermissions.value);
-      checkedKeys.value = [10601, 10602, 10901, 107, 102, 103, 106, 109];
+    // const data = await getSubPermList(0);
+    const dataCheck = await getPermissionCheckTree(characterListId.value);
+    checkedKeys.value = [];
+    expandedKeys.value = [];
+    if (dataCheck.length) {
+      treeStructure.value = transformTreeData(dataCheck[0].subPermissions[0].subPermissions);
+      operationpermissions.value = transformData(dataCheck[0].subPermissions[1].subPermissions);
+      dataCheck[0].subPermissions[0].subPermissions.forEach((item) => {
+        if (item.subPermissions) {
+          item.subPermissions.forEach((i) => {
+            if (i.checked === 1) {
+              checkedKeys.value.push(i.id);
+            }
+          });
+          expandedKeys.value.push(item.id);
+        } else {
+          if (item.checked === 1) {
+            checkedKeys.value.push(item.id);
+          }
+        }
+      });
     }
   });
 };
@@ -295,6 +337,8 @@ const selectAll = () => {
 const addRadioGroup = () => {
   if (permissions.value === 'functionPermissions') {
     getDeviceGroupList();
+  } else {
+    getFunctionPermList();
   }
   editorChecked.value = true;
 };

+ 10 - 0
vite.config.ts

@@ -3,6 +3,7 @@ import Vue from '@vitejs/plugin-vue';
 import VueJsx from '@vitejs/plugin-vue-jsx';
 import VueDevTools from 'vite-plugin-vue-devtools';
 import VueI18nPlugin from '@intlify/unplugin-vue-i18n/vite';
+import copy from 'rollup-plugin-copy';
 import UnoCSS from 'unocss/vite';
 import AutoImport from 'unplugin-auto-import/vite';
 import { AntDesignVueResolver } from 'unplugin-vue-components/resolvers';
@@ -22,6 +23,15 @@ export default defineConfig({
         },
       },
     }),
+    copy({
+      targets: [
+        {
+          src: './node_modules/libpag/lib/libpag.wasm',
+          dest: process.env.NODE_ENV === 'production' ? 'dist/' : 'public/',
+        },
+      ],
+      hook: process.env.NODE_ENV === 'production' ? 'writeBundle' : 'buildStart',
+    }),
     VueJsx(),
     VueDevTools(),
     VueI18nPlugin({