feat(aihr): 重构运营后台与租户知识治理

- 重构运营总览、侧栏和内容运营工作台\n- 增加大喇叭、成果审核与运营统计链路\n- 补齐租户、知识空间、分类和知识维护闭环\n- 加固标签页租户上下文、停用租户写保护与迁移预检
This commit is contained in:
2026-07-23 01:24:22 +08:00
parent 333c365c6f
commit 163b08ecfe
94 changed files with 8610 additions and 1114 deletions
+75 -2
View File
@@ -1,5 +1,12 @@
<template>
<div class="p-2">
<div class="tenant-governance p-2">
<section class="tenant-governance__head">
<div>
<h1>租户管理</h1>
<p>维护租户主体,并按“租户 → 知识空间 → 分类 → 知识”进入内容治理。</p>
</div>
<div class="tenant-governance__hint">知识空间、分类和知识仍按当前切换租户隔离管理。</div>
</section>
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
@@ -72,8 +79,18 @@
<el-switch v-model="scope.row.status" active-value="0" inactive-value="1" @change="handleStatusChange(scope.row)"></el-switch>
</template>
</el-table-column>
<el-table-column width="150" label="操作" align="center" fixed="right" class-name="small-padding fixed-width">
<el-table-column width="230" label="操作" align="center" fixed="right" class-name="small-padding fixed-width">
<template #default="scope">
<el-button
v-hasPermi="['system:tenant:edit']"
link
type="primary"
:loading="tenantContextSwitching"
:disabled="tenantContextSwitching || scope.row.status !== '0'"
:title="scope.row.status === '0' ? '进入此租户的知识维护' : '停用租户不能维护知识'"
@click="manageKnowledge(scope.row)"
>知识维护</el-button
>
<el-tooltip content="修改" placement="top">
<el-button v-hasPermi="['system:tenant:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)"></el-button>
</el-tooltip>
@@ -160,6 +177,7 @@ import {
} from '@/api/system/tenant';
import { selectTenantPackage } from '@/api/system/tenantPackage';
import { useUserStore } from '@/store/modules/user';
import { switchDynamicTenantContext, tenantContextSwitching } from '@/utils/tenant-context';
import { TenantForm, TenantQuery, TenantVO } from '@/api/system/tenant/types';
import { TenantPkgVO } from '@/api/system/tenantPackage/types';
@@ -232,6 +250,21 @@ const data = reactive<PageData<TenantForm, TenantQuery>>({
const { queryParams, form, rules } = toRefs(data);
/** 切换到目标租户后再进入空间、分类和文档维护。后端以动态租户上下文为准。 */
const manageKnowledge = async (row: TenantVO) => {
if (!row.tenantId || tenantContextSwitching.value) return;
if (row.status !== '0') {
proxy?.$modal.msgError('停用租户不能维护知识');
return;
}
try {
await switchDynamicTenantContext({ tenantId: row.tenantId, companyName: row.companyName }, '/knowledge/spaces');
proxy?.$modal.msgSuccess(`已切换至“${row.companyName}”,可继续维护知识空间、分类和知识`);
} catch (error) {
proxy?.$modal.msgError(error instanceof Error && error.message ? error.message : '租户切换失败,请刷新后重试');
}
};
/** 查询所有租户套餐 */
const getTenantPackage = async () => {
const res = await selectTenantPackage();
@@ -380,3 +413,43 @@ onMounted(() => {
getList();
});
</script>
<style scoped lang="scss">
.tenant-governance__head {
display: flex;
align-items: flex-end;
justify-content: space-between;
gap: 16px;
padding: 8px 2px 16px;
h1 {
margin: 0 0 6px;
color: #162033;
font-size: 24px;
line-height: 1.25;
}
p {
margin: 0;
color: #657184;
}
}
.tenant-governance__hint {
max-width: 420px;
padding: 8px 12px;
border: 1px solid #dbe7fb;
border-radius: 8px;
color: #39699f;
background: #f4f8ff;
font-size: 13px;
line-height: 1.5;
}
@media (max-width: 760px) {
.tenant-governance__head {
align-items: flex-start;
flex-direction: column;
}
}
</style>
+7 -5
View File
@@ -294,7 +294,7 @@ import { UserForm, UserQuery, UserVO } from '@/api/system/user/types';
import { DeptTreeVO, DeptVO } from '@/api/system/dept/types';
import { RoleVO } from '@/api/system/role/types';
import { PostVO } from '@/api/system/post/types';
import { globalHeaders } from '@/utils/request';
import { globalHeaders, reconcileTenantContextResponse } from '@/utils/request';
import { to } from 'await-to-js';
import { optionselect } from '@/api/system/post';
import { checkPermi } from '@/utils/permission';
@@ -332,6 +332,9 @@ const upload = reactive<ImportOption>({
// 上传的地址
url: import.meta.env.VITE_APP_BASE_API + '/system/user/importData'
});
watchEffect(() => {
upload.headers = globalHeaders();
});
// 列显隐信息
const columns = ref<FieldOption[]>([
{ key: 0, label: `用户编号`, visible: false, children: [] },
@@ -570,13 +573,14 @@ const handleFileUploadProgress = () => {
};
/** 文件上传成功处理 */
const handleFileSuccess = (response: any, file: UploadFile) => {
const tenantContextChanged = reconcileTenantContextResponse(response);
upload.open = false;
upload.isUploading = false;
uploadRef.value?.handleRemove(file);
ElMessageBox.alert("<div style='overflow: auto;overflow-x: hidden;max-height: 70vh;padding: 10px 20px 0;'>" + response.msg + '</div>', '导入结果', {
dangerouslyUseHTMLString: true
});
getList();
if (!tenantContextChanged) getList();
};
/** 提交上传文件 */
@@ -615,9 +619,7 @@ const handleUpdate = async (row?: UserForm) => {
dialog.title = '修改用户';
Object.assign(form.value, data.user);
postOptions.value = data.posts;
roleOptions.value = Array.from(
new Map([...data.roles, ...data.user.roles].map(role => [role.roleId, role])).values()
);
roleOptions.value = Array.from(new Map([...data.roles, ...data.user.roles].map((role) => [role.roleId, role])).values());
form.value.postIds = data.postIds;
form.value.roleIds = data.roleIds;
form.value.password = '';