@@ -193,7 +193,7 @@ class ClassroomApi {
|
||||
floors(buildingId: number) {
|
||||
return get<number[]>(`${CLASSROOM}/Floors`, { buildingId });
|
||||
}
|
||||
/** 全校教室扁平列表(空间筛选树数据源,轻量字段) */
|
||||
/** 全校教室扁平列表(楼层筛选树数据源,轻量字段) */
|
||||
tree() {
|
||||
return get<ClassroomTreeRow[]>(`${CLASSROOM}/Tree`);
|
||||
}
|
||||
|
||||
@@ -114,7 +114,7 @@ export interface Classroom {
|
||||
headTeacher?: string | null;
|
||||
}
|
||||
|
||||
/** 空间筛选树数据行(classroom/Tree 返回的轻量教室行,前端按 校区→楼栋→楼层 组树) */
|
||||
/** 楼层筛选树数据行(classroom/Tree 返回的轻量教室行,前端按 校区→楼栋→楼层 组树) */
|
||||
export interface ClassroomTreeRow {
|
||||
serID: number;
|
||||
campusId: number;
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user