chore: 初始开源发布
This commit is contained in:
@@ -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: ''
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
Reference in New Issue
Block a user