图标
build / build (push) Successful in 4m19s

This commit is contained in:
wanghu
2026-10-20 16:20:47 +08:00
parent b1f065a790
commit 0f10467ebb
3 changed files with 127 additions and 52 deletions
+1 -1
View File
@@ -193,7 +193,7 @@ class ClassroomApi {
floors(buildingId: number) {
return get<number[]>(`${CLASSROOM}/Floors`, { buildingId });
}
/** 全校教室扁平列表(空间筛选树数据源,轻量字段) */
/** 全校教室扁平列表(楼层筛选树数据源,轻量字段) */
tree() {
return get<ClassroomTreeRow[]>(`${CLASSROOM}/Tree`);
}
+1 -1
View File
@@ -114,7 +114,7 @@ export interface Classroom {
headTeacher?: string | null;
}
/** 空间筛选树数据行(classroom/Tree 返回的轻量教室行,前端按 校区→楼栋→楼层 组树) */
/** 楼层筛选树数据行(classroom/Tree 返回的轻量教室行,前端按 校区→楼栋→楼层 组树) */
export interface ClassroomTreeRow {
serID: number;
campusId: number;
+95 -20
View File
@@ -23,7 +23,7 @@ const GRADE_LABELS: Record<number, string> = { 1: "高一", 2: "高二", 3: "高
/**
* 教室管理(核心页)· 三栏布局
*
* 信息层级:左侧「空间筛选」树(校区→楼栋→楼层→教室,点击节点即切换列表作用域)
* 信息层级:左侧「楼层筛选」树(校区→楼栋→楼层→教室,点击节点即切换列表作用域)
* → 中间教室列表(搜索 / 筛选 / 排序 / 分页 / 多选批量操作 / 批量导出)
* → 右侧选中教室的详情面板(字段 + 布局示意图 + 编辑入口)。
*
@@ -911,10 +911,10 @@ onMounted(async () => {
<template>
<div class="ac-page">
<div class="cm-layout">
<!-- ========== 左:空间筛选树 ========== -->
<!-- ========== 左:楼层筛选树 ========== -->
<aside class="ac-card cm-tree">
<div class="ac-card__head cm-pane-head">
<div class="ac-card__title">空间筛选</div>
<div class="ac-card__title">楼层筛选</div>
<span class="pane-sub">共 {{ treeRooms.length }} 间</span>
</div>
<div class="cm-tree__body">
@@ -952,7 +952,14 @@ onMounted(async () => {
<span class="tree-caret" :class="{ 'is-open': isOpen(b.key) }">
<Icon type="ios-arrow-forward" />
</span>
<span class="tree-icon"><Icon type="ios-folder" /></span>
<span class="tree-icon">
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
<path
fill-rule="evenodd"
d="M6 2h12v20H6V2zm2 2.5v2.5h2.4V4.5H8zm5.6 0v2.5H16V4.5h-2.4zM8 9v2.5h2.4V9H8zm5.6 0v2.5H16V9h-2.4zM8 13.5V16h2.4v-2.5H8zm5.6 0V16H16v-2.5h-2.4zM10.5 22v-5h3v5h-3z"
/>
</svg>
</span>
<span class="tree-label" :title="b.title">{{ b.title }}</span>
<span v-if="b.sub" class="tree-sub">{{ b.sub }}</span>
<span class="tree-count">{{ b.count }}</span>
@@ -1005,7 +1012,18 @@ onMounted(async () => {
>
<Icon type="ios-checkmark" class="tree-check__on" />
</span>
<span class="tree-dot" :class="r.status === 'open' ? 'is-open' : 'is-closed'"></span>
<span
class="tree-door"
:class="r.status === 'open' ? 'is-open' : 'is-closed'"
:title="r.status === 'open' ? '开放' : '关闭'"
>
<svg viewBox="0 0 24 24" fill="currentColor" aria-hidden="true">
<path
fill-rule="evenodd"
d="M6.5 3h11v18h-11zM14.7 11.15a1.15 1.15 0 1 0 0 2.3 1.15 1.15 0 0 0 0-2.3zM4 21h16v1.5H4z"
/>
</svg>
</span>
<span class="tree-label" :title="r.title">{{ r.title }}</span>
</div>
</template>
@@ -1132,9 +1150,15 @@ onMounted(async () => {
</div>
</transition>
<div class="cm-table-wrap">
<!-- 自绘加载层:Table 内置 Spin 的淡出过渡在渲染帧冻结时会卡住并拦截点击,
这里用 v-show 直接切 display,pointer-events 不拦鼠标 -->
<div v-show="loadingRooms" class="cm-table-loading" aria-hidden="true">
<span class="cm-table-loading__dot"></span>
<span>加载中…</span>
</div>
<Table
ref="tableRef"
:loading="loadingRooms"
:columns="columns"
:data="rooms.list"
row-key="serID"
@@ -1150,12 +1174,13 @@ onMounted(async () => {
</template>
<template #status="{ row }">
<Tooltip content="点击切换 开放 / 关闭" placement="top" transfer>
<span
class="status-toggle"
:class="{ 'is-busy': togglingId === row.serID }"
<span class="status-toggle" :class="{ 'is-busy': togglingId === row.serID }">
<!-- 点击必须绑在 Tag 上:Tag 根节点自带 @click.stop,
事件冒泡不到外层 span -->
<Tag
:color="row.status === 'open' ? 'success' : 'error'"
@click.stop="toggleStatus(row)"
>
<Tag :color="row.status === 'open' ? 'success' : 'error'">
{{ row.status === "open" ? "开放" : "关闭" }}
</Tag>
</span>
@@ -1167,6 +1192,7 @@ onMounted(async () => {
<span class="row-action row-action--danger" @click.stop="removeRows([row])">删除</span>
</template>
</Table>
</div>
<!-- view-ui-plus 的 Table 没有 empty 插槽,空态改为表格下方自渲染
(内置空文案已用 no-data-text / no-filtered-data-text 置空关闭) -->
@@ -1241,7 +1267,7 @@ onMounted(async () => {
<div class="detail-row">
<span class="detail-row__label">状态</span>
<span class="detail-row__value">
<Tag :color="focusRoom.status === 'open' ? 'success' : 'error'">
<Tag :color="focusRoom.status === 'open' ? 'success' : 'default'">
{{ focusRoom.status === "open" ? "开放" : "关闭" }}
</Tag>
</span>
@@ -1502,7 +1528,7 @@ onMounted(async () => {
white-space: nowrap;
}
/* ============================ 空间筛选树 ============================ */
/* ============================ 楼层筛选树 ============================ */
.cm-tree__body {
flex: 1 1 auto;
overflow: auto;
@@ -1605,6 +1631,12 @@ onMounted(async () => {
.ivu-icon {
font-size: 15px;
}
> svg {
display: block;
width: 15px;
height: 15px;
}
}
.tree-icon--campus {
@@ -1686,19 +1718,21 @@ onMounted(async () => {
}
}
.tree-dot {
.tree-door {
flex: none;
width: 7px;
height: 7px;
border-radius: 50%;
margin: 0 3px;
display: inline-flex;
width: 13px;
height: 13px;
color: var(--ac-success);
&.is-open {
background: var(--ac-success);
> svg {
display: block;
width: 13px;
height: 13px;
}
&.is-closed {
background: var(--ac-danger);
color: var(--ac-text-disabled);
}
}
@@ -1851,6 +1885,47 @@ onMounted(async () => {
padding: 0 10px;
}
.cm-table-wrap {
position: relative;
}
/* 表格加载层:v-show 直接切 display,不依赖过渡结束;pointer-events 置空,
即使常驻也不会拦截行内按钮/状态标签的点击 */
.cm-table-loading {
position: absolute;
inset: 0;
z-index: 8;
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
background: rgba(255, 255, 255, 0.72);
color: var(--ac-text-secondary);
font-size: 13px;
pointer-events: none;
}
.cm-table-loading__dot {
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--ac-primary);
animation: cm-loading-blink 0.9s ease-in-out infinite;
}
@keyframes cm-loading-blink {
0%,
100% {
opacity: 0.25;
transform: scale(0.8);
}
50% {
opacity: 1;
transform: scale(1);
}
}
.status-toggle {
display: inline-flex;
cursor: pointer;