Newer
Older
smartwell_front / src / views / system / user / listUser.vue
liyaguang 24 days ago 16 KB 用户批量加密功能
<script lang="ts" setup name="SystemDictListDict">
import { ElMessage, ElMessageBox } from 'element-plus'
import { Delete, EditPen, Key, MagicStick, Plus } from '@element-plus/icons-vue'
import type { Ref } from 'vue'
import type { IlistQueryType, TreeStructure, changePageType, userType } from './user-interface'
import AddUser from './userAdd.vue'
import RoleAssignment from './roleAssignment.vue'
import { getDeptTreeList } from '@/api/system/dept'
import { delUser, freezeUserApi, getUserList, resetPwdApi, unfreezeUser, updateUser } from '@/api/system/user'
import { toTreeList } from '@/utils/structure'
import { RSAencrypt } from '@/utils/security'
import type { TableColumn } from '@/components/NormalTable/table_interface'
import { encryptNoCodedString, encrypt, decrypt, isEncrypt } from '@/utils/security1'
import { result } from 'lodash-es'
// 逻辑代码
interface Tree {
  name: string
  children?: Tree[]
  id: string
}
const select = ref()
const { proxy } = getCurrentInstance() as any
const tagNames = { 已冻结: 'danger', 启用: 'success' }
const btnNames = { 已冻结: '解冻', 启用: '冻结' }
const btnStatus = { 已冻结: '', 启用: 'primary' }
const columns: Ref<TableColumn[]> = ref([])
columns.value = [
  { text: '账号', value: 'account', align: 'center' },
  { text: '所在组织机构', value: 'deptName', align: 'center' },
  { text: '真实姓名', value: 'name', align: 'center' },
  { text: '角色', value: 'roleName', align: 'center' },
  { text: '手机号', value: 'phone', align: 'center' },
  { text: '注册时间', value: 'createtime', align: 'center' },
]
const list = ref([])
const total = ref(0)
const loadingTable = ref<boolean>(false)
const loadingTree = ref<boolean>(false)
const resetPwdFlag = ref<string>('111111')
const defaultProps = {
  children: 'children',
  label: 'name',
}
// 查询参数
const listQuery: IlistQueryType = reactive({
  keywords: '',
  beginTime: '',
  endTime: '',
  deptId: '',
  offset: 1,
  limit: 20,
  sort: 'id',
  deptType: '',
})
const treeData = ref<TreeStructure[]>([])
// 获取列表数据
const fetchData = (flag: string) => {
  loadingTable.value = true
  select.value = ''
  var reg = new RegExp("[\\u4E00-\\u9FFF]+", "g")
  getUserList({
    ...listQuery,
    keywords: reg.test(listQuery.keywords) ? encryptNoCodedString(listQuery.keywords) : listQuery.keywords
  }).then((res) => {
    list.value = JSON.parse(JSON.stringify(res.data.rows || [])).map((item: any) => ({
      ...item,
      name: isEncrypt(item.name) ? decrypt(item.name) : item.name,
      phone: isEncrypt(item.phone) ? decrypt(item.phone) : item.phone,
      email: isEncrypt(item.email) ? decrypt(item.email) : item.email,
      isEncrypt: isEncrypt(item.name) && isEncrypt(item.email) && isEncrypt(item.phone)
    }))
    total.value = res.data.total
    loadingTable.value = false
    calcTableHeight()

    // if (JSON.parse(JSON.stringify(res.data.rows || [])).some((item: any) => !isEncrypt(item.name))) {
    //   // console.log('需要加密')
    //   list.value.forEach((item: any) => {
    //     // if (!isEncrypt(item.name) && !isEncrypt(item.phone) && !isEncrypt(item.email)) {
    //     updateUser({
    //       ...item,
    //       name: encrypt(item.name),
    //       phone: encrypt(item.phone),
    //       email: encrypt(item.email)
    //     }).then(res => {
    //       // console.log('更新成功')
    //     })
    //     // }
    //   })
    // }
    // else {
    //   // console.log('不需要加密')
    // }

  }).catch(() => {
    loadingTable.value = false
  })
}
// 点击树形结构
const handleNodeClick = (data: Tree) => {
  listQuery.deptId = data.id as string
  fetchData('click')
}

