feat(theme+规范): 管理端接入 yh-theme 主题层并按 bsch 应用页规范升级页面骨架
- 主题层: scripts/gen-yh-theme.mjs 生成 assets/styles/yh-theme.css(构建期静态化 color-mix), 5 主题档×侧栏深浅×渐变×side-plain 挂 html 档位类名; main.ts 于 icons.css 后引入并 initTheme() 消首帧闪色 - utils/theme.ts(+8 用例): localStorage(yh-theme) 持久化 + <html> 档位类名幂等切换 - ThemeSwitcher.vue(+6 用例): 顶栏配色胶囊(5 色网格+3 开关), 定稿 07-seam-pure 同款皮肤 - SchoolLayout: 顶栏 logo 徽标+配色切换器挂载; 站点身份色带退役, 子站橙由 yh-theme .band-sub::before 融合 - useTableHeight 响应化(+5 用例): ResizeObserver(body) 重算修复窗口缩放冻结, 常量 56/56 档不动 - bsch 规范骨架: 操作区 fix mb-12/次按钮 fr, Table 补 link-table table-bottom-box, 文章关键字 200px 即输即搜(300ms 防抖去 enter-button), 附件分页 customPageCount 记忆+page-box fr, 轮播展示位移入搜索区, 友链分组改 tabs-wrapper+Tabs card, 五表单校验失败统一"请填写红色星号内容!" - 142 用例全绿, vite build 通过
This commit is contained in:
@@ -139,7 +139,10 @@ function buildRequest() {
|
||||
|
||||
async function save(): Promise<number | undefined> {
|
||||
const valid = await formRef.value?.validate();
|
||||
if (!valid) return undefined;
|
||||
if (!valid) {
|
||||
Message.warning("请填写红色星号内容!"); // bsch §4 统一提示
|
||||
return undefined;
|
||||
}
|
||||
saving.value = true;
|
||||
try {
|
||||
return await articleApi.save(buildRequest()).send();
|
||||
|
||||
@@ -116,6 +116,13 @@ function uploadDone(res: any) {
|
||||
}
|
||||
}
|
||||
|
||||
function pageSizeUpdate(size: number) {
|
||||
pageSize.value = size;
|
||||
localStorage.setItem("customPageCount", String(size));
|
||||
pageNum.value = 1;
|
||||
run();
|
||||
}
|
||||
|
||||
onMounted(run);
|
||||
|
||||
// 站点上下文切换即时重载(docs/09 §3.3)
|
||||
@@ -128,7 +135,7 @@ watch(() => store.siteId, () => {
|
||||
<template>
|
||||
<div class="page-wrapper">
|
||||
<div class="main-container">
|
||||
<div class="fix">
|
||||
<div class="fix mb-12">
|
||||
<Upload
|
||||
class="fl"
|
||||
:action="uploadUrl() + '?siteId=' + store.siteId"
|
||||
@@ -140,14 +147,15 @@ watch(() => store.siteId, () => {
|
||||
>
|
||||
<Button type="primary" size="large" icon="ios-cloud-upload-outline">上传附件</Button>
|
||||
</Upload>
|
||||
<Button class="fl" style="margin-left: 8px" size="large" @click="cleanOrphans">
|
||||
<Button class="fr" size="large" @click="cleanOrphans">
|
||||
未使用附件({{ unusedCount }})
|
||||
</Button>
|
||||
</div>
|
||||
<div class="search-wrap">
|
||||
<Checkbox v-model="filter.orphanOnly" @on-change="pageNum = 1; run()">仅看未使用附件</Checkbox>
|
||||
<div class="search-wrap fix">
|
||||
<Checkbox class="fl" v-model="filter.orphanOnly" @on-change="pageNum = 1; run()">仅看未使用附件</Checkbox>
|
||||
</div>
|
||||
<Table
|
||||
class="link-table table-bottom-box"
|
||||
:columns="columns"
|
||||
:data="rows"
|
||||
:height="tableHeight"
|
||||
@@ -156,12 +164,12 @@ watch(() => store.siteId, () => {
|
||||
/>
|
||||
<div class="page-panel">
|
||||
<Page
|
||||
class="fr"
|
||||
class="page-box fr"
|
||||
:total="total"
|
||||
:current="pageNum"
|
||||
:page-size="pageSize"
|
||||
:page-size-opts="[10, 20, 30, 40]"
|
||||
@on-page-size-change="(s: number) => { pageSize = s; pageNum = 1; run(); }"
|
||||
@on-page-size-change="pageSizeUpdate"
|
||||
@on-change="(p: number) => { pageNum = p; run(); }"
|
||||
placement="top"
|
||||
show-total
|
||||
|
||||
@@ -148,7 +148,11 @@ function bannerUploaded(res: any) {
|
||||
|
||||
async function save() {
|
||||
const valid = await formRef.value?.validate();
|
||||
if (!valid) return;
|
||||
if (!valid) {
|
||||
// bsch 规范 §4:校验失败统一提示,不逐字段弹
|
||||
Message.warning("请填写红色星号内容!");
|
||||
return;
|
||||
}
|
||||
if (!form.imageUrl) {
|
||||
Message.warning("请上传轮播图片");
|
||||
return;
|
||||
@@ -200,17 +204,27 @@ watch(position, () => run());
|
||||
<template>
|
||||
<div class="page-wrapper">
|
||||
<div class="main-container">
|
||||
<div class="fix">
|
||||
<RadioGroup v-model="position" class="fl position-switch" type="button">
|
||||
<Radio v-for="p in positionDict" :key="p.value" :label="p.value">{{ p.label }}</Radio>
|
||||
</RadioGroup>
|
||||
<div class="fix mb-12">
|
||||
<Button class="fl" type="primary" size="large" @click="openCreate">
|
||||
<Icon type="ios-add" />
|
||||
新增轮播
|
||||
</Button>
|
||||
<span class="size-tip fr">{{ positionTip }}</span>
|
||||
</div>
|
||||
<Table :columns="columns" :data="rows" :height="tableHeight" :loading="loading" no-data-text="暂无轮播" />
|
||||
<!-- 展示位是筛选维度不是操作,按 bsch 规范归搜索区(左侧下拉) -->
|
||||
<div class="search-wrap fix">
|
||||
<Select v-model="position" class="search-wrap_sel">
|
||||
<Option v-for="p in positionDict" :key="p.value" :value="p.value">{{ p.label }}</Option>
|
||||
</Select>
|
||||
</div>
|
||||
<Table
|
||||
class="link-table table-bottom-box"
|
||||
:columns="columns"
|
||||
:data="rows"
|
||||
:height="tableHeight"
|
||||
:loading="loading"
|
||||
no-data-text="暂无轮播"
|
||||
/>
|
||||
|
||||
<Modal v-model="modalVisible" :title="form.id ? '编辑轮播' : '新增轮播'" :mask-closable="false" width="560px">
|
||||
<Form ref="formRef" :model="form" :rules="rules" :label-width="100">
|
||||
@@ -276,10 +290,6 @@ watch(position, () => run());
|
||||
</template>
|
||||
|
||||
<style scoped lang="scss">
|
||||
.position-switch {
|
||||
margin-right: 12px;
|
||||
margin-top: 6px;
|
||||
}
|
||||
.size-tip {
|
||||
color: #999;
|
||||
line-height: 40px;
|
||||
|
||||
@@ -239,7 +239,11 @@ function findCycleParent(): string {
|
||||
|
||||
async function save() {
|
||||
const valid = await refFormData.value?.validate();
|
||||
if (!valid) return;
|
||||
if (!valid) {
|
||||
// bsch 规范 §4:校验失败统一提示
|
||||
Message.warning("请填写红色星号内容!");
|
||||
return;
|
||||
}
|
||||
// 成环校验(下拉已不再置灰,这里给出可读原因;后端 CategoryService.validateParent 同口径兜底)
|
||||
const cycleHit = findCycleParent();
|
||||
if (cycleHit) {
|
||||
|
||||
@@ -127,7 +127,11 @@ function logoUploaded(res: any) {
|
||||
|
||||
async function save() {
|
||||
const valid = await formRef.value?.validate();
|
||||
if (!valid) return;
|
||||
if (!valid) {
|
||||
// bsch 规范 §4:校验失败统一提示
|
||||
Message.warning("请填写红色星号内容!");
|
||||
return;
|
||||
}
|
||||
saving.value = true;
|
||||
try {
|
||||
await linkApi
|
||||
@@ -169,18 +173,21 @@ watch(() => store.siteId, () => run());
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="page-wrapper">
|
||||
<div class="tabs-wrapper">
|
||||
<!-- 分组即 tab(bsch 规范 §1:tabs-wrapper + Tabs type=card,与 svc-hall ServiceDictManage 同范式) -->
|
||||
<Tabs v-if="groups.length" type="card" :value="activeGroup || '全部'" @on-click="(g: string) => (activeGroup = g === '全部' ? '' : g)">
|
||||
<TabPane name="全部" label="全部" />
|
||||
<TabPane v-for="g in groups" :key="g" :name="g" :label="`${g}(${rows.filter(r => r.groupName === g).length})`" />
|
||||
</Tabs>
|
||||
<div class="main-container">
|
||||
<div class="fix">
|
||||
<div class="fix mb-12">
|
||||
<Button class="fl" type="primary" size="large" @click="openCreate">
|
||||
<Icon type="ios-add" />
|
||||
新增友链
|
||||
</Button>
|
||||
<Tabs v-if="groups.length" class="fr" :value="activeGroup" @on-click="(g: string) => (activeGroup = g)">
|
||||
<TabPane v-for="g in groups" :key="g" :name="g" :label="`${g}(${rows.filter(r => r.groupName === g).length})`" />
|
||||
</Tabs>
|
||||
</div>
|
||||
<Table
|
||||
class="link-table table-bottom-box"
|
||||
:columns="columns"
|
||||
:data="visibleRows"
|
||||
:height="tableHeight"
|
||||
|
||||
@@ -193,13 +193,13 @@ watch(() => store.currentSchool?.schId, () => run());
|
||||
<a href="javascript:void(0);" @click="goBackWorkbench">返回学校工作台</a>
|
||||
</Alert>
|
||||
</div>
|
||||
<div class="fix" v-if="canCreateSite">
|
||||
<div class="fix mb-12" v-if="canCreateSite">
|
||||
<Button class="fl" type="primary" size="large" @click="openCreate">
|
||||
<Icon type="ios-add" />
|
||||
{{ isSchoolAdmin ? "新建院系子站" : "新建站点" }}
|
||||
</Button>
|
||||
</div>
|
||||
<Table :columns="columns" :data="rows" :height="tableHeight" :loading="loading" no-data-text="暂无站点" />
|
||||
<Table class="link-table table-bottom-box" :columns="columns" :data="rows" :height="tableHeight" :loading="loading" no-data-text="暂无站点" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
@@ -251,7 +251,10 @@ async function submitSection(key: string, patch: { configJson?: string }) {
|
||||
|
||||
async function saveBasic() {
|
||||
const valid = await basicRef.value?.validate();
|
||||
if (!valid) return;
|
||||
if (!valid) {
|
||||
Message.warning("请填写红色星号内容!"); // bsch §4 统一提示
|
||||
return;
|
||||
}
|
||||
await submitSection("basic", {});
|
||||
}
|
||||
|
||||
@@ -263,22 +266,34 @@ async function saveTheme() {
|
||||
}
|
||||
|
||||
async function saveDisplay() {
|
||||
if (!(await displayFormRef.value?.validate())) return;
|
||||
if (!(await displayFormRef.value?.validate())) {
|
||||
Message.warning("请填写红色星号内容!"); // bsch §4
|
||||
return;
|
||||
}
|
||||
await submitSection("display", {});
|
||||
}
|
||||
|
||||
async function saveFooter() {
|
||||
if (!(await footerFormRef.value?.validate())) return;
|
||||
if (!(await footerFormRef.value?.validate())) {
|
||||
Message.warning("请填写红色星号内容!"); // bsch §4
|
||||
return;
|
||||
}
|
||||
await submitSection("footer", {});
|
||||
}
|
||||
|
||||
async function saveSeo() {
|
||||
if (!(await seoFormRef.value?.validate())) return;
|
||||
if (!(await seoFormRef.value?.validate())) {
|
||||
Message.warning("请填写红色星号内容!"); // bsch §4
|
||||
return;
|
||||
}
|
||||
await submitSection("seo", {});
|
||||
}
|
||||
|
||||
async function saveExtras() {
|
||||
if (!(await extrasFormRef.value?.validate())) return;
|
||||
if (!(await extrasFormRef.value?.validate())) {
|
||||
Message.warning("请填写红色星号内容!"); // bsch §4
|
||||
return;
|
||||
}
|
||||
await submitSection("extras", {});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user