npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@wecode-team/we0-cms

v1.1.31

Published

A CMS component for React applications with shadcn/ui

Readme

@wecode-team/we0-cms

一个基于 React 的动态 CMS 前端组件库,支持 shadcn/ui 风格的现代化 UI,与 @wecode-team/cms-supabase-api 配合使用实现完整的内容管理系统。

🛡️ CSS样式隔离

本包已实现完整的CSS样式隔离,不会被外部项目的样式污染,也不会污染外部项目的样式

隔离机制

  1. CSS变量命名空间:所有设计token使用--we0-*前缀(如--we0-primary
  2. 容器作用域:所有样式仅在.we0-cms-root容器内生效
  3. important选择器:确保CMS样式优先级高于外部样式
  4. 自动包装:组件已自动包裹在隔离容器中,无需额外配置

使用方式

只需正常引入和使用,样式隔离会自动生效:

```tsx import CmsLayoutShadcn from '@wecode-team/we0-cms'; import '@wecode-team/we0-cms/dist/index.css'; // 引入CSS

function App() { return ( {/* 你的应用代码 */}

  {/* CMS组件 - 样式完全隔离 */}
  <CmsLayoutShadcn
    inputModels={{ models: yourModels }}
    brandName="My CMS"
  />
</div>

); } ```

无需任何额外配置,CMS的样式不会影响<YourApp />,外部样式也不会影响CMS。


📖 设计理念

核心思想

本包的核心理念是 "配置驱动的 CMS UI"

  1. 模型驱动 UI:根据 JSON Schema 模型配置自动生成表单和表格
  2. 零代码管理:无需编写代码,通过配置即可实现数据的增删改查
  3. 关系可视化:支持关联字段的下拉选择和数据展示
  4. 多租户支持:通过 Session ID 实现数据隔离

架构设计

┌─────────────────────────────────────────────────────────────┐
│                    CmsLayoutShadcn                          │
│                   (主布局组件)                               │
├─────────────────────────────────────────────────────────────┤
│  UI Components (shadcn/ui)                                  │
│  ├── Sidebar         侧边栏导航                             │
│  ├── DataTable       数据表格                               │
│  ├── Dialog          弹窗表单                               │
│  └── Form Controls   表单控件                               │
├─────────────────────────────────────────────────────────────┤
│  Pages (页面组件)                                            │
│  ├── LoginPage       登录页面                               │
│  ├── DataListPage    数据列表页                             │
│  └── DataManagePage  数据管理页(按模型)                    │
├─────────────────────────────────────────────────────────────┤
│  Services (服务层)                                           │
│  ├── modelApi        模型管理 API                           │
│  ├── dataApi         数据操作 API                           │
│  └── authApi         认证 API                               │
├─────────────────────────────────────────────────────────────┤
│  Request Layer (请求层)                                      │
│  └── request.ts      Axios 封装,自动添加 Token             │
└─────────────────────────────────────────────────────────────┘

数据流

用户操作 → UI 组件 → Services API → HTTP 请求 → 后端 API → Supabase
                                        ↓
用户界面 ← UI 更新 ← State 更新 ← API 响应 ←

🚀 安装

npm install @wecode-team/we0-cms
# 或
pnpm add @wecode-team/we0-cms
# 或
yarn add @wecode-team/we0-cms

📋 依赖

Peer Dependencies(必须安装)

{
  "react": "^18.2.0",
  "react-dom": "^18.2.0"
}

内置依赖

  • @radix-ui/* - 无障碍 UI 原语
  • lucide-react - 图标库
  • tailwind-merge - Tailwind 类名合并
  • class-variance-authority - 变体样式管理
  • dayjs - 日期处理

🔧 快速开始

第一步:配置 Tailwind CSS

确保你的项目已配置 Tailwind CSS:

// tailwind.config.js
module.exports = {
  content: [
    './src/**/*.{js,jsx,ts,tsx}',
    './node_modules/@wecode-team/we0-cms/dist/**/*.{js,jsx}'
  ],
  theme: {
    extend: {}
  },
  plugins: []
}

第二步:引入样式

import '@wecode-team/we0-cms/dist/index.css'

第三步:使用组件

