Files
effekt-interface-frontend/src/components/EffektHome.vue
qiaoxinjiu 3b359a7fd5 feat(ui): 全局深色主题与登录注册页样式优化
- App 全局主题变量与 Element 组件暗色适配
- 首页、测试平台布局与主题切换
- 登录/注册页改版并支持明暗主题切换
- 用例列表等页面样式与主题保持一致

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-05-18 16:18:40 +08:00

515 lines
13 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<template>
<div class="effekt-home">
<el-row :gutter="20" class="top-row">
<el-col :xs="24" :md="10">
<el-card shadow="never" class="greet-card">
<div class="greet-line">{{ greetingPrefix }}{{ greetingTime }}</div>
<div class="greet-date">{{ todayText }}</div>
<div v-if="currentUser" class="greet-progress">
<span class="greet-progress-label">待处理进度</span>
<el-progress :percentage="100" :stroke-width="10" status="success" />
<span class="greet-progress-tip">已完成 100%</span>
</div>
<div v-else class="greet-login-tip">
<el-link type="primary" @click="goLogin">登录后查看个人工作台</el-link>
</div>
</el-card>
</el-col>
<el-col :xs="24" :md="14">
<el-card shadow="never" class="work-card">
<div class="work-card-title">今天剩余工作总计</div>
<div class="work-stats">
<div class="work-stat">
<div class="work-stat-value">{{ formatCount(workCountOpportunity) }}</div>
<div class="work-stat-label">我的机会</div>
</div>
<div class="work-stat work-stat--click" @click="goMyBugs">
<div class="work-stat-value">{{ formatCount(workCountBug) }}</div>
<div class="work-stat-label">我的 BUG</div>
<div class="work-stat-hint">点击查看指派给我</div>
</div>
<div class="work-stat work-stat--click" @click="goMyPlans">
<div class="work-stat-value">{{ formatCount(workCountPlan) }}</div>
<div class="work-stat-label">我的计划</div>
<div class="work-stat-hint">点击查看我负责的</div>
</div>
</div>
</el-card>
</el-col>
</el-row>
<el-card shadow="never" class="links-card">
<div class="links-card-title">环境与文档</div>
<p class="home-desc">这里汇总各项目常用环境地址和文档链接方便快速进入</p>
<div
v-for="project in projectLinks"
:key="project.name"
class="project-block">
<div class="project-title">{{ project.name }}</div>
<div class="project-links">
<div v-for="item in project.links" :key="item.name" class="link-item">
<span class="link-label">{{ item.name }}</span>
<el-link
:href="item.url"
target="_blank"
type="primary"
class="doc-link">
{{ item.url }}
</el-link>
</div>
</div>
</div>
</el-card>
</div>
</template>
<script>
import { getBugList } from '@/api/bugApi'
import { getPlanList } from '@/api/planApi'
import { readLastProductProjectCache } from '@/utils/lastProductProjectCache'
export default {
name: 'EffektHome',
data() {
return {
workCountOpportunity: null,
workCountBug: null,
workCountPlan: null,
projectLinks: [
{
name: '智慧运营',
links: [
{ name: '测试环境地址', url: 'https://smart-management-web-st.best-envision.com/' },
{ name: 'pre环境地址', url: 'https://smart-management-web-pre.best-envision.com/' },
{ name: '需求文档', url: 'https://vcncbabzm4lv.feishu.cn/wiki/UsvzwMzV0i7Lrgk9VIhc2XgJn6e?fromScene=spaceOverview' },
{ name: '资源连接地址(包含数据库连接jenkins配置git仓库日志查询xxjob等)', url: 'https://vcncbabzm4lv.feishu.cn/wiki/ZKmown7QuiXtwTkONhUcagpQnWh' },
{ name: '领星地址', url: 'https://envision.lingxing.com/erp/home' },
{ name: '禅道地址', url: 'http://39.170.26.156:8888/my.html' }
]
},
{
name: '独立站项目',
links: [
{ name: '管理后台测试环境地址', url: 'https://joyhub-website-manager-web-test.best-envision.com/' },
{ name: 'web的C端测试环境地址', url: 'https://joyhub-website-frontend-test.best-envision.com/' },
{ name: '接口开发地址', url: 'https://joyhub-website-manager-api-test.best-envision.com/doc.html#/home' },
{ name: '资源连接地址', url: 'https://vcncbabzm4lv.feishu.cn/wiki/PXTmw6BBviMjNDkKCxCcewD7nMd' },
{ name: '禅道地址', url: 'http://192.168.16.4:8956/index.php?m=my&f=index' }
]
}
]
}
},
computed: {
currentUser() {
return this.$store.state.currentUser
},
greetingPrefix() {
const u = this.currentUser
if (!u) return ''
const name = u.realName || u.username || ''
return name ? `${name}` : ''
},
greetingTime() {
const h = new Date().getHours()
if (h < 12) return '上午好!'
if (h < 18) return '下午好!'
return '晚上好!'
},
todayText() {
const d = new Date()
const y = d.getFullYear()
const m = String(d.getMonth() + 1).padStart(2, '0')
const day = String(d.getDate()).padStart(2, '0')
return `${y}${m}${day}`
}
},
mounted() {
this.refreshWorkCounts()
},
methods: {
formatCount(n) {
if (n === null || n === undefined || Number.isNaN(Number(n))) return '—'
return String(n)
},
goLogin() {
this.$router.push({ name: 'login' })
},
goMyBugs() {
if (!this.currentUser) {
this.$message.warning('请先登录')
this.goLogin()
return
}
const c = readLastProductProjectCache()
const q = { assignToMe: '1' }
if (c && c.productId !== undefined && c.productId !== null && String(c.productId).trim() !== '') {
q.productId = String(c.productId)
}
if (c && c.projectId !== undefined && c.projectId !== null && String(c.projectId).trim() !== '') {
q.projectId = String(c.projectId)
}
this.$router.push({ path: '/bug/list', query: q })
},
goMyPlans() {
if (!this.currentUser) {
this.$message.warning('请先登录')
this.goLogin()
return
}
const c = readLastProductProjectCache()
const q = { planOwnerSelf: '1' }
if (c && c.productId !== undefined && c.productId !== null && String(c.productId).trim() !== '') {
q.productId = String(c.productId)
}
if (c && c.projectId !== undefined && c.projectId !== null && String(c.projectId).trim() !== '') {
q.projectId = String(c.projectId)
}
this.$router.push({ path: '/test-platform/plan', query: q })
},
refreshWorkCounts() {
const u = this.currentUser
const c = readLastProductProjectCache()
this.workCountOpportunity = null
this.workCountBug = null
this.workCountPlan = null
if (!u || u.id == null || u.id === '' || !c || !c.projectId) {
return
}
getBugList({
productId: c.productId,
projectId: c.projectId,
assigneeId: u.id,
pageNo: 1,
pageSize: 1
})
.then(res => {
const data = (res && res.data) || res || {}
this.workCountBug = Number(data.total != null ? data.total : 0)
})
.catch(() => {
this.workCountBug = null
})
getPlanList(c.projectId, {
owner_id: u.id,
owner: u.id,
pageNo: 1,
pageSize: 1
})
.then(res => {
const data = (res && res.data) || res || {}
this.workCountPlan = Number(data.total != null ? data.total : 0)
})
.catch(() => {
this.workCountPlan = null
})
}
}
}
</script>
<style scoped>
.effekt-home {
max-width: 1240px;
margin: 0 auto;
}
.top-row {
margin-bottom: 20px;
}
.greet-card,
.work-card,
.links-card {
border: 1px solid rgba(148, 163, 184, 0.2);
border-radius: 18px;
box-shadow: 0 18px 48px rgba(0, 0, 0, 0.24), inset 0 1px 0 rgba(255, 255, 255, 0.04);
overflow: hidden;
background: #111827;
}
.greet-card,
.work-card {
min-height: 174px;
}
.greet-card {
position: relative;
background: radial-gradient(circle at 88% 16%, rgba(103, 232, 249, 0.28), transparent 28%), linear-gradient(135deg, rgba(30, 64, 175, 0.95) 0%, rgba(15, 23, 42, 0.96) 62%, rgba(8, 13, 27, 0.98) 100%);
color: #fff;
border-color: rgba(103, 232, 249, 0.22);
}
.greet-card:after {
content: '';
position: absolute;
right: -44px;
bottom: -54px;
width: 170px;
height: 170px;
border-radius: 50%;
background: radial-gradient(circle, rgba(56, 189, 248, 0.26), transparent 68%);
}
.greet-card >>> .el-card__body,
.work-card >>> .el-card__body,
.links-card >>> .el-card__body {
padding: 24px;
position: relative;
z-index: 1;
}
.greet-line {
font-size: 24px;
font-weight: 800;
color: #f8fbff;
margin-bottom: 8px;
letter-spacing: 0.2px;
}
.greet-date {
color: #bae6fd;
font-size: 13px;
margin-bottom: 22px;
}
.greet-progress-label {
font-size: 12px;
color: rgba(224, 242, 254, 0.9);
display: block;
margin-bottom: 8px;
}
.greet-progress >>> .el-progress-bar__outer {
background-color: rgba(15, 23, 42, 0.68);
}
.greet-progress >>> .el-progress-bar__inner {
background: linear-gradient(90deg, #22d3ee 0%, #6366f1 100%);
}
.greet-progress-tip {
font-size: 12px;
color: #a7f3d0;
margin-top: 8px;
display: block;
}
.greet-login-tip {
margin-top: 8px;
}
.greet-login-tip >>> .el-link.el-link--primary {
color: #67e8f9;
font-weight: 700;
}
.work-card-title,
.links-card-title {
position: relative;
font-size: 16px;
font-weight: 800;
color: #e0f2fe;
margin-bottom: 18px;
padding-left: 12px;
letter-spacing: 0.3px;
}
.work-card-title:before,
.links-card-title:before {
content: '';
position: absolute;
left: 0;
top: 3px;
width: 4px;
height: 16px;
border-radius: 999px;
background: linear-gradient(180deg, #67e8f9 0%, #6366f1 100%);
box-shadow: 0 0 14px rgba(103, 232, 249, 0.55);
}
.work-stats {
display: flex;
flex-wrap: wrap;
justify-content: space-between;
gap: 14px;
}
.work-stat {
flex: 1;
min-width: 126px;
text-align: left;
padding: 18px;
border-radius: 16px;
background: #162033;
border: 1px solid rgba(148, 163, 184, 0.18);
cursor: default;
transition: box-shadow 0.2s ease, border-color 0.2s ease, transform 0.2s ease, background 0.2s ease;
}
.work-stat--click {
cursor: pointer;
}
.work-stat--click:hover {
border-color: rgba(56, 189, 248, 0.52);
background: #1e293b;
box-shadow: 0 0 24px rgba(56, 189, 248, 0.14), 0 18px 34px rgba(0, 0, 0, 0.24);
transform: translateY(-2px);
}
.work-stat-value {
font-size: 32px;
font-weight: 900;
color: #67e8f9;
line-height: 1.1;
text-shadow: 0 0 18px rgba(103, 232, 249, 0.35);
}
.work-stat-label {
margin-top: 10px;
font-size: 14px;
color: #dbeafe;
font-weight: 700;
}
.work-stat-hint {
margin-top: 6px;
font-size: 12px;
color: #8fb3d9;
}
.links-card {
background: #111827;
}
.home-content {
display: flex;
flex-direction: column;
}
.home-desc {
margin: 0 0 18px;
color: #94a3b8;
font-size: 13px;
}
.project-block {
padding: 18px;
margin-bottom: 14px;
border: 1px solid rgba(148, 163, 184, 0.16);
border-radius: 16px;
background: #162033;
}
.project-block:last-child {
margin-bottom: 0;
}
.project-title {
margin-bottom: 12px;
font-size: 16px;
font-weight: 800;
color: #e0f2fe;
}
.link-item {
display: flex;
align-items: flex-start;
margin-bottom: 10px;
line-height: 22px;
}
.link-item:last-child {
margin-bottom: 0;
}
.link-label {
min-width: 150px;
color: #b7c9df;
font-weight: 700;
}
.doc-link {
word-break: break-all;
}
.doc-link >>> span {
color: #67e8f9;
}
body.theme-light .greet-card,
body.theme-light .work-card,
body.theme-light .links-card {
background: #ffffff;
border-color: #dbe5f3;
box-shadow: 0 14px 34px rgba(37, 99, 235, 0.08);
}
body.theme-light .greet-card {
background: radial-gradient(circle at 88% 16%, rgba(96, 165, 250, 0.24), transparent 28%), linear-gradient(135deg, #2563eb 0%, #3b82f6 58%, #60a5fa 100%);
color: #ffffff;
border-color: rgba(96, 165, 250, 0.42);
}
body.theme-light .greet-card:after {
background: radial-gradient(circle, rgba(255, 255, 255, 0.26), transparent 68%);
}
body.theme-light .greet-date,
body.theme-light .greet-progress-label {
color: rgba(239, 246, 255, 0.92);
}
body.theme-light .greet-progress >>> .el-progress-bar__outer {
background-color: rgba(255, 255, 255, 0.28);
}
body.theme-light .greet-login-tip >>> .el-link.el-link--primary {
color: #ffffff;
}
body.theme-light .work-card-title,
body.theme-light .links-card-title,
body.theme-light .project-title {
color: #0f172a;
}
body.theme-light .work-card-title:before,
body.theme-light .links-card-title:before {
background: linear-gradient(180deg, #2563eb 0%, #38bdf8 100%);
box-shadow: 0 8px 16px rgba(37, 99, 235, 0.18);
}
body.theme-light .work-stat,
body.theme-light .project-block {
background: #f8fbff;
border-color: #e2e8f0;
}
body.theme-light .work-stat--click:hover {
background: #eef6ff;
border-color: #bfdbfe;
box-shadow: 0 14px 28px rgba(37, 99, 235, 0.12);
}
body.theme-light .work-stat-value {
color: #2563eb;
text-shadow: none;
}
body.theme-light .work-stat-label,
body.theme-light .link-label {
color: #334155;
}
body.theme-light .work-stat-hint,
body.theme-light .home-desc {
color: #64748b;
}
body.theme-light .doc-link >>> span {
color: #2563eb;
}
</style>