fix(dept): 部门表格改用 iView Table,来源切换去英文并固定顺序,自定义排序改为弹框拖动
build / build (push) Successful in 3m51s

- 系统部门:自写 table 换成 iView Table(border + 表头灰底 + 行 hover),
  层级仍用名称列缩进 + 折叠;状态改为可点击的边框 Tag(已启用/已停用)
- 标题行:去掉 SYSTEM / CUSTOM 英文,「包含子组」移到来源切换左侧并整体右对齐,
  切换视图时标签位置不再左右跳动
- 自定义部门:同样换 iView Table;表格内不再拖行,改为「排序管理」按钮 + 弹框拖动,
  保存后按新顺序提交 customDeptSort;操作列保留 编辑/成员/删除
- 修正 Tag 上的 @click.native(Vue 3 已移除 .native)
This commit is contained in:
chenxing
2026-10-23 15:10:24 +08:00
parent c39c0f83d7
commit 51c95dd7b9
2 changed files with 371 additions and 408 deletions
+139 -187
View File
@@ -18,17 +18,19 @@
<Option v-for="s in schoolOptions" :key="s.id" :value="s.id">{{ s.name }}</Option>
</Select>
<!-- 来源切换:只切右侧管理视图,学校上下文共用,不改底层组织树 -->
<div class="source-tabs ml-auto">
<span class="source-tab" :class="{ 'is-active': source === 'SYSTEM' }"
@click="source = 'SYSTEM'">系统部门 SYSTEM</span>
<span class="source-tab" :class="{ 'is-active': source === 'CUSTOM' }"
@click="source = 'CUSTOM'">自定义部门 CUSTOM</span>
<div class="title-tail">
<span v-if="source === 'SYSTEM'" class="inc-ctrl" title="打开后列出该校下所有层级的部门"
@click="toggleInclude">
<span class="inc-label">包含子组</span>
<i class="sw-mini" :class="{ 'is-on': includeOn }"></i>
</span>
<div class="source-tabs">
<span class="source-tab" :class="{ 'is-active': source === 'SYSTEM' }"
@click="source = 'SYSTEM'">系统部门</span>
<span class="source-tab" :class="{ 'is-active': source === 'CUSTOM' }"
@click="source = 'CUSTOM'">自定义部门</span>
</div>
</div>
<span v-if="source === 'SYSTEM'" class="inc-ctrl" title="打开后列出该校下所有层级的部门"
@click="toggleInclude">
<span class="inc-label">包含子组</span>
<i class="sw-mini" :class="{ 'is-on': includeOn }"></i>
</span>
</div>
<template v-if="source === 'SYSTEM'">
@@ -47,50 +49,30 @@
<template v-else>
<div class="row-sum">{{ deptRows.length }} 个一级部门 · 共 {{ flatRows.length }} 行</div>
<table class="dept-table">
<colgroup>
<col style="width: 58px" />
<col />
<col style="width: 120px" />
<col style="width: 268px" />
<col style="width: 74px" />
</colgroup>
<thead>
<tr>
<th class="ta-c">序号</th>
<th>部门名称</th>
<th>上级</th>
<th>人员</th>
<th class="ta-c">状态</th>
</tr>
</thead>
<tbody>
<tr v-for="row in flatRows" :key="row.id" :class="{ 'is-off': row.status === 1 }">
<td class="ta-c dim">{{ row.no }}</td>
<td>
<span class="nmcell" :style="{ paddingLeft: row.level * 20 + 'px' }">
<i v-if="row.kidCount" class="tw" @click="toggleRow(row.id)">{{ row.open ? '▾' : '▸' }}</i>
<i v-else class="tw blank"></i>
<span class="nm" :class="{ root: row.level === 0 }" :title="row.name">{{ row.name }}</span>
<span v-if="row.kidCount" class="cnt">{{ row.kidCount }} 个下级</span>
<span v-if="row.isStudent" class="cnt grey">学生组</span>
</span>
</td>
<td class="dim" :title="row.parentName">{{ row.parentName }}</td>
<td>
<Tooltip v-if="row.memberCount" placement="top" max-width="360"
:content="memberTip(row)">
<span class="mem-cell" @click="openMembers(row)">{{ memberText(row) }}</span>
</Tooltip>
<span v-else class="dim">无成员</span>
</td>
<td class="ta-c">
<i class="sw" :class="{ 'is-on': row.status !== 1 }"
:title="row.status === 1 ? '启用' : '停用'" @click="onToggle(row.raw)"></i>
</td>
</tr>
</tbody>
</table>
<Table class="dept-itable" border :columns="sysColumns" :data="flatRows"
:row-class-name="rowClassName" :loading="false" no-data-text="暂无部门">
<template #name="{ row }">
<span class="nmcell" :style="{ paddingLeft: row.level * 20 + 'px' }">
<i v-if="row.kidCount" class="tw" @click.stop="toggleRow(row.id)">{{ row.open ? '▾' : '▸' }}</i>
<i v-else class="tw blank"></i>
<span class="nm" :class="{ root: row.level === 0 }" :title="row.name">{{ row.name }}</span>
<span v-if="row.kidCount" class="cnt">{{ row.kidCount }} 个下级</span>
<span v-if="row.isStudent" class="cnt grey">学生组</span>
</span>
</template>
<template #members="{ row }">
<Tooltip v-if="row.memberCount" placement="top" max-width="360" :content="memberTip(row)">
<span class="mem-cell" @click="openMembers(row)">{{ memberText(row) }}</span>
</Tooltip>
<span v-else class="dim">无成员</span>
</template>
<template #status="{ row }">
<Tag type="border" :color="row.status === 1 ? 'error' : 'success'" class="sta-tag"
:title="row.status === 1 ? '点击启用' : '点击停用'" @click="onToggle(row.raw)">
{{ row.status === 1 ? '已停用' : '已启用' }}
</Tag>
</template>
</Table>
<div class="table-foot">共 {{ flatRows.length }} 个部门</div>
</template>
</template>
@@ -240,6 +222,19 @@ const flatRows = computed<DeptRow[]>(() => {
return out;
});
/** iView Table 列定义:层级用「部门名称」列的缩进 + 折叠表达,不用 Table 自带树(避免样式不可控) */
const sysColumns: any[] = [
{ title: "序号", type: "index", width: 80, align: "center" },
{ title: "部门名称", slot: "name", minWidth: 300 },
{ title: "上级", key: "parentName", width: 160 },
{ title: "人员", slot: "members", minWidth: 320 },
{ title: "状态", slot: "status", width: 120, align: "center" },
];
/** 停用行整行灰化 */
function rowClassName(row: any) {
return row?.status === 1 ? "is-off" : "";
}
/** 人员列:前 3 个姓名 + 剩余数;无姓名时退回「N 人」 */
function memberText(row: DeptRow) {
if (!row.memberNames.length) return `${row.memberCount} 人`;
@@ -477,6 +472,14 @@ async function loadSchoolScope() {
color: var(--text, #1e2635);
}
/* 标题行右侧:包含子组 + 来源切换,整体右对齐;「包含子组」放左边,切视图时 tab 位置不跳 */
.title-tail {
margin-left: auto;
display: flex;
align-items: center;
gap: 14px;
}
.school-select {
width: 180px;
}
@@ -570,7 +573,7 @@ async function loadSchoolScope() {
}
}
/* ================= 部门表格 ================= */
/* ================= 部门表格(iView Table) ================= */
.row-sum {
flex: 0 0 auto;
margin-bottom: 8px;
@@ -578,147 +581,96 @@ async function loadSchoolScope() {
color: var(--text-3, #98a1b2);
}
.dept-table {
width: 100%;
border-collapse: collapse;
table-layout: fixed;
font-size: 12.5px;
color: var(--text, #1e2635);
th {
height: 40px;
padding: 0 12px;
background: #f7f8fa;
border-bottom: 1px solid var(--line, #e9edf5);
color: var(--text-2, #5a6478);
font-size: 12px;
font-weight: 500;
text-align: left;
white-space: nowrap;
/* 表格本体外观全部交给 iView:只补单元格内部的排版细节 */
.dept-itable {
:deep(.ivu-table-cell) {
padding-left: 12px;
padding-right: 12px;
}
td {
height: 46px;
padding: 0 12px;
border-bottom: 1px solid var(--line, #e9edf5);
vertical-align: middle;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
tbody tr:hover td {
background: #fafbfe;
}
tbody tr.is-off td {
:deep(.ivu-table-row.is-off td) {
background: #fcfcfd;
}
.ta-c {
text-align: center;
}
.dim {
color: var(--text-3, #98a1b2);
font-size: 11.5px;
}
.nmcell {
display: flex;
align-items: center;
gap: 6px;
min-width: 0;
}
.tw {
flex: 0 0 auto;
width: 14px;
text-align: center;
font-style: normal;
font-size: 9px;
color: #aeb6c2;
cursor: pointer;
&.blank {
visibility: hidden;
}
}
.nm {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
&.root {
font-weight: 600;
}
}
tr.is-off .nm {
color: var(--text-3, #98a1b2);
}
.cnt {
flex: 0 0 auto;
padding: 0 5px;
line-height: 16px;
border-radius: 4px;
background: #f1f3f7;
color: #8a94a6;
font-size: 10.5px;
&.grey {
background: #f6f7fa;
color: #a3adbd;
}
}
.mem-cell {
cursor: pointer;
color: var(--text-2, #5a6478);
border-bottom: 1px dashed transparent;
&:hover {
color: var(--brand, #577afb);
border-bottom-color: #c9d4f7;
}
}
/* 胶囊开关(与小组件管理同款比例) */
.sw {
position: relative;
display: inline-block;
width: 30px;
height: 17px;
border-radius: 9px;
background: #d5d8de;
cursor: pointer;
transition: background .18s;
&::after {
content: '';
position: absolute;
top: 2px;
left: 2px;
width: 13px;
height: 13px;
border-radius: 50%;
background: #fff;
box-shadow: 0 1px 3px rgba(0, 0, 0, .2);
transition: left .18s;
}
&.is-on {
background: var(--brand, #577afb);
&::after {
left: 15px;
}
}
:deep(.ivu-table-row.is-off:hover td) {
background: #f8f9fb;
}
}
.nmcell {
display: flex;
align-items: center;
gap: 6px;
min-width: 0;
}
.nmcell .tw {
flex: 0 0 auto;
width: 14px;
text-align: center;
font-style: normal;
font-size: 9px;
color: #aeb6c2;
cursor: pointer;
}
.nmcell .tw.blank {
visibility: hidden;
}
.nmcell .nm {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.nmcell .nm.root {
font-weight: 600;
color: var(--text, #1e2635);
}
.nmcell .cnt {
flex: 0 0 auto;
padding: 0 5px;
line-height: 16px;
border-radius: 4px;
background: #f1f3f7;
color: #8a94a6;
font-size: 10.5px;
}
.nmcell .cnt.grey {
background: #f6f7fa;
color: #a3adbd;
}
.mem-cell {
cursor: pointer;
color: var(--text-2, #5a6478);
border-bottom: 1px dashed transparent;
}
.mem-cell:hover {
color: var(--brand, #577afb);
border-bottom-color: #c9d4f7;
}
.dim {
color: var(--text-3, #98a1b2);
font-size: 11.5px;
}
/* 状态标签可点:hover 给一点反馈,暗示能切换 */
.sta-tag {
cursor: pointer;
}
.sta-tag:hover {
opacity: .82;
}
.table-foot {
flex: 0 0 auto;
padding: 10px 2px;