import React from 'react'
import { CmsLayoutShadcn, setSessionId } from '@wecode-team/we0-cms'
import '@wecode-team/we0-cms/dist/index.css'

// 设置 Session ID(用于多租户隔离)
setSessionId('your-session-id')

// 定义模型配置
const modelData = {
  models: [
    {
      id: 1,
      name: '用户模型',
      table_name: 'users',
      json_schema: {
        fields: [
          {
            name: 'name',
            type: 'string',
            comment: '用户姓名',
            required: true,
            maxLength: 100
          },
          {
            name: 'email',
            type: 'email',
            unique: true,
            comment: '用户邮箱',
            required: true
          },
          {
            name: 'age',
            type: 'integer',
            comment: '年龄',
            required: false
          }
        ]
      },
      created_at: '2023-07-18T19:00:28.098Z',
      updated_at: '2023-07-18T19:00:28.099Z'
    }
  ]
}

function App() {
  const uploadHandler = async (file, context) => {
    const body = new FormData()
    body.append('file', file)
    body.append('directory', context.field.upload?.directory || '')

    const response = await fetch('/your-upload-api', {
      method: 'POST',
      body,
    }).then((res) => res.json())

    return { url: response.url }
  }

  return (
    <div className="min-h-screen">
      <CmsLayoutShadcn inputModels={modelData} uploadHandler={uploadHandler} />
    </div>
  )
}

export default App

📚 组件 API

CmsLayoutShadcn

主布局组件,包含侧边栏、导航和内容区域。

interface CmsLayoutProps {
  inputModels: {
    models: CmsModel[]
  }
  /**
   * 是否跳过登录验证(免登录模式)
   * 当设置为 true 时,组件将跳过所有登录检查,直接进入后台配置页面
   * 适用于由使用方统一处理鉴权的场景
   * @default false
   */
  skipAuth?: boolean
  /**
   * 处理 schema 中 asset 字段上传的宿主回调
   */
  uploadHandler?: CmsUploadHandler
}

Props

| 属性 | 类型 | 默认值 | 说明 | |------|------|--------|------| | inputModels | { models: CmsModel[] } | - | 模型配置对象 | | skipAuth | boolean | false | 是否跳过登录验证 | | brandName | string | "WE0-CMS" | 侧边栏顶部显示的品牌名 | | defaultLocale | "zh-CN" \| "en-US" | "zh-CN" | 默认语言 | | uploadHandler | CmsUploadHandler | - | 宿主注入的资源上传回调,供 asset 字段使用 |

使用示例

// 标准模式(需要登录)
<CmsLayoutShadcn inputModels={modelData} />

// 免登录模式(适用于已有鉴权系统)
<CmsLayoutShadcn inputModels={modelData} skipAuth={true} />

// 设置默认语言为英文
<CmsLayoutShadcn inputModels={modelData} defaultLocale="en-US" />

// 启用资源上传字段
<CmsLayoutShadcn inputModels={modelData} uploadHandler={uploadHandler} />

资源上传字段

如果 schema 里有需要在 CMS 后台上传资源并回填 URL 的字段,可以使用 type: "asset"

const siteConfigModel = {
  id: 10,
  name: '站点配置',
  table_name: 'site_settings',
  json_schema: {
    fields: [
      {
        name: 'site_name',
        type: 'string',
        comment: '站点名称',
        required: true,
      },
      {
        name: 'logo',
        type: 'asset',
        comment: '网站 Logo',
        upload: {
          accept: 'image/*',
          maxSize: 2 * 1024 * 1024,
          directory: 'logos',
          placeholder: '请上传网站 Logo',
          buttonText: '上传 Logo',
        },
      },
    ],
  },
}

asset 字段的值始终是纯 URL 字符串。CMS 只负责选文件、调用 uploadHandler、回填 URL 和展示预览。

上传字段 Schema 写法

下面这个例子适合“网站 Logo / Banner / 封面图”这类字段:

