Skip to content

Feat/UI redesign p3#21

Merged
ailuckly merged 14 commits into
developfrom
feat/ui-redesign-p3
Apr 22, 2026
Merged

Feat/UI redesign p3#21
ailuckly merged 14 commits into
developfrom
feat/ui-redesign-p3

Conversation

@ailuckly
Copy link
Copy Markdown
Owner

📌 变更内容

  • 如:新增用户登录 API
  • 如:修复登录失败时错误提示

✅ 测试验证

  • 本地运行通过
  • 自测通过
  • CI 流水线通过

PR 提交规范提醒:

  • 确保提交主题信息符合约定式提交规范 (feat/fix/docs/style/refactor/test/chore)
  • 确保代码已经通过本地测试
  • 确保没有提交敏感信息(密码、密钥等)

ailuckly added 14 commits April 21, 2026 09:42
- theme.css: full dual-mode token system (light/dark) using oklch,
  new brand color (purple-blue, GPT-style), added surface-raised,
  surface-overlay, line-subtle, text-muted, danger, success tokens,
  shadow-sm/md/lg scale, radius-sm/md/lg/xl scale
- logo.svg: new SVG icon (mic + soundwave arcs + gradient)
- useTheme.ts: composable for dark/light toggle with localStorage
  persistence and prefers-color-scheme detection
- main.ts: init theme before app mount to prevent flash
- AppSidebar: new logo with gradient wordmark, icon+text nav items,
  theme toggle button in footer, width 288→260px
- BasicLayout: grid column 288→260px, clean bg token
…anel

ChatStageHeader: status dot indicator, brand-soft avatar bg, clean tokens
ChatMessageList: token-based bubbles (surface-raised/brand-soft), streaming
  cursor animation, no hardcoded colors, AI avatar only on AI messages
ChatComposer: focus ring with brand color, el-icon buttons, textarea
  auto-resize, primary btn activates on text input
VoiceCallPanel: slide-in transition, pulse animation on speaking,
  mute/hangup icon buttons, transcript strip, all CSS tokens
AppSidebar:
- Width 260→280px
- Top: logo + quick-create (+) button
- Main nav: 3 items with icon+text, active state
- Divider
- History section: compact list (avatar 30px, name+preview, no card bg)
  scrollable, 12 items max, active item uses brand-soft bg
- Footer: user avatar+name (→ profile) + theme toggle icon button
- Mobile: slide-in with transform (not display:none), close button

BasicLayout:
- Grid column 260→280px
- Mobile: overlay backdrop on sidebar open, topbar with hamburger + logo
- view padding moved into .app-layout__view (not __main)
- chat view: overflow:hidden + padding:0
AppSidebar:
- Remove "我的空间" from main nav
- Footer user button opens popup menu (个人资料 / 设置 / 退出登录)
- Chevron rotates when menu open, slide-up transition

ProfilePage: full rewrite - hero (avatar+name+email), stats row,
  recent conversations list, favorite roles list, all token-based

SettingsPage: new page - theme toggle (light/dark segmented control),
  logout button, accessible at /settings

routes.ts: add /settings route
New utils/avatar.ts: generates colorful letter-based SVG avatars
locally (no network, no external service). Uses name-based color
palette (10 colors, oklch-inspired), 2-letter initials, rounded rect.

Added @error="onAvatarError($event, name)" to all img tags:
- AppSidebar: history items + user avatar
- ChatStageHeader: character avatar
- ChatMessageList: character avatar (both instances)
- ProfilePage: user + conversation + role avatars
- RoleShelf: character card avatars
- RoleDialog: character detail avatar

Fixes local dev SSL cert errors from Qiniu CDN (t333x1e4b.hb-bkt.clouddn.com)
Layout:
- Left hero: single featured role, full-bleed image, gradient overlay,
  role name + desc + CTA button
- Right featured grid: 5 cards in 2-row grid (2+3 layout)
- Tag filter bar: horizontal scroll pills with emoji, backend API filter
- Role grid: auto-fill minmax(160px), 2:3 aspect ratio cards,
  gradient overlay with name + chat count
- Load more button (replaces pagination)

Design:
- All cards use gradient overlay (dark bottom) for text legibility
  in both light and dark themes
