用户管理接口完成
This commit is contained in:
@@ -0,0 +1,102 @@
|
|||||||
|
import { request } from '../request';
|
||||||
|
|
||||||
|
// 组织列表
|
||||||
|
export function orgListApi() {
|
||||||
|
return request({
|
||||||
|
url: '/api/organization/tree',
|
||||||
|
method: 'get',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 新增组织
|
||||||
|
export function addOrgApi(params: { label: string, parent_id: number, sort: number, is_bottom: number, org_code: string }) {
|
||||||
|
return request({
|
||||||
|
url: '/api/organization',
|
||||||
|
method: 'post',
|
||||||
|
data: params
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 编辑组织
|
||||||
|
export function editOrgApi(id: number, params: { label: string, parent_id: number, sort: number, is_bottom: number, org_code: string }) {
|
||||||
|
return request({
|
||||||
|
url: `/api/organization/${id}`,
|
||||||
|
method: 'put',
|
||||||
|
data: params
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 删除组织
|
||||||
|
export function deleteOrgApi(id: number) {
|
||||||
|
return request({
|
||||||
|
url: `/api/organization/${id}`,
|
||||||
|
method: 'delete',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取用户列表
|
||||||
|
export function userListApi(params: {org_id: number, keyword: string, page: number, limit: number }) {
|
||||||
|
return request({
|
||||||
|
url: '/api/user',
|
||||||
|
method: 'get',
|
||||||
|
params
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增用户
|
||||||
|
export function addUserApi(params: { real_name: string, username: string, gender: number, mobile?: string, email?: string, dept_ids: number[], role_id: number | null, org_ids: number[], enabled: boolean }) {
|
||||||
|
return request({
|
||||||
|
url: '/api/user',
|
||||||
|
method: 'post',
|
||||||
|
data: params
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 编辑用户
|
||||||
|
export function editUserApi(id: number, params: { real_name: string, username: string, gender: number, mobile?: string, email?: string, dept_ids: number[], role_id: number | null, org_ids: number[], enabled: boolean }) {
|
||||||
|
return request({
|
||||||
|
url: `/api/user/${id}`,
|
||||||
|
method: 'put',
|
||||||
|
data: params
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除用户
|
||||||
|
export function deleteUserApi(params: { id: number, type: number }) {
|
||||||
|
return request({
|
||||||
|
url: '/api/user/operation',
|
||||||
|
method: 'post',
|
||||||
|
data: params
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 重置数据权限
|
||||||
|
export function resetDataApi() {
|
||||||
|
return request({
|
||||||
|
url: `/api/user/reset-data-permission`,
|
||||||
|
method: 'post',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 下载用户导入模板
|
||||||
|
export function downloadTemplateApi() {
|
||||||
|
return request({
|
||||||
|
url: `/api/user/template`,
|
||||||
|
method: 'get',
|
||||||
|
responseType: 'blob',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// 导入用户
|
||||||
|
export function importUserApi(params: FormData) {
|
||||||
|
return request({
|
||||||
|
url: `/api/user/import`,
|
||||||
|
method: 'post',
|
||||||
|
data: params
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 重置用户密码
|
||||||
|
export function resetPasswordApi(id: number) {
|
||||||
|
return request({
|
||||||
|
url: `/api/user/${id}/reset-password`,
|
||||||
|
method: 'post',
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
Vendored
+4
@@ -65,7 +65,9 @@ declare module 'vue' {
|
|||||||
NMessageProvider: typeof import('naive-ui')['NMessageProvider']
|
NMessageProvider: typeof import('naive-ui')['NMessageProvider']
|
||||||
NModal: typeof import('naive-ui')['NModal']
|
NModal: typeof import('naive-ui')['NModal']
|
||||||
NNotificationProvider: typeof import('naive-ui')['NNotificationProvider']
|
NNotificationProvider: typeof import('naive-ui')['NNotificationProvider']
|
||||||
|
NPagination: typeof import('naive-ui')['NPagination']
|
||||||
NPopconfirm: typeof import('naive-ui')['NPopconfirm']
|
NPopconfirm: typeof import('naive-ui')['NPopconfirm']
|
||||||
|
NPopover: typeof import('naive-ui')['NPopover']
|
||||||
NProgress: typeof import('naive-ui')['NProgress']
|
NProgress: typeof import('naive-ui')['NProgress']
|
||||||
NRadio: typeof import('naive-ui')['NRadio']
|
NRadio: typeof import('naive-ui')['NRadio']
|
||||||
NRadioGroup: typeof import('naive-ui')['NRadioGroup']
|
NRadioGroup: typeof import('naive-ui')['NRadioGroup']
|
||||||
@@ -153,7 +155,9 @@ declare global {
|
|||||||
const NMessageProvider: typeof import('naive-ui')['NMessageProvider']
|
const NMessageProvider: typeof import('naive-ui')['NMessageProvider']
|
||||||
const NModal: typeof import('naive-ui')['NModal']
|
const NModal: typeof import('naive-ui')['NModal']
|
||||||
const NNotificationProvider: typeof import('naive-ui')['NNotificationProvider']
|
const NNotificationProvider: typeof import('naive-ui')['NNotificationProvider']
|
||||||
|
const NPagination: typeof import('naive-ui')['NPagination']
|
||||||
const NPopconfirm: typeof import('naive-ui')['NPopconfirm']
|
const NPopconfirm: typeof import('naive-ui')['NPopconfirm']
|
||||||
|
const NPopover: typeof import('naive-ui')['NPopover']
|
||||||
const NProgress: typeof import('naive-ui')['NProgress']
|
const NProgress: typeof import('naive-ui')['NProgress']
|
||||||
const NRadio: typeof import('naive-ui')['NRadio']
|
const NRadio: typeof import('naive-ui')['NRadio']
|
||||||
const NRadioGroup: typeof import('naive-ui')['NRadioGroup']
|
const NRadioGroup: typeof import('naive-ui')['NRadioGroup']
|
||||||
|
|||||||
@@ -96,3 +96,48 @@ export function formatTimestamp(timestamp: number | string,format: string = 'yyy
|
|||||||
}
|
}
|
||||||
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 辅助函数:Fisher-Yates 洗牌算法
|
||||||
|
function shuffle(arr:any) {
|
||||||
|
for (let i = arr.length - 1; i > 0; i--) {
|
||||||
|
const j = Math.floor(Math.random() * (i + 1));
|
||||||
|
[arr[i], arr[j]] = [arr[j], arr[i]];
|
||||||
|
}
|
||||||
|
return arr;
|
||||||
|
}
|
||||||
|
// 辅助函数:从字符串中随机获取字符
|
||||||
|
function getRandomChar(str:any) {
|
||||||
|
return str[Math.floor(Math.random() * str.length)];
|
||||||
|
}
|
||||||
|
// 生成至少12位,包含四类字符中的至少三种的随机密码
|
||||||
|
export function generateSecurePassword(passwordLength:number) {
|
||||||
|
// 定义字符池
|
||||||
|
const charSets:any = {
|
||||||
|
uppercase: 'ABCDEFGHIJKLMNOPQRSTUVWXYZ',
|
||||||
|
lowercase: 'abcdefghijklmnopqrstuvwxyz',
|
||||||
|
numbers: '0123456789',
|
||||||
|
symbols: '!@#$%^&*()_+-=[]{}|;:,.<>?'
|
||||||
|
};
|
||||||
|
|
||||||
|
// 随机选择三种字符类型(保证至少三类)
|
||||||
|
const requiredTypes = shuffle(Object.keys(charSets)).slice(0, 3);
|
||||||
|
|
||||||
|
// 生成必选字符(每类至少1个)
|
||||||
|
let password = requiredTypes
|
||||||
|
.map((type:any) => getRandomChar(charSets[type]))
|
||||||
|
.join('');
|
||||||
|
|
||||||
|
// 生成剩余字符(允许包含所有四类)
|
||||||
|
const remainingLength = passwordLength - password.length;
|
||||||
|
const allChars = Object.values(charSets).join('');
|
||||||
|
for (let i = 0; i < remainingLength; i++) {
|
||||||
|
password += getRandomChar(allChars);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 打乱字符顺序
|
||||||
|
return shuffle(password.split('')).join('');
|
||||||
|
}
|
||||||
|
// 获取 token
|
||||||
|
export const getToken = () => {
|
||||||
|
return localStorage.getItem('token');
|
||||||
|
};
|
||||||
@@ -5,6 +5,7 @@ import json5 from 'json5';
|
|||||||
*
|
*
|
||||||
* @param env The current env
|
* @param env The current env
|
||||||
*/
|
*/
|
||||||
|
let url = 'http://192.168.0.230:8086';
|
||||||
export function createServiceConfig(env: Env.ImportMeta) {
|
export function createServiceConfig(env: Env.ImportMeta) {
|
||||||
const { VITE_SERVICE_BASE_URL, VITE_OTHER_SERVICE_BASE_URL } = env;
|
const { VITE_SERVICE_BASE_URL, VITE_OTHER_SERVICE_BASE_URL } = env;
|
||||||
|
|
||||||
@@ -15,7 +16,6 @@ export function createServiceConfig(env: Env.ImportMeta) {
|
|||||||
// eslint-disable-next-line no-console
|
// eslint-disable-next-line no-console
|
||||||
console.error('VITE_OTHER_SERVICE_BASE_URL is not a valid json5 string');
|
console.error('VITE_OTHER_SERVICE_BASE_URL is not a valid json5 string');
|
||||||
}
|
}
|
||||||
let url = 'http://192.168.0.230:8086';
|
|
||||||
if(env.DEV){
|
if(env.DEV){
|
||||||
url = 'http://192.168.0.230:8086';
|
url = 'http://192.168.0.230:8086';
|
||||||
}else{
|
}else{
|
||||||
@@ -44,6 +44,9 @@ export function createServiceConfig(env: Env.ImportMeta) {
|
|||||||
|
|
||||||
return config;
|
return config;
|
||||||
}
|
}
|
||||||
|
export function getBaseUrl() {
|
||||||
|
return url;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* get backend service base url
|
* get backend service base url
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ import { useAppStore } from '@/store/modules/app';
|
|||||||
import { Icon } from '@iconify/vue'
|
import { Icon } from '@iconify/vue'
|
||||||
import { useThemeStore } from '@/store/modules/theme';
|
import { useThemeStore } from '@/store/modules/theme';
|
||||||
import { useRouter } from 'vue-router';
|
import { useRouter } from 'vue-router';
|
||||||
import { NButton, NIcon } from 'naive-ui';
|
import { NButton } from 'naive-ui';
|
||||||
import { roleListApi, editRoleApi } from '@/service/api/role';
|
import { roleListApi, editRoleApi } from '@/service/api/role';
|
||||||
|
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
@@ -86,12 +86,11 @@ const list = ref<{ id: number, name: string, intro: string, perms: string[] }[]>
|
|||||||
// 获取列表
|
// 获取列表
|
||||||
const getList = async () => {
|
const getList = async () => {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
try {
|
const {data,error} = await roleListApi();
|
||||||
const res = await roleListApi();
|
if(!error){
|
||||||
list.value = res.data;
|
list.value = data;
|
||||||
} finally {
|
|
||||||
loading.value = false;
|
|
||||||
}
|
}
|
||||||
|
loading.value = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 设置权限
|
// 设置权限
|
||||||
@@ -116,18 +115,16 @@ const saveRole = (e: MouseEvent) => {
|
|||||||
formRef.value?.validate(async (errors: any) => {
|
formRef.value?.validate(async (errors: any) => {
|
||||||
if (!errors) {
|
if (!errors) {
|
||||||
btnLoading.value = true;
|
btnLoading.value = true;
|
||||||
try {
|
const {error} = await editRoleApi(currentRole.value);
|
||||||
await editRoleApi(currentRole.value);
|
if(!error){
|
||||||
const target = list.value.find((item:any) => item.id === currentRole.value.id);
|
const target = list.value.find((item:any) => item.id === currentRole.value.id);
|
||||||
if (target) {
|
if (target) {
|
||||||
target.name = currentRole.value.name;
|
target.name = currentRole.value.name;
|
||||||
}
|
}
|
||||||
window.$message?.success('操作成功')
|
window.$message?.success('操作成功')
|
||||||
btnLoading.value = false;
|
|
||||||
roleModal.value = false;
|
roleModal.value = false;
|
||||||
} finally {
|
|
||||||
btnLoading.value = false;
|
|
||||||
}
|
}
|
||||||
|
btnLoading.value = false;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
@@ -135,15 +132,13 @@ const saveRole = (e: MouseEvent) => {
|
|||||||
const savePermission = async () => {
|
const savePermission = async () => {
|
||||||
btnLoading.value = true;
|
btnLoading.value = true;
|
||||||
currentRole.value.perms = defaultCheckedKeys.value;
|
currentRole.value.perms = defaultCheckedKeys.value;
|
||||||
try {
|
const {error} = await editRoleApi(currentRole.value);
|
||||||
await editRoleApi(currentRole.value);
|
if(!error){
|
||||||
window.$message?.success('操作成功')
|
window.$message?.success('操作成功')
|
||||||
btnLoading.value = false;
|
|
||||||
permissionModal.value = false;
|
permissionModal.value = false;
|
||||||
getList();
|
getList();
|
||||||
} finally {
|
|
||||||
btnLoading.value = false;
|
|
||||||
}
|
}
|
||||||
|
btnLoading.value = false;
|
||||||
}
|
}
|
||||||
// 更新选中权限
|
// 更新选中权限
|
||||||
const updateCheckedKeys = (keys: string[]) => {
|
const updateCheckedKeys = (keys: string[]) => {
|
||||||
|
|||||||
@@ -1,87 +1,97 @@
|
|||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, h, onMounted } from 'vue';
|
import { ref, computed, h, onMounted } from 'vue';
|
||||||
import { useAppStore } from '@/store/modules/app';
|
import { useAppStore } from '@/store/modules/app';
|
||||||
|
import { pinyin } from 'pinyin-pro';
|
||||||
import { Icon } from '@iconify/vue'
|
import { Icon } from '@iconify/vue'
|
||||||
import { useThemeStore } from '@/store/modules/theme';
|
import { useThemeStore } from '@/store/modules/theme';
|
||||||
import { NButton, NPopconfirm, NTag, NDropdown, useDialog } from 'naive-ui';
|
import { NButton, NPopconfirm, NTag, NDropdown, useDialog } from 'naive-ui';
|
||||||
import type { DataTableColumns, FormRules } from 'naive-ui';
|
import type { DataTableColumns } from 'naive-ui';
|
||||||
|
import { roleListApi } from '@/service/api/role';
|
||||||
|
import {
|
||||||
|
orgListApi,
|
||||||
|
addOrgApi,
|
||||||
|
editOrgApi,
|
||||||
|
deleteOrgApi,
|
||||||
|
userListApi,
|
||||||
|
addUserApi,
|
||||||
|
editUserApi,
|
||||||
|
deleteUserApi,
|
||||||
|
resetDataApi,
|
||||||
|
downloadTemplateApi,
|
||||||
|
importUserApi,
|
||||||
|
resetPasswordApi
|
||||||
|
} from '@/service/api/user';
|
||||||
|
|
||||||
const appStore = useAppStore();
|
const appStore = useAppStore();
|
||||||
const themeStore = useThemeStore();
|
const themeStore = useThemeStore();
|
||||||
const gap = computed(() => (appStore.isMobile ? 0 : 16));
|
const gap = computed(() => (appStore.isMobile ? 0 : 16));
|
||||||
const dialog = useDialog()
|
const dialog = useDialog()
|
||||||
|
|
||||||
|
const roleList = ref<{ label: string, value: number }[]>([]);
|
||||||
|
const restLoading = ref<boolean>(false);
|
||||||
|
const treeLoading = ref<boolean>(false);
|
||||||
const loading = ref<boolean>(false);
|
const loading = ref<boolean>(false);
|
||||||
const btnLoading = ref<boolean>(false);
|
const btnLoading = ref<boolean>(false);
|
||||||
const currentType = ref<string>('add');
|
const currentType = ref<string>('add');
|
||||||
|
const currentOrg = ref<number>(1);
|
||||||
const formRef = ref<any>(null);
|
const formRef = ref<any>(null);
|
||||||
const keyword = ref<string>('');
|
const keyword = ref<string>('');
|
||||||
// 当前用户
|
// 当前用户
|
||||||
const currentUser = ref<{ id: number, name: string, dept: string[], mobile: string, email: string, account: string, role: string | null, dataPermission: string[], status: number, gender: number }>({
|
const currentUser = ref<{ id: number, real_name: string, dept_ids: number[], mobile?: string, email?: string, username: string, role_id: number | null, org_ids: number[], enabled: boolean, gender: number }>({
|
||||||
id: 0,
|
id: 0,
|
||||||
name: '',
|
real_name: '',
|
||||||
dept: [],
|
dept_ids: [],
|
||||||
mobile: '',
|
mobile: '',
|
||||||
email: '',
|
email: '',
|
||||||
account: '',
|
username: '',
|
||||||
role: null,
|
role_id: null,
|
||||||
dataPermission: [],
|
org_ids: [],
|
||||||
status: 1,
|
enabled: true,
|
||||||
gender: 1,
|
gender: 1,
|
||||||
});
|
});
|
||||||
// 校验规则
|
// 校验规则
|
||||||
const rules = ref<FormRules>({
|
const rules = {
|
||||||
name: { required: true, message: '角色名称不能为空', trigger: ['blur'] },
|
real_name: { required: true, message: '姓名不能为空', trigger: ['blur'] },
|
||||||
dept: {
|
dept_ids: {
|
||||||
type: 'array',
|
type: 'array',
|
||||||
required: true,
|
required: true,
|
||||||
trigger: ['blur', 'change'],
|
trigger: ['blur', 'change'],
|
||||||
message: '请选择所属部门'
|
message: '请选择所属部门'
|
||||||
},
|
},
|
||||||
account: { required: true, message: '账号不能为空', trigger: ['blur'] },
|
username: { required: true, message: '账号不能为空', trigger: ['blur'] },
|
||||||
role: { required: true, message: '请选择账号角色', trigger: ['blur', 'change'] },
|
role_id: { type: 'number', required: true, message: '请选择账号角色', trigger: ['blur', 'change'] },
|
||||||
dataPermission: {
|
org_ids: {
|
||||||
type: 'array',
|
type: 'array',
|
||||||
required: true,
|
required: true,
|
||||||
trigger: ['blur', 'change'],
|
trigger: ['blur', 'change'],
|
||||||
message: '请选择数据权限'
|
message: '请选择数据权限'
|
||||||
},
|
},
|
||||||
status: { required: true, message: '请选择状态', trigger: ['blur', 'change'] },
|
}
|
||||||
gender: { required: true, message: '请选择性别', trigger: ['blur', 'change'] },
|
|
||||||
})
|
|
||||||
const userModal = ref<boolean>(false);
|
const userModal = ref<boolean>(false);
|
||||||
const pagination = ref<{ page: number, pageSize: number, pageCount: number }>({
|
const pagination = ref({
|
||||||
page: 1,
|
page: 1,
|
||||||
pageSize: 20,
|
pageSize: 3,
|
||||||
pageCount: 0,
|
itemCount: 0,
|
||||||
})
|
})
|
||||||
const list = ref<{ id: number, name: string, dept: string[], mobile: string, email: string, account: string, role: string | null, dataPermission: string[], status: number, gender: number }[]>([])
|
const list = ref<{ id: number, name: string, dept: string[], mobile: string, email: string, account: string, role: string | null, dataPermission: string[], status: number, gender: number }[]>([])
|
||||||
// 账号角色选项
|
|
||||||
const roleOptions = ref([
|
|
||||||
{ label: '管理员', value: 'admin' },
|
|
||||||
{ label: '普通用户', value: 'user' },
|
|
||||||
])
|
|
||||||
|
|
||||||
const formNodeRef = ref<any>(null);
|
const formNodeRef = ref<any>(null);
|
||||||
const treeModal = ref<boolean>(false);
|
const treeModal = ref<boolean>(false);
|
||||||
const currentNode = ref<{ id: string, label: string, key: string, children: any[], shortLabel: string, parent: string, isLeaf: number, sort: number }>({
|
const currentNode = ref<{ id: number, label: string, children: any[], is_bottom: number, org_code: string, parent_id: number, tier: number, sort: number }>({
|
||||||
id: '',
|
id: 0,
|
||||||
label: '',
|
label: '',
|
||||||
key: '',
|
|
||||||
children: [],
|
children: [],
|
||||||
shortLabel: '',
|
is_bottom: 0,
|
||||||
parent: '',
|
org_code: '',
|
||||||
isLeaf: 1,
|
parent_id: 0,
|
||||||
|
tier: 0,
|
||||||
sort: 0,
|
sort: 0,
|
||||||
})
|
})
|
||||||
const formNode = ref<{ id: string, label: string, key: string, children: any[], shortLabel: string, parent: string, isLeaf: number, sort: number }>({
|
const formNode = ref<{ label: string, is_bottom: number, org_code: string, parent_id: number, sort: number }>({
|
||||||
id: '',
|
|
||||||
label: '',
|
label: '',
|
||||||
key: '',
|
is_bottom: 0,
|
||||||
children: [],
|
org_code: '',
|
||||||
shortLabel: '',
|
parent_id: 0,
|
||||||
parent: '',
|
|
||||||
isLeaf: 1,
|
|
||||||
sort: 0,
|
sort: 0,
|
||||||
})
|
})
|
||||||
const currentNodeType = ref<string>('edit')
|
const currentNodeType = ref<string>('edit')
|
||||||
@@ -90,42 +100,9 @@ const treeRules = ref({
|
|||||||
label: [{ required: true, message: '请输入层级名称', trigger: ['blur'] }],
|
label: [{ required: true, message: '请输入层级名称', trigger: ['blur'] }],
|
||||||
})
|
})
|
||||||
// 左侧组织树
|
// 左侧组织树
|
||||||
const orgTree = ref<any[]>([
|
const orgTree = ref<any[]>([]);
|
||||||
{
|
const expandedKeys = ref<number[]>([1])
|
||||||
id: "factory", label: "演示工厂", key: "YS", shortLabel: 'YS', parent: '', isLeaf: 0, sort: 0,
|
const selectedKeys = ref<number[]>([1])
|
||||||
children: [
|
|
||||||
{
|
|
||||||
id: "scb", label: "生产部", key: "SCB", shortLabel: 'SCB', parent: '演示工厂', isLeaf: 0, sort: 0,
|
|
||||||
children: [
|
|
||||||
{ id: "cj1", label: "测试车间一", key: "CJ1", shortLabel: 'CJ1', parent: '生产部', isLeaf: 1, sort: 0 },
|
|
||||||
{ id: "cj2", label: "测试车间二", key: "CJ2", shortLabel: 'CJ2', parent: '生产部', isLeaf: 1, sort: 0 },
|
|
||||||
{ id: "cj3", label: "测试车间三", key: "CJ3", shortLabel: 'CJ3', parent: '生产部', isLeaf: 1, sort: 0 },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "ahb", label: "安环部", key: "AHB", shortLabel: 'AHB', parent: '演示工厂', isLeaf: 0, sort: 0,
|
|
||||||
children: [
|
|
||||||
{ id: "aqk", label: "安全科", key: "AQK", shortLabel: 'AQK', parent: '安环部', isLeaf: 1, sort: 0 },
|
|
||||||
{ id: "hbk", label: "环保科", key: "HBK", shortLabel: 'HBK', parent: '安环部', isLeaf: 1, sort: 0 },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "sbb", label: "设备部", key: "SBB", shortLabel: 'SBB', parent: '演示工厂', isLeaf: 0, sort: 0,
|
|
||||||
children: [
|
|
||||||
{ id: "sbk", label: "设备科", key: "SBK", shortLabel: 'SBK', parent: '设备部', isLeaf: 1, sort: 0 },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
id: "zlb", label: "质量部", key: "ZLB", shortLabel: 'ZLB', parent: '演示工厂', isLeaf: 0, sort: 0,
|
|
||||||
children: [
|
|
||||||
{ id: "zjk", label: "质检科", key: "ZJK", shortLabel: 'ZJK', parent: '质量部', isLeaf: 1, sort: 0 },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
]);
|
|
||||||
const defaultExpandedKeys = ref<string[]>(['YS'])
|
|
||||||
const defaultSelectedKeys = ref<string[]>(['YS'])
|
|
||||||
// 右键菜单相关
|
// 右键菜单相关
|
||||||
const menuVisible = ref(false)
|
const menuVisible = ref(false)
|
||||||
const menuX = ref(0)
|
const menuX = ref(0)
|
||||||
@@ -136,8 +113,16 @@ const menuOptions = [
|
|||||||
{ label: '添加子级', key: 'add' },
|
{ label: '添加子级', key: 'add' },
|
||||||
{ label: '删除', key: 'delete' },
|
{ label: '删除', key: 'delete' },
|
||||||
]
|
]
|
||||||
const handleSelectKeys = (keys: string[]) => {
|
// 展开节点
|
||||||
defaultSelectedKeys.value = keys
|
const handleExpandKeys = (keys: number[]) => {
|
||||||
|
expandedKeys.value = keys
|
||||||
|
}
|
||||||
|
// 选中节点
|
||||||
|
const handleSelectKeys = (keys: number[]) => {
|
||||||
|
selectedKeys.value = keys
|
||||||
|
currentOrg.value = keys[0];
|
||||||
|
pagination.value.page = 1;
|
||||||
|
getList()
|
||||||
}
|
}
|
||||||
// 处理右键点击树节点
|
// 处理右键点击树节点
|
||||||
const treeProps = ({ option }: { option: any }) => {
|
const treeProps = ({ option }: { option: any }) => {
|
||||||
@@ -165,13 +150,10 @@ const handleMenuSelect = (key: string) => {
|
|||||||
break
|
break
|
||||||
case 'add':
|
case 'add':
|
||||||
formNode.value = {
|
formNode.value = {
|
||||||
id: '',
|
|
||||||
label: '',
|
label: '',
|
||||||
key: '',
|
is_bottom: 0,
|
||||||
children: [],
|
org_code: '',
|
||||||
shortLabel: '',
|
parent_id: currentNode.value.id,
|
||||||
parent: currentNode.value.label,
|
|
||||||
isLeaf: 0,
|
|
||||||
sort: 0,
|
sort: 0,
|
||||||
}
|
}
|
||||||
treeModal.value = true
|
treeModal.value = true
|
||||||
@@ -184,11 +166,14 @@ const handleMenuSelect = (key: string) => {
|
|||||||
negativeText: '取消',
|
negativeText: '取消',
|
||||||
onPositiveClick: () => {
|
onPositiveClick: () => {
|
||||||
d.loading = true
|
d.loading = true
|
||||||
return new Promise((resolve) => {
|
return new Promise(async (resolve) => {
|
||||||
setTimeout(() => {
|
const {error} = await deleteOrgApi(currentNode.value.id);
|
||||||
d.loading = false
|
if(!error){
|
||||||
resolve(true)
|
window.$message?.success('删除成功')
|
||||||
}, 1000)
|
getOrgList()
|
||||||
|
}
|
||||||
|
d.loading = false
|
||||||
|
resolve(true)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
onNegativeClick: () => {
|
onNegativeClick: () => {
|
||||||
@@ -199,9 +184,19 @@ const handleMenuSelect = (key: string) => {
|
|||||||
}
|
}
|
||||||
closeMenu()
|
closeMenu()
|
||||||
}
|
}
|
||||||
const renderSwitcherIcon = () => {
|
// 自定义渲染展开图标
|
||||||
return h(Icon, { icon: 'formkit:right',class:'size-14px' }, {})
|
const renderSwitcherIcon = (node:any) => {
|
||||||
|
// 如果节点没有 children 或 children 为空数组,则不显示图标
|
||||||
|
if (!node.option.children || node.option.children.length === 0) {
|
||||||
|
return null; // 返回 null 表示不渲染任何图标
|
||||||
|
}
|
||||||
|
// 有子节点:返回 undefined 表示使用默认展开图标,也可自定义
|
||||||
|
return h(Icon,{
|
||||||
|
icon: node.option.expanded ? 'bi:caret-down-fill' : 'bi:caret-right-fill',
|
||||||
|
class: 'size-12px',
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
// 自定义渲染后缀图标
|
||||||
const renderSuffix = ({ option }: { option: any }) => {
|
const renderSuffix = ({ option }: { option: any }) => {
|
||||||
return h(
|
return h(
|
||||||
NButton,
|
NButton,
|
||||||
@@ -222,37 +217,10 @@ const renderSuffix = ({ option }: { option: any }) => {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
// 处理部门选择变化
|
|
||||||
const handleUpdateDept = (value: any) => {
|
|
||||||
currentUser.value.dept = value
|
|
||||||
}
|
|
||||||
|
|
||||||
// 获取列表
|
|
||||||
const getList = async () => {
|
|
||||||
loading.value = true;
|
|
||||||
setTimeout(() => {
|
|
||||||
loading.value = false;
|
|
||||||
list.value = [
|
|
||||||
{id:1,name:'姓名1',dept:['YS'],mobile:'13800000000',email:'user1@example.com',account:'user1',role:'普通用户',dataPermission:[],gender:1,status:1},
|
|
||||||
{id:2,name:'姓名2',dept:[],mobile:'13800000001',email:'user1@example.com',account:'user2',role:'普通用户',dataPermission:[],gender:1,status:1},
|
|
||||||
{id:2,name:'姓名2',dept:[],mobile:'13800000001',email:'user1@example.com',account:'user2',role:'普通用户',dataPermission:[],gender:1,status:2},
|
|
||||||
{id:3,name:'姓名3',dept:[],mobile:'13800000002',email:'user1@example.com',account:'user3',role:'普通用户',dataPermission:[],gender:1,status:1},
|
|
||||||
{id:4,name:'姓名4',dept:[],mobile:'13800000003',email:'user1@example.com',account:'user3',role:'普通用户',dataPermission:[],gender:1,status:2},
|
|
||||||
{id:4,name:'姓名4',dept:['YS'],mobile:'13800000003',email:'user1@example.com',account:'user4',role:'普通用户',dataPermission:[],gender:1,status:1},
|
|
||||||
{id:5,name:'姓名5',dept:[],mobile:'13800000004',email:'user1@example.com',account:'user5',role:'普通用户',dataPermission:[],gender:1,status:2},
|
|
||||||
{id:6,name:'姓名6',dept:['YS'],mobile:'13800000005',email:'user1@example.com',account:'user6',role:'普通用户',dataPermission:[],gender:1,status:1},
|
|
||||||
{id:7,name:'姓名7',dept:[],mobile:'13800000006',email:'user1@example.com',account:'user7',role:'普通用户',dataPermission:[],gender:1,status:1},
|
|
||||||
{id:8,name:'姓名8',dept:[],mobile:'13800000007',email:'user1@example.com',account:'user8',role:'普通用户',dataPermission:[],gender:1,status:2},
|
|
||||||
{id:9,name:'姓名9',dept:['YS'],mobile:'13800000008',email:'user1@example.com',account:'user9',role:'普通用户',dataPermission:[],gender:1,status:1},
|
|
||||||
{id:10,name:'姓名10',dept:[],mobile:'13800000009',email:'user1@example.com',account:'user10',role:'普通用户',dataPermission:[],gender:1,status:1},
|
|
||||||
{id:11,name:'姓名11',dept:['YS'],mobile:'13800000010',email:'user1@example.com',account:'user11',role:'普通用户',dataPermission:[],gender:1,status:1},
|
|
||||||
{id:12,name:'姓名12',dept:[],mobile:'13800000011',email:'user1@example.com',account:'user12',role:'普通用户',dataPermission:[],gender:1,status:1},
|
|
||||||
]
|
|
||||||
}, 1000)
|
|
||||||
}
|
|
||||||
// 处理数据权限选择变化
|
// 处理数据权限选择变化
|
||||||
const handleUpdateChecked = (keys: string[]) => {
|
const handleUpdateChecked = (keys: number[]) => {
|
||||||
currentUser.value.dataPermission = keys
|
currentUser.value.org_ids = keys
|
||||||
}
|
}
|
||||||
|
|
||||||
// 打开添加用户弹框
|
// 打开添加用户弹框
|
||||||
@@ -260,58 +228,51 @@ const addUser = () => {
|
|||||||
currentType.value = 'add';
|
currentType.value = 'add';
|
||||||
currentUser.value = {
|
currentUser.value = {
|
||||||
id: 0,
|
id: 0,
|
||||||
name: '',
|
real_name: '',
|
||||||
dept: [],
|
dept_ids: [],
|
||||||
mobile: '',
|
mobile: '',
|
||||||
email: '',
|
email: '',
|
||||||
account: '',
|
username: '',
|
||||||
role: null,
|
role_id: null,
|
||||||
dataPermission: [],
|
org_ids: [],
|
||||||
status: 1,
|
enabled: true,
|
||||||
gender: 1,
|
gender: 1,
|
||||||
};
|
};
|
||||||
userModal.value = true;
|
userModal.value = true;
|
||||||
}
|
}
|
||||||
// 重置密码
|
// 重置密码
|
||||||
const resetPassword = (row: any) => {
|
const resetPassword = async (row: any) => {
|
||||||
currentUser.value = {
|
const {error} = await resetPasswordApi(row.id);
|
||||||
id: row.id,
|
if(!error){
|
||||||
name: row.name,
|
window.$message?.success('重置密码成功')
|
||||||
dept: row.dept,
|
}
|
||||||
mobile: row.mobile,
|
|
||||||
email: row.email,
|
|
||||||
account: row.account,
|
|
||||||
role: row.role,
|
|
||||||
dataPermission: row.dataPermission,
|
|
||||||
status: row.status,
|
|
||||||
gender: row.gender,
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
// 编辑用户
|
// 编辑用户
|
||||||
const handleEdit = (row: any) => {
|
const handleEdit = (row: any) => {
|
||||||
currentType.value = 'edit';
|
currentType.value = 'edit';
|
||||||
currentUser.value = {
|
currentUser.value = {
|
||||||
id: row.id,
|
id: row.id,
|
||||||
name: row.name,
|
real_name: row.real_name,
|
||||||
dept: row.dept,
|
username: row.username,
|
||||||
|
dept_ids: row.user_departments,
|
||||||
mobile: row.mobile,
|
mobile: row.mobile,
|
||||||
email: row.email,
|
email: row.email,
|
||||||
account: row.account,
|
role_id: row.role_id,
|
||||||
role: row.role,
|
org_ids: row.org_ids,
|
||||||
dataPermission: row.dataPermission,
|
enabled: row.status,
|
||||||
status: row.status,
|
|
||||||
gender: row.gender,
|
gender: row.gender,
|
||||||
};
|
};
|
||||||
userModal.value = true;
|
userModal.value = true;
|
||||||
}
|
}
|
||||||
// 删除用户
|
// 删除用户
|
||||||
const handleDelete = (row: any) => {
|
const handleDelete = async (row: any) => {
|
||||||
loading.value = true;
|
loading.value = true;
|
||||||
setTimeout(() => {
|
const {error} = await deleteUserApi({id: row.id, type: 3});
|
||||||
loading.value = false;
|
if(!error){
|
||||||
list.value = list.value.filter((item: any) => item.id !== row.id);
|
|
||||||
window.$message?.success('删除成功')
|
window.$message?.success('删除成功')
|
||||||
}, 1000)
|
getList()
|
||||||
|
}
|
||||||
|
loading.value = false;
|
||||||
}
|
}
|
||||||
const columns = ref<DataTableColumns>([
|
const columns = ref<DataTableColumns>([
|
||||||
{
|
{
|
||||||
@@ -325,15 +286,18 @@ const columns = ref<DataTableColumns>([
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '姓名',
|
title: '姓名',
|
||||||
key: 'name',
|
key: 'real_name',
|
||||||
width: 8,
|
width: 8,
|
||||||
align: 'center',
|
align: 'center',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '部门',
|
title: '部门',
|
||||||
key: 'dept',
|
key: 'user_departments',
|
||||||
width: 10,
|
width: 10,
|
||||||
align: 'center',
|
align: 'center',
|
||||||
|
render: (_row: any) => {
|
||||||
|
return h('span', {}, { default: () => getOrgLabel(_row.user_departments) })
|
||||||
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '手机号码',
|
title: '手机号码',
|
||||||
@@ -343,13 +307,13 @@ const columns = ref<DataTableColumns>([
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '账号',
|
title: '账号',
|
||||||
key: 'account',
|
key: 'username',
|
||||||
width: 10,
|
width: 10,
|
||||||
align: 'center',
|
align: 'center',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: '账号角色',
|
title: '账号角色',
|
||||||
key: 'role',
|
key: 'role_name',
|
||||||
width: 10,
|
width: 10,
|
||||||
align: 'center',
|
align: 'center',
|
||||||
},
|
},
|
||||||
@@ -361,8 +325,8 @@ const columns = ref<DataTableColumns>([
|
|||||||
render: (row: any) => {
|
render: (row: any) => {
|
||||||
return h(NTag, {
|
return h(NTag, {
|
||||||
size: 'small',
|
size: 'small',
|
||||||
type: `${row.status===1?'success':'error'}`
|
type: `${row.status?'success':'error'}`
|
||||||
}, { default: () => `${row.status===1?'启用':'禁用'}` })
|
}, { default: () => `${row.status?'启用':'禁用'}` })
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -428,7 +392,7 @@ const columns = ref<DataTableColumns>([
|
|||||||
default: () => '重置密码' // 按钮文字
|
default: () => '重置密码' // 按钮文字
|
||||||
}
|
}
|
||||||
),
|
),
|
||||||
default: () => '是否重置该用户密码为)cVLO%M(c08)' // 弹出框的提示内容
|
default: () => `确定重置该用户密码?` // 弹出框的提示内容
|
||||||
}),
|
}),
|
||||||
]
|
]
|
||||||
})
|
})
|
||||||
@@ -437,54 +401,220 @@ const columns = ref<DataTableColumns>([
|
|||||||
])
|
])
|
||||||
// 导入用户
|
// 导入用户
|
||||||
const importUser = () => {
|
const importUser = () => {
|
||||||
|
const dom = document.createElement('input');
|
||||||
|
dom.type = 'file';
|
||||||
|
dom.style.display = 'none';
|
||||||
|
dom.click();
|
||||||
|
dom.onchange = async (e: any) => {
|
||||||
|
const formData = new FormData();
|
||||||
|
formData.append('file', e.target.files[0]);
|
||||||
|
const {error} = await importUserApi(formData);
|
||||||
|
if(!error){
|
||||||
|
window.$message?.success('导入成功')
|
||||||
|
pagination.value.page = 1;
|
||||||
|
getList()
|
||||||
|
}
|
||||||
|
};
|
||||||
}
|
}
|
||||||
// 刷新数据
|
// 下载模板
|
||||||
const refreshData = () => {
|
const downloadTemplate = async () => {
|
||||||
|
const {data,error} = await downloadTemplateApi();
|
||||||
|
if(!error){
|
||||||
|
// 确保 res 为 Blob 类型(若 API 返回 ArrayBuffer 或原始数据,需适配)
|
||||||
|
let blob;
|
||||||
|
if (data instanceof Blob) {
|
||||||
|
blob = data;
|
||||||
|
} else {
|
||||||
|
// 假设返回的是二进制数据(如 ArrayBuffer),指定 MIME 类型
|
||||||
|
blob = new Blob([data], { type: 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet' });
|
||||||
|
}
|
||||||
|
const url = window.URL.createObjectURL(blob);
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = url;
|
||||||
|
link.download = '用户导入模板.xlsx'; // 推荐使用 download 属性
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
// 清理:移除链接并释放 URL 对象
|
||||||
|
document.body.removeChild(link);
|
||||||
|
window.URL.revokeObjectURL(url);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// 重置数据权限
|
||||||
|
const refreshData = async () => {
|
||||||
|
restLoading.value = true;
|
||||||
|
const {error} = await resetDataApi();
|
||||||
|
if(!error){
|
||||||
|
window.$message?.success('重置成功')
|
||||||
|
}
|
||||||
|
restLoading.value = false;
|
||||||
}
|
}
|
||||||
// 保存用户
|
// 保存用户
|
||||||
const saveUser = async (e: MouseEvent) => {
|
const saveUser = (e: MouseEvent) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
formRef.value?.validate((errors: any) => {
|
formRef.value?.validate(async (errors: any) => {
|
||||||
if (!errors) {
|
if (!errors) {
|
||||||
btnLoading.value = true;
|
btnLoading.value = true;
|
||||||
setTimeout(() => {
|
// 新增
|
||||||
// 新增
|
if(currentType.value === 'add'){
|
||||||
if(currentType.value === 'add'){
|
const { id, ...rest } = currentUser.value;
|
||||||
list.value.push({id:list.value.length+1,name:currentUser.value?.name,dept:currentUser.value?.dept,mobile:currentUser.value?.mobile,email:currentUser.value?.email,account:currentUser.value?.account,role:currentUser.value?.role,dataPermission:currentUser.value?.dataPermission,status:currentUser.value?.status,gender:currentUser.value?.gender})
|
const {error} = await addUserApi(rest);
|
||||||
|
if(!error){
|
||||||
|
window.$message?.success('添加成功')
|
||||||
|
userModal.value = false;
|
||||||
|
getList();
|
||||||
}
|
}
|
||||||
// 编辑
|
}
|
||||||
if(currentType.value === 'edit'){
|
// 编辑
|
||||||
const target = list.value.find((item:any) => item.id === currentUser.value.id);
|
if(currentType.value === 'edit'){
|
||||||
if (target) {
|
const {error} = await editUserApi(currentUser.value.id, currentUser.value);
|
||||||
target.name = currentUser.value?.name;
|
if(!error){
|
||||||
}
|
window.$message?.success('编辑成功')
|
||||||
|
userModal.value = false;
|
||||||
|
getList();
|
||||||
}
|
}
|
||||||
window.$message?.success('保存成功')
|
}
|
||||||
btnLoading.value = false;
|
btnLoading.value = false;
|
||||||
userModal.value = false;
|
|
||||||
}, 1000)
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
// 保存节点
|
// 保存节点
|
||||||
const saveNode = async (e: MouseEvent) => {
|
const saveNode = (e: MouseEvent) => {
|
||||||
e.preventDefault()
|
e.preventDefault()
|
||||||
formNodeRef.value?.validate((errors: any) => {
|
formNodeRef.value?.validate(async (errors: any) => {
|
||||||
if (!errors) {
|
if (!errors) {
|
||||||
btnLoading.value = true;
|
btnLoading.value = true;
|
||||||
setTimeout(() => {
|
// 新增
|
||||||
window.$message?.success(`${currentNodeType.value === 'edit' ? '编辑' : '添加'}成功`)
|
if(currentNodeType.value === 'add'){
|
||||||
btnLoading.value = false;
|
const {error} = await addOrgApi(formNode.value);
|
||||||
treeModal.value = false;
|
if(!error){
|
||||||
}, 1000)
|
treeModal.value = false;
|
||||||
|
window.$message?.success('添加成功')
|
||||||
|
getOrgList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 编辑
|
||||||
|
if(currentNodeType.value === 'edit'){
|
||||||
|
const {error} = await editOrgApi(currentNode.value.id, formNode.value);
|
||||||
|
if(!error){
|
||||||
|
treeModal.value = false;
|
||||||
|
window.$message?.success('编辑成功')
|
||||||
|
getOrgList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
btnLoading.value = false;
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 转换为拼音
|
||||||
|
const convertToPinyin = async () => {
|
||||||
|
formNode.value.org_code = pinyin(formNode.value.label, {
|
||||||
|
toneType: 'none',
|
||||||
|
type: 'array',
|
||||||
|
pattern: 'first',
|
||||||
|
}).join('').toUpperCase();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取列表
|
||||||
|
const getList = async () => {
|
||||||
|
loading.value = true;
|
||||||
|
const {data,error} = await userListApi({
|
||||||
|
org_id: currentOrg.value,
|
||||||
|
keyword: keyword.value,
|
||||||
|
page: pagination.value.page,
|
||||||
|
limit: pagination.value.pageSize
|
||||||
|
})
|
||||||
|
if(!error){
|
||||||
|
list.value = data.list;
|
||||||
|
pagination.value.itemCount = data.total;
|
||||||
|
}
|
||||||
|
loading.value = false;
|
||||||
|
}
|
||||||
|
// 分页改变
|
||||||
|
const pageChange = (page: number) => {
|
||||||
|
pagination.value.page = page;
|
||||||
|
getList();
|
||||||
|
}
|
||||||
|
// 搜索用户
|
||||||
|
const searchUser = () => {
|
||||||
|
pagination.value.page = 1;
|
||||||
|
getList();
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取组织列表
|
||||||
|
const getOrgList = async (init: boolean = false) => {
|
||||||
|
treeLoading.value = true;
|
||||||
|
const {data,error} = await orgListApi();
|
||||||
|
if(!error){
|
||||||
|
orgTree.value = data;
|
||||||
|
if(init){
|
||||||
|
if(orgTree.value.length > 0){
|
||||||
|
expandedKeys.value = [orgTree.value[0].id];
|
||||||
|
selectedKeys.value = [orgTree.value[0].id];
|
||||||
|
currentOrg.value = orgTree.value[0].id;
|
||||||
|
getList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 删除当前选中节点时,需要更新选中节点
|
||||||
|
if(currentNodeType.value==='delete'){
|
||||||
|
if(currentNode.value.id===currentOrg.value){
|
||||||
|
expandedKeys.value = [orgTree.value[0].id];
|
||||||
|
selectedKeys.value = [orgTree.value[0].id];
|
||||||
|
currentOrg.value = orgTree.value[0].id;
|
||||||
|
}
|
||||||
|
getList();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
treeLoading.value = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 根据parent_id获取组织名称
|
||||||
|
const getParentOrg = (parentId: number): string => {
|
||||||
|
const findNode = (nodes: any[]): any => {
|
||||||
|
for (const node of nodes) {
|
||||||
|
if (node.id === parentId) return node;
|
||||||
|
if (node.children) {
|
||||||
|
const found = findNode(node.children);
|
||||||
|
if (found) return found;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
};
|
||||||
|
const node = findNode(orgTree.value);
|
||||||
|
return node?.label || '';
|
||||||
|
};
|
||||||
|
|
||||||
|
// 获取组织名称(新增/编辑)
|
||||||
|
const getOrg = () => {
|
||||||
|
if(currentNodeType.value==='edit'){
|
||||||
|
return getParentOrg(currentNode.value.parent_id)
|
||||||
|
}
|
||||||
|
return currentNode.value.label
|
||||||
|
}
|
||||||
|
|
||||||
|
// 获取角色列表
|
||||||
|
const getRoleList = async () => {
|
||||||
|
const {data,error} = await roleListApi();
|
||||||
|
if(!error){
|
||||||
|
const newArray = data.map((item:any) => ({
|
||||||
|
label: item.name,
|
||||||
|
value: item.id
|
||||||
|
}));
|
||||||
|
roleList.value = newArray;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 通过组织id匹配label(多个)
|
||||||
|
const getOrgLabel = (orgIds: number[]) => {
|
||||||
|
const labels = orgIds.map((id: number) => getParentOrg(id));
|
||||||
|
return labels.join(';');
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
onMounted(() => {
|
onMounted(() => {
|
||||||
getList()
|
getRoleList();
|
||||||
|
getOrgList(true);
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
@@ -504,52 +634,67 @@ onMounted(() => {
|
|||||||
<NButton type="primary" size="small" @click="addUser">
|
<NButton type="primary" size="small" @click="addUser">
|
||||||
<Icon icon="ic:round-plus" class="size-16px" />添加成员
|
<Icon icon="ic:round-plus" class="size-16px" />添加成员
|
||||||
</NButton>
|
</NButton>
|
||||||
<NButton type="warning" size="small" @click="importUser">
|
<NPopover trigger="hover">
|
||||||
<Icon icon="material-symbols:download" class="size-16px" />导入用户
|
<template #trigger>
|
||||||
</NButton>
|
<NButton type="warning" size="small" @click="importUser">
|
||||||
<NButton text @click="refreshData">
|
<Icon icon="material-symbols:download" class="size-16px" />导入用户
|
||||||
<Icon icon="tdesign:refresh" class="size-14px" />
|
</NButton>
|
||||||
</NButton>
|
</template>
|
||||||
|
<NButton type="primary" ghost size="small" @click="downloadTemplate">下载模板</NButton>
|
||||||
|
</NPopover>
|
||||||
|
<NPopover trigger="hover">
|
||||||
|
<template #trigger>
|
||||||
|
<NButton text @click="refreshData">
|
||||||
|
<Icon icon="tdesign:refresh" class="size-14px" :class="restLoading? 'animate-spin':''" />
|
||||||
|
</NButton>
|
||||||
|
</template>
|
||||||
|
<span>重置数据权限</span>
|
||||||
|
</NPopover>
|
||||||
</NGi>
|
</NGi>
|
||||||
</NGrid>
|
</NGrid>
|
||||||
</div>
|
</div>
|
||||||
<div class="flex gap-3">
|
<div class="flex gap-3">
|
||||||
<div class="border p-3 w-300px" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
|
<NSpin :show="treeLoading" size="small" :stroke-width="18">
|
||||||
<NInput v-model:value="keyword" placeholder="请输入人员">
|
<div class="border p-3 w-300px" :style="{ borderRadius: themeStore.themeRadius + 'px' }">
|
||||||
<template #suffix>
|
<NInput v-model:value="keyword" placeholder="请输入人员" clearable @keyup.enter="searchUser">
|
||||||
<Icon icon="akar-icons:search" class="size-14px text-slate-400 cursor-pointer" />
|
<template #suffix>
|
||||||
</template>
|
<Icon icon="akar-icons:search" class="size-14px text-slate-400 cursor-pointer" @click="searchUser" />
|
||||||
</NInput>
|
</template>
|
||||||
<!-- 树 -->
|
</NInput>
|
||||||
<div class="mt-2 scroll">
|
<!-- 树 -->
|
||||||
<NTree
|
<div class="mt-2 scroll">
|
||||||
block-line
|
<NTree
|
||||||
:data="orgTree"
|
block-line
|
||||||
:default-expanded-keys="defaultExpandedKeys"
|
:data="orgTree"
|
||||||
:default-selected-keys="defaultSelectedKeys"
|
label-field="label"
|
||||||
:render-switcher-icon="renderSwitcherIcon"
|
key-field="id"
|
||||||
:render-suffix="renderSuffix"
|
:expanded-keys="expandedKeys"
|
||||||
selectable
|
:selected-keys="selectedKeys"
|
||||||
:cancelable="false"
|
:render-switcher-icon="renderSwitcherIcon"
|
||||||
:on-update:selected-keys="handleSelectKeys"
|
:render-suffix="renderSuffix"
|
||||||
:node-props="treeProps"
|
selectable
|
||||||
/>
|
:cancelable="false"
|
||||||
<NDropdown
|
:on-update:expanded-keys="handleExpandKeys"
|
||||||
:show="menuVisible"
|
:on-update:selected-keys="handleSelectKeys"
|
||||||
:options="menuOptions"
|
:node-props="treeProps"
|
||||||
size="small"
|
/>
|
||||||
:x="menuX"
|
<NDropdown
|
||||||
:y="menuY"
|
:show="menuVisible"
|
||||||
placement="bottom-start"
|
:options="menuOptions"
|
||||||
trigger="manual"
|
size="small"
|
||||||
@clickoutside="closeMenu"
|
:x="menuX"
|
||||||
@select="handleMenuSelect"
|
:y="menuY"
|
||||||
/>
|
placement="bottom-start"
|
||||||
|
trigger="manual"
|
||||||
|
@clickoutside="closeMenu"
|
||||||
|
@select="handleMenuSelect"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<p class="mt-2 border-t pt-2 text-[11px] leading-4 text-slate-400">
|
||||||
|
点击层级查看该层级全员名录;右键(或点击 ···)可编辑、添加子级、删除。
|
||||||
|
</p>
|
||||||
</div>
|
</div>
|
||||||
<p class="mt-2 border-t pt-2 text-[11px] leading-4 text-slate-400">
|
</NSpin>
|
||||||
点击层级查看该层级全员名录;右键(或点击 ···)可编辑、添加子级、删除。
|
|
||||||
</p>
|
|
||||||
</div>
|
|
||||||
<div class="flex-1">
|
<div class="flex-1">
|
||||||
<NDataTable
|
<NDataTable
|
||||||
size="small"
|
size="small"
|
||||||
@@ -557,27 +702,41 @@ onMounted(() => {
|
|||||||
:single-line="false"
|
:single-line="false"
|
||||||
:columns="columns"
|
:columns="columns"
|
||||||
:data="list"
|
:data="list"
|
||||||
:pagination="pagination"
|
:pagination="false"
|
||||||
max-height="calc(100vh - 285px)"
|
max-height="calc(100vh - 285px)"
|
||||||
/>
|
/>
|
||||||
|
<div class="flex justify-end mt-3">
|
||||||
|
<NPagination
|
||||||
|
v-model:page="pagination.page"
|
||||||
|
:page-size="pagination.pageSize"
|
||||||
|
:item-count="pagination.itemCount"
|
||||||
|
:on-update:page="pageChange"
|
||||||
|
>
|
||||||
|
<template #prefix="{ itemCount }">
|
||||||
|
共 {{ itemCount }} 条
|
||||||
|
</template>
|
||||||
|
</NPagination>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<!-- 新增或编辑抽屉 -->
|
<!-- 新增或编辑抽屉 -->
|
||||||
<NDrawer v-model:show="userModal" :width="400" placement="right">
|
<NDrawer v-model:show="userModal" :width="400" placement="right">
|
||||||
<NDrawerContent :title="currentType === 'add' ? '新增成员' : '编辑成员'" closable>
|
<NDrawerContent :title="currentType === 'add' ? '新增成员' : '编辑成员'" closable>
|
||||||
<NForm ref="formRef" :model="currentUser" :rules="rules" label-placement="left" label-width="auto">
|
<NForm ref="formRef" :model="currentUser" :rules="rules" label-placement="left" label-width="auto">
|
||||||
<NFormItem label="姓名" path="name">
|
<NFormItem label="姓名" path="real_name">
|
||||||
<NInput
|
<NInput
|
||||||
v-model:value="currentUser.name"
|
v-model:value="currentUser.real_name"
|
||||||
placeholder="请输入姓名"
|
placeholder="请输入姓名"
|
||||||
/>
|
/>
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
<NFormItem label="所属部门" path="dept">
|
<NFormItem label="所属部门" path="dept_ids">
|
||||||
<NTreeSelect
|
<NTreeSelect
|
||||||
:value="currentUser.dept"
|
v-model:value="currentUser.dept_ids"
|
||||||
multiple
|
multiple
|
||||||
|
label-field="label"
|
||||||
|
key-field="id"
|
||||||
:options="orgTree"
|
:options="orgTree"
|
||||||
@update:value="handleUpdateDept"
|
:render-switcher-icon="renderSwitcherIcon"
|
||||||
/>
|
/>
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
<NFormItem label="性别">
|
<NFormItem label="性别">
|
||||||
@@ -586,9 +745,9 @@ onMounted(() => {
|
|||||||
<NRadio :value="2">女</NRadio>
|
<NRadio :value="2">女</NRadio>
|
||||||
</NRadioGroup>
|
</NRadioGroup>
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
<NFormItem label="账号" path="account">
|
<NFormItem label="账号" path="username">
|
||||||
<NInput
|
<NInput
|
||||||
v-model:value="currentUser.account"
|
v-model:value="currentUser.username"
|
||||||
placeholder="请输入账号"
|
placeholder="请输入账号"
|
||||||
/>
|
/>
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
@@ -604,27 +763,30 @@ onMounted(() => {
|
|||||||
placeholder="请输入电子邮箱"
|
placeholder="请输入电子邮箱"
|
||||||
/>
|
/>
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
<NFormItem label="账号角色" path="role">
|
<NFormItem label="账号角色" path="role_id">
|
||||||
<NSelect
|
<NSelect
|
||||||
v-model:value="currentUser.role"
|
v-model:value="currentUser.role_id"
|
||||||
:options="roleOptions"
|
:options="roleList"
|
||||||
placeholder="请选择账号角色"
|
placeholder="请选择账号角色"
|
||||||
/>
|
/>
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
<NFormItem label="数据权限" path="dataPermission">
|
<NFormItem label="数据权限" path="org_ids">
|
||||||
<NTree
|
<NTree
|
||||||
:data="orgTree"
|
:data="orgTree"
|
||||||
block-line
|
block-line
|
||||||
checkable
|
checkable
|
||||||
cascade
|
cascade
|
||||||
:checked-keys="currentUser.dataPermission"
|
label-field="label"
|
||||||
|
key-field="id"
|
||||||
|
:render-switcher-icon="renderSwitcherIcon"
|
||||||
|
:checked-keys="currentUser.org_ids"
|
||||||
:on-update:checked-keys="handleUpdateChecked"
|
:on-update:checked-keys="handleUpdateChecked"
|
||||||
/>
|
/>
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
<NFormItem label="状态">
|
<NFormItem label="状态">
|
||||||
<NRadioGroup v-model:value="currentUser.status" name="status">
|
<NRadioGroup v-model:value="currentUser.enabled" name="enabled">
|
||||||
<NRadio :value="1">启用</NRadio>
|
<NRadio :value="true">启用</NRadio>
|
||||||
<NRadio :value="2">禁用</NRadio>
|
<NRadio :value="false">禁用</NRadio>
|
||||||
</NRadioGroup>
|
</NRadioGroup>
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
</NForm>
|
</NForm>
|
||||||
@@ -653,11 +815,12 @@ onMounted(() => {
|
|||||||
maxlength="10"
|
maxlength="10"
|
||||||
show-count
|
show-count
|
||||||
placeholder="请输入层级名称"
|
placeholder="请输入层级名称"
|
||||||
|
:on-input="convertToPinyin"
|
||||||
/>
|
/>
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
<NFormItem label="层级简称">
|
<NFormItem label="层级简称">
|
||||||
<NInput
|
<NInput
|
||||||
v-model:value="formNode.shortLabel"
|
v-model:value="formNode.org_code"
|
||||||
maxlength="10"
|
maxlength="10"
|
||||||
show-count
|
show-count
|
||||||
placeholder="请输入层级简称"
|
placeholder="请输入层级简称"
|
||||||
@@ -665,13 +828,13 @@ onMounted(() => {
|
|||||||
</NFormItem>
|
</NFormItem>
|
||||||
<NFormItem label="所属层级">
|
<NFormItem label="所属层级">
|
||||||
<NInput
|
<NInput
|
||||||
v-model:value="formNode.parent"
|
:value="getOrg()"
|
||||||
:disabled="true"
|
:disabled="true"
|
||||||
placeholder=""
|
placeholder=""
|
||||||
/>
|
/>
|
||||||
</NFormItem>
|
</NFormItem>
|
||||||
<NFormItem label="末端层级">
|
<NFormItem label="末端层级">
|
||||||
<NRadioGroup v-model:value="formNode.isLeaf" name="isLeaf">
|
<NRadioGroup v-model:value="formNode.is_bottom" name="is_bottom">
|
||||||
<NRadio :value="1">是</NRadio>
|
<NRadio :value="1">是</NRadio>
|
||||||
<NRadio :value="0">否</NRadio>
|
<NRadio :value="0">否</NRadio>
|
||||||
</NRadioGroup>
|
</NRadioGroup>
|
||||||
|
|||||||
Reference in New Issue
Block a user