<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>