1
0

5 Commits b0ab460afb ... 6691565c06

Autor SHA1 Mensagem Data
  wangcong 6691565c06 feat(views): 编写智控日志和操作日志页面 há 1 semana atrás
  wangcong 15c09208bb perf(views): 优化“设备工况”模块参数的历史数据曲线 há 1 semana atrás
  wangcong 946fe73bee perf(views): 优化“设备工况”模块参数的历史数据曲线,无单位时不显示括号 há 1 semana atrás
  wangcong 3916eabd14 perf(views): 优化“设备工况”模块参数的历史数据曲线,默认不显示 symbol há 1 semana atrás
  wangshun 4bbb006f31 perf(views): 编写"角色管理"页面数据权限功能 há 1 semana atrás

+ 44 - 0
src/api/index.ts

@@ -40,6 +40,7 @@ import type {
   DeviceParamGroup,
   DeviceParams,
   DeviceParamType,
+  DevicePermissionsParams,
   DevicesList,
   DevicesListItemData,
   DeviceTypeCount,
@@ -83,12 +84,16 @@ import type {
   MonitorPointInfo,
   MonitorPointItem,
   NoticePageItemData,
+  OperateLogData,
+  OperateLogQuery,
   Organization,
   OrganizationItem,
   OutdooForm,
   PageParams,
   ParameterVerification,
   ParamValueListAutomaticQuery,
+  PermissionGroupItem,
+  PermissionList,
   PointTimeSeriesQuery,
   PostProtocolPage,
   PostProtocolPageItem,
@@ -111,6 +116,8 @@ import type {
   RegionsPointsItem,
   SerialNumberItem,
   SerialNumberItemData,
+  SmartCtrlLogData,
+  SmartCtrlLogQuery,
   SubmitDeviceGroup,
   SubmitSorting,
   TempHumidityControlSettings,
@@ -504,6 +511,21 @@ export const deleteDeviceGroup = async (id: number) => {
   });
 };
 
+export const addDevicePermissions = async (params: DevicePermissionsParams) => {
+  await request(apiBiz('/deviceGroup/update/role'), {
+    method: 'POST',
+    body: JSON.stringify(params),
+  });
+};
+
+export const getAllGroupList = async (params: PermissionList) => {
+  const data = await request<PermissionGroupItem[]>(apiBiz('/deviceGroup/getAllList'), {
+    method: 'POST',
+    body: JSON.stringify(params),
+  });
+  return data;
+};
+
 // 设备列表
 
 export const deviceAdd = async (equipmentInformationForm: EquipmentDetailsForm) => {
@@ -1243,3 +1265,25 @@ export const getAIAdvancedSetting = async (groupId: number) => {
 
   return data;
 };
+
+// 智控日志
+
+export const getSmartCtrlLog = async (params: SmartCtrlLogQuery) => {
+  const data = await request<SmartCtrlLogData>(apiBiz('/algLogInfo/getPageList'), {
+    method: 'POST',
+    body: JSON.stringify(params),
+  });
+
+  return data;
+};
+
+// 操作日志
+
+export const getOperateLog = async (params: OperateLogQuery) => {
+  const data = await request<OperateLogData>(apiBiz('/controlLogInfo/getPageList'), {
+    method: 'POST',
+    body: JSON.stringify(params),
+  });
+
+  return data;
+};

+ 1 - 0
src/i18n/locales/zh.json

@@ -168,6 +168,7 @@
     "operator": "操作人员",
     "opsCenter": "运维中心",
     "pageTotal": "共 {total} 条",
+    "platform": "平台",
     "pleaseEnter": "请输入",
     "plzEnter": "请输入{name}",
     "plzSelect": "请选择{name}",

+ 87 - 0
src/types/index.ts

@@ -2334,6 +2334,52 @@ export interface AIAdvancedParamItem {
   oneValue: string | null;
 }
 
+export type SmartCtrlLogData = PageData<SmartCtrlLogItem>;
+
+export interface SmartCtrlLogItem {
+  content: string;
+  createTime: string;
+  createUserId: number | null;
+  devGroupId: number;
+  devGroupName: string;
+  devParentGroupId: number;
+  deviceId: number;
+  deviceName: string;
+  id: number;
+  orderType: number;
+  orgId: number;
+  result: number;
+  updateTime: string;
+  updateUserId: number | null;
+}
+
+export interface SmartCtrlLogQuery extends PageParams {
+  deviceIds: string;
+  searchContent: string;
+  startTime?: string;
+  endTime?: string;
+}
+
+export type OperateLogData = PageData<OperateLogItem>;
+
+export interface OperateLogItem {
+  deviceName: string;
+  content: string;
+  result: number;
+  userName: string;
+  createUserId: number;
+  createTime: string;
+}
+
+export interface OperateLogQuery extends PageParams {
+  groupIds: number[];
+  content: string;
+  result: number | null;
+  userId: number | null;
+  startTime?: string;
+  endTime?: string;
+}
+
 export interface UploadLogo {
   etag: string;
   versionId: string;
@@ -2489,3 +2535,44 @@ export interface DeviceCop {
   enableCopSet: boolean;
   deviceName?: string;
 }
+
+export interface PermissionList {
+  userId?: number;
+  orgId?: number;
+  parentId?: number;
+  roleId?: number;
+  haveDevice?: boolean;
+  haveChild?: boolean;
+}
+
+export interface RoleIds {
+  roleIds: number[];
+}
+export interface PermissionGroupItem {
+  id: number;
+  userId: number;
+  groupName: string;
+  comment: string;
+  parentId: number;
+  deleted?: number;
+  orgId: number;
+  roleIds: RoleIds;
+  key?: number;
+  deviceGroupChilds: TwoPermissionGroupItem[];
+}
+
+export interface TwoPermissionGroupItem {
+  id: number;
+  groupName: string;
+  comment: string;
+  parentId: number;
+  userId: number;
+  orgId: number;
+  roleIds: RoleIds;
+  key?: number;
+}
+
+export interface DevicePermissionsParams {
+  roleId: number;
+  groupIds: number[];
+}

+ 5 - 3
src/views/device-work-status/DevWorkParamData.vue

@@ -119,7 +119,8 @@ const historyData = ref<DevWorkHistoryDataItem[]>([]);
 const paramLabel = computed<string>(() => {
   if (historyData.value.length) {
     const { deviceParamName, unit } = historyData.value[0];
-    return `${deviceParamName}(${unit})`;
+    const unitText = unit ? `(${unit})` : '';
+    return deviceParamName + unitText;
   }
 
   return '';
@@ -132,6 +133,7 @@ const option = computed<EChartsOption>(() => {
     type: 'line',
     seriesLayoutBy: 'column',
     name,
+    showSymbol: false,
   })) as LineSeriesOption;
 
   return {
@@ -230,8 +232,8 @@ const getParamHistoryData = () => {
     const data = await getDevWorkHistoryData({
       deviceIds: [props.devId],
       deviceParamCode: props.paramCodes,
-      startTime: startTime.format('YYYY-MM-DD HH:mm:ss'),
-      endTime: endTime.format('YYYY-MM-DD HH:mm:ss'),
+      startTime: startTime.format('YYYY-MM-DD 00:00:00'),
+      endTime: endTime.format('YYYY-MM-DD 23:59:59'),
     });
 
     if (data.length) {

+ 44 - 5
src/views/log-center/LogCenter.vue

@@ -1,12 +1,51 @@
 <script setup lang="ts">
-import { ref } from 'vue';
+import { computed, onMounted, ref } from 'vue';
 
-const activeKey = ref('smartControlLogs');
+import { useRefreshView } from '@/hooks/refresh-view';
+import { t } from '@/i18n';
+
+import OperateLog from './OperateLog.vue';
+import SmartCtrlLog from './SmartCtrlLog.vue';
+
+import type { TabComponent } from '@/types';
+
+const { renderView, refreshView } = useRefreshView();
+const activeKey = ref('');
+
+const logTabs = computed<TabComponent[]>(() => {
+  return [
+    {
+      key: 'smartCtrlLog',
+      name: t('logCenter.smartControlLogs'),
+      component: SmartCtrlLog,
+    },
+    {
+      key: 'operateLog',
+      name: t('logCenter.operationLogs'),
+      component: OperateLog,
+    },
+  ];
+});
+
+onMounted(() => {
+  activeKey.value = logTabs.value[0].key;
+  refreshView();
+});
 </script>
 
 <template>
-  <ATabs class="button-tabs-compact" v-model:active-key="activeKey" type="card">
-    <ATabPane key="smartControlLogs" :tab="$t('logCenter.smartControlLogs')"> 智控日志 </ATabPane>
-    <ATabPane key="operationLogs" :tab="$t('logCenter.operationLogs')"> 操作日志 </ATabPane>
+  <ATabs class="button-tabs-compact" v-model:active-key="activeKey" type="card" @tab-click="refreshView">
+    <ATabPane v-for="item in logTabs" :key="item.key" :tab="item.name">
+      <component v-if="activeKey === item.key && renderView" :is="item.component" />
+    </ATabPane>
   </ATabs>
 </template>
+
+<style lang="scss" scoped>
+:deep(.ant-tabs-content-holder) {
+  .ant-card {
+    border-radius: 16px;
+    box-shadow: none;
+  }
+}
+</style>

+ 301 - 0
src/views/log-center/OperateLog.vue

@@ -0,0 +1,301 @@
+<script setup lang="ts">
+import { computed, onMounted, ref } from 'vue';
+import { Cascader } from 'ant-design-vue';
+
+import { useRequest } from '@/hooks/request';
+import { t } from '@/i18n';
+import { getOperateLog, getPageList } from '@/api';
+import { disabledDate } from '@/utils';
+
+import type { ColumnType, TableProps } from 'ant-design-vue/es/table';
+import type { DeviceGroupItem, OperateLogItem, OptionItem, PageParams, RangeValue, UserPageItem } from '@/types';
+
+const enum OperateResult {
+  All = -1,
+  Success,
+  Failure,
+}
+
+const enum OperatorType {
+  All = -2,
+  Platform = -1,
+}
+
+const timeRange = ref<RangeValue>();
+const selectedGroups = ref<number[][]>([]);
+const deviceGroup = ref<DeviceGroupItem[]>([]);
+const searchContent = ref('');
+const selectedResult = ref<number>(OperateResult.All);
+const selectedOperator = ref<number>(OperatorType.All);
+const userList = ref<UserPageItem[]>([]);
+
+const operatorList = computed<OptionItem<number>[]>(() => {
+  return [
+    { value: OperatorType.All, label: t('common.entire') },
+    { value: OperatorType.Platform, label: t('common.platform') },
+    ...userList.value.map((item) => ({
+      value: item.id,
+      label: item.userName,
+    })),
+  ];
+});
+
+const logList = ref<OperateLogItem[]>([]);
+const logTotal = ref(0);
+const pageParams = ref<PageParams>({
+  pageIndex: 1,
+  pageSize: 10,
+});
+
+const columns = computed<ColumnType<OperateLogItem>[]>(() => {
+  return [
+    {
+      title: t('common.device'),
+      dataIndex: 'deviceName',
+      key: 'deviceName',
+    },
+    {
+      title: t('logCenter.deviceParameterChange'),
+      dataIndex: 'content',
+      key: 'content',
+    },
+    {
+      title: t('common.time'),
+      dataIndex: 'createTime',
+      key: 'createTime',
+    },
+    {
+      title: t('common.result'),
+      dataIndex: 'result',
+      key: 'result',
+    },
+    {
+      title: t('common.operator'),
+      dataIndex: 'userName',
+      key: 'userName',
+    },
+  ];
+});
+
+onMounted(() => {
+  getDeviceGroups();
+  getUserList();
+  getLogList();
+});
+
+const { handleRequest } = useRequest();
+
+const getDeviceGroups = () => {
+  handleRequest(async () => {
+    deviceGroup.value = await getPageList();
+  });
+};
+
+const getUserList = () => {
+  handleRequest(async () => {
+    //
+  });
+};
+
+const { isLoading: isLogLoading, handleRequest: handleLogRequest } = useRequest();
+
+const getLogList = () => {
+  handleLogRequest(async () => {
+    const { records, total } = await getOperateLog({
+      ...pageParams.value,
+      groupIds: selectedGroups.value.map((item) => item[1]),
+      content: searchContent.value,
+      startTime: timeRange.value?.[0].format('YYYY-MM-DD HH:mm:ss'),
+      endTime: timeRange.value?.[1].format('YYYY-MM-DD HH:mm:ss'),
+      result: selectedResult.value === OperateResult.All ? null : selectedResult.value,
+      userId: selectedOperator.value === OperatorType.All ? null : selectedOperator.value,
+    });
+
+    logList.value = records;
+    logTotal.value = total;
+  });
+};
+
+const filterOperator = (input: string, option: OptionItem<number>) => {
+  return option.label.toLowerCase().indexOf(input.toLowerCase()) >= 0;
+};
+
+const handleSearch = () => {
+  pageParams.value.pageIndex = 1;
+  getLogList();
+};
+
+const handleReset = () => {
+  timeRange.value = undefined;
+  selectedGroups.value = [];
+  searchContent.value = '';
+  selectedResult.value = OperateResult.All;
+  selectedOperator.value = OperatorType.All;
+  handleSearch();
+};
+
+const handleLogTableChange: TableProps['onChange'] = (pagination) => {
+  pageParams.value.pageIndex = pagination.current || 1;
+  pageParams.value.pageSize = pagination.pageSize || 10;
+  getLogList();
+};
+</script>
+
+<template>
+  <ACard :bordered="false">
+    <ARow class="operate-log-query" :gutter="[80, 16]">
+      <ACol :span="8">
+        <div class="operate-log-label">{{ $t('logCenter.selectTimeRange') }}</div>
+        <ARangePicker
+          class="operate-log-time"
+          v-model:value="timeRange"
+          :placeholder="[$t('common.startTime'), $t('common.endTime')]"
+          :separator="$t('common.to')"
+          :disabled-date="disabledDate"
+          show-time
+        >
+          <template #suffixIcon>
+            <SvgIcon name="calendar" color="#333" />
+          </template>
+        </ARangePicker>
+      </ACol>
+      <ACol :span="8">
+        <div class="operate-log-label">{{ $t('logCenter.selectDeviceGroup') }}</div>
+        <ACascader
+          v-model:value="selectedGroups"
+          class="operate-log-input"
+          :placeholder="$t('logCenter.plzSelectDeviceGroup')"
+          :field-names="{ label: 'groupName', value: 'id', children: 'deviceGroupChilds' }"
+          :options="deviceGroup"
+          
+          
+          :max-tag-count="2"
+          :max-tag-text-length="10"
+          :show-checked-strategy="Cascader.SHOW_CHILD"
+           change-on-select show-search multiple 
+        />
+      </ACol>
+      <ACol :span="8">
+        <div class="operate-log-label">{{ $t('common.operationContent') }}</div>
+        <AInput
+          v-model:value="searchContent"
+          class="operate-log-input"
+          :placeholder="$t('common.plzEnter')"
+          allow-clear
+        />
+      </ACol>
+      <ACol :span="8">
+        <div class="operate-log-label">{{ $t('common.operationResult') }}</div>
+        <ASelect v-model:value="selectedResult" class="operate-log-input">
+          <ASelectOption :value="OperateResult.All">{{ $t('common.entire') }} </ASelectOption>
+          <ASelectOption :value="OperateResult.Success">{{ $t('common.success') }} </ASelectOption>
+          <ASelectOption :value="OperateResult.Failure">{{ $t('common.failure') }} </ASelectOption>
+        </ASelect>
+      </ACol>
+      <ACol :span="8">
+        <div class="operate-log-label">{{ $t('common.operator') }}</div>
+        <ASelect
+          v-model:value="selectedOperator"
+          class="operate-log-input"
+          :options="operatorList"
+          show-search
+          :filter-option="filterOperator"
+        />
+      </ACol>
+      <ACol :span="8" class="operate-log-button">
+        <AButton @click="handleReset">{{ $t('common.reset') }}</AButton>
+        <AButton type="primary" @click="handleSearch">{{ $t('common.query') }}</AButton>
+      </ACol>
+    </ARow>
+    <ATable
+      class="hvac-table"
+      :data-source="logList"
+      :columns="columns"
+      row-key="id"
+      :loading="isLogLoading"
+      :pagination="{
+        current: pageParams.pageIndex,
+        pageSize: pageParams.pageSize,
+        total: logTotal,
+        showSizeChanger: true,
+        showQuickJumper: true,
+        hideOnSinglePage: false,
+        showTotal: (total) => $t('common.pageTotal', { total }),
+      }"
+      @change="handleLogTableChange"
+    >
+      <template #bodyCell="{ column, record }">
+        <template v-if="column.key === 'result'">
+          <span class="operate-log-result operate-result-success" v-if="record.result === OperateResult.Success">
+            {{ $t('common.success') }}
+          </span>
+          <span class="operate-log-result operate-result-failure" v-else-if="record.result === OperateResult.Failure">
+            {{ $t('common.failure') }}
+          </span>
+        </template>
+      </template>
+    </ATable>
+  </ACard>
+</template>
+
+<style lang="scss" scoped>
+.operate-log-query {
+  margin-bottom: 16px;
+
+  > .ant-col {
+    display: flex;
+    align-items: center;
+  }
+}
+
+.operate-log-label {
+  width: 70px;
+  margin-right: 12px;
+  font-size: 14px;
+  line-height: 22px;
+  color: var(--antd-color-text);
+  text-align: right;
+}
+
+.operate-log-time {
+  :deep(input) {
+    text-align: center;
+  }
+}
+
+.operate-log-time,
+.operate-log-input {
+  flex: 1;
+}
+
+.operate-log-button {
+  display: flex;
+  flex-direction: row-reverse;
+
+  button + button {
+    margin-right: 12px;
+  }
+}
+
+.operate-log-result {
+  height: 24px;
+  padding: 2px 8px;
+  font-size: 12px;
+  line-height: 20px;
+  text-align: center;
+  border: 1px solid var(--antd-color-border);
+  border-radius: 4px;
+
+  &.operate-result-success {
+    color: #52c41a;
+    background: #f6ffed;
+    border-color: #b7eb8f;
+  }
+
+  &.operate-result-failure {
+    color: #f56c6c;
+    background: #fff1f0;
+    border-color: #ffa39e;
+  }
+}
+</style>

+ 209 - 0
src/views/log-center/SmartCtrlLog.vue

@@ -0,0 +1,209 @@
+<script setup lang="ts">
+import { computed, onMounted, ref } from 'vue';
+
+import { useRequest } from '@/hooks/request';
+import { t } from '@/i18n';
+import { getSmartCtrlLog, noPaginationDevicesList } from '@/api';
+import { disabledDate } from '@/utils';
+
+import type { ColumnType, TableProps } from 'ant-design-vue/es/table';
+import type { AllDevicesList, PageParams, RangeValue, SmartCtrlLogItem } from '@/types';
+
+const timeRange = ref<RangeValue>();
+const selectedDevices = ref<number[]>([]);
+const deviceList = ref<AllDevicesList[]>([]);
+const searchContent = ref('');
+
+const logList = ref<SmartCtrlLogItem[]>([]);
+const logTotal = ref(0);
+const pageParams = ref<PageParams>({
+  pageIndex: 1,
+  pageSize: 10,
+  pageSorts: [
+    {
+      column: 'id',
+      asc: false,
+    },
+  ],
+});
+
+const columns = computed<ColumnType<SmartCtrlLogItem>[]>(() => {
+  return [
+    {
+      title: t('common.device'),
+      dataIndex: 'deviceName',
+      key: 'deviceName',
+    },
+    {
+      title: t('common.content'),
+      dataIndex: 'content',
+      key: 'content',
+    },
+    {
+      title: t('common.time'),
+      dataIndex: 'updateTime',
+      key: 'updateTime',
+    },
+  ];
+});
+
+onMounted(() => {
+  getDeviceList();
+  getLogList();
+});
+
+const { handleRequest } = useRequest();
+
+const getDeviceList = () => {
+  handleRequest(async () => {
+    deviceList.value = await noPaginationDevicesList();
+  });
+};
+
+const { isLoading: isLogLoading, handleRequest: handleLogRequest } = useRequest();
+
+const getLogList = () => {
+  handleLogRequest(async () => {
+    const { records, total } = await getSmartCtrlLog({
+      ...pageParams.value,
+      deviceIds: selectedDevices.value.join(','),
+      searchContent: searchContent.value,
+      startTime: timeRange.value?.[0].format('YYYY-MM-DD HH:mm:ss'),
+      endTime: timeRange.value?.[1].format('YYYY-MM-DD HH:mm:ss'),
+    });
+
+    logList.value = records;
+    logTotal.value = total;
+  });
+};
+
+const filterDevice = (input: string, option: AllDevicesList) => {
+  return option.deviceName.toLowerCase().indexOf(input.toLowerCase()) >= 0;
+};
+
+const handleSearch = () => {
+  pageParams.value.pageIndex = 1;
+  getLogList();
+};
+
+const handleReset = () => {
+  timeRange.value = undefined;
+  selectedDevices.value = [];
+  searchContent.value = '';
+  handleSearch();
+};
+
+const handleLogTableChange: TableProps['onChange'] = (pagination) => {
+  pageParams.value.pageIndex = pagination.current || 1;
+  pageParams.value.pageSize = pagination.pageSize || 10;
+  getLogList();
+};
+</script>
+
+<template>
+  <ACard :bordered="false">
+    <ARow class="smart-ctrl-log-query" :gutter="[80, 16]">
+      <ACol :span="8">
+        <div class="smart-ctrl-log-label">{{ $t('logCenter.selectTimeRange') }}</div>
+        <ARangePicker
+          class="smart-ctrl-log-time"
+          v-model:value="timeRange"
+          :placeholder="[$t('common.startTime'), $t('common.endTime')]"
+          :separator="$t('common.to')"
+          :disabled-date="disabledDate"
+          show-time
+        >
+          <template #suffixIcon>
+            <SvgIcon name="calendar" color="#333" />
+          </template>
+        </ARangePicker>
+      </ACol>
+      <ACol :span="8">
+        <div class="smart-ctrl-log-label">{{ $t('logCenter.selectDevice') }}</div>
+        <ASelect
+          v-model:value="selectedDevices"
+          class="smart-ctrl-log-input"
+          :options="deviceList"
+          :field-names="{ label: 'deviceName', value: 'id' }"
+          mode="multiple"
+          :max-tag-count="2"
+          :max-tag-text-length="10"
+          :filter-option="filterDevice"
+          :placeholder="$t('logCenter.plzSelectDevice')"
+          allow-clear
+        />
+      </ACol>
+      <ACol :span="8">
+        <div class="smart-ctrl-log-label">{{ $t('common.inputContent') }}</div>
+        <AInput
+          v-model:value="searchContent"
+          class="smart-ctrl-log-input"
+          :placeholder="$t('logCenter.plzEnterContent')"
+          allow-clear
+        />
+      </ACol>
+    </ARow>
+    <div :span="8" class="smart-ctrl-log-button">
+      <AButton @click="handleReset">{{ $t('common.reset') }}</AButton>
+      <AButton type="primary" @click="handleSearch">{{ $t('common.query') }}</AButton>
+    </div>
+    <ATable
+      class="hvac-table"
+      :data-source="logList"
+      :columns="columns"
+      row-key="id"
+      :loading="isLogLoading"
+      :pagination="{
+        current: pageParams.pageIndex,
+        pageSize: pageParams.pageSize,
+        total: logTotal,
+        showSizeChanger: true,
+        showQuickJumper: true,
+        hideOnSinglePage: false,
+        showTotal: (total) => $t('common.pageTotal', { total }),
+      }"
+      @change="handleLogTableChange"
+    />
+  </ACard>
+</template>
+
+<style lang="scss" scoped>
+.smart-ctrl-log-query {
+  margin-bottom: 16px;
+
+  > .ant-col {
+    display: flex;
+    align-items: center;
+  }
+}
+
+.smart-ctrl-log-label {
+  width: 70px;
+  margin-right: 12px;
+  font-size: 14px;
+  line-height: 22px;
+  color: var(--antd-color-text);
+  text-align: right;
+}
+
+.smart-ctrl-log-time {
+  :deep(input) {
+    text-align: center;
+  }
+}
+
+.smart-ctrl-log-time,
+.smart-ctrl-log-input {
+  flex: 1;
+}
+
+.smart-ctrl-log-button {
+  display: flex;
+  flex-direction: row-reverse;
+  margin-block: 16px;
+
+  button + button {
+    margin-right: 12px;
+  }
+}
+</style>

+ 132 - 35
src/views/role-manage/RoleManage.vue

@@ -1,5 +1,5 @@
 <script setup lang="ts">
-import { nextTick, onMounted, ref, useTemplateRef } from 'vue';
+import { nextTick, onMounted, ref, useTemplateRef, watch } from 'vue';
 import { message } from 'ant-design-vue';
 
 import ConfirmModal from '@/components/ConfirmModal.vue';
@@ -7,9 +7,18 @@ import OrganizationalStructure from '@/components/OrganizationalStructure.vue';
 import SvgIcon from '@/components/SvgIcon.vue';
 import { useRequest } from '@/hooks/request';
 import { t } from '@/i18n';
-import { addCharacter, deleteCharacter, getFindRolesByOrgIds, getSubOrgsByToken, updateCharacter } from '@/api';
-
-import type { DataNode } from 'ant-design-vue/es/tree';
+import {
+  addCharacter,
+  addDevicePermissions,
+  deleteCharacter,
+  getAllGroupList,
+  getFindRolesByOrgIds,
+  getSubOrgsByToken,
+  updateCharacter,
+} from '@/api';
+
+import type { DataNode, TreeProps } from 'ant-design-vue/es/tree';
+import type { PermissionGroupItem } from '@/types';
 
 interface HaracterItem {
   name: string;
@@ -24,29 +33,23 @@ const inputRef = ref<HTMLInputElement[]>([]); // 明确的类型声明
 const permissions = ref<string>('dataPermissions');
 const equipmentChecked = ref<boolean>(false);
 const editorChecked = ref<boolean>(true);
-const valueTime = ref<string>('1');
-const pagePermissionsSelectedKeys = ref<number[]>([]);
+// const valueTime = ref<string>('1');
+const devicePermissionsSelectedKeys = ref<number[]>([]);
+const devicePermissionsExpandedKeys = ref<number[]>([]);
+const devicePermissionsCheckedKeys = ref<number[]>([]);
+const allKeys = ref<number[]>([]);
+const indeterminate = ref<boolean>(false);
 const orgId = ref<number>();
 const characterId = ref<number>();
 const enterShow = ref<boolean>(false);
 const modalComponentRef = useTemplateRef('modalComponent');
-const pagePermissionsTree = ref<DataNode[]>([
-  {
-    title: '父节点 1',
-    key: '0-0',
-    children: [
-      {
-        title: '子节点 1',
-        key: '0-0-0',
-        children: [
-          { title: '孙子节点 1', key: '0-0-0-0' },
-          { title: '孙子节点 2', key: '0-0-0-1' },
-        ],
-      },
-      { title: '子节点 2', key: '0-0-1' },
-    ],
-  },
-]);
+const pagePermissionsTree = ref<DataNode[]>([]);
+const fieldNames: TreeProps['fieldNames'] = {
+  children: 'deviceGroupChilds',
+  title: 'groupName',
+  key: 'id',
+};
+
 const addCharacterName = async () => {
   if (characterList.value.some((item) => !isValidString(item.name))) {
     return;
@@ -139,9 +142,23 @@ const editorPermission = () => {
   editorChecked.value = false;
 };
 const cancelPermission = () => {
+  getDeviceGroupList();
   editorChecked.value = true;
 };
+
+const addTree = () => {
+  console.log(devicePermissionsSelectedKeys.value);
+};
 const savePermission = () => {
+  if (permissions.value === 'dataPermissions') {
+    handleRequest(async () => {
+      await addDevicePermissions({
+        roleId: characterListId.value,
+        groupIds: devicePermissionsCheckedKeys.value,
+      });
+      getDeviceGroupList();
+    });
+  }
   editorChecked.value = true;
 };
 const clickOrganizationChange = (id: number) => {
@@ -165,15 +182,79 @@ const getFindRolesByOrg = (id: number) => {
       });
       if (!characterListId.value) {
         characterListId.value = data[0].id;
+        getDeviceGroupList();
       }
     }
   });
 };
 
+const getDeviceGroupList = () => {
+  handleRequest(async () => {
+    const list = await getAllGroupList({
+      roleId: undefined,
+    });
+    const data = await getAllGroupList({
+      roleId: characterListId.value,
+    });
+    devicePermissionsExpandedKeys.value = [];
+    devicePermissionsCheckedKeys.value = [];
+    allKeys.value = [];
+    list.forEach((item) => {
+      item.key = item.id;
+      allKeys.value.push(item.id);
+      devicePermissionsExpandedKeys.value.push(item.id);
+      if (item.deviceGroupChilds.length) {
+        item.deviceGroupChilds.forEach((i) => {
+          i.key = i.id;
+          allKeys.value.push(i.id);
+        });
+      }
+    });
+    devicePermissionsCheckedKeys.value = getAllKeys(data);
+    pagePermissionsTree.value = list as DataNode[];
+  });
+};
+
+const selectAll = () => {
+  indeterminate.value = false;
+  if (equipmentChecked.value) {
+    devicePermissionsCheckedKeys.value = allKeys.value;
+  } else {
+    devicePermissionsCheckedKeys.value = [];
+  }
+};
+
+// 获取所有节点 key方法
+const getAllKeys = (data: PermissionGroupItem[]) => {
+  const keys: number[] = [];
+  data.forEach((item) => {
+    keys.push(item.id);
+    if (item.deviceGroupChilds.length) {
+      item.deviceGroupChilds.forEach((i) => {
+        keys.push(i.id);
+      });
+    }
+  });
+  return keys;
+};
+
 const addRadioGroup = () => {
+  if (permissions.value === 'functionPermissions') {
+    getDeviceGroupList();
+  }
   editorChecked.value = true;
 };
 
+watch(
+  () => devicePermissionsCheckedKeys.value,
+  (count) => {
+    if (count) {
+      indeterminate.value = !!count.length && count.length < allKeys.value.length;
+      equipmentChecked.value = count.length === allKeys.value.length;
+    }
+  },
+);
+
 onMounted(() => {
   handleRequest(async () => {
     await getSubOrgsByToken();
@@ -255,18 +336,29 @@ onMounted(() => {
         </ARadioGroup>
         <div v-if="permissions === 'dataPermissions'">
           <AFlex align="center" class="device-permissions">
-            <ACheckbox class="select-all" v-model:checked="equipmentChecked" :disabled="editorChecked"
+            <ACheckbox
+              class="select-all"
+              v-model:checked="equipmentChecked"
+              :indeterminate="indeterminate"
+              :disabled="editorChecked"
+              @change="selectAll"
               >设备组权限</ACheckbox
             >
           </AFlex>
-          <ATree
-            v-model:selected-keys="pagePermissionsSelectedKeys"
-            :tree-data="pagePermissionsTree"
-            checkable
-            default-expand-all
-            :disabled="editorChecked"
-          />
-          <AFlex align="center" class="device-permissions div-top">
+          <div class="permission-div">
+            <ATree
+              v-model:expanded-keys="devicePermissionsExpandedKeys"
+              v-model:checked-keys="devicePermissionsCheckedKeys"
+              :tree-data="pagePermissionsTree"
+              checkable
+              default-expand-all
+              :field-names="fieldNames"
+              :disabled="editorChecked"
+              @check="addTree"
+            />
+          </div>
+
+          <!-- <AFlex align="center" class="device-permissions div-top">
             <ACheckbox class="select-all" :disabled="editorChecked" v-model:checked="equipmentChecked"
               >启用时间查询颗粒度设置</ACheckbox
             >
@@ -276,20 +368,20 @@ onMounted(() => {
             <ARadio value="2">小时</ARadio>
             <ARadio value="3">天</ARadio>
             <ARadio value="4">月</ARadio>
-          </ARadioGroup>
+          </ARadioGroup> -->
         </div>
         <div v-if="permissions === 'functionPermissions'">
           <AFlex align="center" class="device-permissions">
             <div>查看权限</div>
           </AFlex>
-          <ATree
+          <!-- <ATree
             v-model:selected-keys="pagePermissionsSelectedKeys"
             :tree-data="pagePermissionsTree"
             checkable
             default-expand-all
             class="tree-permissions"
             :disabled="editorChecked"
-          />
+          /> -->
           <AFlex align="center" class="device-permissions div-top">
             <div>操作权限</div>
           </AFlex>
@@ -308,6 +400,11 @@ onMounted(() => {
 </template>
 
 <style lang="scss" scoped>
+.permission-div {
+  height: calc(100vh - 244px);
+  overflow: auto;
+}
+
 .pointer-left {
   margin-left: 24px;
 }