- hover: translateY(-2px) + shadow-md on grid cards
- hover: scale(1.03) on featured cards
- Tag pills: brand color when active, surface-overlay on hover
- Responsive: 1024px (4 featured), 768px (mobile stack)
Hero Banner (420px):
- Left: eyebrow + large title + desc + CTA button + chat count
- Right: character image with left-to-right gradient overlay
- Smooth fade transition when switching hero via carousel

Carousel:
- 6 featured roles, horizontal scroll, 72x72px rounded thumbs
- Click to switch Hero image + copy
- Active state: brand border + glow ring + bold name

Tabs:
- Horizontal pill tabs (推荐/女/男/奇幻/游戏/动漫/影视/历史/科幻)
- Active: brand background + white text
- Backend API filter via tags param

Card Grid:
- auto-fill minmax(170px), 2:3 aspect ratio
- Full-bleed image, dark gradient overlay bottom
- Name + chat count overlaid on image
- hover: translateY(-3px) + shadow-md
- Short desc below card

Dark mode: #0b1020 page bg, #111827 hero bg
Responsive: 900px (stack hero), 640px (smaller cards)
…rchy

Structural changes only (no color/theme changes):

1. Page header: added title + subtitle above Hero
2. Hero Banner restructured:
   - Left/right split: copy area (badge+name+desc+CTA) | character image
   - Character switcher moved INSIDE Banner (was external carousel)
   - Switcher: 48px thumbnails + left/right arrow buttons + active ring
   - heroIndex drives both switcher highlight and Hero content
3. Unified content width: max-width 960px, all modules left-aligned
4. Category tabs: independent module below Hero, pill style with border
5. Content grid: same card design, consistent gap/alignment
6. Removed standalone carousel section
7. Mobile: Hero stacks vertically (image on top), switcher stays inside

Layout flow: Header → Hero[Main + Switcher] → Tabs → Grid → Load More
…s grid

Key fixes:
- hero__main: min-height→height 420px (was too short, looked like card)
- hero__visual: removed position:absolute on img, let grid child fill
  naturally (was causing empty right side)
- hero__name: 32→36px, hero__desc: 14→15px, hero__cta: 10→12px padding
- hero__copy padding: 36→40px
- discovery gap: 24→32px (more breathing room between sections)
- category-tabs: added padding-top 8px for separation from hero
- hero__visual-fade gradient: adjusted to 50% at 25% for cleaner blend
- Mobile: hero height auto, visual 240px, name 26px
Template restructure:
- Page title: "✨ 欢迎来到 VocaTa" (simplified, removed subtitle)
- Hero Banner: added blurred background layer (hero__bg-wrap) with
  full-bleed image + overlay for depth/atmosphere
- Hero right side: changed from plain image to card-style visual
  (hero__visual-card) with rounded corners, overlay gradient, and
  character name/tagline overlaid on the image
- Character switcher: moved from Banner bottom bar into left copy
  area (hero__switcher-inline), making it part of the content flow
  rather than a separate footer section
- Section header: added "✨ 角色聊天" title above category tabs,
  wrapped in discovery__section for grouping
- Content cards: moved desc text inside the gradient overlay (on top
  of image) instead of below the card, making cards more compact

Script changes:
- Added slideDirection ref for left/right slide transition control
- switchHero: changed from clamped to wrapping (infinite carousel)
- Added selectHero(i) for direct index selection with direction calc
- Added watch on heroIndex to auto-scroll active thumbnail into view
  using scrollIntoView({ behavior: 'smooth', inline: 'center' })
- Added nextTick import for scroll timing
- Tab labels: added emoji prefixes (🔮 奇幻, 🎮 游戏, etc.)

Style overhaul:
- Hero: position relative container with blurred bg layer (filter:
  blur(60px) saturate(1.6) scale(1.2)), semi-transparent overlay
- Hero main: grid 1fr 1fr (equal split), min-height 400px, z-index
  layering for bg/content separation
- Hero visual card: 280px wide, aspect-ratio 3/4, border-radius 20px,
  box-shadow, bottom gradient overlay with name/tagline
- Switcher inline: flex row with 44px round thumbnails, active state
  with brand border + glow ring, smooth scroll track
