Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
749 changes: 749 additions & 0 deletions docs/.vitepress/theme/components/ApiEndpoint.vue

Large diffs are not rendered by default.

164 changes: 164 additions & 0 deletions docs/.vitepress/theme/components/ParameterTable.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
<template>
<div class="parameter-table-container">
<!-- 只有顶层表格才显示表头 -->
<div class="table-header" v-if="level === 0">
<span>参数名</span>
<span>类型</span>
<span>必填</span>
<span>位置</span>
<span>说明</span>
</div>
<template
v-for="param in parameters"
:key="param.name + (param.level || 0)"
>
<div
class="table-row"
:style="{ paddingLeft: (param.level || 0) * 20 + 12 + 'px' }"
>
<code class="param-name">{{ param.name }}</code>
<span class="param-type">{{ param.type }}</span>
<span
:class="['param-required', param.required ? 'required' : 'optional']"
>
{{ param.required ? "是" : "否" }}
</span>
<span class="param-in">{{ param.in || "-" }}</span>
<span class="param-description">{{ param.description }}</span>
</div>
<!-- 递归渲染子参数 -->
<ParameterTable
v-if="param.children && param.children.length > 0"
:parameters="param.children"
:level="(param.level || 0) + 1"
/>
</template>
</div>
</template>

<script setup lang="ts">
import { type Parameter } from "../types/api"; // Adjust path as needed

interface ParameterTableProps {
parameters: Parameter[];
level?: number;
}

withDefaults(defineProps<ParameterTableProps>(), {
level: 0,
});
</script>

<style scoped>
.parameter-table-container {
/* No border here, border will be on the parent .params-table */
}

.table-header {
display: grid;
grid-template-columns: 1.5fr 1fr 0.5fr 0.5fr 2fr; /* 调整列宽 */
background: var(--vp-c-bg-soft);
padding: 12px;
font-weight: 600;
color: var(--vp-c-text-1);
border-bottom: 1px solid var(--vp-c-border);
}

.table-row {
display: grid;
grid-template-columns: 1.5fr 1fr 0.5fr 0.5fr 2fr; /* 调整列宽 */
padding: 12px;
border-bottom: 1px solid var(--vp-c-border);
align-items: center;
background-color: var(--vp-c-bg); /* 确保背景色与VitePress主题一致 */
}

.table-row:last-child {
border-bottom: none;
}

/* Indentation for nested parameters */
.table-row .param-name {
/* No specific style here, padding-left on the row handles it */
}

/* Inherit styles from ApiEndpoint.vue for consistency */
.param-name {
font-family: var(--vp-font-family-mono);
font-size: 13px;
background: var(--vp-c-bg-soft);
padding: 2px 6px;
border-radius: 3px;
word-break: break-all;
justify-self: start;
}

.param-type {
color: var(--vp-c-text-2);
font-size: 13px;
word-break: break-all;
}

.param-required.required {
color: #ef4444;
font-weight: 500;
}

.param-required.optional {
color: var(--vp-c-text-3);
}

.param-in {
color: var(--vp-c-text-2);
font-size: 13px;
text-transform: capitalize; /* 首字母大写 */
}

.param-description {
color: var(--vp-c-text-2);
font-size: 14px;
word-break: break-all;
}

/* Responsive adjustments for ParameterTable */
@media (max-width: 768px) {
.table-header {
display: none;
}
.table-row {
grid-template-columns: 1fr;
gap: 8px;
display: block;
padding: 16px 12px;
}
.table-row > * {
display: block;
margin-bottom: 4px;
}
.param-name::before {
content: "参数名: ";
font-weight: 500;
color: var(--vp-c-text-2);
}
.param-type::before {
content: "类型: ";
font-weight: 500;
color: var(--vp-c-text-2);
}
.param-required::before {
content: "必填: ";
font-weight: 500;
color: var(--vp-c-text-2);
}
.param-in::before {
content: "位置: ";
font-weight: 500;
color: var(--vp-c-text-2);
}
.param-description::before {
content: "说明: ";
font-weight: 500;
color: var(--vp-c-text-2);
}
}
</style>
156 changes: 156 additions & 0 deletions docs/.vitepress/theme/composables/useApiValidation.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
// API验证相关的组合式函数

