feat: 列宽拖拽 + 筛选栏 + 截图导出 + UI优化
- 新增 v-column-resize 指令,11 张表格均支持拖拽调整列宽 - 工作计划/商机跟单/要客拜访新增筛选栏(客户经理 + 状态) - 汇总栏 chip 可点击筛选,支持重置与计数 - 新增截图导出功能(dom-to-image-more,像素级文字渲染) - 截图范围从标题开始,自动隐藏操作按钮,5px白边 - 截图时页面闪烁动画反馈 - 全局 CSS 完善(列宽手柄、截图模式、捕获动画) - 仪表盘 UI 细节优化 + 亮灯表标签调整 - 登录页 UI 改进 Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
+108
-63
@@ -9,11 +9,22 @@ const route = useRoute()
|
||||
const auth = useAuthStore()
|
||||
const loading = ref(false)
|
||||
|
||||
// Check for Casdoor callback
|
||||
function goCasdoorLogin() {
|
||||
const returnUrl = (route.query.redirect as string) || ''
|
||||
if (returnUrl) {
|
||||
sessionStorage.setItem('login_return_url', returnUrl)
|
||||
}
|
||||
const endpoint = import.meta.env.VITE_CASDOOR_ENDPOINT
|
||||
const clientId = import.meta.env.VITE_CASDOOR_CLIENT_ID
|
||||
const redirectUri = encodeURIComponent(window.location.origin + '/login')
|
||||
const casdoorUrl = `${endpoint}/login/oauth/authorize?client_id=${clientId}&response_type=code&redirect_uri=${redirectUri}&scope=openid+profile&state=login`
|
||||
window.location.href = casdoorUrl
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
// ── Casdoor callback ──
|
||||
const code = route.query.code as string
|
||||
const state = route.query.state as string
|
||||
|
||||
if (code) {
|
||||
loading.value = true
|
||||
try {
|
||||
@@ -30,14 +41,13 @@ onMounted(async () => {
|
||||
return
|
||||
}
|
||||
|
||||
// Check for wecom code
|
||||
// ── Wecom callback ──
|
||||
const wecomCode = route.query.wecom_code as string
|
||||
if (wecomCode) {
|
||||
loading.value = true
|
||||
try {
|
||||
const res = await auth.wecomLogin(wecomCode)
|
||||
if (res.data.need_bind) {
|
||||
// Redirect to Casdoor for binding
|
||||
window.location.href = res.data.casdoor_url
|
||||
return
|
||||
}
|
||||
@@ -56,98 +66,133 @@ onMounted(async () => {
|
||||
} finally {
|
||||
loading.value = false
|
||||
}
|
||||
return
|
||||
}
|
||||
})
|
||||
|
||||
function goCasdoorLogin() {
|
||||
// Save return URL before redirecting to Casdoor
|
||||
const returnUrl = (route.query.redirect as string) || ''
|
||||
if (returnUrl) {
|
||||
sessionStorage.setItem('login_return_url', returnUrl)
|
||||
}
|
||||
const endpoint = import.meta.env.VITE_CASDOOR_ENDPOINT
|
||||
const clientId = import.meta.env.VITE_CASDOOR_CLIENT_ID
|
||||
const redirectUri = encodeURIComponent(window.location.origin + '/login')
|
||||
const casdoorUrl = `${endpoint}/login/oauth/authorize?client_id=${clientId}&response_type=code&redirect_uri=${redirectUri}&scope=openid+profile&state=login`
|
||||
window.location.href = casdoorUrl
|
||||
}
|
||||
// ── Auto-redirect to Casdoor (no button needed) ──
|
||||
loading.value = true
|
||||
goCasdoorLogin()
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="login-page">
|
||||
<div class="login-card">
|
||||
<!-- Decorative ink-wash circles -->
|
||||
<div class="bg-orb bg-orb--1"></div>
|
||||
<div class="bg-orb bg-orb--2"></div>
|
||||
<div class="bg-orb bg-orb--3"></div>
|
||||
|
||||
<div class="splash-card">
|
||||
<div class="brand">
|
||||
<h1>企迹</h1>
|
||||
<div class="brand-rule"></div>
|
||||
<p>政企周报管理系统</p>
|
||||
</div>
|
||||
<el-button
|
||||
type="primary"
|
||||
size="large"
|
||||
:loading="loading"
|
||||
@click="goCasdoorLogin"
|
||||
style="width: 100%"
|
||||
>
|
||||
登录 / 注册
|
||||
</el-button>
|
||||
<p class="hint">使用 Casdoor 账号登录</p>
|
||||
<div class="redirect-hint">
|
||||
<span class="dot-pulse"></span>
|
||||
正在跳转至统一认证...
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style scoped>
|
||||
/* ═══ Background — editorial ink wash ═══ */
|
||||
.login-page {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
background: linear-gradient(135deg, #4f6ef7 0%, #7b93fa 40%, #a5b4fc 100%);
|
||||
background: var(--ink);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
font-family: var(--font-body);
|
||||
}
|
||||
.login-page::before {
|
||||
content: ''; position: absolute; width: 600px; height: 600px;
|
||||
background: rgba(255,255,255,0.05); border-radius: 50%;
|
||||
top: -200px; right: -200px;
|
||||
|
||||
/* Decorative orbs — subtle ink-wash circles */
|
||||
.bg-orb {
|
||||
position: absolute;
|
||||
border-radius: 50%;
|
||||
pointer-events: none;
|
||||
}
|
||||
.login-page::after {
|
||||
content: ''; position: absolute; width: 400px; height: 400px;
|
||||
background: rgba(255,255,255,0.04); border-radius: 50%;
|
||||
bottom: -100px; left: -100px;
|
||||
.bg-orb--1 {
|
||||
width: 600px; height: 600px;
|
||||
background: radial-gradient(circle, rgba(196,147,74,0.06) 0%, transparent 70%);
|
||||
top: -180px; right: -180px;
|
||||
}
|
||||
.login-card {
|
||||
background: white;
|
||||
border-radius: 20px;
|
||||
padding: 48px 40px;
|
||||
width: 380px;
|
||||
max-width: 90vw;
|
||||
box-shadow: 0 25px 60px rgba(0,0,0,0.15);
|
||||
.bg-orb--2 {
|
||||
width: 400px; height: 400px;
|
||||
background: radial-gradient(circle, rgba(255,255,255,0.03) 0%, transparent 70%);
|
||||
bottom: -100px; left: -80px;
|
||||
}
|
||||
.bg-orb--3 {
|
||||
width: 200px; height: 200px;
|
||||
background: radial-gradient(circle, rgba(196,147,74,0.08) 0%, transparent 60%);
|
||||
top: 50%; left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
/* ═══ Splash Card ═══ */
|
||||
.splash-card {
|
||||
position: relative;
|
||||
z-index: 1;
|
||||
}
|
||||
.brand {
|
||||
text-align: center;
|
||||
margin-bottom: 36px;
|
||||
animation: fade-in 0.6s ease-out;
|
||||
}
|
||||
|
||||
@keyframes fade-in {
|
||||
from { opacity: 0; transform: translateY(12px); }
|
||||
to { opacity: 1; transform: translateY(0); }
|
||||
}
|
||||
|
||||
/* ═══ Brand ═══ */
|
||||
.brand {
|
||||
margin-bottom: 40px;
|
||||
}
|
||||
.brand h1 {
|
||||
margin: 0;
|
||||
font-size: 36px;
|
||||
font-weight: 800;
|
||||
letter-spacing: 4px;
|
||||
background: linear-gradient(135deg, #4f6ef7, #7b93fa);
|
||||
-webkit-background-clip: text;
|
||||
-webkit-text-fill-color: transparent;
|
||||
background-clip: text;
|
||||
font-family: var(--font-heading);
|
||||
font-size: 48px;
|
||||
font-weight: 400;
|
||||
letter-spacing: 0.15em;
|
||||
color: #fff;
|
||||
line-height: 1.2;
|
||||
}
|
||||
.brand-rule {
|
||||
width: 36px; height: 3px;
|
||||
background: var(--gold);
|
||||
margin: 16px auto;
|
||||
}
|
||||
.brand p {
|
||||
margin: 10px 0 0;
|
||||
color: var(--c-text-secondary);
|
||||
font-size: 14px;
|
||||
letter-spacing: 1px;
|
||||
margin: 0;
|
||||
font-family: var(--font-body);
|
||||
font-size: 13px;
|
||||
color: rgba(255,255,255,0.35);
|
||||
letter-spacing: 0.15em;
|
||||
}
|
||||
.hint {
|
||||
text-align: center;
|
||||
color: var(--c-text-muted);
|
||||
font-size: 12px;
|
||||
margin-top: 20px;
|
||||
|
||||
/* ═══ Redirect Hint ═══ */
|
||||
.redirect-hint {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
font-family: var(--font-body);
|
||||
font-size: 13px;
|
||||
color: rgba(255,255,255,0.45);
|
||||
letter-spacing: 0.06em;
|
||||
}
|
||||
|
||||
/* Pulsing dot animation */
|
||||
.dot-pulse {
|
||||
display: inline-block;
|
||||
width: 6px; height: 6px;
|
||||
background: var(--gold);
|
||||
border-radius: 50%;
|
||||
animation: pulse 1.2s ease-in-out infinite;
|
||||
}
|
||||
@keyframes pulse {
|
||||
0%, 100% { opacity: 0.3; transform: scale(0.8); }
|
||||
50% { opacity: 1; transform: scale(1.2); }
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user