feat(theme): 接入云海子主题通道(A 类免插件,规范 v1.12 §2/§3)

本次同时包含会话前遗留的本地未提交改动(作业模块 M5 相关页面/接口/样式调整、
host.config 的 SSO 兜底推导、.gitignore 等),按团队确认一并提交。

主题通道部分:
- index.html 插 A 类 loader:唯一接入点,FOUC 门/manifest 预取/主题 link 追加与排序
  全由通道 yh-theme-init.js 承担,主题升级走 /static 通道,本应用无需重新构建
- vite.config 加 /static dev 代理(YH_THEME_PROXY_TARGET 可覆盖,默认线上通道)
- host.config 回写 __APP_CONFIG__.serviceHost(仅同源):init 的 appearance 请求有硬性
  同源限制,跨源会被直接跳过;本地 dev 保持空串走同源 /api 代理
- 顶栏头像接平台键 yh-avatar,监听 yh-appearance-updated,userInfo 兜底
- view-ui-plus 声明锁 1.3.24(对齐通道皮肤生成版本)
- .gitea/workflows/build.yml:CI 平台统一更新(构建容器名唯一化 + npm 走 npmmirror)
This commit is contained in:
lijing
2026-10-21 17:38:11 +08:00
parent 70bd3a247f
commit bb7a832290
32 changed files with 2294 additions and 740 deletions
+317 -98
View File
@@ -1,30 +1,78 @@
<script setup lang="ts">
import { ref, reactive, computed, watch, h } from "vue";
import { ref, reactive, computed, watch, onMounted, onBeforeUnmount, nextTick } from "vue";
import { useRouter } from "vue-router";
import { useRequest } from "alova/client";
import { Message } from "view-ui-plus";
import { teacherApi } from "@/api/module/teacherHomework";
import type { HomeworkV2Vo, TeacherHomeworkQuery } from "@/types/homeworkV2";
import type { BasePageResult } from "@/types/homeworkV2";
import type { BasePageResult, SubjectVo } from "@/types/homeworkV2";
import MyConfirm from "@/components/MyConfirm";
import Utils from "@/utils";
const router = useRouter();
/* 学科下拉绑定 /subjects/assigned(布置过 ∪ 任教,subId=筛选值与列表 subject 精确匹配);
* 接口失败或为空时降级为内置清单,保证筛选始终可用 */
const SUBJECTS = ["语文", "数学", "英语", "物理", "化学", "生物", "道德与法治", "历史", "地理", "科学", "综合实践"];
const subjectOpts = ref<SubjectVo[]>(SUBJECTS.map((s) => ({ subId: s, subName: s })));
async function loadSubjects() {
try {
const list = await teacherApi.assignedSubjects().send();
if (Array.isArray(list) && list.length) subjectOpts.value = list;
} catch {
/* 降级用内置清单 */
}
}
loadSubjects();
/* ============ 顶级视图:已发布 / 草稿箱 ============ */
/* ============ 视图:单行 Tab(已发布/进行中/已截止/已归档 + 草稿箱,草稿箱用分隔线隔开,带数量) ============ */
type ViewMode = "published" | "drafts";
const viewMode = ref<ViewMode>("published");
/* 已发布状态筛选(空 = 全部) */
const PUB_TABS = [
{ name: "", label: "全部" },
/* 已发布状态筛选(空 = 已发布全部状态) */
const pubStatus = ref("");
const TAB_DEFS = [
{ name: "", label: "已发布" },
{ name: "PUBLISHED", label: "进行中" },
{ name: "CLOSED", label: "已截止" },
{ name: "ARCHIVED", label: "已归档" },
{ name: "drafts", label: "草稿箱" },
];
const pubStatus = ref("");
/* Tab 数量(接口无汇总端点,用 pageSize=1 的分页 total 汇出;随关键字/学科筛选刷新) */
const tabCounts = ref<Record<string, number>>({});
async function loadCounts() {
const base = { keyword: keyword.value || undefined, subject: subject.value || undefined, pageIndex: 1, pageSize: 1 };
try {
const [pub, closed, arch, draft] = await Promise.all([
teacherApi.published({ ...base, hwStatus: "PUBLISHED" }).send(),
teacherApi.published({ ...base, hwStatus: "CLOSED" }).send(),
teacherApi.published({ ...base, hwStatus: "ARCHIVED" }).send(),
teacherApi.drafts(base).send(),
]);
tabCounts.value = {
"": pub.total + closed.total + arch.total,
PUBLISHED: pub.total,
CLOSED: closed.total,
ARCHIVED: arch.total,
drafts: draft.total,
};
} catch {
/* 数量加载失败不影响主列表 */
}
}
/* Tab 标签用 render:文字 + 计数徽标(数量未加载时只显示文字) */
const PUB_TABS = computed(() =>
TAB_DEFS.map((t) => ({
name: t.name,
label: (h: any) =>
h("span", { class: "hw-tab" }, [
h("span", { class: "hw-tab-text" }, t.label),
tabCounts.value[t.name] === undefined
? null
: h("span", { class: "hw-tab-count" }, String(tabCounts.value[t.name])),
]),
}))
);
const tabValue = computed(() => (viewMode.value === "drafts" ? "drafts" : pubStatus.value));
/* ============ 搜索条件 ============ */
const keyword = ref("");
@@ -47,6 +95,7 @@ async function run() {
pageSize: pageSize.value,
};
if (viewMode.value === "published" && pubStatus.value) params.hwStatus = pubStatus.value;
loadCounts();
try {
await reload(params, viewMode.value);
} catch (e: any) {
@@ -57,13 +106,13 @@ function doSearch() {
pageNum.value = 1;
run();
}
function switchView(name: string) {
viewMode.value = name as ViewMode;
pageNum.value = 1;
run();
}
function onPubTab(name: string) {
pubStatus.value = name;
function onTab(name: string) {
if (name === "drafts") {
viewMode.value = "drafts";
} else {
viewMode.value = "published";
pubStatus.value = name;
}
doSearch();
}
function pageUpdate(p: number) {
@@ -90,17 +139,33 @@ watch(
}
);
/* ============ 状态色 ============ */
const STATUS_COLOR: Record<string, string> = {
DRAFT_UNPUBLISHED: "default",
DRAFT_SCHEDULED: "blue",
PUBLISHED: "primary",
CLOSED: "warning",
ARCHIVED: "success",
};
/* ============ 状态列 & 操作列(与「全部作业」共用 Utils 里那一套口径) ============ */
/**
* 状态:统一走 Utils.hwStatusPill(状态图标 + 文字 + 同色浅底)。
* 之前这里用 iview Tag、全部作业用圆点,同一个模块两套观感;现在两边同一张状态表,
* 颜色/图标/文案都只在 utils/index.ts 的 HW_STATUS_META 里定义一次。
*/
function statusCell(h: any, code: string | undefined, text?: string) {
return Utils.hwStatusPill(h, code, text);
}
function fmtTime(v?: string | null): string {
return v ? String(v).slice(0, 16) : "—";
}
/** 短字段单元格:强制单行(时间、状态这类短文本折行很难看,且会顶破 48px 行高) */
function nowrapCell(h: any, text: string, style = "") {
return h("span", { style: `white-space:nowrap;${style}` }, text);
}
/** 长字段单元格:单行 + 省略号,悬停由原生 title 看全文(与宿舍通知「接收宿舍」同一手法) */
function ellipsisCell(h: any, text: string) {
return h(
"span",
{
title: text,
style: "display:inline-block;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;vertical-align:middle;",
},
text
);
}
/* ============ 行操作 ============ */
function viewDetail(row: HomeworkV2Vo) {
@@ -112,6 +177,11 @@ function viewSubmissions(row: HomeworkV2Vo) {
function toGrade(row: HomeworkV2Vo) {
router.push({ path: "/grade", query: { serid: String(row.id) } });
}
/** 勾选了任一提交渠道(拍照/图片、上传附件、文字作答)=教师需批改 → 显示批阅入口;
* 仅查阅(none)=学生只预览,不显示批阅 */
function needGrade(row: HomeworkV2Vo): boolean {
return (row.submitType || []).some((t) => t !== "none");
}
function edit(row: HomeworkV2Vo) {
router.push({ path: "/list/new", query: { serid: String(row.id) } });
}
@@ -211,7 +281,10 @@ function publishDraft(row: HomeworkV2Vo) {
try {
await teacherApi.publish(row.id).send();
Message.success("发布成功");
switchView("published");
viewMode.value = "published";
pubStatus.value = "";
pageNum.value = 1;
run();
} catch (e: any) {
Message.error(e.message || "发布失败");
}
@@ -253,48 +326,68 @@ function del(row: HomeworkV2Vo) {
}
/* ============ 已发布列表列 ============ */
/**
* 列宽口径(对齐宿舍通知 NoticeList):
* - 序号列 ≥ 76:iview 单元格左右各 18px padding,「序号」两字约 36px,给 60 会被挤成「序/号」两行;
* - 「未读未交」「待批阅」「提交率」这类 3~4 字表头各自留足一字不折行的宽度;
* - 时间列 150 + nowrap:130 会把「2026-09-15 10:00」折成两行;
* - 班级单行省略,悬停由原生 title 看全文;
* - 列宽合计 ~1700px,宽屏也保留横向滚动(滚动条 8px 圆角的样式见 App.vue 的
* .ivu-table-overflowX 覆写)。**不要再为了「收进一屏」去压列宽或合并列** ——
* 把列压窄只会让表头折行,越改越难看。
*/
const pubColumns = computed(() => [
{ title: "序号", type: "index", width: 60, align: "center" },
{ title: "序号", type: "index", width: 76, align: "center" },
{
title: "标题",
key: "title",
minWidth: 200,
minWidth: 300,
align: "left",
tooltip: true,
/* 标题是长自由文本,列宽再大也会超 —— 单行 + 省略号,悬停用原生 title 看全文
* (宿舍通知 NoticeList 的 titleCell 就是「蓝色链接 + title 属性」,这里补上省略号,
* 否则长标题会折成两行、和「全部作业」页的单行样式不一致) */
render: (h: any, p: any) =>
h("a", { style: "color:#2d8cf0;", onClick: () => viewDetail(p.row) }, () => p.row.title),
h(
"a",
{
style: "display:inline-block;max-width:100%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;vertical-align:middle;color:#2d8cf0;",
title: p.row.title,
onClick: () => viewDetail(p.row),
},
() => p.row.title
),
},
{ title: "学科", key: "subject", width: 80, align: "center" },
{ title: "学科", key: "subjectName", width: 120, align: "center" },
{
title: "班级",
width: 130,
width: 190,
align: "left",
tooltip: true,
render: (h: any, p: any) => h("span", {}, (p.row.classes || []).map((c: any) => c.className).join("、") || "—"),
render: (h: any, p: any) => ellipsisCell(h, (p.row.classes || []).map((c: any) => c.className).join("、") || "—"),
},
{ title: "发布时间", width: 130, align: "left", render: (h: any, p: any) => h("span", {}, fmtTime(p.row.publishedAt)) },
{ title: "截止时间", width: 130, align: "left", render: (h: any, p: any) => h("span", {}, fmtTime(p.row.deadlineAt)) },
{ title: "发布时间", width: 170, align: "left", render: (h: any, p: any) => nowrapCell(h, fmtTime(p.row.publishedAt)) },
{ title: "截止时间", width: 170, align: "left", render: (h: any, p: any) => nowrapCell(h, fmtTime(p.row.deadlineAt)) },
{
title: "应交",
width: 65,
width: 70,
align: "center",
render: (h: any, p: any) => h("span", {}, String(p.row.progress?.should ?? "—")),
},
{
title: "已读",
width: 65,
width: 70,
align: "center",
render: (h: any, p: any) => h("span", {}, String(p.row.progress?.read ?? "—")),
},
{
title: "已交",
width: 65,
width: 70,
align: "center",
render: (h: any, p: any) => h("span", {}, String(p.row.progress?.submitted ?? "—")),
},
{
title: "未读未交",
width: 85,
width: 100,
align: "center",
render: (h: any, p: any) => {
const g = p.row.progress;
@@ -304,7 +397,7 @@ const pubColumns = computed(() => [
},
{
title: "提交率",
width: 80,
width: 90,
align: "center",
render: (h: any, p: any) => {
const g = p.row.progress;
@@ -314,7 +407,7 @@ const pubColumns = computed(() => [
},
{
title: "待批阅",
width: 75,
width: 95,
align: "center",
render: (h: any, p: any) => {
const g = p.row.progress;
@@ -324,14 +417,16 @@ const pubColumns = computed(() => [
},
{
title: "状态",
width: 90,
width: 120,
align: "center",
render: (h: any, p: any) => h("Tag", { color: STATUS_COLOR[p.row.hwStatus] || "default" }, () => p.row.hwStatusDesc || p.row.hwStatus),
render: (h: any, p: any) => statusCell(h, p.row.hwStatus, p.row.hwStatusDesc),
},
{
title: "操作",
slot: "action",
width: 230,
/* 3 个图标按钮:32px + tooltip 左右各 2px = 36px → 108px,再留单元格左右 padding
各 18px(36px) 与少量余量 → 170 恰好一行;给 150 时最后一个会被挤到第二行 */
width: 170,
align: "center",
fixed: "right",
},
@@ -349,62 +444,92 @@ function onPubMore(name: string, row: HomeworkV2Vo) {
/* ============ 草稿箱列 ============ */
const draftColumns = computed(() => [
{ title: "序号", type: "index", width: 60, align: "center" },
{ title: "标题", key: "title", minWidth: 220, align: "left", tooltip: true },
{ title: "学科", key: "subject", width: 90, align: "center" },
{ title: "序号", type: "index", width: 76, align: "center" },
{ title: "标题", key: "title", minWidth: 300, align: "left", tooltip: true },
{ title: "学科", key: "subjectName", width: 120, align: "center" },
{
title: "班级",
width: 150,
width: 190,
align: "left",
tooltip: true,
render: (h: any, p: any) => h("span", {}, (p.row.classes || []).map((c: any) => c.className).join("、") || "—"),
render: (h: any, p: any) => ellipsisCell(h, (p.row.classes || []).map((c: any) => c.className).join("、") || "—"),
},
{ title: "更新时间", width: 140, align: "left", render: (h: any, p: any) => h("span", {}, fmtTime(p.row.updateTime)) },
{ title: "截止时间", width: 140, align: "left", render: (h: any, p: any) => h("span", {}, fmtTime(p.row.deadlineAt)) },
{ title: "更新时间", width: 170, align: "left", render: (h: any, p: any) => nowrapCell(h, fmtTime(p.row.updateTime)) },
{ title: "截止时间", width: 170, align: "left", render: (h: any, p: any) => nowrapCell(h, fmtTime(p.row.deadlineAt)) },
{
title: "状态",
width: 110,
/* 「定时 2026-09-18 08:00」+ 状态图标 + 胶囊内边距实测 185px,单元格左右 padding 各 18px
→ 列宽给 230 才有余量(220 实测只剩 0~1px,改字号/字体就会截掉一截) */
width: 230,
align: "center",
render: (h: any, p: any) => {
const row = p.row as HomeworkV2Vo;
const st = row.hwStatus === "DRAFT_SCHEDULED" ? `定时 ${fmtTime(row.scheduledAt)}` : "未发布";
return h("Tag", { color: STATUS_COLOR[row.hwStatus] || "default" }, () => st);
const scheduled = row.hwStatus === "DRAFT_SCHEDULED";
return statusCell(h, row.hwStatus, scheduled ? `定时 ${fmtTime(row.scheduledAt)}` : undefined);
},
},
{
title: "操作",
slot: "draftAction",
width: 250,
/* 最多 4 个图标按钮(编辑/发布/取消定时/删除):4 × 36 + padding 36 + 余量 = 200 */
width: 200,
align: "center",
fixed: "right",
},
]);
const columns = computed(() => (viewMode.value === "drafts" ? draftColumns.value : pubColumns.value));
const tableHeight = computed(() => Utils.useTableHeight({ isSearch: true, isOperat: true, isPage: true }));
/* ============ 表格高度:量真实 DOM,不用常量估算 ============ */
/** 表格外层容器(量它的顶边位置) */
const tableWrap = ref<HTMLElement | null>(null);
const tableHeight = ref(400);
/**
* 表格高度 = 分页条顶边 − 表格顶边 − 12px 呼吸位(列宽超出容器时再扣掉横向滚动条约 15px)。
*
* 原先用 `Utils.useTableHeight({isSearch,isOperat,isPage})` 按 body 高度减固定值估算:
* 它既不认本页把状态 Tab 嵌在搜索行里的实际高度,也不认容器内边距,算出的值与真实可用
* 空间对不齐;而 `.page-panel` 是 absolute 吸底定位,表格一旦比可用空间高就压到分页上,
* 横向滚动条也没给它留位置(最后一行会被滚动条切掉)。
* 这里直接量 DOM —— 加减筛选条件、切 Tab、改字号都不会再失配;窗口 resize 时重算。
*/
function calcTableHeight() {
const wrap = tableWrap.value;
if (!wrap) return;
const rect = wrap.getBoundingClientRect();
const panel = wrap.parentElement?.querySelector(".page-panel") as HTMLElement | null;
const limit = panel ? panel.getBoundingClientRect().top : window.innerHeight;
// 列宽合计超过容器宽度 → 会出现横向滚动条,它会吃掉表体 ~15px 高度,一并预留
const colsWidth = (columns.value as any[]).reduce((s, c) => s + (c.width || c.minWidth || 100), 0);
const hBar = colsWidth > wrap.clientWidth ? 15 : 0;
tableHeight.value = Math.max(240, Math.floor(limit - rect.top - 12 - hBar));
}
onMounted(() => {
nextTick(calcTableHeight);
window.addEventListener("resize", calcTableHeight);
});
onBeforeUnmount(() => window.removeEventListener("resize", calcTableHeight));
/* 已发布 / 草稿箱两套列宽不同 → 横向滚动条的有无会变,切 Tab 后重算 */
watch(columns, () => nextTick(calcTableHeight));
</script>
<template>
<div class="page-wrapper">
<div class="main-container">
<!-- 1. 操作 + 视图切换 -->
<!-- 1. 操作 -->
<div class="fix mb-12">
<Button class="fl" type="primary" size="large" @click="router.push('/list/new')">布置作业</Button>
</div>
<Tabs :value="viewMode" @on-click="switchView" class="mb-12">
<TabPane name="published" label="已发布"></TabPane>
<TabPane name="drafts" label="草稿箱"></TabPane>
</Tabs>
<!-- 2. 已发布状态筛选 + 搜索区 -->
<!-- 2. 状态筛选(单行 Tab) + 搜索区 -->
<div class="search-wrap fix">
<div v-if="viewMode === 'published'" class="fl">
<Tabs :value="pubStatus" @on-click="onPubTab" size="small" class="inner-tabs">
<div class="fl">
<Tabs :value="tabValue" @on-click="onTab" size="small" class="inner-tabs">
<TabPane v-for="t in PUB_TABS" :key="t.name" :name="t.name" :label="t.label"></TabPane>
</Tabs>
</div>
<Select v-model="subject" class="search-wrap_sel ml-12 fl" clearable placeholder="学科" style="width: 120px" @on-change="doSearch">
<Option v-for="s in SUBJECTS" :key="s" :value="s">{{ s }}</Option>
<Option v-for="s in subjectOpts" :key="s.subId" :value="s.subId">{{ s.subName }}</Option>
</Select>
<Input
class="fr"
@@ -418,41 +543,59 @@ const tableHeight = computed(() => Utils.useTableHeight({ isSearch: true, isOper
/>
</div>
<!-- 3. 表格 + 分页 -->
<Table
:columns="columns"
:data="page.content"
class="link-table table-bottom-box"
:height="tableHeight"
:loading="loading"
no-data-text="暂无数据"
>
<!-- 已发布行操作 -->
<template #action="{ row }">
<Button type="text" size="small" @click="viewSubmissions(row)">提交情况</Button>
<Button v-if="(row.progress?.pendingGrade ?? 0) > 0" type="text" size="small" @click="toGrade(row)">批阅</Button>
<Dropdown transfer @on-click="(name: string) => onPubMore(name, row)">
<Button type="text" size="small">更多∨</Button>
<template #list>
<DropdownMenu>
<DropdownItem v-if="row.hwStatus === 'PUBLISHED' && (row.progress?.unread ?? 0) > 0" name="notRead">提醒未读</DropdownItem>
<DropdownItem v-if="row.hwStatus === 'PUBLISHED' && (row.progress?.unsubmitted ?? 0) > 0" name="notSubmitted">催交</DropdownItem>
<DropdownItem v-if="row.hwStatus === 'PUBLISHED'" name="extend">延长截止</DropdownItem>
<DropdownItem name="copy">复制</DropdownItem>
<DropdownItem v-if="row.hwStatus === 'PUBLISHED' || row.hwStatus === 'CLOSED'" name="archive">归档</DropdownItem>
<DropdownItem name="export">导出报表</DropdownItem>
</DropdownMenu>
</template>
</Dropdown>
</template>
<!-- 草稿箱行操作 -->
<template #draftAction="{ row }">
<Button type="text" size="small" @click="edit(row)">编辑</Button>
<Button type="text" size="small" @click="publishDraft(row)">发布</Button>
<Button v-if="row.hwStatus === 'DRAFT_SCHEDULED'" type="text" size="small" @click="cancelSchedule(row)">取消定时</Button>
<Button type="text" size="small" @click="del(row)">删除</Button>
</template>
<!-- 3. 表格 + 分页(tableWrap 供 calcTableHeight 量真实可用高度) -->
<div ref="tableWrap">
<Table
:columns="columns"
:data="page.content"
class="link-table table-bottom-box"
:height="tableHeight"
:loading="loading"
no-data-text="暂无数据"
>
<!-- 已发布行操作:图标按钮 + 悬浮说明
(提交情况=icon-chakan、批阅=icon-shenpi,与「全部作业」「提交情况」页同一套语义;
其余低频操作仍收在「更多」里,不丢功能) -->
<template #action="{ row }">
<Tooltip content="提交情况" placement="bottom" transfer>
<div class="operating-btn iconfont icon-chakan" @click="viewSubmissions(row)"></div>
</Tooltip>
<Tooltip v-if="needGrade(row)" content="批阅" placement="bottom" transfer>
<div class="operating-btn iconfont icon-shenpi" @click="toGrade(row)"></div>
</Tooltip>
<Dropdown transfer @on-click="(name: string) => onPubMore(name, row)">
<!-- 触发器不再包 Tooltip:Dropdown 默认 hover 展开,悬停时两个气泡会打架,
这里用原生 title 兜底说明 -->
<div class="operating-btn operating-btn-more" title="更多"><Icon type="ios-more" size="20" /></div>
<template #list>
<DropdownMenu>
<DropdownItem v-if="row.hwStatus === 'PUBLISHED' && (row.progress?.unread ?? 0) > 0" name="notRead">提醒未读</DropdownItem>
<DropdownItem v-if="row.hwStatus === 'PUBLISHED' && (row.progress?.unsubmitted ?? 0) > 0" name="notSubmitted">催交</DropdownItem>
<DropdownItem v-if="row.hwStatus === 'PUBLISHED'" name="extend">延长截止</DropdownItem>
<DropdownItem name="copy">复制</DropdownItem>
<DropdownItem v-if="row.hwStatus === 'PUBLISHED' || row.hwStatus === 'CLOSED'" name="archive">归档</DropdownItem>
<DropdownItem name="export">导出报表</DropdownItem>
</DropdownMenu>
</template>
</Dropdown>
</template>
<!-- 草稿箱行操作:同一套图标口径(编辑/发布/取消定时/删除) -->
<template #draftAction="{ row }">
<Tooltip content="编辑" placement="bottom" transfer>
<div class="operating-btn iconfont icon-edit" @click="edit(row)"></div>
</Tooltip>
<Tooltip content="发布" placement="bottom" transfer>
<div class="operating-btn iconfont icon-fasong" @click="publishDraft(row)"></div>
</Tooltip>
<Tooltip v-if="row.hwStatus === 'DRAFT_SCHEDULED'" content="取消定时" placement="bottom" transfer>
<div class="operating-btn iconfont icon-stop" @click="cancelSchedule(row)"></div>
</Tooltip>
<Tooltip content="删除" placement="bottom" transfer>
<div class="operating-btn iconfont icon-ashbin" @click="del(row)"></div>
</Tooltip>
</template>
</Table>
</div>
<div class="page-panel">
<div class="fl ml-15 align-middle"></div>
<Page
@@ -502,6 +645,73 @@ const tableHeight = computed(() => Utils.useTableHeight({ isSearch: true, isOper
:deep(.ivu-tabs-bar) {
margin-bottom: 0;
}
/* ink bar 由组件 JS 按前序 tab 宽度累计定位,自定义 margin 间距会使其偏移,
* 故隐藏之,改用激活项伪元素画下划线 */
:deep(.ivu-tabs-ink-bar) {
display: none;
}
:deep(.ivu-tabs-tab) {
position: relative;
margin-right: 28px;
padding: 10px 2px;
transition: color 0.2s ease;
}
/* 激活下划线:加粗 + 圆角,随 tab 盒子对齐 */
:deep(.ivu-tabs-tab-active)::after,
:deep(.ivu-tabs-tab-active:hover)::after {
content: "";
position: absolute;
left: 0;
right: 0;
bottom: 0;
height: 3px;
border-radius: 3px 3px 0 0;
background: #2d8cf0;
}
/* 标签文字 + 计数徽标 */
:deep(.hw-tab) {
display: inline-flex;
align-items: center;
.hw-tab-text {
font-size: 14px;
}
.hw-tab-count {
min-width: 16px;
margin-left: 6px;
padding: 0 6px;
font-size: 12px;
line-height: 16px;
text-align: center;
border-radius: 8px;
background: #f2f3f5;
color: #808695;
transition: all 0.2s ease;
}
}
:deep(.ivu-tabs-tab:hover .hw-tab-count) {
background: #e8f3ff;
color: #2d8cf0;
}
:deep(.ivu-tabs-tab-active .hw-tab-count),
:deep(.ivu-tabs-tab-active:hover .hw-tab-count) {
background: #2d8cf0;
color: #fff;
}
/* 末位(草稿箱)与状态组之间用竖线隔开 */
:deep(.ivu-tabs-tab:last-child) {
margin-right: 0;
margin-left: 26px;
&::before {
content: "";
position: absolute;
left: -14px;
top: 50%;
transform: translateY(-50%);
width: 1px;
height: 16px;
background: #dcdee2;
}
}
}
}
.extend-tip {
@@ -509,4 +719,13 @@ const tableHeight = computed(() => Utils.useTableHeight({ isSearch: true, isOper
font-size: 12px;
margin-top: 4px;
}
/* 操作列图标按钮组:Tooltip / Dropdown 两种容器都是 inline-block,
默认 baseline 对齐会让「更多」比另两个偏低 2~3px,统一按中线对齐 */
:deep(.ivu-table-cell) {
.ivu-tooltip,
.ivu-dropdown {
vertical-align: middle;
}
}
</style>