import type { Parameter, StatusCode, HttpMethod } from "../types/api";

export const useApiValidation = () => {
// 验证HTTP方法
const validateHttpMethod = (method: string): method is HttpMethod => {
const validMethods: HttpMethod[] = [
"get",
"post",
"put",
"delete",
"patch",
"head",
"options",
];
return validMethods.includes(method.toLowerCase() as HttpMethod);
};

// 验证参数对象
const validateParameter = (param: any): param is Parameter => {
if (typeof param !== "object" || param === null) {
return false;
}

const requiredFields = ["name", "type", "required", "description"];
for (const field of requiredFields) {
if (!(field in param)) {
console.error(`Missing required field: ${field}`);
return false;
}
}

if (typeof param.name !== "string" || param.name.trim() === "") {
console.error("Parameter name must be a non-empty string");
return false;
}

if (typeof param.type !== "string") {
console.error("Parameter type must be a string");
return false;
}

if (typeof param.required !== "boolean") {
console.error("Parameter required must be a boolean");
return false;
}

if (typeof param.description !== "string") {
console.error("Parameter description must be a string");
return false;
}

// 验证可选字段
if (
param.minLength !== undefined &&
(typeof param.minLength !== "number" || param.minLength < 0)
) {
console.error("Parameter minLength must be a non-negative number");
return false;
}

if (
param.maxLength !== undefined &&
(typeof param.maxLength !== "number" || param.maxLength < 0)
) {
console.error("Parameter maxLength must be a non-negative number");
return false;
}

if (param.minimum !== undefined && typeof param.minimum !== "number") {
console.error("Parameter minimum must be a number");
return false;
}

if (param.maximum !== undefined && typeof param.maximum !== "number") {
console.error("Parameter maximum must be a number");
return false;
}

if (param.pattern !== undefined && typeof param.pattern !== "string") {
console.error("Parameter pattern must be a string");
return false;
}

if (param.enum !== undefined && !Array.isArray(param.enum)) {
console.error("Parameter enum must be an array");
return false;
}

return true;
};

// 验证状态码对象
const validateStatusCode = (status: any): status is StatusCode => {
if (typeof status !== "object" || status === null) {
return false;
}

if (
typeof status.code !== "number" ||
status.code < 100 ||
status.code > 599
) {
console.error("Status code must be a number between 100 and 599");
return false;
}

if (
typeof status.description !== "string" ||
status.description.trim() === ""
) {
console.error("Status description must be a non-empty string");
return false;
}

return true;
};

// 验证URL格式
const validateUrl = (url: string): boolean => {
try {
new URL(url);
return true;
} catch (error) {
console.error(`Invalid URL format: ${url}`);
return false;
}
};

// 验证邮箱格式
const validateEmail = (email: string): boolean => {
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
return emailRegex.test(email);
};

// 验证正则表达式
const validateRegex = (pattern: string): boolean => {
try {
new RegExp(pattern);
return true;
} catch (error) {
console.error(`Invalid regex pattern: ${pattern}`);
return false;
}
};

return {
validateHttpMethod,
validateParameter,
validateStatusCode,
validateUrl,
validateEmail,
validateRegex,
};
};
1 change: 1 addition & 0 deletions docs/.vitepress/theme/constrants/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,7 @@ const items_xrobot_api = [
{ text: "智能体API", link: "agent" },
{ text: "设备API", link: "device" },
{ text: "音色克隆API", link: "voice-clone" },
{ text: "其他API", link: "others" },
].map((item) => apply_prefix(item, Chapters.xrobot_api)),
},
];
Expand Down
3 changes: 3 additions & 0 deletions docs/.vitepress/theme/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ import { useRoute } from "vitepress";
import mediumZoom from "medium-zoom";

import ChapterContents from "./components/ChapterContents.vue";
import ApiEndpoint from "./components/ApiEndpoint.vue";

import MyLayout from "./layout.vue";

import "./index.css";
Expand All @@ -14,6 +16,7 @@ export default {
enhanceApp({ app }) {
// 注册全局组件
app.component("ChapterContents", ChapterContents);
app.component("ApiEndpoint", ApiEndpoint);
},
setup() {
const route = useRoute();
Expand Down
Loading