// 获取组织列表树数据
const fetchTreeData = () => {
  loadingTree.value = true
  getDeptTreeList(listQuery).then((res) => {
    treeData.value = toTreeList<TreeStructure>(res.data, '0', true)
    loadingTree.value = false
  }).catch(() => {
    loadingTree.value = false
  })
}
// 查询数据
const search = () => {
  fetchData('search')
}
const addUserRef = ref()
// 新增用户
const add = () => {
  addUserRef.value.initDialog('create')
}
// 编辑用户
const edit = () => {
  if (select.value) {
    const user: userType = list.value[select.value - 1]
    addUserRef.value.initDialog('update', user)
  }
  else {
    ElMessage.error('必须选中用户')
  }
}
// 重置密码
const resetPwd = () => {
  if (select.value) {
    const user: userType = list.value[select.value - 1]
    ElMessageBox.confirm(
      `确定要重置${user.name}的密码为${resetPwdFlag.value}吗?`,
      '确认重置密码',
      {
        confirmButtonText: '确认',
        cancelButtonText: '取消',
        type: 'warning',
      },
    ).then(async () => {
      const params = {
        id: String(user.id),
        newPwd: await RSAencrypt(resetPwdFlag.value),
      }
      resetPwdApi(params).then(() => {
        ElMessage.success('操作成功')
      })
    }).catch(() => { })
  }
  else {
    ElMessage.error('必须选中用户')
  }
}
// 批量加密
// const index = ref(0)
// const batchEncrypt = () => {
//   if (total.value === 0) {
//     ElMessage.warning('暂无用户')
//     return
//   }
//   for (let i = index.value * 20; i < ((index.value + 1) * 20); i++) {
//     getUserList({ offset: i, limit: 1 }).then(res => {
//       if (res.data.rows.length) {
//         const data = res.data.rows[0]
//         if (!isEncrypt(data.name) && !isEncrypt(data.phone) && !isEncrypt(data.email)) {
//           updateUser({
//             ...data,
//             name: encrypt(data.name),
//             phone: encrypt(data.phone),
//             email: encrypt(data.email)
//           }).then(res => {
//             console.log('更新成功')
//           })
//         }
//       }
//     })
//   }
//   index.value += 1

// }
// 删除用户
const deleteUser = () => {
  if (select.value) {
    const user: userType = list.value[select.value - 1]
    ElMessageBox.confirm(
      `确定要删除${user.name}吗?`,
      'Warning',
      {
        confirmButtonText: '确认',
        cancelButtonText: '取消',
        type: 'warning',
      },
    ).then(() => {
      delUser({ id: user.id }).then(() => {
        ElMessage.success('操作成功')
        fetchData('')
      })
    }).catch(() => { })
  }
  else {
    ElMessage.error('必须选中用户')
  }
}
// 冻结/解冻用户
const frozenUser = (row: userType) => {
  ElMessageBox.confirm(
    `确定要${(btnNames as { [key: string]: string })[row.statusName]}${row.name}吗?`,
    'Warning',
    {
      confirmButtonText: '确认',
      cancelButtonText: '取消',
      type: 'warning',
    },
  ).then(() => {
    if ((btnNames as any)[row.statusName] === '冻结') {
      // 发送请求
      freezeUserApi({ id: row.id }).then((res) => {
        ElMessage.success('操作成功')
        fetchData('')
      })
    }
    else {
      unfreezeUser({ id: row.id }).then((res) => {
        ElMessage.success('操作成功')
        fetchData('')
      })
    }
  }).catch(() => { })
}
const roleAssignmentRef = ref() // 角色分配弹窗
// 角色分配
const assignmentRole = () => {
  if (select.value) {
    const user: userType = list.value[select.value - 1]
    roleAssignmentRef.value.initDialog(user)
  }
  else {
    ElMessage.error('必须选中用户')
  }
}
// 页数发生变化后的操作,可能是页码变化,可能是每页容量变化,此函数必写
const changePage = (val: changePageType) => {
  if (val && val.size) {
    listQuery.limit = val.size
  }
  if (val && val.page) {
    listQuery.offset = val.page
  }
  fetchData('')
}
// 重置页面
const resetData = () => {
  fetchData('')
}
// 挂载
onMounted(() => {
  // 获取树形结构数据
  fetchTreeData()
  // 获取列表数据
  fetchData('click')
  window.addEventListener('resize', calcTableHeight)
  calcTableHeight()
})

