本次同时包含会话前遗留的本地未提交改动(作业模块 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)
560 lines
20 KiB
Vue
560 lines
20 KiB
Vue
<script setup lang="ts">
|
||
import { ref, reactive, computed, onMounted, watch } from "vue";
|
||
import { useRoute, useRouter } from "vue-router";
|
||
import { Message } from "view-ui-plus";
|
||
import { studentApi } from "@/api/module/studentHomework";
|
||
import type { BasePageResult, StudentHomeworkV2Vo, StudentHomeworkDetailV2Vo, AttachmentItem } from "@/types/homeworkV2";
|
||
import AttachmentUpload from "@/components/AttachmentUpload.vue";
|
||
import MyConfirm from "@/components/MyConfirm";
|
||
|
||
const route = useRoute();
|
||
const router = useRouter();
|
||
|
||
/* V2 tab:all/todo/done/closed(redo 桶已并入 todo,RETURNED 红色强化) */
|
||
const TABS = [
|
||
{ name: "all", label: "全部" },
|
||
{ name: "todo", label: "待完成" },
|
||
{ name: "done", label: "已完成" },
|
||
{ name: "closed", label: "已截止" },
|
||
];
|
||
const tab = ref<string>((route.meta as any).initTab || "all");
|
||
const keyword = ref("");
|
||
const pageNum = ref(1);
|
||
const pageSize = ref(10);
|
||
|
||
const EMPTY_PAGE: BasePageResult<StudentHomeworkV2Vo> = { content: [], total: 0, pageIndex: 1, pageSize: 10, totalPages: 0, hasNext: false };
|
||
const page = ref<BasePageResult<StudentHomeworkV2Vo>>(EMPTY_PAGE);
|
||
const loading = ref(false);
|
||
|
||
async function load() {
|
||
loading.value = true;
|
||
try {
|
||
page.value = await studentApi
|
||
.list(tab.value, {
|
||
keyword: keyword.value || undefined,
|
||
pageIndex: pageNum.value,
|
||
pageSize: pageSize.value,
|
||
})
|
||
.send();
|
||
} catch (e: any) {
|
||
Message.error(e.message || "加载作业失败");
|
||
page.value = EMPTY_PAGE;
|
||
} finally {
|
||
loading.value = false;
|
||
}
|
||
}
|
||
function onTab(name: string) {
|
||
tab.value = name;
|
||
pageNum.value = 1;
|
||
load();
|
||
}
|
||
function doSearch() {
|
||
pageNum.value = 1;
|
||
load();
|
||
}
|
||
function pageUpdate(p: number) {
|
||
pageNum.value = p;
|
||
load();
|
||
}
|
||
onMounted(load);
|
||
watch(
|
||
() => route.fullPath,
|
||
() => {
|
||
tab.value = (route.meta as any).initTab || "all";
|
||
keyword.value = "";
|
||
pageNum.value = 1;
|
||
load();
|
||
}
|
||
);
|
||
|
||
/* todo 桶内待订正(RETURNED)排前,红色强化 */
|
||
const rows = computed(() => {
|
||
const list = page.value.content || [];
|
||
if (tab.value === "todo") {
|
||
return [...list].sort((a, b) => Number(b.my?.submitStatus === "RETURNED") - Number(a.my?.submitStatus === "RETURNED"));
|
||
}
|
||
return list;
|
||
});
|
||
|
||
/* ============ 状态展示 ============ */
|
||
const STATUS_TEXT: Record<string, string> = {
|
||
NOT_SUBMITTED: "未提交",
|
||
SUBMITTED: "已提交·待批",
|
||
LATE_SUBMITTED: "逾期补交·待批",
|
||
RETURNED: "待订正",
|
||
REVIEWED: "已批阅",
|
||
};
|
||
/* 左侧色条用色。蓝色对齐主题 #5979EA(原 #2d8cf0 是另一套蓝,与 tab 下划线/主按钮不同色) */
|
||
const STATUS_COLOR: Record<string, string> = {
|
||
NOT_SUBMITTED: "#c5c8ce",
|
||
SUBMITTED: "#5979EA",
|
||
LATE_SUBMITTED: "#ff9900",
|
||
RETURNED: "#ed4014",
|
||
REVIEWED: "#19be6b",
|
||
};
|
||
/**
|
||
* 状态标签(Tag)配色。
|
||
* 「未提交」用 default(白底浅灰描边)——属"未产生动作"的静默态,与宿舍通知里
|
||
* 「仅通知 / 已截止」同款处理;其余为实心语义色。
|
||
*/
|
||
const STATUS_TAG: Record<string, string> = {
|
||
NOT_SUBMITTED: "default",
|
||
SUBMITTED: "#5979EA",
|
||
LATE_SUBMITTED: "#ff9900",
|
||
RETURNED: "#ed4014",
|
||
REVIEWED: "#19be6b",
|
||
};
|
||
function myStatus(x: StudentHomeworkV2Vo) {
|
||
return x.my?.submitStatus || "NOT_SUBMITTED";
|
||
}
|
||
function statusColor(x: StudentHomeworkV2Vo) {
|
||
return STATUS_COLOR[myStatus(x)];
|
||
}
|
||
function statusTag(x: StudentHomeworkV2Vo) {
|
||
return STATUS_TAG[myStatus(x)] || "default";
|
||
}
|
||
/** 已批阅摘要:分数、评语都空时返回空串(不渲染,避免出现「得分 --」) */
|
||
function gradeText(x: StudentHomeworkV2Vo) {
|
||
const parts: string[] = [];
|
||
const s = x.my?.score;
|
||
if (s !== null && s !== undefined && String(s) !== "") parts.push(`得分 ${s}`);
|
||
if (x.my?.comment) parts.push(x.my.comment);
|
||
return parts.join(" · ");
|
||
}
|
||
/** 仅查阅型(submitType 含 none) */
|
||
function isViewOnly(x: StudentHomeworkV2Vo) {
|
||
return (x.submitType || []).includes("none") || (x.submitType || []).length === 0;
|
||
}
|
||
/** 是否可提交:PUBLISHED 期内,或 CLOSED 且允许补交 */
|
||
function canSubmit(x: StudentHomeworkV2Vo) {
|
||
if (isViewOnly(x)) return false;
|
||
if (x.hwStatus === "PUBLISHED") return true;
|
||
if (x.hwStatus === "CLOSED") return x.allowLate === 1;
|
||
return false;
|
||
}
|
||
function dueInfo(deadline: string | null | undefined) {
|
||
if (!deadline) return { text: "无截止", overdue: false, urgent: false };
|
||
const d = new Date(String(deadline).replace(/-/g, "/")).getTime();
|
||
if (isNaN(d)) return { text: "—", overdue: false, urgent: false };
|
||
const diffH = (d - Date.now()) / 3600000;
|
||
if (diffH < 0) return { text: "已截止", overdue: true, urgent: false };
|
||
if (diffH < 24) return { text: `剩 ${Math.max(1, Math.round(diffH))} 小时`, overdue: false, urgent: true };
|
||
return { text: `剩 ${Math.round(diffH / 24)} 天`, overdue: false, urgent: false };
|
||
}
|
||
function goDetail(row: StudentHomeworkV2Vo) {
|
||
router.push({ path: "/stu/detail", query: { serid: String(row.id) } });
|
||
}
|
||
|
||
/* ============ 提交 / 重交 / 撤回(快捷弹窗,主提交面在 /stu/detail) ============ */
|
||
const sub = reactive({
|
||
open: false,
|
||
id: 0,
|
||
title: "",
|
||
mode: "submit" as "submit" | "resubmit",
|
||
submissionId: 0,
|
||
text: "",
|
||
attachments: [] as AttachmentItem[],
|
||
loadingEcho: false,
|
||
submitting: false,
|
||
});
|
||
async function openSubmit(row: StudentHomeworkV2Vo) {
|
||
sub.open = true;
|
||
sub.id = row.id;
|
||
sub.title = row.title;
|
||
sub.mode = row.my?.submitStatus === "RETURNED" ? "resubmit" : "submit";
|
||
sub.submissionId = row.my?.submissionId || 0;
|
||
sub.text = "";
|
||
sub.attachments = [];
|
||
// 回显当前版本内容(修改提交 / 订正重交场景)
|
||
sub.loadingEcho = true;
|
||
try {
|
||
const d: StudentHomeworkDetailV2Vo = await studentApi.detail(row.id).send();
|
||
sub.text = d.myText || "";
|
||
sub.attachments = [...(d.myAttachments || [])];
|
||
} catch {
|
||
/* 回显失败不阻塞提交 */
|
||
} finally {
|
||
sub.loadingEcho = false;
|
||
}
|
||
}
|
||
async function submit() {
|
||
sub.submitting = true;
|
||
try {
|
||
if (sub.mode === "resubmit" && sub.submissionId) {
|
||
await studentApi.action(sub.id, sub.submissionId, { action: "resubmit", text: sub.text, attachments: sub.attachments }).send();
|
||
} else {
|
||
await studentApi.submit(sub.id, { text: sub.text, attachments: sub.attachments }).send();
|
||
}
|
||
Message.success("提交成功");
|
||
sub.open = false;
|
||
load();
|
||
} catch (e: any) {
|
||
Message.error(e.message || "提交失败");
|
||
} finally {
|
||
sub.submitting = false;
|
||
}
|
||
}
|
||
function withdraw(row: StudentHomeworkV2Vo) {
|
||
MyConfirm({
|
||
title: "撤回提交",
|
||
msg: `撤回“ ${row.title} ”的提交?`,
|
||
info: "撤回后可重新编辑提交(截止前有效)",
|
||
async onOk() {
|
||
try {
|
||
await studentApi.action(row.id, row.my!.submissionId, { action: "withdraw" }).send();
|
||
Message.success("已撤回,可重新提交");
|
||
load();
|
||
} catch (e: any) {
|
||
Message.error(e.message || "撤回失败");
|
||
}
|
||
},
|
||
});
|
||
}
|
||
|
||
/* ============ 图片放大预览 ============ */
|
||
/* 列表卡片不直接渲染附件缩略图(提交/查看走 /stu/detail),此处不再保留放大弹窗 */
|
||
</script>
|
||
|
||
<template>
|
||
<div class="page-wrapper">
|
||
<div class="main-container stu">
|
||
<!-- 顶部概览:轻量信息条(对齐宿舍通知学生端的浅蓝提示条语言,
|
||
替换原 91px 渐变大色块:省纵向空间,且不再与主题色 #5979EA 撞色) -->
|
||
<div class="stu-head">
|
||
<div class="sh-left">
|
||
<Icon type="md-book" size="16" />
|
||
<span class="sh-title">我的作业</span>
|
||
<span class="sh-sub">按时提交,查看老师批阅与订正</span>
|
||
</div>
|
||
<div class="sh-count">共 <b>{{ page.total }}</b> 份作业</div>
|
||
</div>
|
||
|
||
<div class="stu-filter">
|
||
<!-- view-ui-plus 4.x 的 Tabs 只有 modelValue,写 :value 不生效(高亮会与控制逻辑脱钩) -->
|
||
<Tabs v-model="tab" @on-click="onTab" class="stu-tabs">
|
||
<TabPane v-for="t in TABS" :key="t.name" :name="t.name" :label="t.label"></TabPane>
|
||
</Tabs>
|
||
<!-- ★ 不要给这个输入框加 clearable:view-ui-plus 的 Input 模板里
|
||
清除图标(clearable) 是 v-if、搜索图标(search) 是 v-else-if,
|
||
一旦有输入内容清除图标就把放大镜顶掉 → 「点放大镜搜索」直接失效。
|
||
本行只做宽度与右对齐,交互保持原样。 -->
|
||
<Input
|
||
class="stu-search"
|
||
search
|
||
suffix="ios-search"
|
||
placeholder="搜索作业标题"
|
||
v-model="keyword"
|
||
@on-search="doSearch"
|
||
@on-clear="doSearch"
|
||
/>
|
||
</div>
|
||
|
||
<div v-if="loading" class="state"><Spin fix /><div style="height: 120px"></div></div>
|
||
<div v-else-if="!rows.length" class="state empty">
|
||
<Icon type="ios-albums-outline" size="52" color="#c5c8ce" />
|
||
<p>该分类下暂无作业</p>
|
||
</div>
|
||
|
||
<div class="cards" :class="{ 'has-page': page.total > pageSize }" v-else>
|
||
<div class="card" v-for="row in rows" :key="row.id" :class="{ redo: myStatus(row) === 'RETURNED' }" :style="{ borderLeftColor: statusColor(row) }">
|
||
<!-- 左:作业主体信息 -->
|
||
<div class="c-main">
|
||
<div class="c-top" @click="goDetail(row)">
|
||
<span class="c-subj">{{ row.subject || "作业" }}</span>
|
||
<span class="c-title" :title="row.title">{{ row.title }}</span>
|
||
<span v-if="!isViewOnly(row) && row.my?.isLate" class="c-late">逾期</span>
|
||
</div>
|
||
<div class="c-meta">
|
||
<span class="m-item"><Icon type="md-person" size="14" /> {{ row.teacherName || "—" }}</span>
|
||
<!-- 「已截止」只在还需要学生动手时标红(未提交/待订正,且非仅查阅);
|
||
已提交、已批阅、仅查阅的条目保持灰字,避免整屏红字像出错 -->
|
||
<span
|
||
class="m-item"
|
||
:class="{
|
||
overdue: dueInfo(row.deadlineAt).overdue && !isViewOnly(row) && ['NOT_SUBMITTED', 'RETURNED'].includes(myStatus(row)),
|
||
urgent: dueInfo(row.deadlineAt).urgent && myStatus(row) === 'NOT_SUBMITTED',
|
||
}"
|
||
>
|
||
<Icon type="md-time" size="14" />
|
||
{{ row.deadlineAt ? `截止 ${String(row.deadlineAt).slice(5, 16)} · ${dueInfo(row.deadlineAt).text}` : "无截止" }}
|
||
</span>
|
||
</div>
|
||
|
||
<!-- 待订正:老师评语强化展示 -->
|
||
<div class="c-grade warn" v-if="myStatus(row) === 'RETURNED'">
|
||
<Icon type="md-refresh" color="#ed4014" size="15" /> 老师要求订正:{{ row.my?.comment || "请订正后重新提交" }}
|
||
</div>
|
||
<!-- 已批阅:得分摘要(分数/评语都没有时不渲染) -->
|
||
<div class="c-grade" v-else-if="myStatus(row) === 'REVIEWED' && gradeText(row)">
|
||
<Icon type="md-checkmark-circle" color="#19be6b" size="15" /> {{ gradeText(row) }}
|
||
</div>
|
||
</div>
|
||
|
||
<!-- 右:状态 + 操作(对齐宿舍通知用 Tag 表达状态,替代原来的裸色文字)
|
||
★ Tag 不要写 size="small":view-ui-plus 4.x 的 Tag.size 只收 default/medium/large,
|
||
small 会被 validator 拦掉并刷 Vue warn(Button 才有 small,两者不一致) -->
|
||
<div class="c-side">
|
||
<Tag v-if="isViewOnly(row)" color="default" class="c-flag">仅查阅</Tag>
|
||
<Tag v-else :color="statusTag(row)" class="c-status">{{ STATUS_TEXT[myStatus(row)] }}</Tag>
|
||
<div class="c-actions">
|
||
<Button v-if="['NOT_SUBMITTED'].includes(myStatus(row)) && canSubmit(row)" type="primary" icon="md-cloud-upload" size="small" @click="openSubmit(row)">
|
||
{{ row.hwStatus === "CLOSED" ? "补交作业" : "提交作业" }}</Button>
|
||
<Button v-else-if="myStatus(row) === 'RETURNED' && canSubmit(row)" type="error" icon="md-refresh" size="small" @click="openSubmit(row)">重新提交</Button>
|
||
<template v-else-if="['SUBMITTED', 'LATE_SUBMITTED'].includes(myStatus(row)) && canSubmit(row) && row.hwStatus === 'PUBLISHED'">
|
||
<!-- 必须带 type:裸 ghost 是白字白底,在白色卡片上等于隐形 -->
|
||
<Button type="primary" ghost icon="md-create" size="small" @click="openSubmit(row)">修改提交</Button>
|
||
<Button icon="md-undo" size="small" @click="withdraw(row)">撤回</Button>
|
||
</template>
|
||
<Button icon="md-eye" size="small" @click="goDetail(row)">{{ myStatus(row) === "REVIEWED" || myStatus(row) === "RETURNED" ? "查看批阅" : "查看详情" }}</Button>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
</div>
|
||
|
||
<div class="page-panel" v-if="page.total > pageSize">
|
||
<Page class="page-box fr" :total="page.total" :current="pageNum" :page-size="pageSize" @on-change="pageUpdate" show-total></Page>
|
||
</div>
|
||
|
||
<!-- 快捷提交弹窗 -->
|
||
<Modal v-model="sub.open" :title="(sub.mode === 'resubmit' ? '重新提交 · ' : '提交作业 · ') + sub.title" width="560px" class-name="submit-modal">
|
||
<Spin v-if="sub.loadingEcho" fix />
|
||
<div class="sm-field">
|
||
<div class="sm-label"><Icon type="md-create" size="16" /> 文字作答</div>
|
||
<Input v-model="sub.text" type="textarea" :rows="4" placeholder="输入作答内容"></Input>
|
||
</div>
|
||
<div class="sm-field">
|
||
<div class="sm-label"><Icon type="md-image" size="16" /> 图片 / 附件(先上传后提交)</div>
|
||
<AttachmentUpload v-model="sub.attachments"></AttachmentUpload>
|
||
</div>
|
||
<template #footer>
|
||
<Button @click="sub.open = false">取消</Button>
|
||
<Button type="primary" :loading="sub.submitting" icon="md-send" @click="submit">提交</Button>
|
||
</template>
|
||
</Modal>
|
||
</div>
|
||
</div>
|
||
</template>
|
||
|
||
<style lang="scss" scoped>
|
||
/*
|
||
* 学生端列表版式对齐宿舍通知学生端(ParentNotices.vue / NoticeList.vue):
|
||
* 1) 铺满内容区(原来是 max-width:960px,1440 视口下右侧空 280px)
|
||
* 2) 顶部不做装饰性大色块,用浅蓝信息条(#e8f4ff + #d3e9ff 描边,与宿舍通知「我的回执」条同款)
|
||
* 3) 列表项改为「左信息 / 右状态+操作」两栏,把横向宽度用起来,行高从 ~118px 收到 ~80px
|
||
* 4) 状态用 Tag 表达(原来是裸色文字,「未提交」的 #c5c8ce 在白卡上几乎看不见)
|
||
* 功能与交互(tab 过滤、搜索、分页、提交/撤回/跳详情)一行未动。
|
||
*/
|
||
.stu {
|
||
width: 100%;
|
||
}
|
||
|
||
/* ---------- 顶部信息条 ---------- */
|
||
.stu-head {
|
||
display: flex;
|
||
align-items: center;
|
||
justify-content: space-between;
|
||
gap: 16px;
|
||
background: #e8f4ff;
|
||
border: 1px solid #d3e9ff;
|
||
border-radius: 4px;
|
||
padding: 9px 14px;
|
||
margin-bottom: 12px;
|
||
.sh-left {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
min-width: 0;
|
||
color: #5979ea;
|
||
}
|
||
.sh-title {
|
||
flex: none;
|
||
font-size: 15px;
|
||
font-weight: 600;
|
||
color: #17233d;
|
||
}
|
||
.sh-sub {
|
||
font-size: 12px;
|
||
color: #808695;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
.sh-count {
|
||
flex: none;
|
||
font-size: 13px;
|
||
color: #515a6e;
|
||
b {
|
||
font-size: 16px;
|
||
color: #5979ea;
|
||
margin: 0 2px;
|
||
}
|
||
}
|
||
}
|
||
|
||
/* ---------- 筛选行(tab 左 / 搜索右) ---------- */
|
||
.stu-filter {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 16px;
|
||
margin-bottom: 12px;
|
||
.stu-tabs {
|
||
flex: 1;
|
||
min-width: 0;
|
||
:deep(.ivu-tabs-bar) {
|
||
margin-bottom: 0;
|
||
}
|
||
}
|
||
.stu-search {
|
||
flex: none;
|
||
width: 240px;
|
||
}
|
||
}
|
||
|
||
.state {
|
||
padding: 30px 0;
|
||
text-align: center;
|
||
&.empty {
|
||
color: #a6aab0;
|
||
p {
|
||
margin-top: 8px;
|
||
}
|
||
}
|
||
}
|
||
|
||
/* ---------- 列表项 ---------- */
|
||
.cards {
|
||
/* 分页条是 position:absolute 钉在视口底部(72px 高、白底),列表在其下方滚动会被盖住;
|
||
有分页时垫出这段高度,让最后一条能滚到分页条上方(同 Table 页预留横向滚动条的道理) */
|
||
&.has-page {
|
||
padding-bottom: 52px;
|
||
}
|
||
}
|
||
.cards .card {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 16px;
|
||
background: #fff;
|
||
border: 1px solid #ebeef4;
|
||
border-left: 3px solid #c5c8ce;
|
||
border-radius: 4px;
|
||
padding: 12px 16px;
|
||
margin-bottom: 10px;
|
||
transition:
|
||
box-shadow 0.2s,
|
||
border-color 0.2s;
|
||
&:hover {
|
||
box-shadow: 0 2px 10px rgba(0, 0, 0, 0.06);
|
||
}
|
||
&.redo {
|
||
border-color: #f5b0a6;
|
||
border-left-color: #ed4014;
|
||
background: #fffaf9;
|
||
}
|
||
|
||
/* 左:主体信息 */
|
||
.c-main {
|
||
flex: 1;
|
||
min-width: 0;
|
||
}
|
||
.c-top {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 8px;
|
||
min-width: 0;
|
||
cursor: pointer;
|
||
.c-subj {
|
||
background: #eaf3ff;
|
||
color: #5979ea;
|
||
border-radius: 3px;
|
||
padding: 1px 8px;
|
||
font-size: 12px;
|
||
line-height: 18px;
|
||
flex: none;
|
||
}
|
||
.c-title {
|
||
font-size: 15px;
|
||
font-weight: 600;
|
||
color: #17233d;
|
||
overflow: hidden;
|
||
text-overflow: ellipsis;
|
||
white-space: nowrap;
|
||
}
|
||
.c-late {
|
||
background: #ff9900;
|
||
color: #fff;
|
||
border-radius: 3px;
|
||
font-size: 11px;
|
||
line-height: 16px;
|
||
padding: 0 5px;
|
||
flex: none;
|
||
}
|
||
}
|
||
.c-meta {
|
||
display: flex;
|
||
flex-wrap: wrap;
|
||
gap: 4px 18px;
|
||
margin-top: 6px;
|
||
color: #808695;
|
||
font-size: 13px;
|
||
.m-item {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
}
|
||
.overdue {
|
||
color: #ed4014;
|
||
}
|
||
.urgent {
|
||
color: #ff9900;
|
||
}
|
||
}
|
||
/* 评语条:inline-flex 让底色只包住内容,不拉满整行 */
|
||
.c-grade {
|
||
display: inline-flex;
|
||
align-items: center;
|
||
gap: 6px;
|
||
max-width: 100%;
|
||
margin-top: 8px;
|
||
padding: 5px 10px;
|
||
background: #f6ffed;
|
||
border-radius: 4px;
|
||
color: #389e0d;
|
||
font-size: 12px;
|
||
line-height: 18px;
|
||
&.warn {
|
||
background: #fff1f0;
|
||
color: #cf1322;
|
||
}
|
||
}
|
||
|
||
/* 右:状态 + 操作 */
|
||
.c-side {
|
||
flex: none;
|
||
display: flex;
|
||
flex-direction: column;
|
||
align-items: flex-end;
|
||
gap: 8px;
|
||
.c-status,
|
||
.c-flag {
|
||
font-size: 12px;
|
||
}
|
||
}
|
||
.c-actions {
|
||
display: flex;
|
||
gap: 8px;
|
||
}
|
||
}
|
||
/* 提交弹窗 */
|
||
.sm-field {
|
||
margin-bottom: 14px;
|
||
.sm-label {
|
||
font-size: 13px;
|
||
color: #515a6e;
|
||
margin-bottom: 6px;
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 4px;
|
||
}
|
||
}
|
||
</style>
|