const siteConfigModel = {
  id: 10,
  name: "站点配置",
  table_name: "site_settings",
  json_schema: {
    fields: [
      {
        name: "site_name",
        type: "string",
        comment: "站点名称",
        required: true,
      },
      {
        name: "logo",
        type: "asset",
        comment: "网站 Logo",
        required: false,
        upload: {
          accept: "image/*",
          maxSize: 2 * 1024 * 1024,
          directory: "logos",
          placeholder: "请上传网站 Logo",
          buttonText: "上传 Logo",
        },
      },
      {
        name: "favicon",
        type: "asset",
        comment: "站点图标",
        required: false,
        upload: {
          accept: ".ico,image/png,image/svg+xml",
          maxSize: 512 * 1024,
          directory: "favicons",
          placeholder: "请上传 favicon",
          buttonText: "上传 favicon",
        },
      },
    ],
  },
}

upload 配置说明:

| 字段 | 类型 | 说明 | |------|------|------| | accept | string | 文件类型限制,等同于 <input type="file" accept="..."> | | maxSize | number | 单文件大小限制,单位字节 | | directory | string | 透传给宿主上传函数的目录标识,CMS 不直接处理存储 | | placeholder | string | 字段为空时的占位文案 | | buttonText | string | 上传按钮文案 |

宿主如何传入上传函数

uploadHandler 由使用 we0-cms 的宿主项目传入,CMS 在用户选中文件后会调用它:

type CmsUploadHandler = (
  file: File,
  context: {
    field: SchemaField
    model: CmsModel
    sessionId?: string
  }
) => Promise<{ url: string }>

也就是说,宿主上传函数至少会拿到:

  • file: 用户刚刚选择的文件对象
  • context.field: 当前 schema 字段配置,可以读取 field.namefield.upload?.directory 等信息
  • context.model: 当前模型信息,可以读取 table_namename
  • context.sessionId: 如果你调用过 setSessionId(),这里会带上当前 sessionId

一个完整示例如下:

import React from "react"
import {
  CmsLayoutShadcn,
  CmsUploadHandler,
  setSessionId,
} from "@wecode-team/we0-cms"

setSessionId("your-session-id")

const uploadHandler: CmsUploadHandler = async (file, context) => {
  const formData = new FormData()
  formData.append("file", file)
  formData.append("fieldName", context.field.name)
  formData.append("tableName", context.model.table_name)
  formData.append("directory", context.field.upload?.directory || "")
  formData.append("sessionId", context.sessionId || "")

  const response = await fetch("/api/upload", {
    method: "POST",
    body: formData,
  }).then((res) => res.json())

  if (!response?.url) {
    throw new Error("Upload API did not return a url")
  }

  return {
    url: response.url,
  }
}

function App() {
  return (
    <CmsLayoutShadcn
      inputModels={{ models: [siteConfigModel] }}
      uploadHandler={uploadHandler}
    />
  )
}

如果你使用的是 Supabase Storage / S3 / OSS,也推荐保持同样的返回格式:

return { url: "https://cdn.example.com/logos/logo.png" }

CMS 不关心你怎么上传,只要求最终返回可展示、可存库的 URL。

🌍 国际化(i18n)

CMS组件内置了完整的国际化支持,可在中英文之间自由切换。

支持的语言

  • 简体中文(zh-CN)- 默认
  • English(en-US)

切换语言

在侧边栏底部点击语言图标,选择所需语言即可切换。语言偏好会自动保存。

设置默认语言

<CmsLayoutShadcn
  inputModels={modelData}
  defaultLocale="en-US" // 使用英文
/>

在自定义组件中使用i18n

import { useT, useLocale } from '@wecode-team/we0-cms';

function MyComponent() {
  const t = useT(); // 翻译函数
  const { locale, setLocale } = useLocale(); // 语言状态

  return (
    <div>
      <p>{t('auth.welcome')}</p>
      <button onClick={() => setLocale('en-US')}>Switch to English</button>
    </div>
  );
}

i18n Hooks

| Hook | 返回值 | 说明 | |------|--------|------| | useT() | (key: string) => string | 翻译函数 | | useLocale() | { locale, setLocale } | 语言状态和切换函数 | | useTranslation() | { t, locale, setLocale, messages } | 完整i18n上下文 |

setSessionId

设置会话 ID,用于多租户数据隔离。

import { setSessionId } from '@wecode-team/we0-cms'

