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:
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user