Compare commits
48 Commits
1baef251ab
...
master
| Author | SHA1 | Date | |
|---|---|---|---|
| 0344ff2862 | |||
| 17bae862ed | |||
| fb64b1a768 | |||
| 05c1f339e6 | |||
| f822e319ba | |||
| 2962865857 | |||
| 5415da5b6c | |||
| 5149b68237 | |||
| a6e7cca92b | |||
| 2c3700f4f8 | |||
| 1bcc63d5de | |||
| 9db3dfe675 | |||
| 9ee93bea60 | |||
| 04fd9f8c0e | |||
| 5f8e55a3a3 | |||
| 01ae26e869 | |||
| bf61c4af4e | |||
| f566a33865 | |||
| d96fac324b | |||
| c8129ce658 | |||
| 5b0783097d | |||
| 9cde482dfe | |||
| 8e793ea37f | |||
| dde1212a57 | |||
| 6527eec133 | |||
| 2109903ba2 | |||
| 46dc2d4682 | |||
| 3c9d0dae22 | |||
| b6a81848ea | |||
| 096fbb661d | |||
| 035c4ab000 | |||
| 1efff107f9 | |||
| 1795cd73c7 | |||
| 5f21126a62 | |||
| 2085588556 | |||
| 025a67ce45 | |||
| cb806e19b1 | |||
| d4680d258c | |||
| a832883f17 | |||
| c086285340 | |||
| 7fa05b0004 | |||
| 65cf893a5f | |||
| e20ee8e065 | |||
| 7d8723715c | |||
| 49bb348707 | |||
| ef6de1d12b | |||
| c76a4e2018 | |||
| e504f806a9 |
11
.gitea/workflows/deploy.yaml
Normal file
11
.gitea/workflows/deploy.yaml
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
name: 自动部署
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ master ]
|
||||||
|
jobs:
|
||||||
|
部署应用:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: 启动部署
|
||||||
|
run: |
|
||||||
|
docker exec gitea-runner /opt/deploy.sh
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
name: 部署 admin-ui 到 K3s
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- main
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
deploy:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- name: 拉取代码
|
|
||||||
uses: actions/checkout@v4
|
|
||||||
|
|
||||||
- name: 部署到 K3s
|
|
||||||
run: |
|
|
||||||
kubectl apply -f k8s/
|
|
||||||
kubectl rollout restart deployment admin-ui
|
|
||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,8 +1,6 @@
|
|||||||
.DS_Store
|
.DS_Store
|
||||||
node_modules
|
node_modules
|
||||||
/dist
|
/dist
|
||||||
|
|
||||||
# Vue language service / accidental transpile artifacts
|
|
||||||
src/**/*.vue.js
|
src/**/*.vue.js
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
120
CLAUDE.md
120
CLAUDE.md
@@ -92,126 +92,6 @@ This is the **GFast UI** project (`gfast-ui`), a Vue 3 admin management system b
|
|||||||
|
|
||||||
5. **Keep-alive**: Route components can be cached via keep-alive, managed by the `useKeepALiveNames` store.
|
5. **Keep-alive**: Route components can be cached via keep-alive, managed by the `useKeepALiveNames` store.
|
||||||
|
|
||||||
## Repository Collaboration Rules
|
|
||||||
|
|
||||||
These rules capture long-term repository preferences confirmed by the user and should be applied in future sessions unless the user explicitly overrides them.
|
|
||||||
|
|
||||||
### Pagination Rules
|
|
||||||
|
|
||||||
- Prefer the project global `pagination` component by default.
|
|
||||||
- Pagination should be fixed and visible as part of the page or dialog layout, not conditionally hidden only because the dataset is small.
|
|
||||||
- In single-page scenarios, the UI should still show total count together with the pagination area.
|
|
||||||
- Avoid designs where pagination only appears after data exceeds one page.
|
|
||||||
|
|
||||||
### Dialog Layout Rules
|
|
||||||
|
|
||||||
- For dialogs that contain searchable tabular data, prefer this structure:
|
|
||||||
1. search area
|
|
||||||
2. table/content area
|
|
||||||
3. pagination area placed under the table inside the dialog body
|
|
||||||
4. footer action buttons
|
|
||||||
- The pagination area should remain visually stable and should not be omitted just because the current result set is small.
|
|
||||||
|
|
||||||
### Theme and Style Rules
|
|
||||||
|
|
||||||
- When functionality and existing theme style conflict, prioritize making the feature clearly visible and usable first.
|
|
||||||
- After ensuring usability, align the implementation with the repository's existing theme and style system as much as possible.
|
|
||||||
- Be cautious about global theme styles that may affect local pagination, dialog, or table layouts.
|
|
||||||
|
|
||||||
### Problem-solving Workflow
|
|
||||||
|
|
||||||
- For repeated UI issues, analyze in this order:
|
|
||||||
1. interface/request data and returned structure
|
|
||||||
2. component logic and render conditions
|
|
||||||
3. theme and style coverage/conflicts
|
|
||||||
4. then decide whether a rewrite is necessary
|
|
||||||
- Do not jump straight to a rewrite before checking interface behavior and style interference.
|
|
||||||
|
|
||||||
### Execution Workflow Rules
|
|
||||||
|
|
||||||
- When modifying a feature, locate it in this order: usage location, component definition, API definition, then at least one similar implementation in the project.
|
|
||||||
- For common UI features such as pagination, dialogs, and tables, align with an existing similar implementation before making changes.
|
|
||||||
- Prefer componentization. If a page or Vue SFC is already large, or a block has a clear independent responsibility, split it into components instead of continuing to extend the same file.
|
|
||||||
- For very long files such as complex page-level `index.vue` files, treat further component splitting as the default direction rather than the exception.
|
|
||||||
- Before editing large files or complex Vue SFCs, first read the target context; if a replacement fails once, do not repeat the same replacement blindly, and instead expand the context or switch to smaller targeted edits.
|
|
||||||
- After UI or interaction changes, verify not only lint status for edited files but also render conditions, interface fields, and possible style impact.
|
|
||||||
- If the same issue is not resolved after one attempt, pause and ask the user before continuing with more speculative changes.
|
|
||||||
- Do not proactively rewrite a component unless the user explicitly asks for a rewrite.
|
|
||||||
- At the start of a new conversation, read `CLAUDE.md` before making changes.
|
|
||||||
- When code changes appear not to take effect, check in this order: compile/HMR status, render conditions, API data, then style overrides.
|
|
||||||
- Turn repeated low-level mistakes into explicit workflow checks and follow those checks in future edits.
|
|
||||||
|
|
||||||
### Environment and Tool Selection Rules
|
|
||||||
|
|
||||||
- Before running environment-dependent commands, first follow the known runtime context already provided by the IDE, such as OS and default shell.
|
|
||||||
- On Windows projects with PowerShell as the active shell, prefer PowerShell-native commands and scripts by default.
|
|
||||||
- Do not assume Python, bash, sed, awk, or other non-default tooling is installed locally unless the user or repository explicitly indicates that they are available.
|
|
||||||
- For reading and editing repository files, prefer dedicated file tools first; use shell-based text replacement only when file tools are not suitable for the change.
|
|
||||||
- When a replacement fails once, do not keep retrying the same method blindly. Re-read the exact target content and switch to a smaller or more reliable edit strategy that matches the current environment.
|
|
||||||
- Avoid trial-and-error probing for basic tooling when the available environment is already known from context.
|
|
||||||
|
|
||||||
### Componentization and Structure Rules
|
|
||||||
|
|
||||||
- If a view block has an independent responsibility, prefer extracting it into a component instead of keeping it inside a large page file.
|
|
||||||
- When a file is already long, or a new feature would further expand responsibilities, proactively split it into components.
|
|
||||||
- Child components may keep their own local logic when that logic belongs clearly to the child’s responsibility; only shared state and necessary events should stay in the parent.
|
|
||||||
- For complex pages, small-scale components can stay in `component/`, while larger feature areas may be grouped by responsibility in subdirectories.
|
|
||||||
- For very long page files, prefer keeping the parent focused on composition, state orchestration, and data flow, while moving feature UI and local behavior into child components.
|
|
||||||
- When extracting new child components from a large file, move their related local styles with them to avoid continued growth of the parent file.
|
|
||||||
|
|
||||||
### API and Data Handling Rules
|
|
||||||
|
|
||||||
- Follow the project’s common response structure by default, but add moderate component-side compatibility handling when equivalent APIs return slightly inconsistent fields.
|
|
||||||
- For page-level error handling, use global handling by default, but use page-level or silent handling when the interaction requires local control.
|
|
||||||
- Prefer a unified parameter-building entry for request params instead of assembling the same parameters across multiple functions.
|
|
||||||
|
|
||||||
### Form and Interaction Rules
|
|
||||||
|
|
||||||
- Follow the project’s existing form behavior style: required fields should be clearly validated on submit, while avoiding unnecessarily disruptive validation during input.
|
|
||||||
- For shared create/edit forms, initialize, patch, and reset form state through a unified initialization path to avoid stale state.
|
|
||||||
- Search interactions should behave consistently across button click, Enter key, and clear actions, and should reset to the first page by default.
|
|
||||||
|
|
||||||
### Dialog and Selector Rules
|
|
||||||
|
|
||||||
- Dialogs should clear temporary internal state on close by default; any state that needs to persist should be explicitly provided back by the parent.
|
|
||||||
- Selector components should follow a consistent pattern: `v-model` for visibility, a default value prop, and a `confirm` event for returning the selected result.
|
|
||||||
- For temporary selectors and lightweight dialogs, prefer `destroy-on-close`; for more complex stateful forms, decide deliberately rather than applying it blindly.
|
|
||||||
|
|
||||||
### Styling Rules
|
|
||||||
|
|
||||||
- Prefer local scoped style changes first; only modify global theme styles after confirming the issue is truly shared across multiple places.
|
|
||||||
- When overriding Element Plus internals, prefer local `:deep()` overrides before considering global style changes.
|
|
||||||
- Use semantic, area-based class names instead of short or overly generic names.
|
|
||||||
|
|
||||||
### Debugging and Validation Rules
|
|
||||||
|
|
||||||
- Minimal temporary debugging is allowed when necessary, but all temporary debugging code must be removed before final delivery.
|
|
||||||
- Temporary `console` output is acceptable only when debugging is genuinely needed and must be removed before final delivery.
|
|
||||||
- For UI and interaction changes, verify lint, key render conditions, interface fields, and style impact before considering the task complete.
|
|
||||||
|
|
||||||
### Change Scope and Consistency Rules
|
|
||||||
|
|
||||||
- Prefer the smallest necessary change set, but do not pretend a local-only fix is sufficient when the real root cause is elsewhere in the chain.
|
|
||||||
- Only fix nearby issues when they are strongly related to the current task; otherwise ask the user before expanding the scope.
|
|
||||||
- Within the current modification scope, move code toward repository rules when practical, but avoid broad unrelated refactors.
|
|
||||||
|
|
||||||
### Communication and Risk Rules
|
|
||||||
|
|
||||||
- 默认使用中文进行回复、说明、提问与思考表达;如果用户明确要求使用其他语言,再按用户要求切换。
|
|
||||||
- Before changing code, confirm with the user when a change involves a rewrite, global side effects, unclear requirements, or a second attempt that still has not solved the issue.
|
|
||||||
- After changes, explain what changed, why it was changed that way, and any remaining risks or points worth confirming.
|
|
||||||
- When a requirement still depends on a meaningful assumption, ask the user first instead of silently choosing a direction.
|
|
||||||
|
|
||||||
### Repository Memory Rules
|
|
||||||
|
|
||||||
- When collaboration reveals a repeated problem pattern, proactively suggest turning it into an explicit repository rule in `CLAUDE.md`.
|
|
||||||
- Project-specific experience or pitfall notes may be added when they become necessary, but do not expand them prematurely without clear recurring value.
|
|
||||||
|
|
||||||
### Rule Documentation Style
|
|
||||||
|
|
||||||
- Keep long-term repository rules concise and practical.
|
|
||||||
- Prefer short rule lists with a small amount of explanation over overly long documentation.
|
|
||||||
|
|
||||||
## Environment Variables
|
## Environment Variables
|
||||||
|
|
||||||
- `.env.development` - Development environment settings
|
- `.env.development` - Development environment settings
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
# ==================== 第一阶段:构建前端 ====================
|
# ==================== 第一阶段:构建前端 ====================
|
||||||
FROM docker.m.daocloud.io/node:18-alpine AS builder
|
FROM node:18-alpine AS builder
|
||||||
WORKDIR /app
|
WORKDIR /app
|
||||||
# 配置Alpine国内镜像源
|
# 配置Alpine国内镜像源
|
||||||
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
|
RUN sed -i 's/dl-cdn.alpinelinux.org/mirrors.aliyun.com/g' /etc/apk/repositories
|
||||||
@@ -13,7 +13,7 @@ COPY . .
|
|||||||
RUN npm run build
|
RUN npm run build
|
||||||
|
|
||||||
# ==================== 第二阶段:部署到Nginx ====================
|
# ==================== 第二阶段:部署到Nginx ====================
|
||||||
FROM docker.m.daocloud.io/nginx:alpine
|
FROM nginx:alpine
|
||||||
|
|
||||||
# 复制构建产物
|
# 复制构建产物
|
||||||
COPY --from=builder /app/dist/ /usr/share/nginx/html/
|
COPY --from=builder /app/dist/ /usr/share/nginx/html/
|
||||||
|
|||||||
52
ngnix.conf
52
ngnix.conf
@@ -1,13 +1,6 @@
|
|||||||
# Nginx 静态文件服务 + 智能代理 + 自定义 404 页面
|
# Nginx 静态文件服务 + 智能代理
|
||||||
|
|
||||||
# Gitea 域名转发(HTTP)
|
# HTTP 重定向到 HTTPS
|
||||||
server {
|
|
||||||
listen 80;
|
|
||||||
server_name gitea.redpowerfuture.com;
|
|
||||||
return 301 https://$host$request_uri;
|
|
||||||
}
|
|
||||||
|
|
||||||
# HTTP 重定向到 HTTPS(其他域名)
|
|
||||||
server {
|
server {
|
||||||
listen 80;
|
listen 80;
|
||||||
server_name _;
|
server_name _;
|
||||||
@@ -26,27 +19,11 @@ server {
|
|||||||
ssl_protocols TLSv1.2 TLSv1.3;
|
ssl_protocols TLSv1.2 TLSv1.3;
|
||||||
ssl_ciphers HIGH:!aNULL:!MD5;
|
ssl_ciphers HIGH:!aNULL:!MD5;
|
||||||
|
|
||||||
# 自定义 404 页面,保持 404 状态码
|
|
||||||
error_page 404 /404.html;
|
|
||||||
|
|
||||||
location = /404.html {
|
|
||||||
internal;
|
|
||||||
}
|
|
||||||
|
|
||||||
# 根路径默认进入网页端
|
# 根路径默认进入网页端
|
||||||
location = / {
|
location = / {
|
||||||
return 302 /web/index.html;
|
return 302 /web/index.html;
|
||||||
}
|
}
|
||||||
|
|
||||||
# 兼容无斜杠访问
|
|
||||||
location = /web {
|
|
||||||
return 302 /web/;
|
|
||||||
}
|
|
||||||
|
|
||||||
location = /sys {
|
|
||||||
return 302 /sys/;
|
|
||||||
}
|
|
||||||
|
|
||||||
# 网页端(public/web/index.html -> dist/web/index.html)
|
# 网页端(public/web/index.html -> dist/web/index.html)
|
||||||
location /web/ {
|
location /web/ {
|
||||||
alias /usr/share/nginx/html/web/;
|
alias /usr/share/nginx/html/web/;
|
||||||
@@ -56,7 +33,7 @@ server {
|
|||||||
# 后台管理端(dist/index.html,前缀 /sys/)
|
# 后台管理端(dist/index.html,前缀 /sys/)
|
||||||
location /sys/ {
|
location /sys/ {
|
||||||
alias /usr/share/nginx/html/;
|
alias /usr/share/nginx/html/;
|
||||||
try_files $uri $uri/ /index.html;
|
try_files $uri $uri/ /sys/index.html;
|
||||||
}
|
}
|
||||||
|
|
||||||
# 1. 先尝试作为静态文件查找
|
# 1. 先尝试作为静态文件查找
|
||||||
@@ -83,26 +60,3 @@ server {
|
|||||||
proxy_read_timeout 30s;
|
proxy_read_timeout 30s;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
# Gitea 域名转发(HTTPS)
|
|
||||||
server {
|
|
||||||
listen 443 ssl;
|
|
||||||
server_name gitea.redpowerfuture.com;
|
|
||||||
|
|
||||||
ssl_certificate /etc/nginx/ssl/gitea.redpowerfuture.com.pem;
|
|
||||||
ssl_certificate_key /etc/nginx/ssl/gitea.redpowerfuture.com.key;
|
|
||||||
ssl_protocols TLSv1.2 TLSv1.3;
|
|
||||||
ssl_ciphers HIGH:!aNULL:!MD5;
|
|
||||||
|
|
||||||
location / {
|
|
||||||
proxy_pass http://gitea.redpowerfuture.com:3000;
|
|
||||||
proxy_http_version 1.1;
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
proxy_connect_timeout 30s;
|
|
||||||
proxy_send_timeout 30s;
|
|
||||||
proxy_read_timeout 30s;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|||||||
212
public/404.html
212
public/404.html
@@ -1,212 +0,0 @@
|
|||||||
<!DOCTYPE html>
|
|
||||||
<html lang="zh-CN">
|
|
||||||
<head>
|
|
||||||
<meta charset="UTF-8" />
|
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
<title>404 - 页面不存在</title>
|
|
||||||
<link rel="icon" href="/favicon.ico" />
|
|
||||||
<style>
|
|
||||||
:root {
|
|
||||||
color-scheme: light;
|
|
||||||
--bg-start: #f5f7fa;
|
|
||||||
--bg-end: #e8ecf3;
|
|
||||||
--card-bg: rgba(255, 255, 255, 0.92);
|
|
||||||
--text-primary: #1f2d3d;
|
|
||||||
--text-secondary: #6b7280;
|
|
||||||
--accent: #409eff;
|
|
||||||
--accent-hover: #66b1ff;
|
|
||||||
--border: rgba(64, 158, 255, 0.12);
|
|
||||||
--shadow: 0 20px 50px rgba(31, 45, 61, 0.12);
|
|
||||||
}
|
|
||||||
|
|
||||||
* {
|
|
||||||
box-sizing: border-box;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
margin: 0;
|
|
||||||
min-height: 100vh;
|
|
||||||
font-family: Inter, -apple-system, BlinkMacSystemFont, 'Segoe UI', 'PingFang SC', 'Hiragino Sans GB', 'Microsoft YaHei', sans-serif;
|
|
||||||
background: linear-gradient(135deg, var(--bg-start), var(--bg-end));
|
|
||||||
color: var(--text-primary);
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-page {
|
|
||||||
min-height: 100vh;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
padding: 32px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-card {
|
|
||||||
width: min(960px, 100%);
|
|
||||||
background: var(--card-bg);
|
|
||||||
border: 1px solid var(--border);
|
|
||||||
border-radius: 24px;
|
|
||||||
box-shadow: var(--shadow);
|
|
||||||
backdrop-filter: blur(10px);
|
|
||||||
display: flex;
|
|
||||||
gap: 48px;
|
|
||||||
align-items: center;
|
|
||||||
padding: 56px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-content {
|
|
||||||
flex: 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-code {
|
|
||||||
margin: 0;
|
|
||||||
font-size: clamp(64px, 12vw, 110px);
|
|
||||||
line-height: 1;
|
|
||||||
color: var(--accent);
|
|
||||||
letter-spacing: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-title {
|
|
||||||
margin: 20px 0 12px;
|
|
||||||
font-size: 30px;
|
|
||||||
line-height: 1.3;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-message {
|
|
||||||
margin: 0;
|
|
||||||
color: var(--text-secondary);
|
|
||||||
font-size: 16px;
|
|
||||||
line-height: 1.8;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-actions {
|
|
||||||
margin-top: 32px;
|
|
||||||
display: flex;
|
|
||||||
flex-wrap: wrap;
|
|
||||||
gap: 14px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-button {
|
|
||||||
appearance: none;
|
|
||||||
border: none;
|
|
||||||
border-radius: 999px;
|
|
||||||
padding: 12px 22px;
|
|
||||||
font-size: 15px;
|
|
||||||
cursor: pointer;
|
|
||||||
text-decoration: none;
|
|
||||||
transition: transform 0.2s ease, box-shadow 0.2s ease, background-color 0.2s ease;
|
|
||||||
display: inline-flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-button:hover {
|
|
||||||
transform: translateY(-1px);
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-button.primary {
|
|
||||||
background: var(--accent);
|
|
||||||
color: #fff;
|
|
||||||
box-shadow: 0 12px 24px rgba(64, 158, 255, 0.24);
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-button.primary:hover {
|
|
||||||
background: var(--accent-hover);
|
|
||||||
}
|
|
||||||
|
|
||||||
.action-button.secondary {
|
|
||||||
background: #fff;
|
|
||||||
color: var(--text-primary);
|
|
||||||
border: 1px solid rgba(31, 45, 61, 0.12);
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-illustration {
|
|
||||||
flex: 0 0 320px;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
.illustration-shell {
|
|
||||||
width: 100%;
|
|
||||||
aspect-ratio: 1 / 1;
|
|
||||||
border-radius: 32px;
|
|
||||||
background: linear-gradient(160deg, rgba(64, 158, 255, 0.12), rgba(64, 158, 255, 0.02));
|
|
||||||
border: 1px solid rgba(64, 158, 255, 0.14);
|
|
||||||
position: relative;
|
|
||||||
overflow: hidden;
|
|
||||||
}
|
|
||||||
|
|
||||||
.illustration-shell::before,
|
|
||||||
.illustration-shell::after {
|
|
||||||
content: '';
|
|
||||||
position: absolute;
|
|
||||||
border-radius: 50%;
|
|
||||||
background: rgba(64, 158, 255, 0.12);
|
|
||||||
}
|
|
||||||
|
|
||||||
.illustration-shell::before {
|
|
||||||
width: 180px;
|
|
||||||
height: 180px;
|
|
||||||
top: -30px;
|
|
||||||
right: -30px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.illustration-shell::after {
|
|
||||||
width: 120px;
|
|
||||||
height: 120px;
|
|
||||||
left: -20px;
|
|
||||||
bottom: -20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.illustration-center {
|
|
||||||
position: absolute;
|
|
||||||
inset: 0;
|
|
||||||
display: flex;
|
|
||||||
align-items: center;
|
|
||||||
justify-content: center;
|
|
||||||
font-size: clamp(72px, 10vw, 120px);
|
|
||||||
font-weight: 800;
|
|
||||||
color: rgba(64, 158, 255, 0.85);
|
|
||||||
letter-spacing: 6px;
|
|
||||||
}
|
|
||||||
|
|
||||||
@media (max-width: 900px) {
|
|
||||||
.error-card {
|
|
||||||
flex-direction: column;
|
|
||||||
padding: 36px 24px;
|
|
||||||
gap: 28px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-illustration {
|
|
||||||
flex-basis: auto;
|
|
||||||
width: min(320px, 100%);
|
|
||||||
}
|
|
||||||
|
|
||||||
.error-title {
|
|
||||||
font-size: 24px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
</head>
|
|
||||||
<body>
|
|
||||||
<main class="error-page">
|
|
||||||
<section class="error-card">
|
|
||||||
<div class="error-content">
|
|
||||||
<h1 class="error-code">404</h1>
|
|
||||||
<h2 class="error-title">抱歉,你访问的页面不存在</h2>
|
|
||||||
<p class="error-message">
|
|
||||||
当前地址可能已失效、输入有误,或者对应内容已被移动。你可以返回首页,或回到后台入口继续操作。
|
|
||||||
</p>
|
|
||||||
<div class="error-actions">
|
|
||||||
<a class="action-button primary" href="/">返回首页</a>
|
|
||||||
<a class="action-button secondary" href="/sys/">进入后台</a>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="error-illustration" aria-hidden="true">
|
|
||||||
<div class="illustration-shell">
|
|
||||||
<div class="illustration-center">404</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</section>
|
|
||||||
</main>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
@@ -37,8 +37,6 @@ export interface NodeLibraryItem {
|
|||||||
nodeName: string;
|
nodeName: string;
|
||||||
modelType: number;
|
modelType: number;
|
||||||
skillOption: boolean;
|
skillOption: boolean;
|
||||||
promptOption: boolean;
|
|
||||||
isSaveFile: boolean;
|
|
||||||
formConfig: NodeLibraryFormItem[];
|
formConfig: NodeLibraryFormItem[];
|
||||||
modelConfig: NodeLibraryModelConfig[];
|
modelConfig: NodeLibraryModelConfig[];
|
||||||
}
|
}
|
||||||
@@ -88,7 +86,6 @@ export interface ExecutionItem {
|
|||||||
timestamp: string;
|
timestamp: string;
|
||||||
content: string;
|
content: string;
|
||||||
label: string;
|
label: string;
|
||||||
type?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ExecutionFlowItem {
|
export interface ExecutionFlowItem {
|
||||||
|
|||||||
@@ -119,22 +119,18 @@ export interface ModelModuleItem {
|
|||||||
/** 会话开关状态(列表接口返回,0 关 1 开;会话开关接口就绪后生效) */
|
/** 会话开关状态(列表接口返回,0 关 1 开;会话开关接口就绪后生效) */
|
||||||
chatSessionEnabled?: number;
|
chatSessionEnabled?: number;
|
||||||
enabled: number;
|
enabled: number;
|
||||||
/** 调用模式:0-同步 1-异步 2-流式 */
|
|
||||||
callMode?: number;
|
|
||||||
maxConcurrency: number;
|
maxConcurrency: number;
|
||||||
queueLimit?: number;
|
queueLimit: number;
|
||||||
timeoutMs?: number;
|
timeoutMs?: number;
|
||||||
timeoutSeconds?: number;
|
timeoutSeconds?: number;
|
||||||
expectedSeconds?: number;
|
expectedSeconds?: number;
|
||||||
retryTimes: number;
|
retryTimes: number;
|
||||||
retryQueueMaxSeconds?: number;
|
retryQueueMaxSeconds: number;
|
||||||
autoCleanSeconds: number;
|
autoCleanSeconds: number;
|
||||||
headMsg?: string | Record<string, string>;
|
remark?: string;
|
||||||
|
headMsg?: string;
|
||||||
form?: ModelFormEntry[] | Record<string, { value: string }>;
|
form?: ModelFormEntry[] | Record<string, { value: string }>;
|
||||||
requestMapping?: Record<string, unknown>;
|
requestMapping?: Record<string, unknown>;
|
||||||
requiredFields?: string[];
|
|
||||||
firstFrame?: string;
|
|
||||||
lastFrame?: string;
|
|
||||||
responseMapping?: Record<string, unknown>;
|
responseMapping?: Record<string, unknown>;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,32 +150,27 @@ export interface CreateModelParams {
|
|||||||
operatorName?: string;
|
operatorName?: string;
|
||||||
baseUrl: string;
|
baseUrl: string;
|
||||||
httpMethod?: string;
|
httpMethod?: string;
|
||||||
headMsg?: Record<string, string>;
|
headMsg?: string;
|
||||||
isPrivate: number;
|
isPrivate: number;
|
||||||
enabled: number;
|
enabled: number;
|
||||||
isChatModel: number;
|
isChatModel: number;
|
||||||
/** 调用模式:0-同步 1-异步 2-流式,默认0 */
|
|
||||||
callMode: number;
|
|
||||||
apiKey?: string;
|
apiKey?: string;
|
||||||
form: ModelFormEntry[];
|
form: ModelFormEntry[];
|
||||||
requestMapping?: Record<string, unknown>;
|
requestMapping?: Record<string, unknown>;
|
||||||
requiredFields?: string[];
|
|
||||||
firstFrame?: string;
|
|
||||||
lastFrame?: string;
|
|
||||||
responseMapping?: Record<string, unknown>;
|
responseMapping?: Record<string, unknown>;
|
||||||
responseBody?: string;
|
responseBody?: Record<string, unknown>;
|
||||||
extendMapping?: Record<string, unknown>;
|
extendMapping?: Record<string, unknown>;
|
||||||
responseTokenField?: string;
|
responseTokenField?: string;
|
||||||
tokenConfig?: Record<string, unknown>;
|
tokenConfig?: Record<string, unknown>;
|
||||||
queryConfig?: Record<string, unknown>;
|
queryConfig?: Record<string, unknown>;
|
||||||
streamConfig?: Record<string, unknown>;
|
|
||||||
maxConcurrency?: number;
|
maxConcurrency?: number;
|
||||||
queueLimit?: number;
|
queueLimit?: number;
|
||||||
timeoutSeconds: number;
|
timeoutSeconds: number;
|
||||||
expectedSeconds?: number;
|
expectedSeconds: number;
|
||||||
retryTimes?: number;
|
retryTimes?: number;
|
||||||
retryQueueMaxSeconds?: number;
|
retryQueueMaxSeconds: number;
|
||||||
autoCleanSeconds: number;
|
autoCleanSeconds: number;
|
||||||
|
remark?: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface ModelConfigTypeItem {
|
export interface ModelConfigTypeItem {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ export interface PromptItem {
|
|||||||
deletedAt?: string | null;
|
deletedAt?: string | null;
|
||||||
nodeType: string;
|
nodeType: string;
|
||||||
prompt: string;
|
prompt: string;
|
||||||
sourceType: number; // 1-管理员 2-用户
|
sourceType: number; // 0-自定义 1-公共
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface PromptListResponse {
|
export interface PromptListResponse {
|
||||||
@@ -23,7 +23,6 @@ export interface PromptListParams {
|
|||||||
pageNum?: number;
|
pageNum?: number;
|
||||||
pageSize?: number;
|
pageSize?: number;
|
||||||
keyword?: string;
|
keyword?: string;
|
||||||
nodeType?: string;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CreatePromptParams {
|
export interface CreatePromptParams {
|
||||||
@@ -32,23 +31,6 @@ export interface CreatePromptParams {
|
|||||||
sourceType: number;
|
sourceType: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface CheckIsSuperAdminResponse {
|
|
||||||
isSuperAdmin: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface CreatePromptResponse {
|
|
||||||
id?: number | string;
|
|
||||||
tenantId?: number;
|
|
||||||
creator?: string;
|
|
||||||
createdAt?: string;
|
|
||||||
updater?: string;
|
|
||||||
updatedAt?: string;
|
|
||||||
deletedAt?: string | null;
|
|
||||||
nodeType?: string;
|
|
||||||
prompt?: string;
|
|
||||||
sourceType?: number;
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface UpdatePromptParams extends CreatePromptParams {
|
export interface UpdatePromptParams extends CreatePromptParams {
|
||||||
id: number | string;
|
id: number | string;
|
||||||
}
|
}
|
||||||
@@ -83,17 +65,6 @@ export function getNodeLibraryList() {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 获取提示词列表(根据节点类型)
|
|
||||||
*/
|
|
||||||
export function getPromptList(params: PromptListParams) {
|
|
||||||
return request<PromptListResponse>({
|
|
||||||
url: '/ai-agent/node/prompt/list',
|
|
||||||
method: 'get',
|
|
||||||
params,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 获取当前用户提示词列表
|
* 获取当前用户提示词列表
|
||||||
*/
|
*/
|
||||||
@@ -109,7 +80,7 @@ export function getMyPromptList(params: PromptListParams) {
|
|||||||
* 创建提示词
|
* 创建提示词
|
||||||
*/
|
*/
|
||||||
export function createPrompt(data: CreatePromptParams) {
|
export function createPrompt(data: CreatePromptParams) {
|
||||||
return request<CreatePromptResponse>({
|
return request({
|
||||||
url: '/ai-agent/node/prompt/create',
|
url: '/ai-agent/node/prompt/create',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
data,
|
data,
|
||||||
@@ -137,13 +108,3 @@ export function deletePrompt(id: number | string) {
|
|||||||
data: { id },
|
data: { id },
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 判断当前用户是否为超级管理员
|
|
||||||
*/
|
|
||||||
export function checkIsSuperAdmin() {
|
|
||||||
return request<CheckIsSuperAdminResponse>({
|
|
||||||
url: '/admin-go/api/v1/system/user/checkIsSuperAdmin',
|
|
||||||
method: 'get',
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -118,22 +118,15 @@ interface ModelItem {
|
|||||||
apiKey?: string;
|
apiKey?: string;
|
||||||
isPrivate?: number;
|
isPrivate?: number;
|
||||||
isChatModel?: number;
|
isChatModel?: number;
|
||||||
headMsg?: string | Record<string, string>;
|
headMsg?: string;
|
||||||
operatorName?: string;
|
operatorName?: string;
|
||||||
responseBody?: Record<string, unknown>;
|
responseBody?: Record<string, unknown>;
|
||||||
responseTokenField?: string;
|
|
||||||
tokenConfig?: Record<string, unknown> | string;
|
tokenConfig?: Record<string, unknown> | string;
|
||||||
extendMapping?: Record<string, unknown> | string;
|
extendMapping?: Record<string, unknown> | string;
|
||||||
queryConfig?: Record<string, unknown>;
|
queryConfig?: Record<string, unknown>;
|
||||||
streamConfig?: Record<string, unknown>;
|
|
||||||
form?: ModelFormEntry[] | Record<string, unknown>;
|
form?: ModelFormEntry[] | Record<string, unknown>;
|
||||||
requestMapping?: Record<string, unknown>;
|
requestMapping?: Record<string, unknown>;
|
||||||
requiredFields?: string[];
|
|
||||||
firstFrame?: string;
|
|
||||||
lastFrame?: string;
|
|
||||||
responseMapping?: Record<string, unknown>;
|
responseMapping?: Record<string, unknown>;
|
||||||
callMode?: number;
|
|
||||||
isAsync?: number;
|
|
||||||
maxConcurrency?: number;
|
maxConcurrency?: number;
|
||||||
queueLimit?: number;
|
queueLimit?: number;
|
||||||
timeoutSeconds?: number;
|
timeoutSeconds?: number;
|
||||||
@@ -278,71 +271,74 @@ const fieldsToUnknownObject = (fields: Array<{ key: string; value: string }>) =>
|
|||||||
return obj;
|
return obj;
|
||||||
};
|
};
|
||||||
|
|
||||||
const parseFormEntries = (raw: unknown): ModelFormEntry[] => {
|
const flattenNestedObject = (obj: Record<string, unknown>, prefix = ''): Array<{ key: string; value: string }> => {
|
||||||
if (Array.isArray(raw)) {
|
const rows: Array<{ key: string; value: string }> = [];
|
||||||
return (raw as ModelFormEntry[])
|
Object.entries(obj || {}).forEach(([k, v]) => {
|
||||||
.filter((item) => item && item.key !== undefined)
|
const fk = prefix ? `${prefix}.${k}` : k;
|
||||||
.map((item) => ({
|
if (v && typeof v === 'object' && !Array.isArray(v)) {
|
||||||
key: String(item.key ?? '').trim(),
|
rows.push(...flattenNestedObject(v as Record<string, unknown>, fk));
|
||||||
value: String(item.value ?? ''),
|
return;
|
||||||
}))
|
|
||||||
.filter((item) => item.key !== '');
|
|
||||||
}
|
|
||||||
|
|
||||||
if (raw && typeof raw === 'object') {
|
|
||||||
return Object.entries(raw as Record<string, unknown>).map(([key, value]) => {
|
|
||||||
let nextValue = value;
|
|
||||||
if (nextValue && typeof nextValue === 'object' && !Array.isArray(nextValue) && 'value' in nextValue) {
|
|
||||||
nextValue = (nextValue as { value: unknown }).value;
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
key: String(key).trim(),
|
|
||||||
value: String(nextValue ?? ''),
|
|
||||||
};
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return [];
|
|
||||||
};
|
|
||||||
|
|
||||||
const normalizeQueryConfig = (raw: unknown): Record<string, unknown> => {
|
|
||||||
if (raw && typeof raw === 'object' && !Array.isArray(raw)) {
|
|
||||||
return raw as Record<string, unknown>;
|
|
||||||
}
|
|
||||||
return {};
|
|
||||||
};
|
|
||||||
|
|
||||||
const parseHeadMsgRecord = (raw: ModelItem['headMsg']) => {
|
|
||||||
const headMsgRecord: Record<string, string> = {};
|
|
||||||
if (typeof raw === 'string') {
|
|
||||||
try {
|
|
||||||
const parsed = JSON.parse(raw);
|
|
||||||
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
||||||
Object.entries(parsed).forEach(([k, v]) => {
|
|
||||||
headMsgRecord[k] = String(v);
|
|
||||||
});
|
|
||||||
return headMsgRecord;
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
const pairs = raw.split(',');
|
|
||||||
pairs.forEach((pair) => {
|
|
||||||
const idx = pair.indexOf(':');
|
|
||||||
if (idx === -1) return;
|
|
||||||
const key = pair.slice(0, idx).trim();
|
|
||||||
const value = pair.slice(idx + 1).trim();
|
|
||||||
if (key) {
|
|
||||||
headMsgRecord[key] = value;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return headMsgRecord;
|
|
||||||
}
|
}
|
||||||
}
|
rows.push({ key: fk, value: String(v ?? '') });
|
||||||
if (raw && typeof raw === 'object' && !Array.isArray(raw)) {
|
});
|
||||||
Object.entries(raw).forEach(([k, v]) => {
|
return rows;
|
||||||
headMsgRecord[k] = String(v);
|
};
|
||||||
|
|
||||||
|
const nestFieldsToObject = (fields: Array<{ key: string; value: string }>) => {
|
||||||
|
const root: Record<string, unknown> = {};
|
||||||
|
fields.forEach((f) => {
|
||||||
|
const path = String(f.key || '').trim();
|
||||||
|
if (!path) return;
|
||||||
|
const parts = path
|
||||||
|
.split('.')
|
||||||
|
.map((p) => p.trim())
|
||||||
|
.filter(Boolean);
|
||||||
|
if (!parts.length) return;
|
||||||
|
let cur: Record<string, unknown> = root;
|
||||||
|
parts.forEach((part, idx) => {
|
||||||
|
if (idx === parts.length - 1) {
|
||||||
|
cur[part] = String(f.value ?? '');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!cur[part] || typeof cur[part] !== 'object' || Array.isArray(cur[part])) {
|
||||||
|
cur[part] = {};
|
||||||
|
}
|
||||||
|
cur = cur[part] as Record<string, unknown>;
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
return root;
|
||||||
|
};
|
||||||
|
|
||||||
|
const buildQueryConfigFromRaw = (rawQc: Record<string, unknown> | null): Record<string, unknown> => {
|
||||||
|
if (!rawQc) return { responseType: 'sync', callbackUrl: '' };
|
||||||
|
const rt = String(rawQc.responseType || 'sync');
|
||||||
|
if (rt === 'callback') return { responseType: 'callback', callbackUrl: String(rawQc.callbackUrl || '') };
|
||||||
|
if (rt === 'pull') {
|
||||||
|
const hFields = Object.entries((rawQc.headers as Record<string, unknown>) || {}).map(([k, v]) => ({ key: k, value: String(v ?? '') }));
|
||||||
|
const bFields = flattenNestedObject((rawQc.body as Record<string, unknown>) || {});
|
||||||
|
return {
|
||||||
|
responseType: 'pull',
|
||||||
|
callbackUrl: '',
|
||||||
|
method: String(rawQc.method || 'GET'),
|
||||||
|
url: String(rawQc.url || ''),
|
||||||
|
headers: fieldsToUnknownObject(hFields),
|
||||||
|
body: nestFieldsToObject(bFields),
|
||||||
|
response: ((rawQc.response as unknown[]) || [])
|
||||||
|
.map((item) => {
|
||||||
|
if (typeof item === 'string') {
|
||||||
|
return { value: item, isTokenField: false, isMainBody: false };
|
||||||
|
}
|
||||||
|
const row = item as Record<string, unknown>;
|
||||||
|
return {
|
||||||
|
value: String(row.value ?? ''),
|
||||||
|
isTokenField: Boolean(row.isTokenField),
|
||||||
|
isMainBody: Boolean(row.isMainBody),
|
||||||
|
};
|
||||||
|
})
|
||||||
|
.filter((row) => row.value !== ''),
|
||||||
|
};
|
||||||
}
|
}
|
||||||
return headMsgRecord;
|
return { responseType: 'sync', callbackUrl: '' };
|
||||||
};
|
};
|
||||||
|
|
||||||
const fetchModelList = async () => {
|
const fetchModelList = async () => {
|
||||||
@@ -400,25 +396,25 @@ const handleCreatePrivateModel = async () => {
|
|||||||
creatingModel.value = true;
|
creatingModel.value = true;
|
||||||
|
|
||||||
const builtInModel = builtInModelToClone.value;
|
const builtInModel = builtInModelToClone.value;
|
||||||
const formList = parseFormEntries(builtInModel.form);
|
const formList: ModelFormEntry[] = Array.isArray(builtInModel.form)
|
||||||
|
? (builtInModel.form as ModelFormEntry[])
|
||||||
|
: Object.entries((builtInModel.form as Record<string, unknown>) || {}).map(([key, value]) => ({
|
||||||
|
key: String(key),
|
||||||
|
value: String(value ?? ''),
|
||||||
|
}));
|
||||||
const createParams: CreateModelParams = {
|
const createParams: CreateModelParams = {
|
||||||
modelName: apiKeyForm.modelName,
|
modelName: apiKeyForm.modelName,
|
||||||
modelType: builtInModel.modelType,
|
modelType: builtInModel.modelType,
|
||||||
operatorName: builtInModel.operatorName || '',
|
operatorName: builtInModel.operatorName || '',
|
||||||
baseUrl: builtInModel.baseUrl,
|
baseUrl: builtInModel.baseUrl,
|
||||||
httpMethod: builtInModel.httpMethod || 'POST',
|
httpMethod: builtInModel.httpMethod || 'POST',
|
||||||
headMsg: parseHeadMsgRecord(builtInModel.headMsg),
|
headMsg: builtInModel.headMsg || '',
|
||||||
isPrivate: builtInModel.isPrivate ?? 1,
|
isPrivate: builtInModel.isPrivate ?? 1,
|
||||||
enabled: builtInModel.enabled ?? 1,
|
enabled: builtInModel.enabled ?? 1,
|
||||||
isChatModel: builtInModel.isChatModel || 0,
|
isChatModel: builtInModel.isChatModel || 0,
|
||||||
callMode: builtInModel.callMode ?? builtInModel.isAsync ?? 0,
|
|
||||||
apiKey: apiKeyForm.apiKey,
|
apiKey: apiKeyForm.apiKey,
|
||||||
form: formList,
|
form: formList,
|
||||||
requestMapping: (builtInModel.requestMapping as Record<string, unknown>) || {},
|
requestMapping: (builtInModel.requestMapping as Record<string, unknown>) || {},
|
||||||
requiredFields: Array.isArray(builtInModel.requiredFields) ? builtInModel.requiredFields : [],
|
|
||||||
firstFrame: String(builtInModel.firstFrame || ''),
|
|
||||||
lastFrame: String(builtInModel.lastFrame || ''),
|
|
||||||
responseMapping: (builtInModel.responseMapping as Record<string, unknown>) || {},
|
responseMapping: (builtInModel.responseMapping as Record<string, unknown>) || {},
|
||||||
responseBody: builtInModel.responseBody || {},
|
responseBody: builtInModel.responseBody || {},
|
||||||
maxConcurrency: builtInModel.maxConcurrency || 10,
|
maxConcurrency: builtInModel.maxConcurrency || 10,
|
||||||
@@ -428,20 +424,19 @@ const handleCreatePrivateModel = async () => {
|
|||||||
retryTimes: builtInModel.retryTimes || 3,
|
retryTimes: builtInModel.retryTimes || 3,
|
||||||
retryQueueMaxSeconds: builtInModel.retryQueueMaxSeconds || 60,
|
retryQueueMaxSeconds: builtInModel.retryQueueMaxSeconds || 60,
|
||||||
autoCleanSeconds: builtInModel.autoCleanSeconds || 300,
|
autoCleanSeconds: builtInModel.autoCleanSeconds || 300,
|
||||||
|
remark: builtInModel.remark || '',
|
||||||
|
|
||||||
extendMapping: fieldsToUnknownObject(
|
extendMapping: fieldsToUnknownObject(
|
||||||
Object.entries(parseJsonObjectField(builtInModel.extendMapping)).map(([k, v]) => ({ key: k, value: String(v ?? '') }))
|
Object.entries(parseJsonObjectField(builtInModel.extendMapping)).map(([k, v]) => ({ key: k, value: String(v ?? '') }))
|
||||||
),
|
),
|
||||||
tokenConfig: fieldsToUnknownObject(
|
tokenConfig: fieldsToUnknownObject(
|
||||||
Object.entries(parseJsonObjectField(builtInModel.tokenConfig)).map(([k, v]) => ({ key: k, value: String(v ?? '') }))
|
Object.entries(parseJsonObjectField(builtInModel.tokenConfig)).map(([k, v]) => ({ key: k, value: String(v ?? '') }))
|
||||||
),
|
),
|
||||||
queryConfig: normalizeQueryConfig(builtInModel.queryConfig),
|
queryConfig: buildQueryConfigFromRaw(
|
||||||
responseTokenField: String(builtInModel.responseTokenField || ''),
|
builtInModel.queryConfig && typeof builtInModel.queryConfig === 'object' && !Array.isArray(builtInModel.queryConfig)
|
||||||
streamConfig:
|
? (builtInModel.queryConfig as Record<string, unknown>)
|
||||||
Number(builtInModel.callMode ?? builtInModel.isAsync ?? 0) === 2
|
: null
|
||||||
? builtInModel.streamConfig && typeof builtInModel.streamConfig === 'object' && !Array.isArray(builtInModel.streamConfig)
|
),
|
||||||
? (builtInModel.streamConfig as Record<string, unknown>)
|
|
||||||
: {}
|
|
||||||
: undefined,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const res: any = await addModelModule(createParams);
|
const res: any = await addModelModule(createParams);
|
||||||
|
|||||||
@@ -34,23 +34,6 @@ export const router = createRouter({
|
|||||||
history: createWebHashHistory(),
|
history: createWebHashHistory(),
|
||||||
routes: staticRoutes,
|
routes: staticRoutes,
|
||||||
});
|
});
|
||||||
const hasValidResolvedRoute = (to: any) => {
|
|
||||||
const resolved = router.resolve(to.fullPath);
|
|
||||||
const matched = resolved.matched || [];
|
|
||||||
|
|
||||||
const isStaticFallbackRoute = matched.length === 1 && matched[0]?.path === '/:pathMatch(.*)*';
|
|
||||||
|
|
||||||
const isRedirectTo404Route = matched.length > 0 && matched.every((item) => item.redirect === '/404');
|
|
||||||
|
|
||||||
return matched.length > 0 && !isStaticFallbackRoute && !isRedirectTo404Route;
|
|
||||||
};
|
|
||||||
const goResolvedOr404AfterInit = (to: any, next: any) => {
|
|
||||||
if (hasValidResolvedRoute(to)) {
|
|
||||||
next({ ...to, replace: true });
|
|
||||||
} else {
|
|
||||||
next('/404');
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 路由多级嵌套数组处理成一维数组
|
* 路由多级嵌套数组处理成一维数组
|
||||||
@@ -123,18 +106,18 @@ router.beforeEach(async (to, from, next) => {
|
|||||||
const { routesList } = storeToRefs(storesRoutesList);
|
const { routesList } = storeToRefs(storesRoutesList);
|
||||||
if (routesList.value.length === 0) {
|
if (routesList.value.length === 0) {
|
||||||
if (isRequestRoutes) {
|
if (isRequestRoutes) {
|
||||||
|
// 后端控制路由:路由数据初始化,防止刷新时丢失
|
||||||
await initBackEndControlRoutes();
|
await initBackEndControlRoutes();
|
||||||
goResolvedOr404AfterInit(to, next);
|
// 动态添加路由:防止非首页刷新时跳转回首页的问题
|
||||||
|
// 确保 addRoute() 时动态添加的路由已经被完全加载上去
|
||||||
|
next({ ...to, replace: true });
|
||||||
} else {
|
} else {
|
||||||
|
// https://gitee.com/lyt-top/vue-next-admin/issues/I5F1HP
|
||||||
await initFrontEndControlRoutes();
|
await initFrontEndControlRoutes();
|
||||||
goResolvedOr404AfterInit(to, next);
|
next({ ...to, replace: true });
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
if (hasValidResolvedRoute(to)) {
|
next();
|
||||||
next();
|
|
||||||
} else {
|
|
||||||
next('/404');
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { RouteRecordRaw } from 'vue-router';
|
import { RouteRecordRaw } from 'vue-router';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 路由meta对象参数说明
|
* 路由meta对象参数说明
|
||||||
@@ -69,9 +69,9 @@ export const dynamicRoutes: Array<RouteRecordRaw> = [
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* 盘点管理路由配置(供后端菜单配置参考)
|
* 盘点管理路由配置(供后端菜单配置参考)
|
||||||
*
|
*
|
||||||
* 父级菜单: 库存作业 (/assets/operation)
|
* 父级菜单: 库存作业 (/assets/operation)
|
||||||
*
|
*
|
||||||
* 盘点菜单配置:
|
* 盘点菜单配置:
|
||||||
* - 路由路径: /assets/operation/count
|
* - 路由路径: /assets/operation/count
|
||||||
* - 组件路径: assets/operation/count/index
|
* - 组件路径: assets/operation/count/index
|
||||||
@@ -1077,8 +1077,8 @@ export const demoRoutes: Array<RouteRecordRaw> = [
|
|||||||
*/
|
*/
|
||||||
export const notFoundAndNoPower = [
|
export const notFoundAndNoPower = [
|
||||||
{
|
{
|
||||||
path: '/404',
|
path: '/:path(.*)*',
|
||||||
name: 'notFoundPage',
|
name: 'notFound',
|
||||||
component: () => import('/@/views/error/404.vue'),
|
component: () => import('/@/views/error/404.vue'),
|
||||||
meta: {
|
meta: {
|
||||||
title: 'message.staticRoutes.notFound',
|
title: 'message.staticRoutes.notFound',
|
||||||
@@ -1094,15 +1094,6 @@ export const notFoundAndNoPower = [
|
|||||||
isHide: true,
|
isHide: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: '/:path(.*)*',
|
|
||||||
name: 'notFound',
|
|
||||||
redirect: '/404',
|
|
||||||
meta: {
|
|
||||||
title: 'message.staticRoutes.notFound',
|
|
||||||
isHide: true,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,68 +0,0 @@
|
|||||||
<template>
|
|
||||||
<el-dialog v-model="visible" title="编辑提示词" width="720px" :close-on-click-modal="false" destroy-on-close @close="handleClose">
|
|
||||||
<div class="prompt-input-dialog">
|
|
||||||
<el-input v-model="promptContent" type="textarea" :rows="12" maxlength="5000" show-word-limit placeholder="请输入提示词内容" />
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<template #footer>
|
|
||||||
<el-button @click="handleClose">取消</el-button>
|
|
||||||
<el-button type="primary" @click="handleConfirm">确定</el-button>
|
|
||||||
</template>
|
|
||||||
</el-dialog>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { ref, watch } from 'vue';
|
|
||||||
|
|
||||||
interface Props {
|
|
||||||
modelValue: boolean;
|
|
||||||
defaultPrompt?: string;
|
|
||||||
}
|
|
||||||
|
|
||||||
interface Emits {
|
|
||||||
(e: 'update:modelValue', value: boolean): void;
|
|
||||||
(e: 'confirm', promptContent: string): void;
|
|
||||||
}
|
|
||||||
|
|
||||||
const props = withDefaults(defineProps<Props>(), {
|
|
||||||
modelValue: false,
|
|
||||||
defaultPrompt: '',
|
|
||||||
});
|
|
||||||
|
|
||||||
const emit = defineEmits<Emits>();
|
|
||||||
|
|
||||||
const visible = ref(false);
|
|
||||||
const promptContent = ref('');
|
|
||||||
|
|
||||||
watch(
|
|
||||||
() => props.modelValue,
|
|
||||||
(val) => {
|
|
||||||
visible.value = val;
|
|
||||||
if (val) {
|
|
||||||
promptContent.value = props.defaultPrompt || '';
|
|
||||||
}
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
watch(visible, (val) => {
|
|
||||||
if (!val) {
|
|
||||||
emit('update:modelValue', false);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
const handleConfirm = () => {
|
|
||||||
emit('confirm', promptContent.value.trim());
|
|
||||||
handleClose();
|
|
||||||
};
|
|
||||||
|
|
||||||
const handleClose = () => {
|
|
||||||
visible.value = false;
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<style scoped lang="scss">
|
|
||||||
.prompt-input-dialog {
|
|
||||||
display: flex;
|
|
||||||
flex-direction: column;
|
|
||||||
}
|
|
||||||
</style>
|
|
||||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
@@ -240,54 +240,18 @@ const handleCreatePrivateModelAndSetChat = async () => {
|
|||||||
|
|
||||||
// 基于内置模型创建新模型(继承原模型的所有配置,只替换 apiKey)
|
// 基于内置模型创建新模型(继承原模型的所有配置,只替换 apiKey)
|
||||||
const builtInModel = builtInModelToClone.value;
|
const builtInModel = builtInModelToClone.value;
|
||||||
|
|
||||||
// Parse headMsg to Record<string, string> - it might be stored as string or already as object
|
|
||||||
let headMsgRecord: Record<string, string> = {};
|
|
||||||
if (builtInModel.headMsg && typeof builtInModel.headMsg === 'string') {
|
|
||||||
// Try to parse as JSON first (new format stored as string)
|
|
||||||
try {
|
|
||||||
const parsed = JSON.parse(builtInModel.headMsg);
|
|
||||||
if (parsed && typeof parsed === 'object' && !Array.isArray(parsed)) {
|
|
||||||
Object.entries(parsed).forEach(([k, v]) => {
|
|
||||||
headMsgRecord[k] = String(v);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// If JSON parse fails, parse as old format "key1:value1,key2:value2"
|
|
||||||
const pairs = builtInModel.headMsg.split(',');
|
|
||||||
pairs.forEach((pair: string) => {
|
|
||||||
const idx = pair.indexOf(':');
|
|
||||||
if (idx === -1) return;
|
|
||||||
const key = pair.slice(0, idx).trim();
|
|
||||||
const value = pair.slice(idx + 1).trim();
|
|
||||||
if (key) {
|
|
||||||
headMsgRecord[key] = value;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
} else if (builtInModel.headMsg && typeof builtInModel.headMsg === 'object' && !Array.isArray(builtInModel.headMsg)) {
|
|
||||||
// Already an object
|
|
||||||
Object.entries(builtInModel.headMsg).forEach(([k, v]) => {
|
|
||||||
headMsgRecord[k] = String(v);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
const createParams = {
|
const createParams = {
|
||||||
modelName: apiKeyForm.modelName,
|
modelName: apiKeyForm.modelName,
|
||||||
modelType: builtInModel.modelType,
|
modelType: builtInModel.modelType,
|
||||||
baseUrl: builtInModel.baseUrl,
|
baseUrl: builtInModel.baseUrl,
|
||||||
httpMethod: builtInModel.httpMethod || 'POST',
|
httpMethod: builtInModel.httpMethod || 'POST',
|
||||||
headMsg: headMsgRecord,
|
headMsg: builtInModel.headMsg || '',
|
||||||
isPrivate: builtInModel.isPrivate ?? 1,
|
isPrivate: builtInModel.isPrivate ?? 1,
|
||||||
enabled: builtInModel.enabled ?? 1,
|
enabled: builtInModel.enabled ?? 1,
|
||||||
isChatModel: 1, // 设置为会话模型
|
isChatModel: 1, // 设置为会话模型
|
||||||
callMode: builtInModel.callMode ?? builtInModel.isAsync ?? 0,
|
|
||||||
apiKey: apiKeyForm.apiKey,
|
apiKey: apiKeyForm.apiKey,
|
||||||
form: builtInModel.form || {},
|
form: builtInModel.form || {},
|
||||||
requestMapping: builtInModel.requestMapping || {},
|
requestMapping: builtInModel.requestMapping || {},
|
||||||
requiredFields: Array.isArray(builtInModel.requiredFields) ? builtInModel.requiredFields : [],
|
|
||||||
firstFrame: String(builtInModel.firstFrame || ''),
|
|
||||||
lastFrame: String(builtInModel.lastFrame || ''),
|
|
||||||
responseMapping: builtInModel.responseMapping || {},
|
responseMapping: builtInModel.responseMapping || {},
|
||||||
responseBody: builtInModel.responseBody || {},
|
responseBody: builtInModel.responseBody || {},
|
||||||
tokenMapping: builtInModel.tokenMapping || '',
|
tokenMapping: builtInModel.tokenMapping || '',
|
||||||
|
|||||||
127
src/views/settings/modelConfig/modelType/index.vue
Normal file
127
src/views/settings/modelConfig/modelType/index.vue
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
<template>
|
||||||
|
<div class="system-model-type-container layout-padding">
|
||||||
|
<el-card shadow="hover" class="layout-padding-auto">
|
||||||
|
<div class="system-model-type-search mb15">
|
||||||
|
<el-input v-model="state.tableData.param.keyword" size="default" placeholder="请输入模型类型名称" style="max-width: 180px" clearable> </el-input>
|
||||||
|
<el-button size="default" type="primary" class="ml10" @click="getTableData">
|
||||||
|
<el-icon>
|
||||||
|
<ele-Search />
|
||||||
|
</el-icon>
|
||||||
|
查询
|
||||||
|
</el-button>
|
||||||
|
<el-button size="default" type="success" class="ml10" @click="onOpenAddType('add')">
|
||||||
|
<el-icon>
|
||||||
|
<ele-FolderAdd />
|
||||||
|
</el-icon>
|
||||||
|
新增模型类型
|
||||||
|
</el-button>
|
||||||
|
</div>
|
||||||
|
<el-table :data="state.tableData.data" v-loading="state.tableData.loading" style="width: 100%">
|
||||||
|
<el-table-column type="index" label="序号" width="60" />
|
||||||
|
<el-table-column prop="typeName" label="类型名称" show-overflow-tooltip></el-table-column>
|
||||||
|
<el-table-column prop="typeCode" label="类型编码" show-overflow-tooltip></el-table-column>
|
||||||
|
<el-table-column prop="description" label="描述" show-overflow-tooltip></el-table-column>
|
||||||
|
<el-table-column prop="status" label="状态" width="100">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-tag :type="scope.row.status === 1 ? 'success' : 'danger'">{{ scope.row.status === 1 ? '启用' : '禁用' }}</el-tag>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
<el-table-column prop="createTime" label="创建时间" show-overflow-tooltip></el-table-column>
|
||||||
|
<el-table-column label="操作" width="200">
|
||||||
|
<template #default="scope">
|
||||||
|
<el-button size="small" text type="primary" @click="onOpenEditType('edit', scope.row)">修改</el-button>
|
||||||
|
<el-button size="small" text type="primary" @click="onRowDel(scope.row)">删除</el-button>
|
||||||
|
</template>
|
||||||
|
</el-table-column>
|
||||||
|
</el-table>
|
||||||
|
<el-pagination
|
||||||
|
@size-change="onHandleSizeChange"
|
||||||
|
@current-change="onHandleCurrentChange"
|
||||||
|
class="mt15"
|
||||||
|
:pager-count="5"
|
||||||
|
:page-sizes="[10, 20, 30]"
|
||||||
|
v-model:current-page="state.tableData.param.pageNum"
|
||||||
|
background
|
||||||
|
v-model:page-size="state.tableData.param.pageSize"
|
||||||
|
layout="total, sizes, prev, pager, next, jumper"
|
||||||
|
:total="state.tableData.total"
|
||||||
|
>
|
||||||
|
</el-pagination>
|
||||||
|
</el-card>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts" name="digitalHumanModelType">
|
||||||
|
import { reactive, onMounted } from 'vue';
|
||||||
|
import { ElMessageBox, ElMessage } from 'element-plus';
|
||||||
|
|
||||||
|
const state = reactive({
|
||||||
|
tableData: {
|
||||||
|
data: [],
|
||||||
|
total: 0,
|
||||||
|
loading: false,
|
||||||
|
param: {
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
keyword: '',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
// 初始化表格数据
|
||||||
|
const getTableData = () => {
|
||||||
|
state.tableData.loading = true;
|
||||||
|
// TODO: 调用API获取数据
|
||||||
|
setTimeout(() => {
|
||||||
|
state.tableData.data = [];
|
||||||
|
state.tableData.total = 0;
|
||||||
|
state.tableData.loading = false;
|
||||||
|
}, 500);
|
||||||
|
};
|
||||||
|
|
||||||
|
// 打开新增模型类型弹窗
|
||||||
|
const onOpenAddType = (type: string) => {
|
||||||
|
ElMessage.info('功能开发中...');
|
||||||
|
};
|
||||||
|
|
||||||
|
// 打开修改模型类型弹窗
|
||||||
|
const onOpenEditType = (type: string, row: any) => {
|
||||||
|
ElMessage.info('功能开发中...');
|
||||||
|
};
|
||||||
|
|
||||||
|
// 删除模型类型
|
||||||
|
const onRowDel = (row: any) => {
|
||||||
|
ElMessage.info('功能开发中...');
|
||||||
|
};
|
||||||
|
|
||||||
|
// 分页改变
|
||||||
|
const onHandleSizeChange = (val: number) => {
|
||||||
|
state.tableData.param.pageSize = val;
|
||||||
|
getTableData();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 分页改变
|
||||||
|
const onHandleCurrentChange = (val: number) => {
|
||||||
|
state.tableData.param.pageNum = val;
|
||||||
|
getTableData();
|
||||||
|
};
|
||||||
|
|
||||||
|
// 页面加载时
|
||||||
|
onMounted(() => {
|
||||||
|
getTableData();
|
||||||
|
});
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
.system-model-type-container {
|
||||||
|
:deep(.el-card__body) {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
flex: 1;
|
||||||
|
overflow: auto;
|
||||||
|
.el-table {
|
||||||
|
flex: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
||||||
@@ -26,8 +26,8 @@
|
|||||||
</el-table-column>
|
</el-table-column>
|
||||||
<el-table-column prop="sourceType" label="来源" width="100">
|
<el-table-column prop="sourceType" label="来源" width="100">
|
||||||
<template #default="{ row }">
|
<template #default="{ row }">
|
||||||
<el-tag v-if="row.sourceType === 1" type="success">管理员</el-tag>
|
<el-tag v-if="row.sourceType === 0">自定义</el-tag>
|
||||||
<el-tag v-else-if="row.sourceType === 2">用户</el-tag>
|
<el-tag v-else-if="row.sourceType === 1" type="success">公共</el-tag>
|
||||||
<el-tag v-else type="info">{{ row.sourceType }}</el-tag>
|
<el-tag v-else type="info">{{ row.sourceType }}</el-tag>
|
||||||
</template>
|
</template>
|
||||||
</el-table-column>
|
</el-table-column>
|
||||||
@@ -128,7 +128,7 @@ const nodeOptions = computed(() => {
|
|||||||
const createForm = reactive({
|
const createForm = reactive({
|
||||||
nodeType: '',
|
nodeType: '',
|
||||||
prompt: '',
|
prompt: '',
|
||||||
sourceType: 2 as number,
|
sourceType: 0 as number,
|
||||||
});
|
});
|
||||||
|
|
||||||
// 加载节点库
|
// 加载节点库
|
||||||
@@ -269,7 +269,7 @@ const cancelCreate = () => {
|
|||||||
editId.value = null;
|
editId.value = null;
|
||||||
createForm.nodeType = '';
|
createForm.nodeType = '';
|
||||||
createForm.prompt = '';
|
createForm.prompt = '';
|
||||||
createForm.sourceType = 2;
|
createForm.sourceType = 0;
|
||||||
};
|
};
|
||||||
|
|
||||||
// 分页
|
// 分页
|
||||||
|
|||||||
@@ -43,6 +43,11 @@
|
|||||||
<span class="ml10 text-muted">天(0表示永不过期)</span>
|
<span class="ml10 text-muted">天(0表示永不过期)</span>
|
||||||
</el-form-item>
|
</el-form-item>
|
||||||
|
|
||||||
|
<el-form-item label="禁止重复使用次数" prop="historyLimit">
|
||||||
|
<el-input-number v-model="ruleForm.historyLimit" :min="0" :max="24" placeholder="请输入禁止重复使用次数" />
|
||||||
|
<span class="ml10 text-muted">次(0表示不限制)</span>
|
||||||
|
</el-form-item>
|
||||||
|
|
||||||
<el-form-item label="登录失败锁定次数" prop="maxRetryCount">
|
<el-form-item label="登录失败锁定次数" prop="maxRetryCount">
|
||||||
<el-input-number v-model="ruleForm.maxRetryCount" :min="0" :max="10" placeholder="请输入登录失败锁定次数" />
|
<el-input-number v-model="ruleForm.maxRetryCount" :min="0" :max="10" placeholder="请输入登录失败锁定次数" />
|
||||||
<span class="ml10 text-muted">次(0表示不锁定)</span>
|
<span class="ml10 text-muted">次(0表示不锁定)</span>
|
||||||
@@ -80,6 +85,7 @@ interface RuleFormState {
|
|||||||
requireDigit: boolean;
|
requireDigit: boolean;
|
||||||
requireSpecialChar: boolean;
|
requireSpecialChar: boolean;
|
||||||
expireDays: number;
|
expireDays: number;
|
||||||
|
historyLimit: number;
|
||||||
maxRetryCount: number;
|
maxRetryCount: number;
|
||||||
lockTimeMinutes: number;
|
lockTimeMinutes: number;
|
||||||
remark: string;
|
remark: string;
|
||||||
@@ -106,6 +112,7 @@ export default defineComponent({
|
|||||||
requireDigit: true,
|
requireDigit: true,
|
||||||
requireSpecialChar: false,
|
requireSpecialChar: false,
|
||||||
expireDays: 90,
|
expireDays: 90,
|
||||||
|
historyLimit: 5,
|
||||||
maxRetryCount: 5,
|
maxRetryCount: 5,
|
||||||
lockTimeMinutes: 30,
|
lockTimeMinutes: 30,
|
||||||
remark: '',
|
remark: '',
|
||||||
@@ -120,6 +127,9 @@ export default defineComponent({
|
|||||||
expireDays: [
|
expireDays: [
|
||||||
{ required: true, message: '请输入密码过期天数', trigger: 'blur' }
|
{ required: true, message: '请输入密码过期天数', trigger: 'blur' }
|
||||||
],
|
],
|
||||||
|
historyLimit: [
|
||||||
|
{ required: true, message: '请输入禁止重复使用次数', trigger: 'blur' }
|
||||||
|
],
|
||||||
maxRetryCount: [
|
maxRetryCount: [
|
||||||
{ required: true, message: '请输入登录失败锁定次数', trigger: 'blur' }
|
{ required: true, message: '请输入登录失败锁定次数', trigger: 'blur' }
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -1,27 +0,0 @@
|
|||||||
-----BEGIN RSA PRIVATE KEY-----
|
|
||||||
MIIEowIBAAKCAQEAqlbH8ExzyW3D87glDgjSz6IoKWmV1iZh33DIYmDFzbaGqdBZ
|
|
||||||
gWyW4TWbBn9huVDrglShjeIEjAdfK3BcHKv4/xh+XjGjzp/GyvKkggLGB2Itc3Wq
|
|
||||||
wxlyAhlm7tgU0bmQGPu/+CnIvmss7lKWx9b5L9eYU+ToU3r/GOP4juGqcUYHLyhW
|
|
||||||
ZDjy2fjO5Ju4/W0U1OcaLho1Sapn9EFXFIjJ5RbiiOF8QVo8Ez3EbV/EsQowaIvp
|
|
||||||
OoqLT1GGBRd6sz/L1TMliw6UPcGd+TTaADTh4PxSklYsXqro0vstft0kiuAElah3
|
|
||||||
Hw3XZMNiRfAqJVvhc8/pXdA6kH4FuZKfxAF8CwIDAQABAoIBAEwB8DKKMsppZ9wr
|
|
||||||
SaGTD1pmHayo+Bq4Qsj83vWDABCUh09j8GR/i23PyS6jjXwvHv+nevVAKs4/Bdjw
|
|
||||||
yLFkjXVqvQyT8ueixQZPMlfvG+U0ilpwYXOwOX61Tgh6tAOHXUTApT77ih9p3IE1
|
|
||||||
qiFfDA8skUSyKncBta2qIAHFsZPcNGB+44hv5AT1aaXtZbuFdwAoptIpNDaWVmbo
|
|
||||||
cjm6AEHOoiTfXx437ORAx+F3e2umzBVkLQZNLPlYsz0pFN8elZAde1e13kAPUaHa
|
|
||||||
q5g49Yad4UA3iIO6xo3kZz90BGMlcbe0ckY8wOg9biF4zTxVg0unbvk4DZve8iTZ
|
|
||||||
hDrrAF0CgYEA3oakLouCgo/JWERfXVTYVyTvuh8wkYmsndV4s7hENIjhEiKrgfW1
|
|
||||||
4RfnVNEjgEbsukgyMj8GpeXp9d8mC5Jz9uhKbbpCbLIuTdZpeNvob4RA1a+rgP6L
|
|
||||||
Fan3W9yrMqmrz7gLW2PsPmU3vnn8SI/8QlgwmNLutbMPncWJzNukMM0CgYEAw/Zy
|
|
||||||
dqFjNIgHemnYpK1R0xcLVs6MTOOwqEv0xUor1fZujLittRbb4JAqDlUwQNU6Zn6K
|
|
||||||
xxuSJ3TrRHqpjLcT812yPeMXd1f7jzCnZXDkWYoUT/ILjIB9cEJuycTwDwMJGkPd
|
|
||||||
RGV+gyBPHYBM6+hhxh1YSYm0CebDPMRMC7anADcCgYBg/KYKc1vKtAi5o/M8poI0
|
|
||||||
WTDzGiz9AdhuYmBdVG7FxvLyCJJf7kv7c4OsAAtNGZyMBHN8WuP24qJ6O2xFlutc
|
|
||||||
LhpigM/6uB5eZplJx+8iy63GdO53JnpQUugw7ZjgV/s+aaTpjD+nYk5NgrVmmvsD
|
|
||||||
n5Rux2CraQPha/uHgiOp8QKBgQCwqh6ZOnfgZP4w6vmnxKUu4+iSEtXDf8XEg+2g
|
|
||||||
a3THiUoyzU+1IW2Krw1rLHu7B0CGJHDifcWukqaDbv4p4Oc4kPDfADsomYYtmhRf
|
|
||||||
zH3hBy22wSSOP0xXB10vw78UGY18Q1BYmBONDXSlyQQP7ckvvUbZzS4lewfqYaGZ
|
|
||||||
Mcqu0wKBgDh2tcdxuHUizlRAPwfoZ3JhbITJqGytkpZT96WpQ0fpzcSovNJf0shC
|
|
||||||
5gXCzWWRKQPxV7cUXmB6hBm1Q6WoZ/VKN36+R+7L8Pt7yTmZhBxQKVjVn3k849JC
|
|
||||||
tcWDXiZd5qINC7dsH/vjLahnr76cFtYfNM5stomJkKNRjC7hZfnw
|
|
||||||
-----END RSA PRIVATE KEY-----
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
-----BEGIN CERTIFICATE-----
|
|
||||||
MIIGJTCCBQ2gAwIBAgIQCXpUlFT9Cue3+TsPpxvnWjANBgkqhkiG9w0BAQsFADBu
|
|
||||||
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
|
|
||||||
d3cuZGlnaWNlcnQuY29tMS0wKwYDVQQDEyRFbmNyeXB0aW9uIEV2ZXJ5d2hlcmUg
|
|
||||||
RFYgVExTIENBIC0gRzIwHhcNMjYwNjEwMDAwMDAwWhcNMjYwOTA3MjM1OTU5WjAj
|
|
||||||
MSEwHwYDVQQDExhnaXRlYS5yZWRwb3dlcmZ1dHVyZS5jb20wggEiMA0GCSqGSIb3
|
|
||||||
DQEBAQUAA4IBDwAwggEKAoIBAQCqVsfwTHPJbcPzuCUOCNLPoigpaZXWJmHfcMhi
|
|
||||||
YMXNtoap0FmBbJbhNZsGf2G5UOuCVKGN4gSMB18rcFwcq/j/GH5eMaPOn8bK8qSC
|
|
||||||
AsYHYi1zdarDGXICGWbu2BTRuZAY+7/4Kci+ayzuUpbH1vkv15hT5OhTev8Y4/iO
|
|
||||||
4apxRgcvKFZkOPLZ+M7km7j9bRTU5xouGjVJqmf0QVcUiMnlFuKI4XxBWjwTPcRt
|
|
||||||
X8SxCjBoi+k6iotPUYYFF3qzP8vVMyWLDpQ9wZ35NNoANOHg/FKSVixequjS+y1+
|
|
||||||
3SSK4ASVqHcfDddkw2JF8ColW+Fzz+ld0DqQfgW5kp/EAXwLAgMBAAGjggMIMIID
|
|
||||||
BDAfBgNVHSMEGDAWgBR435GQX+7erPbFdevVTFVT7yRKtjAdBgNVHQ4EFgQUOiq0
|
|
||||||
OCRofHA6BrxoM9do+AWnNgowQQYDVR0RBDowOIIYZ2l0ZWEucmVkcG93ZXJmdXR1
|
|
||||||
cmUuY29tghx3d3cuZ2l0ZWEucmVkcG93ZXJmdXR1cmUuY29tMD4GA1UdIAQ3MDUw
|
|
||||||
MwYGZ4EMAQIBMCkwJwYIKwYBBQUHAgEWG2h0dHA6Ly93d3cuZGlnaWNlcnQuY29t
|
|
||||||
L0NQUzAOBgNVHQ8BAf8EBAMCBaAwHQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUF
|
|
||||||
BwMCMIGABggrBgEFBQcBAQR0MHIwJAYIKwYBBQUHMAGGGGh0dHA6Ly9vY3NwLmRp
|
|
||||||
Z2ljZXJ0LmNvbTBKBggrBgEFBQcwAoY+aHR0cDovL2NhY2VydHMuZGlnaWNlcnQu
|
|
||||||
Y29tL0VuY3J5cHRpb25FdmVyeXdoZXJlRFZUTFNDQS1HMi5jcnQwDAYDVR0TAQH/
|
|
||||||
BAIwADCCAX0GCisGAQQB1nkCBAIEggFtBIIBaQFnAHUAwjF+V0UZo0XufzjespBB
|
|
||||||
68fCIVoiv3/Vta12mtkOUs0AAAGesEYy8QAABAMARjBEAiBD7255l5PRI6mZXWxU
|
|
||||||
iC7txvWxkLlBgVFzAkRrMW7RXQIgZyupslAslfGDqDMk9H948AFZGe0JwB6YiuXK
|
|
||||||
lx1sox0AdgDYCVU7lE96/8gWGW+UT4WrsPj8XodVJg8V0S5yu0VLFAAAAZ6wRjNI
|
|
||||||
AAAEAwBHMEUCIBqfIfl4sL0ME60NI0KG6JIHImcJjR5qkLGDjCp3sEhrAiEA4cvy
|
|
||||||
yFaz3BSjjQa+UspsqgfKxeTjCLJq5pLbDcgBgGQAdgCUTkOH+uzB74HzGSQmqBhl
|
|
||||||
AcfTXzgCAT9yZ31VNy4Z2AAAAZ6wRjL3AAAEAwBHMEUCIQDIIa8mLpO6BmhgCecw
|
|
||||||
pI+QNznn4lhIxmtJQyHmZo7qRAIgFPq9xlLLqqe5/+WgcNwoawggpi8skhg8t/yT
|
|
||||||
JJ5TmV0wDQYJKoZIhvcNAQELBQADggEBAOeiZrRwpNh/bNIuKe+1vWBrLHu2Zj70
|
|
||||||
cKAVUhT08ZAl8scEYusbuqtSAq+oz9sm595YyPgmrB0wSZEMTd/+pUy5GrrJAXal
|
|
||||||
kOx1a3TBxCvpK9F4T7KmBaBWA5HuljHfw9833jXi7v9jLYShDjyNRzSWeKl41kdt
|
|
||||||
Hq4CbDLXp9XVzVPBlOongGZV/6cDrvy4hoJcBq8UANU/QWw0PaJQWxPwHcULekBw
|
|
||||||
AefTX7JwKbHcZ3gGV4EzJU/eoycBOu9VsYgJu9U+p1nNjYHwS8RQjij+cgs8r5Wx
|
|
||||||
r4+wYkdN2767iK2+NRRdQD/ZqBMTgSZ6AXCM7gS8oP/ylLwnzyq7ikc=
|
|
||||||
-----END CERTIFICATE-----
|
|
||||||
-----BEGIN CERTIFICATE-----
|
|
||||||
MIIEqjCCA5KgAwIBAgIQDeD/te5iy2EQn2CMnO1e0zANBgkqhkiG9w0BAQsFADBh
|
|
||||||
MQswCQYDVQQGEwJVUzEVMBMGA1UEChMMRGlnaUNlcnQgSW5jMRkwFwYDVQQLExB3
|
|
||||||
d3cuZGlnaWNlcnQuY29tMSAwHgYDVQQDExdEaWdpQ2VydCBHbG9iYWwgUm9vdCBH
|
|
||||||
MjAeFw0xNzExMjcxMjQ2NDBaFw0yNzExMjcxMjQ2NDBaMG4xCzAJBgNVBAYTAlVT
|
|
||||||
MRUwEwYDVQQKEwxEaWdpQ2VydCBJbmMxGTAXBgNVBAsTEHd3dy5kaWdpY2VydC5j
|
|
||||||
b20xLTArBgNVBAMTJEVuY3J5cHRpb24gRXZlcnl3aGVyZSBEViBUTFMgQ0EgLSBH
|
|
||||||
MjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAO8Uf46i/nr7pkgTDqnE
|
|
||||||
eSIfCFqvPnUq3aF1tMJ5hh9MnO6Lmt5UdHfBGwC9Si+XjK12cjZgxObsL6Rg1njv
|
|
||||||
NhAMJ4JunN0JGGRJGSevbJsA3sc68nbPQzuKp5Jc8vpryp2mts38pSCXorPR+sch
|
|
||||||
QisKA7OSQ1MjcFN0d7tbrceWFNbzgL2csJVQeogOBGSe/KZEIZw6gXLKeFe7mupn
|
|
||||||
NYJROi2iC11+HuF79iAttMc32Cv6UOxixY/3ZV+LzpLnklFq98XORgwkIJL1HuvP
|
|
||||||
ha8yvb+W6JislZJL+HLFtidoxmI7Qm3ZyIV66W533DsGFimFJkz3y0GeHWuSVMbI
|
|
||||||
lfsCAwEAAaOCAU8wggFLMB0GA1UdDgQWBBR435GQX+7erPbFdevVTFVT7yRKtjAf
|
|
||||||
BgNVHSMEGDAWgBROIlQgGJXm427mD/r6uRLtBhePOTAOBgNVHQ8BAf8EBAMCAYYw
|
|
||||||
HQYDVR0lBBYwFAYIKwYBBQUHAwEGCCsGAQUFBwMCMBIGA1UdEwEB/wQIMAYBAf8C
|
|
||||||
AQAwNAYIKwYBBQUHAQEEKDAmMCQGCCsGAQUFBzABhhhodHRwOi8vb2NzcC5kaWdp
|
|
||||||
Y2VydC5jb20wQgYDVR0fBDswOTA3oDWgM4YxaHR0cDovL2NybDMuZGlnaWNlcnQu
|
|
||||||
Y29tL0RpZ2lDZXJ0R2xvYmFsUm9vdEcyLmNybDBMBgNVHSAERTBDMDcGCWCGSAGG
|
|
||||||
/WwBAjAqMCgGCCsGAQUFBwIBFhxodHRwczovL3d3dy5kaWdpY2VydC5jb20vQ1BT
|
|
||||||
MAgGBmeBDAECATANBgkqhkiG9w0BAQsFAAOCAQEAoBs1eCLKakLtVRPFRjBIJ9LJ
|
|
||||||
L0s8ZWum8U8/1TMVkQMBn+CPb5xnCD0GSA6L/V0ZFrMNqBirrr5B241OesECvxIi
|
|
||||||
98bZ90h9+q/X5eMyOD35f8YTaEMpdnQCnawIwiHx06/0BfiTj+b/XQih+mqt3ZXe
|
|
||||||
xNCJqKexdiB2IWGSKcgahPacWkk/BAQFisKIFYEqHzV974S3FAz/8LIfD58xnsEN
|
|
||||||
GfzyIDkH3JrwYZ8caPTf6ZX9M1GrISN8HnWTtdNCH2xEajRa/h9ZBXjUyFKQrGk2
|
|
||||||
n2hcLrfZSbynEC/pSw/ET7H5nWwckjmAJ1l9fcnbqkU/pf6uMQmnfl0JQjJNSg==
|
|
||||||
-----END CERTIFICATE-----
|
|
||||||
Reference in New Issue
Block a user