mirror of
https://wget.la/https://github.com/Wxw-Gu/WechatExplorer
synced 2026-08-17 03:27:00 +08:00
完成 UI-01 应用外壳与设计令牌
This commit is contained in:
+39
-25
@@ -2,6 +2,8 @@
|
||||
import { Sidebar } from './components/Sidebar'
|
||||
import ChatWindow from './components/ChatWindow'
|
||||
import { SettingsPanel } from './components/SettingsPanel'
|
||||
import { AppShell } from './components/layout/AppShell'
|
||||
import { AppPage } from './components/layout/navigation'
|
||||
import { Contact, Message } from '../../shared/types'
|
||||
|
||||
function EyeIcon({ hidden }: { hidden: boolean }): React.ReactElement {
|
||||
@@ -149,6 +151,7 @@ function App(): React.ReactElement {
|
||||
const [showDbKey, setShowDbKey] = useState(false)
|
||||
const [showMacKeyFaq, setShowMacKeyFaq] = useState(false)
|
||||
const [showSettings, setShowSettings] = useState(false)
|
||||
const [activePage, setActivePage] = useState<AppPage>('archive')
|
||||
const [selfInfo, setSelfInfo] = useState<SelfInfo | null>(null)
|
||||
const [isNativeMonitorActive, setIsNativeMonitorActive] = useState(false)
|
||||
const [bootState, setBootState] = useState<'loading' | 'connecting' | 'login'>('loading')
|
||||
@@ -830,30 +833,41 @@ function App(): React.ReactElement {
|
||||
}
|
||||
|
||||
return (
|
||||
<div className="app-container">
|
||||
<Sidebar
|
||||
contacts={filteredContacts}
|
||||
selectedContact={selectedContact}
|
||||
onSelectContact={handleSelectContact}
|
||||
onSearch={handleSearchContacts}
|
||||
onContentFilter={setContentFilter}
|
||||
width={sidebarWidth}
|
||||
dateRange={dateRange}
|
||||
onDateRangeChange={handleDateRangeChange}
|
||||
selfInfo={selfInfo}
|
||||
dbReady={isAuthenticated}
|
||||
onOpenSettings={() => setShowSettings(true)}
|
||||
/>
|
||||
<div className="resizer" onMouseDown={startResizing} />
|
||||
<ChatWindow
|
||||
key={`${selectedContact?.md5}-${contentFilter}`}
|
||||
contact={selectedContact}
|
||||
messages={messages}
|
||||
isLoadingMessages={isMessagesLoading}
|
||||
contentFilter={contentFilter}
|
||||
onRefresh={() => selectedContact && handleSelectContact(selectedContact)}
|
||||
onRefreshData={loadContacts}
|
||||
/>
|
||||
<AppShell
|
||||
activePage={activePage}
|
||||
selfInfo={selfInfo}
|
||||
dbReady={isAuthenticated}
|
||||
onPageChange={setActivePage}
|
||||
onOpenSettings={() => {
|
||||
setActivePage('settings')
|
||||
setShowSettings(true)
|
||||
}}
|
||||
>
|
||||
<div className="app-container">
|
||||
<Sidebar
|
||||
contacts={filteredContacts}
|
||||
selectedContact={selectedContact}
|
||||
onSelectContact={handleSelectContact}
|
||||
onSearch={handleSearchContacts}
|
||||
onContentFilter={setContentFilter}
|
||||
width={sidebarWidth}
|
||||
dateRange={dateRange}
|
||||
onDateRangeChange={handleDateRangeChange}
|
||||
selfInfo={selfInfo}
|
||||
dbReady={isAuthenticated}
|
||||
onOpenSettings={() => setShowSettings(true)}
|
||||
/>
|
||||
<div className="resizer" onMouseDown={startResizing} />
|
||||
<ChatWindow
|
||||
key={`${selectedContact?.md5}-${contentFilter}`}
|
||||
contact={selectedContact}
|
||||
messages={messages}
|
||||
isLoadingMessages={isMessagesLoading}
|
||||
contentFilter={contentFilter}
|
||||
onRefresh={() => selectedContact && handleSelectContact(selectedContact)}
|
||||
onRefreshData={loadContacts}
|
||||
/>
|
||||
</div>
|
||||
<SettingsPanel
|
||||
open={showSettings}
|
||||
selfInfo={selfInfo}
|
||||
@@ -866,7 +880,7 @@ function App(): React.ReactElement {
|
||||
void loadContacts()
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
</AppShell>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -13,9 +13,9 @@
|
||||
body {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
|
||||
background-color: var(--bg-color);
|
||||
color: var(--text-color);
|
||||
font-family: var(--wxex-font);
|
||||
background-color: var(--wxex-bg-app);
|
||||
color: var(--wxex-text-primary);
|
||||
height: 100vh;
|
||||
overflow: hidden;
|
||||
}
|
||||
@@ -25,10 +25,277 @@ body {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.app-shell {
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
overflow: hidden;
|
||||
background: var(--wxex-bg-app);
|
||||
}
|
||||
|
||||
.app-primary-rail {
|
||||
box-sizing: border-box;
|
||||
width: var(--wxex-nav-width);
|
||||
flex: 0 0 var(--wxex-nav-width);
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
border-right: 1px solid var(--wxex-border);
|
||||
background: var(--wxex-bg-sidebar);
|
||||
padding: 12px 8px;
|
||||
overflow: hidden;
|
||||
-webkit-app-region: drag;
|
||||
}
|
||||
|
||||
.app-brand {
|
||||
box-sizing: border-box;
|
||||
width: 42px;
|
||||
height: 42px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
border: 0;
|
||||
border-radius: var(--wxex-radius-md);
|
||||
background: var(--wxex-brand);
|
||||
color: #ffffff;
|
||||
-webkit-app-region: no-drag;
|
||||
}
|
||||
|
||||
.app-brand svg {
|
||||
width: 34px;
|
||||
height: 34px;
|
||||
}
|
||||
|
||||
.app-brand path {
|
||||
fill: none;
|
||||
stroke: currentColor;
|
||||
stroke-width: 1.8;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
}
|
||||
|
||||
.app-brand circle {
|
||||
fill: currentColor;
|
||||
}
|
||||
|
||||
.primary-navigation {
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
flex: 1;
|
||||
flex-direction: column;
|
||||
gap: 6px;
|
||||
width: 100%;
|
||||
margin-top: 18px;
|
||||
-webkit-app-region: no-drag;
|
||||
}
|
||||
|
||||
.primary-nav-item {
|
||||
width: 100%;
|
||||
height: 58px;
|
||||
flex: 0 0 58px;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
gap: 4px;
|
||||
border: 0;
|
||||
border-radius: var(--wxex-radius-md);
|
||||
background: transparent;
|
||||
color: var(--wxex-text-secondary);
|
||||
cursor: pointer;
|
||||
font-family: var(--wxex-font);
|
||||
}
|
||||
|
||||
.primary-nav-item:hover {
|
||||
background: rgba(255, 255, 255, 0.46);
|
||||
color: var(--wxex-text-primary);
|
||||
}
|
||||
|
||||
.primary-nav-item.active {
|
||||
background: var(--wxex-brand-soft);
|
||||
color: var(--wxex-brand);
|
||||
}
|
||||
|
||||
.primary-nav-mark {
|
||||
width: 24px;
|
||||
height: 24px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
}
|
||||
|
||||
.primary-nav-mark svg {
|
||||
width: 22px;
|
||||
height: 22px;
|
||||
}
|
||||
|
||||
.primary-nav-mark path,
|
||||
.primary-nav-mark circle {
|
||||
fill: none;
|
||||
stroke: currentColor;
|
||||
stroke-width: 1.7;
|
||||
stroke-linecap: round;
|
||||
stroke-linejoin: round;
|
||||
}
|
||||
|
||||
.primary-nav-label {
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
font-weight: 600;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.app-rail-account {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
flex: 0 0 auto;
|
||||
-webkit-app-region: no-drag;
|
||||
}
|
||||
|
||||
.account-summary {
|
||||
box-sizing: border-box;
|
||||
width: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
border: 0;
|
||||
border-radius: var(--wxex-radius-md);
|
||||
background: transparent;
|
||||
color: var(--wxex-text-primary);
|
||||
cursor: pointer;
|
||||
font-family: var(--wxex-font);
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
.account-summary:hover {
|
||||
background: rgba(255, 255, 255, 0.72);
|
||||
}
|
||||
|
||||
.account-summary.compact {
|
||||
justify-content: center;
|
||||
padding: 8px 0;
|
||||
}
|
||||
|
||||
.account-summary-avatar {
|
||||
box-sizing: border-box;
|
||||
position: relative;
|
||||
width: 38px;
|
||||
height: 38px;
|
||||
display: grid;
|
||||
place-items: center;
|
||||
flex: 0 0 auto;
|
||||
border-radius: var(--wxex-radius-md);
|
||||
background: var(--wxex-bg-elevated);
|
||||
color: var(--wxex-brand);
|
||||
font-size: 14px;
|
||||
font-weight: 700;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.account-summary-avatar img {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
object-fit: cover;
|
||||
}
|
||||
|
||||
.account-summary-status {
|
||||
position: absolute;
|
||||
right: 2px;
|
||||
bottom: 2px;
|
||||
width: 9px;
|
||||
height: 9px;
|
||||
border: 2px solid var(--wxex-bg-elevated);
|
||||
border-radius: 999px;
|
||||
background: var(--wxex-text-muted);
|
||||
}
|
||||
|
||||
.account-summary-status.ready {
|
||||
background: var(--wxex-success);
|
||||
}
|
||||
|
||||
.account-summary-text {
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.account-summary-name {
|
||||
overflow: hidden;
|
||||
color: var(--wxex-text-primary);
|
||||
font-size: 13px;
|
||||
font-weight: 600;
|
||||
line-height: 18px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.account-summary-meta {
|
||||
overflow: hidden;
|
||||
color: var(--wxex-text-muted);
|
||||
font-size: 12px;
|
||||
line-height: 16px;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.app-shell-main {
|
||||
box-sizing: border-box;
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
min-height: 0;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
overflow: hidden;
|
||||
padding-top: var(--wxex-shell-content-top);
|
||||
background: var(--wxex-bg-main);
|
||||
}
|
||||
|
||||
.app-page-placeholder {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 24px;
|
||||
color: var(--wxex-text-secondary);
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.app-page-placeholder-eyebrow {
|
||||
margin-bottom: 8px;
|
||||
color: var(--wxex-brand);
|
||||
font-size: 11px;
|
||||
font-weight: 700;
|
||||
letter-spacing: 0;
|
||||
}
|
||||
|
||||
.app-page-placeholder h2 {
|
||||
margin: 0 0 8px;
|
||||
color: var(--wxex-text-primary);
|
||||
font-size: 20px;
|
||||
font-weight: 600;
|
||||
line-height: 28px;
|
||||
}
|
||||
|
||||
.app-page-placeholder p {
|
||||
margin: 0;
|
||||
font-size: 13px;
|
||||
line-height: 20px;
|
||||
}
|
||||
|
||||
.app-container {
|
||||
display: flex;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
align-items: stretch;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.sidebar {
|
||||
@@ -37,11 +304,13 @@ body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
flex-shrink: 0;
|
||||
/* Prevent shrinking */
|
||||
}
|
||||
|
||||
.sidebar-header {
|
||||
flex: 0 0 auto;
|
||||
padding: 10px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
-webkit-app-region: drag;
|
||||
@@ -208,12 +477,15 @@ body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
background-color: var(--chat-bg);
|
||||
height: 100%;
|
||||
min-height: 0;
|
||||
min-width: 0;
|
||||
/* Allow flex item to shrink below content size */
|
||||
}
|
||||
|
||||
.chat-header {
|
||||
height: 50px;
|
||||
flex: 0 0 50px;
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
@@ -0,0 +1,50 @@
|
||||
import React from 'react'
|
||||
|
||||
interface SelfInfo {
|
||||
wxid: string
|
||||
nickname: string
|
||||
avatar?: string
|
||||
accountRoot: string
|
||||
}
|
||||
|
||||
interface AccountSummaryProps {
|
||||
selfInfo: SelfInfo | null
|
||||
dbReady: boolean
|
||||
compact?: boolean
|
||||
onClick?: () => void
|
||||
}
|
||||
|
||||
export function AccountSummary({
|
||||
selfInfo,
|
||||
dbReady,
|
||||
compact = false,
|
||||
onClick
|
||||
}: AccountSummaryProps): React.ReactElement {
|
||||
const displayName = dbReady && selfInfo ? selfInfo.nickname || selfInfo.wxid || '当前账号' : '未连接'
|
||||
const subtitle = dbReady && selfInfo ? selfInfo.wxid : '打开设置'
|
||||
const initial = (displayName || '?').charAt(0)
|
||||
|
||||
return (
|
||||
<button
|
||||
type="button"
|
||||
className={`account-summary ${compact ? 'compact' : ''}`}
|
||||
onClick={onClick}
|
||||
title={compact ? `${displayName}\n${subtitle}` : undefined}
|
||||
>
|
||||
<span className="account-summary-avatar">
|
||||
{selfInfo?.avatar ? (
|
||||
<img src={selfInfo.avatar} alt={displayName} referrerPolicy="no-referrer" />
|
||||
) : (
|
||||
initial
|
||||
)}
|
||||
<span className={`account-summary-status ${dbReady ? 'ready' : ''}`} aria-hidden />
|
||||
</span>
|
||||
{!compact && (
|
||||
<span className="account-summary-text">
|
||||
<span className="account-summary-name">{displayName}</span>
|
||||
<span className="account-summary-meta">{subtitle}</span>
|
||||
</span>
|
||||
)}
|
||||
</button>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
import React from 'react'
|
||||
import { AccountSummary } from '../account/AccountSummary'
|
||||
import { PrimaryNavigation } from './PrimaryNavigation'
|
||||
import { AppPage, PRIMARY_NAV_ITEMS } from './navigation'
|
||||
|
||||
interface SelfInfo {
|
||||
wxid: string
|
||||
nickname: string
|
||||
avatar?: string
|
||||
accountRoot: string
|
||||
}
|
||||
|
||||
interface AppShellProps {
|
||||
activePage: AppPage
|
||||
selfInfo: SelfInfo | null
|
||||
dbReady: boolean
|
||||
onPageChange: (page: AppPage) => void
|
||||
onOpenSettings: () => void
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
function BrandLogo(): React.ReactElement {
|
||||
return (
|
||||
<div className="app-brand" title="WechatExplorer" aria-label="WechatExplorer">
|
||||
<svg viewBox="0 0 40 40" aria-hidden="true" focusable="false">
|
||||
<path d="M13 15.5 20 10l7 5.5" />
|
||||
<path d="M13 24.5 20 30l7-5.5" />
|
||||
<path d="M13 15.5v9" />
|
||||
<path d="M27 15.5v9" />
|
||||
<circle cx="20" cy="10" r="2.6" />
|
||||
<circle cx="13" cy="15.5" r="2.6" />
|
||||
<circle cx="27" cy="15.5" r="2.6" />
|
||||
<circle cx="13" cy="24.5" r="2.6" />
|
||||
<circle cx="27" cy="24.5" r="2.6" />
|
||||
<circle cx="20" cy="30" r="2.6" />
|
||||
</svg>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
export function AppShell({
|
||||
activePage,
|
||||
selfInfo,
|
||||
dbReady,
|
||||
onPageChange,
|
||||
onOpenSettings,
|
||||
children
|
||||
}: AppShellProps): React.ReactElement {
|
||||
const activeItem = PRIMARY_NAV_ITEMS.find((item) => item.id === activePage)
|
||||
|
||||
return (
|
||||
<div className="app-shell">
|
||||
<aside className="app-primary-rail">
|
||||
<BrandLogo />
|
||||
<PrimaryNavigation activePage={activePage} onPageChange={onPageChange} />
|
||||
<div className="app-rail-account">
|
||||
<AccountSummary selfInfo={selfInfo} dbReady={dbReady} compact onClick={onOpenSettings} />
|
||||
</div>
|
||||
</aside>
|
||||
<main className="app-shell-main">
|
||||
{activePage === 'archive' ? (
|
||||
children
|
||||
) : (
|
||||
<div className="app-page-placeholder">
|
||||
<div className="app-page-placeholder-eyebrow">WechatExplorer</div>
|
||||
<h2>{activeItem?.label || '工作区'}</h2>
|
||||
<p>这个工作区会在后续 UI 重构阶段接入真实功能。</p>
|
||||
</div>
|
||||
)}
|
||||
</main>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
import React from 'react'
|
||||
import { AppPage, PRIMARY_NAV_ITEMS } from './navigation'
|
||||
|
||||
interface NavIconProps {
|
||||
page: AppPage
|
||||
}
|
||||
|
||||
interface PrimaryNavigationProps {
|
||||
activePage: AppPage
|
||||
onPageChange: (page: AppPage) => void
|
||||
}
|
||||
|
||||
function NavIcon({ page }: NavIconProps): React.ReactElement {
|
||||
switch (page) {
|
||||
case 'archive':
|
||||
return (
|
||||
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
|
||||
<path d="M5 5.5h14v14H5z" />
|
||||
<path d="M8 8.5h8" />
|
||||
<path d="M8 12h8" />
|
||||
<path d="M8 15.5h5" />
|
||||
</svg>
|
||||
)
|
||||
case 'search':
|
||||
return (
|
||||
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
|
||||
<circle cx="10.5" cy="10.5" r="5.5" />
|
||||
<path d="m15 15 4 4" />
|
||||
</svg>
|
||||
)
|
||||
case 'report':
|
||||
return (
|
||||
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
|
||||
<path d="M7 4.5h7l3 3v12H7z" />
|
||||
<path d="M14 4.5v3h3" />
|
||||
<path d="M9.5 13.5h5" />
|
||||
<path d="M9.5 16.5h4" />
|
||||
</svg>
|
||||
)
|
||||
case 'export':
|
||||
return (
|
||||
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
|
||||
<path d="M12 4.5v9" />
|
||||
<path d="m8.5 10 3.5 3.5 3.5-3.5" />
|
||||
<path d="M5.5 15.5v3h13v-3" />
|
||||
</svg>
|
||||
)
|
||||
case 'api':
|
||||
return (
|
||||
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
|
||||
<path d="M8 8.5 4.5 12 8 15.5" />
|
||||
<path d="m16 8.5 3.5 3.5-3.5 3.5" />
|
||||
<path d="m13.5 6.5-3 11" />
|
||||
</svg>
|
||||
)
|
||||
case 'settings':
|
||||
return (
|
||||
<svg viewBox="0 0 24 24" aria-hidden="true" focusable="false">
|
||||
<circle cx="12" cy="12" r="3" />
|
||||
<path d="M12 4.5v2" />
|
||||
<path d="M12 17.5v2" />
|
||||
<path d="M4.5 12h2" />
|
||||
<path d="M17.5 12h2" />
|
||||
<path d="m6.8 6.8 1.4 1.4" />
|
||||
<path d="m15.8 15.8 1.4 1.4" />
|
||||
<path d="m17.2 6.8-1.4 1.4" />
|
||||
<path d="m8.2 15.8-1.4 1.4" />
|
||||
</svg>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export function PrimaryNavigation({
|
||||
activePage,
|
||||
onPageChange
|
||||
}: PrimaryNavigationProps): React.ReactElement {
|
||||
return (
|
||||
<nav className="primary-navigation" aria-label="一级导航">
|
||||
{PRIMARY_NAV_ITEMS.map((item) => (
|
||||
<button
|
||||
key={item.id}
|
||||
type="button"
|
||||
className={`primary-nav-item ${activePage === item.id ? 'active' : ''}`}
|
||||
onClick={() => onPageChange(item.id)}
|
||||
title={item.label}
|
||||
>
|
||||
<span className="primary-nav-mark" aria-hidden>
|
||||
<NavIcon page={item.id} />
|
||||
</span>
|
||||
<span className="primary-nav-label">{item.label}</span>
|
||||
</button>
|
||||
))}
|
||||
</nav>
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
export type AppPage = 'archive' | 'search' | 'report' | 'export' | 'api' | 'settings'
|
||||
|
||||
export interface NavigationItem {
|
||||
id: AppPage
|
||||
label: string
|
||||
}
|
||||
|
||||
export const PRIMARY_NAV_ITEMS: NavigationItem[] = [
|
||||
{ id: 'archive', label: '档案' },
|
||||
{ id: 'search', label: '检索' },
|
||||
{ id: 'report', label: '日报' },
|
||||
{ id: 'export', label: '导出' },
|
||||
{ id: 'api', label: 'API' },
|
||||
{ id: 'settings', label: '设置' }
|
||||
]
|
||||
@@ -1,6 +1,7 @@
|
||||
import React from 'react'
|
||||
import ReactDOM from 'react-dom/client'
|
||||
import App from './App'
|
||||
import './styles/tokens.css'
|
||||
import './assets/main.css'
|
||||
|
||||
ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render(
|
||||
|
||||
@@ -0,0 +1,26 @@
|
||||
:root {
|
||||
--wxex-bg-app: #f4f6f5;
|
||||
--wxex-bg-main: #fafbfa;
|
||||
--wxex-bg-sidebar: #eef2f0;
|
||||
--wxex-bg-elevated: #ffffff;
|
||||
--wxex-text-primary: #202724;
|
||||
--wxex-text-secondary: #66706b;
|
||||
--wxex-text-muted: #929a96;
|
||||
--wxex-border: #dde3e0;
|
||||
--wxex-brand: #247a63;
|
||||
--wxex-brand-hover: #1d6754;
|
||||
--wxex-brand-soft: #ddede7;
|
||||
--wxex-ai: #686edc;
|
||||
--wxex-ai-soft: #eeeffd;
|
||||
--wxex-success: #2e8b68;
|
||||
--wxex-warning: #c68635;
|
||||
--wxex-danger: #c85a5a;
|
||||
--wxex-font:
|
||||
-apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Microsoft YaHei', sans-serif;
|
||||
--wxex-nav-width: 76px;
|
||||
--wxex-shell-content-top: 12px;
|
||||
--wxex-radius-sm: 4px;
|
||||
--wxex-radius-md: 8px;
|
||||
--wxex-radius-lg: 12px;
|
||||
--wxex-shadow-popover: 0 4px 12px rgba(0, 0, 0, 0.08);
|
||||
}
|
||||
Reference in New Issue
Block a user