feat: improve ChatView, add LoginView and demo reference page

- ChatView: enhanced AI chat interface with improved message rendering

- LoginView: new login page component

- demo/main_demo.html: reference implementation for Chart.js dashboards

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
This commit is contained in:
2026-04-29 23:36:40 +08:00
parent f98ad7953f
commit ecc0693cd7
3 changed files with 1609 additions and 130 deletions

497
src/views/LoginView.vue Normal file
View File

@@ -0,0 +1,497 @@
<template>
<div class="login-page">
<!-- Left: Enterprise Brand Hero -->
<aside class="hero">
<div class="hero-grid"></div>
<div class="hero-content">
<div class="brand">
<div class="logo-box">
<svg viewBox="0 0 24 24" fill="none" xmlns="http://www.w3.org/2000/svg">
<path d="M12 2L2 7L12 12L22 7L12 2Z" fill="currentColor"/>
<path d="M2 17L12 22L22 17" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
<path d="M2 12L12 17L22 12" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"/>
</svg>
</div>
<span class="brand-name">X-Financial Ops</span>
</div>
<header class="hero-header">
<h1>智能财务报销<br/><span class="text-gradient">全流程运营中台</span></h1>
<p>基于 AI Agent 的合规预审与自动化处理引擎助力企业财务实现数字化治理与运营效率的跨越式提升</p>
</header>
<div class="value-prop">
<div v-for="item in features" :key="item.title" class="prop-item">
<div class="prop-icon" :style="{ color: item.color }">
<component :is="item.icon" />
</div>
<div class="prop-text">
<strong>{{ item.title }}</strong>
<p>{{ item.desc }}</p>
</div>
</div>
</div>
<div class="hero-footer">
<div class="trust-badge">
<div class="avatars">
<div v-for="i in 3" :key="i" class="avatar-circle"></div>
</div>
<span>已为 500+ 大中型企业提供智能化财务转型支持</span>
</div>
</div>
</div>
<!-- Preview UI Element: Simplified Dashboard Part -->
<div class="hero-preview">
<div class="preview-card">
<div class="preview-header">
<div class="dots"><span></span><span></span><span></span></div>
<div class="preview-title">系统合规性看板</div>
</div>
<div class="preview-body">
<div class="preview-stat">
<div class="stat-circle">
<svg viewBox="0 0 36 36">
<path class="circle-bg" d="M18 2.0845 a 15.9155 15.9155 0 0 1 0 31.831 a 15.9155 15.9155 0 0 1 0 -31.831" />
<path class="circle" stroke-dasharray="82, 100" d="M18 2.0845 a 15.9155 15.9155 0 0 1 0 31.831 a 15.9155 15.9155 0 0 1 0 -31.831" />
</svg>
<div class="stat-val">82%</div>
</div>
<div class="stat-info">
<strong>综合自动通过率</strong>
<p>较上月提升 12.4%</p>
</div>
</div>
<div class="preview-list">
<div v-for="i in 3" :key="i" class="list-item">
<div class="item-bar" :style="{ width: [85, 62, 45][i-1] + '%' }"></div>
</div>
</div>
</div>
</div>
</div>
</aside>
<!-- Right: Login Form (Refined) -->
<main class="login-main">
<div class="login-form-wrap">
<header class="login-header">
<h2>系统登录</h2>
<p>请输入您的凭据以访问控制台</p>
</header>
<form class="login-form" @submit.prevent="emit('login', { username, password })">
<div class="input-group">
<label>用户名</label>
<div class="input-control">
<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>
<input v-model="username" type="text" placeholder="账户名称 / 手机号" required />
</div>
</div>
<div class="input-group">
<label>密码</label>
<div class="input-control">
<svg class="icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="11" width="18" height="11" rx="2" ry="2"/><path d="M7 11V7a5 5 0 0 1 10 0v4"/></svg>
<input v-model="password" type="password" placeholder="请输入登录密码" required />
</div>
</div>
<div class="form-actions">
<label class="remember-me">
<input type="checkbox" v-model="remember" />
<span>记住我</span>
</label>
<a href="#" class="forgot-link">忘记密码</a>
</div>
<button type="submit" class="submit-btn">
<span>登录系统</span>
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><path d="M5 12h14M12 5l7 7-7 7"/></svg>
</button>
</form>
<footer class="login-footer">
<p>© 2026 X-Financial Technology. All Rights Reserved.</p>
</footer>
</div>
</main>
</div>
</template>
<script setup>
import { ref, markRaw } from 'vue'
const emit = defineEmits(['login'])
const username = ref('')
const password = ref('')
const remember = ref(false)
const ShieldCheck = {
template: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M12 22s8-4 8-10V5l-8-3-8 3v7c0 6 8 10 8 10z"/><path d="m9 12 2 2 4-4"/></svg>`
}
const Activity = {
template: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M22 12h-4l-3 9L9 3l-3 9H2"/></svg>`
}
const Zap = {
template: `<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/></svg>`
}
const features = [
{ title: '多维合规引擎', desc: '内置 500+ 企业财务制度,实现毫秒级自动预审。', color: '#3b82f6', icon: markRaw(ShieldCheck) },
{ title: '全链路风控监控', desc: '覆盖票据验真、重复报销及异常交易的深度挖掘。', color: '#10b981', icon: markRaw(Activity) },
{ title: '流程自动化编排', desc: 'AI Agent 驱动的自动分类与审批建议,节省 80% 人力。', color: '#f59e0b', icon: markRaw(Zap) }
]
</script>
<style scoped>
/* ── Main Layout ─────────────────────────────────── */
.login-page {
min-height: 100dvh;
display: grid;
grid-template-columns: minmax(600px, 1.2fr) minmax(400px, 0.8fr);
background: #fff;
font-family: 'PingFang SC', 'Microsoft YaHei', sans-serif;
}
/* ── Hero Side ───────────────────────────────────── */
.hero {
position: relative;
background: #0f172a; /* Slate 900 */
color: #f8fafc;
padding: 60px 80px;
display: flex;
flex-direction: column;
justify-content: space-between;
overflow: hidden;
}
.hero-grid {
position: absolute;
inset: 0;
background-image:
linear-gradient(rgba(59, 130, 246, 0.05) 1px, transparent 1px),
linear-gradient(90deg, rgba(59, 130, 246, 0.05) 1px, transparent 1px);
background-size: 40px 40px;
mask-image: radial-gradient(circle at 20% 30%, black, transparent 70%);
}
.hero-content {
position: relative;
z-index: 2;
max-width: 560px;
}
.brand {
display: flex;
align-items: center;
gap: 12px;
margin-bottom: 60px;
}
.logo-box {
width: 40px;
height: 40px;
background: #3b82f6;
border-radius: 10px;
display: grid;
place-items: center;
color: #fff;
box-shadow: 0 4px 12px rgba(59, 130, 246, 0.4);
}
.logo-box svg { width: 24px; height: 24px; }
.brand-name {
font-size: 18px;
font-weight: 800;
letter-spacing: -0.02em;
color: #fff;
}
.hero-header h1 {
font-size: 42px;
line-height: 1.2;
font-weight: 800;
margin-bottom: 20px;
color: #fff;
}
.text-gradient {
background: linear-gradient(135deg, #60a5fa, #3b82f6);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
}
.hero-header p {
font-size: 16px;
line-height: 1.6;
color: #94a3b8;
margin-bottom: 48px;
}
.value-prop {
display: grid;
gap: 32px;
}
.prop-item {
display: flex;
gap: 16px;
}
.prop-icon {
flex-shrink: 0;
width: 24px;
height: 24px;
margin-top: 2px;
}
.prop-icon svg { width: 100%; height: 100%; }
.prop-text strong {
display: block;
font-size: 15px;
color: #f1f5f9;
margin-bottom: 4px;
}
.prop-text p {
font-size: 13px;
color: #64748b;
line-height: 1.5;
}
.hero-footer {
position: relative;
z-index: 2;
margin-top: 60px;
}
.trust-badge {
display: flex;
align-items: center;
gap: 12px;
padding: 12px 20px;
background: rgba(255,255,255,0.03);
border: 1px solid rgba(255,255,255,0.06);
border-radius: 99px;
width: fit-content;
}
.avatars {
display: flex;
}
.avatar-circle {
width: 24px;
height: 24px;
border-radius: 50%;
background: #334155;
border: 2px solid #0f172a;
margin-left: -8px;
}
.avatar-circle:first-child { margin-left: 0; }
.trust-badge span {
font-size: 12px;
color: #94a3b8;
font-weight: 500;
}
/* ── Hero Preview Element ────────────────────────── */
.hero-preview {
position: absolute;
right: -40px;
top: 50%;
transform: translateY(-50%);
width: 440px;
opacity: 0.8;
pointer-events: none;
}
.preview-card {
background: #1e293b;
border: 1px solid rgba(255,255,255,0.1);
border-radius: 16px;
box-shadow: 0 40px 80px rgba(0,0,0,0.4);
overflow: hidden;
animation: floatPreview 6s ease-in-out infinite alternate;
}
@keyframes floatPreview {
from { transform: translateY(0); }
to { transform: translateY(-20px); }
}
.preview-header {
padding: 12px 16px;
background: rgba(255,255,255,0.03);
border-bottom: 1px solid rgba(255,255,255,0.05);
display: flex;
align-items: center;
gap: 12px;
}
.dots { display: flex; gap: 6px; }
.dots span { width: 8px; height: 8px; border-radius: 50%; background: #334155; }
.preview-title { font-size: 11px; color: #94a3b8; font-weight: 700; text-transform: uppercase; letter-spacing: 0.05em; }
.preview-body { padding: 24px; }
.preview-stat {
display: flex;
align-items: center;
gap: 20px;
margin-bottom: 24px;
}
.stat-circle {
position: relative;
width: 64px;
height: 64px;
}
.stat-circle svg { transform: rotate(-90deg); }
.circle-bg { fill: none; stroke: #334155; stroke-width: 3; }
.circle { fill: none; stroke: #3b82f6; stroke-width: 3; stroke-linecap: round; }
.stat-val {
position: absolute; inset: 0; display: grid; place-items: center;
font-size: 14px; font-weight: 800; color: #fff;
}
.stat-info strong { display: block; font-size: 16px; color: #fff; }
.stat-info p { font-size: 12px; color: #10b981; margin-top: 2px; }
.preview-list { display: grid; gap: 12px; }
.list-item { height: 12px; background: #334155; border-radius: 6px; overflow: hidden; }
.item-bar { height: 100%; background: linear-gradient(90deg, #3b82f6, #60a5fa); border-radius: inherit; }
/* ── Login Main ──────────────────────────────────── */
.login-main {
display: flex;
align-items: center;
justify-content: center;
padding: 40px;
}
.login-form-wrap {
width: 100%;
max-width: 360px;
}
.login-header {
margin-bottom: 40px;
}
.login-header h2 {
font-size: 28px;
font-weight: 800;
color: #0f172a;
margin-bottom: 8px;
}
.login-header p {
font-size: 14px;
color: #64748b;
}
.login-form {
display: grid;
gap: 24px;
}
.input-group label {
display: block;
font-size: 13px;
font-weight: 700;
color: #1e293b;
margin-bottom: 8px;
}
.input-control {
position: relative;
display: flex;
align-items: center;
}
.input-control .icon {
position: absolute;
left: 12px;
width: 18px;
height: 18px;
color: #94a3b8;
}
.input-control input {
width: 100%;
height: 44px;
padding-left: 40px;
padding-right: 12px;
border: 1px solid #e2e8f0;
border-radius: 8px;
font-size: 14px;
transition: all 0.2s;
}
.input-control input:focus {
outline: none;
border-color: #3b82f6;
box-shadow: 0 0 0 4px rgba(59, 130, 246, 0.1);
}
.form-actions {
display: flex;
justify-content: space-between;
align-items: center;
}
.remember-me {
display: flex;
align-items: center;
gap: 8px;
cursor: pointer;
font-size: 13px;
color: #64748b;
}
.forgot-link {
font-size: 13px;
color: #3b82f6;
text-decoration: none;
font-weight: 600;
}
.submit-btn {
width: 100%;
height: 48px;
background: #0f172a;
color: #fff;
border: none;
border-radius: 8px;
font-size: 15px;
font-weight: 700;
display: flex;
align-items: center;
justify-content: center;
gap: 10px;
cursor: pointer;
transition: all 0.2s;
}
.submit-btn:hover {
background: #1e293b;
transform: translateY(-1px);
}
.submit-btn svg {
width: 18px;
height: 18px;
}
.login-footer {
margin-top: 48px;
text-align: center;
}
.login-footer p {
font-size: 12px;
color: #94a3b8;
}
@media (max-width: 1024px) {
.hero { display: none; }
.login-page { grid-template-columns: 1fr; }
}
</style>