chore: 初始开源发布

This commit is contained in:
MiFaZhan
2026-10-22 15:16:43 +08:00
commit 92f239fcc4
114 changed files with 8018 additions and 0 deletions
+122
View File
@@ -0,0 +1,122 @@
import { defineConfig } from 'vitepress'
const webManualItems = [
{ text: '快速开始', link: '/manual/web/' },
{ text: '首页与通用操作', link: '/manual/web/home' },
{ text: '卡信息', link: '/manual/web/card-info' },
{ text: '智能诊断', link: '/manual/web/intelligent-diagnosis' },
{ text: '业务受理与达量断网', link: '/manual/web/business-operations' },
{ text: '企业、用户与角色', link: '/manual/web/enterprise-management' },
{ text: '工单管理', link: '/manual/web/work-orders' },
{ text: 'API 凭证与执行日志', link: '/manual/web/api-and-logs' },
{ text: '常见问题', link: '/manual/web/faq' },
]
const miniProgramManualItems = [
{ text: '快速开始', link: '/manual/mini-program/' },
{ text: '首页公开查卡', link: '/manual/mini-program/home' },
{ text: '公开卡片详情', link: '/manual/mini-program/devices' },
{ text: '卡片排查', link: '/manual/mini-program/diagnosis' },
{ text: '常见问题', link: '/manual/mini-program/faq' },
]
function manualSidebar(_active: 'web' | 'mini' | 'both' = 'both') {
return [
{
text: 'Web端',
collapsed: false,
items: webManualItems,
},
{
text: '小程序端',
collapsed: false,
items: miniProgramManualItems,
},
]
}
export default defineConfig({
srcDir: './docs',
base: '/docs/',
title: 'iotLCM',
description: 'iotLCM documentation center',
lang: 'zh-CN',
cleanUrls: true,
lastUpdated: false,
head: [
['link', { rel: 'icon', type: 'image/png', href: '/docs/favicon.png' }]
],
vite: {
plugins: [
{
name: 'serve-root-favicon-in-dev',
configureServer(server) {
server.middlewares.use('/favicon.ico', (_request, response) => {
response.writeHead(302, { Location: '/docs/favicon.png' })
response.end()
})
}
}
]
},
themeConfig: {
logo: undefined,
nav: [
{ text: '文档首页', link: '/' },
{ text: '使用手册', link: '/manual/' },
{ text: 'OpenAPI', link: '/openapi/' },
{ text: '共享 Token 接口', link: '/openapi/operator/shared-token/' }
],
sidebar: {
'/manual/': manualSidebar(),
'/manual/web/': manualSidebar('web'),
'/manual/mini-program/': manualSidebar('mini'),
'/openapi/operator/': [
{
text: '共享 Token 接口',
collapsed: false,
items: [
{ text: '概览', link: '/openapi/operator/' },
{ text: '共享 Token 接口', link: '/openapi/operator/shared-token/' },
{ text: '白名单转发', link: '/openapi/operator/mobile-v5/' },
]
},
],
'/openapi/': [
{
text: 'V3 接口',
collapsed: false,
items: [
{ text: '概览与鉴权', link: '/openapi/v3/' },
{ text: '获取调用凭证', link: '/openapi/v3/token' },
{ text: '查询类', link: '/openapi/v3/query' },
{ text: '业务类', link: '/openapi/v3/common' },
]
},
{
text: '旧版兼容接口',
collapsed: false,
items: [
{ text: '概览与鉴权', link: '/openapi/legacy/' },
{ text: '查询类', link: '/openapi/legacy/query' },
]
},
]
},
outline: {
level: [2, 3],
label: '本页目录'
},
docFooter: {
prev: '上一页',
next: '下一页'
},
search: {
provider: 'local'
},
footer: {
message: 'iotLCM 文档中心',
copyright: ''
}
}
})
+198
View File
@@ -0,0 +1,198 @@
/* 截图点击放大(medium-zoom)—— 全站生效,范围由 .vitepress/theme/index.ts 的 ZOOM_SELECTOR 定义 */
/* 叠加层必须盖住导航和侧边栏:默认主题 --vp-z-index-nav 为 30、
--vp-z-index-sidebar 为 60,所以取 100。 */
.medium-zoom-overlay {
z-index: 100;
}
.medium-zoom-image--opened {
z-index: 101;
}
/* JS 尚未挂载 medium-zoom 时也提前给出「可点击」的鼠标提示 */
.vp-doc img:not(a img):not(.no-zoom) {
cursor: zoom-in;
}
/* 单张图片保持原始尺寸,并在正文区域水平居中。 */
.vp-doc img {
display: block;
margin: 24px auto;
}
/* 单行只有一张图片时缩放到内容区域的 40%。 */
.vp-doc p > img:only-child,
.vp-doc li > img:only-child {
width: 40%;
max-width: 40%;
height: auto;
}
/* Web 端手册截图恢复为内容区域全宽展示。 */
.vp-doc img[src*="/images/manual/"]:not([src*="mini-program"]) {
width: 100%;
max-width: 100%;
height: auto;
}
/* 同一行包含两张图片时,整行缩放到内容区域的 40%。 */
.vp-doc p:has(> img:nth-child(2):last-child) {
display: flex;
width: 80%;
max-width: 80%;
height: auto;
line-height: 0;
margin: 32px auto;
align-items: center;
justify-content: center;
gap: 8px;
}
.vp-doc p:has(> img:nth-child(2):last-child) > img {
width: calc(50% - 4px);
max-width: calc(50% - 4px);
height: auto;
margin: 0;
}
/* Markdown 内嵌的双图网格统一缩放到内容区域的 80%。 */
.vp-doc div[style*="repeat(2"] {
width: 80% !important;
max-width: 80% !important;
margin: 32px auto !important;
}
.vp-doc div[style*="repeat(2"] img {
width: 100% !important;
max-width: 100% !important;
height: auto;
margin: 0 !important;
}
.vp-doc div[style*="repeat(3"] img {
margin: 0 !important;
}
.vp-doc div[style*="repeat(2"] figcaption,
.vp-doc div[style*="repeat(3"] figcaption {
margin-top: 6px;
color: var(--vp-c-brand-1);
font-size: 16px;
font-weight: 600;
line-height: 1.4;
}
.vp-doc[class*="_docs_openapi_"] table {
display: table;
width: 100%;
max-width: 100%;
table-layout: auto;
}
.openapi-page .custom-block.warning,
.openapi-page .custom-block.danger {
background-color: var(--vp-code-block-bg);
}
.openapi-page .custom-block.warning code,
.openapi-page .custom-block.danger code {
background-color: var(--vp-code-bg);
}
@media (max-width: 767px) {
.vp-doc[class*="_docs_openapi_"] table {
display: block;
overflow-x: auto;
}
}
.manual-entry-grid {
display: grid;
grid-template-columns: repeat(2, minmax(0, 1fr));
gap: 20px;
margin: 28px 0 36px;
}
.manual-entry-card {
display: flex;
min-height: 190px;
padding: 24px;
flex-direction: column;
border: 1px solid var(--vp-c-divider);
border-radius: 10px;
background: var(--vp-c-bg-soft);
color: inherit;
text-decoration: none;
transition: border-color 0.2s, box-shadow 0.2s, transform 0.2s;
}
.manual-entry-card:hover {
border-color: var(--vp-c-brand-1);
box-shadow: 0 8px 24px rgb(60 90 180 / 12%);
transform: translateY(-2px);
}
.manual-entry-title {
color: var(--vp-c-brand-1);
font-size: 22px;
font-weight: 700;
}
.manual-entry-desc {
margin-top: 12px;
color: var(--vp-c-text-2);
font-size: 14px;
line-height: 1.7;
}
.manual-entry-link {
margin-top: auto;
padding-top: 18px;
color: var(--vp-c-brand-1);
font-size: 14px;
font-weight: 600;
}
.mini-program-entry {
display: flex;
width: min(100%, 520px);
margin: 28px auto 12px;
flex-direction: column;
align-items: center;
text-align: center;
}
.vp-doc .mini-program-entry + h2 {
margin-top: 12px;
}
.mini-program-entry__qr {
margin: 0;
}
.mini-program-entry__qr img {
width: 196px;
max-width: 58vw;
margin: 0 auto;
}
.mini-program-entry__qr figcaption {
margin-top: 12px;
color: var(--vp-c-brand-1);
font-size: 15px;
font-weight: 600;
}
.mini-program-entry__hint {
margin: 14px 0 0;
color: var(--vp-c-text-2);
font-size: 14px;
line-height: 1.7;
}
@media (max-width: 640px) {
.manual-entry-grid {
grid-template-columns: 1fr;
}
}
+78
View File
@@ -0,0 +1,78 @@
import DefaultTheme from 'vitepress/theme'
import mediumZoom from 'medium-zoom'
import { onContentUpdated } from 'vitepress'
import { onMounted } from 'vue'
import type { Theme } from 'vitepress'
import './custom.css'
/**
* 参与「点击放大」的图片范围。
*
* `.vp-doc` 是 VitePress 默认主题给正文容器加的类名,使用手册 9 页与接口文档
* 全部页面共用这一套布局,所以在这里定义一次即对全站生效。
*
* 排除项:
* a img 图片本身是链接(如带外链的截图),点击应跳转而不是放大
* .no-zoom 需要单独关闭放大的图片,示例:
* `<img src="/images/manual/xx.png" class="no-zoom" alt="xx">`
*/
const ZOOM_SELECTOR = '.vp-doc img:not(a img):not(.no-zoom)'
/**
* 全站只保留一个 medium-zoom 实例,但【必须惰性创建】。
*
* 两个坑叠在一起,才得到这个写法:
*
* 1. 不能在模块顶层创建实例。medium-zoom 1.1.0 的工厂函数第一件事就是读
* `window.Promise`(dist/medium-zoom.min.js 里即 `var Promise = window.Promise || ...`),
* 而主题模块在 `vitepress build` 时会被 Node 端 SSR 打包并执行 —— 没有 window,
* 直接 `ReferenceError: window is not defined` 构建失败。
* 所以实例只能在浏览器生命周期回调里创建。
*
* 2. 也不能每次路由切换都 `mediumZoom(...)` 新建。每个实例都会在 document 上注册
* click / keyup / scroll、在 window 上注册 resize 监听器,且没有卸载入口
* (见 dist/medium-zoom.esm.js:576-581)。翻 N 个页面就会留下 N 组永不释放的
* 全局监听器。正确做法是复用实例,路由切换时只做 attach —— attach 内部会跳过
* 已挂载的图片(dist/medium-zoom.esm.js:199-204),因此重复调用是幂等的。
*/
let zoom: ReturnType<typeof mediumZoom> | null = null
const attachZoom = () => {
if (typeof window === 'undefined') return
if (!zoom) {
zoom = mediumZoom({
background: 'var(--vp-c-bg)',
margin: 24,
})
}
zoom.attach(ZOOM_SELECTOR)
}
const closeZoomOnClick = (event: MouseEvent) => {
const target = event.target as Element | null
if (!zoom || !target) return
if (target.closest('.medium-zoom-image--opened, .medium-zoom-overlay')) {
void zoom.close()
}
}
const theme: Theme = {
extends: DefaultTheme,
setup() {
// onContentUpdated 由 VitePress 在「新页面内容挂载完成后」触发,覆盖首次进入
// 与 SPA 客户端路由切换。它挂在路由组件的 onVnodeMounted 上
// (见 vitepress/dist/client/app/components/Content.js:13-20),
// 比 watch(route.path) + nextTick 更可靠 —— 后者可能在异步页面组件挂载前就执行,
// 导致某些页面图片没挂上监听、点击放大静默失效。
onContentUpdated(attachZoom)
// 兜底:主题 setup 时内容已就绪(不经过 onVnodeMounted 的极端情况)也能挂上
onMounted(attachZoom)
if (typeof document !== 'undefined') {
document.addEventListener('click', closeZoomOnClick, true)
}
},
}
export default theme