commit 04eec2664c823919796a8f88ebdd17c9540c175a Author: 小蚂蚁云 Date: Tue Sep 29 09:49:04 2026 +0800 签入版本 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..fa646f6 --- /dev/null +++ b/.gitignore @@ -0,0 +1,21 @@ +.DS_Store +.buildpath +.hgignore.swp +.project +.orig +.swp +.idea/ +.settings/ +.vscode/ +vender/ +log/ +composer.lock +gitpush.sh +pkg/ +bin/ +cbuild +*/.DS_Store +.vscode + +.svn +node_modules/ \ No newline at end of file diff --git a/.jenkinsfile b/.jenkinsfile new file mode 100644 index 0000000..e62645c --- /dev/null +++ b/.jenkinsfile @@ -0,0 +1,126 @@ +pipeline { + agent any + options { + buildDiscarder(logRotator(artifactDaysToKeepStr: '', artifactNumToKeepStr: '', daysToKeepStr: '2', numToKeepStr: '2')) + } + stages { + stage('Setup Parameters & ENV') { + steps { + configFileProvider([configFile(fileId: 'rxthinkcmf-wiki', variable: 'settingsFile')]) { + script { + def config = readJSON file:"$settingsFile" + def jobNameStr = JOB_NAME.tokenize('/') as String[] + def jobName = jobNameStr[0] + def branchConfig = config."branchConfig"."${env.BRANCH_NAME}" + def serviceConfig = config."serviceConfig"."${jobName}" + + MAVEN_SET = branchConfig.MAVEN_SET + registry = branchConfig.registry + registryCredential = branchConfig.registryCredential + namespace = branchConfig.namespace + jenkinsAgent = branchConfig.jenkinsAgent + + echo "jobName: ${jobName}" + echo "The Maven Set XML for the ${env.BRANCH_NAME} branch is: ${MAVEN_SET}" + + projectName = serviceConfig.projectName + packageName = serviceConfig.packageName + serverHost01= serviceConfig.serverHost01 + serviceName = serviceConfig.serviceName + + echo "projectName: ${projectName}" + echo "packageName: ${packageName}" + echo "serverHost01: ${serverHost01}" + + if ("${env.BRANCH_NAME}" == "dev") { + serverIP01 = branchConfig."${serverHost01}" + echo "dev serverIP01: ${serverIP01}" + properties([ + parameters([ + string( + defaultValue: 'dev', + description: '编译参数', + name: 'PROFILES', + trim: true + ) + ]) + ]) + } else if ("${env.BRANCH_NAME}" == "test"){ + serverIP01 = branchConfig."${serverHost01}" + echo "test serverIP01: ${serverIP01}" + properties([ + parameters([ + string( + defaultValue: 'test', + description: '编译参数', + name: 'PROFILES', + trim: true + ) + ]) + ]) + } else if ("${env.BRANCH_NAME}" == "master") { + properties([ + parameters([ + string( + defaultValue: 'prod', + description: '编译参数', + name: 'PROFILES', + trim: true + ) + ]) + ]) + serverIP01 = branchConfig."${serverHost01}" + echo "prod serverIP01: ${serverIP01}" + } else { + error("Build failed because failed to fetch settings for branch") + } + echo "The serverIP for the branch: ${env.BRANCH_NAME}, profile: ${PROFILES} is: ${serverIP01}" + } + } + + } + } + stage('Build') { + agent { + label { + label jenkinsAgent + } + } + steps { + sh """ + node -v + yarn -v + yarn config set registry https://registry.npmmirror.com + yarn config get registry + yarn config set strict-ssl false + cd ${serviceName} + yarn install + yarn run docs:build + ls -lsh; + """ + } + + } + stage("Deploy") { + options { + skipDefaultCheckout() + } + agent { + label { + label jenkinsAgent + } + } + steps { + script { + sh 'echo "#### MD5 HASH ####"' + sh "cd ${serviceName}; ls -lsh; cd .vitepress; ls -lsh; rm -rf ${projectName} ${packageName}; mv dist ${projectName} && tar zcf ${packageName} ${projectName}; md5sum ${packageName}" + + sh 'echo "#### scp and restart ####"' + sh "ssh ${serverIP01} 'cd /usr/local/docker/nginx/html/rxthinkcmf-wiki; rm -rf ${projectName}; rm -f ${packageName}'" + sh "cd ${serviceName}; ls -lsh; cd .vitepress; scp ${packageName} ${serverIP01}:/usr/local/docker/nginx/html/rxthinkcmf-wiki" + sh "ssh ${serverIP01} 'cd /usr/local/docker/nginx/html/rxthinkcmf-wiki; tar zxf ${packageName}; ls -l'" + } + } + } + } +} diff --git a/RXThinkCMF_ThinkPhp6_EleVue/.gitignore b/RXThinkCMF_ThinkPhp6_EleVue/.gitignore new file mode 100644 index 0000000..98a3566 --- /dev/null +++ b/RXThinkCMF_ThinkPhp6_EleVue/.gitignore @@ -0,0 +1,5 @@ +.vitepress/cache +.vscode/ +node_modules +.svn +.git \ No newline at end of file diff --git a/RXThinkCMF_ThinkPhp6_EleVue/.vitepress/config/en.ts b/RXThinkCMF_ThinkPhp6_EleVue/.vitepress/config/en.ts new file mode 100644 index 0000000..106ec86 --- /dev/null +++ b/RXThinkCMF_ThinkPhp6_EleVue/.vitepress/config/en.ts @@ -0,0 +1,51 @@ +// import type { DefaultTheme, LocaleSpecificConfig } from "vitepress"; + +// export const META_URL = "https://www.rxthink.cn"; +// export const META_TITLE = "RXThinkCMF"; +// export const META_DESCRIPTION = "Enterprise Admin Framework"; + +// export const enConfig: LocaleSpecificConfig = { +// description: META_DESCRIPTION, +// head: [ +// ["meta", { property: "og:url", content: META_URL }], +// ["meta", { property: "og:description", content: META_DESCRIPTION }], +// ["meta", { property: "twitter:url", content: META_URL }], +// ["meta", { property: "twitter:title", content: META_TITLE }], +// ["meta", { property: "twitter:description", content: META_DESCRIPTION }], +// [ +// "script", +// {}, +// ` +// var _hmt = _hmt || []; +// (function() { +// var hm = document.createElement("script"); +// hm.src = "https://hm.baidu.com/hm.js?f2662051bbed33ef4673edf8bfe609fe"; +// var s = document.getElementsByTagName("script")[0]; +// s.parentNode.insertBefore(hm, s); +// })(); +// `, +// ], +// ], +// +// themeConfig: { +// editLink: { +// pattern: "https://gitee.com/rxthinkcmf/RXThinkCMF_ThinkPhp6_EleVue", +// text: "Suggest changes to this page", +// }, +// +// nav: [ +// { text: "Guide", link: "/en/introduction.html" }, +// ], +// +// sidebar: { +// "/en/": [ +// { +// text: "Introduction", +// items: [ +// { text: "About", link: "/en/introduction.html" }, +// ], +// }, +// ], +// }, +// }, +// }; diff --git a/RXThinkCMF_ThinkPhp6_EleVue/.vitepress/config/index.ts b/RXThinkCMF_ThinkPhp6_EleVue/.vitepress/config/index.ts new file mode 100644 index 0000000..2294856 --- /dev/null +++ b/RXThinkCMF_ThinkPhp6_EleVue/.vitepress/config/index.ts @@ -0,0 +1,42 @@ +import { defineConfig } from "vitepress"; +import { enConfig } from "./en"; +import { sharedConfig } from "./shared"; +import { zhConfig } from "./zh"; + +export default defineConfig({ + ...sharedConfig, + ignoreDeadLinks: [ + (link) => link.includes('localhost'), + ], + + locales: { + root: { label: "简体中文", lang: "zh-CN", link: "/", ...zhConfig }, + // root: { label: "English", lang: "en-US", link: "/", ...enConfig }, + // zh: { label: "简体中文", lang: "zh-CN", link: "/zh/", ...zhConfig }, + // es: { + // label: "Español", + // lang: "es-ES", + // link: "https://es-pinia.vercel.app/", + // }, + // ko: { + // label: "한국어", + // lang: "ko-KR", + // link: "https://pinia.vuejs.kr/", + // }, + // pt: { + // label: "Português", + // lang: "pt-PT", + // link: "https://pinia-docs-pt.netlify.app/", + // }, + // uk: { + // label: "Українська", + // lang: "uk-UA", + // link: "https://pinia-ua.netlify.app", + // }, + // ru: { + // label: "Русский", + // lang: "ru-RU", + // link: "https://pinia-ru.netlify.app", + // }, + }, +}); diff --git a/RXThinkCMF_ThinkPhp6_EleVue/.vitepress/config/shared.ts b/RXThinkCMF_ThinkPhp6_EleVue/.vitepress/config/shared.ts new file mode 100644 index 0000000..464a2d8 --- /dev/null +++ b/RXThinkCMF_ThinkPhp6_EleVue/.vitepress/config/shared.ts @@ -0,0 +1,109 @@ +import { defineConfig, HeadConfig } from "vitepress"; + +export const META_IMAGE = "/social.png"; + +const rControl = /[\u0000-\u001f]/g; +const rSpecial = /[\s~`!@#$%^&*()\-_+=[\]{}|\\;:"'“”‘’<>,.?/]+/g; +const rCombining = /[\u0300-\u036F]/g; + +/** + * Default slugification function + */ +export const slugify = (str: string): string => + str + .normalize("NFKD") + // Remove accents + .replace(rCombining, "") + // Remove control characters + .replace(rControl, "") + // Replace special characters + .replace(rSpecial, "-") + // ensure it doesn't start with a number + .replace(/^(\d)/, "_$1"); + +export const sharedConfig = defineConfig({ + title: "小蚂蚁云", + appearance: "light", + + markdown: { + theme: { + dark: "dracula-soft", + light: "vitesse-light", + }, + + attrs: { + leftDelimiter: "%{", + rightDelimiter: "}%", + }, + + anchor: { + slugify, + }, + }, + + head: [ + ["link", { rel: "icon", type: "image/svg+xml", href: "/logo.svg" }], + ["link", { rel: "icon", type: "image/png", href: "/logo.png" }], + + [ + "meta", + { + property: "og:type", + content: "website", + }, + ], + + [ + "meta", + { + property: "twitter:card", + content: "summary_large_image", + }, + ], + [ + "meta", + { + property: "twitter:image", + content: META_IMAGE, + }, + ], + + // Top banner + [ + "script", + { + src: "/banner.js", + // @ts-expect-error: vitepress bug + async: true, + type: "text/javascript", + }, + ], + ], + + themeConfig: { + logo: "/logo.svg", + outline: [2, 3], + + socialLinks: [ + { + icon: "github", + link: "https://gitee.com/rxthinkcmf/RXThinkCMF_ThinkPhp6_EleVue", + }, + ], + + footer: { + copyright: + "Copyright © 2018~2026 www.rxthink.cn. All rights reserved.", + message: "小蚂蚁云团队 · 提供技术支持", + }, + + editLink: { + pattern: "https://gitee.com/rxthinkcmf/RXThinkCMF_ThinkPhp6_EleVue", + text: "对本页提出修改建议", + }, + + search: { + provider: "local", + }, + }, +}); diff --git a/RXThinkCMF_ThinkPhp6_EleVue/.vitepress/config/zh.ts b/RXThinkCMF_ThinkPhp6_EleVue/.vitepress/config/zh.ts new file mode 100644 index 0000000..ec529db --- /dev/null +++ b/RXThinkCMF_ThinkPhp6_EleVue/.vitepress/config/zh.ts @@ -0,0 +1,542 @@ +import type { DefaultTheme, LocaleSpecificConfig } from "vitepress"; + +export const META_URL = "https://www.rxthink.cn"; +export const META_TITLE = "RXThinkCMF ThinkPHP6+ElementPlus 开发文档"; +export const META_DESCRIPTION = + "RXThinkCMF 企业级前后端分离后台管理系统 - ThinkPHP6.1 + Vue3 + ElementPlus"; + +export const zhConfig: LocaleSpecificConfig = { + description: META_DESCRIPTION, + head: [ + ["meta", { property: "og:url", content: META_URL }], + ["meta", { property: "og:description", content: META_DESCRIPTION }], + ["meta", { property: "twitter:url", content: META_URL }], + ["meta", { property: "twitter:title", content: META_TITLE }], + ["meta", { property: "twitter:description", content: META_DESCRIPTION }], + ], + + themeConfig: { + editLink: { + pattern: "https://gitee.com/rxthink/thinkphp6", + text: "对本页提出修改建议", + }, + + outline: { + label: "本页内容", + }, + + docFooter: { + prev: "上一页", + next: "下一页", + }, + + nav: [ + { text: "官网", link: "https://www.rxthink.cn" }, + { text: "授权", link: "https://www.rxthink.cn/goods/detail/2" }, + { + text: "部署文档", + items: [ + { + text: "RXThinkCMF混编版ThinkPHP6+Layui版本", + link: "http://docs.thinkphp6.layui.rxthink.cn", + }, + { + text: "RXThinkCMF前后端分离ThinkPHP6+ElementPlus版本", + link: "http://docs.thinkphp6.elevue.rxthink.cn", + }, + { + text: "RXThinkCMF前后端分离ThinkPHP6+AntDesign版本", + link: "http://docs.thinkphp6.antdvue.rxthink.cn", + }, + { + text: "RXThinkCMF前后端分离ThinkPHP6+NaiveUI版本", + link: "http://docs.thinkphp6.naivevue.rxthink.cn", + }, + { + text: "RXThinkCMF混编版ThinkPHP8+Layui版本", + link: "http://docs.thinkphp8.layui.rxthink.cn", + }, + { + text: "RXThinkCMF前后端分离ThinkPHP8+ElementPlus版本", + link: "http://docs.thinkphp8.elevue.rxthink.cn", + }, + { + text: "RXThinkCMF前后端分离ThinkPHP8+AntDesign版本", + link: "http://docs.thinkphp8.antdvue.rxthink.cn", + }, + { + text: "RXThinkCMF前后端分离ThinkPHP8+NaiveUI版本", + link: "http://docs.thinkphp8.naivevue.rxthink.cn", + }, + { + text: "RXThinkCMF混编版Laravel13+Layui版本", + link: "http://docs.laravel13.layui.rxthink.cn", + }, + { + text: "RXThinkCMF前后端分离Laravel13+ElementPlus版本", + link: "http://docs.laravel13.elevue.rxthink.cn", + }, + { + text: "RXThinkCMF前后端分离Laravel13+AntDesign版本", + link: "http://docs.laravel13.antdvue.rxthink.cn", + }, + { + text: "RXThinkCMF前后端分离Laravel13+NaiveUI版本", + link: "http://docs.laravel13.naivevue.rxthink.cn", + }, + ], + }, + { + text: "在线演示", + items: [ + { + text: "RXThinkCMF混编版ThinkPHP6+Layui版本", + link: "http://manage.thinkphp6.layui.rxthink.cn", + }, + { + text: "RXThinkCMF前后端分离ThinkPHP6+ElementPlus版本", + link: "http://manage.thinkphp6.elevue.rxthink.cn", + }, + { + text: "RXThinkCMF前后端分离ThinkPHP6+AntDesign版本", + link: "http://manage.thinkphp6.antdvue.rxthink.cn", + }, + { + text: "RXThinkCMF前后端分离ThinkPHP6+NaiveUI版本", + link: "http://manage.thinkphp6.naivevue.rxthink.cn", + }, + { + text: "RXThinkCMF混编版ThinkPHP8+Layui版本", + link: "http://manage.thinkphp8.layui.rxthink.cn", + }, + { + text: "RXThinkCMF前后端分离ThinkPHP8+ElementPlus版本", + link: "http://manage.thinkphp8.elevue.rxthink.cn", + }, + { + text: "RXThinkCMF前后端分离ThinkPHP8+AntDesign版本", + link: "http://manage.thinkphp8.antdvue.rxthink.cn", + }, + { + text: "RXThinkCMF前后端分离ThinkPHP8+NaiveUI版本", + link: "http://manage.thinkphp8.naivevue.rxthink.cn", + }, + { + text: "RXThinkCMF混编版Laravel13+Layui版本", + link: "http://manage.laravel13.layui.rxthink.cn", + }, + { + text: "RXThinkCMF前后端分离Laravel13+ElementPlus版本", + link: "http://manage.laravel13.elevue.rxthink.cn", + }, + { + text: "RXThinkCMF前后端分离Laravel13+AntDesign版本", + link: "http://manage.laravel13.antdvue.rxthink.cn", + }, + { + text: "RXThinkCMF前后端分离Laravel13+NaiveUI版本", + link: "http://manage.laravel13.naivevue.rxthink.cn", + }, + ], + }, + { + text: "广告赞助", + link: "/zh/other/sponsor.html", + }, + { + text: "相关链接", + items: [ + { text: "官方网站", link: "https://www.rxthink.cn" }, + { text: "Gitee 仓库", link: "https://gitee.com/rxthinkcmf/RXThinkCMF_ThinkPhp6_EleVue" }, + { text: "ThinkPHP 官网", link: "https://www.thinkphp.cn/" }, + { text: "Vue3 文档", link: "https://cn.vuejs.org/" }, + { text: "ElementPlus", link: "https://element-plus.org/zh-CN/" }, + ], + }, + ], + + sidebar: { + "/zh/": [ + // ================================================================ + // 第 1 章:了解项目 + // ================================================================ + { + text: "📖 1. 了解项目", + collapsed: false, + items: [ + { text: "1.1 项目介绍", link: "/zh/introduction.html" }, + { text: "1.2 为什么选择我们", link: "/zh/why.html" }, + { text: "1.3 项目目录结构", link: "/zh/directory.html" }, + { text: "1.4 新手入门指南", link: "/zh/course.html" }, + { text: "1.5 技术栈说明", link: "/zh/tech-stack.html" }, + { text: "1.6 更新日志", link: "/zh/changelog.html" }, + ], + }, + + // ================================================================ + // 第 2 章:快速入门 + // ================================================================ + { + text: "🚀 2. 快速入门", + collapsed: false, + items: [ + { text: "2.1 开发环境准备", link: "/zh/quickstart/environment.html" }, + { text: "2.2 后端服务启动", link: "/zh/quickstart/backend-start.html" }, + { text: "2.3 前端服务启动", link: "/zh/quickstart/frontend-start.html" }, + { text: "2.4 数据库初始化", link: "/zh/quickstart/database-init.html" }, + { text: "2.5 环境变量配置", link: "/zh/quickstart/env-vars.html" }, + { text: "2.6 第一个接口调试", link: "/zh/quickstart/first-api.html" }, + { text: "2.7 Docker 快速体验", link: "/zh/quickstart/docker.html" }, + { text: "2.8 常见问题 FAQ", link: "/zh/quickstart/faq.html" }, + ], + }, + + // ================================================================ + // 第 3 章:模块开发实战 + // ================================================================ + { + text: "🧪 3. 模块开发实战", + collapsed: false, + items: [ + { text: "3.1 概述与目标", link: "/zh/guide/overview.html" }, + { text: "3.2 数据库建表", link: "/zh/guide/database.html" }, + { text: "3.3 模型层开发", link: "/zh/guide/model.html" }, + { text: "3.4 验证器开发", link: "/zh/guide/validator.html" }, + { text: "3.5 逻辑层开发", link: "/zh/guide/logic.html" }, + { text: "3.6 控制器开发", link: "/zh/guide/controller.html" }, + { text: "3.7 路由注册", link: "/zh/guide/route.html" }, + { text: "3.8 权限节点配置", link: "/zh/guide/permission.html" }, + { text: "3.9 前端联调指引", link: "/zh/guide/frontend-integration.html" }, + { text: "3.10 完整代码清单", link: "/zh/guide/code-list.html" }, + { text: "3.11 常见问题与排错", link: "/zh/guide/faq.html" }, + ], + }, + + // ================================================================ + // 第 4 章:架构设计 + // ================================================================ + { + text: "🏛️ 4. 架构设计", + collapsed: false, + items: [ + { text: "4.1 整体架构概览", link: "/zh/architecture/overview.html" }, + { text: "4.2 请求处理链路", link: "/zh/architecture/request-flow.html" }, + { text: "4.3 后端分层架构", link: "/zh/architecture/backend-layer.html" }, + { text: "4.4 前端分层架构", link: "/zh/architecture/frontend-layer.html" }, + { text: "4.5 目录结构设计", link: "/zh/architecture/directory-design.html" }, + { text: "4.6 核心基类体系", link: "/zh/architecture/base-classes.html" }, + { text: "4.7 横切机制", link: "/zh/architecture/cross-cutting.html" }, + { text: "4.8 缓存架构设计", link: "/zh/architecture/cache.html" }, + { text: "4.9 数据库设计", link: "/zh/architecture/database-design.html" }, + { text: "4.10 安全架构设计", link: "/zh/architecture/security.html" }, + { text: "4.11 扩展性设计", link: "/zh/architecture/extensibility.html" }, + ], + }, + + // ================================================================ + // 第 5 章:核心功能 + // ================================================================ + { + text: "🔐 5. 核心功能", + collapsed: false, + items: [ + { + text: "认证与安全", + collapsed: false, + items: [ + { text: "5.1 用户认证(JWT)", link: "/zh/core/jwt.html" }, + { text: "5.2 认证流程实战", link: "/zh/core/auth-flow.html" }, + { text: "5.3 验证码机制", link: "/zh/core/captcha.html" }, + { text: "5.4 权限管理(RBAC)", link: "/zh/core/rbac.html" }, + { text: "5.5 跨域资源共享", link: "/zh/core/cors.html" }, + { text: "5.6 密码加密服务", link: "/zh/core/password.html" }, + { text: "5.7 演示模式", link: "/zh/core/demo-mode.html" }, + ], + }, + { + text: "数据与配置", + collapsed: false, + items: [ + { text: "5.8 多租户隔离", link: "/zh/core/tenant.html" }, + { text: "5.9 数据权限", link: "/zh/core/data-scope.html" }, + { text: "5.10 字典模块", link: "/zh/core/dict.html" }, + { text: "5.11 字典配置与使用", link: "/zh/core/dict-guide.html" }, + { text: "5.12 系统配置管理", link: "/zh/core/config.html" }, + { text: "5.13 系统参数管理", link: "/zh/core/param.html" }, + ], + }, + { + text: "日志与监控", + collapsed: false, + items: [ + { text: "5.14 操作日志", link: "/zh/core/operation-log.html" }, + { text: "5.15 登录日志", link: "/zh/core/login-log.html" }, + { text: "5.16 异常与响应处理", link: "/zh/core/exception.html" }, + ], + }, + { + text: "防护与治理", + collapsed: false, + items: [ + { text: "5.17 防重复提交", link: "/zh/core/idempotent.html" }, + ], + }, + { + text: "注解系统", + collapsed: false, + items: [ + { text: "5.18 注解系统总览", link: "/zh/core/attributes.html" }, + ], + }, + ], + }, + + // ================================================================ + // 第 6 章:业务模块 + // ================================================================ + { + text: "📦 6. 业务模块", + collapsed: false, + items: [ + { text: "6.1 用户管理", link: "/zh/modules/user.html" }, + { text: "6.2 角色管理", link: "/zh/modules/role.html" }, + { text: "6.3 菜单管理", link: "/zh/modules/menu.html" }, + { text: "6.4 部门管理", link: "/zh/modules/dept.html" }, + { text: "6.5 职级管理", link: "/zh/modules/level.html" }, + { text: "6.6 岗位管理", link: "/zh/modules/position.html" }, + { text: "6.7 文章管理", link: "/zh/modules/article.html" }, + { text: "6.8 公告与链接管理", link: "/zh/modules/content.html" }, + { text: "6.9 行政区划管理", link: "/zh/modules/city.html" }, + { text: "6.10 分类管理", link: "/zh/modules/category.html" }, + { text: "6.11 定时任务", link: "/zh/modules/job.html" }, + { text: "6.12 参数管理", link: "/zh/modules/param.html" }, + { text: "6.13 租户管理", link: "/zh/modules/tenant.html" }, + { text: "6.14 文件模板管理", link: "/zh/modules/filetemplate.html" }, + { text: "6.15 首页与个人中心", link: "/zh/modules/index.html" }, + ], + }, + + // ================================================================ + // 第 7 章:文件处理 + // ================================================================ + { + text: "🧰 7. 文件处理", + collapsed: false, + items: [ + { text: "7.1 文件上传全流程", link: "/zh/tools/upload.html" }, + { text: "7.2 上传配置详解", link: "/zh/tools/upload-config.html" }, + { text: "7.3 富文本处理", link: "/zh/tools/rich-text.html" }, + { text: "7.4 数据导入导出", link: "/zh/tools/excel.html" }, + { text: "7.5 前端文件组件", link: "/zh/tools/file-components.html" }, + { text: "7.6 数据库迁移工具", link: "/zh/tools/db-migrate.html" }, + ], + }, + + // ================================================================ + // 第 8 章:接口规范与对接 + // ================================================================ + { + text: "🔗 8. 接口规范与对接", + collapsed: false, + items: [ + { text: "8.1 统一响应格式", link: "/zh/api-spec/response.html" }, + { text: "8.2 错误码对照表", link: "/zh/api-spec/error-code.html" }, + { text: "8.3 分页查询规范", link: "/zh/api-spec/pagination.html" }, + { text: "8.4 文件上传接口", link: "/zh/api-spec/upload.html" }, + { text: "8.5 字典数据接口", link: "/zh/api-spec/dict-api.html" }, + { text: "8.6 Token 使用说明", link: "/zh/api-spec/token.html" }, + { text: "8.7 API 接口总览", link: "/zh/api-spec/overview.html" }, + ], + }, + + // ================================================================ + // 第 9 章:前端开发 + // ================================================================ + { + text: "🖥️ 9. 前端开发", + collapsed: false, + items: [ + { + text: "工程基础", + collapsed: false, + items: [ + { text: "9.1 前端工程概述", link: "/zh/frontend/overview.html" }, + { text: "9.2 目录结构详解", link: "/zh/frontend/directory.html" }, + { text: "9.3 环境与构建配置", link: "/zh/frontend/env-config.html" }, + ], + }, + { + text: "核心机制", + collapsed: false, + items: [ + { text: "9.4 HTTP 请求封装", link: "/zh/frontend/http.html" }, + { text: "9.5 路由与动态菜单", link: "/zh/frontend/router.html" }, + { text: "9.6 状态管理(Pinia)", link: "/zh/frontend/store.html" }, + { text: "9.7 权限控制", link: "/zh/frontend/permission.html" }, + { text: "9.8 登录流程", link: "/zh/frontend/login.html" }, + ], + }, + { + text: "公共组件", + collapsed: false, + items: [ + { text: "9.9 数据展示组件", link: "/zh/frontend/display-components.html" }, + { text: "9.10 数据录入组件", link: "/zh/frontend/form-components.html" }, + { text: "9.11 Excel 导入导出组件", link: "/zh/frontend/excel-components.html" }, + ], + }, + { + text: "开发实践", + collapsed: false, + items: [ + { text: "9.12 新增页面开发步骤", link: "/zh/frontend/new-page.html" }, + { text: "9.13 API 模块编写规范", link: "/zh/frontend/api-module.html" }, + { text: "9.14 样式与主题定制", link: "/zh/frontend/style.html" }, + { text: "9.15 布局系统", link: "/zh/frontend/layout.html" }, + { text: "9.16 工具函数", link: "/zh/frontend/utils.html" }, + { text: "9.17 国际化(i18n)", link: "/zh/frontend/i18n.html" }, + ], + }, + ], + }, + + // ================================================================ + // 第 10 章:代码生成器 + // ================================================================ + { + text: "🔧 10. 代码生成器", + collapsed: false, + items: [ + { text: "10.1 概述与架构", link: "/zh/generator/overview.html" }, + { text: "10.2 CLI 命令行使用", link: "/zh/generator/cli.html" }, + { text: "10.3 Web 管理界面", link: "/zh/generator/web-ui.html" }, + { text: "10.4 表结构解析引擎", link: "/zh/generator/parser.html" }, + { text: "10.5 代码生成引擎", link: "/zh/generator/code-engine.html" }, + { text: "10.6 自定义模板开发", link: "/zh/generator/templates.html" }, + { text: "10.7 配置文件详解", link: "/zh/generator/config.html" }, + { text: "10.8 实战案例", link: "/zh/generator/demo.html" }, + { text: "10.9 常见问题", link: "/zh/generator/faq.html" }, + ], + }, + + // ================================================================ + // 第 11 章:运维部署 + // ================================================================ + { + text: "⚙️ 11. 运维部署", + collapsed: false, + items: [ + { text: "11.1 部署 Checklist", link: "/zh/deploy/checklist.html" }, + { text: "11.2 环境规划", link: "/zh/deploy/env-plan.html" }, + { text: "11.3 数据库迁移", link: "/zh/deploy/db-migrate.html" }, + { text: "11.4 后端部署", link: "/zh/deploy/backend.html" }, + { text: "11.5 前端部署", link: "/zh/deploy/frontend.html" }, + { text: "11.6 Docker 生产部署", link: "/zh/deploy/docker.html" }, + { text: "11.7 性能调优", link: "/zh/deploy/performance.html" }, + { text: "11.8 安全加固清单", link: "/zh/deploy/security.html" }, + { text: "11.9 备份与恢复", link: "/zh/deploy/backup.html" }, + { text: "11.10 故障排查手册", link: "/zh/deploy/troubleshooting.html" }, + { text: "11.11 CI/CD 流水线", link: "/zh/deploy/ci-cd.html" }, + ], + }, + + // ================================================================ + // 第 12 章:规范标准 + // ================================================================ + { + text: "📋 12. 规范标准", + collapsed: false, + items: [ + { + text: "⭐ 必须遵守", + collapsed: false, + items: [ + { text: "12.1 API 响应规范", link: "/zh/standards/api-response.html" }, + { text: "12.2 分页查询规范", link: "/zh/standards/pagination.html" }, + { text: "12.3 Git 提交规范", link: "/zh/standards/git.html" }, + ], + }, + { + text: "📌 建议遵守", + collapsed: false, + items: [ + { text: "12.4 数据删除规范", link: "/zh/standards/deletion.html" }, + { text: "12.5 代码审查清单", link: "/zh/standards/code-review.html" }, + { text: "12.6 后端编码规范", link: "/zh/standards/backend-code.html" }, + { text: "12.7 前端编码规范", link: "/zh/standards/frontend-code.html" }, + ], + }, + ], + }, + + // ================================================================ + // 附录 + // ================================================================ + { + text: "📄 附录", + collapsed: false, + items: [ + { text: "A. 常见问题汇总", link: "/zh/appendix/faq.html" }, + { text: "B. 数据库表结构速查", link: "/zh/appendix/database-tables.html" }, + { text: "C. 配置项速查", link: "/zh/appendix/config-reference.html" }, + { text: "D. 公共函数速查", link: "/zh/appendix/functions.html" }, + { text: "E. API 接口速查表", link: "/zh/appendix/api-cheatsheet.html" }, + { text: "F. 中间件执行顺序", link: "/zh/appendix/middleware.html" }, + { text: "G. 前端组件速查", link: "/zh/appendix/components.html" }, + { text: "H. 前端 Store 速查", link: "/zh/appendix/stores.html" }, + { text: "I. 前端 Enums 速查", link: "/zh/appendix/enums.html" }, + { text: "J. 术语表", link: "/zh/appendix/glossary.html" }, + { text: "K. 贡献指南", link: "/zh/other/contributing.html" }, + { text: "L. 免责声明", link: "/zh/other/declare.html" }, + { text: "M. 广告赞助", link: "/zh/other/sponsor.html" }, + { text: "N. 开源协议", link: "https://gitee.com/rxthinkcmf/RXThinkCMF_ThinkPhp6_EleVue" }, + ], + }, + ], + }, + }, +}; + +export const zhSearch: DefaultTheme.AlgoliaSearchOptions["locales"] = { + zh: { + placeholder: "搜索文档", + translations: { + button: { + buttonText: "搜索文档", + buttonAriaLabel: "搜索文档", + }, + modal: { + searchBox: { + resetButtonTitle: "清除查询条件", + resetButtonAriaLabel: "清除查询条件", + cancelButtonText: "取消", + cancelButtonAriaLabel: "取消", + }, + startScreen: { + recentSearchesTitle: "搜索历史", + noRecentSearchesText: "没有搜索历史", + saveRecentSearchButtonTitle: "保存至搜索历史", + removeRecentSearchButtonTitle: "从搜索历史中移除", + favoriteSearchesTitle: "收藏", + removeFavoriteSearchButtonTitle: "从收藏中移除", + }, + errorScreen: { + titleText: "无法获取结果", + helpText: "你可能需要检查你的网络连接", + }, + footer: { + selectText: "选择", + navigateText: "切换", + closeText: "关闭", + searchByText: "搜索供应商", + }, + noResultsScreen: { + noResultsText: "无法找到相关结果", + suggestedQueryText: "你可以尝试查询", + reportMissingResultsText: "你认为该查询应该有结果?", + reportMissingResultsLinkText: "点击反馈", + }, + }, + }, + }, +}; \ No newline at end of file diff --git a/RXThinkCMF_ThinkPhp6_EleVue/.vitepress/theme/components/AsideSponsors.vue b/RXThinkCMF_ThinkPhp6_EleVue/.vitepress/theme/components/AsideSponsors.vue new file mode 100644 index 0000000..9e7f167 --- /dev/null +++ b/RXThinkCMF_ThinkPhp6_EleVue/.vitepress/theme/components/AsideSponsors.vue @@ -0,0 +1,151 @@ + + + + + diff --git a/RXThinkCMF_ThinkPhp6_EleVue/.vitepress/theme/components/HomeSponsors.vue b/RXThinkCMF_ThinkPhp6_EleVue/.vitepress/theme/components/HomeSponsors.vue new file mode 100644 index 0000000..5cd6011 --- /dev/null +++ b/RXThinkCMF_ThinkPhp6_EleVue/.vitepress/theme/components/HomeSponsors.vue @@ -0,0 +1,59 @@ + + + + + diff --git a/RXThinkCMF_ThinkPhp6_EleVue/.vitepress/theme/components/HomeSponsorsGroup.vue b/RXThinkCMF_ThinkPhp6_EleVue/.vitepress/theme/components/HomeSponsorsGroup.vue new file mode 100644 index 0000000..7a7e9b1 --- /dev/null +++ b/RXThinkCMF_ThinkPhp6_EleVue/.vitepress/theme/components/HomeSponsorsGroup.vue @@ -0,0 +1,122 @@ + + + + + diff --git a/RXThinkCMF_ThinkPhp6_EleVue/.vitepress/theme/components/VideoModuleLink.vue b/RXThinkCMF_ThinkPhp6_EleVue/.vitepress/theme/components/VideoModuleLink.vue new file mode 100644 index 0000000..63c3bec --- /dev/null +++ b/RXThinkCMF_ThinkPhp6_EleVue/.vitepress/theme/components/VideoModuleLink.vue @@ -0,0 +1,176 @@ + + + + + diff --git a/RXThinkCMF_ThinkPhp6_EleVue/.vitepress/theme/components/sponsors.json b/RXThinkCMF_ThinkPhp6_EleVue/.vitepress/theme/components/sponsors.json new file mode 100644 index 0000000..98665e5 --- /dev/null +++ b/RXThinkCMF_ThinkPhp6_EleVue/.vitepress/theme/components/sponsors.json @@ -0,0 +1,38 @@ +{ + "platinum": [], + "gold": [], + "silver": [ + { + "alt": "企业级中台解决方案", + "href": "https://www.rxthink.cn", + "imgSrcDark": "/sponsor/rxthinkcmf.png", + "imgSrcLight": "/sponsor/rxthinkcmf.png" + }, + { + "alt": "企业级中台解决方案", + "href": "https://www.rxthink.cn", + "imgSrcDark": "/sponsor/rxthinkcmf.png", + "imgSrcLight": "/sponsor/rxthinkcmf.png" + } + ], + "bronze": [ + { + "alt": "企业级中台解决方案", + "href": "https://www.rxthink.cn", + "imgSrcDark": "/sponsor/rxthinkcmf.png", + "imgSrcLight": "/sponsor/rxthinkcmf.png" + }, + { + "alt": "企业级中台解决方案", + "href": "https://www.rxthink.cn", + "imgSrcDark": "/sponsor/rxthinkcmf.png", + "imgSrcLight": "/sponsor/rxthinkcmf.png" + }, + { + "alt": "企业级中台解决方案", + "href": "https://www.rxthink.cn", + "imgSrcDark": "/sponsor/rxthinkcmf.png", + "imgSrcLight": "/sponsor/rxthinkcmf.png" + } + ] +} diff --git a/RXThinkCMF_ThinkPhp6_EleVue/.vitepress/theme/index.ts b/RXThinkCMF_ThinkPhp6_EleVue/.vitepress/theme/index.ts new file mode 100644 index 0000000..efc6f00 --- /dev/null +++ b/RXThinkCMF_ThinkPhp6_EleVue/.vitepress/theme/index.ts @@ -0,0 +1,36 @@ +import { h } from "vue"; +import { type Theme, useRoute } from "vitepress"; +import DefaultTheme from "vitepress/theme"; +import AsideSponsors from "./components/AsideSponsors.vue"; +import VideoModuleLink from "./components/VideoModuleLink.vue"; +import "./styles/vars.css"; +import imageViewer from "vitepress-plugin-image-viewer"; +import vImageViewer from "vitepress-plugin-image-viewer/lib/vImageViewer.vue"; + +const theme: Theme = { + ...DefaultTheme, + Layout() { + return h(DefaultTheme.Layout, null, { + "home-hero-image": () => + h( + "div", + { class: "image-src" }, + h("img", { + src: "/logo.png", + alt: "小蚂蚁云", + }), + ), + "aside-ads-before": () => h(AsideSponsors), + }); + }, + setup() { + const route = useRoute(); + imageViewer(route); + }, + enhanceApp({ app }) { + app.component("VideoModuleLink", VideoModuleLink); + app.component("vImageViewer", vImageViewer); + }, +}; + +export default theme; diff --git a/RXThinkCMF_ThinkPhp6_EleVue/.vitepress/theme/styles/home-links.css b/RXThinkCMF_ThinkPhp6_EleVue/.vitepress/theme/styles/home-links.css new file mode 100644 index 0000000..01e3777 --- /dev/null +++ b/RXThinkCMF_ThinkPhp6_EleVue/.vitepress/theme/styles/home-links.css @@ -0,0 +1,11 @@ +/* CTA link styles for the home page */ + +a.cta { + text-align: center; + border-radius: 8px; +} + +a.cta:hover { + border-color: var(--vp-c-brand); + background-color: var(--c-bg-accent); +} diff --git a/RXThinkCMF_ThinkPhp6_EleVue/.vitepress/theme/styles/vars.css b/RXThinkCMF_ThinkPhp6_EleVue/.vitepress/theme/styles/vars.css new file mode 100644 index 0000000..8f1833a --- /dev/null +++ b/RXThinkCMF_ThinkPhp6_EleVue/.vitepress/theme/styles/vars.css @@ -0,0 +1,204 @@ +/** + * Colors + * -------------------------------------------------------------------------- */ + +:root { + --c-yellow-1: #165DFF; + --c-yellow-2: #165DFF; + --c-yellow-3: #165DFF; + --c-yellow-soft-1: #165DFF; + --c-yellow-soft-2: #165DFF; + + --c-teal: #086367; + --c-teal-light: #33898d; + + --c-white-dark: #f8f8f8; + --c-black-darker: #0d121b; + --c-black: #111827; + --c-black-light: #161f32; + --c-black-lighter: #262a44; + + --c-green-1: #165DFF; + --c-green-2: #8ae99c; + --c-green-3: #165DFF; + --c-green-soft: #316334; + + /* light theme is a bit different */ + --vp-c-brand-1: var(--vp-c-green-1); + --vp-c-brand-2: var(--vp-c-green-2); + --vp-c-brand-3: var(--vp-c-green-3); + --vp-c-brand-soft: var(--vp-c-green-soft); + + --c-text-dark-1: #d9e6eb; + --c-text-dark-2: #c4dde6; + --c-text-dark-3: #abc4cc; + --c-text-light-1: #2c3e50; + --c-text-light-2: #476582; + --c-text-light-3: #90a4b7; + + --vp-c-brand-dark: var(--c-green-soft); + --vp-c-brand-darker: var(--c-green-soft); + --vp-c-brand-dimm: rgba(100, 108, 255, 0.08); + --vp-c-brand-text: var(--c-text-light-1); + --c-bg-accent: var(--c-white-dark); + --code-bg-color: var(--c-white-dark); + --code-inline-bg-color: var(--c-white-dark); + --code-font-family: 'dm', source-code-pro, Menlo, Monaco, Consolas, + 'Courier New', monospace; + --code-font-size: 16px; + + --vp-code-block-bg: var(--vp-c-bg-alt); + --vp-code-line-highlight-color: rgba(0, 0, 0, 0.075); + --vp-code-color: var(--vp-text-color); +} + +html.dark:root { + /* --c-black: #ffffff; + --c-white: #000000; */ + /* --c-divider-light: rgba(60, 60, 67, 0.12); + --c-divider-dark: rgba(84, 84, 88, 0.48); */ + /* --c-brand-light: var(--c-yellow-light); */ + + --vp-c-brand-1: var(--c-yellow-1); + --vp-c-brand-2: var(--c-yellow-2); + --vp-c-brand-3: var(--c-yellow-3); + + --vp-c-bg-alpha-with-backdrop: rgba(20, 25, 36, 0.7); + --vp-c-bg-alpha-without-backdrop: rgba(20, 25, 36, 0.9); + + --vp-code-line-highlight-color: rgba(0, 0, 0, 0.5); + + --vp-c-text-1: var(--c-text-dark-1); + --vp-c-brand-text: var(--c-text-light-1); + --c-text-light: var(--c-text-dark-2); + --c-text-lighter: var(--c-text-dark-3); + --c-divider: var(--c-divider-dark); + --c-bg-accent: var(--c-black-light); + /* --vp-code-inline-bg: var(--vp-c-black-light); */ + + --vp-c-bg: var(--c-black); + --vp-c-bg-soft: var(--c-black-light); + --vp-c-bg-soft-up: var(--c-black-lighter); + --vp-c-bg-mute: var(--c-black-light); + --vp-c-bg-soft-mute: var(--c-black-lighter); + --vp-c-bg-alt: #0d121b; + --vp-c-bg-elv: var(--vp-c-bg-soft); + --vp-c-bg-elv-mute: var(--vp-c-bg-soft-mute); + --vp-c-mute: var(--vp-c-bg-mute); + --vp-c-mute-dark: var(--c-black-lighter); + --vp-c-mute-darker: var(--c-black-darker); + + --vp-home-hero-name-background: -webkit-linear-gradient( + 78deg, + var(--c-yellow-2) 30%, + var(--c-green-2) + ); +} + +html.dark .DocSearch { + --docsearch-hit-active-color: var(--c-text-light-1); +} + +/** + * Component: Button + * -------------------------------------------------------------------------- */ + +:root { + --vp-button-brand-border: var(--c-yellow-soft-1); + --vp-button-brand-text: var(--c-black); + --vp-button-brand-bg: var(--c-yellow-1); + --vp-button-brand-hover-border: var(--c-yellow-2); + --vp-button-brand-hover-text: var(--c-black-darker); + --vp-button-brand-hover-bg: var(--c-yellow-2); + --vp-button-brand-active-border: var(--c-yellow-soft-1); + --vp-button-brand-active-text: var(--c-black-darker); + --vp-button-brand-active-bg: var(--vp-button-brand-bg); +} + +/** + * Component: Home + * -------------------------------------------------------------------------- */ + +:root { + --vp-home-hero-name-color: transparent; + --vp-home-hero-name-background: linear-gradient( + 292deg, + var(--c-green-1) 50%, + var(--c-green-2) + ); + --vp-home-hero-image-background-image: linear-gradient( + 15deg, + var(--c-yellow-2) 65%, + var(--c-green-1) 30% + ); + --vp-home-hero-image-filter: blur(40px); +} + +.VPHero .VPImage.image-src { + max-height: 192px; +} + +@media (min-width: 640px) { + :root { + --vp-home-hero-image-filter: blur(56px); + } + .VPHero .VPImage.image-src { + max-height: 256px; + } +} + +@media (min-width: 960px) { + :root { + --vp-home-hero-image-filter: blur(72px); + } + .VPHero .VPImage.image-src { + max-height: 320px; + } +} + +.become-sponsor { + font-size: 0.9em; + font-weight: 700; + width: auto; + text-align: center; + background-color: transparent; + padding: 0.75em 2em; + border-radius: 2em; + transition: all 0.15s ease; + box-sizing: border-box; + border: 2px solid var(--c-yellow-2); +} + +.become-sponsor.become-sponsor:hover { + background-color: var(--c-yellow-1); + text-decoration: none; + border-color: var(--c-yellow-1); + color: var(--c-text-light-1); +} + +.vp-doc a { + text-decoration: none; +} +.vp-doc a:hover { + text-decoration: underline; +} + +.sponsors-top .become-sponsor { + font-size: 0.75em; + padding: 0.2em; + width: auto; + max-width: 150px; +} + +kbd { + display: inline-block; + padding: 3px 5px; + font-size: 0.65em; + color: var(--vp-c-text-1); + vertical-align: middle; + background-color: var(--vp-c-bg-mute); + border: solid 1px var(--vp-c-bg-soft-mute); + border-radius: 6px; + box-shadow: inset 0 -1px 0 var(--vp-c-bg-soft-mute); + line-height: 0.95em; +} diff --git a/RXThinkCMF_ThinkPhp6_EleVue/.vitepress/translation-status.json b/RXThinkCMF_ThinkPhp6_EleVue/.vitepress/translation-status.json new file mode 100644 index 0000000..e15a3c7 --- /dev/null +++ b/RXThinkCMF_ThinkPhp6_EleVue/.vitepress/translation-status.json @@ -0,0 +1,6 @@ +{ + "zh": { + "hash": "3.0.0", + "date": "2026-09-07" + } +} diff --git a/RXThinkCMF_ThinkPhp6_EleVue/README.md b/RXThinkCMF_ThinkPhp6_EleVue/README.md new file mode 100644 index 0000000..6c505a9 --- /dev/null +++ b/RXThinkCMF_ThinkPhp6_EleVue/README.md @@ -0,0 +1,39 @@ +# RXThinkCMF 文档 + +基于 VitePress 构建的项目开发者文档站点。 + +## 本地开发 + +```bash +# 安装依赖 +pnpm install + +# 启动开发服务器 +pnpm docs + +# 构建静态站点 +pnpm docs:build + +# 预览构建结果 +pnpm docs:preview +``` + +## 文档结构 + +``` +zh/ +├── introduction.md # 项目介绍 +├── quickstart/ # 快速入门 +├── guide/ # 模块开发实战 +├── architecture/ # 架构设计 +├── core/ # 核心功能 +├── modules/ # 业务模块 +├── tools/ # 文件处理 +├── api-spec/ # 接口规范与对接 +├── frontend/ # 前端开发 +├── generator/ # 代码生成器 +├── deploy/ # 运维部署 +├── standards/ # 规范标准 +├── appendix/ # 附录 +└── other/ # 其他(贡献指南、免责声明、赞助) +``` diff --git a/RXThinkCMF_ThinkPhp6_EleVue/index.md b/RXThinkCMF_ThinkPhp6_EleVue/index.md new file mode 100644 index 0000000..03d6eb3 --- /dev/null +++ b/RXThinkCMF_ThinkPhp6_EleVue/index.md @@ -0,0 +1,95 @@ +--- +layout: home + +title: RXThinkCMF文档 +titleTemplate: 前后端分离ThinkPHP6+ElementPlus版 + +hero: + name: RXThinkCMF + text: "企业级中台解决方案ThinkPHP6+ElementPlus版" + tagline: "赋能开发者,助力企业发展。\n企业级数据平台底座 · 让数字化触手可及。" + image: + src: /logo.svg + alt: RXThinkCMF + actions: + - theme: brand + text: 开始使用 + link: /zh/introduction + - theme: alt + text: 在线演示 + link: http://manage.thinkphp6.elevue.rxthink.cn + - theme: alt + text: 官方授权 + link: https://www.rxthink.cn/goods/detail/2 + - theme: alt + text: ❤️ 成为赞助商 + link: /zh/other/sponsor.html + +features: + - title: 🎨 开箱即用的主题配色 + details: 支持开箱即用的多种主题配色,并提供灵活的配置选项,轻松切换主题颜色方案,满足品牌或个人偏好的视觉需求。 + link: /zh/frontend/style.html + linkText: 查看更多 + rel: noreferrer + - title: 🗄️ 支持多种数据库 + details: 提供强大的多数据库支持,可轻松集成 MySQL、PostgreSQL、SQL Server、SQLite、Oracle 等多种数据库,通过环境变量一键切换。 + link: /zh/architecture/database-design.html + linkText: 查看更多 + rel: noreferrer + - title: 🚀 全新技术栈 + details: ThinkPHP 6.1、PHP 8.2、Vue3、TypeScript、Vite、Pinia、ElementPlus 等技术栈,采用主流前后端分离架构实现。 + link: /zh/tech-stack.html + linkText: 查看更多 + rel: noreferrer + - title: 📚 丰富的模块 + details: 集成丰富的基础功能,如权限架构体系、日志、字典、配置、参数、通知公告、定时任务、多租户等功能。 + link: /zh/introduction.html + linkText: 查看更多 + rel: noreferrer + - title: 🔧 开发工具支持 + details: 内置代码生成器,支持自定义模板,一键生成前后端业务模块代码,大幅提升开发效率,实现敏捷迭代。 + link: /zh/generator/overview.html + linkText: 查看更多 + rel: noreferrer + - title: 🧩 高可扩展性 + details: 声明式 CRUD 架构,属性配置 + 生命周期钩子,核心模块与配置文件支持按需加载,便于二次开发。 + link: /zh/architecture/extensibility.html + linkText: 查看更多 + rel: noreferrer + - title: 🏗️ 前端工程化实践 + details: 基于 Vite 构建,集成 ESLint + Prettier 代码规范,配合 TypeScript 类型安全,保障代码质量与团队协作效率。 + link: /zh/frontend/overview.html + linkText: 查看更多 + rel: noreferrer + - title: 📋 操作日志与审计追踪 + details: 通过 #[Log] 注解自动记录用户操作行为与系统运行日志,支持全链路追踪,方便问题排查与安全审计。 + link: /zh/core/operation-log.html + linkText: 查看更多 + rel: noreferrer + - title: ⏰ 定时任务调度 + details: 内置轻量级任务调度器,支持 Cron 表达式配置定时任务,支持 Linux Crontab、Windows 计划任务、Daemon 守护进程三种触发方式。 + link: /zh/modules/job.html + linkText: 查看更多 + rel: noreferrer + - title: 📊 数据导出与报表 + details: 支持 Excel 格式的数据导入导出,支持大数据量分批导出避免内存溢出,满足运营统计与数据归档需求。 + link: /zh/tools/excel.html + linkText: 查看更多 + rel: noreferrer + - title: 🐳 容器化部署支持 + details: 提供 Docker 容器化部署方案,支持一键部署与弹性伸缩,简化运维流程,降低部署门槛。 + link: /zh/deploy/docker.html + linkText: 查看更多 + rel: noreferrer + - title: 🏢 多租户与数据权限 + details: 内置多租户隔离体系与数据权限控制,支持全部/本部门/仅本人三种数据范围,满足企业级 SaaS 场景需求。 + link: /zh/core/tenant.html + linkText: 查看更多 + rel: noreferrer +--- + + + + diff --git a/RXThinkCMF_ThinkPhp6_EleVue/package.json b/RXThinkCMF_ThinkPhp6_EleVue/package.json new file mode 100644 index 0000000..117bb21 --- /dev/null +++ b/RXThinkCMF_ThinkPhp6_EleVue/package.json @@ -0,0 +1,24 @@ +{ + "name": "@wiki/docs", + "version": "3.0.0", + "private": true, + "type": "module", + "scripts": { + "docs": "vitepress dev .", + "docs:build": "vitepress build .", + "docs:preview": "vitepress preview ." + }, + "dependencies": { + "@mdit-vue/shared": "^2.1.3", + "@vueuse/core": "^10.11.0", + "medium-zoom": "^1.1.0", + "viewerjs": "^1.11.7", + "vitepress": "1.3.4", + "vitepress-plugin-group-icons": "^1.3.6", + "vitepress-plugin-image-viewer": "^1.1.6" + }, + "devDependencies": { + "sass-embedded": "^1.85.1", + "vite": "^5.4.9" + } +} diff --git a/RXThinkCMF_ThinkPhp6_EleVue/public/banner.js b/RXThinkCMF_ThinkPhp6_EleVue/public/banner.js new file mode 100644 index 0000000..7f5fd73 --- /dev/null +++ b/RXThinkCMF_ThinkPhp6_EleVue/public/banner.js @@ -0,0 +1,257 @@ +((window.BitterBrainsBanner = window.BitterBrainsBanner || { + host: "", + affiliateKey: "vuerouter", + emulateDate: void 0, + emulateHour: void 0, + date: "03/09/2025, 01:30 PM Central European Standard Time", + activePhase: { + id: "VS-FREEWEEKEND25_AFFILIATE_SUPERSALE_EXT8", + countdownEnds: "2025-03-09T22:59:59.000000Z", + cta: "获取官方授权", + ends: "2025-03-09T22:59:59.000000Z", + title: "新品首发,限时特惠,即刻抢购! ", + link: "https://www.rxthink.cn", + assets: null, + static: null, + classes: ["bb-campaign-vs-affiliates-freeweekend-urgent"], + prelude: "小蚂蚁云 · 企业级旗舰产品", + // addendum: "Extended for", + subtitle: "赋能开发者,驱动企业增长,提供一站式数据中台解决方案。", + couponCode: null, + isExtended: !1, + hideBanners: !1, + assetsFormat: "png", + showCountdown: 0, + countdownFooter: null, + showDaysAsHours: !0, + affiliates: [ + "vuerouter", + "pinia", + "vueuse", + "vuejsforge", + "nuxtnation", + "vuejsdevelopers", + "VUEDEMY", + "vs_masteringpinia", + "vuejsnation", + "SENODEV", + "NSCRIPT", + ], + interlude: null, + starts: "2025-03-08T22:59:59.000Z", + isRunning: !0, + remaining: 37757670, + closedKey: "VS-FREEWEEKEND25_AFFILIATE_SUPERSALE_EXT8_CLOSED", + countdownRemaining: 37757670, + overrides: { vuejs: { hideSeconds: !0 } }, + logoMobile: "/banners/mark.svg", + logoTablet: "/banners/mark.svg", + logoDesktop: "/banners/logo.svg", + }, + fromCache: !1, + cacheTtl: 0, + type: "top", + closeable: void 0, + isOpen: !1, + countdownInterval: null, + isInitiated: !1, + style: null, + allowList: { + masteringnuxt: (n) => "/" === n || "/nuxt2" === n || "/nuxt3" === n, + vueschool: (n) => + "/" === n || + n.startsWith("/courses") || + n.startsWith("/articles") || + n.startsWith("/lessons") || + n.startsWith("/vueschool-demo"), + vueschool_free: (n) => + "/" === n || + n.startsWith("/courses") || + n.startsWith("/articles") || + n.startsWith("/lessons"), + vueschool_premium: (n) => + "/" === n || + n.startsWith("/courses") || + n.startsWith("/articles") || + n.startsWith("/lessons"), + vs_frontendnation: (n) => "/" === n, + }, + render() { + const { + countdownFooter: n, + prelude: e, + interlude: t, + addendum: b, + title: a, + subtitle: i, + cta: o, + link: r, + classes: s, + showCountdown: l, + } = this.activePhase, + d = n ? `` : "", + c = `\n
\n \n
\n
\n
\n ${ + e ? `
${e}
` : "" + }\n
\n
${a}
\n ${ + i ? `
${i}
` : "" + }\n
\n ${ + t ? `
${t}
` : "" + }\n
\n ${ + o + ? `
${o}
` + : "" + }\n
\n ${ + b ? `
${b}
` : "" + }\n ${ + l + ? `
${this.renderCountdown()}
${d}
` + : "" + }\n
\n
\n
\n \n \n \n
\n `, + p = document.createElement("a"); + p.setAttribute("id", "bb-banner"); + const u = { + utm_source: this.affiliateKey, + utm_medium: "website", + utm_campaign: "affiliate", + utm_content: `${this.type}_banner`, + banner_type: this.type, + }, + g = "internal" in this.activePhase && !!this.activePhase?.internal; + (!g && p.setAttribute("target", "_blank"), + "vueschool" !== this.affiliateKey && + (u.friend = this.affiliateKey?.toUpperCase())); + const h = new URLSearchParams(g ? {} : u).toString(); + (p.setAttribute("href", `${r}${g ? "" : "?" + h}`), + p.classList.add(...s), + (p.innerHTML = c), + this.addBodyClasses(), + (function () { + let n = document.querySelector("#bb-banner-video"); + (n || + (n = document.querySelector("#bb-banner-container") || document.body), + n.append(p)); + })(), + addEventListener("popstate", (n) => { + this.handleNavigation(); + }), + (window.history.pushState = new Proxy(window.history.pushState, { + apply: (n, e, t) => { + const b = n.apply(e, t); + return (this.handleNavigation(), b); + }, + }))); + }, + handleNavigation() { + this.addBodyClasses(); + }, + addBodyClasses() { + if (!this.isOpen) return; + const n = document.querySelectorAll("html")[0]; + (n.classList.add("has-bb-banner"), + n.classList.add(`bb-type-${this.type}`), + this.affiliateKey && n.classList.add(`bb-${this.affiliateKey}`)); + const e = window.location.pathname || "/", + t = !!window.BitterBrainsBanner.emulateDate; + !this.allowList[this.affiliateKey] || + this.allowList[this.affiliateKey](e) || + t + ? n.classList.remove("bb-banner-hidden") + : n.classList.add("bb-banner-hidden"); + }, + renderCountdown() { + const { showDaysAsHours: n, hideSeconds: e } = this.activePhase, + t = ["hours", "minutes"]; + return ( + n || t.unshift("days"), + e || t.push("seconds"), + t.map((n) => this.renderCountdownPart(n)).join("") + ); + }, + renderCountdownPart: (n) => + `\n
\n
\n
${ + n[0] + }${n.slice( + 1, + )}
\n
\n
:
\n
\n `, + getType() { + if (document.querySelector("#bb-banner-video")) return "video"; + return document.querySelector("#bb-banner-container") + ? "inline" + : this.type && ["top", "bottom"].includes(this.type) + ? this.type + : "top"; + }, + insertStyleTag() { + const n = document.createElement("style"); + ((n.textContent = window.BitterBrainsBanner.style), + document.head.append(n)); + }, + close() { + document.querySelector("#bb-banner").remove(); + const n = document.querySelectorAll("html")[0]; + (n.classList.remove("has-bb-banner"), + n.classList.remove("bb-banner-container"), + clearInterval(this.countdownInterval), + (this.isOpen = !1)); + }, + bindCloseButton() { + document.querySelector("#bb-close").addEventListener("click", (n) => { + (n.preventDefault(), + n.stopPropagation(), + this.close(), + localStorage.setItem(this.activePhase.closedKey, 1), + localStorage.setItem(`${this.activePhase.id}_BANNER`, 1)); + }); + }, + startCountdown() { + this.activePhase.countdownRemaining < 0 || + (this.updateCountdown(), + this.activePhase.isCountdownStatic || + (this.countdownInterval = setInterval(() => { + ((this.activePhase.countdownRemaining -= 1e3), + this.activePhase.countdownRemaining > 0 + ? this.updateCountdown() + : this.close()); + }, 1e3))); + }, + updateCountdown() { + if (!this.isOpen) return; + const { countdownRemaining: n, showDaysAsHours: e } = this.activePhase, + t = { + days: Math.floor(n / 864e5), + hours: Math.floor((n % 864e5) / 36e5), + minutes: Math.floor((n % 36e5) / 6e4), + seconds: Math.floor((n % 6e4) / 1e3), + }; + e && ((t.hours = t.hours + 24 * t.days), delete t.days); + for (const [n, e] of Object.entries(t)) { + const t = document.querySelector(`[data-countdown=${n}]`); + t && (t.textContent = e.toString().padStart(2, "0")); + } + }, + init() { + if ( + !this.activePhase || + localStorage.getItem(this.activePhase.closedKey) || + this.isInitiated + ) + return console.info("Not initiating"); + ((this.isOpen = !0), (this.isInitiated = !0)); + const n = setInterval(() => { + "complete" === document.readyState && + (clearInterval(n), + (this.type = this.getType()), + this.insertStyleTag(), + this.render(), + this.bindCloseButton(), + this.activePhase.showCountdown && this.startCountdown()); + }, 100); + }, +}), + (window.BitterBrainsBanner.style = + "\n :root { --bb-logo-desktop: url(/banners/logo.svg);--bb-logo-tablet: url(/banners/mark.svg);--bb-logo-mobile: url(/banners/mark.svg); }#bb-banner {\n height: 72px;\n text-decoration: none;\n color: white;\n padding-right: 30px;\n display: flex;\n justify-content: center;\n align-items: center;\n background: #000;\n overflow: hidden;\n line-height: 1.25;\n background: var(--bb-gradient);\n direction: ltr !important;\n}\n\n#bb-banner .bb-logo {\n width: 30px;\n height: 19px;\n left: 16px;\n position: absolute;\n background-size: contain;\n background-repeat: no-repeat;\n background-image: var(--bb-logo-mobile);\n}\n\n#bb-banner .bb-close {\n font-family: sans-serif;\n color: white;\n position: absolute;\n top: 29px;\n right: 8px;\n line-height: 0;\n font-size: 24px;\n height: 14px;\n width: 14px;\n display: flex;\n align-items: center;\n justify-content: center;\n}\n\n#bb-banner .bb-background {\n background-image: var(--bb-bg-mobile);\n background-repeat: no-repeat;\n background-size: cover;\n background-position: center;\n position: absolute;\n z-index: 0;\n top: 0;\n right: 0;\n bottom: 0;\n left: 0;\n}\n\n#bb-banner .bb-core {\n display: flex;\n align-items: center;\n text-align: center;\n position: relative;\n z-index: 10;\n flex-direction: column;\n}\n\n#bb-banner .bb-core .bb-slogan {\n display: flex;\n flex-direction: row;\n align-items: center;\n order: 1;\n}\n\n#bb-banner .bb-core .bb-title-container {\n display: flex;\n align-items: center;\n flex-direction: column;\n gap: 2px;\n}\n\n#bb-banner .bb-core .bb-extended {\n display: none;\n}\n\n#bb-banner .bb-core .bb-title {\n font-size: 14px;\n font-weight: bold;\n color: #00dc82;\n}\n\n#bb-banner .bb-core .bb-subtitle {\n color: #FFF;\n font-size: 12px;\n}\n\n#bb-banner .bb-core .bb-button-wrapper {\n order: 2;\n}\n\n#bb-banner .bb-core .bb-button-wrapper .bb-button {\n border-radius: 6px;\n font-weight: bold;\n color: #000;\n background: white;\n font-size: 14px;\n padding: 14px 8px;\n white-space: nowrap;\n}\n\n#bb-banner .bb-core .bb-countdown-wrapper {\n display: flex;\n align-items: center;\n flex-direction: row-reverse;\n gap: 6px;\n order: 3;\n}\n\n#bb-banner .bb-core .bb-countdown-wrapper .bb-countdown-footer {\n order: 1;\n}\n\n#bb-banner .bb-core .bb-countdown {\n align-items: center;\n line-height: 1;\n display: flex;\n font-weight: bold;\n font-size: 12px;\n color: #00dc82;\n order: 2;\n}\n\n#bb-banner .bb-core .bb-countdown .bb-countdown-item {\n display: flex;\n align-items: center;\n}\n\n#bb-banner .bb-core .bb-countdown .bb-countdown-item .bb-countdown-part > div {\n display: inline;\n}\n\n#bb-banner .bb-core .bb-countdown .bb-countdown-item:last-child .bb-countdown-colon {\n display: none;\n}\n\n#bb-banner .bb-core .bb-countdown .bb-countdown-item .bb-countdown-part .bb-countdown-text .bb-countdown-text-initial~span {\n display: none;\n}\n\n\n@media screen and (max-width: 768px) {\n #bb-banner {\n padding-right: 0px !important;\n }\n}\n\n@media (min-width: 768px) {\n\n #bb-banner .bb-close {\n right: 12px;\n }\n\n #bb-banner .bb-core {\n gap: 12px;\n flex-direction: row;\n }\n\n #bb-banner .bb-background {\n background-image: var(--bb-bg-tablet);\n }\n\n #bb-banner .bb-logo {\n background-image: var(--bb-logo-tablet);\n }\n\n #bb-banner .bb-core .bb-title {\n font-size: 18px;\n }\n\n #bb-banner .bb-core .bb-subtitle {\n font-size: 16px;\n }\n\n #bb-banner .bb-core .bb-button-wrapper {\n display: block;\n }\n\n #bb-banner .bb-core .bb-button-wrapper .bb-button {\n font-size: 18px;\n }\n\n #bb-banner .bb-core .bb-countdown-wrapper {\n flex-direction: column-reverse;\n }\n}\n\n@media (min-width: 1280px) {\n #bb-banner .bb-core .bb-title-container {\n gap: 4px;\n }\n \n #bb-banner .bb-logo {\n width: 152px;\n left: 24px;\n background-image: var(--bb-logo-desktop);\n }\n\n #bb-banner .bb-core .bb-slogan {\n gap: 4px;\n }\n\n #bb-banner .bb-background {\n background-image: var(--bb-bg-desktop);\n }\n\n #bb-banner .bb-core {\n gap: 32px;\n }\n\n #bb-banner .bb-core .bb-countdown-wrapper {\n flex-direction: column-reverse;\n }\n}\n\nhtml.has-bb-banner.bb-banner-hidden #bb-banner {\n display: none;\n}\n\n/*\nBANNER TYPES\n*********************************************************/\n\n/* Banner rendered in container element */\n\nhtml.has-bb-banner #bb-banner-container #bb-banner, html.has-bb-banner #bb-banner-video #bb-banner {\n position: relative;\n}\n\nhtml.has-bb-banner #bb-banner-container #bb-banner .bb-close {\n/* display: none;*/\n}\n\n/* Banner Fixed Top */\n\nhtml.has-bb-banner.bb-type-top {\n margin-top: 72px;\n}\n\nhtml.has-bb-banner.bb-type-top.bb-banner-hidden {\n margin-top: 0;\n}\n\nhtml.has-bb-banner.bb-type-top #bb-banner {\n position: fixed;\n top: 0;\n z-index: 60;\n left: 0;\n right: 0;\n}\n\n/* Banner Fixed Bottom */\n\nhtml.has-bb-banner.bb-type-bottom #bb-banner {\n position: fixed;\n bottom: 10px;\n z-index: 60;\n left: 10px;\n right: 10px;\n}\n/* Vue School */\n\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent {\n background: #100D1C;\n font-family: 'Inter', sans-serif;\n}\n\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent:hover .bb-core .bb-button-wrapper .bb-button {\n background: #0EFAA5;\n color: #100D1C;\n transition: background-color 0.5s, color 0.5s; \n }\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-logo {\n width: 122px;\n height: 40px;\n position: relative;\n display: block;\n background: url('/banners/logo.svg') !important;\n background-repeat: no-repeat;\n}\n\n\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core {\n gap: 60px;\n transform: translateX(80px);\n\n}\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-title-container {\n display: flex;\n flex-direction: column;\n}\n\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-prelude {\ncolor: #FFF;\nfont-size: 24px;\nfont-style: normal;\nfont-weight: 900;\nline-height: normal; \nwidth: auto;\n \n}\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-prelude b {\n background: var(--Gradient, linear-gradient(90deg, #0EFAA5 1.04%, #FAFF00 100%));\n background-clip: text;\n -webkit-background-clip: text;\n -webkit-text-fill-color: transparent; \n font-style: normal;\n font-weight: 00;\n line-height: normal; \n}\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan {\n gap: 60px;\n}\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-title {\n color: white;\ntext-align: center;\nfont-size: 16px;\nfont-style: normal;\nfont-weight: 400;\nline-height: normal; \n}\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-title b {\n color: #0EFAA5;\n font-weight: 700;\n}\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-subtitle {\n font-weight: 700;\n}\n\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-countdown-container .bb-addendum {color: var(--White, #FFF);\n text-align: center;\n font-size: 10px;\n font-style: normal;\n font-weight: 700;\n line-height: normal;\n letter-spacing: 1px;\n text-transform: uppercase; \n}\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-countdown-container {\n order: 3;\n display: flex;\n align-items: center;\n gap: 10px;\n}\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-countdown-container .bb-countdown {\npadding: 6px 14px;\nborder-radius: 4px;\nbackground: rgba(43, 246, 163, 0.20);\ncolor: white;\ntext-align: center;\nfont-size: 18px;\nfont-style: normal;\nfont-weight: 800;\nline-height: normal;\nletter-spacing: 1.4px; \n}\n\n#bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-button-wrapper .bb-button {\n display: flex;\n color: white;\n align-items: center;\n justify-items: center;\n height: 44px;\n padding: 0px 24px;\n justify-content: center;\n align-items: center;\n gap: 10px;\n border-radius: 50px;\nborder: 2px solid #0EFAA5;\nbackground: rgba(14, 250, 165, 0.40);\nfont-size: 14px;\nfont-style: normal;\nfont-weight: 700;\nline-height: normal; \n}\n\n\n\n@media screen and (max-width: 1440px) {\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core{\n gap: 30px;\n transform: translateX(0px);\n\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-countdown-container {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 2px;\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-logo {\n width: 50px;\n height: 40px;\n position: relative;\n display: block;\n background: url('/banners/mark.svg') !important;\n background-repeat: no-repeat !important;\n margin-right: 40px;\n }\n}\n@media screen and (max-width: 1280px) {\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-title-container {\n display: flex;\n flex-direction: column;\n }\n}\n\n@media screen and (max-width: 1234px) {\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core{\n margin-right: 40px;\n }\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-countdown-container {\n display: flex;\n flex-direction: row;\n justify-items: center;\n position: absolute;\n bottom: 0%;\n left: 50%;\n transform: translate(-35%, 46%);\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-countdown-container .bb-countdown {\n color: #0BD88F;\n padding: 0px;\n background: transparent;\ntext-align: center;\nfont-size: 12px;\nfont-style: normal;\nfont-weight: 700;\nline-height: 16px; /* 177.778% */\nletter-spacing: 0.9px; \nmargin-left: 2px;\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-title-container {\n margin-top: -10px;\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-logo {\n width: 35px;\n height: 40px;\n position: relative;\n display: block;\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan {\n gap: 20px;\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-prelude {\n width: 160px;\n text-align: center;\n font-size: 20px;\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-title-container .bb-title, #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-title-container .bb-subtitle {\n font-size: 14px !important;\n }\n}\n\n@media screen and (max-width: 768px) {\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-countdown-container {\n position: relative;\n bottom: 0%;\n left: 0%;\n transform: translate(0%, 0%);\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan {\n margin-top: 0px;\n gap: 0px;\n flex-direction: column;\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-title-container {\n margin-top: 0px;\n gap: 0px;\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core {\n gap: 0px;\n flex-direction: column;\n \n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-title-container .bb-title, #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-title-container .bb-subtitle {\n font-size: 11px !important;\n }\n \n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-button-wrapper {\n display: none;\n }\n\n #bb-banner.bb-campaign-vs-affiliates-freeweekend-urgent .bb-core .bb-slogan .bb-prelude {\n font-size: 13px; \n width: auto;\n }\n}\n/* Vue Router and Pinia */\n\nhtml.has-bb-banner .theme,\nhtml.has-bb-banner .theme .nav-bar,\nhtml.has-bb-banner .theme .sidebar,\nhtml.has-bb-banner .theme .page {\n margin-top: 72px;\n}\n\n/* Vite Press */\n\nhtml.has-bb-banner {\n scroll-padding-top: 134px;\n overflow: auto;\n}\n\nhtml.has-bb-banner .VPSidebar,\nhtml.has-bb-banner .VPDoc.has-sidebar.has-aside .aside-container {\n top: 72px;\n}\n\n@media (min-width: 960px) {\n html.has-bb-banner .VPNav {\n top: 72px;\n }\n}\n\n ")); +try { + window.BitterBrainsBanner.init(); +} catch { + console.warn("Error when trying to render top banner"); +} diff --git a/RXThinkCMF_ThinkPhp6_EleVue/public/banners/logo2.svg b/RXThinkCMF_ThinkPhp6_EleVue/public/banners/logo2.svg new file mode 100644 index 0000000..737a290 --- /dev/null +++ b/RXThinkCMF_ThinkPhp6_EleVue/public/banners/logo2.svg @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/RXThinkCMF_ThinkPhp6_EleVue/public/banners/mark.svg b/RXThinkCMF_ThinkPhp6_EleVue/public/banners/mark.svg new file mode 100644 index 0000000..7b6d683 --- /dev/null +++ b/RXThinkCMF_ThinkPhp6_EleVue/public/banners/mark.svg @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/RXThinkCMF_ThinkPhp6_EleVue/public/demo/1.png b/RXThinkCMF_ThinkPhp6_EleVue/public/demo/1.png new file mode 100644 index 0000000..db0da4d Binary files /dev/null and b/RXThinkCMF_ThinkPhp6_EleVue/public/demo/1.png differ diff --git a/RXThinkCMF_ThinkPhp6_EleVue/public/demo/10.png b/RXThinkCMF_ThinkPhp6_EleVue/public/demo/10.png new file mode 100644 index 0000000..0051cf3 Binary files /dev/null and b/RXThinkCMF_ThinkPhp6_EleVue/public/demo/10.png differ diff --git a/RXThinkCMF_ThinkPhp6_EleVue/public/demo/11.png b/RXThinkCMF_ThinkPhp6_EleVue/public/demo/11.png new file mode 100644 index 0000000..8d78851 Binary files /dev/null and b/RXThinkCMF_ThinkPhp6_EleVue/public/demo/11.png differ diff --git a/RXThinkCMF_ThinkPhp6_EleVue/public/demo/12.png b/RXThinkCMF_ThinkPhp6_EleVue/public/demo/12.png new file mode 100644 index 0000000..1829045 Binary files /dev/null and b/RXThinkCMF_ThinkPhp6_EleVue/public/demo/12.png differ diff --git a/RXThinkCMF_ThinkPhp6_EleVue/public/demo/13.png b/RXThinkCMF_ThinkPhp6_EleVue/public/demo/13.png new file mode 100644 index 0000000..ea9f63f Binary files /dev/null and b/RXThinkCMF_ThinkPhp6_EleVue/public/demo/13.png differ diff --git a/RXThinkCMF_ThinkPhp6_EleVue/public/demo/14.png b/RXThinkCMF_ThinkPhp6_EleVue/public/demo/14.png new file mode 100644 index 0000000..2e4f544 Binary files /dev/null and b/RXThinkCMF_ThinkPhp6_EleVue/public/demo/14.png differ diff --git a/RXThinkCMF_ThinkPhp6_EleVue/public/demo/15.png b/RXThinkCMF_ThinkPhp6_EleVue/public/demo/15.png new file mode 100644 index 0000000..8f63f66 Binary files /dev/null and b/RXThinkCMF_ThinkPhp6_EleVue/public/demo/15.png differ diff --git a/RXThinkCMF_ThinkPhp6_EleVue/public/demo/16.png b/RXThinkCMF_ThinkPhp6_EleVue/public/demo/16.png new file mode 100644 index 0000000..33617c4 Binary files /dev/null and b/RXThinkCMF_ThinkPhp6_EleVue/public/demo/16.png differ diff --git a/RXThinkCMF_ThinkPhp6_EleVue/public/demo/17.png b/RXThinkCMF_ThinkPhp6_EleVue/public/demo/17.png new file mode 100644 index 0000000..31744a9 Binary files /dev/null and b/RXThinkCMF_ThinkPhp6_EleVue/public/demo/17.png differ diff --git a/RXThinkCMF_ThinkPhp6_EleVue/public/demo/18.png b/RXThinkCMF_ThinkPhp6_EleVue/public/demo/18.png new file mode 100644 index 0000000..8388fcc Binary files /dev/null and b/RXThinkCMF_ThinkPhp6_EleVue/public/demo/18.png differ diff --git a/RXThinkCMF_ThinkPhp6_EleVue/public/demo/19.png b/RXThinkCMF_ThinkPhp6_EleVue/public/demo/19.png new file mode 100644 index 0000000..22c0e2e Binary files /dev/null and b/RXThinkCMF_ThinkPhp6_EleVue/public/demo/19.png differ diff --git a/RXThinkCMF_ThinkPhp6_EleVue/public/demo/2.png b/RXThinkCMF_ThinkPhp6_EleVue/public/demo/2.png new file mode 100644 index 0000000..dee11a4 Binary files /dev/null and b/RXThinkCMF_ThinkPhp6_EleVue/public/demo/2.png differ diff --git a/RXThinkCMF_ThinkPhp6_EleVue/public/demo/20.png b/RXThinkCMF_ThinkPhp6_EleVue/public/demo/20.png new file mode 100644 index 0000000..01c4bd0 Binary files /dev/null and b/RXThinkCMF_ThinkPhp6_EleVue/public/demo/20.png differ diff --git a/RXThinkCMF_ThinkPhp6_EleVue/public/demo/21.png b/RXThinkCMF_ThinkPhp6_EleVue/public/demo/21.png new file mode 100644 index 0000000..04a76c8 Binary files /dev/null and b/RXThinkCMF_ThinkPhp6_EleVue/public/demo/21.png differ diff --git a/RXThinkCMF_ThinkPhp6_EleVue/public/demo/22.png b/RXThinkCMF_ThinkPhp6_EleVue/public/demo/22.png new file mode 100644 index 0000000..82895e4 Binary files /dev/null and b/RXThinkCMF_ThinkPhp6_EleVue/public/demo/22.png differ diff --git a/RXThinkCMF_ThinkPhp6_EleVue/public/demo/23.png b/RXThinkCMF_ThinkPhp6_EleVue/public/demo/23.png new file mode 100644 index 0000000..df93e95 Binary files /dev/null and b/RXThinkCMF_ThinkPhp6_EleVue/public/demo/23.png differ diff --git a/RXThinkCMF_ThinkPhp6_EleVue/public/demo/24.png b/RXThinkCMF_ThinkPhp6_EleVue/public/demo/24.png new file mode 100644 index 0000000..26dcb60 Binary files /dev/null and b/RXThinkCMF_ThinkPhp6_EleVue/public/demo/24.png differ diff --git a/RXThinkCMF_ThinkPhp6_EleVue/public/demo/25.png b/RXThinkCMF_ThinkPhp6_EleVue/public/demo/25.png new file mode 100644 index 0000000..0259c18 Binary files /dev/null and b/RXThinkCMF_ThinkPhp6_EleVue/public/demo/25.png differ diff --git a/RXThinkCMF_ThinkPhp6_EleVue/public/demo/26.png b/RXThinkCMF_ThinkPhp6_EleVue/public/demo/26.png new file mode 100644 index 0000000..2b5c04a Binary files /dev/null and b/RXThinkCMF_ThinkPhp6_EleVue/public/demo/26.png differ diff --git a/RXThinkCMF_ThinkPhp6_EleVue/public/demo/27.png b/RXThinkCMF_ThinkPhp6_EleVue/public/demo/27.png new file mode 100644 index 0000000..6f7b7ca Binary files /dev/null and b/RXThinkCMF_ThinkPhp6_EleVue/public/demo/27.png differ diff --git a/RXThinkCMF_ThinkPhp6_EleVue/public/demo/28.png b/RXThinkCMF_ThinkPhp6_EleVue/public/demo/28.png new file mode 100644 index 0000000..e399af8 Binary files /dev/null and b/RXThinkCMF_ThinkPhp6_EleVue/public/demo/28.png differ diff --git a/RXThinkCMF_ThinkPhp6_EleVue/public/demo/3.png b/RXThinkCMF_ThinkPhp6_EleVue/public/demo/3.png new file mode 100644 index 0000000..5194174 Binary files /dev/null and b/RXThinkCMF_ThinkPhp6_EleVue/public/demo/3.png differ diff --git a/RXThinkCMF_ThinkPhp6_EleVue/public/demo/4.png b/RXThinkCMF_ThinkPhp6_EleVue/public/demo/4.png new file mode 100644 index 0000000..64d209a Binary files /dev/null and b/RXThinkCMF_ThinkPhp6_EleVue/public/demo/4.png differ diff --git a/RXThinkCMF_ThinkPhp6_EleVue/public/demo/5.png b/RXThinkCMF_ThinkPhp6_EleVue/public/demo/5.png new file mode 100644 index 0000000..bc24000 Binary files /dev/null and b/RXThinkCMF_ThinkPhp6_EleVue/public/demo/5.png differ diff --git a/RXThinkCMF_ThinkPhp6_EleVue/public/demo/6.png b/RXThinkCMF_ThinkPhp6_EleVue/public/demo/6.png new file mode 100644 index 0000000..e5385bd Binary files /dev/null and b/RXThinkCMF_ThinkPhp6_EleVue/public/demo/6.png differ diff --git a/RXThinkCMF_ThinkPhp6_EleVue/public/demo/7.png b/RXThinkCMF_ThinkPhp6_EleVue/public/demo/7.png new file mode 100644 index 0000000..546b3fd Binary files /dev/null and b/RXThinkCMF_ThinkPhp6_EleVue/public/demo/7.png differ diff --git a/RXThinkCMF_ThinkPhp6_EleVue/public/demo/8.png b/RXThinkCMF_ThinkPhp6_EleVue/public/demo/8.png new file mode 100644 index 0000000..8c89daf Binary files /dev/null and b/RXThinkCMF_ThinkPhp6_EleVue/public/demo/8.png differ diff --git a/RXThinkCMF_ThinkPhp6_EleVue/public/demo/9.png b/RXThinkCMF_ThinkPhp6_EleVue/public/demo/9.png new file mode 100644 index 0000000..4c56714 Binary files /dev/null and b/RXThinkCMF_ThinkPhp6_EleVue/public/demo/9.png differ diff --git a/RXThinkCMF_ThinkPhp6_EleVue/public/logo.png b/RXThinkCMF_ThinkPhp6_EleVue/public/logo.png new file mode 100644 index 0000000..d9d2fdb Binary files /dev/null and b/RXThinkCMF_ThinkPhp6_EleVue/public/logo.png differ diff --git a/RXThinkCMF_ThinkPhp6_EleVue/public/logo.svg b/RXThinkCMF_ThinkPhp6_EleVue/public/logo.svg new file mode 100644 index 0000000..5ca2d2a --- /dev/null +++ b/RXThinkCMF_ThinkPhp6_EleVue/public/logo.svg @@ -0,0 +1,283 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/RXThinkCMF_ThinkPhp6_EleVue/public/sponsor/djangoadmin.png b/RXThinkCMF_ThinkPhp6_EleVue/public/sponsor/djangoadmin.png new file mode 100644 index 0000000..cb13b29 Binary files /dev/null and b/RXThinkCMF_ThinkPhp6_EleVue/public/sponsor/djangoadmin.png differ diff --git a/RXThinkCMF_ThinkPhp6_EleVue/public/sponsor/easygoadmin.png b/RXThinkCMF_ThinkPhp6_EleVue/public/sponsor/easygoadmin.png new file mode 100644 index 0000000..fb9be3c Binary files /dev/null and b/RXThinkCMF_ThinkPhp6_EleVue/public/sponsor/easygoadmin.png differ diff --git a/RXThinkCMF_ThinkPhp6_EleVue/public/sponsor/rxthinkcmf.png b/RXThinkCMF_ThinkPhp6_EleVue/public/sponsor/rxthinkcmf.png new file mode 100644 index 0000000..4397ad4 Binary files /dev/null and b/RXThinkCMF_ThinkPhp6_EleVue/public/sponsor/rxthinkcmf.png differ diff --git a/RXThinkCMF_ThinkPhp6_EleVue/public/sponsor/xiaomayi.png b/RXThinkCMF_ThinkPhp6_EleVue/public/sponsor/xiaomayi.png new file mode 100644 index 0000000..83bf32d Binary files /dev/null and b/RXThinkCMF_ThinkPhp6_EleVue/public/sponsor/xiaomayi.png differ diff --git a/RXThinkCMF_ThinkPhp6_EleVue/public/volume1/1.png b/RXThinkCMF_ThinkPhp6_EleVue/public/volume1/1.png new file mode 100644 index 0000000..fdebd0a Binary files /dev/null and b/RXThinkCMF_ThinkPhp6_EleVue/public/volume1/1.png differ diff --git a/RXThinkCMF_ThinkPhp6_EleVue/public/volume1/database/1.png b/RXThinkCMF_ThinkPhp6_EleVue/public/volume1/database/1.png new file mode 100644 index 0000000..ba1da16 Binary files /dev/null and b/RXThinkCMF_ThinkPhp6_EleVue/public/volume1/database/1.png differ diff --git a/RXThinkCMF_ThinkPhp6_EleVue/public/volume1/database/2.png b/RXThinkCMF_ThinkPhp6_EleVue/public/volume1/database/2.png new file mode 100644 index 0000000..9c06e15 Binary files /dev/null and b/RXThinkCMF_ThinkPhp6_EleVue/public/volume1/database/2.png differ diff --git a/RXThinkCMF_ThinkPhp6_EleVue/public/volume1/environment/1.png b/RXThinkCMF_ThinkPhp6_EleVue/public/volume1/environment/1.png new file mode 100644 index 0000000..105065e Binary files /dev/null and b/RXThinkCMF_ThinkPhp6_EleVue/public/volume1/environment/1.png differ diff --git a/RXThinkCMF_ThinkPhp6_EleVue/public/volume1/environment/2.png b/RXThinkCMF_ThinkPhp6_EleVue/public/volume1/environment/2.png new file mode 100644 index 0000000..fa10267 Binary files /dev/null and b/RXThinkCMF_ThinkPhp6_EleVue/public/volume1/environment/2.png differ diff --git a/RXThinkCMF_ThinkPhp6_EleVue/public/volume1/environment/3.png b/RXThinkCMF_ThinkPhp6_EleVue/public/volume1/environment/3.png new file mode 100644 index 0000000..60f10f9 Binary files /dev/null and b/RXThinkCMF_ThinkPhp6_EleVue/public/volume1/environment/3.png differ diff --git a/RXThinkCMF_ThinkPhp6_EleVue/public/volume1/environment/4.png b/RXThinkCMF_ThinkPhp6_EleVue/public/volume1/environment/4.png new file mode 100644 index 0000000..bbd7ecd Binary files /dev/null and b/RXThinkCMF_ThinkPhp6_EleVue/public/volume1/environment/4.png differ diff --git a/RXThinkCMF_ThinkPhp6_EleVue/public/volume3/security/jwt_token.png b/RXThinkCMF_ThinkPhp6_EleVue/public/volume3/security/jwt_token.png new file mode 100644 index 0000000..84edc33 Binary files /dev/null and b/RXThinkCMF_ThinkPhp6_EleVue/public/volume3/security/jwt_token.png differ diff --git a/RXThinkCMF_ThinkPhp6_EleVue/public/volume5/1.png b/RXThinkCMF_ThinkPhp6_EleVue/public/volume5/1.png new file mode 100644 index 0000000..5a72867 Binary files /dev/null and b/RXThinkCMF_ThinkPhp6_EleVue/public/volume5/1.png differ diff --git a/RXThinkCMF_ThinkPhp6_EleVue/public/volume5/2.png b/RXThinkCMF_ThinkPhp6_EleVue/public/volume5/2.png new file mode 100644 index 0000000..6b58e8d Binary files /dev/null and b/RXThinkCMF_ThinkPhp6_EleVue/public/volume5/2.png differ diff --git a/RXThinkCMF_ThinkPhp6_EleVue/public/volume5/3.png b/RXThinkCMF_ThinkPhp6_EleVue/public/volume5/3.png new file mode 100644 index 0000000..ee4f2b2 Binary files /dev/null and b/RXThinkCMF_ThinkPhp6_EleVue/public/volume5/3.png differ diff --git a/RXThinkCMF_ThinkPhp6_EleVue/public/volume5/4.png b/RXThinkCMF_ThinkPhp6_EleVue/public/volume5/4.png new file mode 100644 index 0000000..9692c62 Binary files /dev/null and b/RXThinkCMF_ThinkPhp6_EleVue/public/volume5/4.png differ diff --git a/RXThinkCMF_ThinkPhp6_EleVue/vite.config.ts b/RXThinkCMF_ThinkPhp6_EleVue/vite.config.ts new file mode 100644 index 0000000..021734c --- /dev/null +++ b/RXThinkCMF_ThinkPhp6_EleVue/vite.config.ts @@ -0,0 +1,12 @@ +import { defineConfig } from "vite"; + +export default defineConfig({ + clearScreen: false, + define: { + __DEV__: "true", + __BROWSER__: "true", + }, + optimizeDeps: { + exclude: ["vue-demi", "@vueuse/shared", "@vueuse/core"], + }, +}); diff --git a/RXThinkCMF_ThinkPhp6_EleVue/yarn.lock b/RXThinkCMF_ThinkPhp6_EleVue/yarn.lock new file mode 100644 index 0000000..4ea3351 --- /dev/null +++ b/RXThinkCMF_ThinkPhp6_EleVue/yarn.lock @@ -0,0 +1,1677 @@ +# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT THIS FILE DIRECTLY. +# yarn lockfile v1 + + +"@algolia/abtesting@1.25.0": + version "1.25.0" + resolved "https://registry.npmmirror.com/@algolia/abtesting/-/abtesting-1.25.0.tgz#cf3b1ead5b9910b22ec953bcf1e20ad8b308f5d4" + integrity sha512-rSTin9Uta23uaewYVQEp8XI9T3iA/zrg0/1G2vhf8oFFDxFL5vybnZ5IQwsVAg4JpKxPX4/WYNKdcfWrZymk7w== + dependencies: + "@algolia/client-common" "5.59.0" + "@algolia/requester-browser-xhr" "5.59.0" + "@algolia/requester-fetch" "5.59.0" + "@algolia/requester-node-http" "5.59.0" + +"@algolia/autocomplete-core@1.17.9": + version "1.17.9" + resolved "https://registry.npmmirror.com/@algolia/autocomplete-core/-/autocomplete-core-1.17.9.tgz#83374c47dc72482aa45d6b953e89377047f0dcdc" + integrity sha512-O7BxrpLDPJWWHv/DLA9DRFWs+iY1uOJZkqUwjS5HSZAGcl0hIVCQ97LTLewiZmZ402JYUrun+8NqFP+hCknlbQ== + dependencies: + "@algolia/autocomplete-plugin-algolia-insights" "1.17.9" + "@algolia/autocomplete-shared" "1.17.9" + +"@algolia/autocomplete-plugin-algolia-insights@1.17.9": + version "1.17.9" + resolved "https://registry.npmmirror.com/@algolia/autocomplete-plugin-algolia-insights/-/autocomplete-plugin-algolia-insights-1.17.9.tgz#74c86024d09d09e8bfa3dd90b844b77d9f9947b6" + integrity sha512-u1fEHkCbWF92DBeB/KHeMacsjsoI0wFhjZtlCq2ddZbAehshbZST6Hs0Avkc0s+4UyBGbMDnSuXHLuvRWK5iDQ== + dependencies: + "@algolia/autocomplete-shared" "1.17.9" + +"@algolia/autocomplete-preset-algolia@1.17.9": + version "1.17.9" + resolved "https://registry.npmmirror.com/@algolia/autocomplete-preset-algolia/-/autocomplete-preset-algolia-1.17.9.tgz#911f3250544eb8ea4096fcfb268f156b085321b5" + integrity sha512-Na1OuceSJeg8j7ZWn5ssMu/Ax3amtOwk76u4h5J4eK2Nx2KB5qt0Z4cOapCsxot9VcEN11ADV5aUSlQF4RhGjQ== + dependencies: + "@algolia/autocomplete-shared" "1.17.9" + +"@algolia/autocomplete-shared@1.17.9": + version "1.17.9" + resolved "https://registry.npmmirror.com/@algolia/autocomplete-shared/-/autocomplete-shared-1.17.9.tgz#5f38868f7cb1d54b014b17a10fc4f7e79d427fa8" + integrity sha512-iDf05JDQ7I0b7JEA/9IektxN/80a2MZ1ToohfmNS3rfeuQnIKI3IJlIafD0xu4StbtQTghx9T3Maa97ytkXenQ== + +"@algolia/client-abtesting@5.59.0": + version "5.59.0" + resolved "https://registry.npmmirror.com/@algolia/client-abtesting/-/client-abtesting-5.59.0.tgz#3a6c22f3b71b79c9911d7a9b5d137401cd39d57f" + integrity sha512-bm2XN0hCSMYwStSsCBT0/PUB2BDxoyR1Lnub3c392HMEy9bi8PUSW8vR6zltVEKWG2t4PQFWMD5C07bmJxGgPg== + dependencies: + "@algolia/client-common" "5.59.0" + "@algolia/requester-browser-xhr" "5.59.0" + "@algolia/requester-fetch" "5.59.0" + "@algolia/requester-node-http" "5.59.0" + +"@algolia/client-analytics@5.59.0": + version "5.59.0" + resolved "https://registry.npmmirror.com/@algolia/client-analytics/-/client-analytics-5.59.0.tgz#0eaba7b10c46da7bac955841961134a8af16e435" + integrity sha512-XOFPOTa69WuqHR6c5tMgnUUwwqQgNSzMpxmhrgA9KmxRf8WIqEa0cokHJvohk5CYb7CZx0xeSL6Bk2IUJ7Lv7Q== + dependencies: + "@algolia/client-common" "5.59.0" + "@algolia/requester-browser-xhr" "5.59.0" + "@algolia/requester-fetch" "5.59.0" + "@algolia/requester-node-http" "5.59.0" + +"@algolia/client-common@5.59.0": + version "5.59.0" + resolved "https://registry.npmmirror.com/@algolia/client-common/-/client-common-5.59.0.tgz#3def8bbdba75c5eb13e5fbbdbbc80a6b4362f897" + integrity sha512-PC8ipLOYFKRTfIUY1J3FJxS6ryzWziaXmIX9/sNMoUR8L+XhF7hX2QAeUI87YVl5zujvlYTSOb+HAIqKXDjyHQ== + +"@algolia/client-insights@5.59.0": + version "5.59.0" + resolved "https://registry.npmmirror.com/@algolia/client-insights/-/client-insights-5.59.0.tgz#47d8127d4f9093dbb9c4456f18139378614248ac" + integrity sha512-yFNcCMM5fHiyoR0HuxMrzy+VjDcmhFUZTm2IJ2DHwGsVH3B5SEob4zTmeEZ3j/AZqNnmrJOCKl/tJnBg7+84bA== + dependencies: + "@algolia/client-common" "5.59.0" + "@algolia/requester-browser-xhr" "5.59.0" + "@algolia/requester-fetch" "5.59.0" + "@algolia/requester-node-http" "5.59.0" + +"@algolia/client-personalization@5.59.0": + version "5.59.0" + resolved "https://registry.npmmirror.com/@algolia/client-personalization/-/client-personalization-5.59.0.tgz#b4200dbbeeda202440d0a81176bc004347c655ae" + integrity sha512-GYja6HkDt2VrQhWmB2cLx3Z5fDwI9no7q+xwCWcFrTPm5CLH9QZvK0XLO9co1FcNIDxxkMaP3AXM39/XcecEOg== + dependencies: + "@algolia/client-common" "5.59.0" + "@algolia/requester-browser-xhr" "5.59.0" + "@algolia/requester-fetch" "5.59.0" + "@algolia/requester-node-http" "5.59.0" + +"@algolia/client-query-suggestions@5.59.0": + version "5.59.0" + resolved "https://registry.npmmirror.com/@algolia/client-query-suggestions/-/client-query-suggestions-5.59.0.tgz#5abd4de319821587b5fefffcb6a82557877f6fce" + integrity sha512-Wofg7bMpWh8N5qDDZs0wy6whc+KMmdNsxgrIGp9Ug2s1Bka0uq7AjyckdxReKPHLA0Q1qH8X/SNh0wn2t2X2Lw== + dependencies: + "@algolia/client-common" "5.59.0" + "@algolia/requester-browser-xhr" "5.59.0" + "@algolia/requester-fetch" "5.59.0" + "@algolia/requester-node-http" "5.59.0" + +"@algolia/client-search@5.59.0": + version "5.59.0" + resolved "https://registry.npmmirror.com/@algolia/client-search/-/client-search-5.59.0.tgz#04b78294e570410025ae5baa2af726afd94060f6" + integrity sha512-fHnALZfbEnODczGk14Y/1YBRApp6UEpZUTexGcMUPzY7RDc7q4HN2Y6jh0KUG8Jo9B+q+wOoQEc3ziR0ErbuFg== + dependencies: + "@algolia/client-common" "5.59.0" + "@algolia/requester-browser-xhr" "5.59.0" + "@algolia/requester-fetch" "5.59.0" + "@algolia/requester-node-http" "5.59.0" + +"@algolia/ingestion@1.59.0": + version "1.59.0" + resolved "https://registry.npmmirror.com/@algolia/ingestion/-/ingestion-1.59.0.tgz#c151412bef8130958b49f2b67c74267b4390a3ae" + integrity sha512-Fa38s1mHgoaLCT117sfJ6P78rtxUt93CYxBYpq1VOIcslaF+cH+1h5uikAPsKfxLnsuEDZHiBiaI6eNPTsQMRA== + dependencies: + "@algolia/client-common" "5.59.0" + "@algolia/requester-browser-xhr" "5.59.0" + "@algolia/requester-fetch" "5.59.0" + "@algolia/requester-node-http" "5.59.0" + +"@algolia/monitoring@1.59.0": + version "1.59.0" + resolved "https://registry.npmmirror.com/@algolia/monitoring/-/monitoring-1.59.0.tgz#a4d88bd92c7732d9753cd8540113b8b80435824d" + integrity sha512-NyNsRSqM2tF1MX7ZGw/j4rduoEJiQ5wfvbSo/CFVdEzYCjyxbGFRPOZyS/GETJG9rk1TdHOq8NZqKqk/u1fm4g== + dependencies: + "@algolia/client-common" "5.59.0" + "@algolia/requester-browser-xhr" "5.59.0" + "@algolia/requester-fetch" "5.59.0" + "@algolia/requester-node-http" "5.59.0" + +"@algolia/recommend@5.59.0": + version "5.59.0" + resolved "https://registry.npmmirror.com/@algolia/recommend/-/recommend-5.59.0.tgz#59fc6015f6a5d0c044d2ff1f7085a33e6229da73" + integrity sha512-nXBK2uygWtvbCOffMWqqfjjcEtp9enDKY5/2Pw/Hhw8VVaOyK2ThbGK04bNX/Le+qoK1VHYkodWGVTKfw0Otkw== + dependencies: + "@algolia/client-common" "5.59.0" + "@algolia/requester-browser-xhr" "5.59.0" + "@algolia/requester-fetch" "5.59.0" + "@algolia/requester-node-http" "5.59.0" + +"@algolia/requester-browser-xhr@5.59.0": + version "5.59.0" + resolved "https://registry.npmmirror.com/@algolia/requester-browser-xhr/-/requester-browser-xhr-5.59.0.tgz#d5212acb793a600bf456b5de9fa9821cbb982c80" + integrity sha512-yb+4afX/zja8QwX0KmV4/ae2kyYkRknsE79CRusYS2U5D8qwn2wq0cn1x12f3tm3F3+5FgmDdbuGdQTuCLSKMQ== + dependencies: + "@algolia/client-common" "5.59.0" + +"@algolia/requester-fetch@5.59.0": + version "5.59.0" + resolved "https://registry.npmmirror.com/@algolia/requester-fetch/-/requester-fetch-5.59.0.tgz#76eed36e67286b59da4b6685587d93b0e600774f" + integrity sha512-Lp52TmpA1QtNmdHzs505Xwf4tgII7RAapkDSF9AAtWIBETcGaTIaXci4Rd9nS6SdaWK1BWTSqAPp5wzh6UN3Ag== + dependencies: + "@algolia/client-common" "5.59.0" + +"@algolia/requester-node-http@5.59.0": + version "5.59.0" + resolved "https://registry.npmmirror.com/@algolia/requester-node-http/-/requester-node-http-5.59.0.tgz#b443a095ba494f5d176b891dcd07d8edad78c686" + integrity sha512-YYHLEs5rC6oRTFwT7bJaKBR9NSFzikeVHGAT1ffATmUQzR8XqyhHXjoieAUoR7fBU6I5cfy1FVeh5GoRgriuzA== + dependencies: + "@algolia/client-common" "5.59.0" + +"@antfu/install-pkg@^2.0.1": + version "2.1.0" + resolved "https://registry.npmmirror.com/@antfu/install-pkg/-/install-pkg-2.1.0.tgz#0e227e9161f8c9821b1a8944ff26366be929a519" + integrity sha512-sdg9NxU3zR4Mnawfbc/x6GB5Wf17WYud5qOuEuxXjaKpYpMkISSJEjItGebXJ2bQ4DIcly4NYH23mtkGJjvKUw== + dependencies: + package-manager-detector "^1.8.0" + tinyexec "^1.3.1" + +"@babel/helper-string-parser@^7.29.7": + version "7.29.7" + resolved "https://registry.npmmirror.com/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz#7f0871d99824d23137d60f86fcf6130fd5a1b51f" + integrity sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw== + +"@babel/helper-validator-identifier@^7.29.7": + version "7.29.7" + resolved "https://registry.npmmirror.com/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz#bd87084ced0c796ec46bda492de6e83d29e89fc2" + integrity sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg== + +"@babel/parser@^7.29.8": + version "7.29.9" + resolved "https://registry.npmmirror.com/@babel/parser/-/parser-7.29.9.tgz#e7ee0a24f752c5296c6455855ccb72662e67ea96" + integrity sha512-CjXrNHTnvqBVqHgdBysY3vk2T8tpJHb5/RMeHJBTyVa9xgugCB0CJTx/3oO8RV2QRQP391RWpB7D6hLjm8V9uA== + dependencies: + "@babel/types" "^7.29.8" + +"@babel/types@^7.29.8": + version "7.29.8" + resolved "https://registry.npmmirror.com/@babel/types/-/types-7.29.8.tgz#1229eef31d85156d70fa3f4cd859376d0eaf6863" + integrity sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg== + dependencies: + "@babel/helper-string-parser" "^7.29.7" + "@babel/helper-validator-identifier" "^7.29.7" + +"@bufbuild/protobuf@^2.5.0": + version "2.15.0" + resolved "https://registry.npmmirror.com/@bufbuild/protobuf/-/protobuf-2.15.0.tgz#9706bf5dea2c27bc9dcaccc0c2aac24e0efa6677" + integrity sha512-DAheWUkVr/SJTWCc+lg9dhY0eN4SaWlf4+bG1KzHeXbnqt0AfB/NX0Z+VunGlM1ki1B4zVvye27MpKh/svySUA== + +"@docsearch/css@3.9.0", "@docsearch/css@^3.6.1": + version "3.9.0" + resolved "https://registry.npmmirror.com/@docsearch/css/-/css-3.9.0.tgz#3bc29c96bf024350d73b0cfb7c2a7b71bf251cd5" + integrity sha512-cQbnVbq0rrBwNAKegIac/t6a8nWoUAn8frnkLFW6YARaRmAQr5/Eoe6Ln2fqkUCZ40KpdrKbpSAmgrkviOxuWA== + +"@docsearch/js@^3.6.1": + version "3.9.0" + resolved "https://registry.npmmirror.com/@docsearch/js/-/js-3.9.0.tgz#924036ce7717bc64c6a7d1087bf1181301f90ffe" + integrity sha512-4bKHcye6EkLgRE8ze0vcdshmEqxeiJM77M0JXjef7lrYZfSlMunrDOCqyLjiZyo1+c0BhUqA2QpFartIjuHIjw== + dependencies: + "@docsearch/react" "3.9.0" + preact "^10.0.0" + +"@docsearch/react@3.9.0": + version "3.9.0" + resolved "https://registry.npmmirror.com/@docsearch/react/-/react-3.9.0.tgz#d0842b700c3ee26696786f3c8ae9f10c1a3f0db3" + integrity sha512-mb5FOZYZIkRQ6s/NWnM98k879vu5pscWqTLubLFBO87igYYT4VzVazh4h5o/zCvTIZgEt3PvsCOMOswOUo9yHQ== + dependencies: + "@algolia/autocomplete-core" "1.17.9" + "@algolia/autocomplete-preset-algolia" "1.17.9" + "@docsearch/css" "3.9.0" + algoliasearch "^5.14.2" + +"@esbuild/aix-ppc64@0.21.5": + version "0.21.5" + resolved "https://registry.npmmirror.com/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz#c7184a326533fcdf1b8ee0733e21c713b975575f" + integrity sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ== + +"@esbuild/android-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.npmmirror.com/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz#09d9b4357780da9ea3a7dfb833a1f1ff439b4052" + integrity sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A== + +"@esbuild/android-arm@0.21.5": + version "0.21.5" + resolved "https://registry.npmmirror.com/@esbuild/android-arm/-/android-arm-0.21.5.tgz#9b04384fb771926dfa6d7ad04324ecb2ab9b2e28" + integrity sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg== + +"@esbuild/android-x64@0.21.5": + version "0.21.5" + resolved "https://registry.npmmirror.com/@esbuild/android-x64/-/android-x64-0.21.5.tgz#29918ec2db754cedcb6c1b04de8cd6547af6461e" + integrity sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA== + +"@esbuild/darwin-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.npmmirror.com/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz#e495b539660e51690f3928af50a76fb0a6ccff2a" + integrity sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ== + +"@esbuild/darwin-x64@0.21.5": + version "0.21.5" + resolved "https://registry.npmmirror.com/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz#c13838fa57372839abdddc91d71542ceea2e1e22" + integrity sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw== + +"@esbuild/freebsd-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.npmmirror.com/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz#646b989aa20bf89fd071dd5dbfad69a3542e550e" + integrity sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g== + +"@esbuild/freebsd-x64@0.21.5": + version "0.21.5" + resolved "https://registry.npmmirror.com/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz#aa615cfc80af954d3458906e38ca22c18cf5c261" + integrity sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ== + +"@esbuild/linux-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.npmmirror.com/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz#70ac6fa14f5cb7e1f7f887bcffb680ad09922b5b" + integrity sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q== + +"@esbuild/linux-arm@0.21.5": + version "0.21.5" + resolved "https://registry.npmmirror.com/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz#fc6fd11a8aca56c1f6f3894f2bea0479f8f626b9" + integrity sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA== + +"@esbuild/linux-ia32@0.21.5": + version "0.21.5" + resolved "https://registry.npmmirror.com/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz#3271f53b3f93e3d093d518d1649d6d68d346ede2" + integrity sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg== + +"@esbuild/linux-loong64@0.21.5": + version "0.21.5" + resolved "https://registry.npmmirror.com/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz#ed62e04238c57026aea831c5a130b73c0f9f26df" + integrity sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg== + +"@esbuild/linux-mips64el@0.21.5": + version "0.21.5" + resolved "https://registry.npmmirror.com/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz#e79b8eb48bf3b106fadec1ac8240fb97b4e64cbe" + integrity sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg== + +"@esbuild/linux-ppc64@0.21.5": + version "0.21.5" + resolved "https://registry.npmmirror.com/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz#5f2203860a143b9919d383ef7573521fb154c3e4" + integrity sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w== + +"@esbuild/linux-riscv64@0.21.5": + version "0.21.5" + resolved "https://registry.npmmirror.com/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz#07bcafd99322d5af62f618cb9e6a9b7f4bb825dc" + integrity sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA== + +"@esbuild/linux-s390x@0.21.5": + version "0.21.5" + resolved "https://registry.npmmirror.com/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz#b7ccf686751d6a3e44b8627ababc8be3ef62d8de" + integrity sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A== + +"@esbuild/linux-x64@0.21.5": + version "0.21.5" + resolved "https://registry.npmmirror.com/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz#6d8f0c768e070e64309af8004bb94e68ab2bb3b0" + integrity sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ== + +"@esbuild/netbsd-x64@0.21.5": + version "0.21.5" + resolved "https://registry.npmmirror.com/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz#bbe430f60d378ecb88decb219c602667387a6047" + integrity sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg== + +"@esbuild/openbsd-x64@0.21.5": + version "0.21.5" + resolved "https://registry.npmmirror.com/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz#99d1cf2937279560d2104821f5ccce220cb2af70" + integrity sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow== + +"@esbuild/sunos-x64@0.21.5": + version "0.21.5" + resolved "https://registry.npmmirror.com/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz#08741512c10d529566baba837b4fe052c8f3487b" + integrity sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg== + +"@esbuild/win32-arm64@0.21.5": + version "0.21.5" + resolved "https://registry.npmmirror.com/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz#675b7385398411240735016144ab2e99a60fc75d" + integrity sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A== + +"@esbuild/win32-ia32@0.21.5": + version "0.21.5" + resolved "https://registry.npmmirror.com/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz#1bfc3ce98aa6ca9a0969e4d2af72144c59c1193b" + integrity sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA== + +"@esbuild/win32-x64@0.21.5": + version "0.21.5" + resolved "https://registry.npmmirror.com/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz#acad351d582d157bb145535db2a6ff53dd514b5c" + integrity sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw== + +"@iconify-json/logos@^1.2.11": + version "1.2.14" + resolved "https://registry.npmmirror.com/@iconify-json/logos/-/logos-1.2.14.tgz#98ed4368b199e5ba3441aca5b79ffe985284a1a7" + integrity sha512-O36DicXkgAMT6NAsH7MTWlOql8NktHz9fBCItTjz6L/9gyRXw4vQj6qTeVgww5UZWiVugUV1MhVFA/g9HWIkBw== + dependencies: + "@iconify/types" "*" + +"@iconify-json/vscode-icons@^1.2.67": + version "1.2.79" + resolved "https://registry.npmmirror.com/@iconify-json/vscode-icons/-/vscode-icons-1.2.79.tgz#b5732e07958058cd18c39d1fb9b388714687b03d" + integrity sha512-JxGn5iS6a9qyF/tE9F9B8cCjQ8sS7SuLgBw3zIfoDhl4JKlIYHtK0FKQuiV47U65E7Q0OYKw0uZ7SMuCrINBHA== + dependencies: + "@iconify/types" "*" + +"@iconify/types@*", "@iconify/types@^2.0.0": + version "2.0.0" + resolved "https://registry.npmmirror.com/@iconify/types/-/types-2.0.0.tgz#ab0e9ea681d6c8a1214f30cd741fe3a20cc57f57" + integrity sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg== + +"@iconify/utils@^3.1.4": + version "3.1.7" + resolved "https://registry.npmmirror.com/@iconify/utils/-/utils-3.1.7.tgz#fff11c528490b119a250ace3cf35a0a78e182317" + integrity sha512-JZHlwdID+dy+lTgbYC8NEC4zeugqeYsc6jewvzb4c58kHauJn+X7rNwQjxz5p2qSjqaEeQoLkCIQ9v/H4PK0/w== + dependencies: + "@antfu/install-pkg" "^2.0.1" + "@iconify/types" "^2.0.0" + import-meta-resolve "^4.2.0" + +"@jridgewell/sourcemap-codec@^1.5.5": + version "1.6.0" + resolved "https://registry.npmmirror.com/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz#f4c663e862f06dc98ca4d453862c46902789a18d" + integrity sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw== + +"@mdit-vue/shared@^2.1.3": + version "2.1.4" + resolved "https://registry.npmmirror.com/@mdit-vue/shared/-/shared-2.1.4.tgz#4d653befe0b05af94bb8daf60965f34399519d97" + integrity sha512-Axd8g2iKQTMuHcPXZH5JY3hbSMeLyoeu0ftdgMrjuPzHpJnWiPSAnA0dAx5NQFQqZkXHhyIrAssLSrOWjFmPKg== + dependencies: + "@mdit-vue/types" "2.1.4" + "@types/markdown-it" "^14.1.2" + markdown-it "^14.1.0" + +"@mdit-vue/types@2.1.4": + version "2.1.4" + resolved "https://registry.npmmirror.com/@mdit-vue/types/-/types-2.1.4.tgz#9f4ea85fe09d3144ac77665bcdee7962ef487631" + integrity sha512-QiGNZslz+zXUs2X8D11UQhB4KAMZ0DZghvYxa7+1B+VMLcDtz//XHpWbcuexjzE3kBXSxIUTPH3eSQCa0puZHA== + +"@napi-rs/lzma-linux-x64-gnu@1.5.1": + version "1.5.1" + resolved "https://registry.npmmirror.com/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz#e57d4306966078662038094fb38eb9146dc3aea9" + integrity sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ== + +"@parcel/watcher-android-arm64@2.6.0": + version "2.6.0" + resolved "https://registry.npmmirror.com/@parcel/watcher-android-arm64/-/watcher-android-arm64-2.6.0.tgz#99aaa3223d43807c9340af439cad7e9b6d26ada6" + integrity sha512-trgpLSCKRC/huFjXX/Smh+0sWe4+YtKfktIToiMl59ghz7z+qkH6kMvNnUbLyRs9N11t8l4svSCs1+5B3rOAhA== + +"@parcel/watcher-darwin-arm64@2.6.0": + version "2.6.0" + resolved "https://registry.npmmirror.com/@parcel/watcher-darwin-arm64/-/watcher-darwin-arm64-2.6.0.tgz#024496e586b4744f09ce532bbe89fe38ef02a64e" + integrity sha512-Y3QV0gl7Q1zbfueunkWIERICbEojQFCgpyG7YqOGNFLsckXyI1xu9mAIUpKY9QBYzBtSkN8dBPwd3yiAO9ovMw== + +"@parcel/watcher-darwin-x64@2.6.0": + version "2.6.0" + resolved "https://registry.npmmirror.com/@parcel/watcher-darwin-x64/-/watcher-darwin-x64-2.6.0.tgz#a4621df1359a93d39a332d9bab5ff09016a0608f" + integrity sha512-Ohv6OpzhUfKYD7Beb8kDvG0jbIxORCYY1JRdZnaBtnjjkJxgD7ZVL0nw2sCYd0yTMKTvz3nnTnOF3cDifK+kvw== + +"@parcel/watcher-freebsd-x64@2.6.0": + version "2.6.0" + resolved "https://registry.npmmirror.com/@parcel/watcher-freebsd-x64/-/watcher-freebsd-x64-2.6.0.tgz#7f565ed1a5b3a5e604e6a4799121518265d62a3d" + integrity sha512-5HmXvDgs8VK+74jF9y9/2FE3/OnlcKmc56tjmSrEuZjpSZOGL+fvAu+HKJBdPs9uwoP2hE6TlSUpXZ/C5jUFmQ== + +"@parcel/watcher-linux-arm-glibc@2.6.0": + version "2.6.0" + resolved "https://registry.npmmirror.com/@parcel/watcher-linux-arm-glibc/-/watcher-linux-arm-glibc-2.6.0.tgz#ad7d3825e67b81999165da42593022045abc0889" + integrity sha512-Ps/hui3A+vMbjdqlqAowK2ZL8+BO8dBjxeWXj6npTBs3jx4wWmbPpaLuqwrQrSqIVMCnpWo238bJ1U37GhQOYg== + +"@parcel/watcher-linux-arm-musl@2.6.0": + version "2.6.0" + resolved "https://registry.npmmirror.com/@parcel/watcher-linux-arm-musl/-/watcher-linux-arm-musl-2.6.0.tgz#fe7d1cccb2c483215c090e938cf5cf404d2f9a8c" + integrity sha512-9c6AUHgHoG+IY88MRIHupztQiQnrbqHYQjkM2btA+Bf/wQnQMuiD0Wfk1EVv3TlNT3x41uU71rn6E4xh/+zvkw== + +"@parcel/watcher-linux-arm64-glibc@2.6.0": + version "2.6.0" + resolved "https://registry.npmmirror.com/@parcel/watcher-linux-arm64-glibc/-/watcher-linux-arm64-glibc-2.6.0.tgz#7e239dcb4646c4c79f006a7131a48238249530da" + integrity sha512-yHRqS2owEXe6Hic9z6Mh1ECsCd+ODVOGvZDyciqRd21+v+o+DnXMOrw50DSpIG2sb8GPEaPPmfeCAWKPJdq46g== + +"@parcel/watcher-linux-arm64-musl@2.6.0": + version "2.6.0" + resolved "https://registry.npmmirror.com/@parcel/watcher-linux-arm64-musl/-/watcher-linux-arm64-musl-2.6.0.tgz#c58b8d9c6d8d81594be00dd83aab741c1aaf7e0e" + integrity sha512-WhB2e/V7rqdHHWZusBSPuy5Ei8S6lSz6FE5TKKQz5h3a0O+C+mhY7vxU9b/stqvMb8beLnPY82ZrFTLKs+SrKA== + +"@parcel/watcher-linux-x64-glibc@2.6.0": + version "2.6.0" + resolved "https://registry.npmmirror.com/@parcel/watcher-linux-x64-glibc/-/watcher-linux-x64-glibc-2.6.0.tgz#5184fa9a770478d86e56875f4ee163a0abdc8791" + integrity sha512-ulGE6x6Oz6iAwg75T8YQSoguBWasniIbX+QWpaYPcCnDOpdWX3k+4xbEYPZVLxOuoJI+svJJPD3sEj8G7lrQ3A== + +"@parcel/watcher-linux-x64-musl@2.6.0": + version "2.6.0" + resolved "https://registry.npmmirror.com/@parcel/watcher-linux-x64-musl/-/watcher-linux-x64-musl-2.6.0.tgz#2d1c55aa7246cbc7670e2612058a8a542c9cf246" + integrity sha512-tkBYKt7YQrjIJWYDnto2YgO8MRkjlMTSNoRHzsXinBqbLdeOM3L32wPZJvIZxqaLMfSlS/4sUjH/6STVP/XDLw== + +"@parcel/watcher-win32-arm64@2.6.0": + version "2.6.0" + resolved "https://registry.npmmirror.com/@parcel/watcher-win32-arm64/-/watcher-win32-arm64-2.6.0.tgz#15e09432040fee9e2213aa9c10ed589012526def" + integrity sha512-gIZAP23jaHjGWasY/TY6yL7NHFClf0Ga7FN+iINvk+KN94rhm94lYZhFsbYFNcA04/onvGD9kKmiJLJB2HbNwQ== + +"@parcel/watcher-win32-x64@2.6.0": + version "2.6.0" + resolved "https://registry.npmmirror.com/@parcel/watcher-win32-x64/-/watcher-win32-x64-2.6.0.tgz#9bee199a2a4accd557b451ac2c1c793f305ae012" + integrity sha512-cA+/pXV2YkfxlIcXOQ5fSWqAzzPyD78/x5qbK/I0vUkrlYHA8TIz+MXjAbGouguKVSI4bOmkTSJ1/poVSsgt+A== + +"@parcel/watcher@^2.4.1": + version "2.6.0" + resolved "https://registry.npmmirror.com/@parcel/watcher/-/watcher-2.6.0.tgz#99661f6220070b76a766aba6b7e313a087a1be4f" + integrity sha512-7FNeNl8NCE7aINx7WXiKQrPYZWC/hvrTsmk6zmxbI7LTXE7hVek/n8AfVgpe2y82zl3w0HvCHN0bVKMBoJcC0w== + dependencies: + detect-libc "^2.0.3" + is-glob "^4.0.3" + node-addon-api "^7.0.0" + picomatch "^4.0.4" + optionalDependencies: + "@parcel/watcher-android-arm64" "2.6.0" + "@parcel/watcher-darwin-arm64" "2.6.0" + "@parcel/watcher-darwin-x64" "2.6.0" + "@parcel/watcher-freebsd-x64" "2.6.0" + "@parcel/watcher-linux-arm-glibc" "2.6.0" + "@parcel/watcher-linux-arm-musl" "2.6.0" + "@parcel/watcher-linux-arm64-glibc" "2.6.0" + "@parcel/watcher-linux-arm64-musl" "2.6.0" + "@parcel/watcher-linux-x64-glibc" "2.6.0" + "@parcel/watcher-linux-x64-musl" "2.6.0" + "@parcel/watcher-win32-arm64" "2.6.0" + "@parcel/watcher-win32-x64" "2.6.0" + +"@rollup/rollup-android-arm-eabi@4.63.4": + version "4.63.4" + resolved "https://registry.npmmirror.com/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.63.4.tgz#e1d06062fb451578b3fb0bdea3360daaa1395f44" + integrity sha512-I+BSHzTAhKN2n7ZwGZsegGcZjDpLqFOMAtJz/u6uFGe0pUFbq56dEHjqJV/ZUdRJtNXNxA+hREUatZBvMR3Oiw== + +"@rollup/rollup-android-arm64@4.63.4": + version "4.63.4" + resolved "https://registry.npmmirror.com/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.63.4.tgz#2e81dec4e9e879c95c8266e30da67074c4998224" + integrity sha512-pu3BdjS2LtEzRu2elmGzS3fIeWSZy4BMDIaLNwjorO76+k2d0LMluijhsDx3KQyQBQ/lLUZCQA9/s6csvUfuhw== + +"@rollup/rollup-darwin-arm64@4.63.4": + version "4.63.4" + resolved "https://registry.npmmirror.com/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.63.4.tgz#f48f92f44454aed23d013f7cdb6da0dc2cb598ae" + integrity sha512-xfSrj9MHnWK9GaSqT9U0ImHtH/N8WZlHLx4cZHiuLcqs640hvZ3hLPd5UR2AZS57FaE8HrRUSpltbZdWRxHiDA== + +"@rollup/rollup-darwin-x64@4.63.4": + version "4.63.4" + resolved "https://registry.npmmirror.com/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.63.4.tgz#39b0b2ad27c49b40aec71fb20210e2e3be5fdbd0" + integrity sha512-bqU99PLJb/dqb3S0GIMdeuyAEETSUgZBoqXYd3Sd+WCsV+MmPhnN6JrotWyir31+QgH7EvvE5/mwGJlEoci8Fw== + +"@rollup/rollup-freebsd-arm64@4.63.4": + version "4.63.4" + resolved "https://registry.npmmirror.com/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.63.4.tgz#0c85d18f5b9228b7195adbbbc97c1adaf41b8106" + integrity sha512-JinsFZ5G40oXQb+sUuiA5x689vhr6dDYK0H0NL+rwKdL6CqnmYN8PE4ZwfRSoIjrCxqTQG/SLfTtSvHeGxoVlw== + +"@rollup/rollup-freebsd-x64@4.63.4": + version "4.63.4" + resolved "https://registry.npmmirror.com/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.63.4.tgz#0c6c27471ea950213aad76418f075c96f57e9533" + integrity sha512-GAdA4UxpiNm27cLHr2GqXBpAD0x9FqwYBY7/YSP0Ss0/PNi4k8gbviqpIpYbVSRBaS2ZcegXEzgTQMbRNCwxCw== + +"@rollup/rollup-linux-arm-gnueabihf@4.63.4": + version "4.63.4" + resolved "https://registry.npmmirror.com/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.63.4.tgz#28cba40d417fb3d2c1536941b02143a2bdda72be" + integrity sha512-qDd6NoA1znaLjp4jR5U/KWCdLAKDJNB8W9ChbbDaKbo0xA+Atln5HK6LFCZ4oJQpemtRZA288DCirFRjrspptw== + +"@rollup/rollup-linux-arm-musleabihf@4.63.4": + version "4.63.4" + resolved "https://registry.npmmirror.com/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.63.4.tgz#c9f06aa876d6d312012a2048d9e5533c581a7341" + integrity sha512-WtB5Tz5KTNINb8ZA+8sQ7bmjuS1JrRT7YverYIhUGdWWDlpzVWmIwuZE+jidkEXUn1l0zrEkaIMa8dHF3NGcsA== + +"@rollup/rollup-linux-arm64-gnu@4.63.4": + version "4.63.4" + resolved "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.63.4.tgz#b26376804ca32b3dcc8f68930f765394fb4e4814" + integrity sha512-VcQ3L1tjnkKzWjryAVaFhHEWcqOfICX9uxVVoDzm2t0DpgKRHd2zOpVrJc0xsWeBZcBFyYROCIBdyR/fS174pg== + +"@rollup/rollup-linux-arm64-musl@4.63.4": + version "4.63.4" + resolved "https://registry.npmmirror.com/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.63.4.tgz#65f6f5deee9d30904b4d52ee3d45679055743cd1" + integrity sha512-6+ZQX6P5s0cMDN2Ypb8Lbm2+/sZYmZjdaYny992ujUU9UKi/4CWoJWsl1pNvjWJHNHGK51m+jKGLlh1ylb2ifQ== + +"@rollup/rollup-linux-loong64-gnu@4.63.4": + version "4.63.4" + resolved "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.63.4.tgz#15d7e44538e967a3d6132f5b9f27f02346df6850" + integrity sha512-D72ZnvkFkBXOfzMMQLcwfPLyGkKb7HZ9/mf97B7v6/P5Lbv4oFOtSY/uHbS8lH6uKUOxoKiuokdb50XZSzzbJw== + +"@rollup/rollup-linux-loong64-musl@4.63.4": + version "4.63.4" + resolved "https://registry.npmmirror.com/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.63.4.tgz#7afad2b9e41941344883154f754b7f772ef16b01" + integrity sha512-piU6BxeqA3O9KSu3kRCIQQtNqFFaTu21SEV4FwaRZowpnj3bLaWPZHw+xFqCs0XlJ+aOH3PTRWGoglH+mKA/OA== + +"@rollup/rollup-linux-ppc64-gnu@4.63.4": + version "4.63.4" + resolved "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.63.4.tgz#3f3131b1a9585851b2875c86a49f6e6deb9e68e0" + integrity sha512-/5PGpHwqt2EEEOUs1XwzubE/ucr0dWDQ+to3zqi4Ds7EWpwtQ79wXc4JBoxqj/OwpawTsKWzJxHfSuBOq3DrWA== + +"@rollup/rollup-linux-ppc64-musl@4.63.4": + version "4.63.4" + resolved "https://registry.npmmirror.com/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.63.4.tgz#463aeee26c62983c1fc235708d1b41baf4735f98" + integrity sha512-cX3beZDLWt7G2oJF+nhChiT+qtaihs+S2xi7ziGmVB+2pwPng6D0Ed0HmElQOgv2UsUmSJJLGwpBao/3TDx3VA== + +"@rollup/rollup-linux-riscv64-gnu@4.63.4": + version "4.63.4" + resolved "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.63.4.tgz#cb495bd8cc978e17593e0d323b36fd95b5eab098" + integrity sha512-1uz2mGWHyptR7DgHHrlbdRAjXK7v7elGZ9lMja910/RP+ZYbX6xAmCiU9UZSX4hqmgtHMv6lr5l3kq1HIOpcag== + +"@rollup/rollup-linux-riscv64-musl@4.63.4": + version "4.63.4" + resolved "https://registry.npmmirror.com/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.63.4.tgz#655b69144b1e786e0bf8ac3b727f0d7fee590a08" + integrity sha512-nLS8topojxyz7SRpKR2IODRpQ0XPZ+xaOXvT3+hqK/Uy8Lo5HFgkkIBiIrCu5tL5YqzTvgovGw55PwpahTAGig== + +"@rollup/rollup-linux-s390x-gnu@4.63.4": + version "4.63.4" + resolved "https://registry.npmmirror.com/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.63.4.tgz#70b175b0361a7898acc294a6b14001e2c3ad99be" + integrity sha512-gs7DRKotr3l3q+jGPQBjH0ng1FjlEDm5ueQrkw5JtQvtLyEIcLASqAEaor56BhkKRzk+IcQzrcanBdb/bBQn8g== + +"@rollup/rollup-linux-x64-gnu@4.63.4": + version "4.63.4" + resolved "https://registry.npmmirror.com/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.63.4.tgz#0c68fa212df249f996b2d75e8fbb3766b1582ee8" + integrity sha512-791ET7W17NnScOZM7h4dX5hYspxE28htPFsb1awY/NRR8+PRNkS53e475rDdxXXDrP+kwnCcNWg9CX5ztn/Aqw== + +"@rollup/rollup-linux-x64-musl@4.63.4": + version "4.63.4" + resolved "https://registry.npmmirror.com/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.63.4.tgz#93af6206edf751469a43c3614164360136540838" + integrity sha512-iwZQRcmj7g88g3tzefIrQY7qvmuA/cfYwhrDtTBhsmukO4U2huVO5W+86XacUMRvdSFVAc6kZUZy21JaRwiB9w== + +"@rollup/rollup-openbsd-x64@4.63.4": + version "4.63.4" + resolved "https://registry.npmmirror.com/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.63.4.tgz#7c7680c4004fd602b59cd19f3294fa1b89d0c683" + integrity sha512-dVHFp9gRWrdTpnqQuGfCwd7hOQDatK1VCP2iWhLY/cGrOQs/ucFzJ6A5SRqbXX12ZDI8EUuejSM5kwg+ja7Png== + +"@rollup/rollup-openharmony-arm64@4.63.4": + version "4.63.4" + resolved "https://registry.npmmirror.com/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.63.4.tgz#f389ff4d8ee8a635ff51d76e8ff20b7db2292bce" + integrity sha512-t3NlauOW6gxZVVFcBEnO62Cb4wbyDFL416gTg1uFI/2tgqYQlf69FbSE115Ajre9I+c26Lk4mcmdFUsS/DGifQ== + +"@rollup/rollup-win32-arm64-msvc@4.63.4": + version "4.63.4" + resolved "https://registry.npmmirror.com/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.63.4.tgz#55a286f40f9d03c653ceb7183d6c6179ec312c75" + integrity sha512-xWuIaSye5FWZF8+UYtVEcHtRJDN5kN9Kfgxx3Kq8XIov9KSKbc1fiqQCm90SKrgQbUXZelbnUhnlUJmfSE7P9A== + +"@rollup/rollup-win32-ia32-msvc@4.63.4": + version "4.63.4" + resolved "https://registry.npmmirror.com/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.63.4.tgz#7eb577b64e2a39115182a87a2ea957608d8331f6" + integrity sha512-9ALJJUOg/ZflMJepVo2PlgsGxSaxN7SQ4Z8GoZfVlarWr6r3rkHUNsd/zAio7p4YMtChSMXPionxej4Hkf6CXQ== + +"@rollup/rollup-win32-x64-gnu@4.63.4": + version "4.63.4" + resolved "https://registry.npmmirror.com/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.63.4.tgz#0e7a7cd5f0db2f570fc312687d67baf21283a3f6" + integrity sha512-blj9z5qx/Pv4WU0W1NMFDB97e0JH5ed+aZGywW8WCvp/NhWX/4PFAq5uu6Q0AebNn+Vo6KzUYDT++JzTT5ojlQ== + +"@rollup/rollup-win32-x64-msvc@4.63.4": + version "4.63.4" + resolved "https://registry.npmmirror.com/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.63.4.tgz#f8e11d8ac827db7695d50f1def4c00a7f3cd1510" + integrity sha512-Erx822VRBwLa124shbj+wNXe//BOgMEctDV0m1aqTQdNO1S69DgNUCFKC1RCeZfixs1J31l6igk1ziyXErbigQ== + +"@shikijs/core@1.29.2", "@shikijs/core@^1.13.0": + version "1.29.2" + resolved "https://registry.npmmirror.com/@shikijs/core/-/core-1.29.2.tgz#9c051d3ac99dd06ae46bd96536380c916e552bf3" + integrity sha512-vju0lY9r27jJfOY4Z7+Rt/nIOjzJpZ3y+nYpqtUZInVoXQ/TJZcfGnNOGnKjFdVZb8qexiCuSlZRKcGfhhTTZQ== + dependencies: + "@shikijs/engine-javascript" "1.29.2" + "@shikijs/engine-oniguruma" "1.29.2" + "@shikijs/types" "1.29.2" + "@shikijs/vscode-textmate" "^10.0.1" + "@types/hast" "^3.0.4" + hast-util-to-html "^9.0.4" + +"@shikijs/engine-javascript@1.29.2": + version "1.29.2" + resolved "https://registry.npmmirror.com/@shikijs/engine-javascript/-/engine-javascript-1.29.2.tgz#a821ad713a3e0b7798a1926fd9e80116e38a1d64" + integrity sha512-iNEZv4IrLYPv64Q6k7EPpOCE/nuvGiKl7zxdq0WFuRPF5PAE9PRo2JGq/d8crLusM59BRemJ4eOqrFrC4wiQ+A== + dependencies: + "@shikijs/types" "1.29.2" + "@shikijs/vscode-textmate" "^10.0.1" + oniguruma-to-es "^2.2.0" + +"@shikijs/engine-oniguruma@1.29.2": + version "1.29.2" + resolved "https://registry.npmmirror.com/@shikijs/engine-oniguruma/-/engine-oniguruma-1.29.2.tgz#d879717ced61d44e78feab16f701f6edd75434f1" + integrity sha512-7iiOx3SG8+g1MnlzZVDYiaeHe7Ez2Kf2HrJzdmGwkRisT7r4rak0e655AcM/tF9JG/kg5fMNYlLLKglbN7gBqA== + dependencies: + "@shikijs/types" "1.29.2" + "@shikijs/vscode-textmate" "^10.0.1" + +"@shikijs/langs@1.29.2": + version "1.29.2" + resolved "https://registry.npmmirror.com/@shikijs/langs/-/langs-1.29.2.tgz#4f1de46fde8991468c5a68fa4a67dd2875d643cd" + integrity sha512-FIBA7N3LZ+223U7cJDUYd5shmciFQlYkFXlkKVaHsCPgfVLiO+e12FmQE6Tf9vuyEsFe3dIl8qGWKXgEHL9wmQ== + dependencies: + "@shikijs/types" "1.29.2" + +"@shikijs/themes@1.29.2": + version "1.29.2" + resolved "https://registry.npmmirror.com/@shikijs/themes/-/themes-1.29.2.tgz#293cc5c83dd7df3fdc8efa25cec8223f3a6acb0d" + integrity sha512-i9TNZlsq4uoyqSbluIcZkmPL9Bfi3djVxRnofUHwvx/h6SRW3cwgBC5SML7vsDcWyukY0eCzVN980rqP6qNl9g== + dependencies: + "@shikijs/types" "1.29.2" + +"@shikijs/transformers@^1.13.0": + version "1.29.2" + resolved "https://registry.npmmirror.com/@shikijs/transformers/-/transformers-1.29.2.tgz#cc7338a36783a4e48f484405c5410338e884f41e" + integrity sha512-NHQuA+gM7zGuxGWP9/Ub4vpbwrYCrho9nQCLcCPfOe3Yc7LOYwmSuhElI688oiqIXk9dlZwDiyAG9vPBTuPJMA== + dependencies: + "@shikijs/core" "1.29.2" + "@shikijs/types" "1.29.2" + +"@shikijs/types@1.29.2": + version "1.29.2" + resolved "https://registry.npmmirror.com/@shikijs/types/-/types-1.29.2.tgz#a93fdb410d1af8360c67bf5fc1d1a68d58e21c4f" + integrity sha512-VJjK0eIijTZf0QSTODEXCqinjBn0joAHQ+aPSBzrv4O2d/QSbsMw+ZeSRx03kV34Hy7NzUvV/7NqfYGRLrASmw== + dependencies: + "@shikijs/vscode-textmate" "^10.0.1" + "@types/hast" "^3.0.4" + +"@shikijs/vscode-textmate@^10.0.1": + version "10.0.2" + resolved "https://registry.npmmirror.com/@shikijs/vscode-textmate/-/vscode-textmate-10.0.2.tgz#a90ab31d0cc1dfb54c66a69e515bf624fa7b2224" + integrity sha512-83yeghZ2xxin3Nj8z1NMd/NCuca+gsYXswywDy5bHvwlWL8tpTQmzGeUuHd9FC3E/SBEMvzJRwWEOz5gGes9Qg== + +"@types/estree@1.0.9": + version "1.0.9" + resolved "https://registry.npmmirror.com/@types/estree/-/estree-1.0.9.tgz#cf3f0e876d7bee15a93ab925b82bf570a3904a24" + integrity sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg== + +"@types/hast@^3.0.0", "@types/hast@^3.0.4": + version "3.0.5" + resolved "https://registry.npmmirror.com/@types/hast/-/hast-3.0.5.tgz#48020de4c0e63492f4ca9db42068c108f68b7f8f" + integrity sha512-rp/ezSWaD1m44dPKICGhiskI13nVr7qTloFwDa/IYkhhf5nzwP+zIQcIJh3WIFSBOy/H1PzB40jPjMDksN4F+g== + dependencies: + "@types/unist" "*" + +"@types/linkify-it@^5": + version "5.0.0" + resolved "https://registry.npmmirror.com/@types/linkify-it/-/linkify-it-5.0.0.tgz#21413001973106cda1c3a9b91eedd4ccd5469d76" + integrity sha512-sVDA58zAw4eWAffKOaQH5/5j3XeayukzDk+ewSsnv3p4yJEZHCCzMDiZM8e0OUrRvmpGZ85jf4yDHkHsgBNr9Q== + +"@types/markdown-it@^14.1.2": + version "14.2.0" + resolved "https://registry.npmmirror.com/@types/markdown-it/-/markdown-it-14.2.0.tgz#4074873f98fbc791131e31d7446b12dfa3a31df9" + integrity sha512-NoQ2yGlLWj4wpxMs+TYmRKk3thDrQ97agr7sFqfLsAlvoS8SNQuTrlObhFqG9iugdTtgOE9jpJ6FNM4ZGsa5xQ== + dependencies: + "@types/linkify-it" "^5" + "@types/mdurl" "^2" + +"@types/mdast@^4.0.0": + version "4.0.4" + resolved "https://registry.npmmirror.com/@types/mdast/-/mdast-4.0.4.tgz#7ccf72edd2f1aa7dd3437e180c64373585804dd6" + integrity sha512-kGaNbPh1k7AFzgpud/gMdvIm5xuECykRR+JnWKQno9TAXVa6WIVCGTPvYGekIDL4uwCZQSYbUxNBSb1aUo79oA== + dependencies: + "@types/unist" "*" + +"@types/mdurl@^2": + version "2.0.0" + resolved "https://registry.npmmirror.com/@types/mdurl/-/mdurl-2.0.0.tgz#d43878b5b20222682163ae6f897b20447233bdfd" + integrity sha512-RGdgjQUZba5p6QEFAVx2OGb8rQDL/cPRG7GiedRzMcJ1tYnUANBncjbSB1NRGwbvjcPeikRABz2nshyPk1bhWg== + +"@types/unist@*", "@types/unist@^3.0.0": + version "3.0.3" + resolved "https://registry.npmmirror.com/@types/unist/-/unist-3.0.3.tgz#acaab0f919ce69cce629c2d4ed2eb4adc1b6c20c" + integrity sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q== + +"@types/web-bluetooth@^0.0.20": + version "0.0.20" + resolved "https://registry.npmmirror.com/@types/web-bluetooth/-/web-bluetooth-0.0.20.tgz#f066abfcd1cbe66267cdbbf0de010d8a41b41597" + integrity sha512-g9gZnnXVq7gM7v3tJCWV/qw7w+KeOlSHAhgF9RytFyifW6AF61hdT2ucrYhPq9hLs5JIryeupHV3qGk95dH9ow== + +"@ungap/structured-clone@^1.0.0": + version "1.4.0" + resolved "https://registry.npmmirror.com/@ungap/structured-clone/-/structured-clone-1.4.0.tgz#5e2e1374c0a30b5a42e8b083523a225c6945f88a" + integrity sha512-1mEZtMKPM09vDmQt5y7YvmN2+DFTP7Tg0EWXdic8/C6VRnpb33e4ghisCIE3WZjsE2N8mf+QV1Zqh7ZFYLWInQ== + +"@vitejs/plugin-vue@^5.1.2": + version "5.2.4" + resolved "https://registry.npmmirror.com/@vitejs/plugin-vue/-/plugin-vue-5.2.4.tgz#9e8a512eb174bfc2a333ba959bbf9de428d89ad8" + integrity sha512-7Yx/SXSOcQq5HiiV3orevHUFn+pmMB4cgbEkDYgnkUWb0WfeQ/wa2yFv6D5ICiCQOVpjA7vYDXrC7AGO8yjDHA== + +"@vue/compiler-core@3.5.43": + version "3.5.43" + resolved "https://registry.npmmirror.com/@vue/compiler-core/-/compiler-core-3.5.43.tgz#ff792a8bb20d29fe2a9f013d5735d39c7808c9a5" + integrity sha512-zdiLhnbe1QQqgDT8xZMpNmyqZ3qlI+/Q/FHQco57Kwl/b05HhCzN6eVGN9QU9rbga4CrS0H5SYY8VGHZCt/1Hg== + dependencies: + "@babel/parser" "^7.29.8" + "@vue/shared" "3.5.43" + entities "^7.0.1" + estree-walker "^2.0.2" + source-map-js "^1.2.1" + +"@vue/compiler-dom@3.5.43": + version "3.5.43" + resolved "https://registry.npmmirror.com/@vue/compiler-dom/-/compiler-dom-3.5.43.tgz#1bec85b4c3078544b278524e521311a6202d673f" + integrity sha512-PEZoAk3NQmsn/ejMzSOCyTYqwGqczrWm70PuhBKjjv1+TCoQAaO/zOqNwjV+honlNstT5ILxtc+8r8UUfj+iEQ== + dependencies: + "@vue/compiler-core" "3.5.43" + "@vue/shared" "3.5.43" + +"@vue/compiler-sfc@3.5.43": + version "3.5.43" + resolved "https://registry.npmmirror.com/@vue/compiler-sfc/-/compiler-sfc-3.5.43.tgz#19c097f398abacfee9627646075a919d2f57a6ae" + integrity sha512-FCbrG3XNCRl+js3huuKx4IVHBLTvMkJhVepjbxSPu1gn4yWLaYtGNQdjJGZaMytXB6qb76qQDDmDSLy/vkmleQ== + dependencies: + "@babel/parser" "^7.29.8" + "@vue/compiler-core" "3.5.43" + "@vue/compiler-dom" "3.5.43" + "@vue/compiler-ssr" "3.5.43" + "@vue/shared" "3.5.43" + estree-walker "^2.0.2" + magic-string "^0.30.21" + postcss "^8.5.28" + source-map-js "^1.2.1" + +"@vue/compiler-ssr@3.5.43": + version "3.5.43" + resolved "https://registry.npmmirror.com/@vue/compiler-ssr/-/compiler-ssr-3.5.43.tgz#aacd1d047545d51d5cb0eca9aa3f490507bf7040" + integrity sha512-GF62orf7KiJX9RqrHNGrYBudsQGD0OhJ5nDs90O8UiDuS40+YMYomiXu6w6EuvtXuRDc3MSNis3EaaSKAVSWpg== + dependencies: + "@vue/compiler-dom" "3.5.43" + "@vue/shared" "3.5.43" + +"@vue/devtools-api@^7.3.8": + version "7.7.10" + resolved "https://registry.npmmirror.com/@vue/devtools-api/-/devtools-api-7.7.10.tgz#bfa52dee8918edeea1bfe04cce9f809eeed19862" + integrity sha512-KxtEpUOOpFz/qOGRrAwA36QF7DqIA+FXgCYit9mk9wjbaZt0sXOFz81ElOZtKA4HbWHUdwNjZHBFsFFyp5BZiA== + dependencies: + "@vue/devtools-kit" "^7.7.10" + +"@vue/devtools-kit@^7.7.10": + version "7.7.10" + resolved "https://registry.npmmirror.com/@vue/devtools-kit/-/devtools-kit-7.7.10.tgz#2f2b6b6eaad9ba96c031fb20c4d59c80d35dfad3" + integrity sha512-3WNi2Kq4tbpVbmhml7RiphmAt0279oh3fKNeWMQIrltfX8Q91b4i5PL8DtyNKdwmcsGrV4fg+erwWOmD05CLIw== + dependencies: + "@vue/devtools-shared" "^7.7.10" + birpc "^2.3.0" + hookable "^5.5.3" + mitt "^3.0.1" + perfect-debounce "^1.0.0" + speakingurl "^14.0.1" + superjson "^2.2.2" + +"@vue/devtools-shared@^7.7.10": + version "7.7.10" + resolved "https://registry.npmmirror.com/@vue/devtools-shared/-/devtools-shared-7.7.10.tgz#ba45001aeb5f7b99a72db2fb824fc04a280aca53" + integrity sha512-wOPslzB8vTvpxwdaOcR2qAbwmuSP0L+rhpoC6Cf56V3Jip+HWb7PQQXOUPgBNQARpXsbQX/+mvi8kKucmBGRwQ== + dependencies: + rfdc "^1.4.1" + +"@vue/reactivity@3.5.43": + version "3.5.43" + resolved "https://registry.npmmirror.com/@vue/reactivity/-/reactivity-3.5.43.tgz#26441233103e834616959a032d8fabd4524fd089" + integrity sha512-G/c9GyOZNI2jVaaS6OX1EF1SSFSv7H0ERqNTl4+DTFMlZmB5eVAB53aLQNam/7NL2NPtaDD7RdVrzf8uJzMuOA== + dependencies: + "@vue/shared" "3.5.43" + +"@vue/runtime-core@3.5.43": + version "3.5.43" + resolved "https://registry.npmmirror.com/@vue/runtime-core/-/runtime-core-3.5.43.tgz#eca01c87c32047d32f3639c1a9206d368abe0c46" + integrity sha512-hU6U6VnVhBGQDpvlnnDlIB8ZGJBiOcgk2lh/0InltHiz3D8oSkluvuvY+do1G2H3+udeKFsmaBlgVYP7gXQzEw== + dependencies: + "@vue/reactivity" "3.5.43" + "@vue/shared" "3.5.43" + +"@vue/runtime-dom@3.5.43": + version "3.5.43" + resolved "https://registry.npmmirror.com/@vue/runtime-dom/-/runtime-dom-3.5.43.tgz#c40a898bd1ce11697741b9a079414a803413028a" + integrity sha512-Bb2Jc0YjjJdMt1SJmb9b2L/IWd3I8lIT9x9eS/xvvP9CiVgna0ffua74xKRmt4/uSJ+0r4iN8ex1jrqkhQGWQw== + dependencies: + "@vue/reactivity" "3.5.43" + "@vue/runtime-core" "3.5.43" + "@vue/shared" "3.5.43" + csstype "^3.2.3" + +"@vue/server-renderer@3.5.43": + version "3.5.43" + resolved "https://registry.npmmirror.com/@vue/server-renderer/-/server-renderer-3.5.43.tgz#ae2b84b832821e39c3355705e1c269f5a7d29b3c" + integrity sha512-l2Ygjv9NehV94PSBxNWsAHC0j/eIIKbn92mBuWXAPGLnn6HfJ6MH5ubsd+Nk0YoZ5FRuxWI1P2VSoh+dbPQhCQ== + dependencies: + "@vue/compiler-ssr" "3.5.43" + "@vue/runtime-dom" "3.5.43" + "@vue/shared" "3.5.43" + +"@vue/shared@3.5.43", "@vue/shared@^3.4.38": + version "3.5.43" + resolved "https://registry.npmmirror.com/@vue/shared/-/shared-3.5.43.tgz#22c11ecf1304bfc6f6b745f3b4ba54c410074338" + integrity sha512-uksS7YGMR5NZyr4JNq0Rp+QyLns0ueaz20KwzIPW9R0LH1Vnt4E+XUM29PNseEbf1www2gOuhuDi5AKOIXag9Q== + +"@vueuse/core@11.3.0", "@vueuse/core@^11.0.0": + version "11.3.0" + resolved "https://registry.npmmirror.com/@vueuse/core/-/core-11.3.0.tgz#bb0bd1f0edd5435d20694dbe51091cf548653a4d" + integrity sha512-7OC4Rl1f9G8IT6rUfi9JrKiXy4bfmHhZ5x2Ceojy0jnd3mHNEvV4JaRygH362ror6/NZ+Nl+n13LPzGiPN8cKA== + dependencies: + "@types/web-bluetooth" "^0.0.20" + "@vueuse/metadata" "11.3.0" + "@vueuse/shared" "11.3.0" + vue-demi ">=0.14.10" + +"@vueuse/core@^10.11.0": + version "10.11.1" + resolved "https://registry.npmmirror.com/@vueuse/core/-/core-10.11.1.tgz#15d2c0b6448d2212235b23a7ba29c27173e0c2c6" + integrity sha512-guoy26JQktXPcz+0n3GukWIy/JDNKti9v6VEMu6kV2sYBsWuGiTU8OWdg+ADfUbHg3/3DlqySDe7JmdHrktiww== + dependencies: + "@types/web-bluetooth" "^0.0.20" + "@vueuse/metadata" "10.11.1" + "@vueuse/shared" "10.11.1" + vue-demi ">=0.14.8" + +"@vueuse/integrations@^11.0.0": + version "11.3.0" + resolved "https://registry.npmmirror.com/@vueuse/integrations/-/integrations-11.3.0.tgz#2df4468ca3e2f8182ebe2cf7f5b9d768dc7baeb1" + integrity sha512-5fzRl0apQWrDezmobchoiGTkGw238VWESxZHazfhP3RM7pDSiyXy18QbfYkILoYNTd23HPAfQTJpkUc5QbkwTw== + dependencies: + "@vueuse/core" "11.3.0" + "@vueuse/shared" "11.3.0" + vue-demi ">=0.14.10" + +"@vueuse/metadata@10.11.1": + version "10.11.1" + resolved "https://registry.npmmirror.com/@vueuse/metadata/-/metadata-10.11.1.tgz#209db7bb5915aa172a87510b6de2ca01cadbd2a7" + integrity sha512-IGa5FXd003Ug1qAZmyE8wF3sJ81xGLSqTqtQ6jaVfkeZ4i5kS2mwQF61yhVqojRnenVew5PldLyRgvdl4YYuSw== + +"@vueuse/metadata@11.3.0": + version "11.3.0" + resolved "https://registry.npmmirror.com/@vueuse/metadata/-/metadata-11.3.0.tgz#be7ac12e3016c0353a3667b372a73aeeee59194e" + integrity sha512-pwDnDspTqtTo2HwfLw4Rp6yywuuBdYnPYDq+mO38ZYKGebCUQC/nVj/PXSiK9HX5otxLz8Fn7ECPbjiRz2CC3g== + +"@vueuse/shared@10.11.1": + version "10.11.1" + resolved "https://registry.npmmirror.com/@vueuse/shared/-/shared-10.11.1.tgz#62b84e3118ae6e1f3ff38f4fbe71b0c5d0f10938" + integrity sha512-LHpC8711VFZlDaYUXEBbFBCQ7GS3dVU9mjOhhMhXP6txTV4EhYQg/KGnQuvt/sPAtoUKq7VVUnL6mVtFoL42sA== + dependencies: + vue-demi ">=0.14.8" + +"@vueuse/shared@11.3.0": + version "11.3.0" + resolved "https://registry.npmmirror.com/@vueuse/shared/-/shared-11.3.0.tgz#086a4f35bf5bcec5655a03b80eae582605a4b21d" + integrity sha512-P8gSSWQeucH5821ek2mn/ciCk+MS/zoRKqdQIM3bHq6p7GXDAJLmnRRKmF5F65sAVJIfzQlwR3aDzwCn10s8hA== + dependencies: + vue-demi ">=0.14.10" + +algoliasearch@^5.14.2: + version "5.59.0" + resolved "https://registry.npmmirror.com/algoliasearch/-/algoliasearch-5.59.0.tgz#c4d592b6d99fd0fa9929eaf40077b75824f45a68" + integrity sha512-wUXzaeI7B526W4y1gFg3lcxgDZ67XSgRJIiellYWOas/pLpO7rOtxm9Gr2E/C8aiSDXIgx1q5TdSsvK67Uakqw== + dependencies: + "@algolia/abtesting" "1.25.0" + "@algolia/client-abtesting" "5.59.0" + "@algolia/client-analytics" "5.59.0" + "@algolia/client-common" "5.59.0" + "@algolia/client-insights" "5.59.0" + "@algolia/client-personalization" "5.59.0" + "@algolia/client-query-suggestions" "5.59.0" + "@algolia/client-search" "5.59.0" + "@algolia/ingestion" "1.59.0" + "@algolia/monitoring" "1.59.0" + "@algolia/recommend" "5.59.0" + "@algolia/requester-browser-xhr" "5.59.0" + "@algolia/requester-fetch" "5.59.0" + "@algolia/requester-node-http" "5.59.0" + +argparse@^2.0.1: + version "2.0.1" + resolved "https://registry.npmmirror.com/argparse/-/argparse-2.0.1.tgz#246f50f3ca78a3240f6c997e8a9bd1eac49e4b38" + integrity sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q== + +birpc@^2.3.0: + version "2.9.0" + resolved "https://registry.npmmirror.com/birpc/-/birpc-2.9.0.tgz#b59550897e4cd96a223e2a6c1475b572236ed145" + integrity sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw== + +ccount@^2.0.0: + version "2.0.1" + resolved "https://registry.npmmirror.com/ccount/-/ccount-2.0.1.tgz#17a3bf82302e0870d6da43a01311a8bc02a3ecf5" + integrity sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg== + +character-entities-html4@^2.0.0: + version "2.1.0" + resolved "https://registry.npmmirror.com/character-entities-html4/-/character-entities-html4-2.1.0.tgz#1f1adb940c971a4b22ba39ddca6b618dc6e56b2b" + integrity sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA== + +character-entities-legacy@^3.0.0: + version "3.0.0" + resolved "https://registry.npmmirror.com/character-entities-legacy/-/character-entities-legacy-3.0.0.tgz#76bc83a90738901d7bc223a9e93759fdd560125b" + integrity sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ== + +chokidar@^5.0.0: + version "5.0.0" + resolved "https://registry.npmmirror.com/chokidar/-/chokidar-5.0.0.tgz#949c126a9238a80792be9a0265934f098af369a5" + integrity sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw== + dependencies: + readdirp "^5.0.0" + +colorjs.io@^0.7.0: + version "0.7.1" + resolved "https://registry.npmmirror.com/colorjs.io/-/colorjs.io-0.7.1.tgz#311b356a325c2c835c5aed17f298b238d7d9a5a1" + integrity sha512-LY7OHnJZxHwT5UlzNa9bbhHHDbzB6yE5+3MIPwJEQKRvSCt/T4G7epsj+9j2BExUIIfXFIJGsIXUKdfrK9Q5tA== + +comma-separated-tokens@^2.0.0: + version "2.0.3" + resolved "https://registry.npmmirror.com/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz#4e89c9458acb61bc8fef19f4529973b2392839ee" + integrity sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg== + +copy-anything@^4: + version "4.1.1" + resolved "https://registry.npmmirror.com/copy-anything/-/copy-anything-4.1.1.tgz#2734bcffa1f01f8e2afb2232f1ee31cd5fcc6834" + integrity sha512-AoT6Imdr98feSpFfmFwTFN73ccdr7uFPf27cBCgYvyyRyn1BzLRxMvrHNmwXO5LJMddRy4Rdhw2b1h7vSMKsEw== + +csstype@^3.2.3: + version "3.2.3" + resolved "https://registry.npmmirror.com/csstype/-/csstype-3.2.3.tgz#ec48c0f3e993e50648c86da559e2610995cf989a" + integrity sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ== + +dequal@^2.0.0: + version "2.0.3" + resolved "https://registry.npmmirror.com/dequal/-/dequal-2.0.3.tgz#2644214f1997d39ed0ee0ece72335490a7ac67be" + integrity sha512-0je+qPKHEMohvfRTCEo3CrPG6cAzAYgmzKyxRiYSSDkS6eGJdyVJm7WaYA5ECaAD9wLB2T4EEeymA5aFVcYXCA== + +detect-libc@^2.0.3: + version "2.1.2" + resolved "https://registry.npmmirror.com/detect-libc/-/detect-libc-2.1.2.tgz#689c5dcdc1900ef5583a4cb9f6d7b473742074ad" + integrity sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ== + +devlop@^1.0.0: + version "1.1.0" + resolved "https://registry.npmmirror.com/devlop/-/devlop-1.1.0.tgz#4db7c2ca4dc6e0e834c30be70c94bbc976dc7018" + integrity sha512-RWmIqhcFf1lRYBvNmr7qTNuyCt/7/ns2jbpp1+PalgE/rDQcBT0fioSMUpJ93irlUhC5hrg4cYqe6U+0ImW0rA== + dependencies: + dequal "^2.0.0" + +emoji-regex-xs@^1.0.0: + version "1.0.0" + resolved "https://registry.npmmirror.com/emoji-regex-xs/-/emoji-regex-xs-1.0.0.tgz#e8af22e5d9dbd7f7f22d280af3d19d2aab5b0724" + integrity sha512-LRlerrMYoIDrT6jgpeZ2YYl/L8EulRTt5hQcYjy5AInh7HWXKimpqx68aknBFpGL2+/IcogTcaydJEgaTmOpDg== + +entities@^4.5.0: + version "4.5.0" + resolved "https://registry.npmmirror.com/entities/-/entities-4.5.0.tgz#5d268ea5e7113ec74c4d033b79ea5a35a488fb48" + integrity sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw== + +entities@^7.0.1: + version "7.0.1" + resolved "https://registry.npmmirror.com/entities/-/entities-7.0.1.tgz#26e8a88889db63417dcb9a1e79a3f1bc92b5976b" + integrity sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA== + +esbuild@^0.21.3: + version "0.21.5" + resolved "https://registry.npmmirror.com/esbuild/-/esbuild-0.21.5.tgz#9ca301b120922959b766360d8ac830da0d02997d" + integrity sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw== + optionalDependencies: + "@esbuild/aix-ppc64" "0.21.5" + "@esbuild/android-arm" "0.21.5" + "@esbuild/android-arm64" "0.21.5" + "@esbuild/android-x64" "0.21.5" + "@esbuild/darwin-arm64" "0.21.5" + "@esbuild/darwin-x64" "0.21.5" + "@esbuild/freebsd-arm64" "0.21.5" + "@esbuild/freebsd-x64" "0.21.5" + "@esbuild/linux-arm" "0.21.5" + "@esbuild/linux-arm64" "0.21.5" + "@esbuild/linux-ia32" "0.21.5" + "@esbuild/linux-loong64" "0.21.5" + "@esbuild/linux-mips64el" "0.21.5" + "@esbuild/linux-ppc64" "0.21.5" + "@esbuild/linux-riscv64" "0.21.5" + "@esbuild/linux-s390x" "0.21.5" + "@esbuild/linux-x64" "0.21.5" + "@esbuild/netbsd-x64" "0.21.5" + "@esbuild/openbsd-x64" "0.21.5" + "@esbuild/sunos-x64" "0.21.5" + "@esbuild/win32-arm64" "0.21.5" + "@esbuild/win32-ia32" "0.21.5" + "@esbuild/win32-x64" "0.21.5" + +estree-walker@^2.0.2: + version "2.0.2" + resolved "https://registry.npmmirror.com/estree-walker/-/estree-walker-2.0.2.tgz#52f010178c2a4c117a7757cfe942adb7d2da4cac" + integrity sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w== + +focus-trap@^7.5.4: + version "7.8.0" + resolved "https://registry.npmmirror.com/focus-trap/-/focus-trap-7.8.0.tgz#b1d9463fa42b93ad7a5223d750493a6c09b672a8" + integrity sha512-/yNdlIkpWbM0ptxno3ONTuf+2g318kh2ez3KSeZN5dZ8YC6AAmgeWz+GasYYiBJPFaYcSAPeu4GfhUaChzIJXA== + dependencies: + tabbable "^6.4.0" + +fsevents@~2.3.2, fsevents@~2.3.3: + version "2.3.3" + resolved "https://registry.npmmirror.com/fsevents/-/fsevents-2.3.3.tgz#cac6407785d03675a2a5e1a5305c697b347d90d6" + integrity sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw== + +has-flag@^4.0.0: + version "4.0.0" + resolved "https://registry.npmmirror.com/has-flag/-/has-flag-4.0.0.tgz#944771fd9c81c81265c4d6941860da06bb59479b" + integrity sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ== + +hast-util-to-html@^9.0.4: + version "9.0.5" + resolved "https://registry.npmmirror.com/hast-util-to-html/-/hast-util-to-html-9.0.5.tgz#ccc673a55bb8e85775b08ac28380f72d47167005" + integrity sha512-OguPdidb+fbHQSU4Q4ZiLKnzWo8Wwsf5bZfbvu7//a9oTYoqD/fWpe96NuHkoS9h0ccGOTe0C4NGXdtS0iObOw== + dependencies: + "@types/hast" "^3.0.0" + "@types/unist" "^3.0.0" + ccount "^2.0.0" + comma-separated-tokens "^2.0.0" + hast-util-whitespace "^3.0.0" + html-void-elements "^3.0.0" + mdast-util-to-hast "^13.0.0" + property-information "^7.0.0" + space-separated-tokens "^2.0.0" + stringify-entities "^4.0.0" + zwitch "^2.0.4" + +hast-util-whitespace@^3.0.0: + version "3.0.0" + resolved "https://registry.npmmirror.com/hast-util-whitespace/-/hast-util-whitespace-3.0.0.tgz#7778ed9d3c92dd9e8c5c8f648a49c21fc51cb621" + integrity sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw== + dependencies: + "@types/hast" "^3.0.0" + +hookable@^5.5.3: + version "5.5.3" + resolved "https://registry.npmmirror.com/hookable/-/hookable-5.5.3.tgz#6cfc358984a1ef991e2518cb9ed4a778bbd3215d" + integrity sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ== + +html-void-elements@^3.0.0: + version "3.0.0" + resolved "https://registry.npmmirror.com/html-void-elements/-/html-void-elements-3.0.0.tgz#fc9dbd84af9e747249034d4d62602def6517f1d7" + integrity sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg== + +immutable@^5.1.5: + version "5.1.9" + resolved "https://registry.npmmirror.com/immutable/-/immutable-5.1.9.tgz#ac23c3a01992ab665e14ac9ffff298f28cd74a0c" + integrity sha512-m8nVez3rwrgmWxtLMt1ZYXB2Lv7OKYn/disyxAlSDYAlKSlFoPPfIAmAM/M5xqL4m4C/wAPw7S2/CNaUii1Hxg== + +import-meta-resolve@^4.2.0: + version "4.2.0" + resolved "https://registry.npmmirror.com/import-meta-resolve/-/import-meta-resolve-4.2.0.tgz#08cb85b5bd37ecc8eb1e0f670dc2767002d43734" + integrity sha512-Iqv2fzaTQN28s/FwZAoFq0ZSs/7hMAHJVX+w8PZl3cY19Pxk6jFFalxQoIfW2826i/fDLXv8IiEZRIT0lDuWcg== + +is-extglob@^2.1.1: + version "2.1.1" + resolved "https://registry.npmmirror.com/is-extglob/-/is-extglob-2.1.1.tgz#a88c02535791f02ed37c76a1b9ea9773c833f8c2" + integrity sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ== + +is-glob@^4.0.3: + version "4.0.3" + resolved "https://registry.npmmirror.com/is-glob/-/is-glob-4.0.3.tgz#64f61e42cbbb2eec2071a9dac0b28ba1e65d5084" + integrity sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg== + dependencies: + is-extglob "^2.1.1" + +linkify-it@^5.0.2: + version "5.0.2" + resolved "https://registry.npmmirror.com/linkify-it/-/linkify-it-5.0.2.tgz#d3be0a693af3da9df3883f1e346a0e97461a8c19" + integrity sha512-ONTm2jCMAVZjgQa/Fy1kScXsuOoF5NPTsoFBdE1KVIZ2vAh/r9+Bqo+0jINCBYnavTPQZz38QzFTme79ENoN3Q== + dependencies: + uc.micro "^2.0.0" + +magic-string@^0.30.21: + version "0.30.21" + resolved "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.21.tgz#56763ec09a0fa8091df27879fd94d19078c00d91" + integrity sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ== + dependencies: + "@jridgewell/sourcemap-codec" "^1.5.5" + +mark.js@8.11.1: + version "8.11.1" + resolved "https://registry.npmmirror.com/mark.js/-/mark.js-8.11.1.tgz#180f1f9ebef8b0e638e4166ad52db879beb2ffc5" + integrity sha512-1I+1qpDt4idfgLQG+BNWmrqku+7/2bi5nLf4YwF8y8zXvmfiTBY3PV3ZibfrjBueCByROpuBjLLFCajqkgYoLQ== + +markdown-it@^14.1.0: + version "14.3.2" + resolved "https://registry.npmmirror.com/markdown-it/-/markdown-it-14.3.2.tgz#eb41b5855120836603c33c1be4c92f58001f8a26" + integrity sha512-sHHjZ5fJKlgrG4qns2YwVcdNep35h5fERrfkD2YNsb9UFk0UIHarbiTaHKVMlPuWAoiilyK8Fv/jAm11slsY7Q== + dependencies: + argparse "^2.0.1" + entities "^4.5.0" + linkify-it "^5.0.2" + mdurl "^2.0.0" + punycode.js "^2.3.1" + uc.micro "^2.1.0" + +mdast-util-to-hast@^13.0.0: + version "13.2.1" + resolved "https://registry.npmmirror.com/mdast-util-to-hast/-/mdast-util-to-hast-13.2.1.tgz#d7ff84ca499a57e2c060ae67548ad950e689a053" + integrity sha512-cctsq2wp5vTsLIcaymblUriiTcZd0CwWtCbLvrOzYCDZoWyMNV8sZ7krj09FSnsiJi3WVsHLM4k6Dq/yaPyCXA== + dependencies: + "@types/hast" "^3.0.0" + "@types/mdast" "^4.0.0" + "@ungap/structured-clone" "^1.0.0" + devlop "^1.0.0" + micromark-util-sanitize-uri "^2.0.0" + trim-lines "^3.0.0" + unist-util-position "^5.0.0" + unist-util-visit "^5.0.0" + vfile "^6.0.0" + +mdurl@^2.0.0: + version "2.1.0" + resolved "https://registry.npmmirror.com/mdurl/-/mdurl-2.1.0.tgz#d711d3f7bce7f22c487c91be78545f356fa96573" + integrity sha512-1+HBaOx0zi/dQWht8rNv9MYf9qqpqL/kxI0hXImU6Y547zM6Sni8BQibt7ifgMcYtQg41ao3Ivd6cnSM86inpg== + +medium-zoom@^1.1.0: + version "1.1.0" + resolved "https://registry.npmmirror.com/medium-zoom/-/medium-zoom-1.1.0.tgz#6efb6bbda861a02064ee71a2617a8dc4381ecc71" + integrity sha512-ewyDsp7k4InCUp3jRmwHBRFGyjBimKps/AJLjRSox+2q/2H4p/PNpQf+pwONWlJiOudkBXtbdmVbFjqyybfTmQ== + +micromark-util-character@^2.0.0: + version "2.1.1" + resolved "https://registry.npmmirror.com/micromark-util-character/-/micromark-util-character-2.1.1.tgz#2f987831a40d4c510ac261e89852c4e9703ccda6" + integrity sha512-wv8tdUTJ3thSFFFJKtpYKOYiGP2+v96Hvk4Tu8KpCAsTMs6yi+nVmGh1syvSCsaxz45J6Jbw+9DD6g97+NV67Q== + dependencies: + micromark-util-symbol "^2.0.0" + micromark-util-types "^2.0.0" + +micromark-util-encode@^2.0.0: + version "2.0.1" + resolved "https://registry.npmmirror.com/micromark-util-encode/-/micromark-util-encode-2.0.1.tgz#0d51d1c095551cfaac368326963cf55f15f540b8" + integrity sha512-c3cVx2y4KqUnwopcO9b/SCdo2O67LwJJ/UyqGfbigahfegL9myoEFoDYZgkT7f36T0bLrM9hZTAaAyH+PCAXjw== + +micromark-util-sanitize-uri@^2.0.0: + version "2.0.1" + resolved "https://registry.npmmirror.com/micromark-util-sanitize-uri/-/micromark-util-sanitize-uri-2.0.1.tgz#ab89789b818a58752b73d6b55238621b7faa8fd7" + integrity sha512-9N9IomZ/YuGGZZmQec1MbgxtlgougxTodVwDzzEouPKo3qFWvymFHWcnDi2vzV1ff6kas9ucW+o3yzJK9YB1AQ== + dependencies: + micromark-util-character "^2.0.0" + micromark-util-encode "^2.0.0" + micromark-util-symbol "^2.0.0" + +micromark-util-symbol@^2.0.0: + version "2.0.1" + resolved "https://registry.npmmirror.com/micromark-util-symbol/-/micromark-util-symbol-2.0.1.tgz#e5da494e8eb2b071a0d08fb34f6cefec6c0a19b8" + integrity sha512-vs5t8Apaud9N28kgCrRUdEed4UJ+wWNvicHLPxCa9ENlYuAY31M0ETy5y1vA33YoNPDFTghEbnh6efaE8h4x0Q== + +micromark-util-types@^2.0.0: + version "2.0.2" + resolved "https://registry.npmmirror.com/micromark-util-types/-/micromark-util-types-2.0.2.tgz#f00225f5f5a0ebc3254f96c36b6605c4b393908e" + integrity sha512-Yw0ECSpJoViF1qTU4DC6NwtC4aWGt1EkzaQB8KPPyCRR8z9TWeV0HbEFGTO+ZY1wB22zmxnJqhPyTpOVCpeHTA== + +minisearch@^7.1.0: + version "7.2.0" + resolved "https://registry.npmmirror.com/minisearch/-/minisearch-7.2.0.tgz#3dc30e41e9464b3836553b6d969b656614f8f359" + integrity sha512-dqT2XBYUOZOiC5t2HRnwADjhNS2cecp9u+TJRiJ1Qp/f5qjkeT5APcGPjHw+bz89Ms8Jp+cG4AlE+QZ/QnDglg== + +mitt@^3.0.1: + version "3.0.1" + resolved "https://registry.npmmirror.com/mitt/-/mitt-3.0.1.tgz#ea36cf0cc30403601ae074c8f77b7092cdab36d1" + integrity sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw== + +nanoid@^3.3.18: + version "3.3.19" + resolved "https://registry.npmmirror.com/nanoid/-/nanoid-3.3.19.tgz#336d4aa4bcd4fb24d2cddede7ffeae40bec03f0a" + integrity sha512-Y2tUNy4ouw6tq5oDSKeQYGOyhkUBhNOcGV/02KC+6kd9eDGqdZd++mjMiIDilrBYvjEnCYvVtsuHCuP+okSfug== + +node-addon-api@^7.0.0: + version "7.1.1" + resolved "https://registry.npmmirror.com/node-addon-api/-/node-addon-api-7.1.1.tgz#1aba6693b0f255258a049d621329329322aad558" + integrity sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ== + +oniguruma-to-es@^2.2.0: + version "2.3.0" + resolved "https://registry.npmmirror.com/oniguruma-to-es/-/oniguruma-to-es-2.3.0.tgz#35ea9104649b7c05f3963c6b3b474d964625028b" + integrity sha512-bwALDxriqfKGfUufKGGepCzu9x7nJQuoRoAFp4AnwehhC2crqrDIAP/uN2qdlsAvSMpeRC3+Yzhqc7hLmle5+g== + dependencies: + emoji-regex-xs "^1.0.0" + regex "^5.1.1" + regex-recursion "^5.1.1" + +package-manager-detector@^1.8.0: + version "1.8.0" + resolved "https://registry.npmmirror.com/package-manager-detector/-/package-manager-detector-1.8.0.tgz#70c9a2c4bd1a513dcd6cad006a9fcebec22a1253" + integrity sha512-yQA4H19AmPEoMUeavPMDIe1higySl/gH/yaQrkT/s07Qp+7pp2hYz30N3z2l5BkjVkF9Ow6o0wjJamm2y7Sn0A== + +perfect-debounce@^1.0.0: + version "1.0.0" + resolved "https://registry.npmmirror.com/perfect-debounce/-/perfect-debounce-1.0.0.tgz#9c2e8bc30b169cc984a58b7d5b28049839591d2a" + integrity sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA== + +picocolors@^1.1.1: + version "1.1.1" + resolved "https://registry.npmmirror.com/picocolors/-/picocolors-1.1.1.tgz#3d321af3eab939b083c8f929a1d12cda81c26b6b" + integrity sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA== + +picomatch@^4.0.4: + version "4.0.7" + resolved "https://registry.npmmirror.com/picomatch/-/picomatch-4.0.7.tgz#6313360034ccb36b3dc61ecbdff78121f90fe21f" + integrity sha512-qcJu88Q2IWqJsDD529JKMdwGm/dvInW4HvQnRwiH9JtihJvzGOscDtHE3x1pBKeUOTysQ8kVmLnJ2kJu7yhcGA== + +postcss@^8.4.43, postcss@^8.5.28: + version "8.5.28" + resolved "https://registry.npmmirror.com/postcss/-/postcss-8.5.28.tgz#da4563a99a06e62d6c1cd1acae363224bcaed6e9" + integrity sha512-RRuzqDtt5Y9h3quz5hWhK+TPnsmVs6WwSU6LkJMeY4HstUEDuYTG8UJSdawMRzmzAtV+KEoG8N3Qg2qLy5vM/A== + dependencies: + nanoid "^3.3.18" + picocolors "^1.1.1" + source-map-js "^1.2.1" + +preact@^10.0.0: + version "10.29.8" + resolved "https://registry.npmmirror.com/preact/-/preact-10.29.8.tgz#fc85b82dc2474e245b1430b51781d417361f5fc3" + integrity sha512-ej2aVZ+vZ8WO7tvlQWRM9N63A0KzF9q4mWJfDUHgYaIofWY9hu74QdnQrjoPMmZi2/nZ5gN0bJCQF49xQqx09Q== + +property-information@^7.0.0: + version "7.2.0" + resolved "https://registry.npmmirror.com/property-information/-/property-information-7.2.0.tgz#0809b34264e995c0bfcd3227028a1e35210af80a" + integrity sha512-IAtzIB6sUiWaJYrX9smp3V46pBGbBeLFRGdh25kg1334VcBlD8HzhPeNIWQH9zhGmo2itIe25EHt9dQP7G5hmg== + +punycode.js@^2.3.1: + version "2.3.1" + resolved "https://registry.npmmirror.com/punycode.js/-/punycode.js-2.3.1.tgz#6b53e56ad75588234e79f4affa90972c7dd8cdb7" + integrity sha512-uxFIHU0YlHYhDQtV4R9J6a52SLx28BCjT+4ieh7IGbgwVJWO+km431c4yRlREUAsAmt/uMjQUyQHNEPf0M39CA== + +readdirp@^5.0.0: + version "5.1.1" + resolved "https://registry.npmmirror.com/readdirp/-/readdirp-5.1.1.tgz#520bca06f9d1ae1b96cc0800dbe84b983d19422c" + integrity sha512-Kko+Y5XQ6fM+Ce3dq3m9YGxnacYZYl9cA1wZjaF3Vbry2L3i1qVg8+CAgNPsXRArPMUMCaOR7oa9Nqntc43JKA== + +regex-recursion@^5.1.1: + version "5.1.1" + resolved "https://registry.npmmirror.com/regex-recursion/-/regex-recursion-5.1.1.tgz#5a73772d18adbf00f57ad097bf54171b39d78f8b" + integrity sha512-ae7SBCbzVNrIjgSbh7wMznPcQel1DNlDtzensnFxpiNpXt1U2ju/bHugH422r+4LAVS1FpW1YCwilmnNsjum9w== + dependencies: + regex "^5.1.1" + regex-utilities "^2.3.0" + +regex-utilities@^2.3.0: + version "2.3.0" + resolved "https://registry.npmmirror.com/regex-utilities/-/regex-utilities-2.3.0.tgz#87163512a15dce2908cf079c8960d5158ff43280" + integrity sha512-8VhliFJAWRaUiVvREIiW2NXXTmHs4vMNnSzuJVhscgmGav3g9VDxLrQndI3dZZVVdp0ZO/5v0xmX516/7M9cng== + +regex@^5.1.1: + version "5.1.1" + resolved "https://registry.npmmirror.com/regex/-/regex-5.1.1.tgz#cf798903f24d6fe6e531050a36686e082b29bd03" + integrity sha512-dN5I359AVGPnwzJm2jN1k0W9LPZ+ePvoOeVMMfqIMFz53sSwXkxaJoxr50ptnsC771lK95BnTrVSZxq0b9yCGw== + dependencies: + regex-utilities "^2.3.0" + +rfdc@^1.4.1: + version "1.4.1" + resolved "https://registry.npmmirror.com/rfdc/-/rfdc-1.4.1.tgz#778f76c4fb731d93414e8f925fbecf64cce7f6ca" + integrity sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA== + +rollup@^4.20.0: + version "4.63.4" + resolved "https://registry.npmmirror.com/rollup/-/rollup-4.63.4.tgz#cff29b8d6d959aee7cb5e44b0dad70074493dbf8" + integrity sha512-4U0liVayNIoLp3GFl1FcI8561WepLnZ1rqfraGh7S9B3Ur5F9S283y8Futii7RUU2C/97tOBmBy7nYvhoiOpbQ== + dependencies: + "@types/estree" "1.0.9" + optionalDependencies: + "@napi-rs/lzma-linux-x64-gnu" "1.5.1" + "@rollup/rollup-android-arm-eabi" "4.63.4" + "@rollup/rollup-android-arm64" "4.63.4" + "@rollup/rollup-darwin-arm64" "4.63.4" + "@rollup/rollup-darwin-x64" "4.63.4" + "@rollup/rollup-freebsd-arm64" "4.63.4" + "@rollup/rollup-freebsd-x64" "4.63.4" + "@rollup/rollup-linux-arm-gnueabihf" "4.63.4" + "@rollup/rollup-linux-arm-musleabihf" "4.63.4" + "@rollup/rollup-linux-arm64-gnu" "4.63.4" + "@rollup/rollup-linux-arm64-musl" "4.63.4" + "@rollup/rollup-linux-loong64-gnu" "4.63.4" + "@rollup/rollup-linux-loong64-musl" "4.63.4" + "@rollup/rollup-linux-ppc64-gnu" "4.63.4" + "@rollup/rollup-linux-ppc64-musl" "4.63.4" + "@rollup/rollup-linux-riscv64-gnu" "4.63.4" + "@rollup/rollup-linux-riscv64-musl" "4.63.4" + "@rollup/rollup-linux-s390x-gnu" "4.63.4" + "@rollup/rollup-linux-x64-gnu" "4.63.4" + "@rollup/rollup-linux-x64-musl" "4.63.4" + "@rollup/rollup-openbsd-x64" "4.63.4" + "@rollup/rollup-openharmony-arm64" "4.63.4" + "@rollup/rollup-win32-arm64-msvc" "4.63.4" + "@rollup/rollup-win32-ia32-msvc" "4.63.4" + "@rollup/rollup-win32-x64-gnu" "4.63.4" + "@rollup/rollup-win32-x64-msvc" "4.63.4" + fsevents "~2.3.2" + +rxjs@^7.4.0: + version "7.8.2" + resolved "https://registry.npmmirror.com/rxjs/-/rxjs-7.8.2.tgz#955bc473ed8af11a002a2be52071bf475638607b" + integrity sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA== + dependencies: + tslib "^2.1.0" + +sass-embedded-all-unknown@1.104.1: + version "1.104.1" + resolved "https://registry.npmmirror.com/sass-embedded-all-unknown/-/sass-embedded-all-unknown-1.104.1.tgz#de2cd090dabe8b907485059d6a1bbbad76d7bf5a" + integrity sha512-toyMCyUKkT6DsYlHuKZGjd8T6WltaW4TGX07F7HnF4tNJM5m/8VaVsd74zrq3QSPqGE9JjxIDK4NrNc/QY9pkA== + dependencies: + sass "1.104.1" + +sass-embedded-android-arm64@1.104.1: + version "1.104.1" + resolved "https://registry.npmmirror.com/sass-embedded-android-arm64/-/sass-embedded-android-arm64-1.104.1.tgz#55d032d20679d6a6bc5a985c551137a4349aa06f" + integrity sha512-yW3BVW4YFpN2vu2cVQ+Pz5JrRTvoIz36aCdo+J/Mo1A9gEN8eVPV/AL7aeZ803YdKgIitd3qw3pY4wpmT6TXNg== + +sass-embedded-android-arm@1.104.1: + version "1.104.1" + resolved "https://registry.npmmirror.com/sass-embedded-android-arm/-/sass-embedded-android-arm-1.104.1.tgz#7c09042d840929272d7b1967518ca20cb74c7f45" + integrity sha512-g/XPAJYAzSSQZFP4xCdGD0HLKB4mf24cJ/+5Nls8rMool3jNkBv9+AFO4EV0b+RxN8rLcEsMccH1CR64yIFYcQ== + +sass-embedded-android-riscv64@1.104.1: + version "1.104.1" + resolved "https://registry.npmmirror.com/sass-embedded-android-riscv64/-/sass-embedded-android-riscv64-1.104.1.tgz#a60bfee9b293723ba74227b81b417cd2bfba56c4" + integrity sha512-ZYY8oMKQS7Q/6FBoC8w3T16O3MucDe3xv8iKym41Nht5uvLCGGpm4fC4OF5nlv7wQkJv8oactYddGG3Wd0Rqkg== + +sass-embedded-android-x64@1.104.1: + version "1.104.1" + resolved "https://registry.npmmirror.com/sass-embedded-android-x64/-/sass-embedded-android-x64-1.104.1.tgz#8dcfaf9b6043ee30d04f7829456a55fda742222e" + integrity sha512-5WZKC4lSfjMIjzZdQUlYQUAV+UiatBhD8w28U7cYLLj4/dMgEPOBd4g3VMv0BwpgH2JOaycMS42dXq29W2bcDg== + +sass-embedded-darwin-arm64@1.104.1: + version "1.104.1" + resolved "https://registry.npmmirror.com/sass-embedded-darwin-arm64/-/sass-embedded-darwin-arm64-1.104.1.tgz#721363fcdbe65c2050dd8b237e350356b756e8ae" + integrity sha512-oatuHzn0NsEg0d9Dcze6CuWUPvuETtMxRK4cv0eRAg7p6R0vHnv5GZRyWG6gEy3nc/ha/L+GtTasWVU82f4jrQ== + +sass-embedded-darwin-x64@1.104.1: + version "1.104.1" + resolved "https://registry.npmmirror.com/sass-embedded-darwin-x64/-/sass-embedded-darwin-x64-1.104.1.tgz#bb123114530f1ba22f28fa62d1063a2d1faffd8c" + integrity sha512-gql/5sHGzUTVwuHfM3QLCDeb6NYwen+LLj22gz81C2NKHF/4nUtDyh4N+fURWdT7J0/EhtMOd0+FLML3sGNP7g== + +sass-embedded-linux-arm64@1.104.1: + version "1.104.1" + resolved "https://registry.npmmirror.com/sass-embedded-linux-arm64/-/sass-embedded-linux-arm64-1.104.1.tgz#61ac04585736dfac54c821b0f2da962eee6d8fdb" + integrity sha512-sVWUKXNTu8sP3X95Df0xo/t49Ce5IseTIMqXHxhFBKVmx7gf7kBWNLK0wH5ym+4khc+B5nNJPgq4IxpelG8tMQ== + +sass-embedded-linux-arm@1.104.1: + version "1.104.1" + resolved "https://registry.npmmirror.com/sass-embedded-linux-arm/-/sass-embedded-linux-arm-1.104.1.tgz#4af58cc6af4e564a34bb41c0902dd47ac183e655" + integrity sha512-WfmshPJOp9vUKxgp8wZ2ncaKBbyrlW9J51M2aUQHWECjMNxTULnGOHWSmD7FViXv4zwn1Co5FIFQUipW7RCxtA== + +sass-embedded-linux-musl-arm64@1.104.1: + version "1.104.1" + resolved "https://registry.npmmirror.com/sass-embedded-linux-musl-arm64/-/sass-embedded-linux-musl-arm64-1.104.1.tgz#5eb74ed461e5773120210d82acd71734e0cb9015" + integrity sha512-H7b+LzZ/w8pwNExInM8HDHraqvxCMdUc5ZRGU/aSJJhZ9HjeRKBjF3GxaisVHnx7o18j1l4VQ5cLBL9tTmNzZw== + +sass-embedded-linux-musl-arm@1.104.1: + version "1.104.1" + resolved "https://registry.npmmirror.com/sass-embedded-linux-musl-arm/-/sass-embedded-linux-musl-arm-1.104.1.tgz#285907bda4f80e2fa162465cb805c84e81e2a4ad" + integrity sha512-k0cQ/CxBzFVYiQEObg6CpF6CUglkdEhi/DZgZXQbwC27kbGGY+fatmIs/cFPJL2z1ulACwoAr86RQUFzYvw5eg== + +sass-embedded-linux-musl-riscv64@1.104.1: + version "1.104.1" + resolved "https://registry.npmmirror.com/sass-embedded-linux-musl-riscv64/-/sass-embedded-linux-musl-riscv64-1.104.1.tgz#a4aac6f3dc165430733772d5d50b9c6bfccec3f9" + integrity sha512-9DYdn4wfrkidjxJVtEd2iCx8HKdZe+uF82GDmrYEZhgu4NjVE6a4fQyW7OLwzn3erZMHKBaObBOH1d37DnXI1A== + +sass-embedded-linux-musl-x64@1.104.1: + version "1.104.1" + resolved "https://registry.npmmirror.com/sass-embedded-linux-musl-x64/-/sass-embedded-linux-musl-x64-1.104.1.tgz#e1d8646e15b2da0816c67efe7eceef63e337f4c7" + integrity sha512-5xz+nAr01b6yaRdrGVgE7lNvm1lGFjwNdUvnz+teidvHCbD5oTcbhrRVrnDoLHHvq5n3Jve9TzWaaHhezaEfHA== + +sass-embedded-linux-riscv64@1.104.1: + version "1.104.1" + resolved "https://registry.npmmirror.com/sass-embedded-linux-riscv64/-/sass-embedded-linux-riscv64-1.104.1.tgz#3d173d153edfeb564c86609c804e496b67ee07b9" + integrity sha512-oWryPJWTXK5adtlz/FD7wd2kS4I0CEgp6mWLUVb4oMFURMLz9bA9fO87D7lc4SV++lds7T51PU1kykCbOvNHMg== + +sass-embedded-linux-x64@1.104.1: + version "1.104.1" + resolved "https://registry.npmmirror.com/sass-embedded-linux-x64/-/sass-embedded-linux-x64-1.104.1.tgz#408100eb9c4abaf7d1d54c87b50ee626102d86ad" + integrity sha512-j/sX/yKTx/wJKepC5/ISlR6kPeSuHKThf76D2za5i2VnbHg0MN19jQq3WMorowR3kRghqt6azs3awogG+e7IRA== + +sass-embedded-unknown-all@1.104.1: + version "1.104.1" + resolved "https://registry.npmmirror.com/sass-embedded-unknown-all/-/sass-embedded-unknown-all-1.104.1.tgz#d52ca901b154febc4ea0b72c5bf96ebaf0b6186a" + integrity sha512-RaMsYZB5mRioL4qhoI7T5mmNb2OunakbPTJW2cuu3MeIULnGoxo7Wd5QKFxeQvF7sSQL57RuaQ+q9pH+/pQ9Fg== + dependencies: + sass "1.104.1" + +sass-embedded-win32-arm64@1.104.1: + version "1.104.1" + resolved "https://registry.npmmirror.com/sass-embedded-win32-arm64/-/sass-embedded-win32-arm64-1.104.1.tgz#eb4d574fccca310d6a8a8499b1651e9ae6be7275" + integrity sha512-fS6DL7lcnTPclqGLF4xRt8egZlFY9cU23d57ufbWqaUbCFu9JTu0+8qB4jMxfx1la6byi3GoSTfbnNOLfWv8sw== + +sass-embedded-win32-x64@1.104.1: + version "1.104.1" + resolved "https://registry.npmmirror.com/sass-embedded-win32-x64/-/sass-embedded-win32-x64-1.104.1.tgz#934d8c6eb4ddc40d5fa39e44f4bced35f089b17f" + integrity sha512-/c4N2A93Ckms6DuRFVftKYISuI1tyC8gmlVJ8WaWFzgCBN/Swq2rARJIBUNeuxlmxjmYXfd3+2f/u887K63wbA== + +sass-embedded@^1.85.1: + version "1.104.1" + resolved "https://registry.npmmirror.com/sass-embedded/-/sass-embedded-1.104.1.tgz#60e95bdd2cef42eac48ca3a0c4a6436b962f907a" + integrity sha512-7LxbtPJ1MWKVbhzOnGx1sHTkHTcHVJ9hbgVsU29p/8gY1VeXt19fcJFr5xsx2f6zfrrR3FeOcYMt0uYDLAxpgw== + dependencies: + "@bufbuild/protobuf" "^2.5.0" + colorjs.io "^0.7.0" + immutable "^5.1.5" + rxjs "^7.4.0" + supports-color "^8.1.1" + sync-child-process "^1.0.2" + varint "^6.0.0" + optionalDependencies: + sass-embedded-all-unknown "1.104.1" + sass-embedded-android-arm "1.104.1" + sass-embedded-android-arm64 "1.104.1" + sass-embedded-android-riscv64 "1.104.1" + sass-embedded-android-x64 "1.104.1" + sass-embedded-darwin-arm64 "1.104.1" + sass-embedded-darwin-x64 "1.104.1" + sass-embedded-linux-arm "1.104.1" + sass-embedded-linux-arm64 "1.104.1" + sass-embedded-linux-musl-arm "1.104.1" + sass-embedded-linux-musl-arm64 "1.104.1" + sass-embedded-linux-musl-riscv64 "1.104.1" + sass-embedded-linux-musl-x64 "1.104.1" + sass-embedded-linux-riscv64 "1.104.1" + sass-embedded-linux-x64 "1.104.1" + sass-embedded-unknown-all "1.104.1" + sass-embedded-win32-arm64 "1.104.1" + sass-embedded-win32-x64 "1.104.1" + +sass@1.104.1: + version "1.104.1" + resolved "https://registry.npmmirror.com/sass/-/sass-1.104.1.tgz#29a4bdb33c48a8bb656e300a33afcafbfcbc8491" + integrity sha512-yDA+1aIG3EHgN4V/BvuhCvu61FF6hEd4e+9DxikUm9U0CAGuvdIZ/UYy7qbOxjhbbWQraoyLlMuzdRGOSV5Bmw== + dependencies: + chokidar "^5.0.0" + immutable "^5.1.5" + source-map-js ">=0.6.2 <2.0.0" + optionalDependencies: + "@parcel/watcher" "^2.4.1" + +shiki@^1.13.0: + version "1.29.2" + resolved "https://registry.npmmirror.com/shiki/-/shiki-1.29.2.tgz#5c93771f2d5305ce9c05975c33689116a27dc657" + integrity sha512-njXuliz/cP+67jU2hukkxCNuH1yUi4QfdZZY+sMr5PPrIyXSu5iTb/qYC4BiWWB0vZ+7TbdvYUCeL23zpwCfbg== + dependencies: + "@shikijs/core" "1.29.2" + "@shikijs/engine-javascript" "1.29.2" + "@shikijs/engine-oniguruma" "1.29.2" + "@shikijs/langs" "1.29.2" + "@shikijs/themes" "1.29.2" + "@shikijs/types" "1.29.2" + "@shikijs/vscode-textmate" "^10.0.1" + "@types/hast" "^3.0.4" + +"source-map-js@>=0.6.2 <2.0.0", source-map-js@^1.2.1: + version "1.2.1" + resolved "https://registry.npmmirror.com/source-map-js/-/source-map-js-1.2.1.tgz#1ce5650fddd87abc099eda37dcff024c2667ae46" + integrity sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA== + +space-separated-tokens@^2.0.0: + version "2.0.2" + resolved "https://registry.npmmirror.com/space-separated-tokens/-/space-separated-tokens-2.0.2.tgz#1ecd9d2350a3844572c3f4a312bceb018348859f" + integrity sha512-PEGlAwrG8yXGXRjW32fGbg66JAlOAwbObuqVoJpv/mRgoWDQfgH1wDPvtzWyUSNAXBGSk8h755YDbbcEy3SH2Q== + +speakingurl@^14.0.1: + version "14.0.1" + resolved "https://registry.npmmirror.com/speakingurl/-/speakingurl-14.0.1.tgz#f37ec8ddc4ab98e9600c1c9ec324a8c48d772a53" + integrity sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ== + +stringify-entities@^4.0.0: + version "4.0.4" + resolved "https://registry.npmmirror.com/stringify-entities/-/stringify-entities-4.0.4.tgz#b3b79ef5f277cc4ac73caeb0236c5ba939b3a4f3" + integrity sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg== + dependencies: + character-entities-html4 "^2.0.0" + character-entities-legacy "^3.0.0" + +superjson@^2.2.2: + version "2.2.6" + resolved "https://registry.npmmirror.com/superjson/-/superjson-2.2.6.tgz#a223a3a988172a5f9656e2063fe5f733af40d099" + integrity sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA== + dependencies: + copy-anything "^4" + +supports-color@^8.1.1: + version "8.1.1" + resolved "https://registry.npmmirror.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + +sync-child-process@^1.0.2: + version "1.0.2" + resolved "https://registry.npmmirror.com/sync-child-process/-/sync-child-process-1.0.2.tgz#45e7c72e756d1243e80b547ea2e17957ab9e367f" + integrity sha512-8lD+t2KrrScJ/7KXCSyfhT3/hRq78rC0wBFqNJXv3mZyn6hW2ypM05JmlSvtqRbeq6jqA94oHbxAr2vYsJ8vDA== + dependencies: + sync-message-port "^1.0.0" + +sync-message-port@^1.0.0: + version "1.2.0" + resolved "https://registry.npmmirror.com/sync-message-port/-/sync-message-port-1.2.0.tgz#4b0d622085f21496061037125dec61755d96e330" + integrity sha512-gAQ9qrUN/UCypHtGFbbe7Rc/f9bzO88IwrG8TDo/aMKAApKyD6E3W4Cm0EfhfBb6Z6SKt59tTCTfD+n1xmAvMg== + +tabbable@^6.4.0: + version "6.5.0" + resolved "https://registry.npmmirror.com/tabbable/-/tabbable-6.5.0.tgz#a65101385a4fd6cbd580b7546da0170f307b535d" + integrity sha512-wieBHXygIm7OyQOu5hQlkk62/WyCFYGlWg7L6/ZCUZwx0o398Zkn4pVmMyfYhfMG8kGrj/Krt8eIk6UKC6VzwA== + +tinyexec@^1.3.1: + version "1.3.1" + resolved "https://registry.npmmirror.com/tinyexec/-/tinyexec-1.3.1.tgz#16a2e3c6e23fafce72640e678e651db36145b9c6" + integrity sha512-GCvB3aoys96IuDFBMcTB46JOR6mdMtAToqwiW8JlWhsoh1mhHi/xn9ss/Dg7N555GiJyEt2qzoG/NHCwM6h1EA== + +trim-lines@^3.0.0: + version "3.0.1" + resolved "https://registry.npmmirror.com/trim-lines/-/trim-lines-3.0.1.tgz#d802e332a07df861c48802c04321017b1bd87338" + integrity sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg== + +tslib@^2.1.0: + version "2.8.1" + resolved "https://registry.npmmirror.com/tslib/-/tslib-2.8.1.tgz#612efe4ed235d567e8aba5f2a5fab70280ade83f" + integrity sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w== + +uc.micro@^2.0.0, uc.micro@^2.1.0: + version "2.1.0" + resolved "https://registry.npmmirror.com/uc.micro/-/uc.micro-2.1.0.tgz#f8d3f7d0ec4c3dea35a7e3c8efa4cb8b45c9e7ee" + integrity sha512-ARDJmphmdvUk6Glw7y9DQ2bFkKBHwQHLi2lsaH6PPmz/Ka9sFOBsBluozhDltWmnv9u/cF6Rt87znRTPV+yp/A== + +unist-util-is@^6.0.0: + version "6.0.1" + resolved "https://registry.npmmirror.com/unist-util-is/-/unist-util-is-6.0.1.tgz#d0a3f86f2dd0db7acd7d8c2478080b5c67f9c6a9" + integrity sha512-LsiILbtBETkDz8I9p1dQ0uyRUWuaQzd/cuEeS1hoRSyW5E5XGmTzlwY1OrNzzakGowI9Dr/I8HVaw4hTtnxy8g== + dependencies: + "@types/unist" "^3.0.0" + +unist-util-position@^5.0.0: + version "5.0.0" + resolved "https://registry.npmmirror.com/unist-util-position/-/unist-util-position-5.0.0.tgz#678f20ab5ca1207a97d7ea8a388373c9cf896be4" + integrity sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA== + dependencies: + "@types/unist" "^3.0.0" + +unist-util-stringify-position@^4.0.0: + version "4.0.0" + resolved "https://registry.npmmirror.com/unist-util-stringify-position/-/unist-util-stringify-position-4.0.0.tgz#449c6e21a880e0855bf5aabadeb3a740314abac2" + integrity sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ== + dependencies: + "@types/unist" "^3.0.0" + +unist-util-visit-parents@^6.0.0: + version "6.0.2" + resolved "https://registry.npmmirror.com/unist-util-visit-parents/-/unist-util-visit-parents-6.0.2.tgz#777df7fb98652ce16b4b7cd999d0a1a40efa3a02" + integrity sha512-goh1s1TBrqSqukSc8wrjwWhL0hiJxgA8m4kFxGlQ+8FYQ3C/m11FcTs4YYem7V664AhHVvgoQLk890Ssdsr2IQ== + dependencies: + "@types/unist" "^3.0.0" + unist-util-is "^6.0.0" + +unist-util-visit@^5.0.0: + version "5.1.0" + resolved "https://registry.npmmirror.com/unist-util-visit/-/unist-util-visit-5.1.0.tgz#9a2a28b0aa76a15e0da70a08a5863a2f060e2468" + integrity sha512-m+vIdyeCOpdr/QeQCu2EzxX/ohgS8KbnPDgFni4dQsfSCtpz8UqDyY5GjRru8PDKuYn7Fq19j1CQ+nJSsGKOzg== + dependencies: + "@types/unist" "^3.0.0" + unist-util-is "^6.0.0" + unist-util-visit-parents "^6.0.0" + +varint@^6.0.0: + version "6.0.0" + resolved "https://registry.npmmirror.com/varint/-/varint-6.0.0.tgz#9881eb0ce8feaea6512439d19ddf84bf551661d0" + integrity sha512-cXEIW6cfr15lFv563k4GuVuW/fiwjknytD37jIOLSdSWuOI6WnO/oKwmP2FQTU2l01LP8/M5TSAJpzUaGe3uWg== + +vfile-message@^4.0.0: + version "4.0.3" + resolved "https://registry.npmmirror.com/vfile-message/-/vfile-message-4.0.3.tgz#87b44dddd7b70f0641c2e3ed0864ba73e2ea8df4" + integrity sha512-QTHzsGd1EhbZs4AsQ20JX1rC3cOlt/IWJruk893DfLRr57lcnOeMaWG4K0JrRta4mIJZKth2Au3mM3u03/JWKw== + dependencies: + "@types/unist" "^3.0.0" + unist-util-stringify-position "^4.0.0" + +vfile@^6.0.0: + version "6.0.3" + resolved "https://registry.npmmirror.com/vfile/-/vfile-6.0.3.tgz#3652ab1c496531852bf55a6bac57af981ebc38ab" + integrity sha512-KzIbH/9tXat2u30jf+smMwFCsno4wHVdNmzFyL+T/L3UGqqk6JKfVqOFOZEpZSHADH1k40ab6NUIXZq422ov3Q== + dependencies: + "@types/unist" "^3.0.0" + vfile-message "^4.0.0" + +viewerjs@^1.11.6, viewerjs@^1.11.7: + version "1.14.0" + resolved "https://registry.npmmirror.com/viewerjs/-/viewerjs-1.14.0.tgz#9c26270f8bf5b1f13770879f95e5d7fa64fc629b" + integrity sha512-eFtTGjK9p1idAiax6yz9JLe5zNlmRfG/YEdGCVYeSD+p5lozmp32D8yGu+AhmYDW+o3CkMO9aDtjxpljtB/Ymg== + +vite@^5.4.1, vite@^5.4.9: + version "5.4.21" + resolved "https://registry.npmmirror.com/vite/-/vite-5.4.21.tgz#84a4f7c5d860b071676d39ba513c0d598fdc7027" + integrity sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw== + dependencies: + esbuild "^0.21.3" + postcss "^8.4.43" + rollup "^4.20.0" + optionalDependencies: + fsevents "~2.3.3" + +vitepress-plugin-group-icons@^1.3.6: + version "1.7.6" + resolved "https://registry.npmmirror.com/vitepress-plugin-group-icons/-/vitepress-plugin-group-icons-1.7.6.tgz#016f6b9b04222ab752dc08c7b1e5df818b96503b" + integrity sha512-fXSpQAYHpFpEsZLKWQAwbRx/+0uRSHmY58YNAQcz0AVirGsM6w22jtSmQhOGLEj8ZKyeRtTJrpx6otUI6wvoaQ== + dependencies: + "@iconify-json/logos" "^1.2.11" + "@iconify-json/vscode-icons" "^1.2.67" + "@iconify/utils" "^3.1.4" + +vitepress-plugin-image-viewer@^1.1.6: + version "1.1.6" + resolved "https://registry.npmmirror.com/vitepress-plugin-image-viewer/-/vitepress-plugin-image-viewer-1.1.6.tgz#b8318d918e9ea7d093f6fb80c4fe7b1e2d746241" + integrity sha512-ZfYy1s/bXju6vGVsE/5gPXT449+KaXZ6ZIPAvcmPM07VmC7Bv57bCvFar2II7C2EjexxfPQsxUh70vSBbe14kQ== + dependencies: + viewerjs "^1.11.6" + +vitepress@1.3.4: + version "1.3.4" + resolved "https://registry.npmmirror.com/vitepress/-/vitepress-1.3.4.tgz#3027f2e2c74e8a72479fd7b40615cf8d3cce2d42" + integrity sha512-I1/F6OW1xl3kW4PaIMC6snxjWgf3qfziq2aqsDoFc/Gt41WbcRv++z8zjw8qGRIJ+I4bUW7ZcKFDHHN/jkH9DQ== + dependencies: + "@docsearch/css" "^3.6.1" + "@docsearch/js" "^3.6.1" + "@shikijs/core" "^1.13.0" + "@shikijs/transformers" "^1.13.0" + "@types/markdown-it" "^14.1.2" + "@vitejs/plugin-vue" "^5.1.2" + "@vue/devtools-api" "^7.3.8" + "@vue/shared" "^3.4.38" + "@vueuse/core" "^11.0.0" + "@vueuse/integrations" "^11.0.0" + focus-trap "^7.5.4" + mark.js "8.11.1" + minisearch "^7.1.0" + shiki "^1.13.0" + vite "^5.4.1" + vue "^3.4.38" + +vue-demi@>=0.14.10, vue-demi@>=0.14.8: + version "0.14.10" + resolved "https://registry.npmmirror.com/vue-demi/-/vue-demi-0.14.10.tgz#afc78de3d6f9e11bf78c55e8510ee12814522f04" + integrity sha512-nMZBOwuzabUO0nLgIcc6rycZEebF6eeUfaiQx9+WSk8e29IbLvPU9feI6tqW4kTo3hvoYAJkMh8n8D0fuISphg== + +vue@^3.4.38: + version "3.5.43" + resolved "https://registry.npmmirror.com/vue/-/vue-3.5.43.tgz#bab91368e7b9aad9b89dd92e866b120081deb094" + integrity sha512-o5qZoksdnjIKvW1srZ3ab7pcDNYAerBjRe54D0LBLfRdCYFrSgBHVXokMas35czQc0//lmx4/tuY4ZNQ+Rf2Ng== + dependencies: + "@vue/compiler-dom" "3.5.43" + "@vue/compiler-sfc" "3.5.43" + "@vue/runtime-dom" "3.5.43" + "@vue/server-renderer" "3.5.43" + "@vue/shared" "3.5.43" + +zwitch@^2.0.4: + version "2.0.4" + resolved "https://registry.npmmirror.com/zwitch/-/zwitch-2.0.4.tgz#c827d4b0acb76fc3e685a4c6ec2902d51070e9d7" + integrity sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A== diff --git a/RXThinkCMF_ThinkPhp6_EleVue/zh/api-spec/dict-api.md b/RXThinkCMF_ThinkPhp6_EleVue/zh/api-spec/dict-api.md new file mode 100644 index 0000000..9157169 --- /dev/null +++ b/RXThinkCMF_ThinkPhp6_EleVue/zh/api-spec/dict-api.md @@ -0,0 +1,237 @@ +# 8.5 字典数据接口 + +:::tip 概述 +字典数据接口分为两组:字典管理(DictController)和字典项管理(DictItemController)。前端通过字典编码获取字典项列表用于下拉框,后台通过管理接口维护字典数据。 +::: + +## 接口总览 + +### 字典管理(DictController) + +| 方法 | URL | 权限码 | 说明 | +|------|-----|--------|------| +| GET | `/api/dict/page` | sys:dict:page | 字典分页列表 | +| GET | `/api/dict/detail/{id}` | sys:dict:detail | 字典详情 | +| POST | `/api/dict/add` | sys:dict:add | 添加字典 | +| PUT | `/api/dict/update` | sys:dict:update | 修改字典 | +| DELETE | `/api/dict/delete/{id}` | sys:dict:delete | 删除字典 | +| DELETE | `/api/dict/batchDelete` | sys:dict:batchDelete | 批量删除字典 | +| GET | `/api/dict/refreshCache` | sys:dict:update | 刷新字典缓存 | + +### 字典项管理(DictItemController) + +| 方法 | URL | 权限码 | 说明 | +|------|-----|--------|------| +| GET | `/api/dict/item/page` | sys:dict:page | 字典项分页列表 | +| GET | `/api/dict/item/detail/{id}` | sys:dict:detail | 字典项详情 | +| POST | `/api/dict/item/add` | sys:dict:add | 添加字典项 | +| PUT | `/api/dict/item/update` | sys:dict:update | 修改字典项 | +| DELETE | `/api/dict/item/delete/{id}` | sys:dict:delete | 删除字典项 | +| DELETE | `/api/dict/item/batchDelete` | sys:dict:batchDelete | 批量删除字典项 | +| GET | `/api/dict/item/getDictItemList/{code}` | — | 按编码获取字典项列表 | + +## 按编码获取字典项(最常用) + +### 接口信息 + +| 项目 | 说明 | +|------|------| +| URL | `GET /api/dict/item/getDictItemList/{code}` | +| 权限 | 无需额外权限(已登录即可) | +| 用途 | 前端下拉框数据源 | + +### 请求示例 + +```bash +GET /api/dict/item/getDictItemList/gender +Authorization: Bearer +``` + +### 成功响应 + +```json +{ + "code": 0, + "ok": true, + "msg": "操作成功", + "data": [ + { "id": 1, "name": "男", "value": "1", "sort": 1, "note": "" }, + { "id": 2, "name": "女", "value": "2", "sort": 2, "note": "" } + ] +} +``` + +### 响应字段 + +| 字段 | 类型 | 说明 | +|------|------|------| +| `id` | number | 字典项 ID | +| `name` | string | 字典项名称(显示标签) | +| `value` | string | 字典项值(存储值) | +| `sort` | number | 排序 | +| `note` | string | 备注 | + +### 常用字典编码 + +| 编码 | 说明 | 字典项 | +|------|------|--------| +| `gender` | 性别 | 1=男, 2=女 | +| `user_status` | 用户状态 | 0=禁用, 1=启用 | +| `article_status` | 文章状态 | 0=草稿, 1=已发布 | +| `tenant_status` | 租户状态 | 0=禁用, 1=启用 | +| `notice_type` | 通知类型 | 1=通知, 2=公告 | +| `example_type` | 案例类型 | 1=基础, 2=进阶, 3=高级 | +| `example_status` | 案例状态 | 0=禁用, 1=启用 | +| `data_scope` | 数据权限 | 1=全部, 2=本部门, 3=仅本人 | + +## 刷新字典缓存 + +| 项目 | 说明 | +|------|------| +| URL | `GET /api/dict/refreshCache` | +| 权限 | `sys:dict:update` | +| 演示环境 | 允许执行(标注 `#[DemoAllow]`) | + +修改字典数据后需调用此接口刷新缓存,否则新数据不会立即生效。 + +```bash +GET /api/dict/refreshCache +Authorization: Bearer +``` + +响应: + +```json +{ + "code": 0, + "ok": true, + "msg": "缓存已刷新", + "data": null +} +``` + +## 前端使用 + +### 获取字典项 + +```typescript +// ui/src/api/common/index.ts +export function getDictItemList(code: string) { + return http.request({ + url: '/dict/item/getDictItemList/' + code, + method: 'GET', + }); +} +``` + +### 下拉框使用 + +```vue + + + +``` + +### 多个字典批量获取 + +```typescript +const [genderOptions, statusOptions] = await Promise.all([ + getDictItemList('gender'), + getDictItemList('user_status'), +]); +``` + +### 封装为通用 Hook + +```typescript +// ui/src/hooks/web/useDict.ts +import { ref, onMounted } from 'vue'; +import { getDictItemList } from '@/api/common/index'; + +export function useDict(code: string) { + const options = ref([]); + const loading = ref(false); + + const load = async () => { + loading.value = true; + try { + options.value = await getDictItemList(code); + } finally { + loading.value = false; + } + }; + + onMounted(() => load()); + + return { options, loading, reload: load }; +} + +// 使用 +const { options: genderOptions } = useDict('gender'); +const { options: statusOptions } = useDict('user_status'); +``` + +### 枚举显示名自动翻译 + +Logic 中配置 `serializeMaps` 后,查询数据自动补全 `{字段名}Text`: + +```php +/** + * 枚举显示名映射 + * + * @var array + */ +protected array $serializeMaps = [ + 'status' => 'example_status', +]; +``` + +```json +// API 响应自动包含 statusText +{ "id": 1, "name": "测试", "status": 1, "statusText": "启用" } +``` + +:::tip 缓存机制 +DictService 采用两级缓存(请求级 + 持久缓存,1小时过期)。修改字典数据后需调用刷新缓存接口,否则新数据不会立即生效。 +::: + +## 后端 DictService 调用 + +```php +use app\service\DictService; + +// 根据值获取名称 +$text = DictService::getText('gender', 1); // '男' + +// 根据名称反向获取值 +$value = DictService::getValue('gender', '男'); // '1' + +// 获取下拉框选项 +$options = DictService::getOptions('gender'); +// [['label'=>'男','value'=>'1'], ['label'=>'女','value'=>'2']] + +// 获取完整字典项(含 id、sort、note) +$items = DictService::getFullItems('gender'); + +// 清除缓存 +DictService::clearCache('gender'); // 清除指定字典 +DictService::clearAllCache(); // 清除所有字典 +``` diff --git a/RXThinkCMF_ThinkPhp6_EleVue/zh/api-spec/error-code.md b/RXThinkCMF_ThinkPhp6_EleVue/zh/api-spec/error-code.md new file mode 100644 index 0000000..159d16f --- /dev/null +++ b/RXThinkCMF_ThinkPhp6_EleVue/zh/api-spec/error-code.md @@ -0,0 +1,78 @@ +# 8.2 错误码对照表 + +:::tip 概述 +所有接口返回统一的 JSON 结构,通过 `code` 字段区分业务状态。前端应根据 `code` 做差异化处理。 +::: + +## 业务错误码 + +| code | ok | 含义 | 使用场景 | 前端处理 | +|------|-----|------|----------|----------| +| `0` | true | 成功 | 所有成功操作 | 正常处理 data | +| `1` | false | 一般失败 | 业务逻辑失败 | 显示 msg 提示 | +| `401` | false | 未授权 | Token 缺失/无效/过期 | 跳转登录页 | +| `403` | false | 禁止访问 | 权限不足/租户异常/演示环境 | 显示无权限提示 | +| `404` | false | 数据不存在 | 查询单条记录为空 | 显示数据不存在 | +| `422` | false | 参数验证失败 | 表单验证不通过 | 显示具体字段错误 | + +## 响应示例 + +> 响应示例详见 [统一响应格式](/zh/api-spec/response.html) + +## 常见错误消息 + +### 认证相关 + +| 消息 | 原因 | 解决方案 | +|------|------|----------| +| `未提供认证令牌` | 请求头缺少 Authorization | 携带 Token | +| `无效的认证令牌` | Token 类型不是 access | 使用 access_token | +| `token已过期` | Token 超过有效期 | 调用刷新接口或重新登录 | +| `用户名或密码错误` | 账号或密码不正确 | 检查输入 | +| `账号已被禁用` | 用户 status != 1 | 联系管理员 | + +### 权限相关 + +| 消息 | 原因 | 解决方案 | +|------|------|----------| +| `无访问权限:xxx` | 用户无该接口权限 | 联系管理员分配权限 | +| `租户已被禁用` | 租户 status != 1 | 联系平台管理员 | +| `租户已过期` | 租户 expire_time 已过 | 续费或联系平台 | +| `演示环境,禁止操作` | 演示模式下写操作被拦截 | 非演示环境操作 | + +### 业务相关 + +| 消息 | 原因 | 解决方案 | +|------|------|----------| +| `xxx已存在` | 唯一性校验失败 | 修改重复字段值 | +| `数据不存在` | 记录已被删除或不存在 | 刷新列表 | +| `请选择要删除的数据` | 批量删除时 ids 为空 | 先选择数据 | +| `该部门下存在子部门,不可删除` | 删除前钩子校验 | 先删除子部门 | + +## 前端错误处理 + +```typescript +// Axios 响应拦截器 +axios.interceptors.response.use(response => { + const { code, msg, data } = response.data; + + if (code === 0) { + return data; // 成功 + } + + if (code === 401) { + // Token 过期,跳转登录 + router.push('/login'); + return; + } + + if (code === 403) { + ElMessage.error(msg || '无权限'); + return; + } + + // 其他错误 + ElMessage.error(msg || '操作失败'); + return Promise.reject(new Error(msg)); +}); +``` \ No newline at end of file diff --git a/RXThinkCMF_ThinkPhp6_EleVue/zh/api-spec/overview.md b/RXThinkCMF_ThinkPhp6_EleVue/zh/api-spec/overview.md new file mode 100644 index 0000000..2acc5f1 --- /dev/null +++ b/RXThinkCMF_ThinkPhp6_EleVue/zh/api-spec/overview.md @@ -0,0 +1,211 @@ +# 8.7 API 接口总览 + +:::tip 概述 +本节按模块分组列出所有 API 接口的概述,详细路由清单见[附录 E](/zh/appendix/api-cheatsheet.html)。 +::: + +## API 设计规范 + +### 命名规范 + +| 规范 | 说明 | 示例 | +|------|------|------| +| URL 风格 | RESTful,小写下划线 | `/api/user/page` | +| 模块名 | 与 Controller 对应 | `user`、`article`、`dict` | +| 操作名 | 标准 CRUD 动词 | `page`、`list`、`detail`、`add`、`update`、`delete`、`batchDelete` | +| 扩展操作 | 业务动词 | `import`、`export`、`resetPwd`、`refreshCache` | + +### HTTP 方法规范 + +| 操作 | HTTP 方法 | URL 示例 | 说明 | +|------|----------|---------|------| +| 分页查询 | GET | `/api/user/page` | 参数在 URL query string | +| 全量列表 | GET | `/api/user/list` | 参数在 URL query string | +| 详情 | GET | `/api/user/detail/{id}` | ID 在 URL 路径 | +| 新增 | POST | `/api/user/add` | 参数在 JSON 请求体 | +| 修改 | PUT | `/api/user/update` | 参数在 JSON 请求体(含 id) | +| 删除 | DELETE | `/api/user/delete/{id}` | ID 在 URL 路径 | +| 批量删除 | DELETE | `/api/user/batchDelete` | ids 在 JSON 请求体 | + +### 统一响应格式 + +```json +{ + "code": 0, // 0=成功,其他=失败 + "ok": true, // 是否成功 + "msg": "操作成功", // 提示信息 + "data": {} // 响应数据 +} +``` + +### 认证方式 + +```text +Authorization: Bearer +``` + +## 公开接口(无需认证) + +| 模块 | 接口 | 方法 | 说明 | +|------|------|------|------| +| 登录 | `/api/login` | POST | 用户登录,返回双令牌 | +| 登出 | `/api/logout` | GET | 用户登出(记录日志) | +| 验证码 | `/api/captcha` | GET | 获取图片验证码 | +| Token | `/api/oauth2/token` | POST | 获取/刷新 Token | +| Token | `/api/oauth2/check/token` | GET | 检查 Token 有效性 | +| Token | `/api/oauth2/remove/{token}` | DELETE | 吊销 Token | + +## 系统管理 + +| 模块 | 主要接口 | 方法 | 说明 | +|------|---------|------|------| +| 首页 | `/api/index/getUserInfo` | GET | 获取当前用户信息 | +| 首页 | `/api/index/getMenus` | GET | 获取菜单树(动态路由) | +| 首页 | `/api/index/updatePwd` | PUT | 修改密码 | +| 用户 | `/api/user/page` | GET | 用户分页列表 | +| 用户 | `/api/user/detail/{id}` | GET | 用户详情 | +| 用户 | `/api/user/add` | POST | 新增用户 | +| 用户 | `/api/user/update` | PUT | 修改用户 | +| 用户 | `/api/user/delete/{id}` | DELETE | 删除用户 | +| 用户 | `/api/user/batchDelete` | DELETE | 批量删除用户 | +| 用户 | `/api/user/resetPwd` | POST | 重置密码 | +| 用户 | `/api/user/import` | POST | Excel 导入用户 | +| 用户 | `/api/user/export` | GET | Excel 导出用户 | +| 角色 | `/api/role/page` | GET | 角色分页列表 | +| 角色 | `/api/role/add` | POST | 新增角色(含菜单权限) | +| 角色 | `/api/role/update` | PUT | 修改角色(含菜单权限) | +| 角色 | `/api/role/delete/{id}` | DELETE | 删除角色 | +| 角色 | `/api/role/menus/{id}` | GET | 获取角色的菜单 ID 列表 | +| 菜单 | `/api/menu/treeList` | GET | 菜单树形列表 | +| 菜单 | `/api/menu/add` | POST | 新增菜单 | +| 菜单 | `/api/menu/update` | PUT | 修改菜单 | +| 菜单 | `/api/menu/delete/{id}` | DELETE | 删除菜单 | +| 部门 | `/api/dept/treeList` | GET | 部门树形列表 | +| 部门 | `/api/dept/add` | POST | 新增部门 | +| 部门 | `/api/dept/update` | PUT | 修改部门 | +| 部门 | `/api/dept/delete/{id}` | DELETE | 删除部门 | +| 岗位 | `/api/position/page` | GET | 岗位分页列表 | +| 岗位 | `/api/position/add` | POST | 新增岗位 | +| 岗位 | `/api/position/update` | PUT | 修改岗位 | +| 岗位 | `/api/position/delete/{id}` | DELETE | 删除岗位 | +| 职级 | `/api/level/page` | GET | 职级分页列表 | +| 职级 | `/api/level/add` | POST | 新增职级 | +| 职级 | `/api/level/update` | PUT | 修改职级 | +| 职级 | `/api/level/delete/{id}` | DELETE | 删除职级 | +| 职级 | `/api/level/import` | POST | Excel 导入职级 | +| 职级 | `/api/level/export` | GET | Excel 导出职级 | +| 租户 | `/api/tenant/page` | GET | 租户分页列表 | +| 租户 | `/api/tenant/detail/{id}` | GET | 租户详情 | +| 租户 | `/api/tenant/add` | POST | 新增租户 | +| 租户 | `/api/tenant/update` | PUT | 修改租户 | +| 租户 | `/api/tenant/delete/{id}` | DELETE | 删除租户 | +| 租户 | `/api/tenant/account` | POST | 创建租户管理员账号 | +| 租户 | `/api/tenant/users/{tenantId}` | GET | 获取租户用户列表 | + +## 数据管理 + +| 模块 | 主要接口 | 方法 | 说明 | +|------|---------|------|------| +| 字典 | `/api/dict/page` | GET | 字典分页列表 | +| 字典 | `/api/dict/add` | POST | 新增字典 | +| 字典 | `/api/dict/update` | PUT | 修改字典 | +| 字典 | `/api/dict/delete/{id}` | DELETE | 删除字典 | +| 字典 | `/api/dict/refreshCache` | GET | 刷新字典缓存 | +| 字典项 | `/api/dict/item/page` | GET | 字典项分页列表 | +| 字典项 | `/api/dict/item/add` | POST | 新增字典项 | +| 字典项 | `/api/dict/item/update` | PUT | 修改字典项 | +| 字典项 | `/api/dict/item/delete/{id}` | DELETE | 删除字典项 | +| 字典项 | `/api/dict/item/getDictItemList/{code}` | GET | 按编码获取字典项列表 | +| 配置 | `/api/config/page` | GET | 配置分页列表 | +| 配置 | `/api/config/add` | POST | 新增配置组 | +| 配置 | `/api/config/update` | PUT | 修改配置组 | +| 配置 | `/api/config/delete/{id}` | DELETE | 删除配置组 | +| 配置项 | `/api/config/item/page` | GET | 配置项分页列表 | +| 配置项 | `/api/config/item/add` | POST | 新增配置项 | +| 配置项 | `/api/config/item/update` | PUT | 修改配置项 | +| 配置项 | `/api/config/item/delete/{id}` | DELETE | 删除配置项 | +| 参数 | `/api/param/page` | GET | 参数分页列表 | +| 参数 | `/api/param/add` | POST | 新增参数 | +| 参数 | `/api/param/update` | PUT | 修改参数 | +| 参数 | `/api/param/delete/{id}` | DELETE | 删除参数 | +| 公告 | `/api/notice/page` | GET | 公告分页列表 | +| 公告 | `/api/notice/add` | POST | 新增公告 | +| 公告 | `/api/notice/update` | PUT | 修改公告 | +| 公告 | `/api/notice/delete/{id}` | DELETE | 删除公告 | +| 城市 | `/api/city/list` | GET | 城市列表 | +| 城市 | `/api/city/add` | POST | 新增城市 | +| 城市 | `/api/city/update` | PUT | 修改城市 | +| 城市 | `/api/city/delete/{id}` | DELETE | 删除城市 | +| 城市 | `/api/city/getCityList/{parentCode}` | GET | 按父编码查询下级城市 | +| 城市 | `/api/city/getParent` | GET | 获取省份列表 | + +## 内容管理 + +| 模块 | 主要接口 | 方法 | 说明 | +|------|---------|------|------| +| 文章 | `/api/article/page` | GET | 文章分页列表 | +| 文章 | `/api/article/detail/{id}` | GET | 文章详情 | +| 文章 | `/api/article/add` | POST | 新增文章 | +| 文章 | `/api/article/update` | PUT | 修改文章 | +| 文章 | `/api/article/delete/{id}` | DELETE | 删除文章 | +| 文章 | `/api/article/batchDelete` | DELETE | 批量删除文章 | +| 分类 | `/api/category/treeList` | GET | 分类树形列表 | +| 分类 | `/api/category/add` | POST | 新增分类 | +| 分类 | `/api/category/update` | PUT | 修改分类 | +| 分类 | `/api/category/delete/{id}` | DELETE | 删除分类 | +| 链接 | `/api/link/page` | GET | 链接分页列表 | +| 链接 | `/api/link/add` | POST | 新增链接 | +| 链接 | `/api/link/update` | PUT | 修改链接 | +| 链接 | `/api/link/delete/{id}` | DELETE | 删除链接 | + +## 监控管理 + +| 模块 | 主要接口 | 方法 | 说明 | +|------|---------|------|------| +| 定时任务 | `/api/job/page` | GET | 任务分页列表 | +| 定时任务 | `/api/job/add` | POST | 新增任务 | +| 定时任务 | `/api/job/update` | PUT | 修改任务 | +| 定时任务 | `/api/job/delete/{id}` | DELETE | 删除任务 | +| 定时任务 | `/api/job/run/{id}` | POST | 手动执行任务 | +| 定时任务 | `/api/job/pause/{id}` | POST | 暂停任务 | +| 定时任务 | `/api/job/resume/{id}` | POST | 恢复任务 | +| 任务日志 | `/api/job/log/page` | GET | 任务日志分页列表 | +| 任务日志 | `/api/job/log/delete/{id}` | DELETE | 删除日志 | +| 任务日志 | `/api/job/log/batchDelete` | DELETE | 批量删除日志 | + +## 日志审计 + +| 模块 | 主要接口 | 方法 | 说明 | +|------|---------|------|------| +| 操作日志 | `/api/oper/log/page` | GET | 操作日志分页列表 | +| 操作日志 | `/api/oper/log/detail/{id}` | GET | 日志详情 | +| 操作日志 | `/api/oper/log/delete/{id}` | DELETE | 删除日志 | +| 操作日志 | `/api/oper/log/batchDelete` | DELETE | 批量删除日志 | +| 登录日志 | `/api/login/log/page` | GET | 登录日志分页列表 | +| 登录日志 | `/api/login/log/detail/{id}` | GET | 日志详情 | +| 登录日志 | `/api/login/log/delete/{id}` | DELETE | 删除日志 | +| 登录日志 | `/api/login/log/batchDelete` | DELETE | 批量删除日志 | + +## 公共接口 + +| 模块 | 接口 | 方法 | 说明 | +|------|------|------|------| +| 文件上传 | `/api/upload/uploadFile` | POST | 上传文件(type=file/image) | +| 代码生成 | `/api/generator/page` | GET | 可生成的表列表 | +| 代码生成 | `/api/generator/preview` | POST | 预览生成代码 | +| 代码生成 | `/api/generator/generate` | POST | 执行代码生成 | + +## 接口统计 + +| 模块 | 接口数量 | 说明 | +|------|---------|------| +| 公开接口 | 6 | 登录/登出/验证码/Token | +| 系统管理 | ~35 | 用户/角色/菜单/部门/岗位/职级/租户 | +| 数据管理 | ~25 | 字典/字典项/配置/配置项/参数/公告/城市 | +| 内容管理 | ~12 | 文章/分类/链接 | +| 监控管理 | ~10 | 定时任务/任务日志 | +| 日志审计 | ~8 | 操作日志/登录日志 | +| 公共接口 | ~4 | 文件上传/代码生成 | +| **合计** | **~100** | | + +详细路由清单(HTTP 方法 + 路径 + 权限码)见[附录 E](/zh/appendix/api-cheatsheet.html)。 diff --git a/RXThinkCMF_ThinkPhp6_EleVue/zh/api-spec/pagination.md b/RXThinkCMF_ThinkPhp6_EleVue/zh/api-spec/pagination.md new file mode 100644 index 0000000..1e3162d --- /dev/null +++ b/RXThinkCMF_ThinkPhp6_EleVue/zh/api-spec/pagination.md @@ -0,0 +1,371 @@ +# 8.3 分页查询规范 + +:::tip 概述 +所有分页接口遵循统一的参数命名和返回结构。分页由 `BaseController::_index()` → `BaseLogic::pageList()` 实现,前端只需传标准参数即可获得分页数据。 +::: + +## 请求参数 + +| 参数 | 类型 | 默认值 | 说明 | +|------|------|--------|------| +| `pageNo` | number | 1 | 页码(最小为 1) | +| `pageSize` | number | 20 | 每页条数(1~100) | +| `orderField` | string | - | 排序字段(camelCase,如 `createTime`) | +| `orderType` | string | `desc` | 排序方向(`asc` / `desc`) | +| `fields` | string | - | 返回字段(逗号分隔,如 `id,username,realname`) | +| 其他参数 | - | - | 按 Logic 中配置的 `pageLikeFields` / `pageEqFields` 自动匹配 | + +### 分页参数边界限制 + +| 参数 | 最小值 | 最大值 | 默认值 | 超出处理 | +|------|--------|--------|--------|---------| +| `pageNo` | 1 | - | 1 | 小于 1 时强制为 1 | +| `pageSize` | 1 | 100 | 20 | 超出范围时限制在 1~100 | + +## 请求示例 + +```bash +# 基本分页 +GET /api/example/page?pageNo=1&pageSize=10 + +# 带模糊搜索(LIKE) +GET /api/example/page?pageNo=1&pageSize=10&name=测试 + +# 带精确过滤(=) +GET /api/example/page?pageNo=1&pageSize=10&status=1&type=2 + +# 模糊 + 精确组合 +GET /api/example/page?pageNo=1&pageSize=10&name=测试&status=1 + +# 自定义排序 +GET /api/example/page?pageNo=1&pageSize=10&orderField=sort&orderType=asc + +# 指定返回字段 +GET /api/example/page?pageNo=1&pageSize=10&fields=id,name,status + +# 多条件组合 +GET /api/user/page?pageNo=1&pageSize=20&username=admin&status=1&deptId=3&orderField=id&orderType=desc +``` + +## 返回结构 + +```json +{ + "code": 0, + "ok": true, + "msg": "操作成功", + "data": { + "records": [ + { "id": 1, "name": "测试", "status": 1, "statusText": "启用", "createTime": "2026-01-01 00:00:00" }, + { "id": 2, "name": "示例", "status": 0, "statusText": "禁用", "createTime": "2026-01-02 00:00:00" } + ], + "total": 100, + "size": 10, + "current": 1, + "pages": 10 + } +} +``` + +### 返回字段 + +| 字段 | 类型 | 说明 | +|------|------|------| +| `records` | array | 当前页数据列表 | +| `total` | number | 总记录数 | +| `size` | number | 每页条数 | +| `current` | number | 当前页码 | +| `pages` | number | 总页数(`ceil(total / size)`) | + +## 查询条件匹配 + +### pageLikeFields — 模糊匹配(LIKE) + +在 Logic 中配置的字段,前端传参时自动使用 `LIKE '%值%'` 查询: + +```php +class ExampleLogic extends BaseLogic +{ + protected array $pageLikeFields = ['name', 'title']; +} +``` + +```bash +# name 字段自动使用 LIKE 查询 +GET /api/example/page?name=测试 +# 生成: WHERE name LIKE '%测试%' +``` + +### pageEqFields — 精确匹配(=) + +在 Logic 中配置的字段,前端传参时自动使用 `= 值` 查询: + +```php +class ExampleLogic extends BaseLogic +{ + protected array $pageEqFields = ['status', 'type', 'category_id']; +} +``` + +```bash +# status 和 type 字段自动使用 = 查询 +GET /api/example/page?status=1&type=2 +# 生成: WHERE status = 1 AND type = 2 +``` + +### 条件组合 + +```bash +# 模糊 + 精确 + 排序 + 分页 +GET /api/example/page?pageNo=1&pageSize=20&name=测试&status=1&orderField=sort&orderType=asc + +# 生成 SQL: +# SELECT * FROM think_example +# WHERE is_delete = 0 +# AND name LIKE '%测试%' +# AND status = 1 +# ORDER BY sort ASC +# LIMIT 20 OFFSET 0 +``` + +## 排序 + +### 前端传参排序 + +```bash +# 按创建时间倒序 +GET /api/example/page?orderField=createTime&orderType=desc + +# 按排序字段正序 +GET /api/example/page?orderField=sort&orderType=asc +``` + +### Logic 默认排序 + +```php +class ExampleLogic extends BaseLogic +{ + /** + * 单字段排序 + * + * @var array + */ + protected array $pageOrderBy = ['field' => 'sort', 'type' => 'asc']; + + /** + * 多字段排序 + * + * @var array + */ + protected array $pageOrderBy = [ + ['field' => 'sort', 'type' => 'asc'], + ['field' => 'id', 'type' => 'desc'], + ]; +} +``` + +**优先级:** 前端传参 > Logic 默认配置 + +## 字段过滤 + +### fields 参数 + +前端可通过 `fields` 参数指定只需要的字段,减少数据传输量: + +```bash +# 只返回 id、name、status 三个字段 +GET /api/example/page?fields=id,name,status +``` + +**响应:** + +```json +{ + "records": [ + { "id": 1, "name": "测试", "status": 1 }, + { "id": 2, "name": "示例", "status": 0 } + ] +} +``` + +**注意:** `id` 字段始终保留,即使未在 `fields` 中指定。 + +## 参数转换 + +前端使用 camelCase,后端自动转换为 snake_case: + +| 前端参数 | 转换后 | 说明 | +|---------|--------|------| +| `pageNo` | `pageNo` | 不转换(排除项) | +| `pageSize` | `pageSize` | 不转换(排除项) | +| `orderField` | `orderField` | 不转换(排除项) | +| `orderType` | `orderType` | 不转换(排除项) | +| `fields` | `fields` | 不转换(排除项) | +| `categoryId` | `category_id` | 自动转换 | +| `createTime` | `create_time` | 自动转换 | +| `userName` | `user_name` | 自动转换 | + +### 排序字段值也自动转换 + +```bash +# 前端传入 camelCase +GET /api/example/page?orderField=createTime + +# 后端自动转换为 snake_case +ORDER BY create_time DESC +``` + +## Controller 层实现 + +### 使用 _index 快捷方法(推荐) + +```php +class ExampleController extends BaseController +{ + protected ExampleLogic $logic; + + protected function initialize(): void + { + $this->logic = new ExampleLogic(); + } + + #[Permission('sys:example:page', '案例分页')] + public function page(): Json + { + return parent::_index($this->logic); + } +} +``` + +### 带额外查询条件 + +```php +#[Permission('sys:user:page', '用户分页')] +public function page(): Json +{ + // 额外条件:只查启用状态的用户 + return parent::_index($this->logic, ['status' => 1]); +} +``` + +### 带关联查询 + +```php +#[Permission('sys:article:page', '文章分页')] +public function page(): Json +{ + // 预加载分类关联 + return parent::_index($this->logic, [], ['category']); +} +``` + +## Logic 层实现 + +```php +class ExampleLogic extends BaseLogic +{ + /** + * 关联的模型类 + * + * @var string + */ + protected string $modelClass = Example::class; + + /** + * LIKE 模糊匹配字段 + * + * @var array + */ + protected array $pageLikeFields = ['name', 'title']; + + /** + * 精确匹配字段 + * + * @var array + */ + protected array $pageEqFields = ['status', 'type', 'category_id']; + + /** + * 默认排序规则 + * + * @var array + */ + protected array $pageOrderBy = ['field' => 'sort', 'type' => 'asc']; + + /** + * 枚举显示名映射 + * + * @var array + */ + protected array $serializeMaps = ['status' => 'example_status']; +} +``` + +## 前端集成 + +### API 函数 + +```typescript +// src/api/tool/example.ts +export function getExamplePage(params?) { + return http.request({ url: '/example/page', method: 'GET', params }); +} +``` + +### 组件使用 + +```vue + +``` + +## 常见问题 + +### 问题 1:搜索条件不生效 + +**原因:** 字段名未在 `pageLikeFields` 或 `pageEqFields` 中配置。 + +**解决:** 在 Logic 中添加对应字段配置。 + +### 问题 2:排序不生效 + +**原因:** `orderField` 传了数据库不存在的字段名。 + +**解决:** 确保 `orderField` 传的是数据库字段名的 camelCase 形式。 + +### 问题 3:分页数据重复 + +**原因:** 排序字段值相同,导致数据库返回顺序不确定。 + +**解决:** 排序字段加上唯一字段(如 id)作为第二排序条件。 diff --git a/RXThinkCMF_ThinkPhp6_EleVue/zh/api-spec/response.md b/RXThinkCMF_ThinkPhp6_EleVue/zh/api-spec/response.md new file mode 100644 index 0000000..0281815 --- /dev/null +++ b/RXThinkCMF_ThinkPhp6_EleVue/zh/api-spec/response.md @@ -0,0 +1,328 @@ +# 8.1 统一响应格式 + +:::tip 概述 +所有接口返回统一的 JSON 结构,前端可据此判断请求结果。响应由 `response\Result` 类构造,保证全系统格式一致。 +::: + +## 响应结构 + +```typescript +interface ApiResponse { + code: number; // 状态码:0=成功,其他=失败 + ok: boolean; // 是否成功 + msg: string; // 提示信息 + data: any; // 响应数据 +} +``` + +## 状态码定义 + +| code | 含义 | ok | 说明 | +|------|------|-----|------| +| `0` | 成功 | `true` | 业务成功 | +| `1` | 业务失败 | `false` | 通用业务错误(参数错误、业务校验失败等) | +| `401` | 未授权 | `false` | Token 缺失/无效/过期 | +| `403` | 禁止访问 | `false` | 权限不足 | +| `404` | 数据不存在 | `false` | 查询的记录不存在 | +| `422` | 参数验证失败 | `false` | Validate 校验不通过 | + +## 成功响应 + +### 普通成功 + +```json +{ + "code": 0, + "ok": true, + "msg": "操作成功", + "data": { + "id": 1, + "username": "admin", + "realname": "管理员", + "createTime": "2026-01-01 00:00:00" + } +} +``` + +### 带自定义消息 + +```json +{ + "code": 0, + "ok": true, + "msg": "添加成功", + "data": { "id": 42 } +} +``` + +### data 为 null + +```json +{ + "code": 0, + "ok": true, + "msg": "修改成功", + "data": null +} +``` + +### data 为数组 + +```json +{ + "code": 0, + "ok": true, + "msg": "操作成功", + "data": [ + { "id": 1, "name": "选项一" }, + { "id": 2, "name": "选项二" } + ] +} +``` + +## 分页响应 + +```json +{ + "code": 0, + "ok": true, + "msg": "操作成功", + "data": { + "records": [ + { "id": 1, "username": "admin", "createTime": "2026-01-01 00:00:00" }, + { "id": 2, "username": "user1", "createTime": "2026-01-02 00:00:00" } + ], + "total": 100, + "size": 20, + "current": 1, + "pages": 5 + } +} +``` + +### 分页参数 + +| 字段 | 类型 | 说明 | +|------|------|------| +| `records` | array | 当前页数据列表 | +| `total` | number | 总记录数 | +| `size` | number | 每页条数 | +| `current` | number | 当前页码 | +| `pages` | number | 总页数(`ceil(total / size)`) | + +## 失败响应 + +### 通用业务失败 + +```json +{ + "code": 1, + "ok": false, + "msg": "用户名已存在", + "data": null +} +``` + +### 参数验证失败 + +```json +{ + "code": 1, + "ok": false, + "msg": "参数验证失败", + "data": "用户名不能为空" +} +``` + +### 批量验证失败 + +```json +{ + "code": 1, + "ok": false, + "msg": "参数验证失败", + "data": [ + "用户名不能为空", + "姓名不能为空" + ] +} +``` + +### 数据不存在 + +```json +{ + "code": 1, + "ok": false, + "msg": "数据不存在", + "data": null +} +``` + +### 未授权(Token 问题) + +```json +{ + "code": 401, + "ok": false, + "msg": "token已过期", + "data": null +} +``` + +### 权限不足 + +```json +{ + "code": 403, + "ok": false, + "msg": "无访问权限", + "data": null +} +``` + +## 字段说明 + +| 字段 | 类型 | 说明 | +|------|------|------| +| `code` | number | 状态码,0=成功 | +| `ok` | boolean | 是否成功 | +| `msg` | string | 提示信息 | +| `data` | any | 响应数据,失败时通常为 null 或错误详情 | + +### 分页字段 + +| 字段 | 类型 | 说明 | +|------|------|------| +| `data.records` | array | 分页数据列表 | +| `data.total` | number | 总记录数 | +| `data.size` | number | 每页条数 | +| `data.current` | number | 当前页码 | +| `data.pages` | number | 总页数 | + +## 后端 Result 类 + +**文件:** `extend/response/Result.php` + +### 方法清单 + +| 方法 | 说明 | code | ok | +|------|------|------|-----| +| `success($data, $msg)` | 成功响应 | 0 | true | +| `page($records, $total, $current, $size)` | 分页响应 | 0 | true | +| `fail($msg, $code, $data)` | 失败响应 | 1 | false | +| `unauthorized($msg)` | 未授权 | 401 | false | +| `forbidden($msg)` | 禁止访问 | 403 | false | +| `notFound($msg)` | 数据不存在 | 404 | false | +| `validateError($errors)` | 验证失败 | 422 | false | + +### 使用示例 + +```php +// Controller 中通过 BaseController 调用 +$this->success($data, '操作成功'); +$this->success(['id' => $id], '添加成功'); +$this->fail('用户名已存在'); +$this->fail('缺少id参数', 1); + +// 直接调用 Result 类 +\response\Result::success($data); +\response\Result::fail('错误信息'); +\response\Result::unauthorized('未授权'); +\response\Result::forbidden('无权限'); +\response\Result::notFound('数据不存在'); +\response\Result::validateError('用户名不能为空'); +``` + +## 驼峰自动转换 + +响应数据自动从 snake_case 转为 camelCase: + +| 数据库字段 | 响应字段 | +|-----------|---------| +| `create_time` | `createTime` | +| `user_name` | `userName` | +| `is_delete` | `isDelete` | +| `dept_id` | `deptId` | + +可通过 `config/api.php` 的 `camel_snake_convert` 配置关闭。 + +## 前端响应处理 + +### Axios 响应拦截器 + +```typescript +// src/utils/http/axios/index.ts + +axios.interceptors.response.use( + response => { + const { code, data, msg } = response.data; + + // 成功 + if (code === 0) { + return data; + } + + // Token 过期 + if (code === 401) { + localStorage.clear(); + router.push('/login'); + return Promise.reject(new Error(msg)); + } + + // 权限不足 + if (code === 403) { + ElMessage.error(msg || '无访问权限'); + return Promise.reject(new Error(msg)); + } + + // 其他业务错误 + ElMessage.error(msg || '操作失败'); + return Promise.reject(new Error(msg)); + }, + error => { + ElMessage.error('网络错误'); + return Promise.reject(error); + } +); +``` + +### 处理分页数据 + +```typescript +const fetchData = async () => { + const res = await getUserPage({ pageNo: 1, pageSize: 20 }); + // res 已经是 data 部分(拦截器已解包) + tableData.value = res.records; + total.value = res.total; +}; +``` + +### 处理导入结果 + +```typescript +const handleImport = async (file) => { + const res = await importUsers(file); + // res = { count: 8, errors: [{ row: 3, reason: "..." }] } + if (res.errors.length === 0) { + ElMessage.success(`成功导入 ${res.count} 条`); + } else { + ElMessage.warning(`成功 ${res.count} 条,失败 ${res.errors.length} 条`); + } +}; +``` + +## 常见响应场景汇总 + +| 场景 | code | msg | data | +|------|------|-----|------| +| 查询成功 | 0 | 操作成功 | 数据对象/数组 | +| 分页成功 | 0 | 操作成功 | {records, total, size, current, pages} | +| 新增成功 | 0 | 添加成功 | {id: 42} | +| 修改成功 | 0 | 修改成功 | null | +| 删除成功 | 0 | 删除成功 | null | +| 业务失败 | 1 | 具体错误信息 | null | +| 参数验证失败 | 1 | 参数验证失败 | 错误字符串或数组 | +| Token 过期 | 401 | token已过期 | null | +| 无权限 | 403 | 无访问权限 | null | +| 数据不存在 | 404 | 数据不存在 | null | diff --git a/RXThinkCMF_ThinkPhp6_EleVue/zh/api-spec/token.md b/RXThinkCMF_ThinkPhp6_EleVue/zh/api-spec/token.md new file mode 100644 index 0000000..5b42252 --- /dev/null +++ b/RXThinkCMF_ThinkPhp6_EleVue/zh/api-spec/token.md @@ -0,0 +1,531 @@ +# 8.6 Token 使用说明 + +:::tip 概述 +系统采用 JWT 双令牌认证,access_token 用于接口认证(2小时),refresh_token 用于刷新令牌(7天)。所有需认证的接口必须在请求头中携带 access_token。 +::: + +## Token 完整生命周期 + +### 登录获取 Token + +```text +用户输入账号密码 + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ 前端:POST /api/login │ +│ { username, password, code, key } │ +└─────────────────────────┬───────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ 后端:LoginController → LoginLogic → JwtService │ +│ │ +│ 1. CaptchaService::check() 验证码校验 │ +│ 2. User::where('username') 查询用户 │ +│ 3. PasswordService::verify() 密码校验 │ +│ 4. Jwt::createAccessToken() 签发 access_token(2小时) │ +│ Jwt::createRefreshToken() 签发 refresh_token(7天) │ +└─────────────────────────┬───────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ 响应: │ +│ { │ +│ "accessToken": "eyJ...", ← 用于接口认证 │ +│ "refreshToken": "eyJ...", ← 用于刷新令牌 │ +│ "tokenType": "Bearer", │ +│ "expiresIn": 7200, ← 2小时 = 7200秒 │ +│ "user": { "id":1, "username":"admin" } │ +│ } │ +└─────────────────────────┬───────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ 前端:存储 Token │ +│ │ +│ localStorage.setItem('access_token', accessToken) │ +│ localStorage.setItem('refresh_token', refreshToken) │ +└─────────────────────────────────────────────────────────────┘ +``` + +### 使用 Token 请求 API + +```text +用户操作(如点击"用户列表") + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ 前端:请求拦截器 │ +│ │ +│ const token = localStorage.getItem('access_token') │ +│ config.headers['Authorization'] = `Bearer ${token}` │ +└─────────────────────────┬───────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ GET /api/user/page?pageNo=1&pageSize=10 │ +│ Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9 │ +└─────────────────────────┬───────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ 后端:AuthMiddleware │ +│ │ +│ 1. Jwt::getTokenFromHeader() 从请求头提取 Token │ +│ │ 空 → 返回 401 "未提供认证令牌" │ +│ ▼ │ +│ 2. Jwt::decode($token) 解码并校验签名+有效期 │ +│ │ 过期 → 返回 401 "token已过期" │ +│ │ 签名无效 → 返回 401 "token签名无效" │ +│ ▼ │ +│ 3. $decoded->type === 'access' 校验 Token 类型 │ +│ │ 非 access → 返回 401 "无效的认证令牌" │ +│ ▼ │ +│ 4. $request->userInfo = $decoded 注入用户信息 │ +│ ▼ │ +│ 5. checkPermission() 权限校验 │ +│ │ 无权限 → 返回 403 "无访问权限" │ +│ ▼ │ +│ 6. 放行 → Controller → Logic → Model → 响应 │ +└─────────────────────────┬───────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ 前端:响应拦截器 │ +│ │ +│ code === 0 → 返回 data(成功) │ +│ code === 401 → Token 过期,尝试刷新 │ +│ code === 403 → 提示"无访问权限" │ +│ 其他 → 提示 msg │ +└─────────────────────────────────────────────────────────────┘ +``` + +### Token 过期自动刷新 + +```text +前端请求 API → 后端返回 code=401 "token已过期" + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ 前端:响应拦截器检测到 401 │ +│ │ +│ if (code === 401 && !config._retry) { │ +│ // 尝试用 refresh_token 刷新 │ +│ } │ +└─────────────────────────┬───────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ 前端:POST /api/oauth2/token │ +│ { grant_type: "refresh_token", refresh_token: "eyJ..." } │ +└─────────────────────────┬───────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ 后端:JwtService::refreshToken() │ +│ │ +│ 1. Jwt::decode(refreshToken) 解码 refresh_token │ +│ │ 无效/过期 → 返回错误 │ +│ ▼ │ +│ 2. $decoded->type === 'refresh' 校验必须为 refresh 类型 │ +│ │ 非 refresh → 返回错误 │ +│ ▼ │ +│ 3. User::find($decoded->uid) 校验用户仍存在且状态正常 │ +│ │ 不存在/禁用 → 返回错误 │ +│ ▼ │ +│ 4. 签发新 access_token + 新 refresh_token │ +└─────────────────────────┬───────────────────────────────────┘ + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ 前端:更新存储 + 重试原请求 │ +│ │ +│ localStorage.setItem('access_token', newAccessToken) │ +│ localStorage.setItem('refresh_token', newRefreshToken) │ +│ │ +│ // 用新 Token 重试原失败请求 │ +│ config.headers['Authorization'] = `Bearer ${newAccessToken}`│ +│ return axios(config) │ +└─────────────────────────────────────────────────────────────┘ +``` + +### Token 刷新失败(重新登录) + +```text +前端请求 API → 后端返回 code=401 + │ + ▼ +尝试用 refresh_token 刷新 + │ + ▼ +刷新失败(refresh_token 也过期/无效) + │ + ▼ +┌─────────────────────────────────────────────────────────────┐ +│ 前端:清除登录态,跳转登录页 │ +│ │ +│ localStorage.removeItem('access_token') │ +│ localStorage.removeItem('refresh_token') │ +│ window.location.href = '/login' │ +└─────────────────────────────────────────────────────────────┘ +``` + +### 两种 Token 的区别 + +```text +┌─────────────────────────────────────────────────────────────┐ +│ access_token │ +│ │ +│ 用途:接口认证(每个 API 请求携带) │ +│ 有效期:2 小时(7200 秒) │ +│ payload.type:access │ +│ 传递方式:Authorization: Bearer │ +│ 校验方:AuthMiddleware │ +│ 特点:短期有效,泄露影响范围有限 │ +├─────────────────────────────────────────────────────────────┤ +│ refresh_token │ +│ │ +│ 用途:刷新令牌(仅在 access_token 过期时使用) │ +│ 有效期:7 天(604800 秒) │ +│ payload.type:refresh │ +│ 传递方式:POST /api/oauth2/token 请求体 │ +│ 校验方:JwtService::refreshToken() │ +│ 特点:长期有效,仅用于换取新令牌,不用于接口认证 │ +└─────────────────────────────────────────────────────────────┘ +``` + +## 请求头格式 + +```text +Authorization: Bearer +``` + +### 示例 + +```bash +# curl +curl http://localhost:8000/api/user/page \ + -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..." + +# curl(带分页参数) +curl "http://localhost:8000/api/user/page?pageNo=1&pageSize=10" \ + -H "Authorization: Bearer eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..." +``` + +### 请求头配置 + +| 配置项 | 默认值 | 说明 | +|--------|--------|------| +| `jwt.header_name` | `Authorization` | 请求头名称 | +| `jwt.header_prefix` | `Bearer ` | 请求头前缀(注意有空格) | + +## Token 获取 + +### 登录获取 + +```bash +POST /api/login +Content-Type: application/json + +{ + "username": "admin", + "password": "123456", + "code": "a3Bx", + "key": "captcha_key_xxx" +} +``` + +**成功响应:** + +```json +{ + "code": 0, + "ok": true, + "msg": "登录成功", + "data": { + "accessToken": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...", + "refreshToken": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9...", + "tokenType": "Bearer", + "expiresIn": 7200, + "user": { + "id": 1, + "username": "admin", + "realname": "管理员", + "avatar": "/uploads/user/avatar/avatar.jpg" + } + } +} +``` + +### OAuth2 密码模式获取 + +```bash +POST /api/oauth2/token +Content-Type: application/x-www-form-urlencoded + +grant_type=password&username=admin&password=123456 +``` + +### 响应字段 + +| 字段 | 类型 | 说明 | +|------|------|------| +| `accessToken` | string | 访问令牌(用于接口认证) | +| `refreshToken` | string | 刷新令牌(用于换取新令牌) | +| `tokenType` | string | 令牌类型(固定 `Bearer`) | +| `expiresIn` | number | access_token 有效期(秒,默认 7200) | +| `user` | object | 用户基础信息(id、username、realname、avatar) | + +## Token 刷新 + +access_token 过期后(响应 code=401),使用 refresh_token 换取新令牌对: + +### 接口信息 + +| 项目 | 说明 | +|------|------| +| URL | `POST /api/oauth2/token` | +| Content-Type | `application/json` 或 `application/x-www-form-urlencoded` | + +### 请求示例 + +```bash +# JSON 格式 +POST /api/oauth2/token +Content-Type: application/json + +{ + "grant_type": "refresh_token", + "refresh_token": "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9..." +} + +# 表单格式 +POST /api/oauth2/token +Content-Type: application/x-www-form-urlencoded + +grant_type=refresh_token&refresh_token=eyJ0eXAiOi... +``` + +### 成功响应 + +```json +{ + "code": 0, + "ok": true, + "msg": "success", + "data": { + "accessToken": "eyJ...(新access_token)", + "refreshToken": "eyJ...(新refresh_token)", + "tokenType": "Bearer", + "expiresIn": 7200 + } +} +``` + +### 失败响应 + +| 场景 | code | msg | +|------|------|-----| +| 缺少 refresh_token | 1 | 缺少refresh_token参数 | +| refresh_token 无效/过期 | 1 | 无效的refresh_token | +| 用户不存在/禁用 | 1 | 用户不存在或已被禁用 | + +## Token 有效性检查 + +### 接口信息 + +| 项目 | 说明 | +|------|------| +| URL | `GET /api/oauth2/check/token` | +| 权限 | 无需认证 | + +### 请求示例 + +```bash +# 通过请求头传递 +GET /api/oauth2/check/token +Authorization: Bearer + +# 通过 URL 参数传递 +GET /api/oauth2/check/token?token= +``` + +### 成功响应(有效) + +```json +{ + "code": 0, + "ok": true, + "msg": "操作成功", + "data": { + "valid": true, + "uid": 1, + "exp": 1727007200 + } +} +``` + +### 失败响应(无效) + +```json +{ + "code": 0, + "ok": true, + "msg": "操作成功", + "data": { + "valid": false, + "msg": "token已过期" + } +} +``` + +## Token 吊销 + +| 项目 | 说明 | +|------|------| +| URL | `DELETE /api/oauth2/remove/{token}` | + +```bash +DELETE /api/oauth2/remove/eyJ0eXAiOi... +Authorization: Bearer +``` + +:::warning 简化实现 +当前 Token 吊销为简化实现(直接返回 true),JWT 本身无状态,服务端无法直接使其失效。生产环境建议结合 Redis 黑名单机制实现真正的吊销。 +::: + +## URL 参数传递(SSO 场景) + +路由守卫支持通过 URL 参数传递 Token,适用于 SSO 单点登录场景: + +```html +http://localhost:8001/?token= +``` + +**前端处理逻辑:** + +```text +1. 路由守卫检测 URL 中的 token 参数 +2. 将 token 存入 localStorage +3. 清除 URL 中的 token 参数(防止泄露) +4. 后续请求自动携带该 token +``` + +## 前端存储与使用 + +### 存储 + +```typescript +// 登录后存储 +localStorage.setItem('access_token', data.accessToken); +localStorage.setItem('refresh_token', data.refreshToken); + +// 登出时清除 +localStorage.removeItem('access_token'); +localStorage.removeItem('refresh_token'); +``` + +### 请求拦截器(自动携带) + +```typescript +// src/utils/http/axios/index.ts +axios.interceptors.request.use(config => { + const token = localStorage.getItem('access_token'); + if (token) { + config.headers['Authorization'] = `Bearer ${token}`; + } + return config; +}); +``` + +### 响应拦截器(自动刷新) + +```typescript +axios.interceptors.response.use( + response => response, + async error => { + const { config, response } = error; + + if (response?.status === 401 && !config._retry) { + config._retry = true; + + const refreshToken = localStorage.getItem('refresh_token'); + if (refreshToken) { + try { + // 尝试刷新 Token + const res = await refreshTokenApi(refreshToken); + localStorage.setItem('access_token', res.data.accessToken); + localStorage.setItem('refresh_token', res.data.refreshToken); + + // 用新 Token 重试原请求 + config.headers['Authorization'] = `Bearer ${res.data.accessToken}`; + return axios(config); + } catch (e) { + // 刷新失败,跳转登录 + localStorage.clear(); + window.location.href = '/login'; + } + } + } + + return Promise.reject(error); + } +); +``` + +## Token Payload 结构 + +### access_token + +```json +{ + "uid": 1, + "username": "admin", + "type": "access", + "iss": "rxthinkcmf", + "iat": 1727000000, + "exp": 1727007200 +} +``` + +### refresh_token + +```json +{ + "uid": 1, + "username": "admin", + "type": "refresh", + "iss": "rxthinkcmf", + "iat": 1727000000, + "exp": 1727604800 +} +``` + +## 错误响应汇总 + +| 场景 | HTTP 状态 | code | msg | +|------|----------|------|-----| +| 未提供 Token | 200 | 401 | 未提供认证令牌 | +| Token 过期 | 200 | 401 | token已过期 | +| Token 签名无效 | 200 | 401 | token签名无效 | +| Token 类型错误 | 200 | 401 | 无效的认证令牌 | +| 无权限 | 200 | 403 | 无访问权限 | + +## 配置项 + +| 配置键 | 环境变量 | 默认值 | 说明 | +|--------|---------|--------|------| +| `jwt.secret` | `JWT.SECRET` | 内置默认密钥 | JWT 签名密钥,**生产环境务必修改** | +| `jwt.access_ttl` | `JWT.ACCESS_TTL` | `7200` | access_token 有效期(秒) | +| `jwt.refresh_ttl` | `JWT.REFRESH_TTL` | `604800` | refresh_token 有效期(秒) | +| `jwt.issuer` | — | `rxthinkcmf` | 签发者标识 | +| `jwt.header_name` | — | `Authorization` | 请求头名称 | +| `jwt.header_prefix` | — | `Bearer ` | 请求头前缀 | + +:::warning 生产环境安全 +生产环境务必通过 `.env` 设置 `JWT.SECRET` 为强随机密钥: +```bash +php -r "echo bin2hex(random_bytes(32));" +``` +::: diff --git a/RXThinkCMF_ThinkPhp6_EleVue/zh/api-spec/upload.md b/RXThinkCMF_ThinkPhp6_EleVue/zh/api-spec/upload.md new file mode 100644 index 0000000..f314435 --- /dev/null +++ b/RXThinkCMF_ThinkPhp6_EleVue/zh/api-spec/upload.md @@ -0,0 +1,293 @@ +# 8.4 文件上传接口 + +:::tip 概述 +文件上传接口将文件保存到临时目录,返回文件 URL。前端拿到 URL 后提交表单时传给后端,BaseLogic 在 add/update 时自动迁移到正式目录。通过 `type` 参数可区分上传类型(文件/图片)。 +::: + +## 接口信息 + +| 项目 | 说明 | +|------|------| +| URL | `POST /api/upload/uploadFile` | +| Content-Type | `multipart/form-data` | +| 权限 | 无需认证(公开接口) | + +## 请求参数 + +| 参数 | 类型 | 必填 | 默认值 | 说明 | +|------|------|------|--------|------| +| `file` | File | 是 | - | 文件二进制数据 | +| `type` | string | 否 | `file` | 上传类型:`file`(所有合法文件)/ `image`(仅图片) | + +### 成功响应 + +```json +{ + "code": 0, + "ok": true, + "msg": "上传成功", + "data": { + "fileUrl": "https://cdn.example.com/temp/20260920/a1b2c3d4e5f6.jpg", + "filePath": "temp/20260920/a1b2c3d4e5f6.jpg", + "originalName": "photo.jpg", + "fileSize": 102400, + "fileExtension": "jpg", + "fileType": "image/jpeg", + "fileName": "a1b2c3d4e5f6.jpg" + } +} +``` + +### 响应字段 + +| 字段 | 类型 | 说明 | 示例 | +|------|------|------|------| +| `fileUrl` | string | 完整 URL(含域名) | `https://cdn.example.com/temp/...` | +| `filePath` | string | 相对路径 | `temp/20260920/a1b2c3d4e5f6.jpg` | +| `originalName` | string | 原始文件名 | `photo.jpg` | +| `fileSize` | number | 文件大小(字节) | `102400` | +| `fileExtension` | string | 扩展名 | `jpg` | +| `fileType` | string | MIME 类型 | `image/jpeg` | +| `fileName` | string | 服务器文件名(随机) | `a1b2c3d4e5f6.jpg` | + +### type 参数说明 + +| type 值 | 说明 | 允许的后缀 | 适用场景 | +|---------|------|-----------|---------| +| `file`(默认) | 所有合法文件 | 由 `config/file.php` 的 `file_ext` 控制 | 文档、压缩包等 | +| `image` | 仅图片 | 由 `config/file.php` 的 `image_ext` 控制 | 头像、封面等 | + +**file_ext 默认值:** + +```text +gif, jpg, jpeg, png, bmp, webp, svg, +doc, docx, xls, xlsx, ppt, pptx, +pdf, zip, rar, txt, csv, +mp3, mp4, sql, js, css +``` + +**image_ext 默认值:** + +```text +gif, jpg, jpeg, png, bmp, webp, svg +``` + +## 失败响应 + +```json +{ + "code": 1, + "ok": false, + "msg": "不允许的文件类型: .exe", + "data": null +} +``` + +## 常见错误 + +| 错误信息 | 原因 | 解决方案 | +|---------|------|----------| +| `未接收到上传文件` | 表单字段名不是 `file` | 检查 FormData 的 key 是否为 `file` | +| `不允许的文件类型: .xxx` | 后缀不在白名单 | 检查 `config/file.php` 的 `image_ext`/`file_ext` | +| `文件大小超过限制` | 超过 `max_size` | 调整 `.env` 中 `FILE.MAX_SIZE` 配置 | +| `图片文件内容无效` | 图片损坏或非图片伪装 | 重新选择文件 | +| `文件上传失败` | PHP 上传错误 | 检查 `php.ini` 的 `upload_max_filesize` | + +## 前端使用 + +### Element Plus Upload 组件 + +```vue + + + +``` + +### 手动上传 + +```typescript +// 上传文件 +const formData = new FormData(); +formData.append('file', file); + +const res = await http.request({ + url: '/upload/uploadFile', + method: 'POST', + data: formData, + headers: { 'Content-Type': 'multipart/form-data' }, +}); +// res.fileUrl = "https://cdn.example.com/temp/20260920/abc.jpg" + +// 上传图片(指定 type=image) +const res2 = await http.request({ + url: '/upload/uploadFile?type=image', + method: 'POST', + data: formData, + headers: { 'Content-Type': 'multipart/form-data' }, +}); +``` + +### 提交表单时传 URL + +```typescript +// 前端上传后拿到 fileUrl,提交表单时传给后端 +await exampleAdd({ + name: '测试', + avatar: 'https://cdn.example.com/temp/20260920/abc.jpg', +}); + +// 后端 BaseLogic::add() 自动: +// 1. 检测到 avatar 是临时文件 URL +// 2. 调用 save_file() 迁移到正式目录 +// 3. 去掉域名,只存相对路径入库 +``` + +### 头像上传示例 + +```vue + + + +``` + +## 上传后处理流程 + +```text +前端上传 → temp/日期/xxx.jpg(临时目录) + │ + ▼ 提交表单(携带 URL) +BaseLogic::add() + │ + ├─ processFileFieldsOnSave() + │ └─ save_file() → 正式目录/日期/xxx.jpg + │ + └─ 数据库存储相对路径 + │ + ▼ 读取时 + processFileFieldsOnRead() + └─ get_file_url() → 拼接域名返回完整 URL +``` + +## 配置项 + +```ini +; .env +[FILE] +UPLOAD_DIR = D:/uploads/rxthinkcmf ; 上传根目录 +DOMAIN_URL = https://cdn.example.com ; 文件访问域名 +MAX_SIZE = 10 ; 单文件最大 10MB +IMAGE_EXT = jpg,jpeg,png,gif,bmp,webp,svg +FILE_EXT = jpg,jpeg,png,gif,bmp,webp,svg,doc,docx,xls,xlsx,ppt,pptx,pdf,zip,rar,txt,csv,mp3,mp4,sql,js,css +``` + +## 安全设计 + +| 安全措施 | 说明 | +|---------|------| +| 文件类型校验 | 同时检查后缀名和 MIME 类型(finfo),防止伪造扩展名 | +| 文件大小限制 | 通过 `config/file.php` 的 `max_size` 配置 | +| 图片内容校验 | 图片类型额外用 `getimagesize` 验证,防止非图片伪装 | +| 随机文件名 | 使用 `bin2hex(random_bytes(16))` 防止文件名冲突和路径遍历 | +| 临时目录隔离 | 上传文件先存入 `temp/` 目录,提交表单后才迁移到正式目录 | +| Nginx 防护 | 上传目录禁止执行 PHP 文件 | + +## 常见问题 + +### 问题 1:上传返回 401 + +**原因:** 上传接口需要认证,但请求头未携带 Token。 + +**解决:** 确保请求头包含 `Authorization: Bearer `。 + +### 问题 2:上传返回"不允许的文件类型" + +**原因:** 文件扩展名不在白名单中。 + +**解决:** 检查 `.env` 中 `FILE.FILE_EXT` 或 `FILE.IMAGE_EXT` 配置。 + +### 问题 3:上传后图片不显示 + +**原因:** `DOMAIN_URL` 配置错误或 Nginx 未配置静态文件处理。 + +**解决:** 检查 `.env` 中 `FILE.DOMAIN_URL` 是否正确,Nginx 是否配置了 `/uploads/` 的 alias。 + +### 问题 4:大文件上传失败 + +**原因:** PHP 或 Nginx 限制了上传大小。 + +**解决:** + +```ini +; php.ini +upload_max_filesize = 20M +post_max_size = 25M + +; .env +FILE.MAX_SIZE = 20 +``` + +```nginx +# nginx.conf +client_max_body_size 20M; +``` diff --git a/RXThinkCMF_ThinkPhp6_EleVue/zh/appendix/api-cheatsheet.md b/RXThinkCMF_ThinkPhp6_EleVue/zh/appendix/api-cheatsheet.md new file mode 100644 index 0000000..01ac137 --- /dev/null +++ b/RXThinkCMF_ThinkPhp6_EleVue/zh/appendix/api-cheatsheet.md @@ -0,0 +1,279 @@ +# E. API 接口速查表 + +:::tip 概述 +本表列出所有 API 路由的完整清单,包含 HTTP 方法、路径和所需权限码。 +::: + +## 公开接口 + +| 方法 | 路径 | 说明 | +|------|------|------| +| POST | `/api/login` | 用户登录 | +| GET | `/api/logout` | 用户登出 | +| GET | `/api/captcha` | 获取验证码 | +| POST | `/api/oauth2/token` | 刷新 Token | +| DELETE | `/api/oauth2/remove/:token` | 移除 Token | +| GET | `/api/oauth2/check/token` | 检查 Token | + +## 首页 / 个人中心 + +| 方法 | 路径 | 权限码 | 说明 | +|------|------|--------|------| +| GET | `/api/index/getUser` | - | 获取当前用户信息 | +| GET | `/api/index/getMenus` | - | 获取菜单列表 | +| PUT | `/api/index/updatePassword` | - | 修改密码 | +| PUT | `/api/index/updateProfile` | - | 修改个人资料 | + +## 用户管理 + +| 方法 | 路径 | 权限码 | 说明 | +|------|------|--------|------| +| GET | `/api/user/page` | sys:user:page | 分页列表 | +| GET | `/api/user/detail/:id` | sys:user:detail | 详情 | +| POST | `/api/user/add` | sys:user:add | 新增 | +| PUT | `/api/user/update` | sys:user:update | 修改 | +| PUT | `/api/user/resetPwd` | sys:user:resetPwd | 重置密码 | +| DELETE | `/api/user/delete/:id` | sys:user:delete | 删除 | +| DELETE | `/api/user/batchDelete` | sys:user:batchDelete | 批量删除 | +| POST | `/api/user/import` | sys:user:import | 导入 | +| GET | `/api/user/export` | sys:user:export | 导出 | + +## 角色管理 + +| 方法 | 路径 | 权限码 | 说明 | +|------|------|--------|------| +| GET | `/api/role/page` | sys:role:page | 分页列表 | +| GET | `/api/role/list` | sys:role:list | 全量列表 | +| GET | `/api/role/detail/:id` | sys:role:detail | 详情 | +| POST | `/api/role/add` | sys:role:add | 新增 | +| PUT | `/api/role/update` | sys:role:update | 修改 | +| DELETE | `/api/role/delete/:id` | sys:role:delete | 删除 | +| DELETE | `/api/role/batchDelete` | sys:role:batchDelete | 批量删除 | +| GET | `/api/role/menu/list/:id` | - | 角色菜单列表 | +| POST | `/api/role/menu/save` | sys:role:authorize | 保存角色菜单 | + +## 菜单管理 + +| 方法 | 路径 | 权限码 | 说明 | +|------|------|--------|------| +| GET | `/api/menu/list` | sys:menu:page | 菜单列表(树形) | +| GET | `/api/menu/detail/:id` | sys:menu:detail | 详情 | +| POST | `/api/menu/add` | sys:menu:add | 新增 | +| PUT | `/api/menu/update` | sys:menu:update | 修改 | +| DELETE | `/api/menu/delete/:id` | sys:menu:delete | 删除 | + +## 部门管理 + +| 方法 | 路径 | 权限码 | 说明 | +|------|------|--------|------| +| GET | `/api/dept/list` | sys:dept:page | 部门列表(树形) | +| GET | `/api/dept/detail/:id` | sys:dept:detail | 详情 | +| POST | `/api/dept/add` | sys:dept:add | 新增 | +| PUT | `/api/dept/update` | sys:dept:update | 修改 | +| DELETE | `/api/dept/delete/:id` | sys:dept:delete | 删除 | + +## 岗位管理 + +| 方法 | 路径 | 权限码 | 说明 | +|------|------|--------|------| +| GET | `/api/position/page` | sys:position:page | 分页列表 | +| GET | `/api/position/list` | sys:position:list | 全量列表 | +| GET | `/api/position/detail/:id` | sys:position:detail | 详情 | +| POST | `/api/position/add` | sys:position:add | 新增 | +| PUT | `/api/position/update` | sys:position:update | 修改 | +| DELETE | `/api/position/delete/:id` | sys:position:delete | 删除 | +| DELETE | `/api/position/batchDelete` | sys:position:batchDelete | 批量删除 | + +## 职级管理 + +| 方法 | 路径 | 权限码 | 说明 | +|------|------|--------|------| +| GET | `/api/level/page` | sys:level:page | 分页列表 | +| GET | `/api/level/list` | sys:level:list | 全量列表 | +| GET | `/api/level/detail/:id` | sys:level:detail | 详情 | +| POST | `/api/level/add` | sys:level:add | 新增 | +| PUT | `/api/level/update` | sys:level:update | 修改 | +| DELETE | `/api/level/delete/:id` | sys:level:delete | 删除 | +| DELETE | `/api/level/batchDelete` | sys:level:batchDelete | 批量删除 | +| GET | `/api/level/export` | sys:level:export | 导出 | +| POST | `/api/level/import` | sys:level:import | 导入 | + +## 数据字典 + +| 方法 | 路径 | 权限码 | 说明 | +|------|------|--------|------| +| GET | `/api/dict/page` | sys:dict:page | 分页列表 | +| GET | `/api/dict/detail/:id` | sys:dict:detail | 详情 | +| GET | `/api/dict/refreshCache` | sys:dict:refreshCache | 刷新缓存 | +| POST | `/api/dict/add` | sys:dict:add | 新增 | +| PUT | `/api/dict/update` | sys:dict:update | 修改 | +| DELETE | `/api/dict/delete/:id` | sys:dict:delete | 删除 | +| DELETE | `/api/dict/batchDelete` | sys:dict:batchDelete | 批量删除 | + +## 字典项 + +| 方法 | 路径 | 权限码 | 说明 | +|------|------|--------|------| +| GET | `/api/dict/item/page` | sys:dict:item:page | 分页列表 | +| GET | `/api/dict/item/detail/:id` | sys:dict:item:detail | 详情 | +| GET | `/api/dict/item/getDictItemList/:code` | - | 按编码获取字典项 | +| POST | `/api/dict/item/add` | sys:dict:item:add | 新增 | +| PUT | `/api/dict/item/update` | sys:dict:item:update | 修改 | +| DELETE | `/api/dict/item/delete/:id` | sys:dict:item:delete | 删除 | +| DELETE | `/api/dict/item/batchDelete` | sys:dict:item:batchDelete | 批量删除 | + +## 租户管理 + +| 方法 | 路径 | 权限码 | 说明 | +|------|------|--------|------| +| GET | `/api/tenant/page` | sys:tenant:page | 分页列表 | +| GET | `/api/tenant/detail/:id` | sys:tenant:detail | 详情 | +| GET | `/api/tenant/users/:tenantId` | - | 租户用户列表 | +| POST | `/api/tenant/add` | sys:tenant:add | 新增 | +| POST | `/api/tenant/account` | sys:tenant:add | 创建租户账号 | +| PUT | `/api/tenant/update` | sys:tenant:update | 修改 | +| DELETE | `/api/tenant/delete/:id` | sys:tenant:delete | 删除 | +| DELETE | `/api/tenant/batchDelete` | sys:tenant:batchDelete | 批量删除 | + +## 系统配置 + +| 方法 | 路径 | 权限码 | 说明 | +|------|------|--------|------| +| GET | `/api/config/page` | sys:config:page | 分页列表 | +| GET | `/api/config/list` | sys:config:list | 全量列表 | +| GET | `/api/config/detail/:id` | sys:config:detail | 详情 | +| GET | `/api/config/web/index` | sys:config:web | 网站配置 | +| POST | `/api/config/web/save` | sys:config:web | 保存网站配置 | +| POST | `/api/config/add` | sys:config:add | 新增 | +| PUT | `/api/config/update` | sys:config:update | 修改 | +| DELETE | `/api/config/delete/:id` | sys:config:delete | 删除 | + +## 配置项 + +| 方法 | 路径 | 权限码 | 说明 | +|------|------|--------|------| +| GET | `/api/config/item/page` | sys:config:item:page | 分页列表 | +| GET | `/api/config/item/list` | sys:config:item:list | 全量列表 | +| GET | `/api/config/item/detail/:id` | sys:config:item:detail | 详情 | +| POST | `/api/config/item/add` | sys:config:item:add | 新增 | +| PUT | `/api/config/item/update` | sys:config:item:update | 修改 | +| DELETE | `/api/config/item/delete/:id` | sys:config:item:delete | 删除 | + +## 系统参数 + +| 方法 | 路径 | 权限码 | 说明 | +|------|------|--------|------| +| GET | `/api/param/page` | sys:param:page | 分页列表 | +| GET | `/api/param/list` | sys:param:list | 全量列表 | +| GET | `/api/param/detail/:id` | sys:param:detail | 详情 | +| POST | `/api/param/add` | sys:param:add | 新增 | +| PUT | `/api/param/update` | sys:param:update | 修改 | +| DELETE | `/api/param/delete/:id` | sys:param:delete | 删除 | + +## 通知公告 + +| 方法 | 路径 | 权限码 | 说明 | +|------|------|--------|------| +| GET | `/api/notice/page` | sys:notice:page | 分页列表 | +| GET | `/api/notice/list` | - | 全量列表 | +| GET | `/api/notice/detail/:id` | sys:notice:detail | 详情 | +| POST | `/api/notice/add` | sys:notice:add | 新增 | +| PUT | `/api/notice/update` | sys:notice:update | 修改 | +| DELETE | `/api/notice/delete/:id` | sys:notice:delete | 删除 | + +## 城市行政区划 + +| 方法 | 路径 | 权限码 | 说明 | +|------|------|--------|------| +| GET | `/api/city/list` | sys:city:page | 城市列表 | +| GET | `/api/city/detail/:id` | sys:city:detail | 详情 | +| POST | `/api/city/add` | sys:city:add | 新增 | +| PUT | `/api/city/update` | sys:city:update | 修改 | +| DELETE | `/api/city/delete/:id` | sys:city:delete | 删除 | +| GET | `/api/city/getCityList/:parentCode` | - | 按父编码获取城市 | + +## 文章管理 + +| 方法 | 路径 | 权限码 | 说明 | +|------|------|--------|------| +| GET | `/api/article/page` | sys:article:page | 分页列表 | +| GET | `/api/article/list` | sys:article:list | 全量列表 | +| GET | `/api/article/detail/:id` | sys:article:detail | 详情 | +| POST | `/api/article/add` | sys:article:add | 新增 | +| PUT | `/api/article/update` | sys:article:update | 修改 | +| DELETE | `/api/article/delete/:id` | sys:article:delete | 删除 | +| DELETE | `/api/article/batchDelete` | sys:article:batchDelete | 批量删除 | + +## 文章分类 + +| 方法 | 路径 | 权限码 | 说明 | +|------|------|--------|------| +| GET | `/api/category/page` | sys:category:page | 分页列表 | +| GET | `/api/category/list` | sys:category:list | 全量列表 | +| GET | `/api/category/detail/:id` | sys:category:detail | 详情 | +| POST | `/api/category/add` | sys:category:add | 新增 | +| PUT | `/api/category/update` | sys:category:update | 修改 | +| DELETE | `/api/category/delete/:id` | sys:category:delete | 删除 | + +## 友情链接 + +| 方法 | 路径 | 权限码 | 说明 | +|------|------|--------|------| +| GET | `/api/link/page` | sys:link:page | 分页列表 | +| GET | `/api/link/detail/:id` | sys:link:detail | 详情 | +| POST | `/api/link/add` | sys:link:add | 新增 | +| PUT | `/api/link/update` | sys:link:update | 修改 | +| DELETE | `/api/link/delete/:id` | sys:link:delete | 删除 | + +## 定时任务 + +| 方法 | 路径 | 权限码 | 说明 | +|------|------|--------|------| +| GET | `/api/job/page` | sys:job:page | 分页列表 | +| GET | `/api/job/list` | sys:job:list | 全量列表 | +| GET | `/api/job/detail/:id` | sys:job:detail | 详情 | +| GET | `/api/job/runOnce/:id` | sys:job:execute | 手动执行一次 | +| GET | `/api/job/pause/:id` | sys:job:pause | 暂停任务 | +| GET | `/api/job/resume/:id` | sys:job:resume | 恢复任务 | +| POST | `/api/job/add` | sys:job:add | 新增 | +| PUT | `/api/job/update` | sys:job:update | 修改 | +| DELETE | `/api/job/delete/:id` | sys:job:delete | 删除 | + +## 任务日志 + +| 方法 | 路径 | 权限码 | 说明 | +|------|------|--------|------| +| GET | `/api/job/log/page` | sys:job:log:page | 分页列表 | +| GET | `/api/job/log/detail/:id` | sys:job:log:detail | 详情 | +| DELETE | `/api/job/log/delete/:id` | sys:job:log:delete | 删除 | + +## 操作日志 + +| 方法 | 路径 | 权限码 | 说明 | +|------|------|--------|------| +| GET | `/api/oper/log/page` | sys:oper:log:page | 分页列表 | +| GET | `/api/oper/log/detail/:id` | sys:oper:log:detail | 详情 | +| DELETE | `/api/oper/log/delete/:id` | sys:oper:log:delete | 删除 | + +## 登录日志 + +| 方法 | 路径 | 权限码 | 说明 | +|------|------|--------|------| +| GET | `/api/login/log/page` | sys:login:log:page | 分页列表 | +| GET | `/api/login/log/detail/:id` | sys:login:log:detail | 详情 | +| DELETE | `/api/login/log/delete/:id` | sys:login:log:delete | 删除 | + +## 文件上传 + +| 方法 | 路径 | 权限码 | 说明 | +|------|------|--------|------| +| POST | `/api/upload/uploadFile` | - | 上传文件 | + +## 代码生成器 + +| 方法 | 路径 | 权限码 | 说明 | +|------|------|--------|------| +| GET | `/api/generator/page` | sys:generator:page | 可生成表列表 | +| POST | `/api/generator/preview` | sys:generator:preview | 预览生成代码 | +| POST | `/api/generator/generate` | sys:generator:generate | 生成代码 | +| POST | `/api/generator/batchGenerate` | sys:generator:generate | 批量生成 | \ No newline at end of file diff --git a/RXThinkCMF_ThinkPhp6_EleVue/zh/appendix/components.md b/RXThinkCMF_ThinkPhp6_EleVue/zh/appendix/components.md new file mode 100644 index 0000000..be5a8a6 --- /dev/null +++ b/RXThinkCMF_ThinkPhp6_EleVue/zh/appendix/components.md @@ -0,0 +1,105 @@ +# G. 前端组件速查 + +:::tip 概述 +以下组件定义在 `ui/src/components/` 目录下,可在页面中直接引入使用。 +::: + +## 数据展示 + +| 组件 | 路径 | 说明 | 核心 Props | +|------|------|------|-----------| +| BasicTable | `components/Table/` | 数据表格 | `columns`、`dataSource`、`pagination` | +| PageWrapper | `components/Page/` | 页面容器 | `title`、`contentFullHeight` | +| CountTo | `components/CountTo/` | 数字动画 | `startVal`、`endVal`、`duration` | +| Qrcode | `components/Qrcode/` | 二维码 | `value`、`size`、`level` | +| Icon | `components/icon/` | 图标选择器 | `value`、`disabled` | + +## 数据录入 + +| 组件 | 路径 | 说明 | 核心 Props | +|------|------|------|-----------| +| BasicForm | `components/Form/` | 动态表单 | `schemas`、`labelWidth` | +| BasicModal | `components/Modal/` | 弹窗 | `title`、`width`、`okText` | +| BasicUpload | `components/Upload/` | 文件上传 | `action`、`maxCount`、`accept` | +| Editor | `components/Editor/` | 富文本编辑器 | `value`、`height`、`placeholder` | +| Password | `components/Password/` | 密码强度 | `value`、`placeholder` | +| ChinaArea | `components/ChinaArea/` | 省市区联动 | `value`、`placeholder` | +| Region | `components/Region/` | 行政区划 | `value`、`level` | +| Cropper | `components/Cropper/` | 头像裁剪 | `image`、`aspectRatio` | +| NumberInput | `components/numberInput/` | 数字输入 | `value`、`min`、`max` | +| PriceInput | `components/priceInput/` | 价格输入 | `value`、`precision` | + +## Excel + +| 组件 | 路径 | 说明 | +|------|------|------| +| ImportExcel | `components/Excel/` | Excel 导入(解析文件) | +| Export2Excel | `components/Excel/` | Excel 导出(生成文件) | + +## 其他 + +| 组件 | 路径 | 说明 | +|------|------|------| +| Authority | `components/Authority/` | 权限组件(根据权限显示/隐藏) | +| CheckToken | `components/CheckToken/` | Token 校验组件 | +| Lockscreen | `components/Lockscreen/` | 锁屏组件 | +| Websocket | `components/Websocket/` | WebSocket 组件 | + +## 使用示例 + +### BasicTable + +```vue + + + +``` + +### BasicForm + +```vue + + + +``` + +### BasicModal + +```vue + + + +``` \ No newline at end of file diff --git a/RXThinkCMF_ThinkPhp6_EleVue/zh/appendix/config-reference.md b/RXThinkCMF_ThinkPhp6_EleVue/zh/appendix/config-reference.md new file mode 100644 index 0000000..e09e2cf --- /dev/null +++ b/RXThinkCMF_ThinkPhp6_EleVue/zh/appendix/config-reference.md @@ -0,0 +1,145 @@ +# C. 配置项速查 + +## 后端 .env 配置 + +:::tip 格式说明 +`.env` 文件采用 INI 分组格式,`[SECTION]` 表示分组,分组下的 `KEY = value` 对应该分组的配置项。 +::: + +### 全局变量 + +| 变量 | 默认值 | 说明 | +|------|--------|------| +| `APP_DEBUG` | `true` | 调试模式(生产环境设为 false) | + +### 应用配置 `[APP]` + +| 变量 | 默认值 | 说明 | +|------|--------|------| +| `DEFAULT_TIMEZONE` | `Asia/Shanghai` | 默认时区 | +| `DEMO` | `false` | 演示模式(开启后拦截写操作) | +| `SHOW_ERROR_MSG` | `true` | 显示错误详情(生产环境设为 false) | + +### 数据库配置 `[DATABASE]` + +| 变量 | 默认值 | 说明 | +|------|--------|------| +| `TYPE` | `mysql` | 数据库类型:mysql / pgsql / sqlsrv / oracle / sqlite | +| `HOSTNAME` | `127.0.0.1` | 数据库主机 | +| `DATABASE` | - | 数据库名(SQLite 为文件路径) | +| `USERNAME` | `root` | 数据库用户 | +| `PASSWORD` | - | 数据库密码 | +| `HOSTPORT` | `3306` | 数据库端口 | +| `CHARSET` | `utf8mb4` | 字符集 | +| `DEBUG` | `true` | 是否开启 SQL 调试 | +| `PREFIX` | `think_` | 表前缀 | + +各数据库默认端口: + +| 数据库 | 默认端口 | 默认用户 | +|--------|----------|----------| +| MySQL | 3306 | root | +| PostgreSQL | 5432 | postgres | +| SQL Server | 1433 | sa | +| Oracle | 1521 | - | +| SQLite | - | - | + +### JWT 配置 `[JWT]` + +| 变量 | 默认值 | 说明 | +|------|--------|------| +| `SECRET` | 内置默认 | 签名密钥(**生产环境务必修改**) | +| `ACCESS_TTL` | `7200` | access_token 有效期(秒) | +| `REFRESH_TTL` | `604800` | refresh_token 有效期(秒) | +| `ISSUER` | `rxthinkcmf` | 签发者标识 | + +### 文件上传配置 `[FILE]` + +| 变量 | 默认值 | 说明 | +|------|--------|------| +| `UPLOAD_DIR` | - | 上传根目录(绝对路径) | +| `DOMAIN_URL` | - | 文件访问域名 | +| `IMAGE_EXT` | `gif,jpg,jpeg,png,bmp,webp,svg` | 图片允许后缀 | +| `FILE_EXT` | 多种后缀 | 文件允许后缀 | +| `MAX_SIZE` | `10` | 最大文件大小(MB) | + +### 缓存配置 `[CACHE]` + +| 变量 | 默认值 | 说明 | +|------|--------|------| +| `DRIVER` | `file` | 缓存驱动:file / redis | +| `REDIS_HOST` | `127.0.0.1` | Redis 主机(driver=redis 时生效) | +| `REDIS_PORT` | `6379` | Redis 端口(driver=redis 时生效) | +| `REDIS_PASSWORD` | - | Redis 密码 | +| `REDIS_DB` | `0` | Redis 数据库编号 | +| `REDIS_PREFIX` | `rx:` | 缓存键前缀 | + +### CORS 配置 `[CORS]` + +| 变量 | 默认值 | 说明 | +|------|--------|------| +| `ALLOW_ORIGIN` | `*` | 允许的跨域来源 | + +### API 配置 `[API]` + +| 变量 | 默认值 | 说明 | +|------|--------|------| +| `CAMEL_SNAKE_CONVERT` | `true` | 驼峰/下划线自动转换 | +| `PROXY_IPS` | (空) | 受信代理 IP 列表(逗号分隔,支持 CIDR)。部署在 CDN/负载均衡后面时配置,中间件会从 `X-Forwarded-For` / `X-Real-IP` 头提取真实客户端 IP。未配置时默认信任 `127.0.0.1` 和 `::1`。 | + +### 验证码配置 `[CAPTCHA]` + +| 变量 | 默认值 | 说明 | +|------|--------|------| +| `CAPTCHA_BYPASS_CODE` | (空) | 万能验证码,仅开发调试用,**生产环境务必留空** | +| `CAPTCHA_LENGTH` | `6` | 验证码字符长度(4-6位) | +| `CAPTCHA_EXPIRE` | `300` | 过期时间(秒) | +| `CAPTCHA_RATE_WINDOW` | `60` | 防刷窗口期(秒) | +| `CAPTCHA_RATE_LIMIT` | `20` | 窗口期内最大获取次数 | +| `CAPTCHA_CHARS` | `ABCDEFGHJKLMNPQRSTUVWXYZ2346789` | 字符集 | +| `CAPTCHA_FONT_PATH` | `static/fonts/Vera.ttf` | 字体路径 | +| `CAPTCHA_IMAGE_WIDTH` | `160` | 图片宽度(像素) | +| `CAPTCHA_IMAGE_HEIGHT` | `50` | 图片高度(像素) | +| `CAPTCHA_FONT_SIZE` | `28` | 字体大小 | +| `CAPTCHA_NOISE_LINES` | `8` | 干扰线数量 | +| `CAPTCHA_NOISE_DOTS` | `500` | 噪点数量 | + +### 代码生成器配置 `[GENERATOR]` + +| 变量 | 默认值 | 说明 | +|------|--------|------| +| `PARENT_MENU_ID` | `189` | 生成模块的父菜单ID | +| `APP_NAME` | `tool` | 模块应用名(路由前缀) | + +### 国际化配置 `[LANG]` + +| 变量 | 默认值 | 说明 | +|------|--------|------| +| `default_lang` | `zh-cn` | 默认语言 | + +## 前端环境变量 + +| 变量 | 说明 | +|------|------| +| `VITE_GLOB_API_URL` | API 基础路径(如 `/api`) | +| `VITE_GLOB_API_URL_PREFIX` | API 前缀 | + +## 配置文件对照 + +| .env 分组 | 配置文件 | 主要配置键 | +|-----------|---------|-----------| +| `[DATABASE]` | `config/database.php` | `type` / `hostname` / `database` / `username` / `password` | +| `[JWT]` | `config/jwt.php` | `secret` / `access_ttl` / `refresh_ttl` | +| `[FILE]` | `config/file.php` | `upload_dir` / `domain_url` / `max_size` | +| `[CACHE]` | `config/cache.php` | `driver` / `redis_host` / `redis_port` | +| `[CORS]` | `config/cors.php` | `allow_origin` | +| `[API]` | `config/api.php` | `camel_snake_convert` | +| `[CAPTCHA]` | 无配置文件,`CaptchaService` 直接读取 `env()` | `captcha_bypass_code` / `captcha_length` / `captcha_expire` | +| `[LANG]` | `config/lang.php` | `default_lang` | +| — | `config/tenant.php` | `default_tenant_id` | + +### 租户配置 + +| 配置文件 | 配置键 | 默认值 | 说明 | +|----------|--------|--------|------| +| `config/tenant.php` | `default_tenant_id` | `1` | 默认租户ID,超管操作租户隔离表时自动代入 | diff --git a/RXThinkCMF_ThinkPhp6_EleVue/zh/appendix/database-tables.md b/RXThinkCMF_ThinkPhp6_EleVue/zh/appendix/database-tables.md new file mode 100644 index 0000000..5309870 --- /dev/null +++ b/RXThinkCMF_ThinkPhp6_EleVue/zh/appendix/database-tables.md @@ -0,0 +1,76 @@ +# B. 数据库表结构速查 + +## SQL 脚本目录 + +项目为每种数据库提供了独立的 SQL 脚本: + +```text +document/ +├── mysql/rxthinkcmf.thinkphp6.elevue.sql # MySQL 版本 +├── postgresql/rxthinkcmf.thinkphp6.elevue.sql # PostgreSQL 版本 +├── sqlserver/rxthinkcmf.thinkphp6.elevue.sql # SQL Server 版本 +├── oracle/rxthinkcmf.thinkphp6.elevue.sql # Oracle 版本 +└── sqlite/rxthinkcmf.thinkphp6.elevue.sql # SQLite 版本 +``` + +各脚本已针对对应数据库的 SQL 语法做了适配,可直接导入使用。 + +## 用户相关 + +| 表名 | 说明 | 关键字段 | +|------|------|----------| +| think_user | 用户表 | tenant_id, code, username, password, salt, realname, gender, avatar, mobile, email, status, dept_id, level_id, position_id, province_code, city_code, district_code, street_code, city_info, address, intro, note, sort | +| think_user_role | 用户角色关联 | user_id, role_id | +| think_role | 角色表 | tenant_id, name, code, data_scope, sort, note | +| think_role_menu | 角色菜单关联 | role_id, menu_id | +| think_menu | 菜单表 | parent_id, name, permission, type, path, component | +| think_dept | 部门表 | parent_id, name, sort | +| think_position | 岗位表 | name, status, sort | +| think_level | 职级表 | name, status, sort | + +## 系统配置 + +| 表名 | 说明 | 关键字段 | +|------|------|----------| +| think_dict | 字典表 | name, code | +| think_dict_item | 字典项 | dict_id, name, value, sort | +| think_config | 配置表 | name, code | +| think_config_item | 配置项 | config_id, name, code, value | +| think_param | 参数表 | name, code, value, status | + +## 租户 + +| 表名 | 说明 | 关键字段 | +|------|------|----------| +| think_tenant | 租户表 | name, code, contact, mobile, email, address, status, expire_time, max_user_num, note | + +## 内容 + +| 表名 | 说明 | 关键字段 | +|------|------|----------| +| think_article | 文章表 | title, category_id, cover, content, status | +| think_category | 分类表 | parent_id, name, sort | +| think_link | 链接表 | name, url, sort | +| think_notice | 公告表 | title, content, status | + +## 日志 + +| 表名 | 说明 | 关键字段 | +|------|------|----------| +| think_login_log | 登录日志 | username, status, ip, location | +| think_operation_log | 操作日志 | title, type, method, url, param, ip | + +## 定时任务 + +| 表名 | 说明 | 关键字段 | +|------|------|----------| +| think_job | 任务表 | job_name, url, cron_expression, status | +| think_job_log | 任务日志 | job_id, status, job_message | + +## 其他 + +| 表名 | 说明 | 关键字段 | +|------|------|----------| +| think_city | 城市表 | name, code, parent_code, level | +| think_file_template | 文件模板 | name, code, file_path | +| think_example | 案例演示 | name, type, status, sort, avatar | \ No newline at end of file diff --git a/RXThinkCMF_ThinkPhp6_EleVue/zh/appendix/enums.md b/RXThinkCMF_ThinkPhp6_EleVue/zh/appendix/enums.md new file mode 100644 index 0000000..35ab406 --- /dev/null +++ b/RXThinkCMF_ThinkPhp6_EleVue/zh/appendix/enums.md @@ -0,0 +1,84 @@ +# I. 前端 Enums 速查 + +:::tip 概述 +前端枚举定义在 `ui/src/enums/` 目录下,用于统一管理常量值。 +::: + +## httpEnum + +**文件**:`enums/httpEnum.ts` + +```typescript +// 请求方法 +export enum RequestEnum { + GET = 'GET', + POST = 'POST', + PUT = 'PUT', + DELETE = 'DELETE', +} + +// 响应状态码 +export enum ResultEnum { + SUCCESS = 0, // 业务成功 + FAIL = 1, // 业务失败 +} + +// 内容类型 +export enum ContentTypeEnum { + JSON = 'application/json', + FORM_URLENCODED = 'application/x-www-form-urlencoded', +} +``` + +## pageEnum + +**文件**:`enums/pageEnum.ts` + +```typescript +export enum PageEnum { + // 登录页 + BASE_LOGIN = '/login', + // 首页 + BASE_HOME = '/dashboard', + // 错误页 + BASE_ERROR_LOG = '/exception/404', + REDIRECT = '/redirect', +} +``` + +## 使用示例 + +```typescript +import { ResultEnum } from '@/enums/httpEnum'; +import { PageEnum } from '@/enums/pageEnum'; + +// 判断响应是否成功 +if (response.code === ResultEnum.SUCCESS) { + // 处理成功 +} + +// 跳转登录页 +router.push(PageEnum.BASE_LOGIN); + +// 判断请求方法 +if (method === RequestEnum.POST) { + // POST 请求 +} +``` + +## 自定义枚举 + +```typescript +// 添加新枚举时,在 enums/ 目录下创建文件 +// ui/src/enums/statusEnum.ts + +export enum UserStatus { + ENABLE = 1, + DISABLE = 2, +} + +export enum ArticleStatus { + DRAFT = 0, + PUBLISHED = 1, +} +``` \ No newline at end of file diff --git a/RXThinkCMF_ThinkPhp6_EleVue/zh/appendix/faq.md b/RXThinkCMF_ThinkPhp6_EleVue/zh/appendix/faq.md new file mode 100644 index 0000000..987a20f --- /dev/null +++ b/RXThinkCMF_ThinkPhp6_EleVue/zh/appendix/faq.md @@ -0,0 +1,57 @@ +# A. 常见问题汇总 + +:::tip 概述 +本页汇总了项目各模块的常见问题及解决方案,按场景分类索引。遇到问题时可先查阅本页,再深入各章节详细文档。 +::: + +## 环境问题 + +| 问题 | 解决方案 | +|------|----------| +| PHP 版本低于 8.2 | 升级到 PHP 8.2+,项目使用了 8.2 的强类型特性 | +| 缺少 pdo_mysql 扩展 | `apt install php8.2-mysql` 或 `dnf install php-mysqlnd` | +| 缺少 gd 扩展 | `apt install php8.2-gd` 或 `dnf install php-gd` | +| Composer 下载慢 | `composer config -g repo.packagist composer https://mirrors.aliyun.com/composer/` | +| Redis 未安装 | 可选,改用 file 缓存:`.env` 中 `[CACHE]` 下设置 `DRIVER = file` | +| 缺少 redis 扩展 | `pecl install redis` | +| Oracle pdo_oci 加载失败(Windows) | 安装 Oracle Instant Client 19c + VC++ 2015-2022 Redistributable,php.ini 启用 `extension=pdo_oci`,配置系统 PATH 后重启 Apache | +| 命令行有 pdo_oci,浏览器 phpinfo 没有 | Apache 服务启动时 PATH 未刷新,完全停止再启动 Apache 即可 | +| Oracle Instant Client 路径含中文/空格导致加载失败 | 解压到 `C:\oracle\instantclient_19_20` 等固定路径,避免空格与中文 | + +## 启动问题 + +| 问题 | 解决方案 | +|------|----------| +| 端口被占用 | `php think run --port 8080` | +| 数据库连接失败 | 检查 `.env` 中的数据库配置 | +| 401 未授权 | 检查请求头是否携带 `Authorization: Bearer ` | +| 403 禁止访问 | 检查用户权限配置,确认角色已分配菜单 | +| 502 Bad Gateway | 检查 PHP-FPM 是否启动:`systemctl status php8.2-fpm` | + +## 业务问题 + +| 问题 | 解决方案 | +|------|----------| +| 唯一性校验报错 | 检查数据是否重复,确认 `uniqueFields` 配置 | +| 枚举显示名为空 | 检查字典配置,确认 `serializeMaps` 正确 | +| 文件上传失败 | 检查 `FILE.UPLOAD_DIR` 配置和目录权限 | +| 导入失败 | 检查 Excel 表头是否与 `headerMap` 一致 | +| 排序不生效 | 检查 `pageOrderBy` 配置,或前端 `orderField` 参数 | + +## 前端问题 + +| 问题 | 解决方案 | +|------|----------| +| pnpm install 失败 | `rm -rf node_modules pnpm-lock.yaml && pnpm install` | +| API 404 | 检查后端路由注册和 Vite 代理配置 | +| 菜单不显示 | 检查菜单配置、组件路径、角色权限 | +| 白屏 | 打开 F12 控制台查看错误 | +| Token 频繁过期 | 检查 `JWT.ACCESS_TTL` 配置(默认 7200 秒) | + +## 定时任务问题 + +| 问题 | 解决方案 | +|------|----------| +| 任务不执行 | 检查 crontab 配置:`crontab -l` | +| 任务重复执行 | 检查是否有多个 cron 或 daemon 在运行 | +| 任务执行超时 | 检查 PHP `max_execution_time` 配置 | \ No newline at end of file diff --git a/RXThinkCMF_ThinkPhp6_EleVue/zh/appendix/functions.md b/RXThinkCMF_ThinkPhp6_EleVue/zh/appendix/functions.md new file mode 100644 index 0000000..2165254 --- /dev/null +++ b/RXThinkCMF_ThinkPhp6_EleVue/zh/appendix/functions.md @@ -0,0 +1,66 @@ +# D. 公共函数速查 + +:::tip 概述 +以下函数定义在 `app/common.php` 中,可在任何地方直接调用。 +::: + +## 字符串 / 加密 + +| 函数 | 说明 | 签名 | +|------|------|------| +| `random_string($length)` | 生成随机字符串 | `string random_string(int $length = 16)` | +| `encrypt_password($password, $salt)` | 密码加密(bcrypt) | `array encrypt_password(string $password, ?string $salt = null)` | +| `verify_password($password, $hash, $salt)` | 验证密码 | `bool verify_password(string $password, string $encryptPassword, string $salt)` | + +## 文件处理 + +| 函数 | 说明 | 签名 | +|------|------|------| +| `upload_file($formName, $type)` | 上传文件到临时目录 | `array upload_file(string $formName = 'file', string $type = 'file')` | +| `save_file($fileUrl, $saveDir)` | 临时文件迁移到正式目录 | `string\|false save_file(string $fileUrl, string $saveDir = '')` | +| `save_content(&$content, $saveDir)` | 处理富文本中的媒体文件 | `bool save_content(string &$content, string $saveDir = 'article')` | +| `get_file_url($path)` | 相对路径转完整 URL | `string get_file_url(string $path)` | +| `create_file_path($saveDir, $ext)` | 生成文件存储路径 | `string create_file_path(string $saveDir = '', string $ext = '')` | +| `save_remote_file($url, $saveDir)` | 保存远程文件 | `string\|false save_remote_file(string $url, string $saveDir = '')` | +| `detect_image_ext($content)` | 根据内容检测图片后缀 | `?string detect_image_ext(string $content)` | + +## 驼峰 / 下划线转换 + +| 函数 | 说明 | 签名 | +|------|------|------| +| `snake_to_camel($str)` | 下划线转驼峰 | `string snake_to_camel(string $str)` | +| `camel_to_snake($str)` | 驼峰转下划线 | `string camel_to_snake(string $str)` | +| `array_snake_to_camel($data)` | 数组键名下划线转驼峰(递归) | `array array_snake_to_camel(array $data)` | +| `array_camel_to_snake($data, $except)` | 数组键名驼峰转下划线(递归) | `array array_camel_to_snake(array $data, array $except = [])` | +| `is_camel_convert_enabled()` | 是否开启转换 | `bool is_camel_convert_enabled()` | + +## 工具函数 + +| 函数 | 说明 | 签名 | +|------|------|------| +| `get_client_ip()` | 获取客户端 IP | `string get_client_ip()` | +| `format_page_params($params)` | 格式化分页参数 | `array format_page_params(array $params)` | + +## 使用示例 + +```php +// 生成随机字符串 +$str = random_string(32); + +// 密码加密 +$result = encrypt_password('123456'); +// ['password' => '$2y$12$...', 'salt' => 'xK7pMn3qRt'] + +// 验证密码 +$ok = verify_password('123456', $user->password, $user->salt); + +// 文件上传 +$result = upload_file('avatar', 'image'); +if (isset($result['error'])) { + return Result::fail($result['error']); +} + +// 驼峰转换 +camel_to_snake('createTime'); // 'create_time' +snake_to_camel('create_time'); // 'createTime' +``` \ No newline at end of file diff --git a/RXThinkCMF_ThinkPhp6_EleVue/zh/appendix/glossary.md b/RXThinkCMF_ThinkPhp6_EleVue/zh/appendix/glossary.md new file mode 100644 index 0000000..dbd9a56 --- /dev/null +++ b/RXThinkCMF_ThinkPhp6_EleVue/zh/appendix/glossary.md @@ -0,0 +1,72 @@ +# J. 术语表 + +## 后端术语 + +| 术语 | 全称 | 说明 | +|------|------|------| +| JWT | JSON Web Token | 无状态认证令牌,用于接口认证 | +| RBAC | Role-Based Access Control | 基于角色的访问控制 | +| CORS | Cross-Origin Resource Sharing | 跨域资源共享 | +| ORM | Object-Relational Mapping | 对象关系映射,数据库操作抽象层 | +| CRUD | Create/Read/Update/Delete | 增删改查 | +| API | Application Programming Interface | 应用程序编程接口 | +| bcrypt | - | 密码哈希算法,内置盐值 | +| OPcache | - | PHP 字节码缓存扩展 | +| Redis | - | 内存数据库,用于缓存 | +| Nginx | - | Web 服务器 / 反向代理 | +| Composer | - | PHP 依赖管理工具 | +| PSR | PHP Standards Recommendation | PHP 标准推荐 | +| FPM | FastCGI Process Manager | PHP 进程管理器 | +| DNF | Dandified Yum | Rocky Linux / CentOS 包管理器 | +| PECL | PHP Extension Community Library | PHP 扩展安装工具 | + +## 前端术语 + +| 术语 | 全称 | 说明 | +|------|------|------| +| Vue | - | 渐进式 JavaScript 框架 | +| TypeScript | - | JavaScript 超集,类型安全 | +| Vite | - | 前端构建工具 | +| Pinia | - | Vue 3 官方状态管理库 | +| ElementPlus | - | Vue 3 UI 组件库 | +| Axios | - | HTTP 请求库 | +| TailwindCSS | - | 原子化 CSS 框架 | +| TinyMce | - | 富文本编辑器 | +| ECharts | - | 数据可视化图表库 | +| pnpm | - | 高性能 Node.js 包管理器 | +| ESLint | - | JavaScript/TypeScript 代码检查工具 | +| Prettier | - | 代码格式化工具 | +| keep-alive | - | Vue 组件缓存机制 | + +## 业务术语 + +| 术语 | 说明 | +|------|------| +| 软删除 | 通过 `is_delete` 字段标记删除,不物理删除 | +| 租户隔离 | 通过 `tenant_id` 字段实现多租户数据隔离 | +| 数据权限 | 基于角色的 data_scope 控制数据可见范围 | +| access_token | 访问令牌,短期有效(默认 2 小时) | +| refresh_token | 刷新令牌,长期有效(默认 7 天) | +| 驼峰命名 | camelCase,如 `createTime` | +| 下划线命名 | snake_case,如 `create_time` | +| RBAC | 基于角色的访问控制:用户 → 角色 → 菜单/权限 | +| 超级管理员 | uid=1 的用户,跳过所有权限校验 | +| 租户管理员 | tenant_id=0 的用户,不受租户隔离限制 | +| 声明式 CRUD | 通过属性配置定义行为,无需编写通用代码 | +| 生命周期钩子 | beforeAdd/afterAdd 等,用于注入定制逻辑 | + +## 框架术语 + +| 术语 | 说明 | +|------|------| +| BaseLogic | 业务逻辑基类,提供声明式 CRUD 能力 | +| BaseTenantLogic | 租户逻辑基类,继承 BaseLogic,自动租户隔离 | +| BaseController | 控制器基类,提供统一响应和通用 CRUD 方法 | +| BaseModel | 模型基类,提供软删除和审计字段 | +| Result | 统一响应类,构造标准 JSON 响应 | +| ExceptionHandle | 异常处理类,统一捕获异常并返回 JSON | +| AttributeService | 注解服务,通过反射读取控制器方法注解 | +| DictService | 字典服务,提供字典项读取和缓存 | +| ParamService | 参数服务,提供系统参数读取和缓存 | +| PasswordService | 密码服务,bcrypt 双重加盐加密 | +| JwtService | JWT 服务,签发/刷新/解析令牌 | \ No newline at end of file diff --git a/RXThinkCMF_ThinkPhp6_EleVue/zh/appendix/middleware.md b/RXThinkCMF_ThinkPhp6_EleVue/zh/appendix/middleware.md new file mode 100644 index 0000000..321c46e --- /dev/null +++ b/RXThinkCMF_ThinkPhp6_EleVue/zh/appendix/middleware.md @@ -0,0 +1,120 @@ +# F. 中间件执行顺序 + +:::tip 概述 +本页展示请求在框架中的完整中间件执行流程,包括全局中间件和应用中间件的执行顺序,帮助开发者理解请求处理链路。 +::: + +## 执行流程图 + +```text +请求进入 + │ + ▼ +┌────────────────┐ +│ProxyMiddleware │ 全局中间件(最先执行) +│ │ 从代理头提取真实客户端 IP +└──────┬─────────┘ + │ + ▼ +┌──────────────┐ +│CorsMiddleware│ 全局中间件 +│ │ 处理 OPTIONS 预检 + 跨域响应头 +└──────┬───────┘ + │ + ▼ +┌──────────────┐ 401 +│AuthMiddleware│──────────► 未认证(Token 缺失/无效/过期) +│ │ 403 +│ JWT认证 │──────────► 无权限 +│ 权限校验 │ +└──────┬───────┘ + │ 注入 userInfo + ▼ +┌──────────────┐ 403 +│TenantMiddleware│──────────► 租户异常(禁用/过期) +│ │ +│ 租户上下文 │ +└──────┬───────┘ + │ 注入 tenantId, tenantInfo + ▼ +┌──────────────┐ 403 +│DemoMiddleware│──────────► 演示环境禁止写操作 +└──────┬───────┘ + │ + ▼ +┌──────────────┐ +│ LogMiddleware│ 记录操作日志(写操作始终记录) +└──────┬───────┘ + │ + ▼ +┌──────────────┐ +│ 控制器方法 │ +└──────────────┘ +``` + +## 中间件职责 + +| 中间件 | 类型 | 职责 | 配置 | +|--------|------|------|------| +| ProxyMiddleware | 全局 | 代理头解析,提取真实客户端 IP | .env → `[API]` PROXY_IPS | +| CorsMiddleware | 全局 | 跨域处理 | config/cors.php | +| AuthMiddleware | 路由级 | JWT 认证 + 权限校验 | config/middleware.php → auth_except | +| TenantMiddleware | 路由级 | 租户上下文注入 | 内置排除列表 | +| DemoMiddleware | 路由级 | 演示环境拦截 | .env → [APP] DEMO | +| LogMiddleware | 路由级 | 操作日志记录 | config/middleware.php → log_except | + +## 排除配置 + +```php +// config/middleware.php +return [ + 'auth_except' => [ + 'login', + 'captcha', + 'oauth2/token', + 'oauth2/check/token', + ], + 'log_except' => [ + 'login', + 'logout', + 'captcha', + 'oauth2', + ], +]; +``` + +## 注入内容 + +| 中间件 | 注入字段 | 说明 | +|--------|---------|------| +| ProxyMiddleware | `$_SERVER['REMOTE_ADDR']` | 覆盖为真实客户端 IP | +| AuthMiddleware | `$request->userInfo` | 用户信息(uid, username, type 等) | +| TenantMiddleware | `$request->tenantId` | 租户 ID(0=超级管理员) | +| TenantMiddleware | `$request->isSuperAdmin` | 是否超级管理员 | +| TenantMiddleware | `$request->tenantInfo` | 租户信息对象 | + +## ProxyMiddleware 详解 + +当项目部署在 CDN / 负载均衡 / Nginx 反向代理后面时,PHP 的 `REMOTE_ADDR` 是代理服务器的 IP(通常是 `127.0.0.1`),真实客户端 IP 由代理通过 `X-Forwarded-For` / `X-Real-IP` 请求头传递。 + +### 工作原理 + +```text +客户端(真实IP) → CDN/代理 → Nginx → PHP-FPM + ↓ 设置头 + X-Forwarded-For: 真实IP, 代理IP + X-Real-IP: 真实IP +``` + +1. 检查 `REMOTE_ADDR` 是否在受信代理列表(`.env` → `PROXY_IPS`) +2. 如果是,从 `X-Real-IP` 或 `X-Forwarded-For` 头提取真实 IP +3. 覆盖 `$_SERVER['REMOTE_ADDR']`,后续 `request()->ip()` 返回真实 IP + +### 配置 + +```ini +# .env [API] 段 +# 受信代理 IP(逗号分隔,支持 CIDR 网段) +# 未配置时默认信任 127.0.0.1 和 ::1 +PROXY_IPS = 127.0.0.1,::1,101.226.0.0/15,180.101.0.0/16 +``` \ No newline at end of file diff --git a/RXThinkCMF_ThinkPhp6_EleVue/zh/appendix/stores.md b/RXThinkCMF_ThinkPhp6_EleVue/zh/appendix/stores.md new file mode 100644 index 0000000..8db7033 --- /dev/null +++ b/RXThinkCMF_ThinkPhp6_EleVue/zh/appendix/stores.md @@ -0,0 +1,131 @@ +# H. 前端 Store 速查 + +:::tip 概述 +前端使用 Pinia 进行全局状态管理,核心 Store 定义在 `ui/src/store/modules/` 目录下。 +::: + +## userStore + +**文件**:`store/modules/user.ts` + +### 状态 + +| 字段 | 类型 | 说明 | +|------|------|------| +| `token` | string | JWT Token | +| `username` | string | 用户名 | +| `avatar` | string | 头像 URL | +| `permissions` | string[] | 权限列表 | +| `info` | object | 用户信息 | + +### Actions + +| Action | 说明 | 返回 | +|--------|------|------| +| `login(userInfo)` | 登录 | Promise | +| `GetInfo()` | 获取用户信息 | Promise | +| `logout()` | 登出 | Promise | + +### 使用示例 + +```typescript +import { useUserStore } from '@/store/modules/user'; + +const userStore = useUserStore(); + +// 登录 +await userStore.login({ username: 'admin', password: '123456' }); + +// 获取用户信息 +const info = userStore.getUserInfo; +const permissions = userStore.getPermissions; + +// 登出 +await userStore.logout(); +``` + +--- + +## asyncRouteStore + +**文件**:`store/modules/asyncRoute.ts` + +### 状态 + +| 字段 | 类型 | 说明 | +|------|------|------| +| `menus` | array | 菜单列表 | +| `keepAliveComponents` | string[] | 缓存的组件名 | +| `isDynamicAddedRoute` | boolean | 是否已添加动态路由 | + +### Actions + +| Action | 说明 | +|--------|------| +| `generateRoutes(userInfo)` | 从后端获取菜单并生成路由 | +| `setDynamicAddedRoute(flag)` | 设置动态路由添加状态 | +| `setKeepAliveComponents(components)` | 设置 keep-alive 组件列表 | + +--- + +## projectSettingStore + +**文件**:`store/modules/projectSetting.ts` + +| 字段 | 说明 | +|------|------| +| `navMode` | 导航模式(侧边/顶部) | +| `navTheme` | 导航主题(暗色/亮色) | +| `isShowMenuBar` | 是否显示菜单栏 | +| `isCollapse` | 菜单是否折叠 | + +--- + +## ossConfigStore + +**文件**:`store/modules/ossConfig.ts` + +| 字段 | 说明 | +|------|------| +| `ossConfig` | 云存储配置 | + +路由守卫中初始化,用于文件上传。 + +--- + +## designSettingStore + +**文件**:`store/modules/designSetting.ts` + +| 字段 | 说明 | +|------|------| +| `appTheme` | 主题色 | +| `darkMode` | 是否暗色模式 | + +--- + +## tabsViewStore + +**文件**:`store/modules/tabsView.ts` + +| 字段 | 说明 | +|------|------| +| `tabsList` | 标签页列表 | + +| Action | 说明 | +|--------|------| +| `addTab(route)` | 添加标签 | +| `removeTab(route)` | 关闭标签 | +| `closeOtherTabs(route)` | 关闭其他标签 | +| `closeAllTabs()` | 关闭所有标签 | + +--- + +## 在组件外使用 + +```typescript +// 在 setup 外使用 Store +import { useUserStoreWidthOut } from '@/store/modules/user'; + +const userStore = useUserStoreWidthOut(); +``` \ No newline at end of file diff --git a/RXThinkCMF_ThinkPhp6_EleVue/zh/architecture/backend-layer.md b/RXThinkCMF_ThinkPhp6_EleVue/zh/architecture/backend-layer.md new file mode 100644 index 0000000..36f4c46 --- /dev/null +++ b/RXThinkCMF_ThinkPhp6_EleVue/zh/architecture/backend-layer.md @@ -0,0 +1,429 @@ +# 4.3 后端分层架构 + +:::tip 概述 +后端采用"三层主干 + 三层支撑"架构:Controller → Logic → Model 构成主干链路,Validate、Service、Middleware 分别提供参数校验、通用能力、请求拦截等支撑。各层职责明确,层间通过方法调用串联,不越层访问。 +::: + +## 架构全景 + +```text +┌─────────────────────────────────────────────────────────────────────┐ +│ 支撑层 │ +│ │ +│ Validate(参数校验) Service(通用能力) Middleware(请求拦截) │ +│ app/validate/*.php app/service/*.php app/middleware/*.php │ +└──────────────────────────────┬──────────────────────────────────────┘ + │ 被主干层调用 + ▼ +┌─────────────────────────────────────────────────────────────────────┐ +│ 主干链路 │ +│ │ +│ ┌─────────────────────────────────────────────────────────────┐ │ +│ │ Controller(控制器层) │ │ +│ │ 接收请求 → 获取参数 → 调用 Logic → 封装响应 │ │ +│ │ 基类:BaseController | 文件:app/controller/*.php │ │ +│ └──────────────────────────┬──────────────────────────────────┘ │ +│ │ 调用 Logic 方法 │ +│ ▼ │ +│ ┌─────────────────────────────────────────────────────────────┐ │ +│ │ Logic(逻辑层) │ │ +│ │ 属性配置 → 参数校验 → 生命周期钩子 → 横切处理 → 调用 Model │ │ +│ │ 基类:BaseLogic / BaseTenantLogic | 文件:app/logic/*.php │ │ +│ └──────────────────────────┬──────────────────────────────────┘ │ +│ │ 调用 Model ORM │ +│ ▼ │ +│ ┌─────────────────────────────────────────────────────────────┐ │ +│ │ Model(模型层) │ │ +│ │ 表映射 → 软删除 → 审计字段 → ORM → SQL │ │ +│ │ 基类:BaseModel | 文件:app/model/*.php │ │ +│ └──────────────────────────┬──────────────────────────────────┘ │ +│ │ │ +│ ▼ │ +│ ┌─────────────────────────────────────────────────────────────┐ │ +│ │ Database(数据库) │ │ +│ └─────────────────────────────────────────────────────────────┘ │ +└─────────────────────────────────────────────────────────────────────┘ +``` + +## 各层职责详解 + +### Controller 层 + +**文件**:`app/controller/{Module}Controller.php` +**基类**:`BaseController` +**职责**:HTTP 请求入口,只做"搬运"不做"判断" + +| 职责 | 说明 | 示例 | +|------|------|------| +| 参数获取 | 从请求中提取参数 | `$this->getJsonBody()`、`$this->getParams()` | +| 调用 Logic | 委托业务逻辑 | `$this->logic->add($data)` | +| 响应封装 | 统一 JSON 格式返回 | `$this->success()`、`$this->fail()` | +| 注解标注 | 声明权限和日志 | `#[Permission]`、`#[Log]` | + +**Controller 不应该做:** 业务判断、数据库操作、文件处理、参数校验(特殊场景除外)。 + +```php +class ArticleController extends BaseController +{ + protected ArticleLogic $logic; + + protected function initialize(): void + { + $this->logic = new ArticleLogic(); + } + + // 标准 CRUD — 一行调用 + #[Permission('sys:article:page', '文章分页')] + public function page(): Json + { + return parent::_index($this->logic); + } + + #[Permission('sys:article:add', '添加文章')] + public function add(): Json + { + return parent::_add($this->logic, $this->getJsonBody()); + } +} +``` + +### Logic 层 + +**文件**:`app/logic/{Module}Logic.php` +**基类**:`BaseLogic`(系统共享)/ `BaseTenantLogic`(租户隔离) +**职责**:业务核心,通过"属性配置 + 生命周期钩子"声明式 CRUD + +| 职责 | 方式 | 说明 | +|------|------|------| +| 行为定义 | 属性配置 | `$pageLikeFields`、`$uniqueFields`、`$validateClass` 等 | +| 业务定制 | 生命周期钩子 | `beforeAdd`、`afterUpdate`、`beforeDelete` 等 | +| 自动校验 | `$validateClass` | add/update 时自动调用验证器 | +| 横切处理 | 属性驱动 | 文件字段、富文本、枚举映射、数据权限、租户隔离 | + +**Logic 不应该做:** 直接 HTTP 交互、直接返回 JSON 响应。 + +```php +class ArticleLogic extends BaseTenantLogic +{ + /** + * 关联的模型类 + * + * @var string + */ + protected string $modelClass = Article::class; + + /** + * 参数验证器类 + * + * @var string + */ + protected string $validateClass = ArticleValidate::class; + + /** + * 需要自动处理的文件上传字段 + * + * @var array + */ + protected array $fileFields = ['cover']; + + /** + * LIKE 模糊匹配字段 + * + * @var array + */ + protected array $pageLikeFields = ['title']; + + /** + * 精确匹配字段 + * + * @var array + */ + protected array $pageEqFields = ['status', 'category_id']; + + /** + * 唯一性校验字段 + * + * @var array + */ + protected array $uniqueFields = ['title']; + + /** + * 枚举显示名映射 + * + * @var array + */ + protected array $serializeMaps = ['status' => 'article_status']; + + /** + * 新增前处理:设置默认排序值 + * + * @param array $data 待新增的数据 + * @return array 处理后的数据 + */ + protected function beforeAdd(array $data): array + { + $data['sort'] = $data['sort'] ?? 0; + return $data; + } +} +``` + +### Model 层 + +**文件**:`app/model/{TableName}.php` +**基类**:`BaseModel` +**职责**:数据持久化,只做表映射和自动维护 + +| 职责 | 说明 | +|------|------| +| 表映射 | `protected $name = 'article'` | +| 软删除 | 全局查询范围自动过滤 `is_delete=1` | +| 审计字段 | `onBeforeInsert`/`onBeforeUpdate` 自动填充 | +| 关联关系 | `hasMany`、`belongsTo` 等 | + +**Model 不应该做:** 业务判断、复杂查询逻辑(交给 Logic)。 + +```php +class Article extends BaseModel +{ + protected $name = 'article'; + + // 关联关系(按需定义) + public function category() + { + return $this->belongsTo(Category::class, 'category_id'); + } +} +``` + +### Validate 层 + +**文件**:`app/validate/{Module}Validate.php` +**基类**:`think\Validate` +**职责**:参数格式校验,定义规则 + 场景 + +```php +class ArticleValidate extends Validate +{ + protected $rule = [ + 'title' => 'require|max:200', + ]; + protected $message = [ + 'title.require' => '文章标题不能为空', + ]; + protected $scene = [ + 'add' => ['title'], + 'update' => ['title'], + ]; +} +``` + +**触发方式:** Logic 配置 `$validateClass` 后,add/update 自动调用。 + +### Service 层 + +**文件**:`app/service/{Name}Service.php` +**职责**:跨模块通用能力,无状态、无副作用 + +| 服务 | 能力 | +|------|------| +| `DictService` | 字典查询(两级缓存) | +| `PasswordService` | 密码加密(bcrypt 双重加盐) | +| `JwtService` | Token 生成/刷新/解析 | +| `ExcelService` | Excel 导入导出 | +| `CaptchaService` | 图片验证码 | + +**Service 不应该做:** 持有状态、直接操作数据库(通过 Model 静态方法例外)。 + +### Middleware 层 + +**文件**:`app/middleware/{Name}Middleware.php` +**职责**:请求拦截,在 Controller 前后执行 + +```text +CorsMiddleware → AuthMiddleware → TenantMiddleware → DemoMiddleware → LogMiddleware → Controller +``` + +| 中间件 | 作用 | 注入数据 | +|--------|------|----------| +| `AuthMiddleware` | JWT 认证 + 权限校验 | `$request->userInfo` | +| `TenantMiddleware` | 租户上下文 | `$request->tenantId` | +| `DemoMiddleware` | 演示环境拦截 | — | +| `LogMiddleware` | 操作日志记录 | — | + +## 各层方法对照表 + +### 分页查询链路 + +| 层 | 方法 | 输入 | 输出 | +|----|------|------|------| +| Controller | `_index($logic)` | HTTP 请求参数 | Json 响应 | +| Logic | `pageList($params, $where, $with)` | 参数数组 | `['records'=>[], 'total'=>int, ...]` | +| Model | `Model::page($current, $size)->select()` | 分页条件 | Collection | + +### 新增链路 + +| 层 | 方法 | 输入 | 输出 | +|----|------|------|------| +| Controller | `_add($logic, $data)` 或 `$this->logic->add($data)` | Json 请求体 | Json 响应 | +| Logic | `add($data)` | 关联数组 | `int`(新记录 ID) | +| Model | `Model::create($data)` | 关联数组 | Model 实例 | + +**Logic::add 内部流程:** +`beforeAdd` → `validateData` → `camel_to_snake` → `processTenantId` → `processFile` → `processContent` → `filterTableFields` → `checkUnique` → `Model::create` → `afterAdd` + +### 修改链路 + +| 层 | 方法 | 输入 | 输出 | +|----|------|------|------| +| Controller | `_edit($logic, $id, $data)` | id + Json 请求体 | Json 响应 | +| Logic | `update($id, $data)` | id + 关联数组 | `bool` | +| Model | `Model::find($id)->save($data)` | 模型实例 + 关联数组 | `bool` | + +### 删除链路 + +| 层 | 方法 | 输入 | 输出 | +|----|------|------|------| +| Controller | `_remove($logic, $id)` | id | Json 响应 | +| Logic | `delete($id)` | id | `bool` | +| Model | `$model->save(['is_delete' => 1])` | 软删除标记 | `bool` | + +## 数据流向 + +### 请求方向(前端 → 后端 → 数据库) + +```text +前端请求(camelCase JSON) + │ + ▼ +BaseController::getJsonBody() ← 获取原始参数 + │ + ▼ +BaseLogic::beforeAdd($data) ← 钩子:预处理 + │ + ▼ +BaseLogic::validateData($data) ← 自动参数校验 + │ + ▼ +BaseLogic::convertQueryParams() ← camelCase → snake_case + │ + ▼ +BaseLogic::processFileFieldsOnSave ← 文件字段处理 + │ + ▼ +BaseLogic::filterTableFields() ← 过滤非数据库字段 + │ + ▼ +BaseLogic::checkUnique() ← 唯一性校验 + │ + ▼ +BaseModel::create() ← 审计字段自动填充 + │ + ▼ +Database(snake_case) +``` + +### 响应方向(数据库 → 后端 → 前端) + +```text +Database(snake_case 数据) + │ + ▼ +BaseModel ← ORM 查询 + │ + ▼ +BaseLogic 处理 + │ processFileFieldsOnRead() ← 文件字段补全域名 + │ processContentFieldsOnRead() ← 富文本占位符替换 + │ processSerializeMaps() ← 枚举显示名补全 + │ afterDetail() ← 钩子:补充关联数据 + ▼ +Result::convertData() ← snake_case → camelCase + │ + ▼ +JSON 响应 → 前端 +``` + +## 异常处理传播 + +```text +Model 层异常(如数据库连接失败) + │ + ▼ 向上抛出 +Logic 层异常(如 beforeDelete 抛出业务异常、validateData 抛出 ValidateException) + │ + ▼ 向上抛出 +Controller 层 + │ 方式一:try-catch 捕获,返回 fail() + │ 方式二:不捕获,由 ExceptionHandle 统一处理 + ▼ +ExceptionHandle::render() + │ ValidateException → {"code":1, "msg":"参数验证失败", "data":"..."} + │ ModelNotFoundException → {"code":1, "msg":"数据不存在"} + │ RuntimeException(401) → {"code":1, "msg":"未授权"} + │ 其他异常 → {"code":1, "msg":"操作失败"} + ▼ +JSON 响应 +``` + +**推荐做法:** +- Controller 通过 `_add`/`_edit`/`_remove` 调用时,不捕获异常,交给 ExceptionHandle +- Controller 直接调用 `$this->logic->add()` 时,try-catch 捕获并返回 `$this->fail()` +- Logic 层的业务异常(如 beforeDelete 校验)直接 `throw new \Exception` + +## 完整请求示例 + +以"新增文章"为例,展示一个请求从进入到返回的完整链路: + +```text +POST /api/article/add +Content-Type: application/json +Authorization: Bearer + +{"title": "新文章", "categoryId": 1, "status": 0} +``` + +```text +1. CorsMiddleware → 放行(设置跨域头) +2. AuthMiddleware → 校验 Token → 注入 $request->userInfo +3. TenantMiddleware → 解析租户 → 注入 $request->tenantId +4. LogMiddleware → 记录日志(#[Log] 注解) + +5. ArticleController::add() + │ getJsonBody() → ["title"=>"新文章", "categoryId"=>1, "status"=>0] + │ + ▼ +6. ArticleLogic::add($data) + │ beforeAdd($data) → 设置默认 sort=0 + │ validateData($data, 'add') → 校验 title 必填 ✓ + │ camel_to_snake($data) → ["title"=>"新文章", "category_id"=>1, "status"=>0] + │ processTenantId($data) → 加入 tenant_id + │ processFileFieldsOnSave → cover 无值,跳过 + │ filterTableFields($data) → 过滤非表字段 + │ checkUnique($data) → title 唯一 ✓ + │ + ▼ +7. Article::create($data) + │ onBeforeInsert → 自动填充 create_user, create_time + │ + ▼ +8. Database INSERT + +9. ArticleLogic::afterAdd($id, $data) → 无额外操作 + +10. 返回 {"code":0, "ok":true, "msg":"添加成功", "data":{"id":42}} +``` + +## 层间通信规范 + +| 规则 | 说明 | +|------|------| +| Controller → Logic | ✅ 允许,标准调用 | +| Controller → Model | ❌ 禁止,越层 | +| Logic → Model | ✅ 允许,标准调用 | +| Logic → Service | ✅ 允许,调用通用能力 | +| Logic → Controller | ❌ 禁止,反向依赖 | +| Model → Logic | ❌ 禁止,反向依赖 | +| Model → Service | ⚠️ 慎用,仅静态工具方法 | +| Middleware → Controller | ✅ 通过 `$request` 注入数据 | +| Validate → Logic | ❌ 禁止,只做格式校验 | diff --git a/RXThinkCMF_ThinkPhp6_EleVue/zh/architecture/base-classes.md b/RXThinkCMF_ThinkPhp6_EleVue/zh/architecture/base-classes.md new file mode 100644 index 0000000..97dd121 --- /dev/null +++ b/RXThinkCMF_ThinkPhp6_EleVue/zh/architecture/base-classes.md @@ -0,0 +1,639 @@ +# 4.6 核心基类体系 + +:::tip 概述 +项目有四个核心基类,构成"控制器 → 逻辑层 → 模型"三层架构的骨架。所有业务模块都必须继承对应的基类,通过属性配置 + 生命周期钩子实现声明式 CRUD,子类只需声明"做什么"而不用写"怎么做"。 +::: + +## 基类继承关系 + +:::warning 继承规则 +- 系统共享模块(Menu、Dict、Config 等)继承 `BaseLogic` +- 租户隔离模块(User、Role、Article、Dept、Level 等)继承 `BaseTenantLogic` +- 所有模型继承 `BaseModel`,所有控制器继承 `BaseController` +::: + +```text +BaseController(控制器基类) + └── 所有 Controller + +BaseLogic(逻辑基类 - 系统共享模块) + ├── MenuLogic + ├── DictLogic + ├── ConfigLogic + └── ... + │ + └── BaseTenantLogic(租户逻辑基类,继承 BaseLogic) + ├── UserLogic + ├── RoleLogic + ├── ArticleLogic + ├── DeptLogic + ├── LevelLogic + └── ... + +BaseModel(模型基类,继承 ThinkPHP Model) + └── 所有 Model +``` + +## 请求处理全链路 + +理解基类体系的关键是理解一次请求如何流经各层: + +```text +HTTP 请求 + │ + ▼ +BaseController(参数获取、响应封装) + │ getJsonBody() / getParams() + │ success() / fail() / _page() + │ + ▼ +BaseLogic(业务编排、属性驱动) + │ validateData() → 自动参数校验 + │ beforeAdd() → 生命周期钩子 + │ add() → 文件处理、唯一性校验、写入 + │ afterAdd() → 关联表保存 + │ + ▼ +BaseModel(数据持久化) + │ onBeforeInsert → 审计字段自动填充 + │ soft_delete → 全局过滤已删除数据 + │ + ▼ +数据库 +``` + +--- + +## BaseController + +**文件**:`app/BaseController.php` + +### 设计要点 + +- 通用 CRUD 方法以 `_` 前缀命名,避免与子类路由方法冲突 +- 子类只需实现 `initialize()` 并实例化自己的 Logic 层 +- 登录用户信息来自 `AuthMiddleware` 注入的 `$request->userInfo` + +### 方法分类 + +```text +┌─────────────────────────────────────────────────────────────┐ +│ BaseController │ +├─────────────────────────────────────────────────────────────┤ +│ 统一响应 │ +│ success($data, $msg, $raw) 成功响应 │ +│ _page($result) 分页响应 │ +│ fail($msg, $code, $data) 失败响应 │ +├─────────────────────────────────────────────────────────────┤ +│ 参数获取 │ +│ getParam($name, $default) 获取单个参数 │ +│ getParams() 获取所有参数 │ +│ getPageParams() 获取分页参数(含边界限制) │ +│ getJsonBody() 获取 JSON 请求体 │ +├─────────────────────────────────────────────────────────────┤ +│ 登录用户 │ +│ getLoginUser() 获取用户信息对象 │ +│ getLoginUserId() 获取用户 ID(未登录返回 0) │ +│ getLoginUsername() 获取用户名(未登录返回 '') │ +├─────────────────────────────────────────────────────────────┤ +│ 通用 CRUD(子类通过 parent:: 调用) │ +│ _index($logic, $where, $with) 分页查询 │ +│ _list($logic, $where) 全量列表 │ +│ _treeList($logic, $where) 树形列表 │ +│ _detail($logic, $id) 详情查询 │ +│ _add($logic, $data) 新增 │ +│ _edit($logic, $id, $data) 修改 │ +│ _remove($logic, $id) 删除 │ +│ _batchRemove($logic, $ids) 批量删除 │ +├─────────────────────────────────────────────────────────────┤ +│ 手动验证(特殊场景) │ +│ validate($data, $validate) 数据验证 │ +└─────────────────────────────────────────────────────────────┘ +``` + +### 通用 CRUD 方法与 Logic 层的对应关系 + +| Controller 方法 | Logic 方法 | 说明 | +|---|---|---| +| `_index($logic)` | `$logic->pageList()` | 分页查询 | +| `_list($logic)` | `$logic->allList()` | 全量列表 | +| `_treeList($logic)` | `$logic->treeList()` | 树形列表 | +| `_detail($logic, $id)` | `$logic->detail($id)` | 详情查询 | +| `_add($logic, $data)` | `$logic->add($data)` | 新增 | +| `_edit($logic, $id, $data)` | `$logic->update($id, $data)` | 修改 | +| `_remove($logic, $id)` | `$logic->delete($id)` | 删除 | +| `_batchRemove($logic, $ids)` | `$logic->batchDelete($ids)` | 批量删除 | + +### 分页参数 + +`getPageParams()` 返回 `['pageNo' => int, 'pageSize' => int]`,带边界限制: + +- `pageNo` 最小为 1 +- `pageSize` 限制在 1~100,默认 20 + +前端传参示例:`GET /api/article/page?pageNo=1&pageSize=10&title=测试&status=1` + +### 子类实现示例 + +```php +class ArticleController extends BaseController +{ + protected ArticleLogic $logic; + + protected function initialize(): void + { + $this->logic = new ArticleLogic(); + } + + // 分页查询 — 一行搞定 + #[Permission('sys:article:list', '文章列表')] + public function page(): Json + { + return parent::_index($this->logic); + } + + // 详情 + #[Permission('sys:article:detail', '文章详情')] + public function detail(int $id): Json + { + return parent::_detail($this->logic, $id); + } + + // 新增(使用 _add 快捷方法) + #[Log('文章管理-新增', Log::TYPE_ADD, '新增文章:{title}')] + #[Permission('sys:article:add', '添加文章')] + public function add(): Json + { + return parent::_add($this->logic, $this->getJsonBody()); + } + + // 删除 — 一行搞定 + #[Permission('sys:article:delete', '删除文章')] + public function delete(int $id): Json + { + return parent::_remove($this->logic, $id); + } +} +``` + +--- + +## BaseLogic + +**文件**:`app/BaseLogic.php` + +BaseLogic 是整个项目的核心,通过"属性配置 + 生命周期钩子"实现声明式 CRUD。子类只需配置属性和按需重写钩子,即可获得完整的增删改查能力。 + +### 属性配置清单 + +| 属性 | 类型 | 说明 | 示例 | +|---|---|---|---| +| `modelClass` | string | 关联模型类名(**必须**) | `Article::class` | +| `validateClass` | string | 验证器类名(设置后 add/update 自动校验) | `ArticleValidate::class` | +| `fileFields` | array | 单文件字段列表 | `['cover', 'avatar']` | +| `multiFileFields` | array | 多文件字段列表(逗号分隔的多个URL) | `['images']` | +| `fileSaveDir` | string | 文件保存子目录 | `'article'` | +| `pageLikeFields` | array | LIKE 模糊匹配字段 | `['title', 'author']` | +| `pageEqFields` | array | 精确匹配字段 | `['status', 'category_id']` | +| `pageOrderBy` | array | 默认排序规则 | `['field'=>'sort','type'=>'asc']` | +| `uniqueFields` | array | 唯一性校验字段 | `['username', ['code','pid']]` | +| `serializeMaps` | array | 枚举显示名映射 | `['status'=>'article_status']` | +| `contentFields` | array | 富文本字段 | `['content']` | +| `dataScopeUserField` | string | 数据权限:归属字段 | `'create_user'` | +| `dataScopeDeptField` | string | 数据权限:部门字段 | `'dept_id'` | +| `tenantScopeField` | string | 租户隔离字段 | `'tenant_id'` | +| `treeParentField` | string | 树形结构父级字段 | `'parent_id'` | +| `treeLikeField` | string | 树形列表模糊搜索字段 | `'name'` | + +### 生命周期钩子 + +| 钩子 | 时机 | 用途 | 可拦截 | +|---|---|---|---| +| `beforeAdd($data)` | 新增前 | 修改数据、预处理,**返回处理后的 data** | ✅ 抛异常 | +| `afterAdd($id, $data)` | 新增后 | 保存关联表、发送通知 | ❌ | +| `beforeUpdate($id, $data)` | 修改前 | 修改数据、预处理,**返回处理后的 data** | ✅ 抛异常 | +| `afterUpdate($id, $data)` | 修改后 | 更新关联表 | ❌ | +| `beforeDelete($id)` | 删除前 | 检查子级、业务约束 | ✅ 抛异常 | +| `afterDelete($id)` | 删除后 | 清理关联数据 | ❌ | +| `beforeBatchDelete($ids)` | 批量删除前 | 整体校验 | ✅ 抛异常 | +| `afterBatchDelete($ids)` | 批量删除后 | 批量清理 | ❌ | +| `afterDetail($id, &$data)` | 详情查询后 | 补充关联数据、格式化输出(引用传递) | ❌ | +| `afterPageList(&$records, $params)` | 列表查询后 | 批量补充关联数据,分页 pageList 与全量 allList 均触发(引用传递) | ❌ | + +### 新增流程(add) + +```text +beforeAdd($data) ← 钩子:可修改数据,抛异常可拦截 + │ + ▼ +validateData($data, 'add') ← 自动参数校验(配置了 validateClass 时) + │ + ▼ +array_camel_to_snake($data) ← 键名 camelCase → snake_case + │ + ▼ +processTenantId($data) ← 自动填充 tenant_id + │ + ▼ +processFileFieldsOnSave ← 文件字段:临时文件 → 正式目录 + │ + ▼ +processContentFieldsOnSave ← 富文本:迁移媒体文件 + │ + ▼ +filterTableFields($data) ← 过滤非数据库字段 + │ + ▼ +checkUnique($data) ← 唯一性校验 + │ + ▼ +Model::create($data) ← 写入数据库 + │ + ▼ +afterAdd($id, $data) ← 钩子:保存关联表等副作用 +``` + +### 修改流程(update) + +```text +beforeUpdate($id, $data) ← 钩子 + │ + ▼ +validateData($data, 'update', $id) ← 自动参数校验 + │ + ▼ +array_camel_to_snake → 文件 → 富文本 → 过滤 → 唯一性(排除自身) + │ + ▼ +Model::find($id)->save($data) + │ + ▼ +afterUpdate($id, $data) +``` + +### 删除流程(delete) + +```text +beforeDelete($id) ← 钩子:抛异常可拦截 + │ + ▼ +is_delete = 1 ← 软删除 + │ + ▼ +afterDelete($id) +``` + +### 查询流程(pageList / allList) + +```text +buildQuery($model) ← 构建查询条件(LIKE + 精确匹配 + 数据权限 + 租户隔离) + │ + ▼ +applyOrderBy($model) ← 排序(前端传参优先,否则使用配置) + │ + ▼ +$page / $select ← 分页 / 全量查询 + │ + ▼ +processFileFieldsOnReadList ← 文件字段补全域名 + │ + ▼ +processSerializeMapsList ← 枚举显示名补全 + │ + ▼ +filterFields ← 字段过滤 + │ + ▼ +afterPageList(&$records) ← 钩子:批量补充关联数据(引用传递) +``` + +### 自动参数校验(validateClass) + +配置 `$validateClass` 属性后,`add`/`update` 时自动调用对应验证器校验参数,无需在 Controller 中手动调用。 + +```php +class ArticleLogic extends BaseLogic +{ + /** + * 关联的模型类 + * + * @var string + */ + protected string $modelClass = Article::class; + + /** + * 参数验证器类 + * + * @var string + */ + protected string $validateClass = \app\validate\ArticleValidate::class; + // ... +} +``` + +**工作原理:** + +1. `add()` 中:`beforeAdd` → `validateData($data, 'add')` → 后续入库 +2. `update()` 中:`beforeUpdate` → `validateData($data, 'update', $id)` → 后续入库 +3. 验证器按场景(`add`/`update`)校验,场景不存在时使用默认规则 +4. update 场景自动注入 `id` 到 data,确保 `unique` 规则排除当前记录 +5. 校验失败抛出 `ValidateException`,由 `ExceptionHandle` 统一返回 + +**覆盖范围:** 无论是 Controller 通过 `_add`/`_edit` 调用,还是 `import` 等内部调用都会生效。 + +**向后兼容:** `$validateClass` 默认为空字符串,不设置则不验证。 + +### 唯一性校验(uniqueFields) + +```php +// 全局唯一 +protected array $uniqueFields = ['username', 'mobile']; + +// 分组内唯一:code 在同一 pid 下唯一 +protected array $uniqueFields = [ + 'username', // 全局唯一 + ['code', 'pid'], // code 在同一 pid 下唯一 +]; +``` + +校验时机:add/update 时自动校验(在 `checkUnique()` 中执行)。错误提示格式:"字段中文名 + 已存在",可通过重写 `getFieldLabel()` 自定义。 + +### 查询条件构建 + +`buildQuery()` 按以下顺序叠加条件: + +1. 额外固定条件(代码层面通过 `$where` 传入) +2. LIKE 模糊匹配(`pageLikeFields` 中配置的字段) +3. 精确匹配(`pageEqFields` 中配置的字段) +4. 数据权限过滤(`applyDataScope`) +5. 租户隔离过滤(`applyTenantScope`) + +### 子类实现示例 + +```php +class ArticleLogic extends BaseLogic +{ + /** + * 关联的模型类 + * + * @var string + */ + protected string $modelClass = Article::class; + + /** + * 参数验证器类 + * + * @var string + */ + protected string $validateClass = \app\validate\ArticleValidate::class; + + /** + * 需要自动处理的文件上传字段 + * + * @var array + */ + protected array $fileFields = ['cover']; + + /** + * 文件保存的子目录 + * + * @var string + */ + protected string $fileSaveDir = 'article'; + + /** + * 富文本字段 + * + * @var array + */ + protected array $contentFields = ['content']; + + /** + * LIKE 模糊匹配字段 + * + * @var array + */ + protected array $pageLikeFields = ['title']; + + /** + * 精确匹配字段 + * + * @var array + */ + protected array $pageEqFields = ['status', 'category_id']; + + /** + * 默认排序规则 + * + * @var array + */ + protected array $pageOrderBy = ['field' => 'sort', 'type' => 'asc']; + + /** + * 唯一性校验字段 + * + * @var array + */ + protected array $uniqueFields = ['title']; + + /** + * 枚举显示名映射 + * + * @var array + */ + protected array $serializeMaps = ['status' => 'article_status']; + + /** + * 数据权限:归属字段名(用于按创建人过滤) + * + * @var string + */ + protected string $dataScopeUserField = 'create_user'; + + /** + * 新增前处理:设置默认排序值 + * + * @param array $data 待新增的数据 + * @return array 处理后的数据 + */ + protected function beforeAdd(array $data): array + { + $data['sort'] = $data['sort'] ?? 0; + return $data; + } +} +``` + +--- + +## BaseTenantLogic + +**文件**:`app/BaseTenantLogic.php` + +### 设计说明 + +BaseTenantLogic 继承 BaseLogic,**仅做一件事**:将 `tenantScopeField` 设为 `'tenant_id'`,从而让所有子类自动获得租户隔离能力。 + +```php +abstract class BaseTenantLogic extends BaseLogic +{ + /** + * 租户隔离字段名 + * + * @var string + */ + protected string $tenantScopeField = 'tenant_id'; +} +``` + +### 隔离机制(由 BaseLogic 实现) + +| 场景 | 行为 | +|---|---| +| **查询时** | `applyTenantScope` 自动叠加 `WHERE tenant_id = ?` 条件 | +| **新增时** | `processTenantId` 自动填充 `tenant_id` 字段 | +| **超级管理员** | `tenantId = 0` 时不过滤,可跨租户查看数据 | + +### 租户上下文来源 + +`TenantMiddleware` 解析并注入 `$request->tenantId`。 + +### 使用约定 + +- 需要租户隔离的模块继承此基类(如 User、Article、Dept、Level 等) +- 系统共享表不继承此基类,直接继承 BaseLogic(如 Menu、Role、Dict、Config 等) + +### 子类示例 + +```php +class ArticleLogic extends BaseTenantLogic // 注意:继承 BaseTenantLogic +{ + /** + * 关联的模型类 + * + * @var string + */ + protected string $modelClass = Article::class; + + /** + * 参数验证器类 + * + * @var string + */ + protected string $validateClass = \app\validate\ArticleValidate::class; + // tenantScopeField 已由基类设为 'tenant_id',无需再声明 + // ... +} +``` + +--- + +## BaseModel + +**文件**:`app/BaseModel.php` + +### 设计说明 + +BaseModel 继承 ThinkPHP 的 Model,提供两项横切能力: + +1. **软删除**:基于 `is_delete` 字段,不使用 ThinkPHP 内置的 `deleteTime` 机制 +2. **审计字段自动写入**:`create_user` / `create_time` / `update_user` / `update_time` + +### 软删除机制 + +```text +┌─────────────────────────────────────────────────────────┐ +│ 软删除机制 │ +├─────────────────────────────────────────────────────────┤ +│ │ +│ 全局查询范围 soft_delete: │ +│ 所有查询自动叠加 WHERE is_delete = 0 │ +│ → 已删除数据默认不可见 │ +│ │ +│ 软删除操作: │ +│ $model->softDelete() → UPDATE SET is_delete = 1 │ +│ BaseModel::batchSoftDelete($ids) → 批量软删除 │ +│ │ +│ 查询已删除数据: │ +│ Model::withoutGlobalScope(['soft_delete'])->... │ +│ │ +└─────────────────────────────────────────────────────────┘ +``` + +**方法列表:** + +| 方法 | 类型 | 说明 | +|---|---|---| +| `softDelete()` | 实例方法 | 设置 `is_delete = 1` | +| `batchSoftDelete($ids)` | 静态方法 | 批量软删除,先关闭全局范围再 whereIn 更新 | +| `scopeSoftDelete($query)` | 全局范围 | 自动过滤 `is_delete = 1` 的数据 | + +### 审计字段自动写入 + +通过模型事件自动填充,**无需手动处理**: + +| 事件 | 自动填充 | 说明 | +|---|---|---| +| `onBeforeInsert`(创建前) | `create_user`、`create_time` | 若调用方已显式传入,则不覆盖 | +| `onBeforeUpdate`(更新前) | `update_user`、`update_time` | `update_time` 始终覆盖 | + +用户来源:`AuthMiddleware` 注入的 `$request->userInfo->username`。若无登录用户(如控制台任务),则跳过用户字段,时间字段仍写入。 + +### 数据库字段规范 + +所有表建议包含以下公共字段: + +| 字段名 | 类型 | 说明 | +|---|---|---| +| `id` | int | 主键,自增 | +| `is_delete` | tinyint(1) | 软删除标记,0=正常 1=已删除 | +| `create_user` | varchar(50) | 创建人 | +| `create_time` | datetime | 创建时间 | +| `update_user` | varchar(50) | 更新人 | +| `update_time` | datetime | 更新时间 | + +### 使用示例 + +```php +// 模型定义 — 最简形式 +class Article extends BaseModel +{ + /** + * 对应数据库表名(不含前缀) + * + * @var string + */ + protected $name = 'article'; +} + +// 软删除 +$article = Article::find(1); +$article->softDelete(); // UPDATE article SET is_delete = 1 WHERE id = 1 + +// 批量软删除 +Article::batchSoftDelete([1, 2, 3]); + +// 查询已删除数据(临时关闭全局范围) +$all = Article::withoutGlobalScope(['soft_delete'])->select(); + +// 审计字段自动写入(无需手动处理) +Article::create(['title' => '测试']); +// 自动填充: create_user='admin', create_time='2026-09-22 12:00:00' +``` + +--- + +## 基类选择指南 + +| 场景 | 继承 | 原因 | +|---|---|---| +| 系统级共享数据(菜单、字典、配置) | `BaseLogic` | 不需要租户隔离 | +| 租户业务数据(用户、角色、文章、部门、职级) | `BaseTenantLogic` | 需要按租户隔离数据 | +| 所有数据模型 | `BaseModel` | 获得软删除 + 审计字段 | +| 所有控制器 | `BaseController` | 获得统一响应 + 通用 CRUD | + +:::tip 快速判断 +问自己:这个模块的数据是否需要按租户隔离? +- **是** → 继承 `BaseTenantLogic` +- **否** → 继承 `BaseLogic` +::: diff --git a/RXThinkCMF_ThinkPhp6_EleVue/zh/architecture/cache.md b/RXThinkCMF_ThinkPhp6_EleVue/zh/architecture/cache.md new file mode 100644 index 0000000..56007cf --- /dev/null +++ b/RXThinkCMF_ThinkPhp6_EleVue/zh/architecture/cache.md @@ -0,0 +1,90 @@ +# 4.8 缓存架构设计 + +:::tip 概述 +项目采用两级缓存架构:请求级缓存(内存)+ 持久缓存(file/Redis),大幅减少数据库查询。 +::: + +## 两级缓存架构 + +```text +┌─────────────────────────────────────────────────────────┐ +│ 第一级:请求级缓存(static 变量) │ +│ │ +│ 生命周期:单次 HTTP 请求 │ +│ 存储位置:PHP 内存(static $cache) │ +│ 速度:极快(纳秒级) │ +│ │ +│ 同一请求内多次查询同一数据,只查一次库或缓存 │ +└──────────────────────────┬──────────────────────────────┘ + │ 未命中 + ▼ +┌─────────────────────────────────────────────────────────┐ +│ 第二级:持久缓存(cache() 门面) │ +│ │ +│ 生命周期:跨请求复用 │ +│ 存储位置:file 或 Redis │ +│ 速度:快(毫秒级) │ +│ 有效期:1 小时(3600 秒) │ +│ │ +│ 跨请求复用,减少数据库压力 │ +└──────────────────────────┬──────────────────────────────┘ + │ 未命中 + ▼ +┌─────────────────────────────────────────────────────────┐ +│ 数据库查询 │ +│ │ +│ 查询后同时写入两级缓存 │ +└─────────────────────────────────────────────────────────┘ +``` + +## 使用两级缓存的服务 + +| 服务 | 缓存键格式 | 说明 | +|------|-----------|------| +| `DictService` | `dict_{code}` | 字典项映射 | +| `DictService` | `dict_full_{code}` | 字典项完整数据 | +| `ParamService` | `param_{code}` | 系统参数值 | + +## 缓存驱动配置 + +`.env` 配置: + +```ini +; 文件缓存(默认) +[CACHE] +DRIVER = file + +; Redis 缓存(推荐生产环境) +[CACHE] +DRIVER = redis +REDIS_HOST = 127.0.0.1 +REDIS_PORT = 6379 +REDIS_PASSWORD = +REDIS_DB = 0 +``` + +:::warning 生产环境建议使用 Redis +文件缓存在多进程/多服务器环境下无法共享,生产环境建议配置 Redis 缓存驱动。使用 Redis 前需安装 `php redis` 扩展。 +::: + +## 缓存清除 + +```php +// 清除指定字典缓存 +DictService::clearCache('gender'); + +// 清除所有字典缓存 +DictService::clearAllCache(); + +// 清除指定参数缓存 +ParamService::clearCache('DEFAULT_PASSWORD'); + +// 清除所有参数缓存 +ParamService::clearAllCache(); +``` + +对应接口: + +```text +GET /api/dict/refreshCache 刷新字典缓存 +``` \ No newline at end of file diff --git a/RXThinkCMF_ThinkPhp6_EleVue/zh/architecture/cross-cutting.md b/RXThinkCMF_ThinkPhp6_EleVue/zh/architecture/cross-cutting.md new file mode 100644 index 0000000..7156071 --- /dev/null +++ b/RXThinkCMF_ThinkPhp6_EleVue/zh/architecture/cross-cutting.md @@ -0,0 +1,536 @@ +# 4.7 横切机制 + +:::tip 概述 +横切机制是 BaseLogic/BaseModel 内置的自动处理能力,开发者通过属性配置启用,无需手动编写处理代码。项目共有 9 种横切机制,覆盖数据转换、文件处理、校验、权限、隔离等维度。 +::: + +## 横切机制总览 + +| 机制 | 配置位置 | 配置属性 | 触发时机 | +|------|---------|---------|---------| +| 驼峰/下划线转换 | `config/api.php` | `camel_snake_convert` | 请求入、响应出 | +| 文件字段处理 | Logic | `fileFields` + `fileSaveDir` | add/update 写入,detail/page 读取 | +| 多文件字段处理 | Logic | `multiFileFields` + `fileSaveDir` | add/update 写入,detail/page 读取 | +| 富文本占位符 | Logic | `contentFields` | add/update 写入,detail/page 读取 | +| 枚举显示名翻译 | Logic | `serializeMaps` | detail/page 查询后 | +| 自动参数校验 | Logic | `validateClass` | add/update 前 | +| 唯一性校验 | Logic | `uniqueFields` | add/update 时 | +| 数据权限过滤 | Logic | `dataScopeUserField` / `dataScopeDeptField` | 查询时 | +| 租户隔离 | Logic(继承 BaseTenantLogic) | `tenantScopeField` | 查询时 + 新增时 | +| 审计字段 + 软删除 | BaseModel | 自动生效 | 所有 CRUD | + +## 1. 驼峰/下划线自动转换 + +前端使用 camelCase,数据库使用 snake_case,框架自动完成双向转换。 + +### 转换流程 + +```text +请求方向(前端 → 数据库): + 前端传参:{ userName: "admin", createTime: "2026-01-01" } + │ + ▼ BaseLogic::convertQueryParams() + │ array_camel_to_snake($params, $except) + ▼ + 数据库操作:WHERE user_name = 'admin' AND create_time = '2026-01-01' + +响应方向(数据库 → 前端): + 数据库数据:{ user_name: "admin", create_time: "2026-01-01" } + │ + ▼ Result::convertData() + │ array_snake_to_camel($data) + ▼ + 前端接收:{ userName: "admin", createTime: "2026-01-01" } +``` + +### 排除项(不参与转换) + +以下参数保持 camelCase,不转为 snake_case: + +- `pageNo`、`pageSize` — 分页参数 +- `orderField`、`orderType` — 排序参数 +- `fields` — 字段过滤参数 + +### 配置开关 + +```php +// config/api.php +'camel_snake_convert' => env('api.camel_snake_convert', true), +``` + +```ini +# .env +API.CAMEL_SNAKE_CONVERT = true # 开启(默认) +API.CAMEL_SNAKE_CONVERT = false # 关闭(请求保持驼峰,响应保持下划线) +``` + +:::tip 开发者无需手动处理 +驼峰/下划线转换完全自动,开发者只需在前端用 camelCase,在数据库用 snake_case,框架自动处理双向转换。 +::: + +## 2. 文件字段自动处理 + +**配置方式**:Logic 中设置 `fileFields` 和 `fileSaveDir` + +```php +class ArticleLogic extends BaseTenantLogic +{ + protected array $fileFields = ['cover', 'attachment']; + protected string $fileSaveDir = 'article'; +} +``` + +### 写入时(add/update) + +```text +前端传入:cover = "https://temp.example.com/tmp/abc123.jpg" + │ + ▼ processFileFieldsOnSave() + │ 1. 检测字段值是否为临时文件 URL + │ 2. 调用 save_file() 将临时文件迁移到正式目录 + │ 3. 去掉域名,只存储相对路径 + ▼ +入库存储:cover = "/uploads/article/20260922/abc123.jpg" +``` + +### 读取时(detail/pageList/allList) + +```text +数据库读取:cover = "/uploads/article/20260922/abc123.jpg" + │ + ▼ processFileFieldsOnRead() + │ 调用 get_file_url() 补全域名 + ▼ +前端接收:cover = "https://cdn.example.com/uploads/article/20260922/abc123.jpg" +``` + +### 多个单文件字段 + +```php +protected array $fileFields = ['cover', 'avatar', 'attachment']; +// 三个字段都会自动处理 +``` + +## 3. 多文件字段自动处理 + +当一个字段存储多个文件URL(逗号分隔)时,使用 `multiFileFields`: + +**配置方式**:Logic 中设置 `multiFileFields`(复用 `fileSaveDir`) + +```php +class ArticleLogic extends BaseTenantLogic +{ + protected array $multiFileFields = ['images', 'attachments']; + protected string $fileSaveDir = 'article'; +} +``` + +**写入时(add/update)** + +```text +前端传入:images = "http://example.com/temp/a.png,http://example.com/temp/b.png" + │ + ▼ processMultiFileFieldsOnSave() + │ 1. 按逗号拆分为数组 + │ 2. 逐个调用 save_file() 迁移临时文件并去掉域名 + │ 3. 重新用逗号拼接 + ▼ +入库存储:images = "article/20260922/a.png,article/20260922/b.png" +``` + +**读取时(detail/pageList/allList)** + +```text +数据库读取:images = "article/20260922/a.png,article/20260922/b.png" + │ + ▼ processMultiFileFieldsOnRead() + │ 1. 按逗号拆分为数组 + │ 2. 逐个调用 get_file_url() 补全域名 + │ 3. 重新用逗号拼接 + ▼ +前端接收:images = "http://cdn.example.com/article/20260922/a.png,http://cdn.example.com/article/20260922/b.png" +``` + +## 4. 富文本占位符机制 + +**配置方式**:Logic 中设置 `contentFields` + +```php +class ArticleLogic extends BaseTenantLogic +{ + protected array $contentFields = ['content']; +} +``` + +### 写入时 + +```text +前端传入:content = '

文章内容

' + │ + ▼ processContentFieldsOnSave() + │ 1. 解析 HTML 中的 /