// 设置 Session ID
setSessionId('tenant-123')

// 后续 API 调用会自动添加前缀
// 例如:GET /data/users → GET /data/tenant-123_users

🏗️ 模型配置

CmsModel 结构

interface CmsModel {
  id: number
  name: string           // 模型显示名称
  table_name: string     // 数据表名
  json_schema: {
    fields: SchemaField[]
  }
  created_at: string
  updated_at: string
}

SchemaField 结构

interface SchemaField {
  name: string           // 字段名
  type: string           // 字段类型
  comment?: string       // 字段显示名称
  required?: boolean     // 是否必填
  unique?: boolean       // 是否唯一
  maxLength?: number     // 最大长度
  defaultValue?: any     // 默认值
  relation?: RelationConfig  // 关联配置
  editable?: boolean     // 是否允许在后台编辑(默认 true;false=不可编辑)
  readOnly?: boolean     // 兼容字段:readOnly=true 等价于 editable=false
}

字段不可编辑(协议层)

当你希望某些字段(如 created_atupdated_atowner_idtoken)在后台 只展示不允许修改 时,可以在字段上标记:

  • editable: false(推荐)
  • readOnly: true(兼容写法)

前端行为:

  • 表单控件会被禁用
  • create/update 提交时会自动跳过该字段(不会传到后端)

示例:

{
  name: "created_at",
  type: "datetime",
  comment: "创建时间",
  readOnly: true
},
{
  name: "owner_id",
  type: "string",
  comment: "归属用户",
  editable: false
}

字段类型

| 类型 | 渲染组件 | 说明 | |------|----------|------| | string | <Input /> | 单行文本输入 | | text | <Textarea /> | 多行文本输入 | | integer | <Input type="number" /> | 整数输入 | | float | <Input type="number" /> | 浮点数输入 | | boolean | <Switch /> | 开关切换 | | date | <Input type="date" /> | 日期选择 | | datetime | <Input type="datetime-local" /> | 日期时间选择 | | email | <Input type="email" /> | 邮箱输入 | | relation | <Select /> | 关联下拉选择 |

关联字段配置

interface RelationConfig {
  type: 'belongsTo' | 'hasMany' | 'belongsToMany'
  target: string         // 目标表名
  foreignKey?: string    // 外键字段名
  displayField?: string  // 下拉显示的字段
  showInList?: boolean   // 是否在列表中显示
}

示例:文章关联作者

{
  name: 'author',
  type: 'relation',
  comment: '作者',
  required: true,
  relation: {
    type: 'belongsTo',
    target: 'users',        // 关联 users 表
    foreignKey: 'author_id', // 外键字段
    displayField: 'name'     // 下拉显示用户姓名
  }
}

时间戳和枚举字段配置

CMS支持多种时间格式和枚举字段类型,以满足不同的业务需求。

时间戳字段(timestamp)

时间戳字段支持多种显示格式:

{
  name: 'last_login',
  type: 'timestamp',
  comment: '最后登录时间',
  format: 'datetime',  // 可选: 'date' | 'datetime' | 'time' | 'timestamp' | 'iso'
  required: false
}

支持的format选项

  • date: 仅日期(YYYY-MM-DD)
  • datetime: 日期时间(YYYY-MM-DD HH:mm:ss)
  • time: 仅时间(HH:mm:ss)
  • timestamp: Unix时间戳(毫秒)
  • iso: ISO 8601格式

枚举字段(enum)

枚举字段支持下拉选择,可配置选项标签:

{
  name: 'status',
  type: 'string',
  comment: '状态',
  required: true,
  enum: ['active', 'inactive', 'pending'],  // 枚举值
  enumLabels: {                               // 可选:显示标签
    'active': '激活',
    'inactive': '未激活',
    'pending': '待审核'
  }
}

日期和时间字段

// 日期字段
{
  name: 'birthday',
  type: 'date',
  comment: '生日',
  format: 'date',  // YYYY-MM-DD
  required: false
}

// 日期时间字段
{
  name: 'created_at',
  type: 'datetime',
  comment: '创建时间',
  format: 'datetime',  // YYYY-MM-DD HH:mm:ss
  required: true
}

