Compare commits
3 Commits
3c4100c231
...
c5fad30849
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c5fad30849 | ||
|
|
d19d7d9a2c | ||
|
|
dcaa7e1779 |
@ -96,11 +96,22 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
|
||||
switch (userType) {
|
||||
case "platform" -> params.put("isSuperTenantFilter", 1);
|
||||
case "org" -> {
|
||||
// 排除超管、公众、评委租户
|
||||
// 此逻辑较复杂,通过 filterTenantId 或 tenantType 筛选
|
||||
// 排除超管、公众、评委租户,使用 org 租户 ID 列表过滤
|
||||
List<Long> orgTenantIds = getOrgTenantIds();
|
||||
if (orgTenantIds.isEmpty()) {
|
||||
// 如果没有机构租户,返回空结果
|
||||
return new PageResult<>(List.of(), 0L, page, pageSize);
|
||||
}
|
||||
params.put("orgTenantIdsFilter", orgTenantIds);
|
||||
}
|
||||
case "judge" -> {
|
||||
// 按租户 code='judge' 过滤(因为 judge 租户的 tenant_type 可能是 other)
|
||||
params.put("tenantCodeFilter", "judge");
|
||||
}
|
||||
case "public" -> {
|
||||
// 按租户 code='public' 过滤(因为 public 租户的 tenant_type=platform,不是 public)
|
||||
params.put("tenantCodeFilter", "public");
|
||||
}
|
||||
case "judge" -> params.put("tenantTypeFilter", "judge_pool");
|
||||
case "public" -> params.put("tenantTypeFilter", "public");
|
||||
}
|
||||
}
|
||||
if (filterTenantId != null) {
|
||||
@ -127,6 +138,18 @@ public class SysUserServiceImpl extends ServiceImpl<SysUserMapper, SysUser> impl
|
||||
return PageResult.from(result);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取机构租户 ID 列表(排除超管、公众、评委)
|
||||
*/
|
||||
private List<Long> getOrgTenantIds() {
|
||||
List<SysTenant> tenants = tenantMapper.selectList(
|
||||
new LambdaQueryWrapper<SysTenant>().eq(SysTenant::getValidState, 1));
|
||||
return tenants.stream()
|
||||
.filter(t -> t.getIsSuper() == 0 && !"public".equals(t.getCode()) && !"judge".equals(t.getCode()))
|
||||
.map(SysTenant::getId)
|
||||
.toList();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Map<String, Object> getStats() {
|
||||
List<SysTenant> tenants = tenantMapper.selectList(
|
||||
|
||||
@ -51,6 +51,17 @@
|
||||
<if test="params.filterTenantId != null">
|
||||
AND u.tenant_id = #{params.filterTenantId}
|
||||
</if>
|
||||
<!-- 机构租户 ID 列表过滤(排除超管、公众、评委) -->
|
||||
<if test="params.orgTenantIdsFilter != null and params.orgTenantIdsFilter.size() > 0">
|
||||
AND u.tenant_id IN
|
||||
<foreach collection="params.orgTenantIdsFilter" item="tenantId" open="(" separator="," close=")">
|
||||
#{tenantId}
|
||||
</foreach>
|
||||
</if>
|
||||
<!-- 按租户 code 过滤(用于 public、judge 等固定租户) -->
|
||||
<if test="params.tenantCodeFilter != null and params.tenantCodeFilter != ''">
|
||||
AND t.code = #{params.tenantCodeFilter}
|
||||
</if>
|
||||
<!-- 超管按 userType 过滤时,映射到租户类型 -->
|
||||
<if test="params.tenantTypeFilter != null and params.tenantTypeFilter != ''">
|
||||
AND t.tenant_type = #{params.tenantTypeFilter}
|
||||
|
||||
@ -308,3 +308,32 @@ public → tenant.code = 'public'
|
||||
**验证结果:**
|
||||
- 后端重启成功,编译无错误
|
||||
- 前端 HMR 热更新生效,无新增 TS 错误
|
||||
|
||||
### 2026-04-02 — 机构用户过滤逻辑修复
|
||||
|
||||
**问题:**
|
||||
- 前端点击"机构"统计卡片时,后端 `findAll(userType=org)` 返回 0 条数据,与统计接口返回不一致
|
||||
- 原因:`case "org"` 分支没有设置任何过滤参数,Mapper XML 中也没有对应的处理逻辑
|
||||
|
||||
**改动(2 个文件):**
|
||||
- `backend/.../SysUserServiceImpl.java` — `findAll()` 方法:`case "org"` 分支增加调用 `getOrgTenantIds()` 获取机构租户 ID 列表,传递 `orgTenantIdsFilter` 参数;新增 `getOrgTenantIds()` 私有方法
|
||||
- `backend/.../SysUserMapper.xml` — `selectUserPage` 增加 `<if test="params.orgTenantIdsFilter != null">` 条件,使用 `<foreach>` 遍历 ID 列表进行 `IN` 查询
|
||||
|
||||
**验证结果:**
|
||||
- 后端编译成功,Maven 编译无错误
|
||||
- 机构用户列表查询应正确排除超管、公众、评委租户
|
||||
|
||||
### 2026-04-02 — 公众/评委用户过滤逻辑修复
|
||||
|
||||
**问题:**
|
||||
- 前端点击"公众"卡片时,传递 `userType=public`,但返回空数据(统计显示 9 条)
|
||||
- 原因:后端 `case "public"` 按 `tenant_type = 'public'` 过滤,但数据库中 public 租户的 `tenant_type = 'platform'`(不是 `'public'`)
|
||||
- 同理,`case "judge"` 按 `tenant_type = 'judge_pool'` 过滤,但 judge 租户的 `tenant_type = 'other'`
|
||||
|
||||
**改动(2 个文件):**
|
||||
- `backend/.../SysUserServiceImpl.java` — `findAll()` 方法:`case "public"` 和 `case "judge"` 改为传递 `tenantCodeFilter` 参数(值分别为 `"public"` 和 `"judge"`),按租户 code 过滤而非 tenant_type
|
||||
- `backend/.../SysUserMapper.xml` — `selectUserPage` 增加 `<if test="params.tenantCodeFilter != null">` 条件,使用 `t.code = #{params.tenantCodeFilter}` 过滤
|
||||
|
||||
**验证结果:**
|
||||
- 后端编译成功
|
||||
- 公众/评委用户列表查询应按租户 code 正确过滤
|
||||
|
||||
@ -38,6 +38,12 @@ export interface User {
|
||||
modifier?: number;
|
||||
createTime?: string;
|
||||
modifyTime?: string;
|
||||
// 平铺的租户字段(后端直接返回)
|
||||
tenantName?: string;
|
||||
tenantCode?: string;
|
||||
tenantType?: string;
|
||||
tenantIsSuper?: number;
|
||||
// 兼容嵌套对象(详情接口可能返回)
|
||||
tenant?: UserTenant;
|
||||
roles?: Array<{
|
||||
id: number;
|
||||
|
||||
@ -6,7 +6,9 @@
|
||||
<div class="header-bar">
|
||||
<div class="header-left">
|
||||
<a-button type="text" @click="$router.back()">
|
||||
<template #icon><ArrowLeftOutlined /></template>
|
||||
<template #icon>
|
||||
<ArrowLeftOutlined />
|
||||
</template>
|
||||
返回
|
||||
</a-button>
|
||||
<h1 class="title">{{ contest.contestName }}</h1>
|
||||
@ -77,11 +79,8 @@
|
||||
|
||||
<!-- 时间轴 -->
|
||||
<div class="timeline-bar">
|
||||
<div
|
||||
v-for="(phase, idx) in phases"
|
||||
:key="phase.key"
|
||||
:class="['phase', { completed: phase.status === 'completed', current: phase.status === 'current', future: phase.status === 'future' }]"
|
||||
>
|
||||
<div v-for="(phase, idx) in phases" :key="phase.key"
|
||||
:class="['phase', { completed: phase.status === 'completed', current: phase.status === 'current', future: phase.status === 'future' }]">
|
||||
<div class="phase-dot">
|
||||
<CheckOutlined v-if="phase.status === 'completed'" />
|
||||
<span v-else>{{ idx + 1 }}</span>
|
||||
@ -90,7 +89,8 @@
|
||||
<span class="phase-name">{{ phase.name }}</span>
|
||||
<span class="phase-time">{{ phase.timeRange }}</span>
|
||||
</div>
|
||||
<div v-if="idx < phases.length - 1" class="phase-line" :class="{ done: phase.status === 'completed' }"></div>
|
||||
<div v-if="idx < phases.length - 1" class="phase-line" :class="{ done: phase.status === 'completed' }">
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@ -100,9 +100,11 @@
|
||||
<a-tab-pane key="config" tab="活动配置">
|
||||
<a-descriptions bordered :column="2" size="small" class="config-desc">
|
||||
<a-descriptions-item label="活动名称" :span="2">{{ contest.contestName }}</a-descriptions-item>
|
||||
<a-descriptions-item label="活动类型">{{ contest.contestType === 'individual' ? '个人参与' : '团队参与' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="活动类型">{{ contest.contestType === 'individual' ? '个人参与' : '团队参与'
|
||||
}}</a-descriptions-item>
|
||||
<a-descriptions-item label="可见范围">{{ visibilityMap[contest.visibility] || '-' }}</a-descriptions-item>
|
||||
<a-descriptions-item v-if="contest.visibility === 'targeted' && contest.targetCities?.length" label="定向城市" :span="2">
|
||||
<a-descriptions-item v-if="contest.visibility === 'targeted' && contest.targetCities?.length"
|
||||
label="定向城市" :span="2">
|
||||
<a-tag v-for="c in contest.targetCities" :key="c">{{ c }}</a-tag>
|
||||
</a-descriptions-item>
|
||||
<a-descriptions-item v-if="contest.visibility === 'targeted'" label="年龄限制">
|
||||
@ -112,7 +114,8 @@
|
||||
|
||||
<a-divider orientation="left">报名配置</a-divider>
|
||||
<a-descriptions bordered :column="2" size="small">
|
||||
<a-descriptions-item label="报名时间" :span="2">{{ formatDate(contest.registerStartTime) }} ~ {{ formatDate(contest.registerEndTime) }}</a-descriptions-item>
|
||||
<a-descriptions-item label="报名时间" :span="2">{{ formatDate(contest.registerStartTime) }} ~ {{
|
||||
formatDate(contest.registerEndTime) }}</a-descriptions-item>
|
||||
<a-descriptions-item label="是否需要审核">{{ contest.requireAudit ? '是' : '否' }}</a-descriptions-item>
|
||||
<a-descriptions-item v-if="contest.contestType === 'team'" label="团队人数">
|
||||
{{ contest.teamMinMembers || '-' }} ~ {{ contest.teamMaxMembers || '-' }} 人
|
||||
@ -121,15 +124,19 @@
|
||||
|
||||
<a-divider orientation="left">作品配置</a-divider>
|
||||
<a-descriptions bordered :column="2" size="small">
|
||||
<a-descriptions-item label="提交时间" :span="2">{{ formatDate(contest.submitStartTime) }} ~ {{ formatDate(contest.submitEndTime) }}</a-descriptions-item>
|
||||
<a-descriptions-item label="提交规则">{{ contest.submitRule === 'resubmit' ? '允许重新提交' : '单次提交' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="提交时间" :span="2">{{ formatDate(contest.submitStartTime) }} ~ {{
|
||||
formatDate(contest.submitEndTime) }}</a-descriptions-item>
|
||||
<a-descriptions-item label="提交规则">{{ contest.submitRule === 'resubmit' ? '允许重新提交' : '单次提交'
|
||||
}}</a-descriptions-item>
|
||||
<a-descriptions-item label="作品类型">{{ workTypeMap[contest.workType] || '-' }}</a-descriptions-item>
|
||||
<a-descriptions-item v-if="contest.workRequirement" label="作品要求" :span="2">{{ contest.workRequirement }}</a-descriptions-item>
|
||||
<a-descriptions-item v-if="contest.workRequirement" label="作品要求" :span="2">{{ contest.workRequirement
|
||||
}}</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
|
||||
<a-divider orientation="left">评审配置</a-divider>
|
||||
<a-descriptions bordered :column="2" size="small">
|
||||
<a-descriptions-item label="评审时间" :span="2">{{ formatDate(contest.reviewStartTime) }} ~ {{ formatDate(contest.reviewEndTime) }}</a-descriptions-item>
|
||||
<a-descriptions-item label="评审时间" :span="2">{{ formatDate(contest.reviewStartTime) }} ~ {{
|
||||
formatDate(contest.reviewEndTime) }}</a-descriptions-item>
|
||||
<a-descriptions-item label="评审规则">{{ contest.reviewRule?.ruleName || '-' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="成果状态">
|
||||
<a-tag :color="contest.resultState === 'published' ? 'green' : 'default'">
|
||||
@ -143,7 +150,8 @@
|
||||
<a-descriptions-item label="主办单位">{{ formatOrgList(contest.organizers) }}</a-descriptions-item>
|
||||
<a-descriptions-item label="协办单位">{{ formatOrgList(contest.coOrganizers) }}</a-descriptions-item>
|
||||
<a-descriptions-item label="赞助单位">{{ formatOrgList(contest.sponsors) }}</a-descriptions-item>
|
||||
<a-descriptions-item label="联系人">{{ contest.contactName || '-' }} {{ contest.contactPhone ? `/ ${contest.contactPhone}` : '' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="联系人">{{ contest.contactName || '-' }} {{ contest.contactPhone ? `/
|
||||
${contest.contactPhone}` : '' }}</a-descriptions-item>
|
||||
</a-descriptions>
|
||||
</a-tab-pane>
|
||||
|
||||
@ -308,6 +316,8 @@ $primary: #6366f1;
|
||||
|
||||
.super-detail-page {
|
||||
max-width: 1200px;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
}
|
||||
|
||||
// 顶部信息栏
|
||||
@ -316,7 +326,7 @@ $primary: #6366f1;
|
||||
border-radius: 12px;
|
||||
padding: 20px 24px;
|
||||
margin-bottom: 16px;
|
||||
box-shadow: 0 2px 12px rgba(0,0,0,0.06);
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
|
||||
|
||||
.header-left {
|
||||
display: flex;
|
||||
@ -361,7 +371,7 @@ $primary: #6366f1;
|
||||
background: #fff;
|
||||
border-radius: 12px;
|
||||
padding: 18px 20px;
|
||||
box-shadow: 0 2px 12px rgba(0,0,0,0.06);
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
|
||||
cursor: pointer;
|
||||
transition: all 0.2s;
|
||||
|
||||
@ -392,11 +402,24 @@ $primary: #6366f1;
|
||||
color: #1e1b4b;
|
||||
line-height: 1.2;
|
||||
}
|
||||
.ov-sub { font-size: 14px; color: #9ca3af; font-weight: 400; }
|
||||
.ov-label { font-size: 12px; color: #9ca3af; margin-top: 2px; }
|
||||
|
||||
.ov-sub {
|
||||
font-size: 14px;
|
||||
color: #9ca3af;
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.ov-label {
|
||||
font-size: 12px;
|
||||
color: #9ca3af;
|
||||
margin-top: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.ov-arrow { color: #d1d5db; font-size: 12px; }
|
||||
.ov-arrow {
|
||||
color: #d1d5db;
|
||||
font-size: 12px;
|
||||
}
|
||||
}
|
||||
|
||||
// 时间轴
|
||||
@ -407,7 +430,7 @@ $primary: #6366f1;
|
||||
border-radius: 12px;
|
||||
padding: 24px 32px;
|
||||
margin-bottom: 16px;
|
||||
box-shadow: 0 2px 12px rgba(0,0,0,0.06);
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
|
||||
}
|
||||
|
||||
.phase {
|
||||
@ -435,8 +458,18 @@ $primary: #6366f1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
margin-left: 10px;
|
||||
.phase-name { font-size: 13px; font-weight: 600; color: #374151; }
|
||||
.phase-time { font-size: 11px; color: #9ca3af; margin-top: 2px; }
|
||||
|
||||
.phase-name {
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
color: #374151;
|
||||
}
|
||||
|
||||
.phase-time {
|
||||
font-size: 11px;
|
||||
color: #9ca3af;
|
||||
margin-top: 2px;
|
||||
}
|
||||
}
|
||||
|
||||
.phase-line {
|
||||
@ -446,12 +479,21 @@ $primary: #6366f1;
|
||||
margin: 0 16px;
|
||||
min-width: 20px;
|
||||
|
||||
&.done { background: $primary; }
|
||||
&.done {
|
||||
background: $primary;
|
||||
}
|
||||
}
|
||||
|
||||
&.completed {
|
||||
.phase-dot { background: $primary; color: #fff; border-color: $primary; }
|
||||
.phase-info .phase-name { color: $primary; }
|
||||
.phase-dot {
|
||||
background: $primary;
|
||||
color: #fff;
|
||||
border-color: $primary;
|
||||
}
|
||||
|
||||
.phase-info .phase-name {
|
||||
color: $primary;
|
||||
}
|
||||
}
|
||||
|
||||
&.current {
|
||||
@ -461,7 +503,11 @@ $primary: #6366f1;
|
||||
border-color: $primary;
|
||||
box-shadow: 0 0 0 4px rgba($primary, 0.15);
|
||||
}
|
||||
.phase-info .phase-name { color: $primary; font-weight: 700; }
|
||||
|
||||
.phase-info .phase-name {
|
||||
color: $primary;
|
||||
font-weight: 700;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -469,13 +515,18 @@ $primary: #6366f1;
|
||||
.tab-card {
|
||||
border: none;
|
||||
border-radius: 12px;
|
||||
box-shadow: 0 2px 12px rgba(0,0,0,0.06);
|
||||
box-shadow: 0 2px 12px rgba(0, 0, 0, 0.06);
|
||||
|
||||
.config-desc { margin-bottom: 0; }
|
||||
.config-desc {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.rich-content {
|
||||
:deep(img) { max-width: 100%; border-radius: 8px; }
|
||||
:deep(img) {
|
||||
max-width: 100%;
|
||||
border-radius: 8px;
|
||||
}
|
||||
}
|
||||
|
||||
// 通知
|
||||
@ -492,9 +543,24 @@ $primary: #6366f1;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 6px;
|
||||
}
|
||||
.notice-title { font-weight: 600; color: #1e1b4b; font-size: 14px; }
|
||||
.notice-body { font-size: 13px; color: #4b5563; line-height: 1.6; }
|
||||
.notice-time { font-size: 11px; color: #9ca3af; margin-top: 8px; }
|
||||
|
||||
.notice-title {
|
||||
font-weight: 600;
|
||||
color: #1e1b4b;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
.notice-body {
|
||||
font-size: 13px;
|
||||
color: #4b5563;
|
||||
line-height: 1.6;
|
||||
}
|
||||
|
||||
.notice-time {
|
||||
font-size: 11px;
|
||||
color: #9ca3af;
|
||||
margin-top: 8px;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@ -84,7 +84,7 @@
|
||||
<template v-else-if="column.key === 'tenantName'">
|
||||
{{ getUserTypeKey(record) === 'platform' || getUserTypeKey(record) === 'public'
|
||||
? '-'
|
||||
: record.tenant?.name || '-' }}
|
||||
: record.tenantName || '-' }}
|
||||
</template>
|
||||
<template v-else-if="column.key === 'phone'">
|
||||
{{ record.phone || '-' }}
|
||||
@ -140,7 +140,7 @@
|
||||
<a-descriptions-item label="邮箱">{{ detailData.email || '-' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="城市">{{ detailData.city || '-' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="性别">{{ genderLabel(detailData.gender) }}</a-descriptions-item>
|
||||
<a-descriptions-item label="所属机构">{{ detailData.tenant?.name || '-' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="所属机构">{{ detailData.tenantName || '-' }}</a-descriptions-item>
|
||||
<a-descriptions-item label="用户类型">
|
||||
<a-tag :color="getUserTypeTag(detailData).color">{{ getUserTypeTag(detailData).label }}</a-tag>
|
||||
</a-descriptions-item>
|
||||
|
||||
Loading…
Reference in New Issue
Block a user