diff --git a/.gitignore b/.gitignore index 51ccdbb..958dc77 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,11 @@ node_modules .vitepress/.temp dist *.log + +# 本地构建产物(多形态验证用) +.vitepress/dist-build* +.vitepress/final + +# 本地构建产物(多形态验证用) +.vitepress/dist-build* +.vitepress/final diff --git a/.vitepress/theme/components/CapabilityTabs.vue b/.vitepress/theme/components/CapabilityTabs.vue new file mode 100644 index 0000000..04c3ce2 --- /dev/null +++ b/.vitepress/theme/components/CapabilityTabs.vue @@ -0,0 +1,54 @@ + + + + + diff --git a/.vitepress/theme/components/FeatureGrid.vue b/.vitepress/theme/components/FeatureGrid.vue new file mode 100644 index 0000000..877b0bc --- /dev/null +++ b/.vitepress/theme/components/FeatureGrid.vue @@ -0,0 +1,75 @@ + + + + + diff --git a/.vitepress/theme/components/HomePage.vue b/.vitepress/theme/components/HomePage.vue new file mode 100644 index 0000000..a11b94d --- /dev/null +++ b/.vitepress/theme/components/HomePage.vue @@ -0,0 +1,134 @@ + + + diff --git a/.vitepress/theme/components/ProjectCard.vue b/.vitepress/theme/components/ProjectCard.vue index a3744cc..9ef598e 100644 --- a/.vitepress/theme/components/ProjectCard.vue +++ b/.vitepress/theme/components/ProjectCard.vue @@ -5,107 +5,19 @@ defineProps<{ project: Project }>() - - diff --git a/.vitepress/theme/components/ProjectList.vue b/.vitepress/theme/components/ProjectList.vue index a39caf6..96be2ee 100644 --- a/.vitepress/theme/components/ProjectList.vue +++ b/.vitepress/theme/components/ProjectList.vue @@ -4,16 +4,22 @@ import ProjectCard from './ProjectCard.vue' diff --git a/.vitepress/theme/index.ts b/.vitepress/theme/index.ts index b27d346..0357d09 100644 --- a/.vitepress/theme/index.ts +++ b/.vitepress/theme/index.ts @@ -1,11 +1,32 @@ import DefaultTheme from 'vitepress/theme' import type { Theme } from 'vitepress' +import { h } from 'vue' +import { useData } from 'vitepress' import ProjectList from './components/ProjectList.vue' +import HomePage from './components/HomePage.vue' +import CapabilityTabs from './components/CapabilityTabs.vue' +import FeatureGrid from './components/FeatureGrid.vue' + +import './styles/portal.css' + +/** + * 首页用 layout: page 承载自定义整页排版, + * 这里给它套一层 .portal-page,把默认窄栏与 padding 去掉。 + */ +const PortalLayout = () => { + const { frontmatter } = useData() + const isPortal = frontmatter.value.layout === 'page' + return h('div', { class: isPortal ? 'portal-page' : '' }, h(DefaultTheme.Layout)) +} export default { extends: DefaultTheme, + Layout: PortalLayout, enhanceApp({ app }) { app.component('ProjectList', ProjectList) + app.component('HomePage', HomePage) + app.component('CapabilityTabs', CapabilityTabs) + app.component('FeatureGrid', FeatureGrid) }, } satisfies Theme diff --git a/.vitepress/theme/styles/portal.css b/.vitepress/theme/styles/portal.css new file mode 100644 index 0000000..bfa92ce --- /dev/null +++ b/.vitepress/theme/styles/portal.css @@ -0,0 +1,743 @@ +/* ============================================================ + 雪羊科技开源门户 —— 视觉系统 + 独立的排版语言:中性底色 + 单一强调色,正文以留白和层级取胜。 + ============================================================ */ + +:root { + --p-accent: #4f46e5; + --p-accent-2: #06b6d4; + --p-accent-soft: rgba(79, 70, 229, 0.08); + --p-accent-line: rgba(79, 70, 229, 0.22); + --p-surface: #ffffff; + --p-surface-alt: #f7f8fb; + --p-line: rgba(15, 23, 42, 0.08); + --p-line-strong: rgba(15, 23, 42, 0.14); + --p-ink: #0f172a; + --p-ink-2: #475569; + --p-ink-3: #8a94a6; + --p-shadow: 0 1px 2px rgba(15, 23, 42, 0.04), 0 8px 24px -12px rgba(15, 23, 42, 0.12); + --p-shadow-hover: 0 1px 2px rgba(15, 23, 42, 0.05), 0 18px 40px -18px rgba(15, 23, 42, 0.22); + --p-radius: 16px; + --p-radius-sm: 10px; + --p-max: 1180px; +} + +.portal { + color: var(--p-ink); + background: var(--p-surface); +} + +html.dark { + --p-accent: #8b8bfa; + --p-accent-2: #45d3e8; + --p-accent-soft: rgba(139, 139, 250, 0.12); + --p-accent-line: rgba(139, 139, 250, 0.28); + --p-surface: #0c0f16; + --p-surface-alt: #11151e; + --p-line: rgba(255, 255, 255, 0.09); + --p-line-strong: rgba(255, 255, 255, 0.16); + --p-ink: #eef1f6; + --p-ink-2: #a8b2c4; + --p-ink-3: #6f7a8d; + --p-shadow: 0 1px 2px rgba(0, 0, 0, 0.3), 0 8px 24px -12px rgba(0, 0, 0, 0.5); + --p-shadow-hover: 0 1px 2px rgba(0, 0, 0, 0.35), 0 18px 40px -18px rgba(0, 0, 0, 0.7); +} + +/* 让 page 布局摆脱默认窄栏,门户整页自管宽度 */ +.portal-page .VPPage, +.portal-page .vp-doc.container { + max-width: none !important; + padding: 0 !important; +} + +.portal-page .VPContent { + padding: 0 !important; +} + +.portal-page .vp-doc > div > h1:first-child, +.portal-page .vp-doc > h1:first-child { + display: none; +} + +.portal *, +.portal *::before, +.portal *::after { + box-sizing: border-box; +} + +/* ---------- 容器与分区 ---------- */ +.p-wrap { + max-width: var(--p-max); + margin: 0 auto; + padding: 0 28px; +} + +.p-section { + padding: 96px 0; + border-top: 1px solid var(--p-line); +} + +.p-section--alt { + background: var(--p-surface-alt); +} + +.p-section--tight { + padding: 72px 0; +} + +/* ---------- 区块标题(居中) ---------- */ +.p-head { + max-width: 720px; + margin: 0 auto 56px; + text-align: center; +} + +.p-head--left { + margin-left: 0; + text-align: left; +} + +.p-eyebrow { + display: inline-flex; + align-items: center; + gap: 8px; + margin-bottom: 16px; + padding: 4px 12px; + font-size: 12px; + font-weight: 600; + letter-spacing: 0.06em; + color: var(--p-accent); + background: var(--p-accent-soft); + border: 1px solid var(--p-accent-line); + border-radius: 999px; +} + +.p-title { + margin: 0; + font-size: 36px; + font-weight: 650; + line-height: 1.25; + letter-spacing: -0.02em; + color: var(--p-ink); +} + +.p-desc { + margin: 16px 0 0; + font-size: 16px; + line-height: 1.75; + color: var(--p-ink-2); +} + +@media (max-width: 720px) { + .p-section { + padding: 64px 0; + } + .p-title { + font-size: 26px; + } + .p-desc { + font-size: 15px; + } + .p-head { + margin-bottom: 36px; + } +} + +/* ---------- Hero ---------- */ +.p-hero { + position: relative; + padding: 104px 0 88px; + text-align: center; + overflow: hidden; +} + +.p-hero::before { + content: ''; + position: absolute; + inset: -40% -20% auto; + height: 620px; + background: radial-gradient(50% 50% at 50% 50%, var(--p-accent-soft) 0%, transparent 70%); + pointer-events: none; +} + +.p-hero > .p-wrap { + position: relative; +} + +.p-hero-title { + margin: 20px 0 0; + font-size: 60px; + font-weight: 700; + line-height: 1.1; + letter-spacing: -0.035em; + color: var(--p-ink); +} + +.p-hero-title em { + font-style: normal; + background: linear-gradient(100deg, var(--p-accent), var(--p-accent-2)); + -webkit-background-clip: text; + background-clip: text; + -webkit-text-fill-color: transparent; +} + +.p-hero-sub { + margin: 22px auto 0; + max-width: 640px; + font-size: 17px; + line-height: 1.8; + color: var(--p-ink-2); +} + +.p-hero-actions { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 12px; + margin-top: 34px; +} + +.p-btn { + display: inline-flex; + align-items: center; + gap: 8px; + height: 46px; + padding: 0 26px; + font-size: 15px; + font-weight: 600; + border-radius: 999px; + border: 1px solid transparent; + text-decoration: none !important; + transition: transform 0.15s, box-shadow 0.2s, background 0.2s, border-color 0.2s; +} + +.p-btn:hover { + transform: translateY(-1px); +} + +.p-btn--primary { + color: #fff !important; + background: var(--p-accent); + box-shadow: 0 8px 20px -8px var(--p-accent); +} + +.p-btn--primary:hover { + box-shadow: 0 14px 28px -10px var(--p-accent); +} + +.p-btn--ghost { + color: var(--p-ink) !important; + background: var(--p-surface); + border-color: var(--p-line-strong); +} + +.p-btn--ghost:hover { + border-color: var(--p-accent); + color: var(--p-accent) !important; +} + +@media (max-width: 720px) { + .p-hero { + padding: 56px 0 48px; + } + .p-hero-title { + font-size: 29px; + letter-spacing: -0.02em; + line-height: 1.28; + } + .p-hero-sub { + font-size: 14.5px; + line-height: 1.75; + margin-top: 18px; + } + .p-hero-actions { + margin-top: 26px; + } + .p-btn { + height: 44px; + padding: 0 22px; + font-size: 14.5px; + } +} + +/* ---------- 数据条 ---------- */ +.p-stats { + display: grid; + grid-template-columns: repeat(4, 1fr); + gap: 0; + max-width: 900px; + margin: 64px auto 0; + padding: 28px 0; + border-top: 1px solid var(--p-line); + border-bottom: 1px solid var(--p-line); +} + +.p-stat { + text-align: center; + padding: 0 12px; +} + +.p-stat + .p-stat { + border-left: 1px solid var(--p-line); +} + +.p-stat-num { + display: block; + font-size: 34px; + font-weight: 700; + letter-spacing: -0.03em; + line-height: 1.1; + color: var(--p-ink); +} + +.p-stat-num span { + font-size: 18px; + font-weight: 600; + color: var(--p-accent); +} + +.p-stat-label { + display: block; + margin-top: 8px; + font-size: 13px; + color: var(--p-ink-3); +} + +@media (max-width: 720px) { + .p-stats { + grid-template-columns: repeat(2, 1fr); + gap: 24px 0; + margin-top: 44px; + } + .p-stat:nth-child(3) { + border-left: none; + } + .p-stat-num { + font-size: 26px; + } +} + +/* ---------- 通用卡片网格 ---------- */ +.p-grid { + display: grid; + gap: 20px; +} + +.p-grid--3 { + grid-template-columns: repeat(3, 1fr); +} + +.p-grid--4 { + grid-template-columns: repeat(4, 1fr); +} + +@media (max-width: 1024px) { + .p-grid--4 { + grid-template-columns: repeat(2, 1fr); + } +} + +@media (max-width: 860px) { + .p-grid--3, + .p-grid--4 { + grid-template-columns: 1fr; + } +} + +.p-card { + display: flex; + flex-direction: column; + padding: 28px 26px; + background: var(--p-surface); + border: 1px solid var(--p-line); + border-radius: var(--p-radius); + box-shadow: var(--p-shadow); + transition: transform 0.2s, box-shadow 0.2s, border-color 0.2s; +} + +.p-card:hover { + transform: translateY(-3px); + border-color: var(--p-accent-line); + box-shadow: var(--p-shadow-hover); +} + +.p-card-index { + display: inline-flex; + align-items: center; + justify-content: center; + width: 32px; + height: 32px; + margin-bottom: 16px; + font-size: 12px; + font-weight: 700; + letter-spacing: 0.02em; + color: var(--p-accent); + background: var(--p-accent-soft); + border-radius: 8px; +} + +.p-card-title { + margin: 0; + font-size: 16px; + font-weight: 650; + line-height: 1.4; + color: var(--p-ink); +} + +.p-card-text { + margin: 10px 0 0; + font-size: 14px; + line-height: 1.75; + color: var(--p-ink-2); +} + +/* ---------- 标签切换 ---------- */ +.p-tabs { + display: flex; + flex-wrap: wrap; + justify-content: center; + gap: 8px; + margin-bottom: 40px; +} + +.p-tab { + height: 38px; + padding: 0 18px; + font-family: inherit; + font-size: 14px; + font-weight: 500; + color: var(--p-ink-2); + background: transparent; + border: 1px solid var(--p-line-strong); + border-radius: 999px; + cursor: pointer; + transition: all 0.18s; +} + +.p-tab:hover { + color: var(--p-ink); + border-color: var(--p-ink-3); +} + +.p-tab.is-active { + color: #fff; + background: var(--p-accent); + border-color: var(--p-accent); +} + +html.dark .p-tab.is-active { + color: #0c0f16; +} + +/* ---------- 左右图文 ---------- */ +.p-split { + display: grid; + grid-template-columns: 1fr 1fr; + gap: 64px; + align-items: center; +} + +@media (max-width: 960px) { + .p-split { + grid-template-columns: 1fr; + gap: 40px; + } +} + +.p-list { + margin: 28px 0 0; + padding: 0; + list-style: none; +} + +.p-list li { + position: relative; + padding: 14px 0 14px 30px; + font-size: 15px; + line-height: 1.7; + color: var(--p-ink-2); + border-top: 1px solid var(--p-line); +} + +.p-list li::before { + content: ''; + position: absolute; + left: 4px; + top: 22px; + width: 7px; + height: 7px; + border-radius: 2px; + background: var(--p-accent); +} + +.p-list strong { + color: var(--p-ink); + font-weight: 600; +} + +/* ---------- 代码面板 ---------- */ +.p-panel { + padding: 22px 24px; + font-family: var(--vp-font-family-mono); + font-size: 13px; + line-height: 1.9; + color: var(--p-ink-2); + background: var(--p-surface-alt); + border: 1px solid var(--p-line); + border-radius: var(--p-radius); +} + +.p-panel .c { + color: var(--p-ink-3); +} + +.p-panel .k { + color: var(--p-accent); +} + +/* ---------- 步骤 ---------- */ +.p-steps { + display: grid; + grid-template-columns: repeat(3, 1fr); + gap: 20px; + margin-top: 8px; +} + +@media (max-width: 860px) { + .p-steps { + grid-template-columns: 1fr; + } +} + +.p-step { + padding: 26px; + background: var(--p-surface); + border: 1px solid var(--p-line); + border-radius: var(--p-radius); +} + +.p-step-num { + display: inline-block; + font-size: 12px; + font-weight: 700; + letter-spacing: 0.08em; + color: var(--p-accent); +} + +.p-step h3 { + margin: 10px 0 0; + font-size: 16px; + font-weight: 650; + color: var(--p-ink); +} + +.p-step p { + margin: 8px 0 0; + font-size: 14px; + line-height: 1.75; + color: var(--p-ink-2); +} + +.p-step code { + display: block; + margin-top: 14px; + padding: 10px 12px; + font-size: 12.5px; + color: var(--p-ink); + background: var(--p-surface-alt); + border: 1px solid var(--p-line); + border-radius: var(--p-radius-sm); + word-break: break-all; + white-space: pre-wrap; +} + +/* ---------- 底部 CTA ---------- */ +.p-cta { + text-align: center; + padding: 88px 0; +} + +.p-cta .p-title { + font-size: 32px; +} + +/* ---------- 项目卡片 ---------- */ +.p-proj { + display: flex; + flex-direction: column; + padding: 30px 28px; + background: var(--p-surface); + border: 1px solid var(--p-line); + border-radius: var(--p-radius); + box-shadow: var(--p-shadow); + color: var(--p-ink) !important; + text-decoration: none !important; + transition: transform 0.2s, box-shadow 0.2s, border-color 0.2s; +} + +.p-proj:hover { + transform: translateY(-3px); + border-color: var(--p-accent-line); + box-shadow: var(--p-shadow-hover); +} + +.p-proj-top { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 12px; +} + +.p-proj-name { + margin: 0; + font-size: 21px; + font-weight: 680; + letter-spacing: -0.01em; + color: var(--p-ink); + border: none; + padding: 0; +} + +.p-badge { + flex: none; + padding: 3px 10px; + font-size: 11.5px; + font-weight: 600; + color: var(--p-accent); + background: var(--p-accent-soft); + border: 1px solid var(--p-accent-line); + border-radius: 999px; +} + +.p-proj-tagline { + margin: 10px 0 0; + font-size: 14px; + font-weight: 600; + color: var(--p-ink-2); +} + +.p-proj-summary { + margin: 12px 0 0; + font-size: 14px; + line-height: 1.8; + color: var(--p-ink-2); +} + +.p-tags { + display: flex; + flex-wrap: wrap; + gap: 6px; + margin: 18px 0 0; + padding: 0; + list-style: none; +} + +.p-tags li { + padding: 3px 9px; + font-size: 11.5px; + color: var(--p-ink-3); + background: var(--p-surface-alt); + border: 1px solid var(--p-line); + border-radius: 6px; +} + +.p-proj-foot { + display: flex; + align-items: center; + justify-content: space-between; + gap: 12px; + margin-top: 22px; + padding-top: 18px; + border-top: 1px solid var(--p-line); + font-size: 12.5px; + color: var(--p-ink-3); +} + +.p-proj-more { + font-weight: 600; + color: var(--p-accent); +} + +/* ============================================================ + 内容页(非首页)的细节调整 + ============================================================ */ + +/* 属性表首列不要挤成竖排 */ +.vp-doc table th:first-child, +.vp-doc table td:first-child { + white-space: nowrap; +} + +/* 表格更轻,去掉重边框感 */ +.vp-doc table { + display: table; + width: 100%; +} + +.vp-doc tr { + background: transparent !important; +} + +.vp-doc th { + font-weight: 600; + color: var(--p-ink, inherit); +} + +/* 详情页一级标题更醒目 */ +.vp-doc h1 { + font-size: 38px; + font-weight: 700; + letter-spacing: -0.025em; + line-height: 1.2; +} + +@media (max-width: 720px) { + .vp-doc h1 { + font-size: 28px; + } +} + +/* 二级标题加一条短强调线 */ +.vp-doc h2 { + padding-top: 40px; + margin-top: 56px; + font-size: 26px; + font-weight: 650; + letter-spacing: -0.02em; +} + +.vp-doc h2::before { + content: ''; + display: block; + width: 32px; + height: 3px; + margin-bottom: 18px; + border-radius: 2px; + background: var(--p-accent, #4f46e5); +} + +.vp-doc h3 { + font-size: 18px; + font-weight: 650; +} + +/* 提示块圆角统一 */ +.vp-doc .custom-block { + border-radius: 10px; +} + +/* 行内代码轻一点 */ +.vp-doc :not(pre) > code { + border-radius: 5px; + font-size: 0.86em; +} + +/* 内容页正文用强调色变量,避免与默认主题脱节 */ +.vp-doc a { + color: var(--p-accent); + text-decoration-color: var(--p-accent-line); +} + +.vp-doc a:hover { + text-decoration-color: var(--p-accent); +} diff --git a/data/content.ts b/data/content.ts new file mode 100644 index 0000000..6f1ad95 --- /dev/null +++ b/data/content.ts @@ -0,0 +1,172 @@ +/** + * 门户首页的内容数据 —— 与 data/projects.ts 并列,只放首页排版需要的文案。 + * + * 口径约束:这里的每一条都取自各项目的 README / 详情页,不写没有依据的数字。 + * 需要新增能力时,先在对应项目仓核实,再补到这里。 + */ + +export interface Stat { + num: string + unit?: string + label: string +} + +/** 首页数据条。只列可从仓库直接核对的事实。 */ +export const stats: Stat[] = [ + { num: '3', label: '开源项目' }, + { num: '3', label: '运营商协议直连' }, + { num: '22', unit: '+', label: 'XiuAdmin 内置模块' }, + { num: '100', unit: '%', label: 'Apache-2.0 许可' }, +] + +export interface Capability { + title: string + text: string +} + +/** 通用能力底座 —— 三个项目共享的工程特征 */ +export const foundations: Capability[] = [ + { + title: '单仓多服务', + text: '后端服务、前端应用、文档站与部署编排收在同一个仓库,一次 clone 拿到全部内容。', + }, + { + title: '一条命令起', + text: 'Docker Compose 编排齐备,首次启动自动建库建表与种子数据,不需要本地编译。', + }, + { + title: '接口开放', + text: '对外接口有独立的版本化合同与鉴权方式,可被自有系统或三方平台直接调用。', + }, + { + title: '权限与审计', + text: '多租户、角色、菜单按钮权限与数据范围划分,关键操作留痕可追溯。', + }, + { + title: '离线可部署', + text: '镜像可导出为归档文件带入内网,无外网环境同样可以完成安装与升级。', + }, + { + title: '文档随仓发布', + text: '使用手册、部署手册与接口合同与代码同仓维护,版本不会脱节。', + }, +] + +/** iotLCM 的能力分组,按业务域拆 */ +export interface CapGroup { + id: string + name: string + intro: string + items: Capability[] +} + +export const iotlcmGroups: CapGroup[] = [ + { + id: 'card', + name: '卡与库存', + intro: '从运营商渠道入库开始,一路管到销户,卡片状态与位置始终可查。', + items: [ + { title: '卡信息管理', text: '单卡详情、按三码检索、状态与用量快照,支持批量导入建立卡档案。' }, + { title: '三级库存流转', text: '入库、在库、出库全程留痕,按批次追溯每一张卡的来源与去向。' }, + { title: '批量开停复机', text: '按批次下发至运营商,执行结果自动回写,失败项可单独重试。' }, + { title: '续费与变更套餐', text: '单卡与批量受理并行,支持指定生效时间与到期处理策略。' }, + { title: '达量断网', text: '按用量阈值自动停机,阈值可批量设置,避免超量产生额外费用。' }, + { title: '智能诊断', text: '结合卡状态、通道与用量给出诊断结论与处置建议,缩短排障时间。' }, + ], + }, + { + id: 'channel', + name: '通道与上游', + intro: '把不同运营商的接口差异收敛到一层,上层业务不用关心底层协议。', + items: [ + { title: '三大运营商直连', text: '中国移动 OneLink、中国电信 5G CMP、中国联通 10646 协议适配内置。' }, + { title: '通道与接入模板', text: '按运营商维护通道,接入参数模板化,新增通道不改业务代码。' }, + { title: '共享 Token 接入', text: '三方渠道商以共享 Token 模式接入,配合白名单转发控制调用范围。' }, + { title: '通道缓存转发', text: '独立转发层隔离上游抖动,上游异常不影响管理端可用性。' }, + { title: '凭据与配额管理', text: '上游凭据集中维护,调用量与配额可观测,异常调用及时告警。' }, + { title: '执行日志', text: '每次上游调用的请求与响应留存,问题定位有据可依。' }, + ], + }, + { + id: 'billing', + name: '资费与计费', + intro: '套餐、流量池与定价分离建模,同一张卡可以按不同口径结算。', + items: [ + { title: '套餐配置', text: '套餐与流量池定义,支持多层套餐结构与绑定关系。' }, + { title: '定价规则', text: '面向代理商与终端客户的定价分开维护,支持差异化报价。' }, + { title: '生效与到期', text: '套餐生效、到期、续期的处理策略可配置,避免人工干预。' }, + { title: '用量查询', text: '实时用量与月累计分开统计,支持历史账期回溯。' }, + { title: '时序数据留存', text: '流量与状态按账期落入时序库,长期趋势分析不失真。' }, + { title: '分佣与结算', text: '代理商分佣规则配置,划拨与回收动作与结算口径对齐。' }, + ], + }, + { + id: 'org', + name: '组织与账号', + intro: '多租户下的企业、账号与权限治理,对外凭证与对内角色分开管理。', + items: [ + { title: '企业与用户', text: '多级组织结构,企业下挂用户,数据按组织隔离。' }, + { title: '角色与权限', text: '按角色分配菜单与操作权限,按钮级权限可粒度控制。' }, + { title: '代理商体系', text: '代理商分级管理,设备与卡片划拨、回收动作全程留痕。' }, + { title: 'API 凭证', text: '对外调用凭证的签发、吊销与使用记录,异常调用可即时阻断。' }, + { title: '操作审计', text: '关键操作留痕,可按时段、操作人、对象多维检索。' }, + { title: '工单流转', text: '内部与客户侧工单流转,处理时效与责任人可追踪。' }, + ], + }, + { + id: 'delivery', + name: '对外交付', + intro: '把平台能力以接口形式交付出去,自有系统与三方平台都能接。', + items: [ + { title: 'V3 版本化接口', text: '接口按版本演进,升级不破坏既有调用方。' }, + { title: '旧版兼容接口', text: '历史接口保持兼容,存量系统无需改造即可迁移。' }, + { title: '运营商能力代理', text: '白名单转发与共享 Token 两种模式,按需开放上游能力。' }, + { title: 'Web 管理端', text: '完整的后台界面,覆盖卡管理、资费、组织、运维全场景。' }, + { title: '小程序 / H5', text: '面向终端用户的移动端,支持查卡、办理与自助服务。' }, + { title: '接口文档', text: '接口合同与示例随文档站发布,接入方可以自助完成联调。' }, + ], + }, +] + +/** XiuAdmin 内置模块(取自项目详情页的 22 项,归并为可读分组) */ +export interface AdminGroup { + name: string + items: string[] +} + +export const adminGroups: AdminGroup[] = [ + { name: '租户与组织', items: ['租户管理', '租户套餐', '客户端管理', '部门管理', '岗位管理'] }, + { name: '账号与权限', items: ['用户管理', '角色管理', '菜单管理', '在线用户'] }, + { name: '开发效率', items: ['代码生成', '定时任务', '消息队列', '字典管理'] }, + { name: '配置与运维', items: ['配置管理', '服务监控', '缓存监控', '操作日志', '登录日志', '服务日志'] }, +] + +/** DTU 的能力分组 */ +export const dtuGroups: CapGroup[] = [ + { + id: 'produce', + name: '生产与研发', + intro: '从产品定义到固件版本,设备出厂前的每个环节都在系统里。', + items: [ + { title: '产品与型号', text: '产品定义、硬件型号与工厂信息维护,生产批次可追溯。' }, + { title: '设备生产', text: '批量生产导入导出,设备序列号与产品型号绑定。' }, + { title: '固件管理', text: '固件版本登记与发布,支持按型号推送不同版本。' }, + { title: 'OTA 升级', text: '升级任务分批下发,进度与结果可查,失败设备可重试。' }, + { title: '配置中心', text: '产品通用模型、配置分组与版本,下发状态逐台可见。' }, + { title: '指令下发', text: 'HTTP / TCP 双通道接入,指令下发与回执确认。' }, + ], + }, + { + id: 'channel', + name: '代理与客户', + intro: '设备划拨链路透明,代理商与终端客户的归属关系清晰。', + items: [ + { title: '代理商管理', text: '多级代理结构,下级代理与设备列表分开维护。' }, + { title: '设备划拨', text: '划拨与回收动作留痕,历史记录可回溯到具体批次。' }, + { title: '终端绑定', text: '终端客户扫码绑定设备,一机一码防止串货。' }, + { title: '我的订单', text: '代理商自助查看订单与设备状态,减少人工对接。' }, + { title: '运行监控', text: '设备在线状态、信号与事件上报集中呈现。' }, + { title: '联调工具', text: '仓库自带设备联调与压测脚本,新设备接入可先本地验证。' }, + ], + }, +] diff --git a/pages/about.md b/pages/about.md index 3cde6c7..5fcda35 100644 --- a/pages/about.md +++ b/pages/about.md @@ -10,13 +10,13 @@ outline: deep ## 许可证 -全部项目以 **Apache-2.0** 发布。可自由使用、修改、商用与再分发;分发时保留版权声明与许可证原文,并对修改部分注明变更。各项目依赖的上游组件保留其原始许可。 +全部项目以 **Apache-2.0** 发布。 -## 参与贡献 - -1. 从目标项目的默认分支切出特性分支,命名 `feat/xxx`、`fix/xxx` -2. 提交前确保各自的构建与测试通过(Go 服务跑 `go build ./...` 与 `go test ./...`,前端跑 `pnpm build`) -3. 提交合入请求并说明变更动机 +| 允许 | 需要 | +| --- | --- | +| 自由使用、修改、商用、再分发 | 保留版权声明与许可证原文 | +| 用于闭源商业产品 | 对修改过的文件注明变更 | +| 专利授权(贡献者授予) | 各项目上游依赖保留其原始许可 | ## 各项目仓库 @@ -27,17 +27,29 @@ outline: deep | XiuAdmin | | | 本门户站 | | +## 参与贡献 + +| 步骤 | 做什么 | +| --- | --- | +| 1 | 从目标项目的默认分支切出特性分支,命名 `feat/xxx`、`fix/xxx` | +| 2 | 提交前确保构建与测试通过:Go 服务跑 `go build ./...` 与 `go test ./...`,前端跑 `pnpm build` | +| 3 | 提交合入请求,说明变更动机与影响范围 | + ## 提问与反馈 -- 使用问题与缺陷:对应仓库开 issue -- 功能建议:issue 里描述清楚场景与期望 -- 部署排错:先看各项目详情页的常见问题表,再开 issue 附上 `docker compose ps` 与相关日志 +| 类型 | 去哪 | +| --- | --- | +| 使用问题与缺陷 | 对应仓库开 issue | +| 功能建议 | issue 里描述清楚场景与期望 | +| 部署排错 | 先看各项目详情页的常见问题表,再开 issue 并附 `docker compose ps` 与相关日志 | -## 运维自用组件的边界 +## 边界说明 -飞书通知机器人(`with-bot` profile)是给运维方自用的工具,需要自建应用的凭据才能启动,**不是平台功能**,未配置时不影响平台运行。 - -运营商通道的生产凭据不随仓发布,需自行向运营商申请。 +| 项 | 说明 | +| --- | --- | +| 飞书通知机器人 | `with-bot` profile 下的运维自用工具,需要自建应用凭据才能启动,**不是平台功能**,未配置不影响运行 | +| 运营商凭据 | 通道的生产凭据不随仓发布,需自行向运营商申请与配置 | +| 老平台私有模块 | 开源版发布的是微服务家族,不含历史项目的私有定制部分 | ## 门户本身 diff --git a/pages/index.md b/pages/index.md index 411759f..ca1cb59 100644 --- a/pages/index.md +++ b/pages/index.md @@ -1,39 +1,9 @@ --- -layout: home - -hero: - name: 雪羊科技开源 - text: 物联网与中后台基础软件 - tagline: 开源项目门户 · Apache-2.0 - actions: - - theme: brand - text: 浏览全部项目 - link: /projects/ - - theme: alt - text: iotLCM 快速开始 - link: /projects/iotlcm/guide/quickstart - -features: - - title: 全生命周期管理 - details: iotLCM 覆盖物联网 SIM 卡的入库、划拨、激活、续费、停机、流量与状态同步、销户全流程。 - - title: 设备接入闭环 - details: 秀杰 DTU 管理平台覆盖 DTU 设备生产、出厂、划拨、绑定、配置下发、固件 OTA 与指令下发。 - - title: 中后台开发底座 - details: XiuAdmin 是基于 GoFrame + Vue3 的全栖中后台框架,租户、权限、代码生成、消息队列等内置可用。 - - title: 一条命令部署 - details: 各项目均提供 Docker Compose 编排与预构建镜像,clone 之后即可跑起来。 - - title: 统一 Apache-2.0 - details: 全部项目以 Apache-2.0 发布,可自由使用、修改、商用与再分发。 - - title: 持续新增 - details: 门户项目清单由单一注册表驱动,新增项目不需要改动站点其它部分。 +layout: page +title: 雪羊科技开源 +description: 雪羊科技开源项目门户:物联网卡管理、DTU 设备管理、中后台开发框架 +sidebar: false +aside: false --- -## 开源项目 - - - -## 使用说明 - -- 每个项目在「项目详情」页给出定位、能力边界与代码仓地址 -- 部署与运维细节见各项目的指南页 -- 源码仓统一托管在 Gitee 企业仓 `snowgoat` 命名空间下 + diff --git a/pages/projects/index.md b/pages/projects/index.md index 47fb029..8e2f507 100644 --- a/pages/projects/index.md +++ b/pages/projects/index.md @@ -6,15 +6,19 @@ outline: deep # 全部项目 +雪羊科技对外开源的项目集合。三个项目覆盖物联网卡管理、硬件设备管理与中后台开发底座,全部以 Apache-2.0 发布。 + -## 关于这份清单 +## 清单怎么维护 项目清单由站点源码里的单一注册表维护(`data/projects.ts`),首页卡片、侧边栏与本页都从它生成。 新增一个项目需要两步: -1. 在注册表里加一条记录(名称、定位、说明、代码仓、许可证、技术栈、状态) -2. 写 `pages/projects//index.md` 详情页,若有子页面(快速开始、部署、教程)在记录的 `sections` 里登记 +| 步骤 | 做什么 | +| --- | --- | +| 1 | 在 `data/projects.ts` 里加一条记录(名称、定位、说明、代码仓、许可证、技术栈、状态、详情页路径、子页面) | +| 2 | 写 `pages/projects//index.md` 详情页,有子页面则在记录的 `sections` 里登记 | -站点构建前会自动校验「注册表条目」与「详情页目录」是否一一对应,不一致直接报错,避免清单与页面脱节。 +构建前会自动校验「注册表条目」与「详情页目录」是否一一对应,不一致直接报错,避免清单与页面脱节。 diff --git a/pages/projects/iotlcm/index.md b/pages/projects/iotlcm/index.md index 41b96e1..d2d742e 100644 --- a/pages/projects/iotlcm/index.md +++ b/pages/projects/iotlcm/index.md @@ -18,32 +18,11 @@ outline: deep | 技术栈 | GoFrame v2、Vue 3 + Vben Admin、MySQL、Redis、RabbitMQ、TDengine、Consul、Docker Compose | | 文档站 | | -## 能力概览 +## 能力矩阵 -按业务域拆开的能力矩阵: +按业务域拆开的能力矩阵,点击标签切换查看各域下的具体能力。 -| 域 | 能力 | -| --- | --- | -| 卡资产 | 卡信息、批量导入、入库 / 库存 / 出库、批量开停复机、续费、变更套餐、达量断网 | -| 通信管理 | 通道与接入模板、运营商能力对接、共享 Token 接入三方渠道、通道缓存转发 | -| 资费 | 套餐与流量池、定价、生效与到期策略 | -| 代理商 | 代理商与分佣、划拨与回收 | -| 数据同步 | 流量 / 状态 / IMEI 全量同步与增量对齐、历史账期时序数据 | -| 对外接口 | V3 接口、旧版兼容接口、运营商能力代理(白名单转发 / 共享 Token) | -| 运营 | 工单、预警、执行日志、审计、智能诊断 | -| 端 | Web 管理端、小程序 / H5 用户端 | - -细分能力: - -| 模块 | 说明 | -| --- | --- | -| 卡与库存 | 卡信息检索、按三码查卡、批量导入出入库、库存与出库、批量开停复机 | -| 智能诊断 | 结合卡状态、通道与用量给出诊断结论与处置建议 | -| 资费与计费 | 套餐与定价管理、用量与流量查询、达量断网阈值 | -| 业务受理 | 开通、续费、停复机、变更套餐 | -| 通道与上游 | 上游通道配置与凭据、协议适配、共享 Token、缓存转发 | -| 组织与账号 | 多租户下的企业与账号治理、角色权限、对外 API 凭证 | -| 运维支撑 | 全量同步、批次执行、工单与告警、执行日志、飞书通知、指标与日志 | + ## 服务构成 @@ -67,9 +46,12 @@ outline: deep ## 下一步 -- [快速开始](/projects/iotlcm/guide/quickstart) — 三步把整套跑起来 -- [部署与运维](/projects/iotlcm/guide/deploy) — 生产部署前的必改项、可选组件、离线部署与排错 -- [教程视频](/projects/iotlcm/guide/tutorial) — 部署与使用的录屏教程 +| 文档 | 内容 | +| --- | --- | +| [快速开始](/projects/iotlcm/guide/quickstart) | 三步把整套跑起来 | +| [部署与运维](/projects/iotlcm/guide/deploy) | 生产部署前的必改项、可选组件、离线部署与排错 | +| [教程视频](/projects/iotlcm/guide/tutorial) | 部署与使用的录屏教程 | +| [方案对比](/projects/iotlcm/compare) | 与同类开源方案的可验证差异 | ## 边界 diff --git a/pages/projects/xiu-admin/index.md b/pages/projects/xiu-admin/index.md index 543e8e5..aec1f41 100644 --- a/pages/projects/xiu-admin/index.md +++ b/pages/projects/xiu-admin/index.md @@ -25,28 +25,9 @@ outline: deep ## 内置功能 -1. 租户管理:系统内租户的管理,如租户套餐、过期时间、用户数量、企业信息等 -2. 租户套餐管理:租户所能使用的套餐管理,如套餐内所包含的菜单等 -3. 客户端管理:系统内对接的所有客户端管理,如 pc 端、小程序端等 -4. 用户管理:用户是系统操作者,完成系统用户配置 -5. 部门管理:配置系统组织机构(公司、部门、岗位),树结构展现支持数据权限 -6. 岗位管理:配置系统用户所属担任职务 -7. 菜单管理:配置系统菜单,操作权限、按钮权限标识等 -8. 角色管理:角色菜单权限分配、设置角色按机构或按上下级关系进行数据范围权限划分 -9. 字典管理:对系统中经常使用的一些特定数据进行维护,支持枚举字典和自定义方法字典 -10. 配置管理:对系统动态配置常用参数 -11. 操作日志:系统正常操作日志记录和查询;系统异常信息日志记录和查询 -12. 登录日志:系统登录日志记录查询,包含登录异常 -13. 服务日志:服务端运行所产生的警告、异常、崩溃日志的详细数据和堆栈信息 -14. 在线用户:当前系统中活跃用户状态监控 -15. 定时任务:在线(添加、修改、删除)任务调度,包含执行结果日志 -16. 代码生成:支持自动化生成前后端代码。CURD 关联表、树表、消息队列、定时任务一键生成等 -17. 服务监控:监视当前系统 CPU、内存、磁盘、网络、堆栈等相关信息 -18. 缓存监控:对系统的缓存信息查询、命令统计等 -19. 附件管理:文件图片上传,大文件分片上传、断点续传,支持本地 -20. 消息队列:Asynq 消息队列 -21. 通知公告:采用 WebSocket 实时推送在线用户最新通知、公告、私信消息 -22. 常用工具:集成常用的工具包和命令行工具,可以快速开发自定义命令行,多种启动入口 +22 项功能开箱可用,按用途归为四组。每项都不需要自己实现,装上就能用。 + + ## 目录结构 diff --git a/pages/projects/xiu-dtu/index.md b/pages/projects/xiu-dtu/index.md index 9df7d12..c5af2bd 100644 --- a/pages/projects/xiu-dtu/index.md +++ b/pages/projects/xiu-dtu/index.md @@ -15,16 +15,11 @@ outline: deep | 状态 | 已发布 | | 技术栈 | GoFrame v2、Vue 3 + Vite + TypeScript、Vben Admin 5.x、Ant Design Vue、MySQL 8、Redis、TDengine(可选)、asynq、VitePress | -## 功能概览 +## 能力分组 -| 模块 | 说明 | -| --- | --- | -| 生产中心 | 产品、硬件型号、工厂、生产批次、设备生产与导入导出 | -| 研发中心 | 固件管理、固件版本、OTA 升级任务 | -| 配置中心 | 产品通用模型、配置分组、配置版本与下发状态 | -| 代理中心 | 代理商设备列表、下级代理、设备划拨与划拨历史、我的订单 | -| 系统管理 | 用户、角色、菜单、部门、字典、参数、租户、监控 | -| 设备接入 | HTTP / TCP 双通道接入,指令下发、事件上报、UART 隧道 | +按业务环节拆开,点击标签切换查看各环节下的具体能力。 + + ## 目录结构 diff --git a/scripts/check-registry.mjs b/scripts/check-registry.mjs index ce30b51..80f1fea 100644 --- a/scripts/check-registry.mjs +++ b/scripts/check-registry.mjs @@ -14,11 +14,9 @@ const root = join(dirname(fileURLToPath(import.meta.url)), '..') const dataDir = join(root, 'data') const pagesDir = join(root, 'pages', 'projects') -// 注册表是 TS 源文件,这里只需要 id 列表,正则抽取即可,不引入编译步骤 -const registrySrc = readdirSync(dataDir) - .filter((f) => f.endsWith('.ts')) - .map((f) => readFileSync(join(dataDir, f), 'utf-8')) - .join('\n') +// 注册表是 TS 源文件,这里只需要 id 列表,正则抽取即可,不引入编译步骤。 +// 只看 projects.ts:data/ 下其它文件(如首页文案 content.ts)里的 id 不是项目 id。 +const registrySrc = readFileSync(join(dataDir, 'projects.ts'), 'utf-8') const registered = [...registrySrc.matchAll(/\bid:\s*'([^']+)'/g)].map((m) => m[1])