const tableHeight = ref(400)
// const
const calcTableHeight = () => {
  // 顶部高度
  const topHeight = 50 + 60
  // app-container的 padding距离
  const appPadding = 20
  // 查询组件的高度
  const searchDiv = document.getElementById('search-div-id')
  const searchHeight = searchDiv ? searchDiv.clientHeight : 0
  // 表格顶部的文字提示高度
  const tableTopHeight = 32 + 10
  // 表格表头
  const tableHeaderHeight = 40
  // 分页器的高度
  const tablePaginationHeight = 40
  // 判断数据长度
  const height = window.innerHeight - topHeight - appPadding - searchHeight - tableTopHeight - tableHeaderHeight - tablePaginationHeight
  if (list.value.length * 50 >= height) {
    tableHeight.value = height
  }
  else {
    tableHeight.value = list.value.length ? (list.value.length + 1) * 50 : 100
  }
}
onBeforeUnmount(() => {
  window.removeEventListener('resize', calcTableHeight)
})

// 批量加密用户
const tableRef = ref()
const multiSelectData = ref([])
const clearSelect = () => {
  multiSelectData.value = []
  tableRef.value?.clearMulti()
}
const multiSelect = (data: any) => {
  multiSelectData.value = data
}
const loadingBtn = ref(false)
const encryptUser = () => {
  if (!multiSelectData.value.length) {
    ElMessage.warning('请选择要加密的用户')
    return
  }
  loadingBtn.value = true
  async function updateArrayItems(arr) {
    const results = await Promise.allSettled(
      arr.map((item, index) =>
        updateUser({
          ...item,
          name: isEncrypt(item.name) ? item.name : encrypt(item.name),
          phone: isEncrypt(item.phone) ? item.phone : encrypt(item.phone),
          email: isEncrypt(item.email) ? item.email : encrypt(item.email),
          isEncrypt: undefined,
        })
          .then(() => ({ index, success: true }))
          .catch(error => ({ index, success: false, error }))
      )
    );

    const failedRequests = results
      .filter(r => r.status === 'rejected' || !r.value.success)
      .map(r => ({
        index: r.value?.index ?? results.indexOf(r),
        error: r.reason || r.value.error
      }));

    if (failedRequests.length > 0) {
      console.error('以下请求失败:');
      failedRequests.forEach(req => {
        const item = arr[req.index];
        console.error(`- 数据 ${item.id} (索引 ${req.index}) 失败:${req.error.message}`);
      });
      return { success: false, failedRequests };
    } else {
      console.log('所有请求成功!');
      return { success: true };
    }
  }
  updateArrayItems(multiSelectData.value).then(result => {
    loadingBtn.value = false
    if (result.success) {
      // 显示成功提示
      ElMessage.success('加密成功')
      clearSelect()
      fetchData('')
    } else {
      console.log(result.failedRequests, '加密失败')
      const fail = (result.failedRequests || []).map((item) => multiSelectData.value[item.index]?.name || '')
      // 显示失败详情
      ElMessage.warning(`${(result.failedRequests || []).length}个加密失败,分别是:${fail.join(',')}`)
      clearSelect()
      fetchData('')
    }
  }).catch(() => {
    loadingBtn.value = false
  })

}
</script>