// 时间字段
{
  name: 'work_time',
  type: 'time',
  comment: '工作时间',
  format: 'time',  // HH:mm:ss
  required: false
}

完整示例

const userModel = {
  name: "用户模型",
  table_name: "users",
  json_schema: {
    fields: [
      {
        name: "name",
        type: "string",
        comment: "用户姓名",
        required: true
      },
      {
        name: "status",
        type: "string",
        comment: "状态",
        required: true,
        enum: ["active", "inactive", "pending"],
        enumLabels: {
          active: "激活",
          inactive: "未激活",
          pending: "待审核"
        }
      },
      {
        name: "birthday",
        type: "date",
        comment: "生日",
        format: "date"
      },
      {
        name: "last_login",
        type: "timestamp",
        comment: "最后登录时间",
        format: "datetime"
      }
    ]
  }
}

📡 API 配置

配置 API 基础 URL

request.ts 中配置:

// 创建自定义 request 实例
import axios from 'axios'

const request = axios.create({
  baseURL: 'http://your-api-url/api/cms',
  timeout: 10000
})

// 请求拦截器 - 自动添加 Token
request.interceptors.request.use((config) => {
  const token = localStorage.getItem('cms_token')
  if (token) {
    config.headers.Authorization = `Bearer ${token}`
  }
  return config
})

API 服务

import { modelApi, dataApi, authApi } from '@wecode-team/we0-cms'

// 模型 API
await modelApi.getModels()
await modelApi.createModel(data)
await modelApi.updateModel(data)
await modelApi.deleteModel(id)

// 数据 API
await dataApi.getTableData('users', { page: 1, limit: 10 })
await dataApi.createData('users', { name: 'John' })
await dataApi.updateData('users', { id: '1', name: 'Jane' })
await dataApi.deleteData('users', '1')

// 关联选项 API
await dataApi.getRelationOptions('users', { displayField: 'name' })

// 认证 API
await authApi.login({ username: 'admin', password: '123456' })
await authApi.verifyAuth()
await authApi.getCurrentUser()
await authApi.logout()

🎨 自定义样式

覆盖 CSS 变量

:root {
  --background: 0 0% 100%;
  --foreground: 222.2 84% 4.9%;
  --primary: 222.2 47.4% 11.2%;
  --primary-foreground: 210 40% 98%;
  /* ... 更多变量 */
}

.dark {
  --background: 222.2 84% 4.9%;
  --foreground: 210 40% 98%;
  /* ... 暗色模式变量 */
}

扩展组件

import { CmsLayoutShadcn } from '@wecode-team/we0-cms'

function CustomCms() {
  return (
    <div className="custom-wrapper">
      <header className="custom-header">
        <h1>我的 CMS</h1>
      </header>
      <CmsLayoutShadcn inputModels={modelData} skipAuth={true} />
    </div>
  )
}

🔐 认证流程

1. 登录流程

用户输入账号密码 → 调用 authApi.login() → 保存 Token 到 localStorage
                                              ↓
                                        跳转到数据管理页

2. Token 验证

页面加载 → 检查 localStorage 中的 Token → 调用 authApi.verifyAuth()
                                              ↓
                                    验证成功:显示内容
                                    验证失败:跳转登录页

3. 免登录模式

// 适用于已有鉴权系统的场景
<CmsLayoutShadcn inputModels={modelData} skipAuth={true} />

📱 响应式设计

组件内置响应式支持:

  • 桌面端:完整侧边栏 + 内容区域
  • 平板端:可折叠侧边栏
  • 移动端:底部抽屉式导航

🐛 常见问题

1. 样式不生效

确保引入了 CSS 文件:

import '@wecode-team/we0-cms/dist/index.css'

2. Tailwind 类名冲突

确保 Tailwind 配置包含了组件库的路径:

content: [
  './node_modules/@wecode-team/we0-cms/dist/**/*.{js,jsx}'
]

3. 关联字段不显示数据

检查模型配置中的 relation.displayField 是否正确设置为目标表中存在的字段。

4. API 请求失败

检查:

  1. Session ID 是否正确设置
  2. API 基础 URL 是否正确
  3. CORS 是否配置正确

📄 许可证

MIT

🔗 相关包