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
+232 -221
View File
@@ -6,7 +6,7 @@
<template v-else>
<div class="custom-body">
<!-- 左侧:分类平铺列表(可拖动排序,↑↓ 保留兜底) -->
<!-- 左侧:分类平铺列表(容器,可拖动排序,↑↓ 保留兜底) -->
<div class="cat-col">
<div class="col-head">
<span class="col-title">自定义分类</span>
@@ -38,11 +38,14 @@
</ul>
</div>
<!-- 右侧:当前分类下的部门表格(拖动行排序) -->
<!-- 右侧:当前分类下的部门表格(iView Table;排序走弹框) -->
<div class="dept-col">
<div class="col-head">
<span class="col-title">{{ activeCategoryName || '部门' }}</span>
<span class="col-sub">{{ departments.length }} 个部门 · 拖动行可排序</span>
<span class="col-sub">{{ departments.length }} 个部门</span>
<Button size="small" :disabled="!activeCategoryId || !departments.length" @click="openSort">
排序管理
</Button>
<Button size="small" type="primary" :disabled="!activeCategoryId" @click="openDeptCreate">
新增部门
</Button>
@@ -51,58 +54,50 @@
请先在左侧选择一个分类,或新建一个分类。
</Alert>
<div v-else-if="loadingDepartments" class="col-tip">加载中…</div>
<div v-else-if="!departments.length" class="col-tip">
该分类下暂无部门。部门可先新建再配成员,成员为空时不影响保存。
</div>
<table v-else class="dept-table">
<colgroup>
<col style="width: 58px" />
<col />
<col style="width: 268px" />
<col style="width: 74px" />
<col style="width: 150px" />
</colgroup>
<thead>
<tr>
<th class="ta-c">序号</th>
<th>部门名称</th>
<th>人员</th>
<th class="ta-c">状态</th>
<th class="ta-r">操作</th>
</tr>
</thead>
<tbody>
<tr v-for="(d, idx) in departments" :key="d.id"
:class="{ 'is-off': !d.enabled, dragging: dragIdx === idx, 'drag-over': dragOverIdx === idx }"
draggable="true" @dragstart="onDragStart(idx)" @dragover.prevent="onDragOver(idx)"
@drop.prevent="onDrop(idx)" @dragend="onDragEnd">
<td class="ta-c dim">
<i class="grip" title="拖动排序">⠿</i>{{ idx + 1 }}
</td>
<td :title="d.name">{{ d.name }}</td>
<td>
<Tooltip v-if="Number(d.memberCount || 0)" placement="top" max-width="360"
:content="memberTip(d)">
<span class="mem-cell" @click="openMembers(d)">{{ memberText(d) }}</span>
</Tooltip>
<span v-else class="dim">无成员</span>
</td>
<td class="ta-c">
<i class="sw" :class="{ 'is-on': !!d.enabled }"
:title="d.enabled ? '停用' : '启用'" @click="onToggleDept(d)"></i>
</td>
<td class="ta-r">
<a class="lnk" @click="openDeptEdit(d)">编辑</a>
<a class="lnk" @click="openMembers(d)">成员</a>
<a class="lnk danger" @click="removeDept(d)">删除</a>
</td>
</tr>
</tbody>
</table>
<Table v-else class="dept-itable" border :columns="deptColumns" :data="departments"
:row-class-name="rowClassName" no-data-text="该分类下暂无部门">
<template #members="{ row }">
<Tooltip v-if="Number(row.memberCount || 0)" 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.enabled ? 'success' : 'error'" class="sta-tag"
:title="row.enabled ? '点击停用' : '点击启用'" @click="onToggleDept(row)">
{{ row.enabled ? '已启用' : '已停用' }}
</Tag>
</template>
<template #actions="{ row }">
<a class="lnk" @click="openDeptEdit(row)">编辑</a>
<a class="lnk" @click="openMembers(row)">成员</a>
<a class="lnk danger" @click="removeDept(row)">删除</a>
</template>
</Table>
<div v-if="departments.length" class="table-foot">共 {{ departments.length }} 个部门</div>
</div>
</div>
<!-- 排序弹框:拖动左侧手柄调整顺序,保存后按新顺序落库 -->
<Modal v-model="sortVisible" :title="`部门排序 · ${activeCategoryName}`" width="520" :mask-closable="false">
<Alert type="info" show-icon class="sort-tip">拖动左侧手柄调整顺序,保存后生效。</Alert>
<ul class="sort-list">
<li v-for="(d, idx) in sortList" :key="d.id" class="sort-item"
:class="{ dragging: sortDragIdx === idx, 'drag-over': sortOverIdx === idx }" draggable="true"
@dragstart="onSortDragStart(idx)" @dragover.prevent="onSortOver(idx)" @drop.prevent="onSortDrop(idx)"
@dragend="onSortDragEnd">
<i class="grip">⠿</i>
<span class="sno">{{ idx + 1 }}</span>
<span class="snm" :title="d.name">{{ d.name }}</span>
</li>
</ul>
<template #footer>
<Button @click="sortVisible = false">取消</Button>
<Button type="primary" :loading="sortSaving" @click="saveSort">保存排序</Button>
</template>
</Modal>
<!-- 名称录入(分类新增/改名,部门新增/改名共用) -->
<Modal v-model="nameModal.visible" :title="nameModal.title" :mask-closable="false" width="420">
<Form :label-width="72">
@@ -140,7 +135,8 @@ import CustomMemberModal from "./CustomMemberModal.vue";
* CUSTOM 自定义部门管理面板(部门管理页的第二个来源页签)。
* 口径:分类(平铺列表,容器)→ 部门(可启停 / 可配成员 / 软删)→ 成员(本校教职工,服务端排除学生组)。
* 学校上下文由父级传入,和 SYSTEM 页签共用,切换来源不改学校。
* 2026-10-23:部门改为表格 + 行拖动排序(沿用 customDeptSort),分类也支持拖动(沿用 customDeptCategorySort)。
* 2026-10-23:部门改为 iView Table;排序移入「排序管理」弹框拖动(沿用 customDeptSort),
* 分类仍是列表内拖动(沿用 customDeptCategorySort)。
*/
const props = defineProps<{ schoolId?: number | null }>();
@@ -155,6 +151,18 @@ const loadingDepartments = ref(false);
const activeCategory = computed(() => categories.value.find((c) => c.id === activeCategoryId.value) || null);
const activeCategoryName = computed(() => activeCategory.value?.name || "");
/** iView Table 列定义(排序不在表格里做,行不拖拽) */
const deptColumns: any[] = [
{ title: "序号", type: "index", width: 80, align: "center" },
{ title: "部门名称", key: "name", minWidth: 220 },
{ title: "人员", slot: "members", minWidth: 300 },
{ title: "状态", slot: "status", width: 120, align: "center" },
{ title: "操作", slot: "actions", width: 160, align: "right" },
];
function rowClassName(row: any) {
return row?.enabled ? "" : "is-off";
}
const nameModal = reactive({
visible: false,
saving: false,
@@ -272,6 +280,69 @@ watch(() => props.schoolId, () => {
watch(activeCategoryId, () => loadDepartments());
/* ================= 部门排序(弹框拖动) ================= */
const sortVisible = ref(false);
const sortSaving = ref(false);
const sortList = ref<CustomDepartmentVo[]>([]);
const sortDragIdx = ref<number | null>(null);
const sortOverIdx = ref<number | null>(null);
function openSort() {
if (!departments.value.length) return;
sortList.value = [...departments.value];
sortDragIdx.value = null;
sortOverIdx.value = null;
sortVisible.value = true;
}
function onSortDragStart(idx: number) {
sortDragIdx.value = idx;
}
function onSortOver(idx: number) {
if (sortDragIdx.value === null) return;
sortOverIdx.value = idx;
if (sortDragIdx.value === idx) return;
// 拖动过程中实时重排,松手即落当前顺序
const ordered = [...sortList.value];
const [row] = ordered.splice(sortDragIdx.value, 1);
ordered.splice(idx, 0, row);
sortList.value = ordered;
sortDragIdx.value = idx;
}
function onSortDrop() {
sortDragIdx.value = null;
}
function onSortDragEnd() {
sortDragIdx.value = null;
sortOverIdx.value = null;
}
async function saveSort() {
if (!sortList.value.length) return;
sortSaving.value = true;
try {
await Apis.CustomDept.customDeptSort({
data: {
schoolId: props.schoolId as number,
categoryId: activeCategoryId.value as number,
ids: sortList.value.map((d) => d.id as number),
},
cacheFor: null,
});
// 右表直接采用弹框里的顺序,避免整表重取导致姓名预览再拉一轮
departments.value = [...sortList.value];
sortVisible.value = false;
Message.success("排序已保存");
} catch (e: any) {
console.warn(e?.message || "部门排序保存失败");
await loadDepartments();
} finally {
sortSaving.value = false;
}
}
/* ================= 分类拖动排序 ================= */
const catDragIdx = ref<number | null>(null);
@@ -484,52 +555,6 @@ async function doRemoveDept(d: CustomDepartmentVo) {
}
}
/* ================= 部门拖动排序 ================= */
const dragIdx = ref<number | null>(null);
const dragOverIdx = ref<number | null>(null);
function onDragStart(idx: number) {
dragIdx.value = idx;
}
function onDragOver(idx: number) {
if (dragIdx.value === null) return;
dragOverIdx.value = idx;
}
function onDrop(idx: number) {
if (dragIdx.value === null || dragIdx.value === idx) {
onDragEnd();
return;
}
const ordered = [...departments.value];
const [row] = ordered.splice(dragIdx.value, 1);
ordered.splice(idx, 0, row);
departments.value = ordered;
onDragEnd();
persistDeptOrder();
}
function onDragEnd() {
dragIdx.value = null;
dragOverIdx.value = null;
}
/** 拖完一次性提交整表顺序(服务端按传入 ids 落 sort_no) */
async function persistDeptOrder() {
try {
await Apis.CustomDept.customDeptSort({
data: {
schoolId: props.schoolId as number,
categoryId: activeCategoryId.value as number,
ids: departments.value.map((d) => d.id as number),
},
cacheFor: null,
});
} catch (e: any) {
console.warn(e?.message || "部门排序保存失败");
await loadDepartments();
}
}
function openMembers(d: CustomDepartmentVo) {
memberModal.dept = d;
memberModal.visible = true;
@@ -675,7 +700,7 @@ async function refreshAfterMemberChange() {
}
.cat-item:hover .grip,
.dept-table tbody tr:hover .grip {
.sort-item:hover .grip {
color: #8b96a8;
}
@@ -714,138 +739,62 @@ async function refreshAfterMemberChange() {
overflow: auto;
}
.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;
.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 {
cursor: grab;
&:active {
cursor: grabbing;
}
}
tbody tr:hover td {
background: #fafbfe;
}
tbody tr.is-off td {
:deep(.ivu-table-row.is-off td) {
background: #fcfcfd;
color: var(--text-3, #98a1b2);
}
tbody tr.dragging {
opacity: .45;
:deep(.ivu-table-row.is-off:hover td) {
background: #f8f9fb;
}
}
.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;
}
.sta-tag {
cursor: pointer;
}
.sta-tag:hover {
opacity: .82;
}
.lnk {
margin-left: 10px;
color: var(--brand, #577afb);
cursor: pointer;
font-size: 12px;
&:first-child {
margin-left: 0;
}
tbody tr.drag-over td {
border-top-color: var(--brand, #577afb);
box-shadow: inset 0 2px 0 var(--brand, #577afb);
&:hover {
text-decoration: underline;
}
.ta-c {
text-align: center;
}
.ta-r {
text-align: right;
}
.dim {
color: var(--text-3, #98a1b2);
font-size: 11.5px;
}
.grip {
margin-right: 6px;
}
.mem-cell {
cursor: pointer;
border-bottom: 1px dashed transparent;
&:hover {
color: var(--brand, #577afb);
border-bottom-color: #c9d4f7;
}
}
.lnk {
margin-left: 10px;
color: var(--brand, #577afb);
cursor: pointer;
font-size: 12px;
&:first-child {
margin-left: 0;
}
&:hover {
text-decoration: underline;
}
&.danger {
color: #ed4014;
}
}
/* 胶囊开关(与系统部门表格同款比例) */
.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;
}
}
&.danger {
color: #ed4014;
}
}
@@ -855,4 +804,66 @@ async function refreshAfterMemberChange() {
font-size: 12px;
color: var(--text-3, #98a1b2);
}
/* ================= 排序弹框 ================= */
.sort-tip {
margin-bottom: 10px;
}
.sort-list {
margin: 0;
padding: 0;
list-style: none;
max-height: 52vh;
overflow: auto;
}
.sort-item {
display: flex;
align-items: center;
gap: 10px;
height: 44px;
padding: 0 12px;
margin-bottom: 6px;
border: 1px solid var(--line, #e9edf5);
border-radius: 6px;
background: #fff;
cursor: grab;
transition: border-color .15s, box-shadow .15s;
&:active {
cursor: grabbing;
}
&.dragging {
opacity: .5;
}
&.drag-over {
border-color: var(--brand, #577afb);
box-shadow: 0 0 0 2px rgba(87, 122, 251, .16);
}
}
.sort-item .sno {
flex: 0 0 auto;
width: 20px;
height: 20px;
border-radius: 50%;
background: var(--brand, #577afb);
color: #fff;
font-size: 11px;
line-height: 20px;
text-align: center;
}
.sort-item .snm {
flex: 1;
min-width: 0;
font-size: 12.5px;
color: var(--text, #1e2635);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
</style>
+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;