批量优化

This commit is contained in:
hero920103 committed 2026-10-08 15:30:37 +08:00
1 parent 438145cae7
commit c925b198d1
50 files changed
+5716 -291

No files matched your search

@@ -0,0 +1,353 @@
<template>
<div class="container-box">
<div class="header-bar">
<h2>加入申请</h2>
<div class="header-actions">
<el-button @click="refresh">
<el-icon><Refresh /></el-icon>
刷新
</el-button>
</div>
</div>
<el-divider></el-divider>
<!-- 搜索筛选 -->
<div class="filter-bar">
<el-form :inline="true" :model="filterForm" class="filter-form">
<el-form-item label="状态">
<el-select
v-model="filterForm.status"
placeholder="请选择状态"
clearable
style="width: 140px"
@change="handleSearch"
>
<el-option label="待审核" value="0" />
<el-option label="已通过" value="1" />
<el-option label="已驳回" value="2" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="handleSearch">
<el-icon><Search /></el-icon>
搜索
</el-button>
<el-button @click="handleReset">重置</el-button>
</el-form-item>
</el-form>
</div>
<!-- 申请列表 -->
<el-table :data="applies" style="width: 100%" v-loading="loading">
<el-table-column prop="id" label="ID" width="70" align="center" />
<el-table-column label="申请人" min-width="140" align="center">
<template #default="scope">
<div>{{ scope.row.identity_nickname || "-" }}</div>
<div class="sub-text">{{ scope.row.identity_mobile || "" }}</div>
</template>
</el-table-column>
<el-table-column
prop="tenant_name"
label="申请加入企业"
min-width="160"
align="center"
/>
<el-table-column label="联系方式" min-width="150" align="center">
<template #default="scope">
<div>{{ scope.row.contact_person || "-" }}</div>
<div class="sub-text">{{ scope.row.contact_phone || "" }}</div>
</template>
</el-table-column>
<el-table-column prop="status" label="状态" width="100" align="center">
<template #default="scope">
<el-tag :type="statusTagType(scope.row.status)" size="small">
{{ statusText(scope.row.status) }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="驳回原因" min-width="140" align="center">
<template #default="scope">
<span v-if="scope.row.status === 2">{{
scope.row.reject_reason || "-"
}}</span>
<span v-else>-</span>
</template>
</el-table-column>
<el-table-column
prop="reviewer_name"
label="审核人"
width="110"
align="center"
>
<template #default="scope">
{{ scope.row.reviewer_name || "-" }}
</template>
</el-table-column>
<el-table-column label="审核时间" width="170" align="center">
<template #default="{ row }">
{{ row.review_time ? formatDateTime(row.review_time) : "-" }}
</template>
</el-table-column>
<el-table-column label="申请时间" width="170" align="center">
<template #default="{ row }">
{{ formatDateTime(row.create_time) }}
</template>
</el-table-column>
<el-table-column
label="操作"
width="150"
align="center"
fixed="right"
>
<template #default="scope">
<template v-if="scope.row.status === 0">
<el-button
size="small"
type="success"
link
@click="handleApprove(scope.row)"
>
通过
</el-button>
<el-button
size="small"
type="danger"
link
@click="handleReject(scope.row)"
>
驳回
</el-button>
</template>
<span v-else class="sub-text">已处理</span>
</template>
</el-table-column>
</el-table>
<!-- 分页 -->
<div class="pagination-bar">
<el-pagination
v-model:current-page="page"
v-model:page-size="pageSize"
:total="total"
:page-sizes="[10, 20, 50, 100]"
layout="total, sizes, prev, pager, next, jumper"
@size-change="handleSizeChange"
@current-change="handlePageChange"
/>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, onMounted } from "vue";
import { ElMessage, ElMessageBox } from "element-plus";
import { Refresh, Search } from "@element-plus/icons-vue";
import {
getTenantApplies,
approveTenantApply,
rejectTenantApply,
} from "@/api/tenantApply";
import { formatDateTime } from "@/utils/datetime";
interface TenantApply {
id: number;
identity_id: number;
tenant_name: string;
identity_nickname: string;
identity_mobile: string;
contact_person: string;
contact_phone: string;
contact_email: string;
status: number;
reject_reason: string;
reviewer_name: string;
review_time: string | null;
create_time: string;
}
const page = ref(1);
const pageSize = ref(20);
const total = ref(0);
const loading = ref(false);
const applies = ref<TenantApply[]>([]);
const filterForm = ref({
status: "",
});
const statusText = (status: number): string => {
const map: Record<number, string> = {
0: "待审核",
1: "已通过",
2: "已驳回",
};
return map[status] || "未知";
};
const statusTagType = (status: number): string => {
const map: Record<number, string> = {
0: "warning",
1: "success",
2: "danger",
};
return map[status] || "info";
};
// 获取申请列表
const fetchApplies = async () => {
loading.value = true;
try {
const params: any = {
page: page.value,
pageSize: pageSize.value,
};
if (filterForm.value.status !== "") {
params.status = filterForm.value.status;
}
const res = await getTenantApplies(params);
if (res.code === 200) {
applies.value = res.data.list || [];
total.value = res.data.total || 0;
} else {
ElMessage.error(res.msg || "获取申请列表失败");
}
} catch (error: any) {
ElMessage.error(error?.message || "获取申请列表失败");
} finally {
loading.value = false;
}
};
const refresh = () => {
fetchApplies();
};
const handleSearch = () => {
page.value = 1;
fetchApplies();
};
const handleReset = () => {
filterForm.value = { status: "" };
page.value = 1;
fetchApplies();
};
const handleSizeChange = () => {
page.value = 1;
fetchApplies();
};
const handlePageChange = () => {
fetchApplies();
};
// 通过申请:申请人加入本企业
const handleApprove = async (row: TenantApply) => {
try {
await ElMessageBox.confirm(
`确认通过「${row.identity_nickname || row.identity_mobile}」加入本企业的申请?通过后将默认分配「租户用户」角色。`,
"通过申请",
{ confirmButtonText: "确定", cancelButtonText: "取消", type: "warning" },
);
} catch {
return;
}
try {
const res = await approveTenantApply(row.id);
if (res.code === 200) {
ElMessage.success(res.msg || "已通过");
fetchApplies();
} else {
ElMessage.error(res.msg || "操作失败");
}
} catch (error: any) {
ElMessage.error(error?.message || "操作失败");
}
};
// 驳回申请(需填写原因)
const handleReject = async (row: TenantApply) => {
let reason = "";
try {
const { value } = await ElMessageBox.prompt(
"请输入驳回原因",
`驳回「${row.identity_nickname || row.identity_mobile}」的申请`,
{
confirmButtonText: "确定",
cancelButtonText: "取消",
inputPlaceholder: "驳回原因(可选)",
inputValidator: (v: string) => {
if (v && v.length > 200) return "原因不能超过200字";
return true;
},
},
);
reason = value || "";
} catch {
return;
}
try {
const res = await rejectTenantApply(row.id, { reason });
if (res.code === 200) {
ElMessage.success(res.msg || "已驳回");
fetchApplies();
} else {
ElMessage.error(res.msg || "操作失败");
}
} catch (error: any) {
ElMessage.error(error?.message || "操作失败");
}
};
onMounted(() => {
fetchApplies();
});
</script>
<style lang="less" scoped>
.container-box {
padding: 20px;
}
.header-bar {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 20px;
h2 {
margin: 0;
font-size: 20px;
font-weight: 600;
}
.header-actions {
display: flex;
gap: 10px;
}
}
.filter-bar {
margin-bottom: 20px;
padding: 20px;
border-radius: 4px;
.filter-form {
margin: 0;
}
}
.pagination-bar {
margin-top: 20px;
display: flex;
justify-content: flex-end;
}
.sub-text {
color: #8a94ad;
font-size: 12px;
line-height: 1.4;
}
</style>
@@ -161,7 +161,8 @@ const handleSubmit = async () => {
}
// 调用接口修改密码
const res = await changePassword(form.value.id, passwordForm.value);
// 第三个参数 true:userId 是用户管理列表行主键,后端按主键精确匹配
const res = await changePassword(form.value.id, passwordForm.value, true);
if (res.code === 200 || res.msg === '修改成功') {
ElMessage.success("密码修改成功");
@@ -109,7 +109,7 @@
<script setup lang="ts">
import { ref, computed, watch } from "vue";
import { ElMessage } from "element-plus";
import { ElMessage, ElMessageBox } from "element-plus";
import { addUser, editUser, getUserInfo } from "@/api/user";
import { getAllRoles } from "@/api/role";
@@ -161,11 +161,25 @@ const fetchRoles = async () => {
try {
const res = await getAllRoles();
roles.value = (res.data || []).filter((r: RoleOption) => r.status !== 0);
applyDefaultRole();
} catch (e) {
roles.value = [];
}
};
// 新增用户时,角色默认选中全局「租户用户」角色(tenant_id=0,平台统一维护)
const applyDefaultRole = () => {
if (!isAdd.value || form.value.group_id) {
return;
}
const def = roles.value.find(
(r) => r.name === "租户用户" && Number(r.tenant_id || 0) === 0
);
if (def) {
form.value.group_id = def.id;
}
};
// 平台规定的默认角色(全局角色,tenant_id=0,平台端统一维护)
const globalRoles = computed(() =>
roles.value.filter((r) => Number(r.tenant_id || 0) === 0)
@@ -273,7 +287,9 @@ const loadUserData = async (user: any) => {
throw new Error("未提供有效的用户 ID");
}
const res = await getUserInfo(userId);
// 列表行的 id 是 yz_system_tenant_user 主键,必须 by=id 精确匹配,
// 否则后端会先当 identity_id 换算老 uid,命中别人的记录
const res = await getUserInfo(userId, { by: "id" });
const data = res.data || res;
@@ -362,8 +378,53 @@ const handleSubmit = async () => {
group_id: form.value.group_id,
};
await addUser(submitData);
ElMessage.success("添加成功");
const res: any = await addUser(submitData);
// 统一认证:账号在别的企业已存在(后端不覆盖),需验证原身份密码后绑定
if (res?.code === 409 && res?.data?.need_verify) {
const tenantNames = (res.data.tenants || [])
.map((t: any) => t.tenant_name)
.filter(Boolean)
.join("、");
const tip = tenantNames
? `该账号已在「${tenantNames}」创建过账号。请输入该账号的登录密码验证身份,验证通过后直接绑定本企业(不会修改原密码)`
: "该账号已在其他企业存在。请输入该账号的登录密码验证身份,验证通过后直接绑定本企业(不会修改原密码)";
// 密码错误时重新弹窗让用户重试,取消则停留在表单
for (;;) {
let verifyPassword = "";
try {
const { value } = await ElMessageBox.prompt(tip, "身份验证", {
confirmButtonText: "验证并绑定",
cancelButtonText: "取消",
inputType: "password",
inputPlaceholder: "请输入该账号的登录密码",
inputValidator: (v: string) =>
v && v.trim() ? true : "密码不能为空",
});
verifyPassword = value;
} catch {
return; // 用户取消验证
}
// 带 identity_id + verify_password 重新提交,后端验证通过只建绑定
const retry: any = await addUser({
...submitData,
identity_id: res.data.identity_id,
verify_password: verifyPassword,
});
if (retry?.code === 200) {
ElMessage.success("身份验证通过,已绑定本企业");
break;
}
ElMessage.error(retry?.msg || "验证失败,请重新输入密码");
}
} else if (res?.code === 200) {
ElMessage.success("添加成功");
} else {
ElMessage.error(res?.msg || "添加失败");
}
} else {
// 编辑用户
if (!form.value.id || form.value.id === 0) {
@@ -388,7 +449,7 @@ const handleSubmit = async () => {
submitData.password = form.value.password;
}
await editUser(form.value.id, submitData);
await editUser(form.value.id, submitData, { by: "id" });
ElMessage.success("更新成功");
}
@@ -423,6 +484,7 @@ defineExpose({
if (formRef.value) {
formRef.value.clearValidate();
}
applyDefaultRole();
},
openEdit: (user: any) => {
isAdd.value = false;
@@ -451,7 +513,9 @@ defineExpose({
confirmPassword: "",
email: "",
status: 1,
group_id: undefined,
};
applyDefaultRole();
}
visible.value = true;
// 清除表单验证
@@ -306,7 +306,7 @@ const handleDelete = async (user: User) => {
type: "warning",
}).then(async () => {
try {
await deleteUser(user.id);
await deleteUser(user.id, { by: "id" });
ElMessage.success("删除成功");
fetchUsers();
fetchQuotaInfo();