- Slide transitions: slide-left and slide-right with translateX
  animations for hero image switching
- Hero bg transition: opacity fade for background blur layer
- Section header: 20px title with bottom margin
- Category tabs: gap increased, font-size 14px
- Content grid: minmax(180px, 1fr), gap 20px
- Content cards: border-radius 16px, gradient overlay covers bottom
  40%, desc text inside overlay (white, 12px, 2-line clamp)
- Load more button: updated to match new spacing
- Responsive (768px): hero stacks vertically, visual card 200px
  height, grid 2-column min 140px
- Removed old hero__visual, hero__visual-fade, hero__switcher
  (bottom bar), hero-img transition, standalone card desc
Copilot AI review requested due to automatic review settings April 22, 2026 15:07
@ailuckly ailuckly merged commit f947e38 into develop Apr 22, 2026
5 of 7 checks passed
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR continues the “UI redesign” work by refreshing the main shell (layout + sidebar), discovery/search, login/profile pages, and chat UI components, while also adding dark/light theme support, a settings page, and a shared avatar fallback utility.

Changes:

  • Add theme system (CSS variables + useTheme composable) and a new Settings page with theme toggle + logout.
  • Redesign discovery (SearchRole), profile, login, sidebar/layout, and several chat components’ UI.
  • Introduce a reusable avatar fallback (onAvatarError) and apply it across multiple avatar <img> usages.

Reviewed changes

Copilot reviewed 20 out of 21 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
vocata-web/src/views/components/RoleDialog.vue Add avatar error fallback handler.
vocata-web/src/views/SettingsPage.vue New settings page for theme + logout.
vocata-web/src/views/SearchRole.vue Major discovery page redesign (hero, tabs, grid, load-more).
vocata-web/src/views/ProfilePage.vue Profile page layout redesign and data shaping changes.
vocata-web/src/views/LoginPage.vue Login/register UI redesign.
vocata-web/src/utils/avatar.ts New SVG avatar generator + onAvatarError.
vocata-web/src/router/routes.ts Add /settings route and update profile title.
vocata-web/src/main.ts Initialize theme before app mount to reduce flicker.
vocata-web/src/layouts/BasicLayout.vue Layout styling + mobile overlay/topbar adjustments.
vocata-web/src/components/shell/AppSidebar.vue Sidebar redesign with history list and user menu actions.
vocata-web/src/components/profile/ProfileRecentConversations.vue Styling adjustments to match new theme tokens.
vocata-web/src/components/profile/ProfileOverview.vue Styling adjustments to match new theme tokens.
vocata-web/src/components/profile/ProfileFavoriteRoles.vue Styling adjustments to match new theme tokens.
vocata-web/src/components/discovery/RoleShelf.vue Add avatar fallback handler in role cards.
vocata-web/src/components/chat/VoiceCallPanel.vue Voice panel UI redesign with transitions and icon buttons.
vocata-web/src/components/chat/ChatStageHeader.vue Header polish + avatar fallback + status dot.
vocata-web/src/components/chat/ChatMessageList.vue Message list UI changes + streaming cursor indicator.
vocata-web/src/components/chat/ChatComposer.vue Composer UI polish and icon-based actions.
vocata-web/src/assets/styles/theme.css Expanded theme tokens + dark theme variables.
vocata-web/src/assets/logo.svg New logo asset.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@@ -1,13 +1,26 @@
<template>
<div class="app-layout" :class="{ 'is-mobile': isMobileDevice, 'is-chat-route': isChatRoute }">
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isChatRoute is derived from route.path in the script, but in BasicLayout the route changes while the layout component stays mounted. Ensure the value used by this class binding is reactive (e.g., a computed based on useRoute()), otherwise the layout won’t update when navigating into/out of /chat/....

Copilot uses AI. Check for mistakes.
<p class="brand-copy__eyebrow">Bright companion space</p>
<h1>把每一次对话留在更舒服的空间里</h1>
<span>登录后继续你最近的聊天,或者创建一个新的陪伴角色。</span>
<div class="login-page" :class="isMobileDevice ? 'is-mobile' : 'is-desktop'">
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The template no longer uses layoutClass, but it is still declared in the script section. This will fail no-unused-vars linting; remove layoutClass (and any now-unused related code).

