init project
This commit is contained in:
@@ -1,3 +1,5 @@
|
|||||||
|
/.npmrc
|
||||||
|
|
||||||
# ---> JetBrains
|
# ---> JetBrains
|
||||||
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio, WebStorm and Rider
|
||||||
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
|
||||||
|
|||||||
Generated
+10
@@ -0,0 +1,10 @@
|
|||||||
|
# Default ignored files
|
||||||
|
/shelf/
|
||||||
|
/workspace.xml
|
||||||
|
# Editor-based HTTP Client requests
|
||||||
|
/httpRequests/
|
||||||
|
# Ignored default folder with query files
|
||||||
|
/queries/
|
||||||
|
# Datasource local storage ignored files
|
||||||
|
/dataSources/
|
||||||
|
/dataSources.local.xml
|
||||||
Generated
+8
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="ProjectModuleManager">
|
||||||
|
<modules>
|
||||||
|
<module fileurl="file://$PROJECT_DIR$/.idea/timi-web-api.iml" filepath="$PROJECT_DIR$/.idea/timi-web-api.iml" />
|
||||||
|
</modules>
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
Generated
+8
@@ -0,0 +1,8 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<module type="WEB_MODULE" version="4">
|
||||||
|
<component name="NewModuleRootManager">
|
||||||
|
<content url="file://$MODULE_DIR$" />
|
||||||
|
<orderEntry type="inheritedJdk" />
|
||||||
|
<orderEntry type="sourceFolder" forTests="false" />
|
||||||
|
</component>
|
||||||
|
</module>
|
||||||
Generated
+6
@@ -0,0 +1,6 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project version="4">
|
||||||
|
<component name="VcsDirectoryMappings">
|
||||||
|
<mapping directory="" vcs="Git" />
|
||||||
|
</component>
|
||||||
|
</project>
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
/cache
|
||||||
|
/project.local.yml
|
||||||
@@ -0,0 +1,153 @@
|
|||||||
|
# the name by which the project can be referenced within Serena
|
||||||
|
project_name: "timi-web-api"
|
||||||
|
|
||||||
|
|
||||||
|
# list of languages for which language servers are started; choose from:
|
||||||
|
# al bash clojure cpp csharp
|
||||||
|
# csharp_omnisharp dart elixir elm erlang
|
||||||
|
# fortran fsharp go groovy haskell
|
||||||
|
# haxe java julia kotlin lua
|
||||||
|
# markdown
|
||||||
|
# matlab nix pascal perl php
|
||||||
|
# php_phpactor powershell python python_jedi r
|
||||||
|
# rego ruby ruby_solargraph rust scala
|
||||||
|
# swift terraform toml typescript typescript_vts
|
||||||
|
# vue yaml zig
|
||||||
|
# (This list may be outdated. For the current list, see values of Language enum here:
|
||||||
|
# https://github.com/oraios/serena/blob/main/src/solidlsp/ls_config.py
|
||||||
|
# For some languages, there are alternative language servers, e.g. csharp_omnisharp, ruby_solargraph.)
|
||||||
|
# Note:
|
||||||
|
# - For C, use cpp
|
||||||
|
# - For JavaScript, use typescript
|
||||||
|
# - For Free Pascal/Lazarus, use pascal
|
||||||
|
# Special requirements:
|
||||||
|
# Some languages require additional setup/installations.
|
||||||
|
# See here for details: https://oraios.github.io/serena/01-about/020_programming-languages.html#language-servers
|
||||||
|
# When using multiple languages, the first language server that supports a given file will be used for that file.
|
||||||
|
# The first language is the default language and the respective language server will be used as a fallback.
|
||||||
|
# Note that when using the JetBrains backend, language servers are not used and this list is correspondingly ignored.
|
||||||
|
languages: []
|
||||||
|
|
||||||
|
# the encoding used by text files in the project
|
||||||
|
# For a list of possible encodings, see https://docs.python.org/3.11/library/codecs.html#standard-encodings
|
||||||
|
encoding: "utf-8"
|
||||||
|
|
||||||
|
# line ending convention to use when writing source files.
|
||||||
|
# Possible values: unset (use global setting), "lf", "crlf", or "native" (platform default)
|
||||||
|
# This does not affect Serena's own files (e.g. memories and configuration files), which always use native line endings.
|
||||||
|
line_ending:
|
||||||
|
|
||||||
|
# The language backend to use for this project.
|
||||||
|
# If not set, the global setting from serena_config.yml is used.
|
||||||
|
# Valid values: LSP, JetBrains
|
||||||
|
# Note: the backend is fixed at startup. If a project with a different backend
|
||||||
|
# is activated post-init, an error will be returned.
|
||||||
|
language_backend:
|
||||||
|
|
||||||
|
# whether to use project's .gitignore files to ignore files
|
||||||
|
ignore_all_files_in_gitignore: true
|
||||||
|
|
||||||
|
# advanced configuration option allowing to configure language server-specific options.
|
||||||
|
# Maps the language key to the options.
|
||||||
|
# Have a look at the docstring of the constructors of the LS implementations within solidlsp (e.g., for C# or PHP) to see which options are available.
|
||||||
|
# No documentation on options means no options are available.
|
||||||
|
ls_specific_settings: {}
|
||||||
|
|
||||||
|
# list of additional paths to ignore in this project.
|
||||||
|
# Same syntax as gitignore, so you can use * and **.
|
||||||
|
# Note: global ignored_paths from serena_config.yml are also applied additively.
|
||||||
|
ignored_paths: []
|
||||||
|
|
||||||
|
# whether the project is in read-only mode
|
||||||
|
# If set to true, all editing tools will be disabled and attempts to use them will result in an error
|
||||||
|
# Added on 2025-04-18
|
||||||
|
read_only: false
|
||||||
|
|
||||||
|
# list of tool names to exclude.
|
||||||
|
# This extends the existing exclusions (e.g. from the global configuration)
|
||||||
|
#
|
||||||
|
# Below is the complete list of tools for convenience.
|
||||||
|
# To make sure you have the latest list of tools, and to view their descriptions,
|
||||||
|
# execute `uv run scripts/print_tool_overview.py`.
|
||||||
|
#
|
||||||
|
# * `activate_project`: Activates a project based on the project name or path.
|
||||||
|
# * `check_onboarding_performed`: Checks whether project onboarding was already performed.
|
||||||
|
# * `create_text_file`: Creates/overwrites a file in the project directory.
|
||||||
|
# * `delete_memory`: Delete a memory file. Should only happen if a user asks for it explicitly,
|
||||||
|
# for example by saying that the information retrieved from a memory file is no longer correct
|
||||||
|
# or no longer relevant for the project.
|
||||||
|
# * `edit_memory`: Replaces content matching a regular expression in a memory.
|
||||||
|
# * `execute_shell_command`: Executes a shell command.
|
||||||
|
# * `find_file`: Finds files in the given relative paths
|
||||||
|
# * `find_referencing_symbols`: Finds symbols that reference the given symbol using the language server backend
|
||||||
|
# * `find_symbol`: Performs a global (or local) search using the language server backend.
|
||||||
|
# * `get_current_config`: Prints the current configuration of the agent, including the active and available projects, tools, contexts, and modes.
|
||||||
|
# * `get_symbols_overview`: Gets an overview of the top-level symbols defined in a given file.
|
||||||
|
# * `initial_instructions`: Provides instructions Serena usage (i.e. the 'Serena Instructions Manual')
|
||||||
|
# for clients that do not read the initial instructions when the MCP server is connected.
|
||||||
|
# * `insert_after_symbol`: Inserts content after the end of the definition of a given symbol.
|
||||||
|
# * `insert_before_symbol`: Inserts content before the beginning of the definition of a given symbol.
|
||||||
|
# * `list_dir`: Lists files and directories in the given directory (optionally with recursion).
|
||||||
|
# * `list_memories`: List available memories. Any memory can be read using the `read_memory` tool.
|
||||||
|
# * `onboarding`: Performs onboarding (identifying the project structure and essential tasks, e.g. for testing or building).
|
||||||
|
# * `read_file`: Reads a file within the project directory.
|
||||||
|
# * `read_memory`: Read the content of a memory file. This tool should only be used if the information
|
||||||
|
# is relevant to the current task. You can infer whether the information
|
||||||
|
# is relevant from the memory file name.
|
||||||
|
# You should not read the same memory file multiple times in the same conversation.
|
||||||
|
# * `rename_memory`: Renames or moves a memory. Moving between project and global scope is supported
|
||||||
|
# (e.g., renaming "global/foo" to "bar" moves it from global to project scope).
|
||||||
|
# * `rename_symbol`: Renames a symbol throughout the codebase using language server refactoring capabilities.
|
||||||
|
# For JB, we use a separate tool.
|
||||||
|
# * `replace_content`: Replaces content in a file (optionally using regular expressions).
|
||||||
|
# * `replace_symbol_body`: Replaces the full definition of a symbol using the language server backend.
|
||||||
|
# * `safe_delete_symbol`:
|
||||||
|
# * `search_for_pattern`: Performs a search for a pattern in the project.
|
||||||
|
# * `write_memory`: Write some information (utf-8-encoded) about this project that can be useful for future tasks to a memory in md format.
|
||||||
|
# The memory name should be meaningful.
|
||||||
|
excluded_tools: []
|
||||||
|
|
||||||
|
# list of tools to include that would otherwise be disabled (particularly optional tools that are disabled by default).
|
||||||
|
# This extends the existing inclusions (e.g. from the global configuration).
|
||||||
|
included_optional_tools: []
|
||||||
|
|
||||||
|
# fixed set of tools to use as the base tool set (if non-empty), replacing Serena's default set of tools.
|
||||||
|
# This cannot be combined with non-empty excluded_tools or included_optional_tools.
|
||||||
|
fixed_tools: []
|
||||||
|
|
||||||
|
# list of mode names to that are always to be included in the set of active modes
|
||||||
|
# The full set of modes to be activated is base_modes + default_modes.
|
||||||
|
# If the setting is undefined, the base_modes from the global configuration (serena_config.yml) apply.
|
||||||
|
# Otherwise, this setting overrides the global configuration.
|
||||||
|
# Set this to [] to disable base modes for this project.
|
||||||
|
# Set this to a list of mode names to always include the respective modes for this project.
|
||||||
|
base_modes:
|
||||||
|
|
||||||
|
# list of mode names that are to be activated by default.
|
||||||
|
# The full set of modes to be activated is base_modes + default_modes.
|
||||||
|
# If the setting is undefined, the default_modes from the global configuration (serena_config.yml) apply.
|
||||||
|
# Otherwise, this overrides the setting from the global configuration (serena_config.yml).
|
||||||
|
# This setting can, in turn, be overridden by CLI parameters (--mode).
|
||||||
|
default_modes:
|
||||||
|
|
||||||
|
# initial prompt for the project. It will always be given to the LLM upon activating the project
|
||||||
|
# (contrary to the memories, which are loaded on demand).
|
||||||
|
initial_prompt: ""
|
||||||
|
|
||||||
|
# time budget (seconds) per tool call for the retrieval of additional symbol information
|
||||||
|
# such as docstrings or parameter information.
|
||||||
|
# This overrides the corresponding setting in the global configuration; see the documentation there.
|
||||||
|
# If null or missing, use the setting from the global configuration.
|
||||||
|
symbol_info_budget:
|
||||||
|
|
||||||
|
# list of regex patterns which, when matched, mark a memory entry as read‑only.
|
||||||
|
# Extends the list from the global configuration, merging the two lists.
|
||||||
|
read_only_memory_patterns: []
|
||||||
|
|
||||||
|
# list of regex patterns for memories to completely ignore.
|
||||||
|
# Matching memories will not appear in list_memories or activate_project output
|
||||||
|
# and cannot be accessed via read_memory or write_memory.
|
||||||
|
# To access ignored memory files, use the read_file tool on the raw file path.
|
||||||
|
# Extends the list from the global configuration, merging the two lists.
|
||||||
|
# Example: ["_archive/.*", "_episodes/.*"]
|
||||||
|
ignored_memory_patterns: []
|
||||||
@@ -1,2 +1,32 @@
|
|||||||
# timi-web-lib
|
# timi-web-api
|
||||||
|
|
||||||
|
Timi Web 的业务 API、领域类型、用户状态和权限相关能力。
|
||||||
|
|
||||||
|
通用组件与工具位于 [timi-web](https://gitea.imyeyu.com/timi/timi-web),本项目使用自己的 Axios 网络层,并依赖它提供 Cookie、Storage 等通用工具。
|
||||||
|
|
||||||
|
## 依赖方向
|
||||||
|
|
||||||
|
本项目是独立的业务能力包。只有需要业务 API、领域类型、用户状态或权限能力的项目才安装 `timi-web-api`;只使用通用组件的项目不需要安装本项目。
|
||||||
|
|
||||||
|
```text
|
||||||
|
只使用通用组件的业务项目 -> timi-tdesign-mobile / timi-tdesign-pc -> timi-web
|
||||||
|
需要业务能力的业务项目 -> timi-web-api -> timi-web
|
||||||
|
```
|
||||||
|
|
||||||
|
`timi-tdesign-mobile` 和 `timi-tdesign-pc` 不依赖 `timi-web-api`。其中的文章、通知组件只约束 `Article`、`Attachment`、`NotifyDetail` 的最小字段,业务对象可以携带自定义字段,并通过上传和资源解析回调接入任意后端。
|
||||||
|
|
||||||
|
## 目录
|
||||||
|
|
||||||
|
- `src/api`:业务接口封装
|
||||||
|
- `src/types`:业务领域类型
|
||||||
|
- `src/store`:用户登录状态
|
||||||
|
- `src/utils`:权限指令和配置映射
|
||||||
|
- `src/editor`:文章、通知编辑器的数据适配
|
||||||
|
|
||||||
|
## 检查
|
||||||
|
|
||||||
|
```bash
|
||||||
|
pnpm install
|
||||||
|
pnpm run check
|
||||||
|
```
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
{
|
||||||
|
"name": "timi-web-api",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "Timi Web 业务 API、领域类型和用户状态库",
|
||||||
|
"main": "./dist/index.js",
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"module": "./dist/index.js",
|
||||||
|
"private": false,
|
||||||
|
"license": "MIT",
|
||||||
|
"files": [
|
||||||
|
"dist/**",
|
||||||
|
"src/**",
|
||||||
|
"README.md",
|
||||||
|
"package.json"
|
||||||
|
],
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"types": "./dist/index.d.ts",
|
||||||
|
"import": "./dist/index.js",
|
||||||
|
"require": "./dist/index.js"
|
||||||
|
},
|
||||||
|
"./editor": {
|
||||||
|
"types": "./dist/editor/index.d.ts",
|
||||||
|
"import": "./dist/editor/index.js",
|
||||||
|
"require": "./dist/editor/index.js"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsc --outDir dist --declaration --declarationMap",
|
||||||
|
"check": "tsc --noEmit"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"timi-web": "^0.0.24",
|
||||||
|
"vue": "^3.5.26"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "^1.18.0",
|
||||||
|
"pinia": "^3.0.2"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"typescript": "^5.9.3"
|
||||||
|
}
|
||||||
|
}
|
||||||
Generated
+815
@@ -0,0 +1,815 @@
|
|||||||
|
lockfileVersion: '9.0'
|
||||||
|
|
||||||
|
settings:
|
||||||
|
autoInstallPeers: true
|
||||||
|
excludeLinksFromLockfile: false
|
||||||
|
|
||||||
|
importers:
|
||||||
|
|
||||||
|
.:
|
||||||
|
dependencies:
|
||||||
|
axios:
|
||||||
|
specifier: ^1.18.0
|
||||||
|
version: 1.18.0
|
||||||
|
pinia:
|
||||||
|
specifier: ^3.0.2
|
||||||
|
version: 3.0.4(typescript@5.9.3)(vue@3.5.41(typescript@5.9.3))
|
||||||
|
timi-web:
|
||||||
|
specifier: ^0.0.24
|
||||||
|
version: 0.0.24(typescript@5.9.3)(vue@3.5.41(typescript@5.9.3))
|
||||||
|
vue:
|
||||||
|
specifier: ^3.5.26
|
||||||
|
version: 3.5.41(typescript@5.9.3)
|
||||||
|
devDependencies:
|
||||||
|
typescript:
|
||||||
|
specifier: ^5.9.3
|
||||||
|
version: 5.9.3
|
||||||
|
|
||||||
|
packages:
|
||||||
|
|
||||||
|
'@babel/helper-string-parser@7.29.7':
|
||||||
|
resolution: {integrity: sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==}
|
||||||
|
engines: {node: '>=6.9.0'}
|
||||||
|
|
||||||
|
'@babel/helper-validator-identifier@7.29.7':
|
||||||
|
resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==}
|
||||||
|
engines: {node: '>=6.9.0'}
|
||||||
|
|
||||||
|
'@babel/parser@7.29.8':
|
||||||
|
resolution: {integrity: sha512-E8lTAYNB1KW+FH+VGJuZM1ioAx2E6oVlvQFRrf5P8ZZmsiJXYAD9vTFV7yyEURNzgh1dFqMZuO6tUwcARbqFCA==}
|
||||||
|
engines: {node: '>=6.0.0'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
'@babel/types@7.29.8':
|
||||||
|
resolution: {integrity: sha512-Vj1jF3cPfxg7OAfoI7QnVKLoILlm2JF9pnVHrX8qx7AHMiYWT+NDAA7jChlNgRS4WTLc/fD1lXLmPixluj+3Gg==}
|
||||||
|
engines: {node: '>=6.9.0'}
|
||||||
|
|
||||||
|
'@jridgewell/gen-mapping@0.3.13':
|
||||||
|
resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==}
|
||||||
|
|
||||||
|
'@jridgewell/resolve-uri@3.1.2':
|
||||||
|
resolution: {integrity: sha512-bRISgCIjP20/tbWSPWMEi54QVPRZExkuD9lJL+UIxUKtwVJA8wW1Trb1jMs1RFXo1CBTNZ/5hpC9QvmKWdopKw==}
|
||||||
|
engines: {node: '>=6.0.0'}
|
||||||
|
|
||||||
|
'@jridgewell/source-map@0.3.11':
|
||||||
|
resolution: {integrity: sha512-ZMp1V8ZFcPG5dIWnQLr3NSI1MiCU7UETdS/A0G8V/XWHvJv3ZsFqutJn1Y5RPmAPX6F3BiE397OqveU/9NCuIA==}
|
||||||
|
|
||||||
|
'@jridgewell/sourcemap-codec@1.5.5':
|
||||||
|
resolution: {integrity: sha512-cYQ9310grqxueWbl+WuIUIaiUaDcj7WOq5fVhEljNVgRfOUhY9fy2zTvfoqWsnebh8Sl70VScFbICvJnLKB0Og==}
|
||||||
|
|
||||||
|
'@jridgewell/trace-mapping@0.3.31':
|
||||||
|
resolution: {integrity: sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==}
|
||||||
|
|
||||||
|
'@vue/compiler-core@3.5.41':
|
||||||
|
resolution: {integrity: sha512-q0Xtv/F9w2YO/7htQhtiL+Ev2WCJbe5N2hc+XfgyKkEKqWpSxknmT8QOuGdEKNdjPq0c3F7rNpFkTo3Kfrm7pg==}
|
||||||
|
|
||||||
|
'@vue/compiler-dom@3.5.41':
|
||||||
|
resolution: {integrity: sha512-oKacVfNglLvGjnS6BXOlGL7EyG2h8X03pqXCjzotRZUaXGjbrTJUnVAQjrCqUnS+lyu31nwQjZY/d817GmCnfw==}
|
||||||
|
|
||||||
|
'@vue/compiler-sfc@3.5.41':
|
||||||
|
resolution: {integrity: sha512-XJhip7R2wy6vX3knCxdZN4KracFaZUef58s1KYewqluedHIJaPIVfXoYT7MF1F8nCvv6k8bWWxDC8opMkg1VTQ==}
|
||||||
|
|
||||||
|
'@vue/compiler-ssr@3.5.41':
|
||||||
|
resolution: {integrity: sha512-U3v5OejKEGqOI0Wy0+Sz7hGuIFZHA4LSXzrNM3IMIeDyJEBBfTpX26n3SDgToRpP2bLc9FfI2j/kSgcJ8Emq5A==}
|
||||||
|
|
||||||
|
'@vue/devtools-api@7.7.10':
|
||||||
|
resolution: {integrity: sha512-KxtEpUOOpFz/qOGRrAwA36QF7DqIA+FXgCYit9mk9wjbaZt0sXOFz81ElOZtKA4HbWHUdwNjZHBFsFFyp5BZiA==}
|
||||||
|
|
||||||
|
'@vue/devtools-kit@7.7.10':
|
||||||
|
resolution: {integrity: sha512-3WNi2Kq4tbpVbmhml7RiphmAt0279oh3fKNeWMQIrltfX8Q91b4i5PL8DtyNKdwmcsGrV4fg+erwWOmD05CLIw==}
|
||||||
|
|
||||||
|
'@vue/devtools-shared@7.7.10':
|
||||||
|
resolution: {integrity: sha512-wOPslzB8vTvpxwdaOcR2qAbwmuSP0L+rhpoC6Cf56V3Jip+HWb7PQQXOUPgBNQARpXsbQX/+mvi8kKucmBGRwQ==}
|
||||||
|
|
||||||
|
'@vue/reactivity@3.5.41':
|
||||||
|
resolution: {integrity: sha512-rznsqKM0np0x18EjzF8x88MpEhdNsffbvFbckLL5+oUKz1BxAImEmO7J1ArRYSyo6aQaVoBDp7jEkT91OOxydA==}
|
||||||
|
|
||||||
|
'@vue/runtime-core@3.5.41':
|
||||||
|
resolution: {integrity: sha512-Vcry58hiAKwGen9Z1jUZE0feFsNArPCMOImYI8el48A9Idf6DuQYD0U05zZIF2Iad1hGhPSvcbBbAOhNr55fhg==}
|
||||||
|
|
||||||
|
'@vue/runtime-dom@3.5.41':
|
||||||
|
resolution: {integrity: sha512-3vVBahVBS9+U6cmXBLyb8nE6/yYo4J/CGI9eVFs3KiMc0YHuudwKyShTD65jtJy/L9PUUxNAFu4cj4LiJ0UFbw==}
|
||||||
|
|
||||||
|
'@vue/server-renderer@3.5.41':
|
||||||
|
resolution: {integrity: sha512-n6hx/pNFfbD6SuyeuMVkvqox8bwf/ET9JlA/kAz/imw8sw++wkqKe2mHX5KutjPpbKE4Z56yTHszoOjGMI9igQ==}
|
||||||
|
|
||||||
|
'@vue/shared@3.5.41':
|
||||||
|
resolution: {integrity: sha512-IOnwSCma8j+9xJT6b8H0dEYidC80NsYmNMlZxRsukYcSoGaDBohog5hDxzeUXdFeGWFA++vWvxqOmrr96VlqMA==}
|
||||||
|
|
||||||
|
acorn@8.18.0:
|
||||||
|
resolution: {integrity: sha512-lGq+9yr1/GuAWaVYIHRjvvySG5/4VfKIvC8EWxStPdcDh/Ka7FG3twP6v4d5BkravUilhIAsG4Qj83t02LWUPQ==}
|
||||||
|
engines: {node: '>=0.4.0'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
agent-base@6.0.2:
|
||||||
|
resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==}
|
||||||
|
engines: {node: '>= 6.0.0'}
|
||||||
|
|
||||||
|
asynckit@0.4.0:
|
||||||
|
resolution: {integrity: sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==}
|
||||||
|
|
||||||
|
axios@1.18.0:
|
||||||
|
resolution: {integrity: sha512-E32NzpYKp++W7XRe52rHiXV2ehxmh3wbdgO7MHeFM+vqxLBYHzt0ElkiImtOBxtOmyp0yoC8C6uESVV84Y2/hw==}
|
||||||
|
|
||||||
|
birpc@2.9.0:
|
||||||
|
resolution: {integrity: sha512-KrayHS5pBi69Xi9JmvoqrIgYGDkD6mcSe/i6YKi3w5kekCLzrX4+nawcXqrj2tIp50Kw/mT/s3p+GVK0A0sKxw==}
|
||||||
|
|
||||||
|
buffer-from@1.1.2:
|
||||||
|
resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==}
|
||||||
|
|
||||||
|
call-bind-apply-helpers@1.0.2:
|
||||||
|
resolution: {integrity: sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
combined-stream@1.0.8:
|
||||||
|
resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==}
|
||||||
|
engines: {node: '>= 0.8'}
|
||||||
|
|
||||||
|
commander@2.20.3:
|
||||||
|
resolution: {integrity: sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==}
|
||||||
|
|
||||||
|
copy-anything@2.0.6:
|
||||||
|
resolution: {integrity: sha512-1j20GZTsvKNkc4BY3NpMOM8tt///wY3FpIzozTOFO2ffuZcV61nojHXVKIy3WM+7ADCy5FVhdZYHYDdgTU0yJw==}
|
||||||
|
|
||||||
|
copy-anything@4.1.0:
|
||||||
|
resolution: {integrity: sha512-ufbM3smX/Jbnpk5wcQjzd1MgBpzmqfNETUAyZNrGwU9foRlyHoGzMMBBCRzEhQLBjZfFDE1W2ufPXX2vdWkV8Q==}
|
||||||
|
engines: {node: '>=18'}
|
||||||
|
|
||||||
|
csstype@3.2.3:
|
||||||
|
resolution: {integrity: sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==}
|
||||||
|
|
||||||
|
debug@4.4.3:
|
||||||
|
resolution: {integrity: sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==}
|
||||||
|
engines: {node: '>=6.0'}
|
||||||
|
peerDependencies:
|
||||||
|
supports-color: '*'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
supports-color:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
delayed-stream@1.0.0:
|
||||||
|
resolution: {integrity: sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==}
|
||||||
|
engines: {node: '>=0.4.0'}
|
||||||
|
|
||||||
|
dunder-proto@1.0.1:
|
||||||
|
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
entities@7.0.1:
|
||||||
|
resolution: {integrity: sha512-TWrgLOFUQTH994YUyl1yT4uyavY5nNB5muff+RtWaqNVCAK408b5ZnnbNAUEWLTCpum9w6arT70i1XdQ4UeOPA==}
|
||||||
|
engines: {node: '>=0.12'}
|
||||||
|
|
||||||
|
errno@0.1.8:
|
||||||
|
resolution: {integrity: sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
es-define-property@1.0.1:
|
||||||
|
resolution: {integrity: sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
es-errors@1.3.0:
|
||||||
|
resolution: {integrity: sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
es-object-atoms@1.1.2:
|
||||||
|
resolution: {integrity: sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
es-set-tostringtag@2.1.0:
|
||||||
|
resolution: {integrity: sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
estree-walker@2.0.2:
|
||||||
|
resolution: {integrity: sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==}
|
||||||
|
|
||||||
|
follow-redirects@1.16.0:
|
||||||
|
resolution: {integrity: sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==}
|
||||||
|
engines: {node: '>=4.0'}
|
||||||
|
peerDependencies:
|
||||||
|
debug: '*'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
debug:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
form-data@4.0.6:
|
||||||
|
resolution: {integrity: sha512-vKatAh4SlVfgbv+YtmhiRjhEMJsYpsG1Y2rMQtR+SVSbytsSD1YGzDIcrAJmdFec88u/+VoGmxnl+80gL1tRCQ==}
|
||||||
|
engines: {node: '>= 6'}
|
||||||
|
|
||||||
|
function-bind@1.1.2:
|
||||||
|
resolution: {integrity: sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==}
|
||||||
|
|
||||||
|
get-intrinsic@1.3.0:
|
||||||
|
resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
get-proto@1.0.1:
|
||||||
|
resolution: {integrity: sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
github-slugger@2.0.0:
|
||||||
|
resolution: {integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==}
|
||||||
|
|
||||||
|
gopd@1.2.0:
|
||||||
|
resolution: {integrity: sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
graceful-fs@4.2.11:
|
||||||
|
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
|
||||||
|
|
||||||
|
has-symbols@1.1.0:
|
||||||
|
resolution: {integrity: sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
has-tostringtag@1.0.2:
|
||||||
|
resolution: {integrity: sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
hasown@2.0.4:
|
||||||
|
resolution: {integrity: sha512-T2UbfbBEF32wiepXIsMlTW9+dDYC6wMh/t/vYA4tuOMKqWz/n3vr1NFSxQiyP+zk2mXsoMA/i/7qV6LKut1t1A==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
hookable@5.5.3:
|
||||||
|
resolution: {integrity: sha512-Yc+BQe8SvoXH1643Qez1zqLRmbA5rCL+sSmk6TVos0LWVfNIB7PGncdlId77WzLGSIB5KaWgTaNTs2lNVEI6VQ==}
|
||||||
|
|
||||||
|
https-proxy-agent@5.0.1:
|
||||||
|
resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==}
|
||||||
|
engines: {node: '>= 6'}
|
||||||
|
|
||||||
|
iconv-lite@0.6.3:
|
||||||
|
resolution: {integrity: sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==}
|
||||||
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
|
image-size@0.5.5:
|
||||||
|
resolution: {integrity: sha512-6TDAlDPZxUFCv+fuOkIoXT/V/f3Qbq8e37p+YOiYrUv3v9cc3/6x78VdfPgFVaB9dZYeLUfKgHRebpkm/oP2VQ==}
|
||||||
|
engines: {node: '>=0.10.0'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
is-what@3.14.1:
|
||||||
|
resolution: {integrity: sha512-sNxgpk9793nzSs7bA6JQJGeIuRBQhAaNGG77kzYQgMkrID+lS6SlK07K5LaptscDlSaIgH+GPFzf+d75FVxozA==}
|
||||||
|
|
||||||
|
less@4.5.1:
|
||||||
|
resolution: {integrity: sha512-UKgI3/KON4u6ngSsnDADsUERqhZknsVZbnuzlRZXLQCmfC/MDld42fTydUE9B+Mla1AL6SJ/Pp6SlEFi/AVGfw==}
|
||||||
|
engines: {node: '>=14'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
magic-string@0.30.21:
|
||||||
|
resolution: {integrity: sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==}
|
||||||
|
|
||||||
|
make-dir@2.1.0:
|
||||||
|
resolution: {integrity: sha512-LS9X+dc8KLxXCb8dni79fLIIUA5VyZoyjSMCwTluaXA0o27cCK0bhXkpgw+sTXVpPy/lSO57ilRixqk0vDmtRA==}
|
||||||
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
|
marked-gfm-heading-id@4.1.4:
|
||||||
|
resolution: {integrity: sha512-CspnvVfHSkb/znqdPS4jUR8HtCjq3M/DnrsJCrfLBLvdrgbemmoINKpeWKQYkBiXAoBGejw0cV7xzqrPdup3WA==}
|
||||||
|
peerDependencies:
|
||||||
|
marked: '>=13 <19'
|
||||||
|
|
||||||
|
marked-highlight@2.2.4:
|
||||||
|
resolution: {integrity: sha512-PZxisNMJDduSjc0q6uvjsnqqHCXc9s0eyzxDO9sB1eNGJnd/H1/Fu+z6g/liC1dfJdFW4SftMwMlLvsBhUPrqQ==}
|
||||||
|
peerDependencies:
|
||||||
|
marked: '>=4 <19'
|
||||||
|
|
||||||
|
marked-mangle@1.1.13:
|
||||||
|
resolution: {integrity: sha512-phz1W/nYMr1T08Q7wqH2aj+PPiK85E69WQGfId+prvryfgjY/Idibx4YUvKaYMDV9rK1qo+/yC+Quu/3gdaBeA==}
|
||||||
|
peerDependencies:
|
||||||
|
marked: '>=4 <19'
|
||||||
|
|
||||||
|
marked@17.0.6:
|
||||||
|
resolution: {integrity: sha512-gB0gkNafnonOw0obSTEGZTT86IuhILt2Wfx0mWH/1Au83kybTayroZ/V6nS25mN7u8ASy+5fMhgB3XPNrOZdmA==}
|
||||||
|
engines: {node: '>= 20'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
math-intrinsics@1.1.0:
|
||||||
|
resolution: {integrity: sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==}
|
||||||
|
engines: {node: '>= 0.4'}
|
||||||
|
|
||||||
|
mime-db@1.52.0:
|
||||||
|
resolution: {integrity: sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==}
|
||||||
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
|
mime-types@2.1.35:
|
||||||
|
resolution: {integrity: sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==}
|
||||||
|
engines: {node: '>= 0.6'}
|
||||||
|
|
||||||
|
mime@1.6.0:
|
||||||
|
resolution: {integrity: sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==}
|
||||||
|
engines: {node: '>=4'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
mitt@3.0.1:
|
||||||
|
resolution: {integrity: sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==}
|
||||||
|
|
||||||
|
ms@2.1.3:
|
||||||
|
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
|
||||||
|
|
||||||
|
nanoid@3.3.18:
|
||||||
|
resolution: {integrity: sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==}
|
||||||
|
engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
needle@3.5.0:
|
||||||
|
resolution: {integrity: sha512-jaQyPKKk2YokHrEg+vFDYxXIHTCBgiZwSHOoVx/8V3GIBS8/VN6NdVRmg8q1ERtPkMvmOvebsgga4sAj5hls/w==}
|
||||||
|
engines: {node: '>= 4.4.x'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
parse-node-version@1.0.1:
|
||||||
|
resolution: {integrity: sha512-3YHlOa/JgH6Mnpr05jP9eDG254US9ek25LyIxZlDItp2iJtwyaXQb57lBYLdT3MowkUFYEV2XXNAYIPlESvJlA==}
|
||||||
|
engines: {node: '>= 0.10'}
|
||||||
|
|
||||||
|
perfect-debounce@1.0.0:
|
||||||
|
resolution: {integrity: sha512-xCy9V055GLEqoFaHoC1SoLIaLmWctgCUaBaWxDZ7/Zx4CTyX7cJQLJOok/orfjZAh9kEYpjJa4d0KcJmCbctZA==}
|
||||||
|
|
||||||
|
picocolors@1.1.1:
|
||||||
|
resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==}
|
||||||
|
|
||||||
|
pify@4.0.1:
|
||||||
|
resolution: {integrity: sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==}
|
||||||
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
|
pinia@3.0.4:
|
||||||
|
resolution: {integrity: sha512-l7pqLUFTI/+ESXn6k3nu30ZIzW5E2WZF/LaHJEpoq6ElcLD+wduZoB2kBN19du6K/4FDpPMazY2wJr+IndBtQw==}
|
||||||
|
peerDependencies:
|
||||||
|
typescript: '>=4.5.0'
|
||||||
|
vue: ^3.5.11
|
||||||
|
peerDependenciesMeta:
|
||||||
|
typescript:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
postcss@8.5.26:
|
||||||
|
resolution: {integrity: sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==}
|
||||||
|
engines: {node: ^10 || ^12 || >=14}
|
||||||
|
|
||||||
|
prismjs@1.30.0:
|
||||||
|
resolution: {integrity: sha512-DEvV2ZF2r2/63V+tK8hQvrR2ZGn10srHbXviTlcv7Kpzw8jWiNTqbVgjO3IY8RxrrOUF8VPMQQFysYYYv0YZxw==}
|
||||||
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
|
proxy-from-env@2.1.0:
|
||||||
|
resolution: {integrity: sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
prr@1.0.1:
|
||||||
|
resolution: {integrity: sha512-yPw4Sng1gWghHQWj0B3ZggWUm4qVbPwPFcRG8KyxiU7J2OHFSoEHKS+EZ3fv5l1t9CyCiop6l/ZYeWbrgoQejw==}
|
||||||
|
|
||||||
|
rfdc@1.4.1:
|
||||||
|
resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==}
|
||||||
|
|
||||||
|
safer-buffer@2.1.2:
|
||||||
|
resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==}
|
||||||
|
|
||||||
|
sax@1.6.1:
|
||||||
|
resolution: {integrity: sha512-42tBVwLWnaQvW5zc4HbZrTuWccECCZfBi92FDuwtqxasH+JbPB3/FOKb1m222K42R4WxuxzzMsTswfzgtSu64Q==}
|
||||||
|
engines: {node: '>=11.0.0'}
|
||||||
|
|
||||||
|
semver@5.7.2:
|
||||||
|
resolution: {integrity: sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
source-map-js@1.2.1:
|
||||||
|
resolution: {integrity: sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==}
|
||||||
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
|
source-map-support@0.5.21:
|
||||||
|
resolution: {integrity: sha512-uBHU3L3czsIyYXKX88fdrGovxdSCoTGDRZ6SYXtSRxLZUzHg5P/66Ht6uoUlHu9EZod+inXhKo3qQgwXUT/y1w==}
|
||||||
|
|
||||||
|
source-map@0.6.1:
|
||||||
|
resolution: {integrity: sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==}
|
||||||
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
|
speakingurl@14.0.1:
|
||||||
|
resolution: {integrity: sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==}
|
||||||
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
|
superjson@2.2.6:
|
||||||
|
resolution: {integrity: sha512-H+ue8Zo4vJmV2nRjpx86P35lzwDT3nItnIsocgumgr0hHMQ+ZGq5vrERg9kJBo5AWGmxZDhzDo+WVIJqkB0cGA==}
|
||||||
|
engines: {node: '>=16'}
|
||||||
|
|
||||||
|
terser@5.50.0:
|
||||||
|
resolution: {integrity: sha512-CN9BVxWhgS/hRxtUMjtC2uRWSTcSfQFHMDWma6sKKfIivCD91sM+FOPfvwoaRMqCSrUpe1nv3jDamd9eEQ4y+w==}
|
||||||
|
engines: {node: '>=10'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
timi-web@0.0.24:
|
||||||
|
resolution: {integrity: sha512-qzcm72W9EE3BrYiUzc8a20J8xqxH17moEm/ovdijhRbIJtJUr81sslXFe2+OjNtx14meEIPJm3IfRooFzRswLg==}
|
||||||
|
engines: {node: '>=16.0.0'}
|
||||||
|
|
||||||
|
tslib@2.8.1:
|
||||||
|
resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==}
|
||||||
|
|
||||||
|
typescript@5.9.3:
|
||||||
|
resolution: {integrity: sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==}
|
||||||
|
engines: {node: '>=14.17'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
|
vue@3.5.41:
|
||||||
|
resolution: {integrity: sha512-2laE0p+aK+/AOPG/XL/WepOs/GlK755LJ1XECi9kDUrz1FKNw8rb2Xzlw9JS1rqEV55nb0ttsKxVlTCcd+R5cg==}
|
||||||
|
peerDependencies:
|
||||||
|
typescript: '*'
|
||||||
|
peerDependenciesMeta:
|
||||||
|
typescript:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
snapshots:
|
||||||
|
|
||||||
|
'@babel/helper-string-parser@7.29.7': {}
|
||||||
|
|
||||||
|
'@babel/helper-validator-identifier@7.29.7': {}
|
||||||
|
|
||||||
|
'@babel/parser@7.29.8':
|
||||||
|
dependencies:
|
||||||
|
'@babel/types': 7.29.8
|
||||||
|
|
||||||
|
'@babel/types@7.29.8':
|
||||||
|
dependencies:
|
||||||
|
'@babel/helper-string-parser': 7.29.7
|
||||||
|
'@babel/helper-validator-identifier': 7.29.7
|
||||||
|
|
||||||
|
'@jridgewell/gen-mapping@0.3.13':
|
||||||
|
dependencies:
|
||||||
|
'@jridgewell/sourcemap-codec': 1.5.5
|
||||||
|
'@jridgewell/trace-mapping': 0.3.31
|
||||||
|
|
||||||
|
'@jridgewell/resolve-uri@3.1.2': {}
|
||||||
|
|
||||||
|
'@jridgewell/source-map@0.3.11':
|
||||||
|
dependencies:
|
||||||
|
'@jridgewell/gen-mapping': 0.3.13
|
||||||
|
'@jridgewell/trace-mapping': 0.3.31
|
||||||
|
|
||||||
|
'@jridgewell/sourcemap-codec@1.5.5': {}
|
||||||
|
|
||||||
|
'@jridgewell/trace-mapping@0.3.31':
|
||||||
|
dependencies:
|
||||||
|
'@jridgewell/resolve-uri': 3.1.2
|
||||||
|
'@jridgewell/sourcemap-codec': 1.5.5
|
||||||
|
|
||||||
|
'@vue/compiler-core@3.5.41':
|
||||||
|
dependencies:
|
||||||
|
'@babel/parser': 7.29.8
|
||||||
|
'@vue/shared': 3.5.41
|
||||||
|
entities: 7.0.1
|
||||||
|
estree-walker: 2.0.2
|
||||||
|
source-map-js: 1.2.1
|
||||||
|
|
||||||
|
'@vue/compiler-dom@3.5.41':
|
||||||
|
dependencies:
|
||||||
|
'@vue/compiler-core': 3.5.41
|
||||||
|
'@vue/shared': 3.5.41
|
||||||
|
|
||||||
|
'@vue/compiler-sfc@3.5.41':
|
||||||
|
dependencies:
|
||||||
|
'@babel/parser': 7.29.8
|
||||||
|
'@vue/compiler-core': 3.5.41
|
||||||
|
'@vue/compiler-dom': 3.5.41
|
||||||
|
'@vue/compiler-ssr': 3.5.41
|
||||||
|
'@vue/shared': 3.5.41
|
||||||
|
estree-walker: 2.0.2
|
||||||
|
magic-string: 0.30.21
|
||||||
|
postcss: 8.5.26
|
||||||
|
source-map-js: 1.2.1
|
||||||
|
|
||||||
|
'@vue/compiler-ssr@3.5.41':
|
||||||
|
dependencies:
|
||||||
|
'@vue/compiler-dom': 3.5.41
|
||||||
|
'@vue/shared': 3.5.41
|
||||||
|
|
||||||
|
'@vue/devtools-api@7.7.10':
|
||||||
|
dependencies:
|
||||||
|
'@vue/devtools-kit': 7.7.10
|
||||||
|
|
||||||
|
'@vue/devtools-kit@7.7.10':
|
||||||
|
dependencies:
|
||||||
|
'@vue/devtools-shared': 7.7.10
|
||||||
|
birpc: 2.9.0
|
||||||
|
hookable: 5.5.3
|
||||||
|
mitt: 3.0.1
|
||||||
|
perfect-debounce: 1.0.0
|
||||||
|
speakingurl: 14.0.1
|
||||||
|
superjson: 2.2.6
|
||||||
|
|
||||||
|
'@vue/devtools-shared@7.7.10':
|
||||||
|
dependencies:
|
||||||
|
rfdc: 1.4.1
|
||||||
|
|
||||||
|
'@vue/reactivity@3.5.41':
|
||||||
|
dependencies:
|
||||||
|
'@vue/shared': 3.5.41
|
||||||
|
|
||||||
|
'@vue/runtime-core@3.5.41':
|
||||||
|
dependencies:
|
||||||
|
'@vue/reactivity': 3.5.41
|
||||||
|
'@vue/shared': 3.5.41
|
||||||
|
|
||||||
|
'@vue/runtime-dom@3.5.41':
|
||||||
|
dependencies:
|
||||||
|
'@vue/reactivity': 3.5.41
|
||||||
|
'@vue/runtime-core': 3.5.41
|
||||||
|
'@vue/shared': 3.5.41
|
||||||
|
csstype: 3.2.3
|
||||||
|
|
||||||
|
'@vue/server-renderer@3.5.41':
|
||||||
|
dependencies:
|
||||||
|
'@vue/compiler-ssr': 3.5.41
|
||||||
|
'@vue/runtime-dom': 3.5.41
|
||||||
|
'@vue/shared': 3.5.41
|
||||||
|
|
||||||
|
'@vue/shared@3.5.41': {}
|
||||||
|
|
||||||
|
acorn@8.18.0: {}
|
||||||
|
|
||||||
|
agent-base@6.0.2:
|
||||||
|
dependencies:
|
||||||
|
debug: 4.4.3
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
|
asynckit@0.4.0: {}
|
||||||
|
|
||||||
|
axios@1.18.0:
|
||||||
|
dependencies:
|
||||||
|
follow-redirects: 1.16.0
|
||||||
|
form-data: 4.0.6
|
||||||
|
https-proxy-agent: 5.0.1
|
||||||
|
proxy-from-env: 2.1.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- debug
|
||||||
|
- supports-color
|
||||||
|
|
||||||
|
birpc@2.9.0: {}
|
||||||
|
|
||||||
|
buffer-from@1.1.2: {}
|
||||||
|
|
||||||
|
call-bind-apply-helpers@1.0.2:
|
||||||
|
dependencies:
|
||||||
|
es-errors: 1.3.0
|
||||||
|
function-bind: 1.1.2
|
||||||
|
|
||||||
|
combined-stream@1.0.8:
|
||||||
|
dependencies:
|
||||||
|
delayed-stream: 1.0.0
|
||||||
|
|
||||||
|
commander@2.20.3: {}
|
||||||
|
|
||||||
|
copy-anything@2.0.6:
|
||||||
|
dependencies:
|
||||||
|
is-what: 3.14.1
|
||||||
|
|
||||||
|
copy-anything@4.1.0: {}
|
||||||
|
|
||||||
|
csstype@3.2.3: {}
|
||||||
|
|
||||||
|
debug@4.4.3:
|
||||||
|
dependencies:
|
||||||
|
ms: 2.1.3
|
||||||
|
|
||||||
|
delayed-stream@1.0.0: {}
|
||||||
|
|
||||||
|
dunder-proto@1.0.1:
|
||||||
|
dependencies:
|
||||||
|
call-bind-apply-helpers: 1.0.2
|
||||||
|
es-errors: 1.3.0
|
||||||
|
gopd: 1.2.0
|
||||||
|
|
||||||
|
entities@7.0.1: {}
|
||||||
|
|
||||||
|
errno@0.1.8:
|
||||||
|
dependencies:
|
||||||
|
prr: 1.0.1
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
es-define-property@1.0.1: {}
|
||||||
|
|
||||||
|
es-errors@1.3.0: {}
|
||||||
|
|
||||||
|
es-object-atoms@1.1.2:
|
||||||
|
dependencies:
|
||||||
|
es-errors: 1.3.0
|
||||||
|
|
||||||
|
es-set-tostringtag@2.1.0:
|
||||||
|
dependencies:
|
||||||
|
es-errors: 1.3.0
|
||||||
|
get-intrinsic: 1.3.0
|
||||||
|
has-tostringtag: 1.0.2
|
||||||
|
hasown: 2.0.4
|
||||||
|
|
||||||
|
estree-walker@2.0.2: {}
|
||||||
|
|
||||||
|
follow-redirects@1.16.0: {}
|
||||||
|
|
||||||
|
form-data@4.0.6:
|
||||||
|
dependencies:
|
||||||
|
asynckit: 0.4.0
|
||||||
|
combined-stream: 1.0.8
|
||||||
|
es-set-tostringtag: 2.1.0
|
||||||
|
hasown: 2.0.4
|
||||||
|
mime-types: 2.1.35
|
||||||
|
|
||||||
|
function-bind@1.1.2: {}
|
||||||
|
|
||||||
|
get-intrinsic@1.3.0:
|
||||||
|
dependencies:
|
||||||
|
call-bind-apply-helpers: 1.0.2
|
||||||
|
es-define-property: 1.0.1
|
||||||
|
es-errors: 1.3.0
|
||||||
|
es-object-atoms: 1.1.2
|
||||||
|
function-bind: 1.1.2
|
||||||
|
get-proto: 1.0.1
|
||||||
|
gopd: 1.2.0
|
||||||
|
has-symbols: 1.1.0
|
||||||
|
hasown: 2.0.4
|
||||||
|
math-intrinsics: 1.1.0
|
||||||
|
|
||||||
|
get-proto@1.0.1:
|
||||||
|
dependencies:
|
||||||
|
dunder-proto: 1.0.1
|
||||||
|
es-object-atoms: 1.1.2
|
||||||
|
|
||||||
|
github-slugger@2.0.0: {}
|
||||||
|
|
||||||
|
gopd@1.2.0: {}
|
||||||
|
|
||||||
|
graceful-fs@4.2.11:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
has-symbols@1.1.0: {}
|
||||||
|
|
||||||
|
has-tostringtag@1.0.2:
|
||||||
|
dependencies:
|
||||||
|
has-symbols: 1.1.0
|
||||||
|
|
||||||
|
hasown@2.0.4:
|
||||||
|
dependencies:
|
||||||
|
function-bind: 1.1.2
|
||||||
|
|
||||||
|
hookable@5.5.3: {}
|
||||||
|
|
||||||
|
https-proxy-agent@5.0.1:
|
||||||
|
dependencies:
|
||||||
|
agent-base: 6.0.2
|
||||||
|
debug: 4.4.3
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- supports-color
|
||||||
|
|
||||||
|
iconv-lite@0.6.3:
|
||||||
|
dependencies:
|
||||||
|
safer-buffer: 2.1.2
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
image-size@0.5.5:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
is-what@3.14.1: {}
|
||||||
|
|
||||||
|
less@4.5.1:
|
||||||
|
dependencies:
|
||||||
|
copy-anything: 2.0.6
|
||||||
|
parse-node-version: 1.0.1
|
||||||
|
tslib: 2.8.1
|
||||||
|
optionalDependencies:
|
||||||
|
errno: 0.1.8
|
||||||
|
graceful-fs: 4.2.11
|
||||||
|
image-size: 0.5.5
|
||||||
|
make-dir: 2.1.0
|
||||||
|
mime: 1.6.0
|
||||||
|
needle: 3.5.0
|
||||||
|
source-map: 0.6.1
|
||||||
|
|
||||||
|
magic-string@0.30.21:
|
||||||
|
dependencies:
|
||||||
|
'@jridgewell/sourcemap-codec': 1.5.5
|
||||||
|
|
||||||
|
make-dir@2.1.0:
|
||||||
|
dependencies:
|
||||||
|
pify: 4.0.1
|
||||||
|
semver: 5.7.2
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
marked-gfm-heading-id@4.1.4(marked@17.0.6):
|
||||||
|
dependencies:
|
||||||
|
github-slugger: 2.0.0
|
||||||
|
marked: 17.0.6
|
||||||
|
|
||||||
|
marked-highlight@2.2.4(marked@17.0.6):
|
||||||
|
dependencies:
|
||||||
|
marked: 17.0.6
|
||||||
|
|
||||||
|
marked-mangle@1.1.13(marked@17.0.6):
|
||||||
|
dependencies:
|
||||||
|
marked: 17.0.6
|
||||||
|
|
||||||
|
marked@17.0.6: {}
|
||||||
|
|
||||||
|
math-intrinsics@1.1.0: {}
|
||||||
|
|
||||||
|
mime-db@1.52.0: {}
|
||||||
|
|
||||||
|
mime-types@2.1.35:
|
||||||
|
dependencies:
|
||||||
|
mime-db: 1.52.0
|
||||||
|
|
||||||
|
mime@1.6.0:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
mitt@3.0.1: {}
|
||||||
|
|
||||||
|
ms@2.1.3: {}
|
||||||
|
|
||||||
|
nanoid@3.3.18: {}
|
||||||
|
|
||||||
|
needle@3.5.0:
|
||||||
|
dependencies:
|
||||||
|
iconv-lite: 0.6.3
|
||||||
|
sax: 1.6.1
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
parse-node-version@1.0.1: {}
|
||||||
|
|
||||||
|
perfect-debounce@1.0.0: {}
|
||||||
|
|
||||||
|
picocolors@1.1.1: {}
|
||||||
|
|
||||||
|
pify@4.0.1:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
pinia@3.0.4(typescript@5.9.3)(vue@3.5.41(typescript@5.9.3)):
|
||||||
|
dependencies:
|
||||||
|
'@vue/devtools-api': 7.7.10
|
||||||
|
vue: 3.5.41(typescript@5.9.3)
|
||||||
|
optionalDependencies:
|
||||||
|
typescript: 5.9.3
|
||||||
|
|
||||||
|
postcss@8.5.26:
|
||||||
|
dependencies:
|
||||||
|
nanoid: 3.3.18
|
||||||
|
picocolors: 1.1.1
|
||||||
|
source-map-js: 1.2.1
|
||||||
|
|
||||||
|
prismjs@1.30.0: {}
|
||||||
|
|
||||||
|
proxy-from-env@2.1.0: {}
|
||||||
|
|
||||||
|
prr@1.0.1:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
rfdc@1.4.1: {}
|
||||||
|
|
||||||
|
safer-buffer@2.1.2:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
sax@1.6.1:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
semver@5.7.2:
|
||||||
|
optional: true
|
||||||
|
|
||||||
|
source-map-js@1.2.1: {}
|
||||||
|
|
||||||
|
source-map-support@0.5.21:
|
||||||
|
dependencies:
|
||||||
|
buffer-from: 1.1.2
|
||||||
|
source-map: 0.6.1
|
||||||
|
|
||||||
|
source-map@0.6.1: {}
|
||||||
|
|
||||||
|
speakingurl@14.0.1: {}
|
||||||
|
|
||||||
|
superjson@2.2.6:
|
||||||
|
dependencies:
|
||||||
|
copy-anything: 4.1.0
|
||||||
|
|
||||||
|
terser@5.50.0:
|
||||||
|
dependencies:
|
||||||
|
'@jridgewell/source-map': 0.3.11
|
||||||
|
acorn: 8.18.0
|
||||||
|
commander: 2.20.3
|
||||||
|
source-map-support: 0.5.21
|
||||||
|
|
||||||
|
timi-web@0.0.24(typescript@5.9.3)(vue@3.5.41(typescript@5.9.3)):
|
||||||
|
dependencies:
|
||||||
|
axios: 1.18.0
|
||||||
|
less: 4.5.1
|
||||||
|
marked: 17.0.6
|
||||||
|
marked-gfm-heading-id: 4.1.4(marked@17.0.6)
|
||||||
|
marked-highlight: 2.2.4(marked@17.0.6)
|
||||||
|
marked-mangle: 1.1.13(marked@17.0.6)
|
||||||
|
pinia: 3.0.4(typescript@5.9.3)(vue@3.5.41(typescript@5.9.3))
|
||||||
|
prismjs: 1.30.0
|
||||||
|
terser: 5.50.0
|
||||||
|
transitivePeerDependencies:
|
||||||
|
- debug
|
||||||
|
- supports-color
|
||||||
|
- typescript
|
||||||
|
- vue
|
||||||
|
|
||||||
|
tslib@2.8.1: {}
|
||||||
|
|
||||||
|
typescript@5.9.3: {}
|
||||||
|
|
||||||
|
vue@3.5.41(typescript@5.9.3):
|
||||||
|
dependencies:
|
||||||
|
'@vue/compiler-dom': 3.5.41
|
||||||
|
'@vue/compiler-sfc': 3.5.41
|
||||||
|
'@vue/runtime-dom': 3.5.41
|
||||||
|
'@vue/server-renderer': 3.5.41
|
||||||
|
'@vue/shared': 3.5.41
|
||||||
|
optionalDependencies:
|
||||||
|
typescript: 5.9.3
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
import type { Article } from "../types";
|
||||||
|
import { axios } from "./BaseAPI";
|
||||||
|
|
||||||
|
export const ArticleAPI = {
|
||||||
|
/**
|
||||||
|
* 获取文章
|
||||||
|
*
|
||||||
|
* @param id 文章 ID
|
||||||
|
* @returns 文章数据
|
||||||
|
*/
|
||||||
|
async view(id: string): Promise<Article> {
|
||||||
|
return axios.get(`/article/${id}`);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 喜欢文章,后端有限调用,1240ms 一次
|
||||||
|
*
|
||||||
|
* @param id 文章 ID
|
||||||
|
* @returns 最新喜欢数量
|
||||||
|
*/
|
||||||
|
async like(id: string): Promise<number> {
|
||||||
|
return axios.get(`/article/like/${id}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ArticleAPI;
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
import type { Attachment, BizIdUpdate, TempFile } from "../types";
|
||||||
|
import { axios, getBaseURI } from "./BaseAPI";
|
||||||
|
|
||||||
|
const BASE_URI = "/attach";
|
||||||
|
|
||||||
|
export const AttachmentAPI = {
|
||||||
|
/**
|
||||||
|
* 上传临时附件
|
||||||
|
*
|
||||||
|
* @param file 文件或文件列表
|
||||||
|
* @param ttl 有效期
|
||||||
|
* @returns 临时附件列表
|
||||||
|
*/
|
||||||
|
async upload(file: File | File[], ttl?: string): Promise<TempFile[]> {
|
||||||
|
const formData = new FormData();
|
||||||
|
const fileList = Array.isArray(file) ? file : [file];
|
||||||
|
for (const item of fileList) {
|
||||||
|
formData.append("file", item);
|
||||||
|
}
|
||||||
|
if (ttl) {
|
||||||
|
formData.append("ttl", ttl);
|
||||||
|
}
|
||||||
|
return await axios.post(`${BASE_URI}/temp/upload`, formData);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 按业务差分更新附件
|
||||||
|
*
|
||||||
|
* @param req 更新请求
|
||||||
|
*/
|
||||||
|
async updateByBizId(req: BizIdUpdate): Promise<void> {
|
||||||
|
return await axios.post(`${BASE_URI}/update/biz/id`, req);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 按业务查询附件列表
|
||||||
|
*
|
||||||
|
* @param bizType 业务类型
|
||||||
|
* @param bizId 业务 ID
|
||||||
|
* @param attachTypeList 附件类型列表
|
||||||
|
*/
|
||||||
|
async listByBizId(bizType: string, bizId: string, attachTypeList?: string[]): Promise<Attachment[]> {
|
||||||
|
return await axios.get(`${BASE_URI}/list/biz/id`, {
|
||||||
|
params: {
|
||||||
|
bizType,
|
||||||
|
bizId,
|
||||||
|
attachTypeList
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
getReadURL(id: string): string {
|
||||||
|
return `${getBaseURI()}${BASE_URI}/read/${id}`;
|
||||||
|
},
|
||||||
|
|
||||||
|
getTempReadURL(id: string): string {
|
||||||
|
return `${getBaseURI()}${BASE_URI}/temp/read?id=${id}`;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default AttachmentAPI;
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
import Network from "../utils/Network";
|
||||||
|
|
||||||
|
let serviceAxios: any | null = null;
|
||||||
|
|
||||||
|
function getServiceAxios(): any {
|
||||||
|
if (serviceAxios === null) {
|
||||||
|
serviceAxios = Network.axios;
|
||||||
|
}
|
||||||
|
return serviceAxios;
|
||||||
|
}
|
||||||
|
|
||||||
|
export const axios = new Proxy({} as any, {
|
||||||
|
get(_target: any, prop: string | symbol, receiver: any): any {
|
||||||
|
return Reflect.get(getServiceAxios(), prop, receiver);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
export function getBaseURI(): string {
|
||||||
|
const baseURL = axios.defaults.baseURL;
|
||||||
|
if (!baseURL || baseURL === "undefined") {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return baseURL.replace(/\/$/, "");
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import type { CaptchaData, Comment, Page, PageResult } from "../types";
|
||||||
|
import { axios } from "./BaseAPI";
|
||||||
|
|
||||||
|
const BASE_URI = "/comment";
|
||||||
|
|
||||||
|
export const CommentAPI = {
|
||||||
|
async page(page: Page<Comment>): Promise<PageResult<Comment>> {
|
||||||
|
return axios.post(`${BASE_URI}/list`, page);
|
||||||
|
},
|
||||||
|
async create(captchaData: CaptchaData<Comment>): Promise<void> {
|
||||||
|
return axios.post(`${BASE_URI}/create`, captchaData);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CommentAPI;
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
import type { CaptchaResult } from "../types";
|
||||||
|
import { axios, getBaseURI } from "./BaseAPI";
|
||||||
|
|
||||||
|
export const CommonAPI = {
|
||||||
|
|
||||||
|
getCaptchaAPI(): string {
|
||||||
|
return `${getBaseURI()}/captcha`;
|
||||||
|
},
|
||||||
|
|
||||||
|
async captcha(width: number, height: number): Promise<CaptchaResult> {
|
||||||
|
return await axios.get(`/captcha?width=${width}&height=${height}`);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export default CommonAPI;
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
import type { Developer } from "../types";
|
||||||
|
import { axios } from "./BaseAPI";
|
||||||
|
|
||||||
|
const BASE_URI = "/git/developer";
|
||||||
|
|
||||||
|
export const DeveloperAPI = {
|
||||||
|
async get(): Promise<Developer> {
|
||||||
|
return axios.post(`${BASE_URI}`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default DeveloperAPI;
|
||||||
@@ -0,0 +1,83 @@
|
|||||||
|
import { axios } from "./BaseAPI";
|
||||||
|
import type { CaptchaData, Notify, NotifyDetail, NotifyManagementItem, Page, PageResult } from "../types";
|
||||||
|
|
||||||
|
const BASE_URI = "/notify/internal";
|
||||||
|
const MANAGEMENT_BASE_URI = "/notify/management";
|
||||||
|
|
||||||
|
export const NotifyAPI = {
|
||||||
|
|
||||||
|
/** 管理端创建并发送通知。 */
|
||||||
|
async create(notify: Notify): Promise<Notify> {
|
||||||
|
return await axios.post("/notify/create", notify);
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 查询当前管理用户创建的通知列表。 */
|
||||||
|
async listManagement(page?: Page<Notify>): Promise<PageResult<NotifyManagementItem>> {
|
||||||
|
return await axios.post(`${MANAGEMENT_BASE_URI}/list`, page);
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 查询管理端通知详情及收件人。 */
|
||||||
|
async managementDetail(id: string): Promise<Notify> {
|
||||||
|
return await axios.post(`${MANAGEMENT_BASE_URI}/detail`, undefined, {
|
||||||
|
params: { id }
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 删除管理端通知。 */
|
||||||
|
async managementDelete(id: string): Promise<void> {
|
||||||
|
await axios.post(`${MANAGEMENT_BASE_URI}/delete`, undefined, {
|
||||||
|
params: { id }
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 取消管理端尚未发送的通知。 */
|
||||||
|
async managementCancel(id: string): Promise<void> {
|
||||||
|
await axios.post(`${MANAGEMENT_BASE_URI}/cancel`, undefined, {
|
||||||
|
params: { id }
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
async smsSend(captchaData: CaptchaData<Notify>) {
|
||||||
|
return await axios.post("/notify/sms/captcha/create", captchaData);
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 查询当前用户的站内通知详情。 */
|
||||||
|
async internalDetail(id: string): Promise<NotifyDetail> {
|
||||||
|
return await axios.post(`${BASE_URI}/detail`, undefined, {
|
||||||
|
params: { id }
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 查询当前用户的站内通知。 */
|
||||||
|
async listInternal(page?: Page<NotifyDetail>, unreadOnly = false): Promise<PageResult<NotifyDetail>> {
|
||||||
|
return await axios.post(`${BASE_URI}/internal`, page, {
|
||||||
|
params: { unreadOnly }
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 查询当前用户未读站内通知数量。 */
|
||||||
|
async countUnreadInternal(): Promise<number> {
|
||||||
|
return await axios.post(`${BASE_URI}/unread/count`);
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 标记当前用户的一条站内通知已读。 */
|
||||||
|
async markInternalRead(id: string): Promise<void> {
|
||||||
|
await axios.post(`${BASE_URI}/read`, undefined, {
|
||||||
|
params: { id }
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 删除当前用户的一条站内通知。 */
|
||||||
|
async deleteInternal(id: string): Promise<void> {
|
||||||
|
await axios.post(`${BASE_URI}/delete`, undefined, {
|
||||||
|
params: { id }
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
|
/** 标记当前用户全部站内通知已读。 */
|
||||||
|
async markAllInternalRead(): Promise<void> {
|
||||||
|
await axios.post(`${BASE_URI}/read/all`);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default NotifyAPI;
|
||||||
@@ -0,0 +1,29 @@
|
|||||||
|
import { axios } from "./BaseAPI";
|
||||||
|
import type { ModuleCode, Page, PageResult, Permission, PermissionPayload } from "../types";
|
||||||
|
|
||||||
|
const BASE_URI = "/user/permission";
|
||||||
|
|
||||||
|
export const PermissionAPI = {
|
||||||
|
async list(req: Page<Permission>): Promise<PageResult<Permission>> {
|
||||||
|
return axios.post(`${BASE_URI}/list`, req);
|
||||||
|
},
|
||||||
|
async create(req: PermissionPayload): Promise<void> {
|
||||||
|
return axios.post(`${BASE_URI}/create`, req);
|
||||||
|
},
|
||||||
|
async update(req: PermissionPayload): Promise<void> {
|
||||||
|
return axios.post(`${BASE_URI}/update`, req);
|
||||||
|
},
|
||||||
|
async remove(id: string): Promise<void> {
|
||||||
|
return axios.post(`${BASE_URI}/delete`, undefined, {
|
||||||
|
params: { id }
|
||||||
|
});
|
||||||
|
},
|
||||||
|
/** 查询当前登录用户在指定模块内可转授的权限。 */
|
||||||
|
async grantableList(moduleCode: ModuleCode): Promise<Permission[]> {
|
||||||
|
return await axios.post(`${BASE_URI}/grantable/list`, undefined, {
|
||||||
|
params: { moduleCode }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default PermissionAPI;
|
||||||
@@ -0,0 +1,100 @@
|
|||||||
|
import { axios } from "./BaseAPI";
|
||||||
|
import type { ModuleCode, Page, PageResult, Permission, Role, RolePayload, AuthorizeUserRole } from "../types";
|
||||||
|
|
||||||
|
const BASE_URI = "/user/role";
|
||||||
|
|
||||||
|
export const RoleAPI = {
|
||||||
|
async list(req: Page<Role>): Promise<PageResult<Role>> {
|
||||||
|
return axios.post(`${BASE_URI}/list`, req);
|
||||||
|
},
|
||||||
|
async detail(id: string): Promise<Role> {
|
||||||
|
return axios.post(`${BASE_URI}/detail`, undefined, {
|
||||||
|
params: { id }
|
||||||
|
});
|
||||||
|
},
|
||||||
|
async create(req: RolePayload): Promise<void> {
|
||||||
|
return axios.post(`${BASE_URI}/create`, req);
|
||||||
|
},
|
||||||
|
async update(req: RolePayload): Promise<void> {
|
||||||
|
return axios.post(`${BASE_URI}/update`, req);
|
||||||
|
},
|
||||||
|
async remove(id: string): Promise<void> {
|
||||||
|
return axios.post(`${BASE_URI}/delete`, undefined, {
|
||||||
|
params: { id }
|
||||||
|
});
|
||||||
|
},
|
||||||
|
/** 查询当前登录用户在模块内可管理的角色。 */
|
||||||
|
async manageableList(moduleCode: ModuleCode): Promise<Role[]> {
|
||||||
|
return await axios.post(`${BASE_URI}/manageable/list`, undefined, {
|
||||||
|
params: { moduleCode }
|
||||||
|
});
|
||||||
|
},
|
||||||
|
/** 查询当前登录用户在模块内可授权给账号的角色。 */
|
||||||
|
async grantableList(moduleCode: ModuleCode): Promise<Role[]> {
|
||||||
|
return await axios.post(`${BASE_URI}/grantable/list`, undefined, {
|
||||||
|
params: { moduleCode }
|
||||||
|
});
|
||||||
|
},
|
||||||
|
/** 查询当前登录用户直属角色详情,不按可管理范围过滤。 */
|
||||||
|
async currentDetailList(moduleCode?: ModuleCode): Promise<Role[]> {
|
||||||
|
return await axios.post(`${BASE_URI}/current/detail/list`, undefined, {
|
||||||
|
params: { moduleCode }
|
||||||
|
});
|
||||||
|
},
|
||||||
|
/** 查询当前登录用户可以给目标角色配置的直接权限 P 候选项。 */
|
||||||
|
async grantablePermissionList(roleId: string): Promise<Permission[]> {
|
||||||
|
return await axios.post(`${BASE_URI}/permission/grantable/list`, undefined, {
|
||||||
|
params: { roleId }
|
||||||
|
});
|
||||||
|
},
|
||||||
|
/** 查询目标角色当前可继续转授的权限 D。 */
|
||||||
|
async delegationList(roleId: string): Promise<Permission[]> {
|
||||||
|
return await axios.post(`${BASE_URI}/delegation/list`, undefined, {
|
||||||
|
params: { roleId }
|
||||||
|
});
|
||||||
|
},
|
||||||
|
/** 查询当前登录用户可以给目标角色配置的 D 候选项。 */
|
||||||
|
async grantableDelegationList(roleId: string): Promise<Permission[]> {
|
||||||
|
return await axios.post(`${BASE_URI}/delegation/grantable/list`, undefined, {
|
||||||
|
params: { roleId }
|
||||||
|
});
|
||||||
|
},
|
||||||
|
/** 覆盖保存目标角色可继续转授的权限 D。 */
|
||||||
|
async updateDelegation(req: Pick<RolePayload, "id" | "delegationPermissionIdList">): Promise<void> {
|
||||||
|
await axios.post(`${BASE_URI}/delegation/update`, req);
|
||||||
|
},
|
||||||
|
/** 查询目标角色可选管理父级。 */
|
||||||
|
async parentGrantableList(roleId: string): Promise<Role[]> {
|
||||||
|
return await axios.post(`${BASE_URI}/parent/grantable/list`, undefined, {
|
||||||
|
params: { roleId }
|
||||||
|
});
|
||||||
|
},
|
||||||
|
/** 查询创建模块角色时可选的管理父级。 */
|
||||||
|
async createParentGrantableList(moduleCode: ModuleCode): Promise<Role[]> {
|
||||||
|
return await axios.post(`${BASE_URI}/parent/create/grantable/list`, undefined, {
|
||||||
|
params: { moduleCode }
|
||||||
|
});
|
||||||
|
},
|
||||||
|
/** 保存目标角色管理父级。 */
|
||||||
|
async updateParent(req: Pick<RolePayload, "id" | "parentRoleId">): Promise<void> {
|
||||||
|
await axios.post(`${BASE_URI}/parent/update`, req);
|
||||||
|
},
|
||||||
|
async authorizedList(userId: string, moduleCode?: ModuleCode): Promise<Role[]> {
|
||||||
|
return axios.post(`${BASE_URI}/authorized/list`, undefined, {
|
||||||
|
params: {
|
||||||
|
userId,
|
||||||
|
moduleCode
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
async authorize(req: AuthorizeUserRole): Promise<void> {
|
||||||
|
return axios.post(`${BASE_URI}/authorized/create`, req);
|
||||||
|
},
|
||||||
|
async authorizedPermission(userId: string): Promise<Permission[]> {
|
||||||
|
return axios.post(`${BASE_URI}/authorized/permission`, undefined, {
|
||||||
|
params: { userId }
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default RoleAPI;
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
import type { Setting } from "../types";
|
||||||
|
import { axios } from "./BaseAPI";
|
||||||
|
|
||||||
|
const BASE_URI = "/setting";
|
||||||
|
|
||||||
|
export const SettingAPI = {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 查询公开配置映射
|
||||||
|
*
|
||||||
|
* @param bizType 业务类型
|
||||||
|
* @param bizId 业务 ID,可为空
|
||||||
|
* @param code 配置代码列表,可为空
|
||||||
|
*/
|
||||||
|
async map(bizType: string, bizId?: string, code?: string[]): Promise<Record<string, Setting>> {
|
||||||
|
return await axios.get(`${BASE_URI}/map`, {
|
||||||
|
params: {
|
||||||
|
bizType,
|
||||||
|
bizId,
|
||||||
|
code
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SettingAPI;
|
||||||
@@ -0,0 +1,94 @@
|
|||||||
|
import type {
|
||||||
|
Attachment,
|
||||||
|
CaptchaData,
|
||||||
|
Gender,
|
||||||
|
LoginRequest,
|
||||||
|
LoginResponse,
|
||||||
|
RegisterRequest,
|
||||||
|
SmsLoginRequest,
|
||||||
|
UpdatePasswordRequest,
|
||||||
|
User,
|
||||||
|
UserAttachType
|
||||||
|
} from "../types";
|
||||||
|
import { axios } from "./BaseAPI";
|
||||||
|
import AttachmentAPI from "./AttachmentAPI";
|
||||||
|
|
||||||
|
const BASE_URI = "/user";
|
||||||
|
type UserAttachmentOwner = {
|
||||||
|
attachmentList?: readonly Attachment[];
|
||||||
|
};
|
||||||
|
type UpdateCurrentRequest = Omit<Partial<User>, "gender"> & {
|
||||||
|
gender?: Gender | "";
|
||||||
|
};
|
||||||
|
|
||||||
|
function findAttachmentByType(attachmentList: readonly Attachment[], ...types: UserAttachType[]) {
|
||||||
|
for (let i = 0; i < attachmentList.length; i++) {
|
||||||
|
const attachType = attachmentList[i].attachType as UserAttachType | undefined;
|
||||||
|
const id = attachmentList[i].id;
|
||||||
|
if (id && attachType && types.includes(attachType)) {
|
||||||
|
return AttachmentAPI.getReadURL(id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const UserAPI = {
|
||||||
|
async register(req: CaptchaData<RegisterRequest>): Promise<LoginResponse> {
|
||||||
|
return await axios.post(`${BASE_URI}/register`, req);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 登录
|
||||||
|
*
|
||||||
|
* @param req 验证码登录对象
|
||||||
|
* @returns LoginResponse
|
||||||
|
*/
|
||||||
|
async login(req: CaptchaData<LoginRequest>): Promise<LoginResponse> {
|
||||||
|
return await axios.post(`${BASE_URI}/login`, req);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 短信验证码登录
|
||||||
|
*
|
||||||
|
* @param req 短信验证码登录请求
|
||||||
|
* @returns LoginResponse
|
||||||
|
*/
|
||||||
|
async loginBySms(req: SmsLoginRequest): Promise<LoginResponse> {
|
||||||
|
return await axios.post(`${BASE_URI}/login/sms`, req);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 验证是否已登录
|
||||||
|
*
|
||||||
|
* @returns true 为已登录
|
||||||
|
*/
|
||||||
|
async loginByToken(): Promise<LoginResponse> {
|
||||||
|
return await axios.post(`${BASE_URI}/login/token`);
|
||||||
|
},
|
||||||
|
async logout(): Promise<void> {
|
||||||
|
return await axios.post(`${BASE_URI}/logout`);
|
||||||
|
},
|
||||||
|
async updateCurrent(req: UpdateCurrentRequest): Promise<LoginResponse> {
|
||||||
|
return await axios.post(`${BASE_URI}/current/update`, req);
|
||||||
|
},
|
||||||
|
async updatePassword(req: UpdatePasswordRequest): Promise<void> {
|
||||||
|
return await axios.post(`${BASE_URI}/update/password`, req);
|
||||||
|
},
|
||||||
|
/**
|
||||||
|
* 获取用户数据
|
||||||
|
*
|
||||||
|
* @param id 用户 ID
|
||||||
|
* @returns 用户数据
|
||||||
|
*/
|
||||||
|
async view(id: string): Promise<User> {
|
||||||
|
return await axios.post(`${BASE_URI}/view/${id}`);
|
||||||
|
},
|
||||||
|
getAvatarURL(user?: UserAttachmentOwner) {
|
||||||
|
if (user?.attachmentList) {
|
||||||
|
return findAttachmentByType(user.attachmentList, "AVATAR");
|
||||||
|
}
|
||||||
|
},
|
||||||
|
getWrapperURL(user?: UserAttachmentOwner) {
|
||||||
|
if (user?.attachmentList) {
|
||||||
|
return findAttachmentByType(user.attachmentList, "WRAPPER");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export default UserAPI;
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
export { ArticleAPI } from "./ArticleAPI";
|
||||||
|
export { AttachmentAPI } from "./AttachmentAPI";
|
||||||
|
export { CommentAPI } from "./CommentAPI";
|
||||||
|
export { CommonAPI } from "./CommonAPI";
|
||||||
|
export { DeveloperAPI } from "./DeveloperAPI";
|
||||||
|
export { NotifyAPI } from "./NotifyAPI";
|
||||||
|
export { PermissionAPI } from "./PermissionAPI";
|
||||||
|
export { RoleAPI } from "./RoleAPI";
|
||||||
|
export { SettingAPI } from "./SettingAPI";
|
||||||
|
export { UserAPI } from "./UserAPI";
|
||||||
@@ -0,0 +1,150 @@
|
|||||||
|
/**
|
||||||
|
* 文章编辑器的公共数据处理。
|
||||||
|
* 负责统一内容类型、附件标识和存储前的富文本格式,平台层不参与这些规则。
|
||||||
|
*/
|
||||||
|
import type { ArticleContentType, Attachment } from "../types";
|
||||||
|
import { ATTACHMENT_ID_ATTR, isLocalMediaId, normalizeContent, TEMP_FILE_ID_ATTR } from "timi-web/editor";
|
||||||
|
|
||||||
|
/** 文章内容中附件和临时文件的标识前缀。 */
|
||||||
|
export const ATTACHMENT_ID_PREFIX = "attachment-id:";
|
||||||
|
export const TEMP_FILE_ID_PREFIX = "temp-file-id:";
|
||||||
|
export const ARTICLE_IMAGE_ATTACH_TYPE = "CONTENT_IMAGE";
|
||||||
|
export const ARTICLE_VIDEO_ATTACH_TYPE = "CONTENT_VIDEO";
|
||||||
|
/** 支持的文章内容类型和默认类型。 */
|
||||||
|
export const DEFAULT_ARTICLE_CONTENT_TYPE: ArticleContentType = "TIP_TAP";
|
||||||
|
export const ARTICLE_CONTENT_TYPE_LIST: ArticleContentType[] = ["TEXT", "MARKDOWN", "TIP_TAP"];
|
||||||
|
|
||||||
|
/** 将后端历史值和未知值归一化为当前支持的内容类型。 */
|
||||||
|
export function normalizeArticleContentType(contentType?: string): ArticleContentType {
|
||||||
|
return ARTICLE_CONTENT_TYPE_LIST.includes(contentType as ArticleContentType) ? contentType as ArticleContentType : DEFAULT_ARTICLE_CONTENT_TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 将空内容转换为空字符串,避免编辑器收到 undefined。 */
|
||||||
|
export function normalizeRawArticleContent(content?: string) {
|
||||||
|
return content || "";
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 从媒体 src 中读取持久化附件 ID。 */
|
||||||
|
export function getAttachmentIdFromSrc(src?: string) {
|
||||||
|
if (!src?.startsWith(ATTACHMENT_ID_PREFIX)) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return src.slice(ATTACHMENT_ID_PREFIX.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 从媒体 src 中读取临时文件 ID。 */
|
||||||
|
export function getTempFileIdFromSrc(src?: string) {
|
||||||
|
if (!src?.startsWith(TEMP_FILE_ID_PREFIX)) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
return src.slice(TEMP_FILE_ID_PREFIX.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 构造带有附件 ID 的内部媒体地址。 */
|
||||||
|
export function buildAttachmentIdSrc(attachmentId: string) {
|
||||||
|
return `${ATTACHMENT_ID_PREFIX}${attachmentId}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 根据媒体标签判断附件类型。 */
|
||||||
|
function getMediaAttachType(tagName: string) {
|
||||||
|
return tagName.toLowerCase() === "video" ? ARTICLE_VIDEO_ATTACH_TYPE : ARTICLE_IMAGE_ATTACH_TYPE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 仅为 blob 预览地址保留预览元数据,服务器地址不重复保存。 */
|
||||||
|
function buildAttachmentMetadata(src: string) {
|
||||||
|
if (!src.startsWith("blob:")) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
previewUrl: src
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 过滤本地临时媒体 ID,避免把前端占位 ID 当成可上传附件。 */
|
||||||
|
function normalizeAttachmentTempFileId(tempFileId?: string) {
|
||||||
|
if (!tempFileId || isLocalMediaId(tempFileId)) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
return tempFileId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 从文章 HTML 中提取图片和视频附件,并按 ID 去重。 */
|
||||||
|
export function buildAttachmentListFromContent(content?: string) {
|
||||||
|
const rawContent = normalizeContent(content);
|
||||||
|
if (!rawContent) {
|
||||||
|
return [] as Attachment[];
|
||||||
|
}
|
||||||
|
const parser = new DOMParser();
|
||||||
|
const doc = parser.parseFromString(rawContent, "text/html");
|
||||||
|
const mediaList = Array.from(doc.querySelectorAll("img, video"));
|
||||||
|
const attachmentMap = new Map<string, Attachment>();
|
||||||
|
|
||||||
|
for (const media of mediaList) {
|
||||||
|
const src = media.getAttribute("src") || "";
|
||||||
|
const attachmentId = media.getAttribute(ATTACHMENT_ID_ATTR) || getAttachmentIdFromSrc(src);
|
||||||
|
const tempFileId = media.getAttribute(TEMP_FILE_ID_ATTR) || getTempFileIdFromSrc(src);
|
||||||
|
const key = attachmentId || tempFileId;
|
||||||
|
if (!key || attachmentMap.has(key)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
attachmentMap.set(key, {
|
||||||
|
id: attachmentId || key,
|
||||||
|
tempFileId: normalizeAttachmentTempFileId(tempFileId),
|
||||||
|
name: media.getAttribute("title") || media.getAttribute("alt") || "",
|
||||||
|
attachType: getMediaAttachType(media.tagName),
|
||||||
|
metadata: buildAttachmentMetadata(src)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return Array.from(attachmentMap.values());
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 将媒体地址统一改写为附件地址,供后端保存文章内容。 */
|
||||||
|
export function normalizeContentForStore(content?: string) {
|
||||||
|
const rawContent = normalizeContent(content);
|
||||||
|
if (!rawContent) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
const parser = new DOMParser();
|
||||||
|
const doc = parser.parseFromString(rawContent, "text/html");
|
||||||
|
const mediaList = Array.from(doc.querySelectorAll("img, video"));
|
||||||
|
for (const media of mediaList) {
|
||||||
|
const src = media.getAttribute("src") || "";
|
||||||
|
const attachmentId = media.getAttribute(ATTACHMENT_ID_ATTR) || getAttachmentIdFromSrc(src);
|
||||||
|
const tempFileId = media.getAttribute(TEMP_FILE_ID_ATTR) || getTempFileIdFromSrc(src);
|
||||||
|
const finalAttachmentId = attachmentId || tempFileId;
|
||||||
|
if (!finalAttachmentId) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
media.setAttribute(ATTACHMENT_ID_ATTR, finalAttachmentId);
|
||||||
|
media.removeAttribute(TEMP_FILE_ID_ATTR);
|
||||||
|
media.setAttribute("src", buildAttachmentIdSrc(finalAttachmentId));
|
||||||
|
}
|
||||||
|
return normalizeContent(doc.body.innerHTML);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 比较附件列表的业务字段,不比较对象引用。 */
|
||||||
|
export function sameAttachmentList(left?: Attachment[], right?: Attachment[]) {
|
||||||
|
const leftList = left || [];
|
||||||
|
const rightList = right || [];
|
||||||
|
if (leftList.length !== rightList.length) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (let i = 0; i < leftList.length; i += 1) {
|
||||||
|
const leftItem = leftList[i];
|
||||||
|
const rightItem = rightList[i];
|
||||||
|
if (!rightItem) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if (leftItem.id !== rightItem.id) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ((leftItem.tempFileId || "") !== (rightItem.tempFileId || "")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
if ((leftItem.accessKey?.value || "") !== (rightItem.accessKey?.value || "")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
@@ -0,0 +1,7 @@
|
|||||||
|
/** 业务编辑器数据适配和通用编辑器能力出口。 */
|
||||||
|
export * from "timi-web/editor";
|
||||||
|
export * from "./article";
|
||||||
|
export * from "./notify";
|
||||||
|
export type { Article, ArticleContentType } from "../types/Article";
|
||||||
|
export type { Attachment } from "../types/Attachment";
|
||||||
|
export type { NotifyDetail } from "../types/Notify";
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
/** 通知编辑器的公共数据规则。 */
|
||||||
|
import type { NotifyContentType } from "../types";
|
||||||
|
|
||||||
|
/** 通知缺少内容类型时默认使用 Tiptap。 */
|
||||||
|
export const DEFAULT_NOTIFY_CONTENT_TYPE: NotifyContentType = "TIP_TAP";
|
||||||
|
|
||||||
|
/** 规范化通知内容类型,兼容旧通知未返回内容类型的情况。 */
|
||||||
|
export function normalizeNotifyContentType(contentType?: string): NotifyContentType {
|
||||||
|
return contentType === "TEXT" ? "TEXT" : DEFAULT_NOTIFY_CONTENT_TYPE;
|
||||||
|
}
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
export * from "./api";
|
||||||
|
export { axios } from "./api/BaseAPI";
|
||||||
|
export * from "./editor";
|
||||||
|
export * from "./store";
|
||||||
|
export * from "./types";
|
||||||
|
export * from "./utils";
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
export * from "./userStore";
|
||||||
@@ -0,0 +1,217 @@
|
|||||||
|
import {defineStore} from "pinia";
|
||||||
|
import {computed, reactive, readonly, ref} from "vue";
|
||||||
|
import type {CaptchaData, LoginRequest, LoginResponse, LoginUser} from "../types";
|
||||||
|
import {Cooker, Storage} from "timi-web";
|
||||||
|
import {UserAPI} from "../api";
|
||||||
|
|
||||||
|
/** 用户 Store 配置项 */
|
||||||
|
interface UserStoreOptions {
|
||||||
|
/** localStorage 储存 key,默认 "loginUser" */
|
||||||
|
storageKey?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 访问控制匹配模式 */
|
||||||
|
export type AccessMatchMode = "and" | "or";
|
||||||
|
|
||||||
|
let _storageKey = "loginUser";
|
||||||
|
|
||||||
|
type AccessItem = string | {
|
||||||
|
moduleCode?: string;
|
||||||
|
code?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
function normalizeAccessItem(item?: AccessItem): string {
|
||||||
|
if (!item) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof item === "string") {
|
||||||
|
return item.trim();
|
||||||
|
}
|
||||||
|
|
||||||
|
const code = item.code?.trim();
|
||||||
|
if (!code) {
|
||||||
|
return "";
|
||||||
|
}
|
||||||
|
|
||||||
|
const moduleCode = item.moduleCode?.trim();
|
||||||
|
return moduleCode && !code.includes(":") ? `${moduleCode}:${code}` : code;
|
||||||
|
}
|
||||||
|
|
||||||
|
function normalizeAccessList(list?: AccessItem[]): string[] {
|
||||||
|
if (!list?.length) {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
return list
|
||||||
|
.map(normalizeAccessItem)
|
||||||
|
.filter((item): item is string => !!item);
|
||||||
|
}
|
||||||
|
|
||||||
|
function matchAccess(sourceList?: string[], targetList?: string | string[], mode: AccessMatchMode = "or"): boolean {
|
||||||
|
const normalizedTargetList = normalizeAccessList(Array.isArray(targetList) ? targetList : [targetList || ""]);
|
||||||
|
if (!normalizedTargetList.length) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const sourceSet = new Set(normalizeAccessList(sourceList));
|
||||||
|
if (!sourceSet.size) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (mode === "and") {
|
||||||
|
return normalizedTargetList.every(item => sourceSet.has(item));
|
||||||
|
}
|
||||||
|
return normalizedTargetList.some(item => sourceSet.has(item));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 配置用户 Store,应在使用 useUserStore 之前调用
|
||||||
|
* @param options 配置项
|
||||||
|
*/
|
||||||
|
function configureUserStore(options: UserStoreOptions): void {
|
||||||
|
if (options.storageKey) {
|
||||||
|
_storageKey = options.storageKey;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const useUserStore = defineStore("user", () => {
|
||||||
|
type LoginUserState = LoginUser & Record<string, unknown>;
|
||||||
|
|
||||||
|
const loginUser = reactive<LoginUserState>({});
|
||||||
|
const isRestored = ref(false);
|
||||||
|
|
||||||
|
const token = computed(() => loginUser.token ?? "");
|
||||||
|
const isLoggedIn = computed(() => isValidLoginUser(loginUser));
|
||||||
|
|
||||||
|
function resetLoginUser(nextUser?: LoginUserState): void {
|
||||||
|
Object.keys(loginUser).forEach((key) => {
|
||||||
|
delete loginUser[key];
|
||||||
|
});
|
||||||
|
if (nextUser) {
|
||||||
|
Object.assign(loginUser, nextUser);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function isValidLoginUser(user?: LoginUserState | null): user is LoginResponse {
|
||||||
|
return !!user?.token && !!user.expireAt && new Date().getTime() < user.expireAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
function isLogged(): boolean {
|
||||||
|
return isLoggedIn.value;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getLoginUser<T extends LoginUser = LoginUser>(): T | null {
|
||||||
|
if (!isLogged()) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return loginUser as T;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getRoleList(): string[] {
|
||||||
|
return normalizeAccessList(loginUser.user?.roleList);
|
||||||
|
}
|
||||||
|
|
||||||
|
function getPermissionList(): string[] {
|
||||||
|
return normalizeAccessList(loginUser.user?.permissionList);
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasRoles(roleList: string | string[], mode: AccessMatchMode = "or"): boolean {
|
||||||
|
return matchAccess(getRoleList(), roleList, mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasPermissions(permissionList: string | string[], mode: AccessMatchMode = "or"): boolean {
|
||||||
|
return matchAccess(getPermissionList(), permissionList, mode);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function login<T extends LoginUser = LoginUser>(req: CaptchaData<LoginRequest>): Promise<T> {
|
||||||
|
const result = await UserAPI.login(req);
|
||||||
|
const nextUser = result as unknown as T;
|
||||||
|
await updateLoginUser(nextUser);
|
||||||
|
return nextUser;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loginByToken<T extends LoginUser = LoginUser>(token: string): Promise<T | null> {
|
||||||
|
loginUser.token = token;
|
||||||
|
// 未过期
|
||||||
|
try {
|
||||||
|
const result = await UserAPI.loginByToken();
|
||||||
|
if (!result) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
const nextUser = result as unknown as T;
|
||||||
|
await updateLoginUser(nextUser);
|
||||||
|
return nextUser;
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
clearLoginUser();
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loginByStorage<T extends LoginUser = LoginUser>(): Promise<T | null> {
|
||||||
|
if (loginUser.token) {
|
||||||
|
isRestored.value = true;
|
||||||
|
return getLoginUser<T>();
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!Storage.has(_storageKey)) {
|
||||||
|
isRestored.value = true;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 未登录,储存有令牌。
|
||||||
|
const storageUser = Storage.getJSON(_storageKey) as T;
|
||||||
|
if (isValidLoginUser(storageUser)) {
|
||||||
|
const result = await loginByToken<T>(storageUser.token);
|
||||||
|
isRestored.value = true;
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
clearLoginUser();
|
||||||
|
isRestored.value = true;
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function updateLoginUser<T extends LoginUser = LoginUser>(user: T): Promise<void> {
|
||||||
|
resetLoginUser(user as LoginUserState);
|
||||||
|
Storage.setJSON(_storageKey, user);
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearLoginUser(): void {
|
||||||
|
resetLoginUser();
|
||||||
|
Cooker.remove("Token");
|
||||||
|
Storage.remove(_storageKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
async function logout(): Promise<void> {
|
||||||
|
try {
|
||||||
|
await UserAPI.logout();
|
||||||
|
} finally {
|
||||||
|
clearLoginUser();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
token,
|
||||||
|
isLoggedIn,
|
||||||
|
isRestored,
|
||||||
|
getRoleList,
|
||||||
|
getPermissionList,
|
||||||
|
hasRoles,
|
||||||
|
hasPermissions,
|
||||||
|
isLogged,
|
||||||
|
loginUser: readonly(loginUser),
|
||||||
|
getLoginUser,
|
||||||
|
login,
|
||||||
|
loginByToken,
|
||||||
|
loginByStorage,
|
||||||
|
updateLoginUser,
|
||||||
|
clearLoginUser,
|
||||||
|
logout
|
||||||
|
};
|
||||||
|
});
|
||||||
|
|
||||||
|
export {
|
||||||
|
useUserStore,
|
||||||
|
configureUserStore
|
||||||
|
};
|
||||||
@@ -0,0 +1,52 @@
|
|||||||
|
import type { Attachment } from "./Attachment";
|
||||||
|
import type { Comment } from "./Comment";
|
||||||
|
import type { Model } from "./Model";
|
||||||
|
import type { Setting } from "./Setting";
|
||||||
|
|
||||||
|
/** 文章业务类型 */
|
||||||
|
export type ArticleBizType = "BLOG" | "FEEDBACK";
|
||||||
|
|
||||||
|
/** 文章正文类型 */
|
||||||
|
export type ArticleContentType = "TEXT" | "MARKDOWN" | "TIP_TAP";
|
||||||
|
|
||||||
|
/** 文章状态 */
|
||||||
|
export type ArticleStatus = "DART" | "NORMAL" | "HIDDEN";
|
||||||
|
|
||||||
|
/** 文章实体 */
|
||||||
|
export type Article = {
|
||||||
|
/** 业务类型 */
|
||||||
|
bizType?: ArticleBizType;
|
||||||
|
|
||||||
|
/** 业务 ID */
|
||||||
|
bizId?: string;
|
||||||
|
|
||||||
|
/** 标题 */
|
||||||
|
title?: string;
|
||||||
|
|
||||||
|
/** 描述 */
|
||||||
|
description?: string;
|
||||||
|
|
||||||
|
/** 正文类型 */
|
||||||
|
contentType?: ArticleContentType;
|
||||||
|
|
||||||
|
/** 正文内容 */
|
||||||
|
content?: string;
|
||||||
|
|
||||||
|
/** 阅读数量 */
|
||||||
|
reads?: number;
|
||||||
|
|
||||||
|
/** 喜欢数量 */
|
||||||
|
likes?: number;
|
||||||
|
|
||||||
|
/** 文章状态 */
|
||||||
|
status?: ArticleStatus;
|
||||||
|
|
||||||
|
/** 设置列表 */
|
||||||
|
settingList?: Setting[];
|
||||||
|
|
||||||
|
/** 评论列表 */
|
||||||
|
commentList?: Comment[];
|
||||||
|
|
||||||
|
/** 附件列表 */
|
||||||
|
attachmentList?: Attachment[];
|
||||||
|
} & Model;
|
||||||
@@ -0,0 +1,48 @@
|
|||||||
|
import type { Model } from "./Model";
|
||||||
|
|
||||||
|
export type Attachment = {
|
||||||
|
bizType?: AttachmentBizType;
|
||||||
|
bizId?: string;
|
||||||
|
attachType?: string;
|
||||||
|
mongoId?: string;
|
||||||
|
title?: string;
|
||||||
|
name?: string;
|
||||||
|
mimeType?: string;
|
||||||
|
metadata?: Record<string, any>;
|
||||||
|
accessKey?: AttachmentAccessKey;
|
||||||
|
size?: number;
|
||||||
|
md5?: string;
|
||||||
|
uploaderIp?: string;
|
||||||
|
isDestroyed?: boolean;
|
||||||
|
destroyAt?: number;
|
||||||
|
tempFileId?: string;
|
||||||
|
} & Model;
|
||||||
|
|
||||||
|
export type TempFile = {
|
||||||
|
id: string;
|
||||||
|
expireAt?: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type BizIdUpdate = {
|
||||||
|
bizType: AttachmentBizType;
|
||||||
|
bizId: string;
|
||||||
|
items: Partial<Attachment>[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export type AttachmentBizType =
|
||||||
|
| "ARTICLE"
|
||||||
|
| "USER"
|
||||||
|
| "GAO_CUSTOMER"
|
||||||
|
| "GAO_EVENT_RECORD"
|
||||||
|
| "GAO_REGISTER_RECORD"
|
||||||
|
| "TEMP_FILE"
|
||||||
|
| "MIRROR";
|
||||||
|
|
||||||
|
/** 附件访问密钥 */
|
||||||
|
export type AttachmentAccessKey = {
|
||||||
|
value: string;
|
||||||
|
attachmentId: string;
|
||||||
|
isDisposable: boolean;
|
||||||
|
expiredAt: number;
|
||||||
|
createdAt: number;
|
||||||
|
};
|
||||||
@@ -0,0 +1,34 @@
|
|||||||
|
import type { Model, Page } from "./Model";
|
||||||
|
import type { User } from "./User";
|
||||||
|
|
||||||
|
/** 评论 */
|
||||||
|
export type Comment = {
|
||||||
|
bizType?: CommentBizType;
|
||||||
|
bizId?: number;
|
||||||
|
userId?: number;
|
||||||
|
nick?: string;
|
||||||
|
content?: string;
|
||||||
|
|
||||||
|
/** 所属用户 */
|
||||||
|
user?: User;
|
||||||
|
|
||||||
|
/** 回复列表 */
|
||||||
|
replies?: Comment[];
|
||||||
|
|
||||||
|
/** 回复分页 */
|
||||||
|
repliesPage?: Page<Comment>;
|
||||||
|
|
||||||
|
/** 用于绑定组件当前页下标 */
|
||||||
|
repliesCurrent?: number;
|
||||||
|
|
||||||
|
/** 回复数量 */
|
||||||
|
repliesLength?: number;
|
||||||
|
|
||||||
|
/** 关联文章 */
|
||||||
|
article?: object;
|
||||||
|
|
||||||
|
/** 关联 Git 仓库 */
|
||||||
|
repository?: object;
|
||||||
|
} & Model
|
||||||
|
|
||||||
|
export type CommentBizType = "ARTICLE" | "GIT_ISSUE" | "GIT_MERGE";
|
||||||
@@ -0,0 +1,6 @@
|
|||||||
|
/** 开发者配置 */
|
||||||
|
export type Developer = {
|
||||||
|
developerId?: number;
|
||||||
|
name?: string;
|
||||||
|
rsa?: string;
|
||||||
|
}
|
||||||
@@ -0,0 +1,67 @@
|
|||||||
|
// 基本实体模型
|
||||||
|
export type Model = {
|
||||||
|
id?: string;
|
||||||
|
|
||||||
|
createdAt?: number;
|
||||||
|
updatedAt?: number;
|
||||||
|
deletedAt?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type ApiResponse<T = unknown> = {
|
||||||
|
code: number;
|
||||||
|
message: string;
|
||||||
|
messageCode: string;
|
||||||
|
success: boolean;
|
||||||
|
fail: boolean;
|
||||||
|
data: T;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type Page<T> = {
|
||||||
|
orderMap?: { [K in keyof T]?: OrderType };
|
||||||
|
equalsExample?: Partial<T>;
|
||||||
|
likesExample?: Partial<T>;
|
||||||
|
} & BasePage;
|
||||||
|
|
||||||
|
export type BasePage = {
|
||||||
|
index: number;
|
||||||
|
size: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type OrderType = "ASC" | "DESC";
|
||||||
|
|
||||||
|
export type PageResult<T> = {
|
||||||
|
total: number;
|
||||||
|
list: T[];
|
||||||
|
}
|
||||||
|
|
||||||
|
// 携带验证码的请求体
|
||||||
|
export type CaptchaData<T> = {
|
||||||
|
captchaId: string;
|
||||||
|
captcha: string;
|
||||||
|
data: T;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 图形验证码
|
||||||
|
export type CaptchaResult = {
|
||||||
|
id: string;
|
||||||
|
data: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 用户背景图和头像的图片渲染样式。 */
|
||||||
|
export const ImageType = {
|
||||||
|
"IR-AUTO": "ir-auto",
|
||||||
|
"IR-SMOOTH": "ir-smooth",
|
||||||
|
"IR-PIXELATED": "ir-pixelated"
|
||||||
|
} as const;
|
||||||
|
|
||||||
|
export type ImageType = keyof typeof ImageType;
|
||||||
|
|
||||||
|
export type KeyValue<V, K = string> = {
|
||||||
|
key: K;
|
||||||
|
value?: V;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type LabelValue<L, V = string> = {
|
||||||
|
label: L;
|
||||||
|
value?: V;
|
||||||
|
}
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
import type { Model } from "./Model";
|
||||||
|
import type { Attachment } from "./Attachment";
|
||||||
|
import type { User } from "./User";
|
||||||
|
|
||||||
|
/** 通知消息类型 */
|
||||||
|
export type NotifyMsgType = "INTERNAL" | "SMS" | "MAIL" | "HTTP" | "WECHAT";
|
||||||
|
|
||||||
|
/** 通知投递状态 */
|
||||||
|
export type NotifyStatus = "WAITING" | "REMINDED" | "TIMEOUT" | "CANCEL" | "FAIL";
|
||||||
|
|
||||||
|
/** 通知内容类型 */
|
||||||
|
export type NotifyContentType = "TEXT" | "TIP_TAP";
|
||||||
|
|
||||||
|
/** 通知 */
|
||||||
|
export type Notify = {
|
||||||
|
/** 业务类型 */
|
||||||
|
bizType?: string;
|
||||||
|
/** 提醒业务类型 */
|
||||||
|
bizRemindedType?: string;
|
||||||
|
/** 提醒业务 ID */
|
||||||
|
bizRemindedId?: string;
|
||||||
|
/** 参数 */
|
||||||
|
args?: string;
|
||||||
|
/** 创建者 */
|
||||||
|
createdBy?: string;
|
||||||
|
/** 通知详情列表 */
|
||||||
|
detailList?: NotifyDetail[];
|
||||||
|
} & Model;
|
||||||
|
|
||||||
|
/** 管理端通知列表项 */
|
||||||
|
export type NotifyManagementItem = {
|
||||||
|
/** 创建者用户 ID */
|
||||||
|
createdBy?: string;
|
||||||
|
/** 通知标题 */
|
||||||
|
subject?: string;
|
||||||
|
/** 通知内容 */
|
||||||
|
content?: string;
|
||||||
|
/** 内容类型 */
|
||||||
|
contentType?: NotifyContentType;
|
||||||
|
/** 发送时间 */
|
||||||
|
sendAt?: number;
|
||||||
|
/** 接收用户数量 */
|
||||||
|
recipientCount?: number;
|
||||||
|
/** 已读用户数量 */
|
||||||
|
readCount?: number;
|
||||||
|
/** 等待发送数量 */
|
||||||
|
waitingCount?: number;
|
||||||
|
/** 已发送数量 */
|
||||||
|
remindedCount?: number;
|
||||||
|
/** 超时数量 */
|
||||||
|
timeoutCount?: number;
|
||||||
|
/** 已取消数量 */
|
||||||
|
cancelCount?: number;
|
||||||
|
/** 失败数量 */
|
||||||
|
failCount?: number;
|
||||||
|
} & Model;
|
||||||
|
|
||||||
|
/** 通知详情 */
|
||||||
|
export type NotifyDetail = {
|
||||||
|
/** 所属通知 ID */
|
||||||
|
notifyId?: string;
|
||||||
|
/** 消息类型 */
|
||||||
|
msgType?: NotifyMsgType;
|
||||||
|
/** 模板 ID */
|
||||||
|
templateId?: string;
|
||||||
|
/** 标题 */
|
||||||
|
subject?: string;
|
||||||
|
/** 内容 */
|
||||||
|
content?: string;
|
||||||
|
/** 内容类型 */
|
||||||
|
contentType?: NotifyContentType;
|
||||||
|
/** 媒体附件列表 */
|
||||||
|
attachmentList?: Attachment[];
|
||||||
|
/** 参数 */
|
||||||
|
args?: string;
|
||||||
|
/** 发送来源 */
|
||||||
|
sendFrom?: string;
|
||||||
|
/** 发送去向 */
|
||||||
|
sendTo?: string;
|
||||||
|
/** 发送时间 */
|
||||||
|
sendAt?: number;
|
||||||
|
/** 重试计数 */
|
||||||
|
retry?: number;
|
||||||
|
/** 超时时间 */
|
||||||
|
timeoutAt?: number;
|
||||||
|
/** 结果描述 */
|
||||||
|
resultDesc?: string;
|
||||||
|
/** 投递状态 */
|
||||||
|
status?: NotifyStatus;
|
||||||
|
/** 已读时间,站内通知未读时为空 */
|
||||||
|
readAt?: number;
|
||||||
|
/** 所属通知 */
|
||||||
|
notify?: Notify;
|
||||||
|
/** 发送用户 */
|
||||||
|
fromUser?: User;
|
||||||
|
/** 接收用户 */
|
||||||
|
toUser?: User;
|
||||||
|
} & Model;
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
export type ModuleCode = "CORE" | "GAO";
|
||||||
|
|
||||||
|
export interface Permission {
|
||||||
|
id: string;
|
||||||
|
moduleCode: ModuleCode;
|
||||||
|
code: string;
|
||||||
|
nameLangId?: string;
|
||||||
|
name?: string;
|
||||||
|
description?: string;
|
||||||
|
builtin?: boolean;
|
||||||
|
protectedPermission?: boolean;
|
||||||
|
ownerType?: AuthOwnerType;
|
||||||
|
ownerId?: string;
|
||||||
|
createdBy?: string;
|
||||||
|
createdAt?: number;
|
||||||
|
updatedAt?: number;
|
||||||
|
deletedAt?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type AuthOwnerType = "SYSTEM" | "MODULE" | "TENANT" | "STORE" | "USER";
|
||||||
|
|
||||||
|
export type PermissionPayload = Pick<Permission, "moduleCode" | "code" | "nameLangId" | "name" | "description"> & {
|
||||||
|
id?: string;
|
||||||
|
};
|
||||||
@@ -0,0 +1,41 @@
|
|||||||
|
import type { AuthOwnerType, ModuleCode, Permission } from "./Permission";
|
||||||
|
import type { Model } from "./Model";
|
||||||
|
|
||||||
|
export type Role = {
|
||||||
|
moduleCode: ModuleCode;
|
||||||
|
code: string;
|
||||||
|
nameLangId?: string;
|
||||||
|
name?: string;
|
||||||
|
description?: string;
|
||||||
|
builtin?: boolean;
|
||||||
|
protectedRole?: boolean;
|
||||||
|
parentRoleId?: string;
|
||||||
|
ownerType?: AuthOwnerType;
|
||||||
|
ownerId?: string;
|
||||||
|
createdBy?: string;
|
||||||
|
|
||||||
|
permissionList?: Permission[];
|
||||||
|
allPermissionList?: Permission[];
|
||||||
|
childRoleList?: Role[];
|
||||||
|
parentRole?: Role;
|
||||||
|
delegationPermissionList?: Permission[];
|
||||||
|
permissionIdList?: string[];
|
||||||
|
childRoleIdList?: string[];
|
||||||
|
delegationPermissionIdList?: string[];
|
||||||
|
} & Model;
|
||||||
|
|
||||||
|
export type RolePayload = Partial<Pick<Role, "moduleCode" | "code" | "nameLangId" | "name" | "description">> & {
|
||||||
|
id?: string;
|
||||||
|
parentRoleId?: string;
|
||||||
|
ownerType?: AuthOwnerType;
|
||||||
|
ownerId?: string;
|
||||||
|
permissionIdList?: string[];
|
||||||
|
childRoleIdList?: string[];
|
||||||
|
delegationPermissionIdList?: string[];
|
||||||
|
};
|
||||||
|
|
||||||
|
export interface AuthorizeUserRole {
|
||||||
|
userId: string;
|
||||||
|
moduleCode?: ModuleCode;
|
||||||
|
roleIdList?: string[];
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
import type {Model} from "./Model";
|
||||||
|
|
||||||
|
export type SettingValueType =
|
||||||
|
| "STRING"
|
||||||
|
| "SELECT_RADIO"
|
||||||
|
| "SELECT_CHECKBOX"
|
||||||
|
| "INTEGER"
|
||||||
|
| "COLOR"
|
||||||
|
| "FLOAT"
|
||||||
|
| "DATE"
|
||||||
|
| "DATETIME"
|
||||||
|
| "TIME"
|
||||||
|
| "DURATION"
|
||||||
|
| "BOOLEAN"
|
||||||
|
| "JSON_LIST"
|
||||||
|
| "JSON_OBJECT"
|
||||||
|
;
|
||||||
|
|
||||||
|
export type Setting = {
|
||||||
|
module: string;
|
||||||
|
key: string;
|
||||||
|
title?: string;
|
||||||
|
description?: string;
|
||||||
|
value?: string;
|
||||||
|
valueType: SettingValueType;
|
||||||
|
valueArgs?: any;
|
||||||
|
} & Model;
|
||||||
@@ -0,0 +1,98 @@
|
|||||||
|
import { ImageType } from "./Model";
|
||||||
|
import type { Model } from "./Model";
|
||||||
|
import type { Attachment } from "./Attachment";
|
||||||
|
import type { Setting } from "./Setting";
|
||||||
|
import type { Role } from "./Role";
|
||||||
|
|
||||||
|
export type Gender = "MALE" | "FEMALE";
|
||||||
|
|
||||||
|
export type User = {
|
||||||
|
name: string;
|
||||||
|
nick?: string;
|
||||||
|
password?: string;
|
||||||
|
email?: string;
|
||||||
|
emailVerifyAt?: number;
|
||||||
|
phoneNo?: string;
|
||||||
|
phoneNoVerifyAt?: number;
|
||||||
|
wrapperType?: ImageType;
|
||||||
|
avatarType?: ImageType;
|
||||||
|
exp?: number;
|
||||||
|
gender?: Gender;
|
||||||
|
birthdate?: number;
|
||||||
|
qq?: string;
|
||||||
|
description?: string;
|
||||||
|
lastLoginIP?: string;
|
||||||
|
lastLoginAt?: number;
|
||||||
|
unmuteAt?: number;
|
||||||
|
unbanAt?: number;
|
||||||
|
|
||||||
|
roleList?: string[];
|
||||||
|
roleEntityList?: Role[];
|
||||||
|
permissionList?: string[];
|
||||||
|
attachmentList?: Attachment[];
|
||||||
|
settingList?: Setting[];
|
||||||
|
tempFileIdList?: string[];
|
||||||
|
muting?: boolean;
|
||||||
|
banning?: boolean;
|
||||||
|
emailVerified?: boolean;
|
||||||
|
phoneNoVerified?: boolean;
|
||||||
|
} & Model;
|
||||||
|
|
||||||
|
/** 用户资料组件使用的用户视图。 */
|
||||||
|
export type UserView = {
|
||||||
|
id?: string;
|
||||||
|
name: string;
|
||||||
|
profile: {
|
||||||
|
wrapperType?: ImageType;
|
||||||
|
avatarType?: ImageType;
|
||||||
|
exp?: number;
|
||||||
|
sex?: number;
|
||||||
|
description?: string;
|
||||||
|
attachmentList?: Attachment[];
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export type UserAttachType = "AVATAR" | "WRAPPER";
|
||||||
|
|
||||||
|
export type RegisterRequest = {
|
||||||
|
name: string;
|
||||||
|
password: string;
|
||||||
|
email?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type LoginRequest = {
|
||||||
|
user: string;
|
||||||
|
password: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SmsLoginRequest = {
|
||||||
|
detailId: string;
|
||||||
|
captcha: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type UpdatePasswordRequest = {
|
||||||
|
oldValue: string;
|
||||||
|
newValue: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
// 登录返回
|
||||||
|
export type LoginResponse = {
|
||||||
|
id: string;
|
||||||
|
token: string;
|
||||||
|
expireAt: number;
|
||||||
|
user?: User;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type LoginUser = {
|
||||||
|
id?: string;
|
||||||
|
token?: string;
|
||||||
|
expireAt?: number;
|
||||||
|
user?: User;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type UserLevelType = {
|
||||||
|
exp: number; // 经验数值,和 UserData.exp 一样
|
||||||
|
value: number; // 经验对应等级,[0, 8]
|
||||||
|
percent: number; // 该经验在该等级的百分比 [0, 1]
|
||||||
|
nextLevelUp: number; // 下一级经验值
|
||||||
|
}
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
export * from "./Article";
|
||||||
|
export * from "./Attachment";
|
||||||
|
export * from "./Comment";
|
||||||
|
export * from "./Developer";
|
||||||
|
export * from "./Model";
|
||||||
|
export * from "./Notify";
|
||||||
|
export * from "./Permission";
|
||||||
|
export * from "./Role";
|
||||||
|
export * from "./Setting";
|
||||||
|
export * from "./User";
|
||||||
@@ -0,0 +1,118 @@
|
|||||||
|
import axios, { type AxiosInstance, type AxiosResponse, type InternalAxiosRequestConfig } from "axios";
|
||||||
|
import type { ApiResponse } from "../types";
|
||||||
|
|
||||||
|
/** 当前后端服务的响应包装。 */
|
||||||
|
export type ErrorCallback = (response: ApiResponse) => void;
|
||||||
|
export type RequestInterceptor = (config: InternalAxiosRequestConfig) => InternalAxiosRequestConfig;
|
||||||
|
export interface ResponseObserverPayload {
|
||||||
|
config?: InternalAxiosRequestConfig;
|
||||||
|
status?: number;
|
||||||
|
response?: AxiosResponse;
|
||||||
|
error?: unknown;
|
||||||
|
serverResponse?: ApiResponse;
|
||||||
|
}
|
||||||
|
export type ResponseObserver = (payload: ResponseObserverPayload) => void;
|
||||||
|
|
||||||
|
let globalErrorCallback: ErrorCallback | null = null;
|
||||||
|
let globalRequestInterceptor: RequestInterceptor | null = null;
|
||||||
|
let globalResponseObserver: ResponseObserver | null = null;
|
||||||
|
|
||||||
|
export const setGlobalErrorCallback = (callback: ErrorCallback) => {
|
||||||
|
globalErrorCallback = callback;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const setGlobalRequestInterceptor = (callback: RequestInterceptor | null) => {
|
||||||
|
globalRequestInterceptor = callback;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const setGlobalResponseObserver = (callback: ResponseObserver | null) => {
|
||||||
|
globalResponseObserver = callback;
|
||||||
|
};
|
||||||
|
|
||||||
|
const notifyResponseObserver = (payload: ResponseObserverPayload) => {
|
||||||
|
if (!globalResponseObserver) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
globalResponseObserver(payload);
|
||||||
|
};
|
||||||
|
|
||||||
|
const applyDefaultInterceptors = (instance: AxiosInstance) => {
|
||||||
|
instance.defaults.withCredentials = true;
|
||||||
|
instance.interceptors.request.use((config) => {
|
||||||
|
if (globalRequestInterceptor) {
|
||||||
|
return globalRequestInterceptor(config);
|
||||||
|
}
|
||||||
|
return config;
|
||||||
|
});
|
||||||
|
instance.interceptors.response.use((axiosResp: AxiosResponse) => {
|
||||||
|
if (!axiosResp.config.responseType) {
|
||||||
|
const serverResp = axiosResp.data as ApiResponse;
|
||||||
|
if (serverResp.code < 40000) {
|
||||||
|
notifyResponseObserver({
|
||||||
|
config: axiosResp.config,
|
||||||
|
status: axiosResp.status,
|
||||||
|
response: axiosResp,
|
||||||
|
serverResponse: serverResp
|
||||||
|
});
|
||||||
|
return serverResp.data;
|
||||||
|
}
|
||||||
|
notifyResponseObserver({
|
||||||
|
config: axiosResp.config,
|
||||||
|
status: axiosResp.status,
|
||||||
|
response: axiosResp,
|
||||||
|
serverResponse: serverResp,
|
||||||
|
error: serverResp.message
|
||||||
|
});
|
||||||
|
if (globalErrorCallback) {
|
||||||
|
globalErrorCallback(serverResp);
|
||||||
|
}
|
||||||
|
return Promise.reject(serverResp.message);
|
||||||
|
}
|
||||||
|
notifyResponseObserver({
|
||||||
|
config: axiosResp.config,
|
||||||
|
status: axiosResp.status,
|
||||||
|
response: axiosResp
|
||||||
|
});
|
||||||
|
return axiosResp.data;
|
||||||
|
}, (error: unknown) => {
|
||||||
|
const axiosError = error as {
|
||||||
|
config?: InternalAxiosRequestConfig;
|
||||||
|
response?: AxiosResponse;
|
||||||
|
request?: unknown;
|
||||||
|
message?: string;
|
||||||
|
};
|
||||||
|
notifyResponseObserver({
|
||||||
|
config: axiosError.config,
|
||||||
|
status: axiosError.response?.status,
|
||||||
|
response: axiosError.response,
|
||||||
|
error
|
||||||
|
});
|
||||||
|
if (axiosError.response?.status) {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
if (axiosError.request) {
|
||||||
|
if (axiosError.message?.startsWith("timeout")) {
|
||||||
|
throw new Error("time out");
|
||||||
|
}
|
||||||
|
throw new Error(axiosError.message || "request error");
|
||||||
|
}
|
||||||
|
throw error;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const axiosInstance = axios.create();
|
||||||
|
applyDefaultInterceptors(axiosInstance);
|
||||||
|
|
||||||
|
export const createServiceAxios = (baseURL?: string) => {
|
||||||
|
const instance = axios.create({ baseURL });
|
||||||
|
applyDefaultInterceptors(instance);
|
||||||
|
return instance;
|
||||||
|
};
|
||||||
|
|
||||||
|
export default {
|
||||||
|
axios: axiosInstance,
|
||||||
|
setGlobalErrorCallback,
|
||||||
|
setGlobalRequestInterceptor,
|
||||||
|
setGlobalResponseObserver,
|
||||||
|
createServiceAxios
|
||||||
|
};
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
import type { UserLevelType } from "../types/User";
|
||||||
|
|
||||||
|
/** 根据经验值计算用户等级。 */
|
||||||
|
export function toUserLevel(exp = 0): UserLevelType {
|
||||||
|
const levels = [0, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096];
|
||||||
|
for (let i = 1; i < levels.length; i++) {
|
||||||
|
if (exp < levels[i]) {
|
||||||
|
return {
|
||||||
|
exp,
|
||||||
|
value: i - 1,
|
||||||
|
percent: (exp - levels[i - 1]) / (levels[i] - levels[i - 1]),
|
||||||
|
nextLevelUp: i === levels.length - 1 ? 4096 : levels[i]
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
exp,
|
||||||
|
value: 8,
|
||||||
|
percent: 1,
|
||||||
|
nextLevelUp: 4096
|
||||||
|
};
|
||||||
|
}
|
||||||
@@ -0,0 +1,227 @@
|
|||||||
|
import type { Directive, DirectiveBinding, EffectScope } from "vue";
|
||||||
|
import { effectScope, watch } from "vue";
|
||||||
|
import { useUserStore } from "../../store";
|
||||||
|
import type { AccessMatchMode } from "../../store";
|
||||||
|
|
||||||
|
/** 访问控制类型 */
|
||||||
|
export type AccessControlType = "permission" | "role";
|
||||||
|
/** 指令结果生效方式 */
|
||||||
|
export type AccessEffectMode = "if" | "show";
|
||||||
|
|
||||||
|
/** 自定义回调参数 */
|
||||||
|
export type AccessDirectiveCallbackPayload = {
|
||||||
|
type: AccessControlType;
|
||||||
|
granted: boolean;
|
||||||
|
valueList: string[];
|
||||||
|
mode: AccessMatchMode;
|
||||||
|
el: HTMLElement;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 访问控制指令配置
|
||||||
|
* `value` 支持单个值或值数组
|
||||||
|
* 数组默认按 `or` 逻辑处理,需要 `and` 时显式传 `mode`
|
||||||
|
* `callback` 存在时优先执行回调,不再使用 `effect`
|
||||||
|
*/
|
||||||
|
export type AccessDirectiveConfig = {
|
||||||
|
value: string | string[];
|
||||||
|
mode?: AccessMatchMode;
|
||||||
|
effect?: AccessEffectMode;
|
||||||
|
reverse?: boolean;
|
||||||
|
display?: string;
|
||||||
|
callback?: (payload: AccessDirectiveCallbackPayload) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
type AccessDirectiveValue = string | string[] | AccessDirectiveConfig;
|
||||||
|
|
||||||
|
type AccessDirectiveState = {
|
||||||
|
scope: EffectScope;
|
||||||
|
placeholder: Comment | null;
|
||||||
|
display: string;
|
||||||
|
config: AccessDirectiveConfig;
|
||||||
|
lastGranted?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
type AccessMatcher = (store: ReturnType<typeof useUserStore>, valueList: string[], mode: AccessMatchMode) => boolean;
|
||||||
|
|
||||||
|
const ACCESS_STATE_KEY = "__vAccessState";
|
||||||
|
|
||||||
|
/** 过滤空字符串并统一裁剪空白 */
|
||||||
|
function normalizeValueList(value: string | string[]): string[] {
|
||||||
|
return (Array.isArray(value) ? value : [value])
|
||||||
|
.map(item => item?.trim())
|
||||||
|
.filter((item): item is string => !!item);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 统一把指令值转成标准配置 */
|
||||||
|
function normalizeConfig(value: AccessDirectiveValue): AccessDirectiveConfig {
|
||||||
|
if (typeof value === "string" || Array.isArray(value)) {
|
||||||
|
return {
|
||||||
|
value,
|
||||||
|
mode: "or",
|
||||||
|
effect: "if",
|
||||||
|
reverse: false
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
value: value?.value || [],
|
||||||
|
mode: value?.mode || "or",
|
||||||
|
effect: value?.effect || "if",
|
||||||
|
reverse: !!value?.reverse,
|
||||||
|
display: value?.display,
|
||||||
|
callback: value?.callback
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 恢复被 `if` 模式替换掉的真实元素 */
|
||||||
|
function restoreElement(el: HTMLElement, state: AccessDirectiveState): void {
|
||||||
|
if (state.placeholder?.parentNode) {
|
||||||
|
state.placeholder.parentNode.replaceChild(el, state.placeholder);
|
||||||
|
}
|
||||||
|
state.placeholder = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** `if` 模式:无权限时直接移出 DOM */
|
||||||
|
function applyIfEffect(el: HTMLElement, state: AccessDirectiveState, granted: boolean): void {
|
||||||
|
if (granted) {
|
||||||
|
restoreElement(el, state);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!state.placeholder && el.parentNode) {
|
||||||
|
state.placeholder = document.createComment("v-access");
|
||||||
|
el.parentNode.replaceChild(state.placeholder, el);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** `show` 模式:保留 DOM,仅切换 display */
|
||||||
|
function applyShowEffect(el: HTMLElement, state: AccessDirectiveState, granted: boolean): void {
|
||||||
|
restoreElement(el, state);
|
||||||
|
el.style.display = granted ? state.display : "none";
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 自定义模式:交给调用方自己处理样式或行为 */
|
||||||
|
function applyCustomCallback(
|
||||||
|
el: HTMLElement,
|
||||||
|
state: AccessDirectiveState,
|
||||||
|
granted: boolean,
|
||||||
|
type: AccessControlType,
|
||||||
|
valueList: string[],
|
||||||
|
mode: AccessMatchMode
|
||||||
|
): void {
|
||||||
|
restoreElement(el, state);
|
||||||
|
if (state.lastGranted === granted) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.config.callback?.({
|
||||||
|
type,
|
||||||
|
granted,
|
||||||
|
valueList,
|
||||||
|
mode,
|
||||||
|
el
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 根据当前登录用户状态刷新指令效果 */
|
||||||
|
function updateAccess(
|
||||||
|
el: HTMLElement,
|
||||||
|
state: AccessDirectiveState,
|
||||||
|
type: AccessControlType,
|
||||||
|
matcher: AccessMatcher
|
||||||
|
): void {
|
||||||
|
const store = useUserStore();
|
||||||
|
const config = state.config;
|
||||||
|
const mode = config.mode || "or";
|
||||||
|
const valueList = normalizeValueList(config.value);
|
||||||
|
const granted = config.reverse ? !matcher(store, valueList, mode) : matcher(store, valueList, mode);
|
||||||
|
|
||||||
|
if (config.callback) {
|
||||||
|
applyCustomCallback(el, state, granted, type, valueList, mode);
|
||||||
|
} else if (config.effect === "show") {
|
||||||
|
applyShowEffect(el, state, granted);
|
||||||
|
} else {
|
||||||
|
applyIfEffect(el, state, granted);
|
||||||
|
}
|
||||||
|
|
||||||
|
state.lastGranted = granted;
|
||||||
|
}
|
||||||
|
|
||||||
|
function getState(el: HTMLElement): AccessDirectiveState | undefined {
|
||||||
|
return (el as HTMLElement & { [ACCESS_STATE_KEY]?: AccessDirectiveState })[ACCESS_STATE_KEY];
|
||||||
|
}
|
||||||
|
|
||||||
|
function setState(el: HTMLElement, state: AccessDirectiveState): void {
|
||||||
|
(el as HTMLElement & { [ACCESS_STATE_KEY]?: AccessDirectiveState })[ACCESS_STATE_KEY] = state;
|
||||||
|
}
|
||||||
|
|
||||||
|
function clearState(el: HTMLElement): void {
|
||||||
|
delete (el as HTMLElement & { [ACCESS_STATE_KEY]?: AccessDirectiveState })[ACCESS_STATE_KEY];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 卸载时清掉占位注释,避免遗留无用节点 */
|
||||||
|
function clearPlaceholder(state: AccessDirectiveState): void {
|
||||||
|
if (state.placeholder?.parentNode) {
|
||||||
|
state.placeholder.parentNode.removeChild(state.placeholder);
|
||||||
|
}
|
||||||
|
state.placeholder = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 创建角色/权限通用指令 */
|
||||||
|
function createAccessDirective(type: AccessControlType, matcher: AccessMatcher): Directive<HTMLElement, AccessDirectiveValue> {
|
||||||
|
return {
|
||||||
|
mounted(el: HTMLElement, binding: DirectiveBinding<AccessDirectiveValue>) {
|
||||||
|
const state: AccessDirectiveState = {
|
||||||
|
scope: effectScope(),
|
||||||
|
placeholder: null,
|
||||||
|
display: el.style.display,
|
||||||
|
config: normalizeConfig(binding.value)
|
||||||
|
};
|
||||||
|
|
||||||
|
setState(el, state);
|
||||||
|
|
||||||
|
state.scope.run(() => {
|
||||||
|
const store = useUserStore();
|
||||||
|
watch(
|
||||||
|
() => {
|
||||||
|
return type === "permission" ? store.getPermissionList().join("|") : store.getRoleList().join("|");
|
||||||
|
},
|
||||||
|
() => updateAccess(el, state, type, matcher),
|
||||||
|
{ immediate: true }
|
||||||
|
);
|
||||||
|
});
|
||||||
|
},
|
||||||
|
updated(el: HTMLElement, binding: DirectiveBinding<AccessDirectiveValue>) {
|
||||||
|
const state = getState(el);
|
||||||
|
if (!state) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.config = normalizeConfig(binding.value);
|
||||||
|
updateAccess(el, state, type, matcher);
|
||||||
|
},
|
||||||
|
unmounted(el: HTMLElement) {
|
||||||
|
const state = getState(el);
|
||||||
|
if (!state) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
state.scope.stop();
|
||||||
|
clearPlaceholder(state);
|
||||||
|
clearState(el);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 权限指令:`v-can` */
|
||||||
|
export const VCan = createAccessDirective("permission", (store, valueList, mode) => {
|
||||||
|
return store.hasPermissions(valueList, mode);
|
||||||
|
});
|
||||||
|
|
||||||
|
/** 角色指令:`v-role` */
|
||||||
|
export const VRole = createAccessDirective("role", (store, valueList, mode) => {
|
||||||
|
return store.hasRoles(valueList, mode);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default VCan;
|
||||||
@@ -0,0 +1,5 @@
|
|||||||
|
export { default as VCan } from "./directives/Access";
|
||||||
|
export * from "./directives/Access";
|
||||||
|
export { default as Network } from "./Network";
|
||||||
|
export * from "./Network";
|
||||||
|
export { toUserLevel } from "./User";
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "ESNext",
|
||||||
|
"module": "ESNext",
|
||||||
|
"moduleResolution": "Bundler",
|
||||||
|
"strict": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"rootDir": "src",
|
||||||
|
"lib": [
|
||||||
|
"ESNext",
|
||||||
|
"DOM",
|
||||||
|
"DOM.Iterable"
|
||||||
|
],
|
||||||
|
"baseUrl": ".",
|
||||||
|
"paths": {
|
||||||
|
"timi-web": [
|
||||||
|
"../timi-web/dist/src/index.d.ts"
|
||||||
|
],
|
||||||
|
"timi-web/editor": [
|
||||||
|
"../timi-web/dist/src/editor/index.d.ts"
|
||||||
|
],
|
||||||
|
"vue": [
|
||||||
|
"../timi-web/node_modules/vue"
|
||||||
|
],
|
||||||
|
"pinia": [
|
||||||
|
"../timi-web/node_modules/pinia"
|
||||||
|
]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"include": [
|
||||||
|
"src/**/*.ts",
|
||||||
|
"src/**/*.d.ts"
|
||||||
|
]
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user