<template>
  <!-- 布局 -->
  <app-container>
    <div class="container">
      <!-- 左侧组织机构 -->
      <div class="left-container">
        <div class="dept-div">
          <el-card class="box-card" shadow="always">
            <template #header>
              <div class="clearfix">
                <span>组织机构</span>
              </div>
            </template>
            <el-scrollbar height="100%" class="user-dept-scroll">
              <el-tree v-loading="loadingTree" :data="treeData" :props="defaultProps" default-expand-all
                :expand-on-click-node="false" @node-click="handleNodeClick" />
            </el-scrollbar>
          </el-card>
        </div>
      </div>
      <!-- 右侧表格 -->
      <div ref="tableContainer" class="table">
        <!-- 筛选条件 -->
        <search-area @search="search">
          <search-item>
            <el-input v-model.trim="listQuery.keywords" placeholder="账户/真实姓名/手机号" clearable style="width: 162px;" />
          </search-item>
          <search-item>
            <el-date-picker v-model="listQuery.beginTime" type="datetime" format="YYYY-MM-DD HH:mm:ss"
              value-format="YYYY-MM-DD HH:mm:ss" placeholder="选择开始时间" />
          </search-item>
          <search-item>
            <el-date-picker v-model="listQuery.endTime" type="datetime" format="YYYY-MM-DD HH:mm:ss"
              value-format="YYYY-MM-DD HH:mm:ss" placeholder="选择结束时间" />
          </search-item>
        </search-area>
        <!-- 表头标题 -->
        <table-container :title-show="false">
          <template #btns-right>
            <!-- 操作 -->
            <div>
              <el-button v-if="proxy.hasPerm('/sys/mgr/add')" type="primary" :icon="Plus" @click="add">
                新增
              </el-button>
              <el-button v-if="proxy.hasPerm('/sys/mgr/update')" type="primary" :icon="EditPen" @click="edit">
                修改
              </el-button>
              <el-button v-if="proxy.hasPerm('/sys/mgr/delete')" type="primary" :icon="Delete" @click="deleteUser">
                删除
              </el-button>
              <el-button v-if="proxy.hasPerm('/sys/mgr/reset')" type="primary" :icon="Key" @click="resetPwd">
                重置密码
              </el-button>
              <el-button v-if="proxy.hasPerm('/sys/mgr/roleAssign')" type="primary" :icon="MagicStick"
                @click="assignmentRole">
                角色分配
              </el-button>
              <!-- v-if="proxy.hasPerm('/sys/mgr/encrypt')" -->
              <el-button v-if="proxy.hasPerm('/sys/mgr/encrypt')" type="primary" :icon="Key" @click="encryptUser" :disabled="loadingBtn">
                加密
              </el-button>
            </div>
          </template>
          <!-- 查询结果Table显示 -->
          <normal-table ref="tableRef" :data="list" :total="total" :columns="columns" :query="listQuery"
            :list-loading="loadingTable" :height="tableHeight" @change="changePage" :is-showmulti-select="proxy.hasPerm('/sys/mgr/encrypt')"
            @multi-select="multiSelect">
            <template #preColumns>
              <el-table-column label="选择" width="55" align="center">
                <template #default="scope">
                  <el-radio v-model="select" :label="scope.$index + 1" class="radio" />
                </template>
              </el-table-column>
              <el-table-column label="#" width="80" align="center">
                <template #default="scope">
                  {{ (listQuery.offset - 1) * listQuery.limit + scope.$index + 1 }}
                </template>
              </el-table-column>
            </template>
            <template #columns>
              <el-table-column v-if="proxy.hasPerm('/sys/mgr/encrypt')" label="是否加密" align="center" width="85">
                <template #default="scope">
                  {{ scope.row.isEncrypt ? '是' : '否' }}
                </template>
              </el-table-column>
              <el-table-column label="状态" align="center" width="80">
                <template #default="scope">
                  <el-tag :type="(tagNames as any)[scope.row.statusName]">
                    {{ scope.row.statusName }}
                  </el-tag>
                </template>
              </el-table-column>
              <el-table-column label="操作" align="center" width="60">
                <template #default="scope">
                  <el-button :type="(btnStatus as any)[scope.row.statusName]" link size="small"
                    @click="frozenUser(scope.row)">
                    {{ (btnNames as any)[scope.row.statusName] }}
                  </el-button>
                </template>
              </el-table-column>
            </template>
          </normal-table>
        </table-container>
      </div>
    </div>
    <!-- 角色分配 -->
    <role-assignment ref="roleAssignmentRef" @reset-data="resetData" />
    <!-- 添加或修改用户 -->
    <add-user ref="addUserRef" @reset-data="resetData" />
  </app-container>
</template>

<style lang="scss" scoped>
// 样式
.container {
  width: 100%;
  display: flex;

  .left-container {
    width: 22%;
  }

  :deep(.el-radio__label) {
    display: none;
  }

  .table {
    width: 78%;
  }
}

.dept-div {
  padding-right: 12px;

  :deep(.el-card) {
    height: calc(100vh - 50px - 25px - 60px);
    background-color: #fff;
  }

  .box-card {
    width: 100%;

    .user-dept-scroll {
      width: 100%;
      height: calc(100vh - 50px - 60px - 20px - 40px - 60px);
    }
  }
}
</style>