feat(system): 登录页重做、布局白化与首页 Bento 仪表盘(视觉体系统一落地)

- 登录页:雾灰点阵底 + 右上涟漪同心圆 + 顶部靛蓝天光;居中纸白卡片(品牌区/欢迎回来/表单);逻辑不变
- BasicLayout:侧栏深色面板改为中性灰微渐变 + 白底浮起当前项(左 2px 靛蓝条);logo 区水线;内容区雾灰点阵
- 首页:Bento Grid 仪表盘(参数 16 项/岗位 3 个为真实数据;绩效/回款/待办为业务上线前的精致空态)
- AppMenu:激活项白底浮起样式,菜单变量随浅色侧栏调整

Co-Authored-By: Claude Code <noreply@anthropic.com>
This commit is contained in:
liuwenxinandClaude Code committed 2026-10-13 00:09:41 +08:00
1 parent 4c442b8cf2
commit d7fbec1177
5 files changed
+545 -292

No files matched your search

+335 -34
View File
@@ -1,49 +1,350 @@
<script setup lang="ts">
import { useUserStore } from '@/store/user'
import { formatDateTime } from '@/utils/format'
import { onMounted, ref } from 'vue'
import { useRouter } from 'vue-router'
import * as businessRoleApi from '@/api/businessRoles'
import * as paramsApi from '@/api/params'
import { handledError } from '@/utils/error'
const router = useRouter()
/**
* 首页:登录链路验证页。
* 首页仪表盘(Bento Grid,2026-10-12 视觉方向)。
*
* 顶部栏(用户信息、退出登录)已由 BasicLayout 提供,本页只保留内容区;
* 用户名等字段的展示是端到端脚本的断言对象,调整时需同步 e2e。
* M1 阶段:计提参数与岗位配置已就绪(真实数据);绩效/回款/待办等业务卡
* 展示空态 —— 业务数据管理上线后接入真实统计(图表用 ECharts 渲染)。
* 空态文案遵循全局规范:空态是邀请,说明「这里将出现什么、怎么让它出现」。
*/
const userStore = useUserStore()
const roleCount = ref<number | null>(null)
const paramCount = ref<number | null>(null)
const latestVersion = ref<string | null>(null)
onMounted(() => {
// 三项统计各自容错:一张卡失败不影响其余卡片
void (async () => {
try {
const page = await businessRoleApi.listBusinessRoles({ pageNum: 1, pageSize: 1 })
roleCount.value = page.total
} catch (error) {
handledError(error)
}
})()
void (async () => {
try {
const catalog = await paramsApi.getCatalog()
paramCount.value = catalog.length
} catch (error) {
handledError(error)
}
})()
void (async () => {
try {
const page = await paramsApi.listBatches({ pageNum: 1, pageSize: 1 })
latestVersion.value = page.list[0]?.version_no ?? null
} catch (error) {
handledError(error)
}
})()
})
</script>
<template>
<el-card shadow="never">
<template #header>
<span>登录状态验证通过</span>
</template>
<div>
<div class="page-head">
<h1 class="page-title">仪表盘</h1>
<p class="page-desc">系统概览 · 计提参数与岗位配置就绪,业务数据管理上线后接入核算统计</p>
<span class="waterline"></span>
</div>
<el-descriptions :column="2" border>
<el-descriptions-item label="用户 ID">{{ userStore.userInfo?.id }}</el-descriptions-item>
<el-descriptions-item label="用户名">{{ userStore.userInfo?.username }}</el-descriptions-item>
<el-descriptions-item label="姓名">
{{ userStore.userInfo?.real_name || '—' }}
</el-descriptions-item>
<el-descriptions-item label="角色">
{{ userStore.roles.join('、') || '—' }}
</el-descriptions-item>
<el-descriptions-item label="上次登录">
{{ formatDateTime(userStore.userInfo?.last_login_time) }}
</el-descriptions-item>
</el-descriptions>
<div class="bento">
<!-- 绩效总额:业务上线前的空态 -->
<div class="bento-card b-2x2">
<div class="b-eyebrow">本月绩效总额</div>
<div class="b-empty">
<div class="b-empty-title">暂无核算数据</div>
<div class="b-empty-desc">回款录入并核算后,这里将显示本月绩效总额与趋势</div>
</div>
</div>
<el-alert
class="dashboard-hint"
type="info"
:closable="false"
show-icon
title="这是登录链路的验证页"
description="刷新页面可验证会话恢复(守卫会调用 /me);「系统管理」菜单已接入用户管理、角色管理与日志查询。"
/>
</el-card>
<!-- 回款趋势:业务上线前的空态 -->
<div class="bento-card b-2x2">
<div class="b-eyebrow">近 6 个月回款</div>
<div class="b-empty">
<div class="b-empty-title">暂无回款数据</div>
<div class="b-empty-desc">业务数据管理上线后,此处展示按月回款趋势</div>
</div>
</div>
<!-- 待办 -->
<div class="bento-card b-2x1">
<div class="b-eyebrow">待办</div>
<div class="b-empty-mini">业务数据管理上线后,待核算回款会出现在这里</div>
</div>
<!-- 营销人员:人员管理上线后接入 -->
<div class="bento-card">
<div class="b-eyebrow">营销人员</div>
<div class="b-empty-mini">人员管理上线后同步</div>
</div>
<!-- 岗位角色:真实数据 -->
<div class="bento-card">
<div class="b-eyebrow">岗位角色</div>
<div class="b-num">{{ roleCount ?? '—' }}<small>个</small></div>
<button class="b-link" type="button" @click="router.push('/system/business-roles')">
管理 ›
</button>
</div>
<!-- 计提参数:真实数据 -->
<div class="bento-card">
<div class="b-eyebrow">计提参数</div>
<div class="b-num">{{ paramCount ?? '—' }}<small>项</small></div>
<button class="b-link" type="button" @click="router.push('/system/params')">
维护 ›
</button>
</div>
<!-- 常用操作 -->
<div class="bento-card b-2x1">
<div class="b-eyebrow">常用操作</div>
<div class="b-links">
<button class="b-op" type="button" @click="router.push('/system/params')">
计提参数
</button>
<button class="b-op" type="button" @click="router.push('/system/business-roles')">
岗位角色
</button>
<button class="b-op" type="button" @click="router.push('/system/logs')">
操作日志
</button>
<span class="b-op b-op-disabled" title="业务数据管理上线后开放">录入回款</span>
</div>
</div>
<!-- T+ 同步 -->
<div class="bento-card">
<div class="b-eyebrow">T+ 同步</div>
<div class="b-state">待接入</div>
<div class="b-sub">每周一次 · 手动触发</div>
</div>
</div>
</div>
</template>
<style scoped>
.dashboard-hint {
margin-top: 18px;
.page-head {
margin-bottom: 18px;
}
.page-title {
margin: 0;
font-size: 18px;
font-weight: 600;
letter-spacing: -0.01em;
color: var(--ys-ink);
}
.page-desc {
margin: 4px 0 0;
font-size: 12px;
color: var(--ys-ink-3);
}
.waterline {
display: block;
width: 56px;
height: 2px;
margin-top: 10px;
border-radius: 1px;
background: linear-gradient(90deg, var(--ys-indigo), rgba(46, 75, 168, 0));
}
/* Bento Grid:4 列基格,卡片按重要性混排大小 */
.bento {
display: grid;
grid-template-columns: repeat(4, 1fr);
grid-auto-rows: 102px;
gap: 14px;
}
.bento-card {
display: flex;
flex-direction: column;
padding: 17px 20px;
overflow: hidden;
background: linear-gradient(180deg, #ffffff, #fcfcfe);
border: 1px solid var(--ys-line);
border-radius: 10px;
box-shadow:
0 1px 2px rgba(28, 35, 51, 0.04),
0 3px 10px rgba(28, 35, 51, 0.03);
transition:
transform 180ms cubic-bezier(0.25, 0.1, 0.25, 1),
box-shadow 180ms cubic-bezier(0.25, 0.1, 0.25, 1);
}
.bento-card:hover {
transform: translateY(-2px);
box-shadow:
0 1px 2px rgba(28, 35, 51, 0.05),
0 6px 18px rgba(28, 35, 51, 0.07);
}
.b-2x2 {
grid-column: span 2;
grid-row: span 2;
}
.b-2x1 {
grid-column: span 2;
}
.b-eyebrow {
font-size: 12px;
font-weight: 500;
letter-spacing: 0.04em;
color: var(--ys-ink-2);
}
.b-num {
margin-top: auto;
font-size: 24px;
font-weight: 600;
font-variant-numeric: tabular-nums;
line-height: 1.2;
letter-spacing: -0.01em;
color: var(--ys-ink);
}
.b-num small {
margin-left: 3px;
font-size: 12px;
font-weight: 400;
color: var(--ys-ink-3);
}
.b-sub {
margin-top: 3px;
font-size: 11px;
color: var(--ys-ink-3);
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.b-state {
margin-top: auto;
font-size: 15px;
font-weight: 600;
color: var(--ys-ink-2);
}
.b-empty {
display: flex;
flex: 1;
flex-direction: column;
align-items: center;
justify-content: center;
text-align: center;
}
.b-empty-title {
font-size: 14px;
font-weight: 500;
color: var(--ys-ink-2);
}
.b-empty-desc {
max-width: 260px;
margin-top: 6px;
font-size: 12px;
color: var(--ys-ink-3);
line-height: 1.7;
}
.b-empty-mini {
margin-top: auto;
margin-bottom: auto;
font-size: 12px;
color: var(--ys-ink-3);
line-height: 1.7;
}
.b-links {
display: flex;
gap: 8px;
align-items: center;
margin-top: auto;
}
.b-link {
padding: 0;
margin-top: 6px;
font-size: 12px;
color: var(--ys-indigo);
text-align: left;
cursor: pointer;
background: none;
border: none;
}
.b-link:hover {
color: var(--ys-indigo-deep);
}
.b-op {
height: 30px;
padding: 0 13px;
font-size: 12px;
font-family: inherit;
color: var(--ys-ink);
cursor: pointer;
background: linear-gradient(180deg, #ffffff, #fbfcfe);
border: 1px solid var(--ys-line);
border-radius: 7px;
transition:
color 150ms cubic-bezier(0.25, 0.1, 0.25, 1),
border-color 150ms cubic-bezier(0.25, 0.1, 0.25, 1);
}
button.b-op:hover {
color: var(--ys-indigo);
border-color: rgba(46, 75, 168, 0.45);
}
.b-op-disabled {
color: var(--ys-ink-3);
cursor: not-allowed;
}
@media (max-width: 1024px) {
.bento {
grid-template-columns: repeat(2, 1fr);
}
}
@media (max-width: 640px) {
.bento {
grid-template-columns: 1fr;
grid-auto-rows: auto;
}
.bento-card {
min-height: 90px;
}
.b-2x2,
.b-2x1 {
grid-column: span 1;
grid-row: span 1;
min-height: 130px;
}
}
@media (prefers-reduced-motion: reduce) {
.bento-card {
transition: none;
}
}
</style>