Suggested change
<div class="login-page" :class="isMobileDevice ? 'is-mobile' : 'is-desktop'">
<div class="login-page" :class="layoutClass">

Copilot uses AI. Check for mistakes.
Comment on lines +2 to +6
<div class="discovery">
<!-- ── 页面标题 ──────────────────────────────────────── -->
<div class="discovery__header">
<h1 class="discovery__title">✨ 欢迎来到 VocaTa</h1>
</div>
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The redesign removed the data-test hooks that existing unit tests assert on (e.g. discovery-hero, role-filter-bar, role-shelf, role-card in src/tests/discovery-page.spec.ts). Restore equivalent data-test attributes in the new markup or update the tests, otherwise CI will fail.

Copilot uses AI. Check for mistakes.
Comment on lines +197 to +201
const getRoleList = async (reset = false) => {
if (loading.value) return
loading.value = true
try {
const res = await roleApi.getPublicRoleList({
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getRoleList has no catch: if roleApi.getPublicRoleList throws, the UI silently fails and the user can’t tell why the list didn’t update. Add error handling (e.g., ElMessage.error) and consider how to keep pagination state consistent on failures.

Copilot uses AI. Check for mistakes.
Comment on lines +221 to +223
const loadMore = async () => {
pageNum.value++
await getRoleList(false)
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

loadMore increments pageNum before fetching; if the request fails, the next retry will skip a page. Increment only after a successful fetch, or roll back pageNum in a failure path.

Copilot uses AI. Check for mistakes.
Comment on lines +2 to +6
<div class="profile-page">
<!-- 顶部用户信息 -->
<div class="profile-page__hero">
<div class="profile-page__avatar">
<img v-if="userInfo.avatar" :src="userInfo.avatar" :alt="userInfo.nickname"
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The profile page redesign removed data-test hooks that existing unit tests assert on (see src/tests/app-shell.spec.ts for profile-overview, profile-recents, profile-favorites). Add equivalent data-test attributes to the new sections or update the tests, otherwise CI will fail.

Copilot uses AI. Check for mistakes.
Comment on lines +14 to +18
<div v-if="isMobileDevice" class="app-layout__topbar">
<button type="button" class="app-layout__menu-btn" @click="handleToggleSidebar" aria-label="打开菜单">
<el-icon><Expand /></el-icon>
</button>
<button type="button" @click="handleExplore">去探索</button>
<RouterLink to="/searchRole" class="app-layout__topbar-logo">
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The template no longer uses handleExplore (and potentially router), so the remaining script code will likely fail no-unused-vars linting. Remove the unused handler/variables or wire them back into the UI.

Copilot uses AI. Check for mistakes.
const route = useRoute()
const isMobileDevice = isMobile()
const historyStore = chatHistoryStore()
const { isDark } = useTheme()
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

isDark is destructured from useTheme() but never used, which will fail no-unused-vars linting. Either use it (e.g., display theme state) or remove the destructuring/import.

Copilot uses AI. Check for mistakes.
Comment on lines +123 to 127
<div v-if="hasMore" class="discovery__more">
<button @click="loadMore" :disabled="loading">{{ loading ? '加载中…' : '加载更多' }}</button>
</div>

<RoleDialog :item="roleSelected" v-if="infoShow && roleSelected" @close="infoShow = false" />
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

RoleDialog is still gated by infoShow && roleSelected, but the new UI never sets infoShow true or assigns roleSelected (the old detail handler was removed). Either reintroduce a way to open the dialog or remove the dialog + related state to avoid dead UI.

Copilot uses AI. Check for mistakes.
Comment on lines +233 to +237
const msg = ElMessage.info('正在创建对话…')
const uuid = await chatHistoryStore().addChatHistory(characterId)
msg.close()
router.push(`/chat/${uuid}`)
} catch {
Copy link

Copilot AI Apr 22, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The “正在创建对话…” info message is only closed on success. If addChatHistory throws, the info message will linger while an error message is shown. Close the handler in a finally (or in catch) so it’s always dismissed.

Copilot uses AI. Check for mistakes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants