Skip to content

Solid.js 版本

HaloLight Solid.js 版本基于 SolidStart 1.0 构建,采用 Solid.js 细粒度响应式 + TypeScript,实现高性能管理后台。

在线预览https://halolight-solidjs.h7ml.cn/

GitHubhttps://github.com/halolight/halolight-solidjs

技术栈

技术版本说明
SolidStart1.xSolid 全栈框架
Solid.js1.9+细粒度响应式框架
TypeScript5.x类型安全
Tailwind CSS4.x原子化 CSS
Kobalte0.13+无障碍 UI 原语
solid-primitiveslatest响应式工具库
Zod3.x数据验证
@solid-primitives/storagelatest持久化存储
solid-chartslatest图表可视化
Mock.js1.x数据模拟

核心特性

  • 细粒度响应式:无虚拟 DOM,精确追踪依赖更新
  • 编译时优化:JSX 编译为高效 DOM 操作
  • Signals:简洁的响应式原语
  • 服务端渲染:SolidStart 内置 SSR 支持
  • 文件路由:基于文件系统的路由
  • RPC:服务端函数无缝调用

目录结构

halolight-solidjs/
├── src/
│   ├── routes/                    # 文件路由
│   │   ├── index.tsx            # 首页
│   │   ├── (auth)/              # 认证路由组
│   │   │   ├── login.tsx
│   │   │   ├── register.tsx
│   │   │   ├── forgot-password.tsx
│   │   │   └── reset-password.tsx
│   │   ├── (dashboard)/         # 仪表盘路由组
│   │   │   ├── dashboard.tsx
│   │   │   ├── users/
│   │   │   │   ├── index.tsx
│   │   │   │   ├── create.tsx
│   │   │   │   └── [id].tsx
│   │   │   ├── roles.tsx
│   │   │   ├── permissions.tsx
│   │   │   ├── settings.tsx
│   │   │   └── profile.tsx
│   │   └── api/                 # API 路由
│   │       └── auth/
│   │           └── login.ts
│   ├── components/              # 组件库
│   │   ├── ui/                  # Kobalte 组件
│   │   ├── layout/              # 布局组件
│   │   │   ├── AdminLayout.tsx
│   │   │   ├── AuthLayout.tsx
│   │   │   ├── Sidebar.tsx
│   │   │   └── Header.tsx
│   │   ├── dashboard/           # 仪表盘组件
│   │   │   ├── DashboardGrid.tsx
│   │   │   ├── WidgetWrapper.tsx
│   │   │   └── StatsWidget.tsx
│   │   └── shared/              # 共享组件
│   │       └── PermissionGuard.tsx
│   ├── stores/                  # 状态管理
│   │   ├── auth.ts
│   │   ├── ui-settings.ts
│   │   └── dashboard.ts
│   ├── lib/                     # 工具库
│   │   ├── api.ts
│   │   ├── permission.ts
│   │   └── cn.ts
│   ├── server/                  # 服务端代码
│   │   ├── auth.ts
│   │   └── middleware.ts
│   └── types/                   # 类型定义
├── public/                      # 静态资源
├── app.config.ts               # SolidStart 配置
├── tailwind.config.ts          # Tailwind 配置
└── package.json

快速开始

安装

bash
git clone https://github.com/halolight/halolight-solidjs.git
cd halolight-solidjs
pnpm install

环境变量

bash
cp .env.example .env
env
# .env 示例
VITE_API_URL=/api
VITE_USE_MOCK=true
VITE_DEMO_EMAIL=admin@example.com
VITE_DEMO_PASSWORD=123456
VITE_SHOW_DEMO_HINT=true
VITE_APP_TITLE=Admin Pro
VITE_BRAND_NAME=Halolight

启动开发

bash
pnpm dev

访问 http://localhost:3000

构建生产

bash
pnpm build
pnpm start

核心功能

状态管理 (Signals + Store)

tsx
// stores/auth.ts
import { createSignal, createMemo } from 'solid-js'
import { createStore } from 'solid-js/store'
import { makePersisted } from '@solid-primitives/storage'

interface User {
  id: number
  name: string
  email: string
  permissions: string[]
}

interface AuthState {
  user: User | null
  token: string | null
}

const [state, setState] = makePersisted(
  createStore<AuthState>({
    user: null,
    token: null,
  }),
  { name: 'auth' }
)

const [loading, setLoading] = createSignal(false)

export const authStore = {
  get user() { return state.user },
  get token() { return state.token },
  get loading() { return loading() },

  isAuthenticated: createMemo(() => !!state.token && !!state.user),
  permissions: createMemo(() => state.user?.permissions ?? []),

  async login(credentials: { email: string; password: string }) {
    setLoading(true)
    try {
      const response = await fetch('/api/auth/login', {
        method: 'POST',
        body: JSON.stringify(credentials),
        headers: { 'Content-Type': 'application/json' },
      })
      const data = await response.json()

      setState({
        user: data.user,
        token: data.token,
      })
    } finally {
      setLoading(false)
    }
  },

  logout() {
    setState({ user: null, token: null })
  },

  hasPermission(permission: string): boolean {
    const perms = state.user?.permissions ?? []
    return perms.some(p =>
      p === '*' || p === permission ||
      (p.endsWith(':*') && permission.startsWith(p.slice(0, -1)))
    )
  },
}

路由中间件

tsx
// src/middleware.ts
import { createMiddleware } from '@solidjs/start/middleware'

export default createMiddleware({
  onRequest: [
    async (event) => {
      const url = new URL(event.request.url)

      // 保护 dashboard 路由
      if (url.pathname.startsWith('/dashboard')) {
        const token = event.request.headers.get('cookie')?.match(/token=([^;]+)/)?.[1]

        if (!token) {
          return new Response(null, {
            status: 302,
            headers: { Location: `/login?redirect=${url.pathname}` },
          })
        }
      }
    },
  ],
})

服务端函数 (RPC)

tsx
// server/auth.ts
'use server'
import { z } from 'zod'

const loginSchema = z.object({
  email: z.string().email(),
  password: z.string().min(6),
})

export async function login(credentials: z.infer<typeof loginSchema>) {
  const validated = loginSchema.parse(credentials)

  // 验证逻辑...

  return {
    success: true,
    user: { id: 1, name: '管理员', email: validated.email },
    token: 'mock_token',
  }
}

export async function getCurrentUser(token: string) {
  // 验证 token 并返回用户
  return {
    id: 1,
    name: '管理员',
    permissions: ['*'],
  }
}

权限组件

tsx
// components/shared/PermissionGuard.tsx
import { Show, type ParentComponent, type JSX } from 'solid-js'
import { authStore } from '~/stores/auth'

interface Props {
  permission: string
  fallback?: JSX.Element
}

export const PermissionGuard: ParentComponent<Props> = (props) => {
  const hasPermission = () => authStore.hasPermission(props.permission)

  return (
    <Show when={hasPermission()} fallback={props.fallback}>
      {props.children}
    </Show>
  )
}
tsx
// 使用
<PermissionGuard
  permission="users:delete"
  fallback={<span class="text-muted-foreground">无权限</span>}
>
  <Button variant="destructive">删除</Button>
</PermissionGuard>

数据获取

tsx
// routes/(dashboard)/users/index.tsx
import { createAsync, cache } from '@solidjs/router'

const getUsers = cache(async (params: { page: number }) => {
  'use server'
  const users = await db.users.findMany({
    skip: (params.page - 1) * 10,
    take: 10,
  })
  return users
}, 'users')

export const route = {
  load: ({ location }) => {
    const page = Number(location.query.page) || 1
    void getUsers({ page })
  },
}

export default function UsersPage() {
  const users = createAsync(() => getUsers({ page: 1 }))

  return (
    <div>
      <h1>用户列表</h1>
      <Show when={users()}>
        {(data) => (
          <For each={data()}>
            {(user) => <UserCard user={user} />}
          </For>
        )}
      </Show>
    </div>
  )
}

表单处理

tsx
// routes/(auth)/login.tsx
import { createSignal } from 'solid-js'
import { useNavigate, useSearchParams } from '@solidjs/router'
import { authStore } from '~/stores/auth'

export default function LoginPage() {
  const navigate = useNavigate()
  const [searchParams] = useSearchParams()
  const [email, setEmail] = createSignal('')
  const [password, setPassword] = createSignal('')
  const [error, setError] = createSignal('')

  const handleSubmit = async (e: Event) => {
    e.preventDefault()
    setError('')

    try {
      await authStore.login({
        email: email(),
        password: password(),
      })
      navigate(searchParams.redirect || '/dashboard')
    } catch (e) {
      setError('邮箱或密码错误')
    }
  }

  return (
    <form onSubmit={handleSubmit}>
      <Show when={error()}>
        <div class="text-destructive">{error()}</div>
      </Show>

      <input
        type="email"
        value={email()}
        onInput={(e) => setEmail(e.currentTarget.value)}
        placeholder="邮箱"
      />

      <input
        type="password"
        value={password()}
        onInput={(e) => setPassword(e.currentTarget.value)}
        placeholder="密码"
      />

      <button type="submit" disabled={authStore.loading}>
        {authStore.loading ? '登录中...' : '登录'}
      </button>
    </form>
  )
}

页面路由

路径页面权限
/首页公开
/login登录公开
/register注册公开
/forgot-password忘记密码公开
/reset-password重置密码公开
/dashboard仪表盘dashboard:view
/users用户列表users:list
/users/create创建用户users:create
/users/[id]用户详情users:view
/roles角色管理roles:list
/permissions权限管理permissions:list
/settings系统设置settings:view
/profile个人中心登录即可

配置

SolidStart 配置

ts
// app.config.ts
import { defineConfig } from '@solidjs/start/config'

export default defineConfig({
  server: {
    preset: 'node-server',
  },
  vite: {
    plugins: [],
  },
})

部署

Node.js 服务器

bash
pnpm build
node .output/server/index.mjs

Docker

dockerfile
FROM node:20-alpine AS builder
WORKDIR /app
COPY package.json pnpm-lock.yaml ./
RUN npm install -g pnpm && pnpm install --frozen-lockfile
COPY . .
RUN pnpm build

FROM node:20-alpine
WORKDIR /app
COPY --from=builder /app/.output ./.output
EXPOSE 3000
CMD ["node", ".output/server/index.mjs"]

Vercel

ts
// app.config.ts
export default defineConfig({
  server: {
    preset: 'vercel',
  },
})

Cloudflare Pages

ts
// app.config.ts
export default defineConfig({
  server: {
    preset: 'cloudflare-pages',
  },
})

测试

bash
# 运行测试
pnpm test

# E2E 测试
pnpm test:e2e

与其他版本对比

功能Solid.js 版本Vue 版本Next.js 版本
状态管理Signals + StorePiniaZustand
数据获取createAsyncTanStack QueryTanStack Query
表单验证自定义 + ZodVeeValidate + ZodReact Hook Form + Zod
服务端SolidStart 内置独立后端API Routes
组件库Kobalteshadcn-vueshadcn/ui
路由文件路由Vue RouterApp Router
响应式细粒度 SignalsProxy-basedHooks
Bundle 大小~7KB~33KB~85KB