backend/src/views/basicSettings/tenants/components/TenantUsersTab.vue

245 lines
7.0 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<template>
<div class="tenant-users-tab">
<div class="section-header">
<div class="section-title">用户列表</div>
<el-button type="primary" size="small" :disabled="!tid" @click="handleAddUser">
<el-icon><Plus /></el-icon>
添加用户
</el-button>
</div>
<el-form :inline="true" class="user-search-form" @submit.prevent>
<el-form-item label="关键词">
<el-input
v-model="userSearchKeyword"
clearable
placeholder="姓名 / 手机 / 邮箱 / 账号"
style="width: 260px"
:disabled="!tid"
@keyup.enter="handleSearchUsers"
/>
</el-form-item>
<el-form-item>
<el-button type="primary" :disabled="!tid" @click="handleSearchUsers">查询</el-button>
<el-button :disabled="!tid" @click="resetUserSearch">重置</el-button>
</el-form-item>
</el-form>
<el-table :data="tenantUsers" v-loading="usersLoading" border style="width: 100%">
<el-table-column prop="account" label="用户名" min-width="140" align="center" />
<el-table-column prop="name" label="姓名" min-width="120" align="center" />
<el-table-column prop="phone" label="手机号" min-width="140" align="center" />
<el-table-column prop="email" label="邮箱" min-width="180" align="center" />
<el-table-column prop="status" label="状态" width="90" align="center">
<template #default="{ row }">
<el-tag :type="Number(row.status) === 1 ? 'success' : 'danger'">
{{ Number(row.status) === 1 ? "启用" : "禁用" }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" width="140" align="center" fixed="right">
<template #default="{ row }">
<el-button text type="primary" @click="openPasswordDialog(row)">
修改密码
</el-button>
</template>
</el-table-column>
</el-table>
<AddUser ref="addUserRef" @success="refreshTenantUsers" />
<el-dialog v-model="passwordDialogVisible" title="修改密码" width="420px" destroy-on-close>
<el-form ref="passwordFormRef" :model="passwordForm" :rules="passwordRules" label-width="90px">
<el-form-item label="新密码" prop="password">
<el-input v-model="passwordForm.password" type="password" show-password placeholder="请输入新密码" />
</el-form-item>
<el-form-item label="确认密码" prop="password2">
<el-input v-model="passwordForm.password2" type="password" show-password placeholder="请再次输入新密码" />
</el-form-item>
</el-form>
<template #footer>
<el-button @click="passwordDialogVisible = false">取消</el-button>
<el-button type="primary" :loading="passwordSubmitting" @click="submitPassword">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup lang="ts">
import { ref, reactive, watch } from "vue";
import { ElMessage } from "element-plus";
import { Plus } from "@element-plus/icons-vue";
import { getTenantUserList, editTenantUser } from "@/api/tenantUser";
import AddUser from "./adduser.vue";
const props = defineProps<{
/** 当前租户 ID为空时不请求 */
tid: number | null;
}>();
const tenantUsers = ref<any[]>([]);
const usersLoading = ref(false);
const userSearchKeyword = ref("");
const addUserRef = ref<{ open: (tenantId: number) => void } | null>(null);
const passwordDialogVisible = ref(false);
const passwordSubmitting = ref(false);
const currentTenantUserId = ref<number | null>(null);
const passwordFormRef = ref();
const passwordForm = reactive({
password: "",
password2: "",
});
const passwordRules = {
password: [
{ required: true, message: "请输入新密码", trigger: "blur" },
{ min: 5, message: "密码至少 5 位", trigger: "blur" },
],
password2: [
{ required: true, message: "请再次输入新密码", trigger: "blur" },
{
validator: (_rule: any, value: string, callback: any) => {
if (!value) return callback(new Error("请再次输入新密码"));
if (value !== passwordForm.password) return callback(new Error("两次密码不一致"));
callback();
},
trigger: "blur",
},
],
};
const buildTenantUserQuery = (tid: number) => {
const params: Record<string, string | number> = { tid };
const kw = userSearchKeyword.value.trim();
if (kw) params.keyword = kw;
return params;
};
const refreshTenantUsers = async () => {
const id = props.tid;
if (id == null) return;
usersLoading.value = true;
try {
const usersRes = await getTenantUserList(buildTenantUserQuery(id));
if (usersRes?.code === 200) {
tenantUsers.value = usersRes?.data?.list || usersRes?.data || [];
} else {
tenantUsers.value = [];
}
} catch {
tenantUsers.value = [];
} finally {
usersLoading.value = false;
}
};
const loadUsersForTid = async (tid: number) => {
usersLoading.value = true;
try {
const usersRes = await getTenantUserList({ tid });
if (usersRes?.code === 200) {
tenantUsers.value = usersRes?.data?.list || usersRes?.data || [];
} else {
tenantUsers.value = [];
}
} catch {
tenantUsers.value = [];
} finally {
usersLoading.value = false;
}
};
watch(
() => props.tid,
(id) => {
userSearchKeyword.value = "";
if (id == null) {
tenantUsers.value = [];
return;
}
loadUsersForTid(id);
},
{ immediate: true }
);
const handleSearchUsers = () => {
refreshTenantUsers();
};
const resetUserSearch = () => {
userSearchKeyword.value = "";
refreshTenantUsers();
};
const handleAddUser = () => {
if (props.tid != null) {
addUserRef.value?.open(props.tid);
}
};
const openPasswordDialog = (row: any) => {
currentTenantUserId.value = Number(row?.id || 0) || null;
passwordForm.password = "";
passwordForm.password2 = "";
passwordDialogVisible.value = true;
};
const submitPassword = async () => {
if (!passwordFormRef.value || !currentTenantUserId.value) return;
try {
await passwordFormRef.value.validate();
} catch {
return;
}
passwordSubmitting.value = true;
try {
const res = await editTenantUser(currentTenantUserId.value, { password: passwordForm.password });
if (res.code === 200) {
ElMessage.success("密码修改成功");
passwordDialogVisible.value = false;
} else {
ElMessage.error(res.msg || "密码修改失败");
}
} finally {
passwordSubmitting.value = false;
}
};
defineExpose({
refreshTenantUsers,
});
</script>
<style scoped>
.tenant-users-tab {
min-width: 0;
}
.section-header {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
margin-bottom: 12px;
}
.section-title {
font-size: 15px;
font-weight: 600;
color: #303133;
}
.user-search-form {
margin-bottom: 8px;
padding: 8px 12px;
background: var(--el-fill-color-light);
border-radius: 4px;
}
.user-search-form :deep(.el-form-item) {
margin-bottom: 8px;
margin-top: 0;
}
</style>