初始化本地仓库
This commit is contained in:
@@ -0,0 +1,48 @@
|
||||
# 【通用】环境变量
|
||||
|
||||
# 版本号
|
||||
VITE_VERSION = 3.0.1
|
||||
|
||||
# 端口号
|
||||
VITE_PORT = 3006
|
||||
|
||||
# 应用部署基础路径(如部署在子目录 /admin,则设置为 /admin/)
|
||||
VITE_BASE_URL = /
|
||||
|
||||
# API 地址前缀
|
||||
VITE_API_URL = /adminapi
|
||||
|
||||
# 是否启用 Token 自动刷新(true: 401 时自动刷新 token,false: 401 时直接退出)
|
||||
VITE_TOKEN_REFRESH = true
|
||||
|
||||
# 权限模式( frontend | backend )
|
||||
VITE_ACCESS_MODE = backend
|
||||
|
||||
# 跨域请求时是否携带 Cookie(开启前需确保后端支持)
|
||||
VITE_WITH_CREDENTIALS = false
|
||||
|
||||
# 是否打开路由信息
|
||||
VITE_OPEN_ROUTE_INFO = false
|
||||
|
||||
# 锁屏加密密钥
|
||||
VITE_LOCK_ENCRYPT_KEY = s3cur3k3y4adpro
|
||||
|
||||
# 是否启用离线图标
|
||||
# 图标前缀-提供给icon-picker选择的图标,这里的值建议和src/utils/load-icon.ts中引入的一致
|
||||
VITE_APP_ICONIFY_PREFIX = ep,ant-design,carbon,ic,logos,lucide,ri
|
||||
|
||||
# 后端模式是否使用权限码接口
|
||||
VITE_BACKEND_MODE_USE_CODE = true
|
||||
|
||||
# =============================================================================
|
||||
# 静态资源配置(两种模式)
|
||||
# =============================================================================
|
||||
|
||||
# 【模式 1】同域访问(默认,dist 部署到后端 public 目录)
|
||||
# 静态资源使用相对路径,浏览器自动使用当前域名访问
|
||||
VITE_STATIC_URL =
|
||||
|
||||
# 【模式 2】CDN 访问(静态资源使用对象存储或 CDN)
|
||||
# 直接配置 CDN 域名
|
||||
# VITE_STATIC_URL = https://cdn.example.com
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
# 【开发】环境变量
|
||||
|
||||
# 应用部署基础路径(如部署在子目录 /admin,则设置为 /admin/)
|
||||
VITE_BASE_URL = /
|
||||
|
||||
# API 地址前缀
|
||||
VITE_API_URL = /adminapi
|
||||
|
||||
# 是否启用 Token 自动刷新(true: 401 时自动刷新 token,false: 401 时直接退出)
|
||||
VITE_TOKEN_REFRESH = true
|
||||
|
||||
# Delete console
|
||||
VITE_DROP_CONSOLE = false
|
||||
|
||||
# 本地开发代理的目标后端地址(仅开发环境生效,用于解决跨域)
|
||||
VITE_API_PROXY_URL = http://127.0.0.1:8001
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,13 @@
|
||||
# 【生产】环境变量
|
||||
|
||||
# 网站地址前缀
|
||||
VITE_BASE_URL = /
|
||||
|
||||
# API 地址前缀
|
||||
VITE_API_URL = https://m1.apifoxmock.com/m1/7233009-0-default
|
||||
|
||||
# 权限模式( frontend | backend )
|
||||
VITE_ACCESS_MODE = backend
|
||||
|
||||
# Delete console
|
||||
VITE_DROP_CONSOLE = true
|
||||
@@ -0,0 +1,13 @@
|
||||
# 【生产】环境变量
|
||||
|
||||
# 应用部署基础路径(如部署在子目录 /admin,则设置为 /admin/)
|
||||
VITE_BASE_URL = /admin/
|
||||
|
||||
# API 地址前缀
|
||||
VITE_API_URL = /adminapi
|
||||
|
||||
# 是否启用 Token 自动刷新(true: 401 时自动刷新 token,false: 401 时直接退出)
|
||||
VITE_TOKEN_REFRESH = true
|
||||
|
||||
# Delete console
|
||||
VITE_DROP_CONSOLE = true
|
||||
@@ -0,0 +1,23 @@
|
||||
# 【生产】环境变量
|
||||
|
||||
# API 地址前缀
|
||||
VITE_API_URL = http://127.0.0.1:8001/adminapi
|
||||
|
||||
# 删除 console
|
||||
VITE_DROP_CONSOLE = true
|
||||
|
||||
# ==============================================================================
|
||||
# 静态资源配置(选择其中一种)
|
||||
# ==============================================================================
|
||||
VITE_STATIC_URL =
|
||||
|
||||
# 【场景 1】同域访问(dist 部署到后端 public 目录)
|
||||
# 静态资源使用相对路径,浏览器自动使用当前域名访问
|
||||
|
||||
# 【场景 2】代理转发(前后端分离,通过后端服务器代理)
|
||||
# 静态资源通过后端域名访问,可能需要添加路径前缀
|
||||
# VITE_STATIC_URL = http://example.com
|
||||
|
||||
# 【场景 3】OSS/CDN(静态资源使用对象存储或 CDN)
|
||||
# 直接配置 CDN 域名
|
||||
# VITE_STATIC_URL = https://cdn.example.com
|
||||
@@ -0,0 +1,10 @@
|
||||
# 【生产】环境变量
|
||||
|
||||
# 网站地址前缀
|
||||
VITE_BASE_URL = /
|
||||
|
||||
# API 地址前缀
|
||||
VITE_API_URL = /api
|
||||
|
||||
# Delete console
|
||||
VITE_DROP_CONSOLE = true
|
||||
@@ -0,0 +1,2 @@
|
||||
*.html linguist-detectable=false
|
||||
*.vue linguist-detectable=true
|
||||
@@ -0,0 +1,18 @@
|
||||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
dist-ssr
|
||||
*.local
|
||||
.cursorrules
|
||||
*.idea
|
||||
|
||||
|
||||
# 忽略 src/apps 目录下除了 demo 之外的内容
|
||||
src/apps/*
|
||||
!src/apps/demo/
|
||||
|
||||
|
||||
# Auto-generated files
|
||||
src/types/import/auto-imports.d.ts
|
||||
src/types/import/components.d.ts
|
||||
.auto-import.json
|
||||
@@ -0,0 +1,2 @@
|
||||
registry = https://registry.npmmirror.com
|
||||
allow-build=esbuild,vue-demi,@parcel/watcher
|
||||
@@ -0,0 +1,3 @@
|
||||
/node_modules/*
|
||||
/dist/*
|
||||
/src/main.ts
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"printWidth": 100,
|
||||
"tabWidth": 2,
|
||||
"useTabs": false,
|
||||
"semi": false,
|
||||
"vueIndentScriptAndStyle": true,
|
||||
"singleQuote": true,
|
||||
"quoteProps": "as-needed",
|
||||
"bracketSpacing": true,
|
||||
"trailingComma": "none",
|
||||
"bracketSameLine": false,
|
||||
"jsxSingleQuote": false,
|
||||
"arrowParens": "always",
|
||||
"insertPragma": false,
|
||||
"requirePragma": false,
|
||||
"proseWrap": "never",
|
||||
"htmlWhitespaceSensitivity": "strict",
|
||||
"endOfLine": "auto",
|
||||
"rangeStart": 0
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
dist
|
||||
node_modules
|
||||
public
|
||||
.husky
|
||||
.vscode
|
||||
|
||||
src/components/Layout/MenuLeft/index.vue
|
||||
src/assets
|
||||
stats.html
|
||||
@@ -0,0 +1,82 @@
|
||||
module.exports = {
|
||||
// 继承推荐规范配置
|
||||
extends: [
|
||||
'stylelint-config-standard',
|
||||
'stylelint-config-recommended-scss',
|
||||
'stylelint-config-recommended-vue/scss',
|
||||
'stylelint-config-html/vue',
|
||||
'stylelint-config-recess-order'
|
||||
],
|
||||
// 指定不同文件对应的解析器
|
||||
overrides: [
|
||||
{
|
||||
files: ['**/*.{vue,html}'],
|
||||
customSyntax: 'postcss-html'
|
||||
},
|
||||
{
|
||||
files: ['**/*.{css,scss}'],
|
||||
customSyntax: 'postcss-scss'
|
||||
}
|
||||
],
|
||||
// 自定义规则
|
||||
rules: {
|
||||
'import-notation': 'string', // 指定导入CSS文件的方式("string"|"url")
|
||||
'selector-class-pattern': null, // 选择器类名命名规则
|
||||
'custom-property-pattern': null, // 自定义属性命名规则
|
||||
'keyframes-name-pattern': null, // 动画帧节点样式命名规则
|
||||
'no-descending-specificity': null, // 允许无降序特异性
|
||||
'no-empty-source': null, // 允许空样式
|
||||
'property-no-vendor-prefix': null, // 允许属性前缀
|
||||
// 允许 global 、export 、deep伪类
|
||||
'selector-pseudo-class-no-unknown': [
|
||||
true,
|
||||
{
|
||||
ignorePseudoClasses: ['global', 'export', 'deep']
|
||||
}
|
||||
],
|
||||
// 允许未知属性
|
||||
'property-no-unknown': [
|
||||
true,
|
||||
{
|
||||
ignoreProperties: []
|
||||
}
|
||||
],
|
||||
// 允许未知规则
|
||||
'at-rule-no-unknown': [
|
||||
true,
|
||||
{
|
||||
ignoreAtRules: [
|
||||
'apply',
|
||||
'use',
|
||||
'mixin',
|
||||
'include',
|
||||
'extend',
|
||||
'each',
|
||||
'if',
|
||||
'else',
|
||||
'for',
|
||||
'while',
|
||||
'reference'
|
||||
]
|
||||
}
|
||||
],
|
||||
'scss/at-rule-no-unknown': [
|
||||
true,
|
||||
{
|
||||
ignoreAtRules: [
|
||||
'apply',
|
||||
'use',
|
||||
'mixin',
|
||||
'include',
|
||||
'extend',
|
||||
'each',
|
||||
'if',
|
||||
'else',
|
||||
'for',
|
||||
'while',
|
||||
'reference'
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
+160
@@ -0,0 +1,160 @@
|
||||
{
|
||||
"mldong-art-design-dialog": {
|
||||
"prefix": "dialog",
|
||||
"body": [
|
||||
"<template>",
|
||||
" <Dialog> 我是弹窗内容 </Dialog>",
|
||||
"</template>",
|
||||
"<script setup lang=\"tsx\">",
|
||||
" import { useDialog } from '@/components/dialog'",
|
||||
" import { CrudMethods } from '@/components/crud'",
|
||||
" const record = ref<any>({})",
|
||||
" const crudApi = ref<CrudMethods>()",
|
||||
" const [Dialog, dialogApi] = useDialog({",
|
||||
" title: '我是标题',",
|
||||
" width: '50%'",
|
||||
" })",
|
||||
" defineExpose({",
|
||||
" show({ data, getCrudApi }: { data: any; getCrudApi: () => CrudMethods }) {",
|
||||
" record.value = data",
|
||||
" crudApi.value = getCrudApi()",
|
||||
" dialogApi.open()",
|
||||
" }",
|
||||
" })",
|
||||
"</script>",
|
||||
"<style lang=\"scss\" scoped></style>",
|
||||
""
|
||||
],
|
||||
"description": "生成mldong-art-design dialog模板"
|
||||
},
|
||||
"mldong-art-design-dialog-drawer": {
|
||||
"prefix": "drawer",
|
||||
"body": [
|
||||
"<template>",
|
||||
" <Dialog> 我是弹窗内容 </Dialog>",
|
||||
"</template>",
|
||||
"<script setup lang=\"tsx\">",
|
||||
" import { useDialog } from '@/components/dialog'",
|
||||
" import { CrudMethods } from '@/components/crud'",
|
||||
" const record = ref<any>({})",
|
||||
" const crudApi = ref<CrudMethods>()",
|
||||
" const [Dialog, dialogApi] = useDialog({",
|
||||
" title: '我是标题',",
|
||||
" width: '50%',",
|
||||
" dialogType: 'drawer'",
|
||||
" })",
|
||||
" defineExpose({",
|
||||
" show({ data, getCrudApi }: { data: any; getCrudApi: () => CrudMethods }) {",
|
||||
" record.value = data",
|
||||
" crudApi.value = getCrudApi()",
|
||||
" dialogApi.open()",
|
||||
" }",
|
||||
" })",
|
||||
"</script>",
|
||||
"<style lang=\"scss\" scoped></style>",
|
||||
""
|
||||
],
|
||||
"description": "生成mldong-art-design dialog-drawer模板"
|
||||
},
|
||||
"mldong-art-design-dialog-form": {
|
||||
"prefix": "dialog-form",
|
||||
"body": [
|
||||
"<template>",
|
||||
" <Dialog>",
|
||||
" <BasicForm />",
|
||||
" </Dialog>",
|
||||
"</template>",
|
||||
"<script setup lang=\"tsx\">",
|
||||
" import { useDialog } from '@/components/dialog'",
|
||||
" import { CrudMethods } from '@/components/crud'",
|
||||
" import { useForm } from '@/components/form'",
|
||||
" const record = ref<any>({})",
|
||||
" const crudApi = ref<CrudMethods>()",
|
||||
" const [Dialog, dialogApi] = useDialog({",
|
||||
" title: '我是标题',",
|
||||
" width: '50%',",
|
||||
" onConfirm() {",
|
||||
" formApi.validate().then(async () => {",
|
||||
" const values = await formApi.getValues()",
|
||||
" console.log(values)",
|
||||
" })",
|
||||
" }",
|
||||
" })",
|
||||
" const [BasicForm, formApi] = useForm({",
|
||||
" labelWidth: '120px',",
|
||||
" schema: [",
|
||||
" {",
|
||||
" prop: 'name',",
|
||||
" label: '姓名',",
|
||||
" component: 'Input',",
|
||||
" rules: [{ required: true, message: '请输入姓名' }]",
|
||||
" }",
|
||||
" ]",
|
||||
" })",
|
||||
" defineExpose({",
|
||||
" show({ data, getCrudApi }: { data: any; getCrudApi: () => CrudMethods }) {",
|
||||
" record.value = data",
|
||||
" crudApi.value = getCrudApi()",
|
||||
" dialogApi.open()",
|
||||
" nextTick(() => {",
|
||||
" formApi.setValues(record.value)",
|
||||
" })",
|
||||
" }",
|
||||
" })",
|
||||
"</script>",
|
||||
"<style lang=\"scss\" scoped></style>"
|
||||
],
|
||||
"description": "生成mldong-art-design dialog-form模板"
|
||||
},
|
||||
"mldong-art-design-drawer-form": {
|
||||
"prefix": "drawer-form",
|
||||
"body": [
|
||||
"<template>",
|
||||
" <Dialog>",
|
||||
" <BasicForm />",
|
||||
" </Dialog>",
|
||||
"</template>",
|
||||
"<script setup lang=\"tsx\">",
|
||||
" import { useDialog } from '@/components/dialog'",
|
||||
" import { CrudMethods } from '@/components/crud'",
|
||||
" import { useForm } from '@/components/form'",
|
||||
" const record = ref<any>({})",
|
||||
" const crudApi = ref<CrudMethods>()",
|
||||
" const [Dialog, dialogApi] = useDialog({",
|
||||
" title: '我是标题',",
|
||||
" width: '50%',",
|
||||
" dialogType: 'drawer',",
|
||||
" onConfirm() {",
|
||||
" formApi.validate().then(async () => {",
|
||||
" const values = await formApi.getValues()",
|
||||
" console.log(values)",
|
||||
" })",
|
||||
" }",
|
||||
" })",
|
||||
" const [BasicForm, formApi] = useForm({",
|
||||
" labelWidth: '120px',",
|
||||
" schema: [",
|
||||
" {",
|
||||
" prop: 'name',",
|
||||
" label: '姓名',",
|
||||
" component: 'Input',",
|
||||
" rules: [{ required: true, message: '请输入姓名' }]",
|
||||
" }",
|
||||
" ]",
|
||||
" })",
|
||||
" defineExpose({",
|
||||
" show({ data, getCrudApi }: { data: any; getCrudApi: () => CrudMethods }) {",
|
||||
" record.value = data",
|
||||
" crudApi.value = getCrudApi()",
|
||||
" dialogApi.open()",
|
||||
" nextTick(() => {",
|
||||
" formApi.setValues(record.value)",
|
||||
" })",
|
||||
" }",
|
||||
" })",
|
||||
"</script>",
|
||||
"<style lang=\"scss\" scoped></style>"
|
||||
],
|
||||
"description": "生成mldong-art-design drawer-form模板"
|
||||
}
|
||||
}
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
{
|
||||
"recommendations": [
|
||||
"vue.volar",
|
||||
"dbaeumer.vscode-eslint",
|
||||
"esbenp.prettier-vscode",
|
||||
"stylelint.vscode-stylelint",
|
||||
"lokalise.i18n-ally",
|
||||
"bradlc.vscode-tailwindcss"
|
||||
]
|
||||
}
|
||||
Vendored
+20
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"volar.inlayHints.eventArgumentInInlineHandlers": true,
|
||||
"i18n-ally.localesPaths": ["src/locales", "src/locales/langs"],
|
||||
"editor.formatOnSave": true, // 启用保存时自动格式化
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode", // 设置默认格式化工具为 Prettier
|
||||
"css.lint.unknownAtRules": "ignore",
|
||||
"i18n-ally.enabledParsers": ["json"],
|
||||
"i18n-ally.sourceLanguage": "en",
|
||||
"i18n-ally.displayLanguage": "zh",
|
||||
"i18n-ally.enabledFrameworks": ["vue", "react"],
|
||||
"i18n-ally.keystyle": "nested",
|
||||
"i18n-ally.sortKeys": true,
|
||||
"i18n-ally.namespace": true,
|
||||
"[typescriptreact]": {
|
||||
"editor.defaultFormatter": "esbenp.prettier-vscode"
|
||||
},
|
||||
"[json]": {
|
||||
"editor.defaultFormatter": "vscode.json-language-features"
|
||||
} // 设置默认格式化工具为 Prettier
|
||||
}
|
||||
@@ -0,0 +1,968 @@
|
||||
# CRUD 功能模块开发模板
|
||||
|
||||
## 📋 概述
|
||||
|
||||
本模板提供了一套完整的 CRUD 功能模块开发规范,适用于当前项目框架(基于 Vue3 + TypeScript + Element Plus + VxeTable)。通过统一的目录结构、命名规范和代码模板,确保项目代码的一致性和可维护性。
|
||||
|
||||
## 📁 目录结构规范
|
||||
|
||||
### 标准结构(基于当前项目框架)
|
||||
|
||||
```
|
||||
src/apps/{模块名}/
|
||||
├── api/ # 模块API接口
|
||||
├── assets/ # 模块静态资源
|
||||
├── components/ # 模块私有组件
|
||||
├── lang/ # 模块国际化文件
|
||||
├── mock/ # 模块模拟数据
|
||||
├── routes/ # 模块路由配置
|
||||
├── schemas/ # 模块CRUD配置
|
||||
└── views/ # 模块页面视图
|
||||
├── {实体名}/
|
||||
│ ├── index.vue # 页面入口文件
|
||||
│ ├── schemas/ # Schema配置
|
||||
│ └── components/ # 私有组件(可选)
|
||||
```
|
||||
|
||||
### 核心文件位置
|
||||
|
||||
- **页面入口**: `src/apps/{模块名}/views/{实体名}/index.vue`
|
||||
- **Schema配置**: `src/apps/{模块名}/views/{实体名}/schemas/index.tsx`
|
||||
- **API服务**: `src/apps/{模块名}/api/{实体名}/index.ts`
|
||||
- **组件库**: `src/components/crud/`(核心CRUD组件)
|
||||
|
||||
### ⚠️ 重要注意事项
|
||||
|
||||
- **禁止增加不必要的子目录**,避免目录结构混乱
|
||||
- 保持目录结构简洁统一
|
||||
- 模块名使用小写字母,多个单词用连字符分隔
|
||||
- 实体名使用小写字母,多个单词用连字符分隔
|
||||
- 文件名统一使用小写字母和连字符
|
||||
- 目录结构必须严格遵循此规范
|
||||
|
||||
## 🔤 命名规范
|
||||
|
||||
### 命名规则
|
||||
|
||||
- **数据库表名**:使用下划线命名法,如 `system_user`、`system_user_role`
|
||||
- **模块名**:使用表名中下划线前的部分,如 `system`
|
||||
- **实体名**:使用表名中下划线后的部分,如 `user`,多个部分用中横线连接,如 `user-role`
|
||||
- **文件名**:使用中横线命名法,如 `user-role.ts`
|
||||
|
||||
### 命名示例
|
||||
|
||||
| 表名 | 模块名 | 实体名 | 实体小驼峰名 | API 服务文件 | API 服务类名 |
|
||||
| --- | --- | --- | --- | --- | --- |
|
||||
| `system_user` | `system` | `user` | `user` | `./src/app/api/system/user.ts` | `UserService` |
|
||||
| `system_user_role` | `system` | `user-role` | `userRole` | `./src/app/api/system/user-role.ts` | `UserRoleService` |
|
||||
|
||||
## 📏 约束规则
|
||||
|
||||
1. **ID字段**:不在列表上显示
|
||||
2. **列宽度**:默认自适应,无需手动设置
|
||||
3. **备注字段**:字段过多时不在列表显示
|
||||
4. **表单布局**:
|
||||
- 字段较少时:`colSpan` 默认为 24(全宽)
|
||||
- 字段较多时:可设置 `colSpan` 为 12(半宽)
|
||||
|
||||
## 🔧 API 服务模板
|
||||
|
||||
### 基础服务模板
|
||||
|
||||
**文件路径**: `src/apps/{模块名}/api/{实体名}/index.ts`
|
||||
|
||||
```typescript
|
||||
import { BaseService } from '@/utils/request'
|
||||
import { {EntityName}Row } from '@/apps/{模块名}/api/{实体名}/types'
|
||||
|
||||
/**
|
||||
* {实体名} API 服务
|
||||
*/
|
||||
export const {EntityName}Service = new BaseService<{EntityName}Row>('/{模块名}/{实体名}')
|
||||
|
||||
export default {EntityName}Service
|
||||
```
|
||||
|
||||
### BaseService 配置说明
|
||||
|
||||
- **baseUrl**: 接口基础路径
|
||||
- **allowedMethods**: 允许的方法列表(空数组表示允许所有)
|
||||
- **forbiddenMethods**: 禁止的方法列表(如 `['export']` 表示禁用导出功能)
|
||||
|
||||
### 默认包含的接口方法
|
||||
|
||||
- `page()`: 分页查询
|
||||
- `save()`: 新增
|
||||
- `update()`: 修改
|
||||
- `remove()`: 删除
|
||||
- `detail()`: 详情
|
||||
- `export()`: 导出(可禁用)
|
||||
|
||||
## 📋 Schema 配置模板
|
||||
|
||||
### 基础配置模板
|
||||
|
||||
**文件路径**: `src/apps/{模块名}/views/{实体名}/schemas/index.tsx`
|
||||
|
||||
```typescript
|
||||
import { CrudSchema } from '@/components/crud'
|
||||
import { {EntityName}Service } from '@/apps/{模块名}/api/{实体名}'
|
||||
import { $t } from '@/locales'
|
||||
|
||||
/**
|
||||
* {实体名} CRUD Schema 配置
|
||||
*/
|
||||
export const crudSchema = (): CrudSchema => {
|
||||
return {
|
||||
// 接口配置
|
||||
api: {EntityName}Service.page,
|
||||
|
||||
// CRUD API 映射
|
||||
crudApi: {
|
||||
add: {EntityName}Service.save,
|
||||
edit: {EntityName}Service.update,
|
||||
remove: {EntityName}Service.remove,
|
||||
view: {EntityName}Service.detail
|
||||
},
|
||||
|
||||
// 功能开关
|
||||
useCrud: true,
|
||||
hasAdd: true,
|
||||
hasRemove: true,
|
||||
hasEdit: true,
|
||||
hasView: true,
|
||||
|
||||
// 权限控制
|
||||
addAuth: '{模块名}:{实体名}:create',
|
||||
removeAuth: '{模块名}:{实体名}:delete',
|
||||
editAuth: '{模块名}:{实体名}:update',
|
||||
viewAuth: '{模块名}:{实体名}:read',
|
||||
|
||||
// 表格列定义
|
||||
columns: [
|
||||
// 选择列
|
||||
{ type: 'selection' },
|
||||
|
||||
// 示例列定义
|
||||
{
|
||||
prop: 'name',
|
||||
label: $t('{模块名}.{实体名}.list.table.columns.name'),
|
||||
minWidth: 120
|
||||
},
|
||||
|
||||
// 更多列定义...
|
||||
],
|
||||
|
||||
// 搜索表单配置
|
||||
searchFormSchema: {
|
||||
schema: [
|
||||
{
|
||||
prop: 'LIKE_name',
|
||||
label: $t('{模块名}.{实体名}.list.query.name'),
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
clearable: true,
|
||||
placeholder: $t('{模块名}.{实体名}.list.query.placeholder.name')
|
||||
},
|
||||
colSpan: 6
|
||||
}
|
||||
// 更多搜索字段...
|
||||
]
|
||||
},
|
||||
|
||||
// 弹窗配置
|
||||
dialogProps: {
|
||||
dialogType: 'drawer',
|
||||
width: '50%'
|
||||
},
|
||||
|
||||
// 表单配置
|
||||
formSchema: {
|
||||
showDefaultActions: false,
|
||||
labelWidth: '100px',
|
||||
schema: [
|
||||
// 表单字段定义...
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 配置项说明
|
||||
|
||||
| 配置项 | 说明 | 默认值 |
|
||||
| ------------------- | ------------------ | ------- |
|
||||
| `useCrud` | 是否启用 CRUD 功能 | `true` |
|
||||
| `hasAdd` | 是否显示新增按钮 | `true` |
|
||||
| `hasRemove` | 是否显示删除按钮 | `true` |
|
||||
| `hasEdit` | 是否显示编辑按钮 | `true` |
|
||||
| `hasView` | 是否显示详情按钮 | `true` |
|
||||
| `labelWidth` | 表单标签宽度 | `100px` |
|
||||
| `dialogProps.width` | 弹窗宽度 | `50%` |
|
||||
|
||||
## 🖥️ 页面入口模板
|
||||
|
||||
### 基础页面模板
|
||||
|
||||
**文件路径**: `src/apps/{模块名}/views/{实体名}/index.vue`
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<div class="{模块名}-{实体名}-page art-full-height">
|
||||
<BasicCrud />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="tsx">
|
||||
import { useCrud } from '@/components/crud'
|
||||
import { crudSchema } from './schemas'
|
||||
|
||||
// 使用 CRUD 组件
|
||||
const [BasicCrud] = useCrud(crudSchema())
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.{模块名}-{实体名}-page {
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
### 操作按钮配置说明
|
||||
|
||||
| 配置项 | 说明 | 示例 |
|
||||
| ------------------------ | ------------------ | ------------------ |
|
||||
| `toolbarActions` | 表格上方工具栏按钮 | 导出、批量操作等 |
|
||||
| `dropDownToolbarActions` | 工具栏下拉菜单 | 更多操作 |
|
||||
| `tableActions` | 表格行操作按钮 | 编辑、删除、详情等 |
|
||||
| `dropDownActions` | 行操作下拉菜单 | 扩展操作 |
|
||||
|
||||
## ⚙️ 关键配置说明
|
||||
|
||||
### 表格列配置 (columns)
|
||||
|
||||
```typescript
|
||||
{
|
||||
prop: '字段名', // 字段名称
|
||||
label: '列标题', // 列显示标题
|
||||
component: '组件名', // 渲染组件,如 'ApiDict', 'ApiSelect' 等
|
||||
componentProps: { // 组件属性
|
||||
code: '字典编码', // 字典组件特有
|
||||
renderType: 'Select' // 渲染类型
|
||||
},
|
||||
width: 120, // 列宽度(可选)
|
||||
fixed: 'left' // 固定列(可选)
|
||||
}
|
||||
```
|
||||
|
||||
**常用列属性:**
|
||||
|
||||
- `type: 'selection'`: 选择列
|
||||
- `type: 'index'`: 序号列
|
||||
- `prop`: 数据字段名
|
||||
- `label`: 列标题
|
||||
- `component`: 渲染组件
|
||||
- `width`: 列宽度
|
||||
- `minWidth`: 最小宽度
|
||||
- `fixed`: 固定列位置
|
||||
|
||||
### 表单字段配置 (formSchema.schema)
|
||||
|
||||
```typescript
|
||||
{
|
||||
label: '字段标签',
|
||||
prop: '字段名',
|
||||
component: '组件名', // 如 'Input', 'Select', 'ApiDict' 等
|
||||
colSpan: 12, // 栅格占比 (1-24)
|
||||
rules: [ // 校验规则
|
||||
{
|
||||
required: true,
|
||||
message: '校验提示信息'
|
||||
}
|
||||
],
|
||||
componentProps: { // 组件属性
|
||||
placeholder: '占位符',
|
||||
clearable: true
|
||||
},
|
||||
ifShow(values) { // 动态显示条件
|
||||
return !!values.条件字段
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**常用表单属性:**
|
||||
|
||||
- `colSpan`: 栅格宽度(1-24)
|
||||
- `rules`: 校验规则数组
|
||||
- `componentProps`: 组件特有属性
|
||||
- `ifShow`: 条件显示函数
|
||||
- `show`: 静态显示控制
|
||||
|
||||
### 搜索表单配置 (searchFormSchema.schema)
|
||||
|
||||
```typescript
|
||||
{
|
||||
prop: '操作符_字段名', // 如 'LIKE_name', 'EQ_status'
|
||||
label: '搜索字段标签',
|
||||
component: 'Input', // 搜索组件
|
||||
componentProps: {
|
||||
clearable: true,
|
||||
placeholder: '请输入搜索条件'
|
||||
},
|
||||
colSpan: 6 // 栅格占比
|
||||
}
|
||||
```
|
||||
|
||||
**常用搜索操作符:**
|
||||
|
||||
- `EQ_`: 等于
|
||||
- `LIKE_`: 模糊匹配
|
||||
- `GT_`: 大于
|
||||
- `LT_`: 小于
|
||||
- `IN_`: 包含
|
||||
|
||||
### 树形结构配置 (tree)
|
||||
|
||||
对于具有树形结构的数据(如菜单、组织架构等),需要配置 tree 属性:
|
||||
|
||||
```typescript
|
||||
export const crudSchema: CrudSchema = {
|
||||
// 使用 tree 接口而不是 page 接口
|
||||
api: {EntityName}Service.tree,
|
||||
|
||||
// 树型是全量数据,不需要搜索
|
||||
useSearchForm: false,
|
||||
|
||||
// 树形结构配置
|
||||
tree: {
|
||||
id: 'id', // 树形节点唯一标识字段名
|
||||
pid: 'parentId', // 父节点字段名
|
||||
children: 'children' // 子节点字段名
|
||||
},
|
||||
|
||||
// 其他配置...
|
||||
}
|
||||
```
|
||||
|
||||
**树形配置注意事项:**
|
||||
|
||||
- 必须使用 `tree` 接口而非 `page` 接口
|
||||
- 禁用搜索表单(`useSearchForm: false`)
|
||||
- 确保 API 返回树形结构数据
|
||||
|
||||
## 🧩 常用组件
|
||||
|
||||
### 基础表单组件
|
||||
|
||||
| 组件 | 说明 | 常用属性 |
|
||||
| --------------- | ---------- | -------------------------- |
|
||||
| `Input` | 普通输入框 | `placeholder`, `clearable` |
|
||||
| `InputNumber` | 数字输入框 | `min`, `max`, `precision` |
|
||||
| `Select` | 下拉选择框 | `options`, `clearable` |
|
||||
| `RadioGroup` | 单选框组 | `options` |
|
||||
| `CheckboxGroup` | 多选框组 | `options` |
|
||||
| `DatePicker` | 日期选择器 | `type`, `format` |
|
||||
| `Divider` | 分割线 | `content-position` |
|
||||
|
||||
### 异步数据组件
|
||||
|
||||
| 组件 | 说明 | 常用属性 |
|
||||
| ------------------ | ------------ | --------------------------------- |
|
||||
| `ApiDict` | 字典组件 | `code`, `renderType` |
|
||||
| `ApiSelect` | 异步下拉选择 | `api`, `labelField`, `valueField` |
|
||||
| `ApiCascader` | 异步级联选择 | `api`, `checkStrictly` |
|
||||
| `ApiTreeSelect` | 异步树形选择 | `api`, `checkStrictly` |
|
||||
| `ApiRadioGroup` | 异步单选框组 | `api` |
|
||||
| `ApiCheckboxGroup` | 异步多选框组 | `api` |
|
||||
|
||||
### 特殊功能组件
|
||||
|
||||
| 组件 | 说明 | 常用属性 |
|
||||
| ------------ | ------------ | ----------------- |
|
||||
| `IconPicker` | 图标选择器 | - |
|
||||
| `Editor` | 富文本编辑器 | - |
|
||||
| `Upload` | 文件上传组件 | `accept`, `limit` |
|
||||
| `Avatar` | 头像上传组件 | `shape`, `size` |
|
||||
|
||||
### ApiDict 组件配置示例
|
||||
|
||||
```typescript
|
||||
{
|
||||
label: '状态',
|
||||
prop: 'status',
|
||||
component: 'ApiDict',
|
||||
componentProps: {
|
||||
code: 'yes_no', // 字典编码
|
||||
renderType: 'RadioGroup', // 渲染类型:Select/RadioGroup/Checkbox
|
||||
isBtn: true // 按钮样式(RadioGroup特有)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### ApiSelect 组件配置示例
|
||||
|
||||
```typescript
|
||||
{
|
||||
label: '用户',
|
||||
prop: 'userId',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: '/system/user/select', // 接口地址
|
||||
labelField: 'name', // 显示字段
|
||||
valueField: 'id', // 值字段
|
||||
placeholder: '请选择用户'
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 🔒 权限控制
|
||||
|
||||
### 权限码配置
|
||||
|
||||
通过以下属性控制功能权限,权限码格式为 `{模块名}:{实体名}:{操作}`:
|
||||
|
||||
| 权限属性 | 说明 | 示例 |
|
||||
| ------------ | ---------- | -------------------- |
|
||||
| `addAuth` | 新增权限码 | `system:user:create` |
|
||||
| `editAuth` | 编辑权限码 | `system:user:update` |
|
||||
| `removeAuth` | 删除权限码 | `system:user:delete` |
|
||||
| `viewAuth` | 查看权限码 | `system:user:read` |
|
||||
|
||||
### 权限配置示例
|
||||
|
||||
```typescript
|
||||
export const crudSchema: CrudSchema = {
|
||||
// ... 其他配置
|
||||
addAuth: 'system:user:create',
|
||||
removeAuth: 'system:user:delete',
|
||||
editAuth: 'system:user:update',
|
||||
viewAuth: 'system:user:read'
|
||||
// ...
|
||||
}
|
||||
```
|
||||
|
||||
## 🔄 CRUD 操作方法
|
||||
|
||||
### useCrud 返回方法
|
||||
|
||||
通过 `useCrud` 返回的方法可以控制 CRUD 行为:
|
||||
|
||||
| 方法 | 说明 | 使用场景 |
|
||||
| ----------------- | ------------- | -------------- |
|
||||
| `reload()` | 刷新表格数据 | 手动刷新 |
|
||||
| `setState()` | 设置组件状态 | 动态控制 |
|
||||
| `removeByApi()` | 通过 API 删除 | 自定义删除逻辑 |
|
||||
| `refreshData()` | 全量刷新数据 | 数据同步 |
|
||||
| `refreshCreate()` | 新增后刷新 | 新增操作后 |
|
||||
| `refreshUpdate()` | 更新后刷新 | 编辑操作后 |
|
||||
| `refreshRemove()` | 删除后刷新 | 删除操作后 |
|
||||
|
||||
### 方法使用示例
|
||||
|
||||
```typescript
|
||||
const [BasicCrud, { reload, refreshCreate }] = useCrud({
|
||||
// ... 配置
|
||||
})
|
||||
|
||||
// 手动刷新表格
|
||||
const handleRefresh = () => {
|
||||
reload()
|
||||
}
|
||||
|
||||
// 新增后刷新
|
||||
const handleCreateSuccess = () => {
|
||||
refreshCreate()
|
||||
}
|
||||
```
|
||||
|
||||
## 📚 完整示例
|
||||
|
||||
### 示例一:普通表 - 岗位管理 (sys_post)
|
||||
|
||||
#### 1. 接口文件
|
||||
|
||||
**文件路径:** `src/apps/system/api/post/index.ts`
|
||||
|
||||
```typescript
|
||||
import { BaseService } from '@/utils/request'
|
||||
import { PostRow } from '@/apps/system/api/post/types'
|
||||
|
||||
/**
|
||||
* 岗位管理 API 服务
|
||||
*/
|
||||
export const PostService = new BaseService<PostRow>('/system/post')
|
||||
|
||||
export default PostService
|
||||
```
|
||||
|
||||
#### 2. Schema 配置文件
|
||||
|
||||
**文件路径:** `src/apps/system/views/post/schemas/index.tsx`
|
||||
|
||||
```typescript
|
||||
import { CrudSchema } from '@/components/crud'
|
||||
import { PostService } from '@/apps/system/api/post'
|
||||
import { $t } from '@/locales'
|
||||
|
||||
/** 岗位管理 CRUD 配置 */
|
||||
export const crudSchema = (): CrudSchema => {
|
||||
return {
|
||||
// 基础配置
|
||||
api: PostService.page,
|
||||
dialogTitle: '岗位',
|
||||
|
||||
// API 映射
|
||||
crudApi: {
|
||||
add: PostService.save,
|
||||
edit: PostService.update,
|
||||
remove: PostService.remove,
|
||||
view: PostService.detail
|
||||
},
|
||||
|
||||
// 功能开关
|
||||
useCrud: true,
|
||||
hasAdd: true,
|
||||
hasRemove: true,
|
||||
hasEdit: true,
|
||||
hasView: true,
|
||||
|
||||
// 权限控制
|
||||
addAuth: 'system:post:create',
|
||||
removeAuth: 'system:post:delete',
|
||||
editAuth: 'system:post:update',
|
||||
viewAuth: 'system:post:read',
|
||||
|
||||
// 表格列定义
|
||||
columns: [
|
||||
{ type: 'selection' },
|
||||
{
|
||||
prop: 'name',
|
||||
label: $t('system.post.list.table.columns.name'),
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
prop: 'code',
|
||||
label: $t('system.post.list.table.columns.code'),
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
prop: 'sort',
|
||||
label: $t('system.post.list.table.columns.sort'),
|
||||
minWidth: 80
|
||||
},
|
||||
{
|
||||
prop: 'enabled',
|
||||
label: $t('system.post.list.table.columns.enabled'),
|
||||
component: 'ApiDict',
|
||||
componentProps: { code: 'yes_no' },
|
||||
minWidth: 100
|
||||
},
|
||||
{
|
||||
prop: 'remark',
|
||||
label: $t('system.post.list.table.columns.remark'),
|
||||
minWidth: 150
|
||||
}
|
||||
],
|
||||
|
||||
// 搜索表单
|
||||
searchFormSchema: {
|
||||
schema: [
|
||||
{
|
||||
prop: 'LIKE_name',
|
||||
label: $t('system.post.list.query.name'),
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
clearable: true,
|
||||
placeholder: $t('system.post.list.query.placeholder.name')
|
||||
},
|
||||
colSpan: 6
|
||||
},
|
||||
{
|
||||
prop: 'EQ_enabled',
|
||||
label: $t('system.post.list.query.enabled'),
|
||||
component: 'ApiDict',
|
||||
componentProps: {
|
||||
code: 'yes_no',
|
||||
placeholder: $t('system.post.list.query.placeholder.enabled')
|
||||
},
|
||||
colSpan: 6
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
// 弹窗配置
|
||||
dialogProps: {
|
||||
dialogType: 'drawer',
|
||||
width: '50%'
|
||||
},
|
||||
|
||||
// 表单配置
|
||||
formSchema: {
|
||||
showDefaultActions: false,
|
||||
labelWidth: '100px',
|
||||
schema: [
|
||||
{
|
||||
label: $t('system.post.form.fields.id'),
|
||||
prop: 'id',
|
||||
component: 'Input',
|
||||
colSpan: 24,
|
||||
show: false,
|
||||
ifDetail: false
|
||||
},
|
||||
{
|
||||
label: $t('system.post.form.fields.name'),
|
||||
prop: 'name',
|
||||
component: 'Input',
|
||||
rules: [{ required: true, message: $t('system.post.form.rules.name.required') }],
|
||||
componentProps: {
|
||||
placeholder: $t('system.post.form.placeholder.name')
|
||||
},
|
||||
colSpan: 24
|
||||
},
|
||||
{
|
||||
label: $t('system.post.form.fields.code'),
|
||||
prop: 'code',
|
||||
component: 'Input',
|
||||
rules: [{ required: true, message: $t('system.post.form.rules.code.required') }],
|
||||
componentProps: {
|
||||
placeholder: $t('system.post.form.placeholder.code')
|
||||
},
|
||||
colSpan: 24
|
||||
},
|
||||
{
|
||||
label: $t('system.post.form.fields.sort'),
|
||||
prop: 'sort',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
min: 0,
|
||||
placeholder: $t('system.post.form.placeholder.sort')
|
||||
},
|
||||
colSpan: 24
|
||||
},
|
||||
{
|
||||
label: $t('system.post.form.fields.enabled'),
|
||||
prop: 'enabled',
|
||||
component: 'ApiDict',
|
||||
componentProps: {
|
||||
code: 'yes_no',
|
||||
isBtn: true,
|
||||
renderType: 'RadioGroup'
|
||||
},
|
||||
rules: [{ required: true, message: $t('system.post.form.rules.enabled.required') }],
|
||||
colSpan: 24
|
||||
},
|
||||
{
|
||||
label: $t('system.post.form.fields.remark'),
|
||||
prop: 'remark',
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
type: 'textarea',
|
||||
placeholder: $t('system.post.form.placeholder.remark')
|
||||
},
|
||||
colSpan: 24
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 3. 页面入口文件
|
||||
|
||||
**文件路径:** `src/apps/system/views/post/index.vue`
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<div class="system-post-page art-full-height">
|
||||
<BasicCrud />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="tsx">
|
||||
import { useCrud } from '@/components/crud'
|
||||
import { crudSchema } from './schemas'
|
||||
|
||||
// 使用 CRUD 组件
|
||||
const [BasicCrud] = useCrud(crudSchema())
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.system-post-page {
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
```
|
||||
|
||||
### 示例二:树型表 - 部门管理 (sys_dept)
|
||||
|
||||
#### 1. 接口文件
|
||||
|
||||
**文件路径:** `src/apps/system/api/dept/index.ts`
|
||||
|
||||
```typescript
|
||||
import { BaseService } from '@/utils/request'
|
||||
import { DeptRow } from '@/apps/system/api/dept/types'
|
||||
|
||||
/**
|
||||
* 部门管理 API 服务
|
||||
*/
|
||||
export const DeptService = new BaseService<DeptRow>('/system/dept')
|
||||
|
||||
// 扩展树形接口
|
||||
export const getDeptTree = (args?: Record<string, any>) => {
|
||||
const params = { ...args, format: 'tree' }
|
||||
return DeptService.list<DeptRow[]>(params)
|
||||
}
|
||||
|
||||
export default DeptService
|
||||
```
|
||||
|
||||
#### 2. Schema 配置文件
|
||||
|
||||
**文件路径:** `src/apps/system/views/dept/schemas/index.tsx`
|
||||
|
||||
```tsx
|
||||
import { CrudSchema } from '@/components/crud'
|
||||
import { DeptService, getDeptTree } from '@/apps/system/api/dept'
|
||||
import { $t } from '@/locales'
|
||||
|
||||
/** 部门管理 CRUD 配置 */
|
||||
export const crudSchema = (): CrudSchema => {
|
||||
return {
|
||||
// 接口配置:使用 tree 接口以支持树形结构
|
||||
api: getDeptTree,
|
||||
|
||||
// CRUD API 映射
|
||||
crudApi: {
|
||||
add: DeptService.save,
|
||||
edit: DeptService.update,
|
||||
remove: DeptService.remove,
|
||||
view: DeptService.detail
|
||||
},
|
||||
|
||||
// 功能开关
|
||||
useCrud: true,
|
||||
hasAdd: true,
|
||||
hasRemove: true,
|
||||
hasEdit: true,
|
||||
hasView: true,
|
||||
|
||||
// 权限控制
|
||||
addAuth: 'system:dept:create',
|
||||
removeAuth: 'system:dept:delete',
|
||||
editAuth: 'system:dept:update',
|
||||
viewAuth: 'system:dept:read',
|
||||
|
||||
// 禁用搜索表单,因为树形结构是全量数据
|
||||
useSearchForm: false,
|
||||
|
||||
// 树形结构配置
|
||||
tree: {
|
||||
id: 'id',
|
||||
pid: 'pid',
|
||||
children: 'children'
|
||||
},
|
||||
|
||||
columns: [
|
||||
{
|
||||
type: 'selection'
|
||||
},
|
||||
{
|
||||
prop: 'name',
|
||||
label: $t('system.dept.list.table.columns.name'),
|
||||
component: 'Input',
|
||||
minWidth: 150
|
||||
},
|
||||
{
|
||||
prop: 'code',
|
||||
label: $t('system.dept.list.table.columns.code'),
|
||||
component: 'Input',
|
||||
minWidth: 120
|
||||
},
|
||||
{
|
||||
prop: 'sort',
|
||||
label: $t('system.dept.list.table.columns.sort'),
|
||||
component: 'InputNumber',
|
||||
minWidth: 80
|
||||
},
|
||||
{
|
||||
prop: 'enabled',
|
||||
label: $t('system.dept.list.table.columns.enabled'),
|
||||
component: 'ApiDict',
|
||||
componentProps: {
|
||||
code: 'yes_no',
|
||||
renderType: 'RadioGroup'
|
||||
},
|
||||
minWidth: 100
|
||||
}
|
||||
],
|
||||
|
||||
searchFormSchema: {
|
||||
schema: []
|
||||
},
|
||||
|
||||
// 弹窗配置
|
||||
dialogProps: {
|
||||
dialogType: 'drawer',
|
||||
width: '50%'
|
||||
},
|
||||
|
||||
// 表单配置
|
||||
formSchema: {
|
||||
showDefaultActions: false,
|
||||
labelWidth: '100px',
|
||||
schema: [
|
||||
{
|
||||
label: $t('system.dept.form.fields.id'),
|
||||
prop: 'id',
|
||||
component: 'Input',
|
||||
colSpan: 24,
|
||||
show: false,
|
||||
ifDetail: false
|
||||
},
|
||||
{
|
||||
label: $t('system.dept.form.fields.pid'),
|
||||
prop: 'pid',
|
||||
component: 'ApiTreeSelect',
|
||||
componentProps: {
|
||||
api: '/system/dept/tree',
|
||||
labelField: 'name',
|
||||
valueField: 'id',
|
||||
// 任意节点可选
|
||||
checkStrictly: true,
|
||||
clearable: true,
|
||||
placeholder: '请选择父级部门'
|
||||
},
|
||||
colSpan: 24,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择父级部门'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: '部门名称',
|
||||
prop: 'name',
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
placeholder: '请输入部门名称'
|
||||
},
|
||||
colSpan: 24,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入部门名称'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: '唯一编码',
|
||||
prop: 'code',
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
placeholder: '请输入唯一编码'
|
||||
},
|
||||
colSpan: 24,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入唯一编码'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: '排序',
|
||||
prop: 'sort',
|
||||
component: 'InputNumber',
|
||||
componentProps: {
|
||||
min: 0,
|
||||
max: 9999
|
||||
},
|
||||
colSpan: 24,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请输入排序值'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: '是否启用',
|
||||
prop: 'enabled',
|
||||
component: 'ApiDict',
|
||||
componentProps: {
|
||||
code: 'yes_no',
|
||||
isBtn: true,
|
||||
renderType: 'RadioGroup'
|
||||
},
|
||||
colSpan: 24,
|
||||
rules: [
|
||||
{
|
||||
required: true,
|
||||
message: '请选择启用状态'
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
label: '部门负责人',
|
||||
prop: 'leaderIdList',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: '/system/user/select',
|
||||
placeholder: '请选择负责人',
|
||||
multiple: true
|
||||
},
|
||||
colSpan: 24
|
||||
},
|
||||
{
|
||||
label: '分管领导',
|
||||
prop: 'main_leader_id',
|
||||
component: 'ApiSelect',
|
||||
componentProps: {
|
||||
api: '/system/user/select',
|
||||
placeholder: '请选择分管领导'
|
||||
},
|
||||
colSpan: 24
|
||||
},
|
||||
{
|
||||
label: '备注',
|
||||
prop: 'remark',
|
||||
component: 'Input',
|
||||
componentProps: {
|
||||
type: 'textarea',
|
||||
rows: 3,
|
||||
placeholder: '请输入备注'
|
||||
},
|
||||
colSpan: 24
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 3. 页面入口文件
|
||||
|
||||
**文件路径:** `src/apps/system/views/dept/index.vue`
|
||||
|
||||
```vue
|
||||
<template>
|
||||
<div class="sys-dept-page art-full-height">
|
||||
<BasicCrud />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup lang="tsx">
|
||||
import { useCrud } from '@/components/crud'
|
||||
import { crudSchema } from './schemas'
|
||||
|
||||
const [BasicCrud] = useCrud(crudSchema())
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.sys-dept-page {
|
||||
height: 100%;
|
||||
}
|
||||
</style>
|
||||
```
|
||||
@@ -0,0 +1,650 @@
|
||||
# Changelog
|
||||
|
||||
All notable changes to this project will be documented in this file.
|
||||
|
||||
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
||||
|
||||
## [Released]
|
||||
|
||||
### v3.0.1 - 2025-11-15
|
||||
|
||||
#### Fixed
|
||||
|
||||
- Fixed repeated API calls issue when accessing non-existent interfaces during route registration
|
||||
- Fixed one-click cleanup script build failure issue
|
||||
|
||||
#### Optimized
|
||||
|
||||
- Enhanced route configuration validation mechanism, automatically detects and prompts path configuration errors for non-first-level menus
|
||||
- Optimized top progress bar ghosting issue
|
||||
- Optimized vite pre-build configuration
|
||||
- Unified border radius and border consistency
|
||||
- Redesigned lock screen page, improved visual experience
|
||||
- Optimized visual experience issue caused by logout menu disappearing prematurely
|
||||
|
||||
#### Added
|
||||
|
||||
- ArtForm and ArtSearchBar component labels support custom render functions, enabling complex labels with tooltips
|
||||
- Menu management form key fields added Tooltip hints, lowering user configuration threshold
|
||||
- Iconify added offline icon loading mode
|
||||
- Logout added redirect property for redirecting to corresponding page after re-login
|
||||
- Logout and re-entry preserves user tabs, identified by userId
|
||||
- Dual menu added collapse button
|
||||
- Enhanced menu icons
|
||||
- Multi-tab pages added icon display
|
||||
- Added WebSocket connection example
|
||||
- Lock screen password error triggers input box shake animation
|
||||
|
||||
#### ⚠️ Important Notice
|
||||
|
||||
> This version requires re-login
|
||||
|
||||
---
|
||||
|
||||
### v3.0.0 - 2025-11-09 🎉
|
||||
|
||||
#### Refactored
|
||||
|
||||
- Style system refactored: Sass fully migrated to Tailwind CSS, improving development efficiency and style consistency
|
||||
- Icon solution upgraded: Iconfont replaced with Iconify, supporting richer icon libraries and on-demand loading
|
||||
- Route registration refactored: Comprehensive refactoring of route registration system with object-oriented design, improving code maintainability, testability and extensibility
|
||||
- Refactored ArtTextScroll component, improving performance and maintainability
|
||||
- Refactored color system, unified UI visual specifications, improving interface consistency
|
||||
|
||||
#### Optimized
|
||||
|
||||
- Build optimization: Complete package size reduced by 1.3 MB, significantly improving loading performance
|
||||
- Architecture optimization: Optimized directory structure with clearer responsibility division, reducing user learning curve and onboarding difficulty
|
||||
- Comment optimization: Unified module comment standards, improved component descriptions, feature explanations and usage examples, reducing user understanding cost and onboarding difficulty
|
||||
- Performance improvement: Optimized core code logic, improving system runtime efficiency
|
||||
- Menu optimization: Refined menu styles, optimized interaction experience and visual presentation
|
||||
- Responsive optimization: Optimized ArtForm and ArtSearchBar grid layout, adapting to various screen sizes
|
||||
- Configuration management optimization: Added setting.ts configuration file, supporting one-click copy and reset of system default settings
|
||||
|
||||
#### Fixed
|
||||
|
||||
- Fixed ArtForm and ArtSearchBar custom component rendering issues
|
||||
|
||||
#### Added
|
||||
|
||||
- ArtForm and ArtSearchBar added render property, supporting custom component rendering
|
||||
- Festival feature enhancement: Fireworks configuration supports cross-date range settings and custom playback counts
|
||||
|
||||
#### Upgraded
|
||||
|
||||
- Dependency updates: Upgraded core dependencies to latest stable versions
|
||||
|
||||
#### ⚠️ Important Notice
|
||||
|
||||
> This upgrade involves fundamental refactoring of the style system (Sass → Tailwind CSS) and icon library (Iconfont → Iconify), which is a breaking change. It is recommended to use v3.0 for new projects. Upgrading existing projects is not recommended.
|
||||
|
||||
---
|
||||
|
||||
### v2.6.1 - 2025-10-19
|
||||
|
||||
#### Fixed
|
||||
|
||||
- Fixed repeated API calls issue when accessing invalid addresses for user info and menu endpoints
|
||||
- Fixed ElButton circle mode styles
|
||||
- Fixed ElSelect keyboard selection issue
|
||||
- Fixed static routes with parameters redirecting to login page issue
|
||||
|
||||
#### Optimized
|
||||
|
||||
- Upgraded some dependencies to be compatible with tailwindcss
|
||||
- Optimized external link menu click selection state
|
||||
- Added theme color switching feature to authorization pages
|
||||
|
||||
---
|
||||
|
||||
### v2.6.0 - 2025-10-16
|
||||
|
||||
#### Added
|
||||
|
||||
- Extended registration and password reset page top component support
|
||||
|
||||
#### Optimized
|
||||
|
||||
- Optimized streamlined version menu data structure, improved data consistency
|
||||
- Optimized local development environment network request proxy configuration
|
||||
- Optimized ElTree component default styles
|
||||
- Added VsCode recommended plugin related configuration
|
||||
- Optimized ElDropdown component click trigger mode interaction styles
|
||||
- Optimized menu filtering logic
|
||||
- Optimized page switching animation, improved loading speed
|
||||
- Optimized dark mode text color
|
||||
|
||||
#### Fixed
|
||||
|
||||
- Fixed static route custom home page path first visit redirecting to login page issue
|
||||
- Fixed logout briefly jumping to 500 page issue
|
||||
- Fixed v2.5.9 version home route redirect configuration failure issue
|
||||
- Fixed v2.5.9 auto-import mechanism causing build exception
|
||||
|
||||
#### ⚠️ Important Notice
|
||||
|
||||
> This version requires re-login
|
||||
|
||||
---
|
||||
|
||||
### v2.5.9 - 2025-10-12
|
||||
|
||||
#### Code Optimized
|
||||
|
||||
- Optimized views file directory, file names, and code
|
||||
- Added global configuration for useTable pagination request fields (tableConfig.ts)
|
||||
- Optimized routing configuration to modular structure
|
||||
- Menu API now uses apifox mock data (set VITE_ACCESS_MODE to backend mode in .env)
|
||||
|
||||
---
|
||||
|
||||
### v2.5.8 - 2025-09-29
|
||||
|
||||
#### Upgraded
|
||||
|
||||
- Upgraded core libraries including vue, vite, element-plus
|
||||
|
||||
#### Fixed
|
||||
|
||||
- Fixed rich text editor fullscreen top bar z-index issue
|
||||
- Fixed table column sorting component text overflow issue
|
||||
- Fixed statistics card condition judgment
|
||||
- Fixed infinite redirect when root path / and HOME_PAGE_PATH are both /
|
||||
|
||||
#### Optimized
|
||||
|
||||
- Optimized el-tag styles
|
||||
- Optimized top progress bar color
|
||||
- Optimized custom theme configuration
|
||||
- Optimized ElementPlus custom theme issues
|
||||
|
||||
#### ⚠️ Important Notice
|
||||
|
||||
> Due to dependency upgrades, Node version needs to be upgraded to v20.19.0 or above
|
||||
|
||||
---
|
||||
|
||||
### v2.5.7 - 2025-09-14
|
||||
|
||||
#### Added
|
||||
|
||||
- Added ArtForm component
|
||||
|
||||
#### Fixed
|
||||
|
||||
- Fixed theme switching flicker issue in new Chrome versions
|
||||
|
||||
#### Optimized
|
||||
|
||||
- Optimized form label height alignment issue
|
||||
- First screen startup performance optimization
|
||||
|
||||
---
|
||||
|
||||
### v2.5.6 - 2025-09-06
|
||||
|
||||
#### Added
|
||||
|
||||
- ArtTableHeader added search button to control top search bar visibility
|
||||
- Added permission demonstration examples
|
||||
- Added global error handling framework
|
||||
|
||||
#### Optimized
|
||||
|
||||
- useTable type inference optimization, no need to manually pass types for type hints
|
||||
- useTable removeColumn supports multiple data deletion
|
||||
- useTable auto-recognizes response body with custom configuration (src/utils/table/tableConfig.ts)
|
||||
- useTable empty data browser warning optimization
|
||||
- API request code optimization, api.d.ts type optimization
|
||||
- Optimized ArtTable top button wrapping adaptive issue
|
||||
- ArtTable pagination component selection style optimization
|
||||
- ArtTable empty state height fills by default
|
||||
- ArtButtonMore component added icon and color configuration
|
||||
- ArtSearchBar label doesn't occupy space when empty
|
||||
- Table operation column drag prohibits fixed column dragging
|
||||
- Role management page API integration and code optimization
|
||||
- Menu management page optimization
|
||||
- Optimized settings center scroll following issue
|
||||
- First-level route external link component validation logic optimization
|
||||
- Optimized map bottom-right drag issue
|
||||
- Optimized dark mode page refresh white background issue
|
||||
- Optimized left menu collapse button spacing issue
|
||||
- Mobile displays left menu logo
|
||||
- Network request added showSuccessMessage to configure success message display
|
||||
- Global components use async loading strategy to improve first screen loading performance
|
||||
|
||||
#### Fixed
|
||||
|
||||
- Fixed bug where batch deleting entire page data doesn't return to previous page
|
||||
- Fixed dynamic route parameter issues
|
||||
- Fixed dynamic route configuration first-level route iframe page fullscreen issue
|
||||
|
||||
---
|
||||
|
||||
### v2.5.5 - 2025-08-17
|
||||
|
||||
#### Added
|
||||
|
||||
- Added ArtSearchBar component examples
|
||||
- useTable added excludeParams to exclude certain parameters from requests
|
||||
|
||||
#### Refactored
|
||||
|
||||
- Refactored ArtSearchBar component, supports more components and form validation
|
||||
|
||||
#### Optimized
|
||||
|
||||
- useTable column configuration: supports dynamic update capability
|
||||
- Optimized color picker border radius
|
||||
- Unified el-radio and el-checkbox sizes
|
||||
- art-stats-card added decimal places and separator configuration
|
||||
- Route configuration example optimization
|
||||
- Advanced table added custom data fetching example
|
||||
- Optimized path alias type issues
|
||||
- Local development CORS configuration optimization
|
||||
- useTable property and method naming optimization
|
||||
- Login page UI upgrade
|
||||
- 403, 404, 500 page UI upgrade
|
||||
|
||||
#### Fixed
|
||||
|
||||
- Fixed multiple rich text editor icon inconsistency issue
|
||||
- Fixed useTable deleting last entire page data not returning to previous page issue
|
||||
- Fixed echarts chart data initialization and update browser error
|
||||
- Network request supports logout on HTTP status code 401
|
||||
- Optimized network request logout multiple prompt issue
|
||||
|
||||
#### Removed
|
||||
|
||||
- Removed art-chart-empty component
|
||||
|
||||
---
|
||||
|
||||
### v2.5.4 - 2025-07-27
|
||||
|
||||
#### Added
|
||||
|
||||
- Network request headers support custom configuration
|
||||
- Expand row supports formatter rendering
|
||||
|
||||
#### Optimized
|
||||
|
||||
- Route registration added component validation
|
||||
|
||||
#### Fixed
|
||||
|
||||
- Fixed user info API timing issue causing route registration menu rendering error
|
||||
- Fixed dynamic route validation issue causing iframe not to display
|
||||
- Fixed reset file syntax error
|
||||
- Fixed ArtTable data type error
|
||||
- Fixed map scroll wheel zoom issue
|
||||
|
||||
---
|
||||
|
||||
### v2.5.3 - 2025-07-20
|
||||
|
||||
#### Added
|
||||
|
||||
- Added table left-right layout example
|
||||
|
||||
#### Refactored
|
||||
|
||||
- ArtTable component refactored
|
||||
|
||||
#### Upgraded
|
||||
|
||||
- Element Plus upgraded to v2.10.2
|
||||
|
||||
#### Optimized
|
||||
|
||||
- Optimized useTable pagination parameter issue
|
||||
- Optimized network request example: initialization parameters, pagination carrying parameters
|
||||
- Optimized search date range parameter handling
|
||||
- Optimized el-date-picker component border radius
|
||||
- Optimized el-select component hover style
|
||||
- Search component and pagination component height reduced
|
||||
- Optimized login page slider animation interval
|
||||
- Optimized menu without submenu display issue
|
||||
|
||||
#### Fixed
|
||||
|
||||
- Fixed ArtTable switching page size executing two requests bug
|
||||
|
||||
---
|
||||
|
||||
### v2.5.2 - 2025-07-13
|
||||
|
||||
#### Added
|
||||
|
||||
- Added one-click cleanup script for quick development environment preparation
|
||||
- useTable hooks support custom pagination field name mapping
|
||||
- Horizontal menu, mixed menu, double column menu support badge display
|
||||
- Quick entry supports configuration file mode
|
||||
- Top bar function supports configuration file mode
|
||||
- Support custom home page path
|
||||
- Route supports redirect and other attributes configuration
|
||||
|
||||
#### Optimized
|
||||
|
||||
- Global event bus mittBus type safety optimization
|
||||
- Optimized mobile settings container width style
|
||||
- Optimized login page verification slider text centering effect
|
||||
|
||||
#### Fixed
|
||||
|
||||
- Fixed table no data header not displaying issue
|
||||
- Fixed v2.5.0 top progress bar not displaying issue
|
||||
- Fixed left menu mask abnormal display issue
|
||||
- Fixed hiding all submenus still showing parent menu issue
|
||||
- Fixed stylelint causing login page slider style abnormality
|
||||
- Fixed old mobile device loading positioning issue
|
||||
|
||||
---
|
||||
|
||||
### v2.5.1 - 2025-07-08
|
||||
|
||||
#### Optimized
|
||||
|
||||
- el-card and el-table background color consistent with system
|
||||
|
||||
#### Fixed
|
||||
|
||||
- Fixed first login system loading closing early bug
|
||||
- Fixed v2.5.0 version causing fullscreen page style z-index too low bug
|
||||
- Fixed v2.5.0 version causing table expand row collapse bug
|
||||
|
||||
---
|
||||
|
||||
### v2.5.0 - 2025-07-06 🎉
|
||||
|
||||
#### Added
|
||||
|
||||
- Added useTable hooks table encapsulation, supports data fetching, transformation, response adaptation, intelligent caching (based on LRU algorithm), error handling, column configuration and slots, pagination control, refresh strategies and other core functions, comprehensively improving development efficiency and user experience
|
||||
|
||||
#### Refactored
|
||||
|
||||
- Refactored ArtTable, ArtTableHeader, ArtNotification components
|
||||
|
||||
#### Upgraded
|
||||
|
||||
- Echarts version upgraded to 5.6.0
|
||||
|
||||
#### Removed
|
||||
|
||||
- Removed CountTo plugin, replaced with ArtCountTo component
|
||||
|
||||
#### Fixed
|
||||
|
||||
- Fixed menu management search directly modifying pinia data issue
|
||||
- Fixed route guard loading flicker issue
|
||||
|
||||
#### ⚠️ Important Notice
|
||||
|
||||
> Recommended upgrade for more efficient and intelligent table development experience
|
||||
|
||||
---
|
||||
|
||||
### v2.4.2.9 - 2025-07-02
|
||||
|
||||
#### Refactored
|
||||
|
||||
- Menu layout and top navigation code refactored
|
||||
|
||||
#### Optimized
|
||||
|
||||
- Optimized mobile menu scroll user experience
|
||||
- Optimized top menu style issues
|
||||
- Top menu width adaptive, can display more content, mixed menu supports mouse scroll
|
||||
- asyncRoutes route configuration auth_mark field changed to authMark
|
||||
- Removed duplicate components.d.ts file, components.d.ts and auto-imports.d.ts ignored from commits
|
||||
- Optimized i18n language file loading method, changed from async to sync mode
|
||||
- Optimized el-pagination size inconsistency issue
|
||||
|
||||
#### Fixed
|
||||
|
||||
- Fixed mobile lock screen page some browsers unable to unlock bug
|
||||
|
||||
---
|
||||
|
||||
### v2.4.2.8 - 2025-06-26
|
||||
|
||||
#### Fixed
|
||||
|
||||
- Fixed v2.4.2.7 version accessing / path showing 404 issue
|
||||
|
||||
---
|
||||
|
||||
### v2.4.2.7 - 2025-06-25
|
||||
|
||||
#### Added
|
||||
|
||||
- Route supports fullscreen mode configuration
|
||||
- Route supports auto-jump to first valid route of menu
|
||||
- Dynamic route added removeAllDynamicRoutes method for completely clearing all dynamic routes
|
||||
- Permission custom directive optimization, added role permission directive v-roles for controlling element visibility
|
||||
- Added tab operation examples
|
||||
|
||||
#### Optimized
|
||||
|
||||
- CORS request carrying cookie configuration from environment variables, disabled by default
|
||||
- Some optimizations for SEO and accessibility
|
||||
|
||||
#### Fixed
|
||||
|
||||
- Fixed login page drag component ArtDragVerify width and color abnormal bug
|
||||
- Fixed iframe page mixed mode and double column mode abnormal bug
|
||||
- Optimized lock screen page being penetrated by el-loading bug
|
||||
|
||||
---
|
||||
|
||||
### v2.4.2.6 - 2025-06-23
|
||||
|
||||
#### Refactored
|
||||
|
||||
- Refactored form-related components in components/core/forms folder, improved maintainability and consistency
|
||||
- Refactored ArtBreadcrumb breadcrumb navigation component, optimized logic structure and styles
|
||||
- Refactored ArtFireworksEffect fireworks effect component, significantly improved rendering performance and animation smoothness
|
||||
|
||||
#### Optimized
|
||||
|
||||
- Optimized ArtChatWindow and ArtFastEnter component code, improved readability and performance
|
||||
|
||||
#### Added
|
||||
|
||||
- README documentation added official website link for easy access to project documentation
|
||||
|
||||
---
|
||||
|
||||
### v2.4.2.5 - 2025-06-22
|
||||
|
||||
#### Refactored
|
||||
|
||||
- Refactored chart components, optimized code structure and maintainability
|
||||
- Fine-tuned chart animations and theme color schemes, improved visual consistency
|
||||
|
||||
---
|
||||
|
||||
### v2.4.2.4 - 2025-06-18
|
||||
|
||||
#### Refactored
|
||||
|
||||
- ArtMenuRight component refactored
|
||||
- Components under components/core/cards refactored, code optimized
|
||||
|
||||
#### Optimized
|
||||
|
||||
- ArtWatermark added type annotations
|
||||
|
||||
---
|
||||
|
||||
### v2.4.2.3 - 2025-06-18
|
||||
|
||||
#### Refactored
|
||||
|
||||
- ArtResultPage component refactored
|
||||
|
||||
#### Optimized
|
||||
|
||||
- ArtTextScroll component code optimization
|
||||
- ArtException component added type hints
|
||||
- ArtCutterImg component style optimization, added type definitions
|
||||
- ArtVideoPlayer component added type definitions
|
||||
|
||||
---
|
||||
|
||||
### v2.4.2.2 - 2025-06-16
|
||||
|
||||
#### Refactored
|
||||
|
||||
- Back to top component refactored
|
||||
- Icon selector component refactored
|
||||
|
||||
#### Changed
|
||||
|
||||
- System Logo component property changes
|
||||
|
||||
---
|
||||
|
||||
### v2.4.2.1 - 2025-06-16
|
||||
|
||||
#### Refactored
|
||||
|
||||
- Banner component refactored and optimized
|
||||
|
||||
#### Fixed
|
||||
|
||||
- Fixed mixed menu first menu nested menu jump bug
|
||||
|
||||
---
|
||||
|
||||
### v2.4.2 - 2025-06-14
|
||||
|
||||
#### Refactored
|
||||
|
||||
- Refactored network request module, enhanced error handling, type safety and multi-language support
|
||||
|
||||
#### Added
|
||||
|
||||
- Route configuration added activePath active menu path property
|
||||
|
||||
#### Optimized
|
||||
|
||||
- Guide and column settings multi-language improvement
|
||||
- Removed invalid code from user list and menu management pages
|
||||
- Updated technical support link
|
||||
|
||||
#### Fixed
|
||||
|
||||
- Fixed mobile search bar unable to scroll, iPad page scroll abnormal issue
|
||||
- Fixed el-dialog enabling draggable property causing custom animation failure issue
|
||||
- Fixed v2.3.0 local storage refactoring causing login, registration page multi-language settings unable to persist issue
|
||||
- Fixed table fixed column not working bug
|
||||
- Fixed infinite redirect when root path / and HOME_PAGE_PATH are both /
|
||||
|
||||
#### ⚠️ Important Notice
|
||||
|
||||
> This version requires re-login
|
||||
|
||||
---
|
||||
|
||||
### v2.4.1.1 - 2025-06-07
|
||||
|
||||
#### Optimized
|
||||
|
||||
- Optimized role management page code
|
||||
- el-dialog visual effect optimization, supports line configuration
|
||||
- System theme mode changed from Light to follow system mode
|
||||
|
||||
#### Fixed
|
||||
|
||||
- Fixed menu management collapse bug
|
||||
- Fixed table empty data height infinitely growing bug
|
||||
|
||||
---
|
||||
|
||||
### v2.4.1 - 2025-06-07
|
||||
|
||||
#### Added
|
||||
|
||||
- Prohibit using developer tools to crack lock screen when locked
|
||||
|
||||
#### Optimized
|
||||
|
||||
- Improved menu operation responsiveness
|
||||
- Page entrance animation time reduced by 0.04s
|
||||
- Echarts chart performance optimization, added visible area initialization, memory leak protection, debounce handling
|
||||
|
||||
#### Fixed
|
||||
|
||||
- Fixed Echarts chart component not displaying in dialog bug
|
||||
|
||||
---
|
||||
|
||||
### v2.4.0 - 2025-06-06 🎉
|
||||
|
||||
#### Refactored
|
||||
|
||||
- Global TypeScript type system refactored, improved type accuracy and maintainability
|
||||
- Refactored utils toolkit, unified utility method structure, enhanced readability and reusability
|
||||
|
||||
#### Added
|
||||
|
||||
- utils added form validation and Cookie operation related utility functions
|
||||
- Page components added defineOptions for explicit component naming
|
||||
- HTTP request added token expiration auto-handling logic, improved security and user experience
|
||||
|
||||
#### Optimized
|
||||
|
||||
- Deleted unused utility modules and invalid resources, streamlined project size
|
||||
- Optimized views page structure, removed redundant page files
|
||||
- Exception page multi-language support, improved internationalization experience
|
||||
- Image resources uniformly converted to webp format, overall resource size reduced by about 50%
|
||||
- Build output reduced by about 1MB, improved loading efficiency
|
||||
|
||||
#### ⚠️ Important Notice
|
||||
|
||||
> This version requires re-login
|
||||
|
||||
---
|
||||
|
||||
## Version Guidelines
|
||||
|
||||
### Version Number Rules
|
||||
|
||||
This project follows [Semantic Versioning 2.0.0](https://semver.org/spec/v2.0.0.html):
|
||||
|
||||
- **Major**: Incompatible API changes
|
||||
- **Minor**: Backwards-compatible functionality additions
|
||||
- **Patch**: Backwards-compatible bug fixes
|
||||
|
||||
### Change Types
|
||||
|
||||
- **Added**: New features
|
||||
- **Changed**: Changes to existing functionality
|
||||
- **Deprecated**: Soon-to-be removed features
|
||||
- **Removed**: Removed features
|
||||
- **Fixed**: Bug fixes
|
||||
- **Security**: Security-related fixes
|
||||
- **Optimized**: Performance or experience optimizations
|
||||
- **Refactored**: Code refactoring
|
||||
- **Upgraded**: Dependency upgrades
|
||||
|
||||
### Important Notice Markers
|
||||
|
||||
- 🎉 **Major Update**: Versions with important new features
|
||||
- ⚠️ **Breaking Changes**: Updates requiring special attention for incompatibility
|
||||
- 🔒 **Security Update**: Versions fixing security vulnerabilities
|
||||
|
||||
---
|
||||
|
||||
## Contributing
|
||||
|
||||
If you want to contribute to the project, please check [CONTRIBUTING.md](./CONTRIBUTING.md).
|
||||
|
||||
## License
|
||||
|
||||
[MIT License](./LICENSE)
|
||||
|
||||
---
|
||||
|
||||
**Note**: This changelog starts recording from version v2.4.0. For earlier version history, please check Git commit records.
|
||||
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2025 SuperManTT
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
@@ -0,0 +1,106 @@
|
||||
# Madong UI Admin Art Design
|
||||
|
||||
A modern admin backend frontend framework based on Vue 3 + Vite, designed specifically for MD-Admin 5.x backend services. Developed with Composition API and TypeScript, providing responsive layout and complete permission management functionality.
|
||||
|
||||
## Project Introduction
|
||||
|
||||
Madong UI Admin Art Design is a feature-complete, aesthetically pleasing admin backend frontend solution designed to provide developers with the ability to quickly build enterprise-level management systems.
|
||||
|
||||
## Core Features
|
||||
|
||||
- 🎨 Modern design style with modular architecture
|
||||
- 📊 Rich data visualization components based on ECharts
|
||||
- 🛡 Complete permission control system with granular permission management
|
||||
- 📱 Responsive layout supporting mobile access
|
||||
- 🌐 Multi-language support (Chinese/English)
|
||||
- 📦 Integrated common business modules (user, role, menu management, etc.)
|
||||
- 📝 Rich text editor integration
|
||||
- 📁 File upload and download functionality
|
||||
- 🕒 Comprehensive log management system
|
||||
- 🔧 Comprehensive CRUD feature templates
|
||||
|
||||
## Technology Stack
|
||||
|
||||
- **Frontend Framework**: Vue 3 + TypeScript + Vite
|
||||
- **UI Component Library**: Element Plus
|
||||
- **Routing Management**: Vue Router 4
|
||||
- **State Management**: Pinia
|
||||
- **HTTP Client**: Axios
|
||||
- **Data Visualization**: ECharts
|
||||
- **CSS Preprocessor**: Sass
|
||||
|
||||
## Directory Structure
|
||||
|
||||
```bash
|
||||
├── public/ # Static assets
|
||||
├── src/ # Main application directory
|
||||
│ ├── apps/ # Feature modules directory
|
||||
│ │ ├── demo/ # Demo module
|
||||
│ │ └── official/ # Official module
|
||||
│ ├── assets/ # Static resource files
|
||||
│ ├── components/ # Common components
|
||||
│ ├── config/ # Configuration files
|
||||
│ ├── locales/ # Multi-language files
|
||||
│ ├── router/ # Routing configuration
|
||||
│ ├── store/ # State management
|
||||
│ ├── types/ # Type definitions
|
||||
│ ├── lang/ # Multi-language files
|
||||
│ ├── api/ # API definitions
|
||||
│ ├── views/ # Page views
|
||||
│ ├── utils/ # Utility functions
|
||||
│ └── main.ts # Entry file
|
||||
├── .env # Environment variables configuration
|
||||
└── vite.config.ts # Vite configuration
|
||||
```
|
||||
|
||||
## Quick Start
|
||||
|
||||
1. **Install dependencies**
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
```
|
||||
|
||||
2. **Start the development server**
|
||||
|
||||
```bash
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
3. **Build for production**
|
||||
|
||||
```bash
|
||||
pnpm build
|
||||
```
|
||||
|
||||
4. **Preview production build**
|
||||
|
||||
```bash
|
||||
pnpm preview
|
||||
```
|
||||
|
||||
## Usage Instructions
|
||||
|
||||
- **System Configuration**: `src/config/index.ts`
|
||||
- **Routing Configuration**: `src/router/index.ts`
|
||||
- **State Management**: `src/store/index.ts`
|
||||
- **API Definitions**: `src/api/` directory
|
||||
- **Component Usage**: `src/components/` directory
|
||||
- **Page Development**: `src/app/views/` directory
|
||||
- **CRUD Templates**: Refer to `AI_CONTEXT.md` file
|
||||
|
||||
## Development Guidelines
|
||||
|
||||
- Use TypeScript for type checking
|
||||
- Follow Vue 3 Composition API best practices
|
||||
- Use PascalCase for component naming
|
||||
- Use kebab-case for file naming
|
||||
- Follow project directory structure conventions
|
||||
|
||||
## Contribution Guidelines
|
||||
|
||||
Contributions and issue feedback are welcome! Please read the CONTRIBUTING.md document first.
|
||||
|
||||
## License
|
||||
|
||||
This project is licensed under the MIT License. See the LICENSE file for details.
|
||||
@@ -0,0 +1,112 @@
|
||||
# Madong-Vue
|
||||
|
||||
基于 Vue 3 + Vite 的现代化管理后台前端框架,专为对接 MD-Admin 5.x 版本后端服务设计。采用 Composition API 和 TypeScript 开发,提供响应式布局和完整的权限管理功能。
|
||||
|
||||
## 项目介绍
|
||||
|
||||
Madong-Vue 是一个基于 Vue 3 + Vite 的现代化管理后台前端框架,专为对接 MD-Admin 5.x完整界面后台前端,旨在为开发者提供快速构建企业级管理系统的能力。
|
||||
|
||||
## 核心特性
|
||||
|
||||
- 🎨 现代化设计风格,模块化架构
|
||||
- 📊 丰富的数据可视化组件,基于 ECharts
|
||||
- 🛡 完整的权限控制系统,细粒度权限管理
|
||||
- 📱 响应式布局,支持移动端访问
|
||||
- 🌐 多语言支持(中文/英文)
|
||||
- 📦 集成常用业务模块(用户、角色、菜单等管理)
|
||||
- 📝 富文本编辑器集成
|
||||
- 📁 文件上传下载功能
|
||||
- 🕒 全面的日志管理系统
|
||||
- 🔧 完善的 CRUD 功能模板
|
||||
|
||||
## 技术栈
|
||||
|
||||
- **前端框架**:Vue 3 + TypeScript + Vite
|
||||
- **UI 组件库**:Element Plus
|
||||
- **路由管理**:Vue Router 4
|
||||
- **状态管理**:Pinia
|
||||
- **网络请求**:Axios
|
||||
- **数据可视化**:ECharts
|
||||
- **样式预处理器**:Sass
|
||||
|
||||
## 目录结构
|
||||
|
||||
```bash
|
||||
├── public/ # 静态资源
|
||||
├── src/ # 主应用目录
|
||||
│ ├── apps/ # 功能模块目录
|
||||
│ │ ├── demo/ # 示例模块
|
||||
│ │ └── official/ # 官方模块
|
||||
│ ├── assets/ # 静态资源文件
|
||||
│ ├── components/ # 通用组件
|
||||
│ ├── config/ # 配置文件
|
||||
│ ├── locales/ # 多语言
|
||||
│ ├── router/ # 路由配置
|
||||
│ ├── store/ # 状态管理
|
||||
│ ├── types/ # 类型定义
|
||||
│ ├── utils/ # 工具函数
|
||||
│ ├── lang/ # 多语言文件
|
||||
│ ├── api/ # 接口定义
|
||||
│ ├── views/ # 页面视图
|
||||
│ └── main.ts # 入口文件
|
||||
├── .env # 环境变量配置
|
||||
└── vite.config.ts # Vite 配置
|
||||
```
|
||||
|
||||
## 快速开始
|
||||
|
||||
1. **安装依赖**
|
||||
|
||||
```bash
|
||||
pnpm install
|
||||
```
|
||||
|
||||
2. **启动开发服务器**
|
||||
|
||||
```bash
|
||||
pnpm dev
|
||||
```
|
||||
|
||||
3. **构建生产环境包**
|
||||
|
||||
```bash
|
||||
pnpm build
|
||||
```
|
||||
|
||||
4. **预览生产构建**
|
||||
|
||||
```bash
|
||||
pnpm preview
|
||||
```
|
||||
|
||||
## 使用说明
|
||||
|
||||
- **系统配置**:`src/config/index.ts`
|
||||
- **路由配置**:`src/router/index.ts`
|
||||
- **状态管理**:`src/store/index.ts`
|
||||
- **接口定义**:`src/api/` 目录
|
||||
- **组件使用**:`src/components/` 目录
|
||||
- **页面开发**:`src/app/views/` 目录
|
||||
- **CRUD 模板**:参考 `AI_CONTEXT.md` 文件
|
||||
|
||||
## 开发规范
|
||||
|
||||
- 采用 TypeScript 进行类型检查
|
||||
- 遵循 Vue 3 Composition API 最佳实践
|
||||
- 组件命名使用 PascalCase
|
||||
- 文件命名使用 kebab-case
|
||||
- 目录结构遵循项目规范
|
||||
|
||||
## 贡献指南
|
||||
|
||||
欢迎贡献代码和反馈问题!请先阅读 CONTRIBUTING.md 文档。
|
||||
|
||||
## 社区支持
|
||||
|
||||
- 官方文档:https://madong.tech
|
||||
- 讨论交流:[点击链接加入群聊:690671595](https://qm.qq.com/q/dEfMqYL42c)
|
||||
- 项目演示:https://demo.madong.tech/ 账号:admin 密码:123456
|
||||
|
||||
## 许可证
|
||||
|
||||
该项目采用 MIT 许可证。详见 LICENSE 文件。
|
||||
@@ -0,0 +1,97 @@
|
||||
/**
|
||||
* commitlint 配置文件
|
||||
* 文档
|
||||
* https://commitlint.js.org/#/reference-rules
|
||||
* https://cz-git.qbb.sh/zh/guide/
|
||||
*/
|
||||
|
||||
module.exports = {
|
||||
// 继承的规则
|
||||
extends: ['@commitlint/config-conventional'],
|
||||
// 自定义规则
|
||||
rules: {
|
||||
// 提交类型枚举,git提交type必须是以下类型
|
||||
'type-enum': [
|
||||
2,
|
||||
'always',
|
||||
[
|
||||
'feat', // 新增功能
|
||||
'fix', // 修复缺陷
|
||||
'docs', // 文档变更
|
||||
'style', // 代码格式(不影响功能,例如空格、分号等格式修正)
|
||||
'refactor', // 代码重构(不包括 bug 修复、功能新增)
|
||||
'perf', // 性能优化
|
||||
'test', // 添加疏漏测试或已有测试改动
|
||||
'build', // 构建流程、外部依赖变更(如升级 npm 包、修改 webpack 配置等)
|
||||
'ci', // 修改 CI 配置、脚本
|
||||
'revert', // 回滚 commit
|
||||
'chore', // 对构建过程或辅助工具和库的更改(不影响源文件、测试用例)
|
||||
'wip' // 对构建过程或辅助工具和库的更改(不影响源文件、测试用例)
|
||||
]
|
||||
],
|
||||
'subject-case': [0] // subject大小写不做校验
|
||||
},
|
||||
|
||||
prompt: {
|
||||
messages: {
|
||||
type: '选择你要提交的类型 :',
|
||||
scope: '选择一个提交范围(可选):',
|
||||
customScope: '请输入自定义的提交范围 :',
|
||||
subject: '填写简短精炼的变更描述 :\n',
|
||||
body: '填写更加详细的变更描述(可选)。使用 "|" 换行 :\n',
|
||||
breaking: '列举非兼容性重大的变更(可选)。使用 "|" 换行 :\n',
|
||||
footerPrefixesSelect: '选择关联issue前缀(可选):',
|
||||
customFooterPrefix: '输入自定义issue前缀 :',
|
||||
footer: '列举关联issue (可选) 例如: #31, #I3244 :\n',
|
||||
generatingByAI: '正在通过 AI 生成你的提交简短描述...',
|
||||
generatedSelectByAI: '选择一个 AI 生成的简短描述:',
|
||||
confirmCommit: '是否提交或修改commit ?'
|
||||
},
|
||||
// prettier-ignore
|
||||
types: [
|
||||
{ value: "feat", name: "feat: 新增功能" },
|
||||
{ value: "fix", name: "fix: 修复缺陷" },
|
||||
{ value: "docs", name: "docs: 文档变更" },
|
||||
{ value: "style", name: "style: 代码格式(不影响功能,例如空格、分号等格式修正)" },
|
||||
{ value: "refactor", name: "refactor: 代码重构(不包括 bug 修复、功能新增)" },
|
||||
{ value: "perf", name: "perf: 性能优化" },
|
||||
{ value: "test", name: "test: 添加疏漏测试或已有测试改动" },
|
||||
{ value: "build", name: "build: 构建流程、外部依赖变更(如升级 npm 包、修改 vite 配置等)" },
|
||||
{ value: "ci", name: "ci: 修改 CI 配置、脚本" },
|
||||
{ value: "revert", name: "revert: 回滚 commit" },
|
||||
{ value: "chore", name: "chore: 对构建过程或辅助工具和库的更改(不影响源文件、测试用例)" },
|
||||
],
|
||||
useEmoji: true,
|
||||
emojiAlign: 'center',
|
||||
useAI: false,
|
||||
aiNumber: 1,
|
||||
themeColorCode: '',
|
||||
scopes: [],
|
||||
allowCustomScopes: true,
|
||||
allowEmptyScopes: true,
|
||||
customScopesAlign: 'bottom',
|
||||
customScopesAlias: 'custom',
|
||||
emptyScopesAlias: 'empty',
|
||||
upperCaseSubject: false,
|
||||
markBreakingChangeMode: false,
|
||||
allowBreakingChanges: ['feat', 'fix'],
|
||||
breaklineNumber: 100,
|
||||
breaklineChar: '|',
|
||||
skipQuestions: ['breaking', 'footerPrefix', 'footer'], // 跳过的步骤
|
||||
issuePrefixes: [{ value: 'closed', name: 'closed: ISSUES has been processed' }],
|
||||
customIssuePrefixAlign: 'top',
|
||||
emptyIssuePrefixAlias: 'skip',
|
||||
customIssuePrefixAlias: 'custom',
|
||||
allowCustomIssuePrefix: true,
|
||||
allowEmptyIssuePrefix: true,
|
||||
confirmColorize: true,
|
||||
maxHeaderLength: Infinity,
|
||||
maxSubjectLength: Infinity,
|
||||
minSubjectLength: 0,
|
||||
scopeOverrides: undefined,
|
||||
defaultBody: '',
|
||||
defaultIssues: '',
|
||||
defaultScope: '',
|
||||
defaultSubject: ''
|
||||
}
|
||||
}
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,89 @@
|
||||
// 从 URL 和路径模块中导入必要的功能
|
||||
import fs from 'fs'
|
||||
import path, { dirname } from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
// 从 ESLint 插件中导入推荐配置
|
||||
import pluginJs from '@eslint/js'
|
||||
import eslintPluginPrettierRecommended from 'eslint-plugin-prettier/recommended'
|
||||
import pluginVue from 'eslint-plugin-vue'
|
||||
import globals from 'globals'
|
||||
import tseslint from 'typescript-eslint'
|
||||
|
||||
// 使用 import.meta.url 获取当前模块的路径
|
||||
const __filename = fileURLToPath(import.meta.url)
|
||||
const __dirname = dirname(__filename)
|
||||
|
||||
// 读取 .auto-import.json 文件的内容,并将其解析为 JSON 对象
|
||||
const autoImportConfig = JSON.parse(
|
||||
fs.readFileSync(path.resolve(__dirname, '.auto-import.json'), 'utf-8')
|
||||
)
|
||||
|
||||
export default [
|
||||
// 指定文件匹配规则
|
||||
{
|
||||
files: ['**/*.{js,mjs,cjs,ts,vue}']
|
||||
},
|
||||
// 指定全局变量和环境
|
||||
{
|
||||
languageOptions: {
|
||||
globals: {
|
||||
...globals.browser,
|
||||
...globals.node
|
||||
}
|
||||
}
|
||||
},
|
||||
// 扩展配置
|
||||
pluginJs.configs.recommended,
|
||||
...tseslint.configs.recommended,
|
||||
...pluginVue.configs['flat/essential'],
|
||||
// 自定义规则
|
||||
{
|
||||
// 针对所有 JavaScript、TypeScript 和 Vue 文件应用以下配置
|
||||
files: ['**/*.{js,mjs,cjs,ts,vue,tsx}'],
|
||||
|
||||
languageOptions: {
|
||||
globals: {
|
||||
// 合并从 autoImportConfig 中读取的全局变量配置
|
||||
...autoImportConfig.globals,
|
||||
// TypeScript 全局命名空间
|
||||
Api: 'readonly'
|
||||
}
|
||||
},
|
||||
rules: {
|
||||
quotes: ['error', 'single'], // 使用单引号
|
||||
semi: ['error', 'never'], // 语句末尾不加分号
|
||||
'no-var': 'error', // 要求使用 let 或 const 而不是 var
|
||||
'@typescript-eslint/no-explicit-any': 'off', // 禁用 any 检查
|
||||
'vue/multi-word-component-names': 'off', // 禁用对 Vue 组件名称的多词要求检查
|
||||
'no-multiple-empty-lines': ['warn', { max: 1 }], // 不允许多个空行
|
||||
'no-unexpected-multiline': 'error', // 禁止空余的多行
|
||||
'no-mixed-spaces-and-tabs': 'off' // 禁用空格和 tab 混合缩进
|
||||
}
|
||||
},
|
||||
// vue 规则
|
||||
{
|
||||
files: ['**/*.vue'],
|
||||
languageOptions: {
|
||||
parserOptions: {
|
||||
parser: tseslint.parser,
|
||||
ecmaFeatures: {
|
||||
jsx: true // 启用 JSX 支持
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
// 忽略文件
|
||||
{
|
||||
ignores: [
|
||||
'node_modules',
|
||||
'dist',
|
||||
'public',
|
||||
'.vscode/**',
|
||||
'src/assets/**',
|
||||
'src/utils/console.ts'
|
||||
]
|
||||
},
|
||||
// prettier 配置
|
||||
eslintPluginPrettierRecommended
|
||||
]
|
||||
@@ -0,0 +1,47 @@
|
||||
<!doctype html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<title>MrleeAdmin</title>
|
||||
<meta charset="UTF-8" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<meta name="description"
|
||||
content="MrleeAdmin - A modern admin dashboard template built with Vue 3, TypeScript, and Element Plus." />
|
||||
<link rel="shortcut icon" type="image/x-icon" href="src/assets/images/favicon.ico" />
|
||||
|
||||
<style>
|
||||
/* 防止页面刷新时白屏的初始样式 */
|
||||
html {
|
||||
background-color: #fafbfc;
|
||||
}
|
||||
|
||||
html.dark {
|
||||
background-color: #070707;
|
||||
}
|
||||
</style>
|
||||
|
||||
<script>
|
||||
// 初始化 html class 主题属性
|
||||
; (function () {
|
||||
try {
|
||||
if (typeof Storage === 'undefined' || !window.localStorage) {
|
||||
return
|
||||
}
|
||||
|
||||
const themeType = localStorage.getItem('sys-theme')
|
||||
if (themeType === 'dark') {
|
||||
document.documentElement.classList.add('dark')
|
||||
}
|
||||
} catch (e) {
|
||||
console.warn('Failed to apply initial theme:', e)
|
||||
}
|
||||
})()
|
||||
</script>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
||||
@@ -0,0 +1,158 @@
|
||||
{
|
||||
"name": "art-design-pro",
|
||||
"version": "0.0.0",
|
||||
"type": "module",
|
||||
"engines": {
|
||||
"node": ">=20.18.0",
|
||||
"pnpm": ">=8.8.0"
|
||||
},
|
||||
"scripts": {
|
||||
"dev": "vite --open",
|
||||
"build": "vite build",
|
||||
"build:mock": "vite build --mode mock",
|
||||
"build:test": "vite build --mode test",
|
||||
"serve": "vite preview",
|
||||
"lint": "eslint",
|
||||
"fix": "eslint --fix",
|
||||
"lint:prettier": "prettier --write \"**/*.{js,cjs,ts,json,tsx,css,less,scss,vue,html,md}\"",
|
||||
"lint:stylelint": "stylelint \"**/*.{css,scss,vue}\" --fix",
|
||||
"lint:lint-staged": "lint-staged",
|
||||
"prepare": "husky",
|
||||
"commit": "git-cz",
|
||||
"clean:dev": "tsx scripts/clean-dev.ts"
|
||||
},
|
||||
"config": {
|
||||
"commitizen": {
|
||||
"path": "node_modules/cz-git"
|
||||
}
|
||||
},
|
||||
"lint-staged": {
|
||||
"*.{js,ts,mjs,mts,tsx}": [
|
||||
"eslint --fix",
|
||||
"prettier --write"
|
||||
],
|
||||
"*.{cjs,json,jsonc}": [
|
||||
"prettier --write"
|
||||
],
|
||||
"*.vue": [
|
||||
"eslint --fix",
|
||||
"stylelint --fix --allow-empty-input",
|
||||
"prettier --write"
|
||||
],
|
||||
"*.{html,htm}": [
|
||||
"prettier --write"
|
||||
],
|
||||
"*.{scss,css,less}": [
|
||||
"stylelint --fix --allow-empty-input",
|
||||
"prettier --write"
|
||||
],
|
||||
"*.{md,mdx}": [
|
||||
"prettier --write"
|
||||
],
|
||||
"*.{yaml,yml}": [
|
||||
"prettier --write"
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@element-plus/icons-vue": "^2.3.2",
|
||||
"@iconify/json": "^2.2.376",
|
||||
"@iconify/vue": "^5.0.0",
|
||||
"@logicflow/core": "^2.2.1",
|
||||
"@logicflow/extension": "^2.2.1",
|
||||
"@tailwindcss/vite": "^4.1.14",
|
||||
"@vue/reactivity": "^3.5.21",
|
||||
"@vueuse/core": "^13.9.0",
|
||||
"@vxe-ui/plugin-render-element": "4.1.1",
|
||||
"@wangeditor/editor": "^5.1.23",
|
||||
"@wangeditor/editor-for-vue": "next",
|
||||
"axios": "^1.12.2",
|
||||
"crypto-js": "^4.2.0",
|
||||
"dayjs": "^1.11.19",
|
||||
"echarts": "^6.0.0",
|
||||
"element-plus": "^2.11.2",
|
||||
"file-saver": "^2.0.5",
|
||||
"highlight.js": "^11.10.0",
|
||||
"ingenious-flow-designer": "^2.0.0",
|
||||
"jsencrypt": "^3.5.4",
|
||||
"lodash": "^4.17.21",
|
||||
"lodash-es": "^4.17.21",
|
||||
"md-editor-v3": "^4.17.0",
|
||||
"mitt": "^3.0.1",
|
||||
"nprogress": "^0.2.0",
|
||||
"ohash": "^2.0.11",
|
||||
"pinia": "^3.0.3",
|
||||
"pinia-plugin-persistedstate": "^4.3.0",
|
||||
"qrcode.vue": "^3.6.0",
|
||||
"save": "^2.9.0",
|
||||
"sortablejs": "^1.15.6",
|
||||
"tailwindcss": "^4.1.14",
|
||||
"vue": "^3.5.21",
|
||||
"vue-draggable-plus": "^0.6.0",
|
||||
"vue-i18n": "^9.14.0",
|
||||
"vue-json-pretty": "^2.6.0",
|
||||
"vue-router": "^4.5.1",
|
||||
"vxe-pc-ui": "4.10.49",
|
||||
"vxe-table": "4.17.27",
|
||||
"xe-utils": "3.8.0",
|
||||
"xgplayer": "^3.0.20",
|
||||
"xlsx": "^0.18.5"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@commitlint/cli": "^19.4.1",
|
||||
"@commitlint/config-conventional": "^19.4.1",
|
||||
"@eslint/js": "^9.9.1",
|
||||
"@logicflow/core": "^2.2.1",
|
||||
"@logicflow/extension": "^2.2.1",
|
||||
"@types/lodash-es": "^4.17.12",
|
||||
"@types/node": "^24.0.5",
|
||||
"@types/sortablejs": "^1.15.9",
|
||||
"@typescript-eslint/eslint-plugin": "^8.3.0",
|
||||
"@typescript-eslint/parser": "^8.3.0",
|
||||
"@vitejs/plugin-vue": "^6.0.1",
|
||||
"@vitejs/plugin-vue-jsx": "^5.0.1",
|
||||
"@vue/compiler-sfc": "^3.0.5",
|
||||
"commitizen": "^4.3.0",
|
||||
"cz-git": "^1.11.1",
|
||||
"eslint": "^9.9.1",
|
||||
"eslint-config-prettier": "^9.1.0",
|
||||
"eslint-plugin-prettier": "^5.2.1",
|
||||
"eslint-plugin-vue": "^9.27.0",
|
||||
"glob": "^13.0.6",
|
||||
"globals": "^15.9.0",
|
||||
"husky": "^9.1.5",
|
||||
"lint-staged": "^15.5.2",
|
||||
"lodash": "^4.17.21",
|
||||
"prettier": "^3.5.3",
|
||||
"rollup-plugin-visualizer": "^5.12.0",
|
||||
"sass": "^1.81.0",
|
||||
"stylelint": "^16.20.0",
|
||||
"stylelint-config-html": "^1.1.0",
|
||||
"stylelint-config-recess-order": "^4.6.0",
|
||||
"stylelint-config-recommended-scss": "^14.1.0",
|
||||
"stylelint-config-recommended-vue": "^1.5.0",
|
||||
"stylelint-config-standard": "^36.0.1",
|
||||
"terser": "^5.36.0",
|
||||
"tsx": "^4.20.3",
|
||||
"typescript": "~5.6.3",
|
||||
"typescript-eslint": "^8.9.0",
|
||||
"unplugin-auto-import": "^20.2.0",
|
||||
"unplugin-element-plus": "^0.10.0",
|
||||
"unplugin-vue-components": "^29.1.0",
|
||||
"vite": "^7.1.5",
|
||||
"vite-plugin-compression": "^0.5.1",
|
||||
"vite-plugin-vue-devtools": "^7.7.6",
|
||||
"vue-demi": "^0.14.9",
|
||||
"vue-img-cutter": "^3.0.5",
|
||||
"vue-json-pretty": "^2.4.0",
|
||||
"vue-tsc": "~2.1.6"
|
||||
},
|
||||
"pnpm": {
|
||||
"onlyBuiltDependencies": [
|
||||
"@parcel/watcher",
|
||||
"core-js",
|
||||
"es5-ext",
|
||||
"esbuild",
|
||||
"vue-demi"
|
||||
]
|
||||
}
|
||||
}
|
||||
Generated
+8814
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,9 @@
|
||||
packages:
|
||||
- '.'
|
||||
|
||||
allowBuilds:
|
||||
'@parcel/watcher': false
|
||||
core-js: false
|
||||
es5-ext: false
|
||||
esbuild: false
|
||||
vue-demi: false
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 6.2 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 6.2 KiB |
@@ -0,0 +1,841 @@
|
||||
// scripts/clean-dev.ts
|
||||
import fs from 'fs/promises'
|
||||
import path from 'path'
|
||||
|
||||
// 现代化颜色主题
|
||||
const theme = {
|
||||
// 基础颜色
|
||||
reset: '\x1b[0m',
|
||||
bold: '\x1b[1m',
|
||||
dim: '\x1b[2m',
|
||||
|
||||
// 前景色
|
||||
primary: '\x1b[38;5;75m', // 亮蓝色
|
||||
success: '\x1b[38;5;82m', // 亮绿色
|
||||
warning: '\x1b[38;5;220m', // 亮黄色
|
||||
error: '\x1b[38;5;196m', // 亮红色
|
||||
info: '\x1b[38;5;159m', // 青色
|
||||
purple: '\x1b[38;5;141m', // 紫色
|
||||
orange: '\x1b[38;5;208m', // 橙色
|
||||
gray: '\x1b[38;5;245m', // 灰色
|
||||
white: '\x1b[38;5;255m', // 白色
|
||||
|
||||
// 背景色
|
||||
bgDark: '\x1b[48;5;235m', // 深灰背景
|
||||
bgBlue: '\x1b[48;5;24m', // 蓝色背景
|
||||
bgGreen: '\x1b[48;5;22m', // 绿色背景
|
||||
bgRed: '\x1b[48;5;52m' // 红色背景
|
||||
}
|
||||
|
||||
// 现代化图标集
|
||||
const icons = {
|
||||
rocket: '🚀',
|
||||
fire: '🔥',
|
||||
star: '⭐',
|
||||
gem: '💎',
|
||||
crown: '👑',
|
||||
magic: '✨',
|
||||
warning: '⚠️',
|
||||
success: '✅',
|
||||
error: '❌',
|
||||
info: 'ℹ️',
|
||||
folder: '📁',
|
||||
file: '📄',
|
||||
image: '🖼️',
|
||||
code: '💻',
|
||||
data: '📊',
|
||||
globe: '🌐',
|
||||
map: '🗺️',
|
||||
chat: '💬',
|
||||
bolt: '⚡',
|
||||
shield: '🛡️',
|
||||
key: '🔑',
|
||||
link: '🔗',
|
||||
clean: '🧹',
|
||||
trash: '🗑️',
|
||||
check: '✓',
|
||||
cross: '✗',
|
||||
arrow: '→',
|
||||
loading: '⏳'
|
||||
}
|
||||
|
||||
// 格式化工具
|
||||
const fmt = {
|
||||
title: (text: string) => `${theme.bold}${theme.primary}${text}${theme.reset}`,
|
||||
subtitle: (text: string) => `${theme.purple}${text}${theme.reset}`,
|
||||
success: (text: string) => `${theme.success}${text}${theme.reset}`,
|
||||
error: (text: string) => `${theme.error}${text}${theme.reset}`,
|
||||
warning: (text: string) => `${theme.warning}${text}${theme.reset}`,
|
||||
info: (text: string) => `${theme.info}${text}${theme.reset}`,
|
||||
highlight: (text: string) => `${theme.bold}${theme.white}${text}${theme.reset}`,
|
||||
dim: (text: string) => `${theme.dim}${theme.gray}${text}${theme.reset}`,
|
||||
orange: (text: string) => `${theme.orange}${text}${theme.reset}`,
|
||||
|
||||
// 带背景的文本
|
||||
badge: (text: string, bg: string = theme.bgBlue) =>
|
||||
`${bg}${theme.white}${theme.bold} ${text} ${theme.reset}`,
|
||||
|
||||
// 渐变效果模拟
|
||||
gradient: (text: string) => {
|
||||
const colors = ['\x1b[38;5;75m', '\x1b[38;5;81m', '\x1b[38;5;87m', '\x1b[38;5;159m']
|
||||
const chars = text.split('')
|
||||
return chars.map((char, i) => `${colors[i % colors.length]}${char}`).join('') + theme.reset
|
||||
}
|
||||
}
|
||||
|
||||
// 创建现代化标题横幅
|
||||
function createModernBanner() {
|
||||
console.log()
|
||||
console.log(
|
||||
fmt.gradient(' ╔══════════════════════════════════════════════════════════════════╗')
|
||||
)
|
||||
console.log(
|
||||
fmt.gradient(' ║ ║')
|
||||
)
|
||||
console.log(
|
||||
` ║ ${icons.rocket} ${fmt.title('ART DESIGN PRO')} ${fmt.subtitle('· 代码精简程序')} ${icons.magic} ║`
|
||||
)
|
||||
console.log(
|
||||
` ║ ${fmt.dim('为项目移除演示数据,快速切换至开发模式')} ║`
|
||||
)
|
||||
console.log(
|
||||
fmt.gradient(' ║ ║')
|
||||
)
|
||||
console.log(
|
||||
fmt.gradient(' ╚══════════════════════════════════════════════════════════════════╝')
|
||||
)
|
||||
console.log()
|
||||
}
|
||||
|
||||
// 创建分割线
|
||||
function createDivider(char = '─', color = theme.primary) {
|
||||
console.log(`${color}${' ' + char.repeat(66)}${theme.reset}`)
|
||||
}
|
||||
|
||||
// 创建卡片样式容器
|
||||
function createCard(title: string, content: string[]) {
|
||||
console.log(` ${fmt.badge('', theme.bgBlue)} ${fmt.title(title)}`)
|
||||
console.log()
|
||||
content.forEach((line) => {
|
||||
console.log(` ${line}`)
|
||||
})
|
||||
console.log()
|
||||
}
|
||||
|
||||
// 进度条动画
|
||||
function createProgressBar(current: number, total: number, text: string, width = 40) {
|
||||
const percentage = Math.round((current / total) * 100)
|
||||
const filled = Math.round((current / total) * width)
|
||||
const empty = width - filled
|
||||
|
||||
const filledBar = '█'.repeat(filled)
|
||||
const emptyBar = '░'.repeat(empty)
|
||||
|
||||
process.stdout.write(
|
||||
`\r ${fmt.info('进度')} [${theme.success}${filledBar}${theme.gray}${emptyBar}${theme.reset}] ${fmt.highlight(percentage + '%')})}`
|
||||
)
|
||||
|
||||
if (current === total) {
|
||||
console.log()
|
||||
}
|
||||
}
|
||||
|
||||
// 统计信息
|
||||
const stats = {
|
||||
deletedFiles: 0,
|
||||
deletedPaths: 0,
|
||||
failedPaths: 0,
|
||||
startTime: Date.now(),
|
||||
totalFiles: 0
|
||||
}
|
||||
|
||||
// 清理目标
|
||||
const targets = [
|
||||
'README.md',
|
||||
'README.zh-CN.md',
|
||||
'CHANGELOG.md',
|
||||
'CHANGELOG.zh-CN.md',
|
||||
'src/views/change',
|
||||
'src/views/safeguard',
|
||||
'src/views/article',
|
||||
'src/views/examples',
|
||||
'src/views/system/nested',
|
||||
'src/views/widgets',
|
||||
'src/views/template',
|
||||
'src/views/dashboard/analysis',
|
||||
'src/views/dashboard/ecommerce',
|
||||
'src/mock/json',
|
||||
'src/mock/temp/articleList.ts',
|
||||
'src/mock/temp/commentDetail.ts',
|
||||
'src/mock/temp/commentList.ts',
|
||||
'src/assets/images/cover',
|
||||
'src/assets/images/safeguard',
|
||||
'src/assets/images/3d',
|
||||
'src/components/core/charts/art-map-chart',
|
||||
'src/api/sys',
|
||||
'src/api/dictApi.ts',
|
||||
'src/api/auth.ts',
|
||||
'src/api/system-manage.ts',
|
||||
]
|
||||
|
||||
// 递归统计文件数量
|
||||
async function countFiles(targetPath: string): Promise<number> {
|
||||
const fullPath = path.resolve(process.cwd(), targetPath)
|
||||
|
||||
try {
|
||||
const stat = await fs.stat(fullPath)
|
||||
|
||||
if (stat.isFile()) {
|
||||
return 1
|
||||
} else if (stat.isDirectory()) {
|
||||
const entries = await fs.readdir(fullPath)
|
||||
let count = 0
|
||||
|
||||
for (const entry of entries) {
|
||||
const entryPath = path.join(targetPath, entry)
|
||||
count += await countFiles(entryPath)
|
||||
}
|
||||
|
||||
return count
|
||||
}
|
||||
} catch {
|
||||
return 0
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
// 统计所有目标的文件数量
|
||||
async function countAllFiles(): Promise<number> {
|
||||
let totalCount = 0
|
||||
|
||||
for (const target of targets) {
|
||||
const count = await countFiles(target)
|
||||
totalCount += count
|
||||
}
|
||||
|
||||
return totalCount
|
||||
}
|
||||
|
||||
// 删除文件和目录
|
||||
async function remove(targetPath: string, index: number) {
|
||||
const fullPath = path.resolve(process.cwd(), targetPath)
|
||||
|
||||
createProgressBar(index + 1, targets.length, targetPath)
|
||||
|
||||
try {
|
||||
const fileCount = await countFiles(targetPath)
|
||||
await fs.rm(fullPath, { recursive: true, force: true })
|
||||
stats.deletedFiles += fileCount
|
||||
stats.deletedPaths++
|
||||
await new Promise((resolve) => setTimeout(resolve, 50))
|
||||
} catch (err) {
|
||||
stats.failedPaths++
|
||||
console.log()
|
||||
console.log(` ${icons.error} ${fmt.error('删除失败')}: ${fmt.highlight(targetPath)}`)
|
||||
console.log(` ${fmt.dim('错误详情: ' + err)}`)
|
||||
}
|
||||
}
|
||||
|
||||
// 清理路由模块
|
||||
async function cleanRouteModules() {
|
||||
const modulesPath = path.resolve(process.cwd(), 'src/router/modules')
|
||||
|
||||
try {
|
||||
// 删除演示相关的路由模块
|
||||
const modulesToRemove = [
|
||||
'template.ts',
|
||||
'widgets.ts',
|
||||
'examples.ts',
|
||||
'article.ts',
|
||||
'safeguard.ts',
|
||||
'help.ts'
|
||||
]
|
||||
|
||||
for (const module of modulesToRemove) {
|
||||
const modulePath = path.join(modulesPath, module)
|
||||
try {
|
||||
await fs.rm(modulePath, { force: true })
|
||||
} catch {
|
||||
// 文件不存在时忽略错误
|
||||
}
|
||||
}
|
||||
|
||||
// 重写 dashboard.ts - 只保留 console
|
||||
const dashboardContent = `import { AppRouteRecord } from '@/types/router'
|
||||
|
||||
export const dashboardRoutes: AppRouteRecord = {
|
||||
name: 'Dashboard',
|
||||
path: '/dashboard',
|
||||
component: '/index/index',
|
||||
meta: {
|
||||
title: 'menus.dashboard.title',
|
||||
icon: 'ri:pie-chart-line',
|
||||
roles: ['R_SUPER', 'R_ADMIN']
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'console',
|
||||
name: 'Console',
|
||||
component: '/dashboard/console',
|
||||
meta: {
|
||||
title: 'menus.dashboard.console',
|
||||
keepAlive: false,
|
||||
fixedTab: true
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
`
|
||||
await fs.writeFile(path.join(modulesPath, 'dashboard.ts'), dashboardContent, 'utf-8')
|
||||
|
||||
// 重写 system.ts - 移除 nested 嵌套菜单
|
||||
const systemContent = `import { AppRouteRecord } from '@/types/router'
|
||||
|
||||
export const systemRoutes: AppRouteRecord = {
|
||||
path: '/system',
|
||||
name: 'System',
|
||||
component: '/index/index',
|
||||
meta: {
|
||||
title: 'menus.system.title',
|
||||
icon: 'ri:user-3-line',
|
||||
roles: ['R_SUPER', 'R_ADMIN']
|
||||
},
|
||||
children: [
|
||||
{
|
||||
path: 'user',
|
||||
name: 'User',
|
||||
component: '/system/user',
|
||||
meta: {
|
||||
title: 'menus.system.user',
|
||||
keepAlive: true,
|
||||
roles: ['R_SUPER', 'R_ADMIN']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'role',
|
||||
name: 'Role',
|
||||
component: '/system/role',
|
||||
meta: {
|
||||
title: 'menus.system.role',
|
||||
keepAlive: true,
|
||||
roles: ['R_SUPER']
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'user-center',
|
||||
name: 'UserCenter',
|
||||
component: '/system/user-center',
|
||||
meta: {
|
||||
title: 'menus.system.userCenter',
|
||||
isHide: true,
|
||||
keepAlive: true,
|
||||
isHideTab: true
|
||||
}
|
||||
},
|
||||
{
|
||||
path: 'menu',
|
||||
name: 'Menus',
|
||||
component: '/system/menu',
|
||||
meta: {
|
||||
title: 'menus.system.menu',
|
||||
keepAlive: true,
|
||||
roles: ['R_SUPER'],
|
||||
authList: [
|
||||
{ title: '新增', authMark: 'add' },
|
||||
{ title: '编辑', authMark: 'edit' },
|
||||
{ title: '删除', authMark: 'delete' }
|
||||
]
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
`
|
||||
await fs.writeFile(path.join(modulesPath, 'system.ts'), systemContent, 'utf-8')
|
||||
|
||||
// 重写 index.ts - 只导入保留的模块
|
||||
const indexContent = `import { AppRouteRecord } from '@/types/router'
|
||||
import { dashboardRoutes } from './dashboard'
|
||||
import { systemRoutes } from './system'
|
||||
import { resultRoutes } from './result'
|
||||
import { exceptionRoutes } from './exception'
|
||||
|
||||
/**
|
||||
* 导出所有模块化路由
|
||||
*/
|
||||
export const routeModules: AppRouteRecord[] = [
|
||||
dashboardRoutes,
|
||||
systemRoutes,
|
||||
resultRoutes,
|
||||
exceptionRoutes
|
||||
]
|
||||
`
|
||||
await fs.writeFile(path.join(modulesPath, 'index.ts'), indexContent, 'utf-8')
|
||||
|
||||
console.log(` ${icons.success} ${fmt.success('清理路由模块完成')}`)
|
||||
} catch (err) {
|
||||
console.log(` ${icons.error} ${fmt.error('清理路由模块失败')}`)
|
||||
console.log(` ${fmt.dim('错误详情: ' + err)}`)
|
||||
}
|
||||
}
|
||||
|
||||
// 清理路由别名
|
||||
async function cleanRoutesAlias() {
|
||||
const routesAliasPath = path.resolve(process.cwd(), 'src/router/routesAlias.ts')
|
||||
|
||||
try {
|
||||
const cleanedAlias = `/**
|
||||
* 公共路由别名
|
||||
# 存放系统级公共路由路径,如布局容器、登录页等
|
||||
*/
|
||||
export enum RoutesAlias {
|
||||
Layout = '/index/index', // 布局容器
|
||||
Login = '/auth/login' // 登录页
|
||||
}
|
||||
`
|
||||
|
||||
await fs.writeFile(routesAliasPath, cleanedAlias, 'utf-8')
|
||||
console.log(` ${icons.success} ${fmt.success('重写路由别名配置完成')}`)
|
||||
} catch (err) {
|
||||
console.log(` ${icons.error} ${fmt.error('清理路由别名失败')}`)
|
||||
console.log(` ${fmt.dim('错误详情: ' + err)}`)
|
||||
}
|
||||
}
|
||||
|
||||
// 清理变更日志
|
||||
async function cleanChangeLog() {
|
||||
const changeLogPath = path.resolve(process.cwd(), 'src/mock/upgrade/changeLog.ts')
|
||||
|
||||
try {
|
||||
const cleanedChangeLog = `import { ref } from 'vue'
|
||||
|
||||
interface UpgradeLog {
|
||||
version: string // 版本号
|
||||
title: string // 更新标题
|
||||
date: string // 更新日期
|
||||
detail?: string[] // 更新内容
|
||||
requireReLogin?: boolean // 是否需要重新登录
|
||||
remark?: string // 备注
|
||||
}
|
||||
|
||||
export const upgradeLogList = ref<UpgradeLog[]>([])
|
||||
`
|
||||
|
||||
await fs.writeFile(changeLogPath, cleanedChangeLog, 'utf-8')
|
||||
console.log(` ${icons.success} ${fmt.success('清空变更日志数据完成')}`)
|
||||
} catch (err) {
|
||||
console.log(` ${icons.error} ${fmt.error('清理变更日志失败')}`)
|
||||
console.log(` ${fmt.dim('错误详情: ' + err)}`)
|
||||
}
|
||||
}
|
||||
|
||||
// 清理语言文件
|
||||
async function cleanLanguageFiles() {
|
||||
const languageFiles = [
|
||||
{ path: 'src/locales/langs/zh.json', name: '中文语言文件' },
|
||||
{ path: 'src/locales/langs/en.json', name: '英文语言文件' }
|
||||
]
|
||||
|
||||
for (const { path: langPath, name } of languageFiles) {
|
||||
try {
|
||||
const fullPath = path.resolve(process.cwd(), langPath)
|
||||
const content = await fs.readFile(fullPath, 'utf-8')
|
||||
const langData = JSON.parse(content)
|
||||
|
||||
const menusToRemove = [
|
||||
'widgets',
|
||||
'template',
|
||||
'article',
|
||||
'examples',
|
||||
'safeguard',
|
||||
'plan',
|
||||
'help'
|
||||
]
|
||||
|
||||
if (langData.menus) {
|
||||
menusToRemove.forEach((menuKey) => {
|
||||
if (langData.menus[menuKey]) {
|
||||
delete langData.menus[menuKey]
|
||||
}
|
||||
})
|
||||
|
||||
if (langData.menus.dashboard) {
|
||||
if (langData.menus.dashboard.analysis) {
|
||||
delete langData.menus.dashboard.analysis
|
||||
}
|
||||
if (langData.menus.dashboard.ecommerce) {
|
||||
delete langData.menus.dashboard.ecommerce
|
||||
}
|
||||
}
|
||||
|
||||
if (langData.menus.system) {
|
||||
const systemKeysToRemove = [
|
||||
'nested',
|
||||
'menu1',
|
||||
'menu2',
|
||||
'menu21',
|
||||
'menu3',
|
||||
'menu31',
|
||||
'menu32',
|
||||
'menu321'
|
||||
]
|
||||
systemKeysToRemove.forEach((key) => {
|
||||
if (langData.menus.system[key]) {
|
||||
delete langData.menus.system[key]
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
await fs.writeFile(fullPath, JSON.stringify(langData, null, 2), 'utf-8')
|
||||
console.log(` ${icons.success} ${fmt.success(`清理${name}完成`)}`)
|
||||
} catch (err) {
|
||||
console.log(` ${icons.error} ${fmt.error(`清理${name}失败`)}`)
|
||||
console.log(` ${fmt.dim('错误详情: ' + err)}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 清理快速入口组件
|
||||
async function cleanFastEnterComponent() {
|
||||
const fastEnterPath = path.resolve(process.cwd(), 'src/config/fastEnter.ts')
|
||||
|
||||
try {
|
||||
const cleanedFastEnter = `/**
|
||||
* 快速入口配置
|
||||
* 包含:应用列表、快速链接等配置
|
||||
*/
|
||||
import { WEB_LINKS } from '@/utils/constants'
|
||||
import type { FastEnterConfig } from '@/types/config'
|
||||
|
||||
const fastEnterConfig: FastEnterConfig = {
|
||||
// 显示条件(屏幕宽度)
|
||||
minWidth: 1200,
|
||||
// 应用列表
|
||||
applications: [
|
||||
{
|
||||
name: '工作台',
|
||||
description: '系统概览与数据统计',
|
||||
icon: 'ri:pie-chart-line',
|
||||
iconColor: '#377dff',
|
||||
enabled: true,
|
||||
order: 1,
|
||||
routeName: 'Console'
|
||||
},
|
||||
{
|
||||
name: '官方文档',
|
||||
description: '使用指南与开发文档',
|
||||
icon: 'ri:bill-line',
|
||||
iconColor: '#ffb100',
|
||||
enabled: true,
|
||||
order: 2,
|
||||
link: WEB_LINKS.DOCS
|
||||
},
|
||||
{
|
||||
name: '技术支持',
|
||||
description: '技术支持与问题反馈',
|
||||
icon: 'ri:user-location-line',
|
||||
iconColor: '#ff6b6b',
|
||||
enabled: true,
|
||||
order: 3,
|
||||
link: WEB_LINKS.COMMUNITY
|
||||
},
|
||||
{
|
||||
name: '哔哩哔哩',
|
||||
description: '技术分享与交流',
|
||||
icon: 'ri:bilibili-line',
|
||||
iconColor: '#FB7299',
|
||||
enabled: true,
|
||||
order: 4,
|
||||
link: WEB_LINKS.BILIBILI
|
||||
}
|
||||
],
|
||||
// 快速链接
|
||||
quickLinks: [
|
||||
{
|
||||
name: '登录',
|
||||
enabled: true,
|
||||
order: 1,
|
||||
routeName: 'Login'
|
||||
},
|
||||
{
|
||||
name: '注册',
|
||||
enabled: true,
|
||||
order: 2,
|
||||
routeName: 'Register'
|
||||
},
|
||||
{
|
||||
name: '忘记密码',
|
||||
enabled: true,
|
||||
order: 3,
|
||||
routeName: 'ForgetPassword'
|
||||
},
|
||||
{
|
||||
name: '个人中心',
|
||||
enabled: true,
|
||||
order: 4,
|
||||
routeName: 'UserCenter'
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
export default Object.freeze(fastEnterConfig)
|
||||
`
|
||||
|
||||
await fs.writeFile(fastEnterPath, cleanedFastEnter, 'utf-8')
|
||||
console.log(` ${icons.success} ${fmt.success('清理快速入口配置完成')}`)
|
||||
} catch (err) {
|
||||
console.log(` ${icons.error} ${fmt.error('清理快速入口配置失败')}`)
|
||||
console.log(` ${fmt.dim('错误详情: ' + err)}`)
|
||||
}
|
||||
}
|
||||
|
||||
// 更新菜单接口
|
||||
async function updateMenuApi() {
|
||||
const apiPath = path.resolve(process.cwd(), 'src/api/system-manage.ts')
|
||||
|
||||
try {
|
||||
const content = await fs.readFile(apiPath, 'utf-8')
|
||||
const updatedContent = content.replace(
|
||||
"url: '/api/v3/system/menus'",
|
||||
"url: '/api/v3/system/menus/simple'"
|
||||
)
|
||||
|
||||
await fs.writeFile(apiPath, updatedContent, 'utf-8')
|
||||
console.log(` ${icons.success} ${fmt.success('更新菜单接口完成')}`)
|
||||
} catch (err) {
|
||||
console.log(` ${icons.error} ${fmt.error('更新菜单接口失败')}`)
|
||||
console.log(` ${fmt.dim('错误详情: ' + err)}`)
|
||||
}
|
||||
}
|
||||
|
||||
// 用户确认函数
|
||||
async function getUserConfirmation(): Promise<boolean> {
|
||||
const { createInterface } = await import('readline')
|
||||
|
||||
return new Promise((resolve) => {
|
||||
const rl = createInterface({
|
||||
input: process.stdin,
|
||||
output: process.stdout
|
||||
})
|
||||
|
||||
console.log(
|
||||
` ${fmt.highlight('请输入')} ${fmt.success('yes')} ${fmt.highlight('确认执行清理操作,或按 Enter 取消')}`
|
||||
)
|
||||
console.log()
|
||||
process.stdout.write(` ${icons.arrow} `)
|
||||
|
||||
rl.question('', (answer: string) => {
|
||||
rl.close()
|
||||
resolve(answer.toLowerCase().trim() === 'yes')
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// 显示清理警告
|
||||
async function showCleanupWarning() {
|
||||
createCard('安全警告', [
|
||||
`${fmt.warning('此操作将永久删除以下演示内容,且无法恢复!')}`,
|
||||
`${fmt.dim('请仔细阅读清理列表,确认后再继续操作')}`
|
||||
])
|
||||
|
||||
const cleanupItems = [
|
||||
{
|
||||
icon: icons.image,
|
||||
name: '图片资源',
|
||||
desc: '演示用的封面图片、3D图片、运维图片等',
|
||||
color: theme.orange
|
||||
},
|
||||
{
|
||||
icon: icons.file,
|
||||
name: '演示页面',
|
||||
desc: 'widgets、template、article、examples、safeguard等页面',
|
||||
color: theme.purple
|
||||
},
|
||||
{
|
||||
icon: icons.code,
|
||||
name: '路由模块文件',
|
||||
desc: '删除演示路由模块,只保留核心模块(dashboard、system、result、exception)',
|
||||
color: theme.primary
|
||||
},
|
||||
{
|
||||
icon: icons.link,
|
||||
name: '路由别名',
|
||||
desc: '重写routesAlias.ts,移除演示路由别名',
|
||||
color: theme.info
|
||||
},
|
||||
{
|
||||
icon: icons.data,
|
||||
name: 'Mock数据',
|
||||
desc: '演示用的JSON数据、文章列表、评论数据等',
|
||||
color: theme.success
|
||||
},
|
||||
{
|
||||
icon: icons.globe,
|
||||
name: '多语言文件',
|
||||
desc: '清理中英文语言包中的演示菜单项',
|
||||
color: theme.warning
|
||||
},
|
||||
{ icon: icons.map, name: '地图组件', desc: '移除art-map-chart地图组件', color: theme.error },
|
||||
{ icon: icons.chat, name: '评论组件', desc: '移除comment-widget评论组件', color: theme.orange },
|
||||
{
|
||||
icon: icons.bolt,
|
||||
name: '快速入口',
|
||||
desc: '移除分析页、礼花效果、聊天、更新日志、定价、留言管理等无效项目',
|
||||
color: theme.purple
|
||||
}
|
||||
]
|
||||
|
||||
console.log(` ${fmt.badge('', theme.bgRed)} ${fmt.title('将要清理的内容')}`)
|
||||
console.log()
|
||||
|
||||
cleanupItems.forEach((item, index) => {
|
||||
console.log(` ${item.color}${theme.reset} ${fmt.highlight(`${index + 1}. ${item.name}`)}`)
|
||||
console.log(` ${fmt.dim(item.desc)}`)
|
||||
})
|
||||
|
||||
console.log()
|
||||
console.log(` ${fmt.badge('', theme.bgGreen)} ${fmt.title('保留的功能模块')}`)
|
||||
console.log()
|
||||
|
||||
const preservedModules = [
|
||||
{ name: 'Dashboard', desc: '工作台页面' },
|
||||
{ name: 'System', desc: '系统管理模块' },
|
||||
{ name: 'Result', desc: '结果页面' },
|
||||
{ name: 'Exception', desc: '异常页面' },
|
||||
{ name: 'Auth', desc: '登录注册功能' },
|
||||
{ name: 'Core Components', desc: '核心组件库' }
|
||||
]
|
||||
|
||||
preservedModules.forEach((module) => {
|
||||
console.log(` ${icons.check} ${fmt.success(module.name)} ${fmt.dim(`- ${module.desc}`)}`)
|
||||
})
|
||||
|
||||
console.log()
|
||||
createDivider()
|
||||
console.log()
|
||||
}
|
||||
|
||||
// 显示统计信息
|
||||
async function showStats() {
|
||||
const duration = Date.now() - stats.startTime
|
||||
const seconds = (duration / 1000).toFixed(2)
|
||||
|
||||
console.log()
|
||||
createCard('清理统计', [
|
||||
`${fmt.success('成功删除')}: ${fmt.highlight(stats.deletedFiles.toString())} 个文件`,
|
||||
`${fmt.info('涉及路径')}: ${fmt.highlight(stats.deletedPaths.toString())} 个目录/文件`,
|
||||
...(stats.failedPaths > 0
|
||||
? [
|
||||
`${icons.error} ${fmt.error('删除失败')}: ${fmt.highlight(stats.failedPaths.toString())} 个路径`
|
||||
]
|
||||
: []),
|
||||
`${fmt.info('耗时')}: ${fmt.highlight(seconds)} 秒`
|
||||
])
|
||||
}
|
||||
|
||||
// 创建成功横幅
|
||||
function createSuccessBanner() {
|
||||
console.log()
|
||||
console.log(
|
||||
fmt.gradient(' ╔══════════════════════════════════════════════════════════════════╗')
|
||||
)
|
||||
console.log(
|
||||
fmt.gradient(' ║ ║')
|
||||
)
|
||||
console.log(
|
||||
` ║ ${icons.star} ${fmt.success('清理完成!项目已准备就绪')} ${icons.rocket} ║`
|
||||
)
|
||||
console.log(
|
||||
` ║ ${fmt.dim('现在可以开始您的开发之旅了!')} ║`
|
||||
)
|
||||
console.log(
|
||||
fmt.gradient(' ║ ║')
|
||||
)
|
||||
console.log(
|
||||
fmt.gradient(' ╚══════════════════════════════════════════════════════════════════╝')
|
||||
)
|
||||
console.log()
|
||||
}
|
||||
|
||||
// 主函数
|
||||
async function main() {
|
||||
// 清屏并显示横幅
|
||||
console.clear()
|
||||
createModernBanner()
|
||||
|
||||
// 显示清理警告
|
||||
await showCleanupWarning()
|
||||
|
||||
// 统计文件数量
|
||||
console.log(` ${fmt.info('正在统计文件数量...')}`)
|
||||
stats.totalFiles = await countAllFiles()
|
||||
|
||||
console.log(` ${fmt.info('即将清理')}: ${fmt.highlight(stats.totalFiles.toString())} 个文件`)
|
||||
console.log(` ${fmt.dim(`涉及 ${targets.length} 个目录/文件路径`)}`)
|
||||
console.log()
|
||||
|
||||
// 用户确认
|
||||
const confirmed = await getUserConfirmation()
|
||||
|
||||
if (!confirmed) {
|
||||
console.log(` ${fmt.warning('操作已取消,清理中止')}`)
|
||||
console.log()
|
||||
return
|
||||
}
|
||||
|
||||
console.log()
|
||||
console.log(` ${icons.check} ${fmt.success('确认成功,开始清理...')}`)
|
||||
console.log()
|
||||
|
||||
// 开始清理过程
|
||||
console.log(` ${fmt.badge('步骤 1/6', theme.bgBlue)} ${fmt.title('删除演示文件')}`)
|
||||
console.log()
|
||||
for (let i = 0; i < targets.length; i++) {
|
||||
await remove(targets[i], i)
|
||||
}
|
||||
console.log()
|
||||
|
||||
console.log(` ${fmt.badge('步骤 2/6', theme.bgBlue)} ${fmt.title('清理路由模块')}`)
|
||||
console.log()
|
||||
await cleanRouteModules()
|
||||
console.log()
|
||||
|
||||
console.log(` ${fmt.badge('步骤 3/6', theme.bgBlue)} ${fmt.title('重写路由别名')}`)
|
||||
console.log()
|
||||
await cleanRoutesAlias()
|
||||
console.log()
|
||||
|
||||
console.log(` ${fmt.badge('步骤 4/6', theme.bgBlue)} ${fmt.title('清空变更日志')}`)
|
||||
console.log()
|
||||
await cleanChangeLog()
|
||||
console.log()
|
||||
|
||||
console.log(` ${fmt.badge('步骤 5/6', theme.bgBlue)} ${fmt.title('清理语言文件')}`)
|
||||
console.log()
|
||||
await cleanLanguageFiles()
|
||||
console.log()
|
||||
|
||||
console.log(` ${fmt.badge('步骤 6/7', theme.bgBlue)} ${fmt.title('清理快速入口')}`)
|
||||
console.log()
|
||||
await cleanFastEnterComponent()
|
||||
console.log()
|
||||
|
||||
console.log(` ${fmt.badge('步骤 7/7', theme.bgBlue)} ${fmt.title('更新菜单接口')}`)
|
||||
console.log()
|
||||
await updateMenuApi()
|
||||
|
||||
// 显示统计信息
|
||||
await showStats()
|
||||
|
||||
// 显示成功横幅
|
||||
createSuccessBanner()
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.log()
|
||||
console.log(` ${icons.error} ${fmt.error('清理脚本执行出错')}`)
|
||||
console.log(` ${fmt.dim('错误详情: ' + err)}`)
|
||||
console.log()
|
||||
process.exit(1)
|
||||
})
|
||||
@@ -0,0 +1,34 @@
|
||||
<template>
|
||||
<ElConfigProvider size="default" :locale="locales[language]" :z-index="3000">
|
||||
<RouterView></RouterView>
|
||||
</ElConfigProvider>
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { useUserStore } from "./store/modules/user";
|
||||
import zh from "element-plus/es/locale/lang/zh-cn";
|
||||
import en from "element-plus/es/locale/lang/en";
|
||||
import { systemUpgrade } from "./utils/sys";
|
||||
import { toggleTransition } from "./utils/ui/animation";
|
||||
import { checkStorageCompatibility } from "./utils/storage";
|
||||
import { initializeTheme } from "./hooks/core/useTheme";
|
||||
|
||||
const userStore = useUserStore();
|
||||
const { language } = storeToRefs(userStore);
|
||||
|
||||
const locales = {
|
||||
zh: zh,
|
||||
en: en,
|
||||
};
|
||||
|
||||
onBeforeMount(() => {
|
||||
toggleTransition(true);
|
||||
initializeTheme();
|
||||
});
|
||||
|
||||
onMounted(() => {
|
||||
checkStorageCompatibility();
|
||||
toggleTransition(false);
|
||||
systemUpgrade();
|
||||
});
|
||||
</script>
|
||||
@@ -0,0 +1,136 @@
|
||||
import request from '@/utils/http'
|
||||
|
||||
type Method = 'list' | 'get' | 'create' | 'update' | 'patch' | 'remove' | 'delete' | 'export' | 'changStatus'
|
||||
|
||||
interface BaseApiOptions {
|
||||
baseUrl: string
|
||||
forbiddenMethods?: Method[]
|
||||
allowedMethods?: Method[]
|
||||
}
|
||||
|
||||
function BaseService<T = any>(options: BaseApiOptions) {
|
||||
const { baseUrl, forbiddenMethods = [], allowedMethods = [] } = options
|
||||
|
||||
function checkMethod(method: Method) {
|
||||
if (forbiddenMethods.includes(method)) {
|
||||
throw new Error(`${method} method is forbidden.`)
|
||||
}
|
||||
if (allowedMethods.length > 0 && !allowedMethods.includes(method)) {
|
||||
throw new Error(`${method} method is not allowed.`)
|
||||
}
|
||||
}
|
||||
|
||||
const list = async (params?: Record<string, any>): Promise<T[]> => {
|
||||
checkMethod('list')
|
||||
return request.get<T[]>({
|
||||
url: baseUrl,
|
||||
params,
|
||||
})
|
||||
}
|
||||
|
||||
const get = (id: string | number): Promise<T> => {
|
||||
checkMethod('get')
|
||||
return request.get<T>({
|
||||
url: `${baseUrl}/${id}`,
|
||||
})
|
||||
}
|
||||
|
||||
const create = (params: Omit<T, 'id'>): Promise<T> => {
|
||||
checkMethod('create')
|
||||
return request.post<T>({
|
||||
url: baseUrl,
|
||||
params,
|
||||
})
|
||||
}
|
||||
|
||||
// update(params: Partial<T>): Promise<T> {
|
||||
// checkMethod('update')
|
||||
// return request.put<T>({
|
||||
// url: baseUrl,
|
||||
// params,
|
||||
// })
|
||||
// },
|
||||
const update = (id: string | number | Partial<T>, params?: Partial<T>): Promise<T> => {
|
||||
checkMethod('update');
|
||||
if (typeof id === 'object' && id !== null) {
|
||||
//@ts-expect-error
|
||||
const { id: extractedId, ...extractedParams } = id;
|
||||
if (extractedId !== undefined) {
|
||||
return request.put<T>({
|
||||
url: `${baseUrl}/${extractedId}`,
|
||||
params: extractedParams,
|
||||
});
|
||||
} else {
|
||||
return request.put<T>({
|
||||
url: baseUrl,
|
||||
params: id,
|
||||
});
|
||||
}
|
||||
} else {
|
||||
return request.put<T>({
|
||||
url: `${baseUrl}/${id}`,
|
||||
params,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const patch = (id: string | number, params: Partial<T>): Promise<T> => {
|
||||
checkMethod('patch')
|
||||
return request.patch<T>({
|
||||
url: `${baseUrl}/${id}`,
|
||||
params,
|
||||
})
|
||||
}
|
||||
|
||||
const remove = (params?: Record<string, any>): Promise<T> => {
|
||||
checkMethod('remove')
|
||||
return request.del({
|
||||
url: `${baseUrl}`,
|
||||
params,
|
||||
})
|
||||
}
|
||||
|
||||
const del = (id: string | number, params?: Record<string, any>): Promise<T> => {
|
||||
checkMethod('delete')
|
||||
return request.del({
|
||||
url: `${baseUrl}/${id}`,
|
||||
params,
|
||||
})
|
||||
}
|
||||
|
||||
const exportData = (params: any) => {
|
||||
checkMethod('export')
|
||||
return request.post({
|
||||
url: `${baseUrl}/export`,
|
||||
params,
|
||||
})
|
||||
}
|
||||
|
||||
const changStatus = (id: string | number, params: Partial<T>): Promise<T> => {
|
||||
checkMethod('changStatus')
|
||||
return request.put<T>({
|
||||
url: `${baseUrl}/${id}/change-status`,
|
||||
params,
|
||||
})
|
||||
}
|
||||
const detail = (params: any): Promise<T> => {
|
||||
checkMethod('get')
|
||||
const { id } = params;
|
||||
return request.get<T>({
|
||||
url: `${baseUrl}/${id}`,
|
||||
})
|
||||
}
|
||||
|
||||
const methods = { list, get, create, update, patch, remove, delete: del, export: exportData, changStatus, detail }
|
||||
Object.values(methods).forEach((method) => {
|
||||
Object.defineProperty(method, '__baseUrl', {
|
||||
value: baseUrl,
|
||||
enumerable: false,
|
||||
configurable: true,
|
||||
})
|
||||
})
|
||||
|
||||
return methods
|
||||
}
|
||||
|
||||
export default BaseService
|
||||
@@ -0,0 +1,11 @@
|
||||
import request from "@/utils/http";
|
||||
|
||||
export class DictService {
|
||||
// 获取所有字典
|
||||
static getAllDict() {
|
||||
return request.get<any>({
|
||||
url: "/dict/dict/enum-dict-list",
|
||||
showErrorMessage: false, // 不显示错误消息
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import BaseService from "./base"; // 导入基础服务类
|
||||
import request from '@/utils/http'; // 导入HTTP请求工具
|
||||
|
||||
|
||||
const baseUrl = '/export';
|
||||
|
||||
const DownloadService = {
|
||||
...BaseService<any>({ baseUrl }),
|
||||
/**
|
||||
* 导出Excel文件
|
||||
* @param params 导出参数(需与后端约定,如`reportType`报表类型、`dateRange`日期范围)
|
||||
* @returns 返回包含文件URL的响应对象(前端可通过URL触发文件下载)
|
||||
* @example
|
||||
* // 调用示例:导出用户数据Excel
|
||||
* downloadApi.excelDownload({
|
||||
* reportType: 'user',
|
||||
* dateRange: ['2024-01-01', '2024-03-31']
|
||||
* })
|
||||
*/
|
||||
excelDownload(params: any) {
|
||||
return request.get({
|
||||
url: `${baseUrl}/download`, // 对接后端`/export/download`接口
|
||||
params // 传递导出参数
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
export default DownloadService
|
||||
@@ -0,0 +1,46 @@
|
||||
import BaseService from '@/api/core/base';
|
||||
import request from '@/utils/http';
|
||||
|
||||
const baseUrl = '/member/auth';
|
||||
|
||||
export const AuthService = {
|
||||
...BaseService<any>({ baseUrl }),
|
||||
tagMenuIds(params: any) {
|
||||
return request.get({ url: `${baseUrl}/tag-menu-ids`, params });
|
||||
},
|
||||
|
||||
tagScopeIds(params: any) {
|
||||
return request.get({ url: `${baseUrl}/tag-scope-ids`, params });
|
||||
},
|
||||
|
||||
userListByTagId(params: any) {
|
||||
return request.get({ url: `${baseUrl}/user-list-by-tag-id`, params });
|
||||
},
|
||||
|
||||
userListExcludeTagId(params: any) {
|
||||
return request.get({ url: `${baseUrl}/user-list-exclude-tag-id`, params });
|
||||
},
|
||||
|
||||
removeUserTag(data: any) {
|
||||
return request.post({ url: `${baseUrl}/remove-user-tag`, data });
|
||||
},
|
||||
|
||||
saveTagMenu(data: any) {
|
||||
return request.post({ url: `${baseUrl}/save-tag-menu`, data });
|
||||
},
|
||||
|
||||
saveUserTag(data: any) {
|
||||
return request.post({ url: `${baseUrl}/save-user-tag`, data });
|
||||
},
|
||||
|
||||
getMemberAllPermission() {
|
||||
return request.get({ url: `${baseUrl}/permissions`, params: {} });
|
||||
},
|
||||
|
||||
getMemberPermissionByTagId(id: number | string, params: any) {
|
||||
return request.get({ url: `${baseUrl}/${id}/tag-permissions`, params: { format: 'tree' } });
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
export default AuthService;
|
||||
@@ -0,0 +1 @@
|
||||
export * from './auth';
|
||||
@@ -0,0 +1,6 @@
|
||||
import { MemberService } from './user';
|
||||
import { MemberTagService } from './tag';
|
||||
import { MemberLevelService } from './level';
|
||||
import { MemberPointsService } from './points';
|
||||
|
||||
export { MemberService, MemberTagService, MemberLevelService, MemberPointsService };
|
||||
@@ -0,0 +1,29 @@
|
||||
import BaseService from '@/api/core/base'
|
||||
import request from '@/utils/http'
|
||||
import type { MemberLevel } from './types'
|
||||
|
||||
const baseUrl = '/member/level'
|
||||
|
||||
export const MemberLevelService = {
|
||||
...BaseService<MemberLevel>({
|
||||
baseUrl,
|
||||
allowedMethods: [],
|
||||
forbiddenMethods: [],
|
||||
}),
|
||||
|
||||
getLevelMembers(levelId: number, params?: Record<string, any>): Promise<any> {
|
||||
return request.get({ url: `${baseUrl}/${levelId}/members`, params })
|
||||
},
|
||||
|
||||
getEnabledLevels(): Promise<MemberLevel[]> {
|
||||
return request.get({ url: `${baseUrl}/enabled` })
|
||||
},
|
||||
|
||||
updateSort(data: Record<string, any>): Promise<any> {
|
||||
return request.put({ url: `${baseUrl}/update-sort`, data })
|
||||
},
|
||||
|
||||
checkDelete(levelId: number): Promise<any> {
|
||||
return request.get({ url: `${baseUrl}/check-delete/${levelId}` })
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* 会员等级模型
|
||||
*/
|
||||
export interface MemberLevel {
|
||||
id?: number;
|
||||
name?: string;
|
||||
icon?: string;
|
||||
min_points?: number;
|
||||
discount?: number;
|
||||
description?: string;
|
||||
member_count?: number;
|
||||
sort?: number;
|
||||
status?: number;
|
||||
create_time?: string;
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import BaseService from '@/api/core/base'
|
||||
import request from '@/utils/http'
|
||||
import type { MemberPointsLog } from './types'
|
||||
|
||||
const baseUrl = '/member/points'
|
||||
|
||||
export const MemberPointsService = {
|
||||
...BaseService<MemberPointsLog>({
|
||||
baseUrl,
|
||||
allowedMethods: [],
|
||||
forbiddenMethods: ['remove', 'update'],
|
||||
}),
|
||||
|
||||
operate(data: Record<string, any>): Promise<any> {
|
||||
return request.post({ url: `${baseUrl}/operate`, data });
|
||||
},
|
||||
|
||||
batchOperate(data: Record<string, any>): Promise<any> {
|
||||
return request.post({ url: `${baseUrl}/batch-operate`, data })
|
||||
},
|
||||
|
||||
getStatistics(params?: Record<string, any>): Promise<any> {
|
||||
return request.get({ url: `${baseUrl}/statistics`, params })
|
||||
},
|
||||
|
||||
exportLogs(params?: Record<string, any>): Promise<any> {
|
||||
return request.get({ url: `${baseUrl}/export`, params, responseType: 'blob' })
|
||||
},
|
||||
|
||||
setRules(data: Record<string, any>): Promise<any> {
|
||||
return request.post({ url: `${baseUrl}/rules`, data })
|
||||
},
|
||||
|
||||
getRules(): Promise<any> {
|
||||
return request.get({ url: `${baseUrl}/rules` })
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* 会员积分记录模型
|
||||
*/
|
||||
export interface MemberPointsLog {
|
||||
id?: number;
|
||||
member_id?: number;
|
||||
username?: string;
|
||||
nickname?: string;
|
||||
type?: number;
|
||||
points?: number;
|
||||
points_before?: number;
|
||||
points_after?: number;
|
||||
source?: string;
|
||||
remark?: string;
|
||||
operator?: string;
|
||||
create_time?: string;
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
import BaseService from '@/api/core/base'
|
||||
import request from '@/utils/http'
|
||||
import type { MemberTag } from './types'
|
||||
|
||||
const baseUrl = '/member/tag'
|
||||
|
||||
export const MemberTagService = {
|
||||
...BaseService<MemberTag>({
|
||||
baseUrl,
|
||||
allowedMethods: [],
|
||||
forbiddenMethods: [],
|
||||
}),
|
||||
|
||||
getTagMembers(tagId: number, params?: Record<string, any>): Promise<any> {
|
||||
return request.get({ url: `${baseUrl}/${tagId}/members`, params })
|
||||
},
|
||||
|
||||
batchAssignTags(data: Record<string, any>): Promise<any> {
|
||||
return request.post({ url: `${baseUrl}/batch-assign`, data })
|
||||
},
|
||||
|
||||
getEnabledTags(): Promise<MemberTag[]> {
|
||||
return request.get({ url: `${baseUrl}/enabled` })
|
||||
},
|
||||
|
||||
updateSort(data: Record<string, any>): Promise<any> {
|
||||
return request.put({ url: `${baseUrl}/update-sort`, data })
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* 会员标签模型
|
||||
*/
|
||||
export interface MemberTag {
|
||||
id?: number;
|
||||
name?: string;
|
||||
color?: string;
|
||||
description?: string;
|
||||
member_count?: number;
|
||||
sort?: number;
|
||||
status?: number;
|
||||
create_time?: string;
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import BaseService from '@/api/core/base'
|
||||
import request from '@/utils/http'
|
||||
import type { Member } from './types'
|
||||
|
||||
const baseUrl = '/member/user'
|
||||
|
||||
export const MemberService = {
|
||||
...BaseService<Member>({
|
||||
baseUrl,
|
||||
allowedMethods: [],
|
||||
forbiddenMethods: [],
|
||||
}),
|
||||
|
||||
resetPassword(id: number, data: Record<string, any>): Promise<any> {
|
||||
return request.put({ url: `${baseUrl}/${id}/reset-password`, data })
|
||||
},
|
||||
|
||||
enable(data: Record<string, any>): Promise<any> {
|
||||
return request.put({ url: `${baseUrl}/enable`, data })
|
||||
},
|
||||
|
||||
disable(data: Record<string, any>): Promise<any> {
|
||||
return request.put({ url: `${baseUrl}/disable`, data })
|
||||
},
|
||||
|
||||
assignTags(id: number | string, data: Record<string, any>): Promise<any> {
|
||||
return request.put({ url: `${baseUrl}/${id}/assign-tags`, data })
|
||||
},
|
||||
|
||||
getDetail(id: number): Promise<Member> {
|
||||
return request.get({ url: `${baseUrl}/${id}` })
|
||||
},
|
||||
|
||||
batchOperation(data: Record<string, any>): Promise<any> {
|
||||
return request.post({ url: `${baseUrl}/batch`, data })
|
||||
},
|
||||
|
||||
statistics(params?: Record<string, any>): Promise<any> {
|
||||
return request.get({ url: `${baseUrl}/statistics`, params })
|
||||
},
|
||||
|
||||
adjustPoints(id: number, data: Record<string, any>): Promise<any> {
|
||||
return request.put({ url: `${baseUrl}/${id}/adjust-points`, data })
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* 会员模型
|
||||
*/
|
||||
export interface Member {
|
||||
id?: number;
|
||||
username?: string;
|
||||
nickname?: string;
|
||||
phone?: string;
|
||||
email?: string;
|
||||
avatar?: string;
|
||||
level_id?: number;
|
||||
level_name?: string;
|
||||
balance?: number;
|
||||
points?: number;
|
||||
status?: number;
|
||||
created_at?: string;
|
||||
updated_at?: string;
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from "./logs";
|
||||
export type * from "./types";
|
||||
@@ -0,0 +1,29 @@
|
||||
import BaseService from '@/api/core/base';
|
||||
import request from '@/utils/http';
|
||||
import type { LoginRecordRow, OnlineUserRow, OperateRecordRow } from './types';
|
||||
|
||||
const loginBaseUrl = '/logs/login';
|
||||
const operateBaseUrl = '/logs/operate';
|
||||
const onlineUserBaseUrl = '/monitor/online-user';
|
||||
|
||||
export const LoginLogService = {
|
||||
...BaseService<LoginRecordRow>({ baseUrl: loginBaseUrl, allowedMethods: ['list', 'get', 'remove'] }),
|
||||
};
|
||||
|
||||
export const OperateLogService = {
|
||||
...BaseService<OperateRecordRow>({ baseUrl: operateBaseUrl, allowedMethods: ['list', 'get', 'remove'] }),
|
||||
};
|
||||
|
||||
export const OnlineUserService = {
|
||||
...BaseService<OnlineUserRow>({ baseUrl: onlineUserBaseUrl, allowedMethods: ['list', 'delete'] }),
|
||||
|
||||
revoke(jti: string) {
|
||||
return request.post({ url: `${onlineUserBaseUrl}/${jti}/revoke` });
|
||||
},
|
||||
};
|
||||
|
||||
export default {
|
||||
loginLogApi: LoginLogService,
|
||||
operateLogApi: OperateLogService,
|
||||
onlineUserApi: OnlineUserService,
|
||||
};
|
||||
@@ -0,0 +1,64 @@
|
||||
|
||||
type Timestamp = number; // Unix 时间戳
|
||||
type DateTime = string; // ISO 8601 格式字符串
|
||||
|
||||
/**
|
||||
* 登录日志
|
||||
*/
|
||||
export interface LoginRecordRow {
|
||||
id: string;
|
||||
user_name: string;
|
||||
ip: string;
|
||||
ip_location: string;
|
||||
os: string;
|
||||
browser: string;
|
||||
status: number;
|
||||
message: string | null;
|
||||
login_time: Timestamp;
|
||||
key: string;
|
||||
remark: string | null;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 操作日志
|
||||
*/
|
||||
export interface OperateRecordRow {
|
||||
id: string;
|
||||
name: string;
|
||||
app: string;
|
||||
ip: string;
|
||||
ip_location: string;
|
||||
browser: string;
|
||||
os: string;
|
||||
url: string;
|
||||
class_name: string;
|
||||
action: string;
|
||||
method: string;
|
||||
param: string;
|
||||
result: string;
|
||||
user_name: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 在线用户
|
||||
*/
|
||||
export interface OnlineUserRow {
|
||||
jti: string;
|
||||
user_id: string;
|
||||
account: string;
|
||||
real_name: string;
|
||||
dept_names: string;
|
||||
post_names: string;
|
||||
role_names: string;
|
||||
ip: string;
|
||||
ip_location: string;
|
||||
device: string;
|
||||
browser: string;
|
||||
status: string;
|
||||
client_type: string;
|
||||
login_time: Timestamp;
|
||||
last_active_at: Timestamp;
|
||||
offline_at: Timestamp | null;
|
||||
online_seconds: number;
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './redis';
|
||||
export type * from './types';
|
||||
@@ -0,0 +1,20 @@
|
||||
import request from '@/utils/http'
|
||||
import { RedisMonitorData } from './types'
|
||||
|
||||
const baseUrl = '/monitor/redis'
|
||||
|
||||
|
||||
/**
|
||||
* Redis监控
|
||||
*/
|
||||
export const RedisService = {
|
||||
/**
|
||||
* 列表
|
||||
* @param params
|
||||
* @returns
|
||||
*/
|
||||
list(params?: Record<string, any>): Promise<RedisMonitorData> {
|
||||
return request.get({ url: baseUrl, params })
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
export interface RedisProps {
|
||||
description?: string;
|
||||
name?: string;
|
||||
title?: string;
|
||||
}
|
||||
|
||||
export interface DescriptionItem {
|
||||
content: Component | string;
|
||||
title: string;
|
||||
}
|
||||
|
||||
|
||||
// 主数据类型
|
||||
export interface RedisMonitorData {
|
||||
uptime_in_seconds: number;
|
||||
connected_clients: number;
|
||||
used_memory: string; // 保持原始格式(如:"686.79 KB")
|
||||
memory_fragmentation_ratio: number;
|
||||
total_commands_processed: number;
|
||||
total_connections_received: number;
|
||||
keyspace_hits: number;
|
||||
keyspace_misses: number;
|
||||
hit_rate: number;
|
||||
variable: RedisVariableInfo;
|
||||
data: RedisDataEntries;
|
||||
}
|
||||
|
||||
// 变量信息类型(嵌套对象)
|
||||
export interface RedisVariableInfo {
|
||||
redis_version: string;
|
||||
redis_git_sha1: number | string; // 原始数据中包含数字0和字符串类型
|
||||
redis_git_dirty: number;
|
||||
redis_build_id: string;
|
||||
redis_mode: "standalone" | "cluster"; // 根据实际值限定
|
||||
os: string;
|
||||
arch_bits: 64 | 32; // 根据实际值限定
|
||||
multiplexing_api: "WinSock_IOCP" | string; // 根据实际值扩展
|
||||
process_id: number;
|
||||
run_id: string;
|
||||
tcp_port: number;
|
||||
uptime_in_seconds: number;
|
||||
uptime_in_days: number;
|
||||
hz: number;
|
||||
lru_clock: number;
|
||||
config_file: string;
|
||||
// 以下字段根据实际数据补充完整类型
|
||||
client_longest_output_list: number;
|
||||
client_biggest_input_buf: number;
|
||||
blocked_clients: number;
|
||||
used_memory: number; // 原始数值(与顶层used_memory格式不同)
|
||||
used_memory_human: string;
|
||||
used_memory_rss: number;
|
||||
used_memory_peak: number;
|
||||
used_memory_peak_human: string;
|
||||
used_memory_lua: number;
|
||||
mem_fragmentation_ratio: number;
|
||||
mem_allocator: string;
|
||||
|
||||
}
|
||||
|
||||
// 数据存储类型(键值对集合)
|
||||
interface RedisDataEntries {
|
||||
[key: string]: string; // 所有值保持原始序列化字符串格式
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './server';
|
||||
export type * from './types';
|
||||
@@ -0,0 +1,18 @@
|
||||
import request from '@/utils/http'
|
||||
import { type ServerInfo } from './types'
|
||||
|
||||
const baseUrl = '/monitor/server'
|
||||
|
||||
/**
|
||||
* 性能监控
|
||||
*/
|
||||
export const ServerService = {
|
||||
/**
|
||||
* 列表
|
||||
* @param params
|
||||
* @returns
|
||||
*/
|
||||
list(params?: Record<string, any>): Promise<ServerInfo> {
|
||||
return request.get({ url: baseUrl, params })
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
// 系统监控数据类型
|
||||
export interface ServerInfo {
|
||||
cpu: CpuInfo;
|
||||
memory: MemoryInfo;
|
||||
disk: DiskInfo[];
|
||||
php: PhpInfo;
|
||||
}
|
||||
|
||||
// CPU 信息类型
|
||||
export interface CpuInfo {
|
||||
cpu_name: string;
|
||||
physical_cores: string; // 物理核心数(字符串类型)
|
||||
logical_cores: string; // 逻辑核心数(字符串类型)
|
||||
cache_size_mb: number; // 缓存大小(MB,数值类型)
|
||||
cpu_usage_percentage: number; // CPU 使用率(百分比)
|
||||
free_cpu_percentage: number; // 空闲 CPU 百分比
|
||||
}
|
||||
|
||||
// 内存信息类型
|
||||
export interface MemoryInfo {
|
||||
total_memory: number; // 总内存(GB,数值类型)
|
||||
available_memory: number; // 可用内存(GB,数值类型)
|
||||
used_memory: number; // 已用内存(GB,数值类型)
|
||||
php_memory_usage: number; // PHP 内存使用率(百分比)
|
||||
memory_usage_rate: string; // 内存使用率(原始字符串格式)
|
||||
}
|
||||
|
||||
// 磁盘信息类型(数组项)
|
||||
export interface DiskInfo {
|
||||
filesystem: string; // 文件系统标识
|
||||
size: string; // 磁盘总大小(含单位)
|
||||
available: string; // 剩余空间(含单位)
|
||||
used: string; // 已用空间(含单位)
|
||||
use_percentage: string; // 使用率百分比(含%符号)
|
||||
mounted_on: string; // 挂载点
|
||||
}
|
||||
|
||||
// PHP 配置信息类型
|
||||
export interface PhpInfo {
|
||||
php_version: string; // PHP 版本
|
||||
os: string; // 操作系统类型
|
||||
project_path: string; // 项目路径
|
||||
memory_limit: string; // 内存限制
|
||||
max_execution_time: string; // 最大执行时间
|
||||
error_reporting: string; // 错误报告级别
|
||||
display_errors: "on" | "off"; // 错误显示开关(严格类型)
|
||||
upload_max_filesize: string; // 最大上传文件大小
|
||||
post_max_size: string; // POST 数据最大大小
|
||||
extension_dir: string; // 扩展目录
|
||||
loaded_extensions: string; // 已加载扩展(逗号分隔)
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
export * from '@/utils/http';
|
||||
|
||||
|
||||
|
||||
export const getApiBaseUrl = (customValue?: string | null): string => {
|
||||
const getBaseUrl = (value: string): string => {
|
||||
// 如果是 useCurrentDomain,返回当前域名并添加 /adminapi
|
||||
if (value === 'useCurrentDomain') {
|
||||
const currentDomain = window.location.protocol + '//' + window.location.host;
|
||||
return currentDomain + '/adminapi';
|
||||
}
|
||||
|
||||
// 如果是完整 URL,直接返回
|
||||
if (/^https?:\/\//i.test(value)) {
|
||||
return value;
|
||||
}
|
||||
|
||||
// 如果是相对路径,确保以 / 开头
|
||||
const normalizedPath = value.replace(/^\/+/, '');
|
||||
|
||||
// 如果是当前域名,添加 /adminapi
|
||||
if (normalizedPath === '') {
|
||||
return window.location.protocol + '//' + window.location.host + '/adminapi';
|
||||
}
|
||||
|
||||
// 如果已经是 /adminapi 开头,直接返回
|
||||
if (normalizedPath.startsWith('adminapi')) {
|
||||
return window.location.protocol + '//' + window.location.host + '/' + normalizedPath;
|
||||
}
|
||||
|
||||
// 其他相对路径,添加当前域名
|
||||
return window.location.protocol + '//' + window.location.host + '/' + normalizedPath;
|
||||
};
|
||||
|
||||
if (customValue) {
|
||||
return getBaseUrl(customValue);
|
||||
}
|
||||
|
||||
const envValue = import.meta.env.VITE_API_URL;
|
||||
if (envValue) {
|
||||
return getBaseUrl(envValue);
|
||||
}
|
||||
|
||||
// 默认返回当前域名 + /adminapi
|
||||
return window.location.protocol + '//' + window.location.host + '/adminapi';
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import request from '@/utils/http'
|
||||
|
||||
const baseUrl = '/system/config'
|
||||
|
||||
export const ConfigService = {
|
||||
getByCode($code: string, params?: Record<string, any>) {
|
||||
return request.get({ url: `${baseUrl}/code/${$code}`, params })
|
||||
},
|
||||
getApiEncryptDetail() {
|
||||
return request.get({ url: '/system/api-encrypt' })
|
||||
},
|
||||
generateApiEncryptKeypair() {
|
||||
return request.post({ url: '/system/api-encrypt/generate-keypair' })
|
||||
},
|
||||
getByGroup(groupCode: string, params?: Record<string, any>) {
|
||||
return request.get({ url: `${baseUrl}/group/${groupCode}`, params })
|
||||
},
|
||||
getItems(params: Record<string, any>) {
|
||||
return request.get({ url: `${baseUrl}/items`, params })
|
||||
},
|
||||
update(code: string, data: Record<string, any>) {
|
||||
return request.put({ url: `${baseUrl}/${code}`, data })
|
||||
},
|
||||
save(data: Record<string, any>) {
|
||||
return request.post({ url: baseUrl, data })
|
||||
},
|
||||
delete(code: string) {
|
||||
return request.del({ url: `${baseUrl}/${code}` })
|
||||
}
|
||||
}
|
||||
|
||||
export default ConfigService
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './config';
|
||||
export type * from './types';
|
||||
@@ -0,0 +1,557 @@
|
||||
|
||||
/**
|
||||
* 网站核心配置接口
|
||||
* @desc 包含站点基础信息、备案数据及安全认证配置
|
||||
*/
|
||||
export interface SiteConfig {
|
||||
/**
|
||||
* 站点开放状态
|
||||
* @example "1" - 启用状态
|
||||
* @example "0" - 维护状态
|
||||
*/
|
||||
site_open: '0' | '1';
|
||||
|
||||
/** 站点访问地址 (需包含协议头) */
|
||||
site_url: string;
|
||||
|
||||
/** 站点名称 (中文/英文品牌标识) */
|
||||
site_name: string;
|
||||
|
||||
/**
|
||||
* 站点LOGO地址
|
||||
* @pattern 支持SVG/PNG格式资源路径
|
||||
*/
|
||||
site_logo: string;
|
||||
|
||||
/**
|
||||
* 公安备案编号
|
||||
* @example "京公网安备 2024042441号-2"
|
||||
*/
|
||||
site_network_security: string;
|
||||
|
||||
/** 站点描述信息 (SEO优化关键字段) */
|
||||
site_description: string;
|
||||
|
||||
/** ICP备案号 (省级备案编号) */
|
||||
site_record_no: string;
|
||||
|
||||
cdn_url?:string;
|
||||
|
||||
cdn_url_params?:string;
|
||||
|
||||
/**
|
||||
* ICP备案跳转链接
|
||||
* @link https://beian.miit.gov.cn/
|
||||
*/
|
||||
site_icp_url: string;
|
||||
|
||||
/**
|
||||
* 公安备案跳转链接
|
||||
* @desc 当前为空时需要隐藏备案跳转按钮
|
||||
*/
|
||||
site_network_security_url: string | null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
/**
|
||||
* 文件传输策略配置
|
||||
* @desc 控制文件上传/下载的精细化策略
|
||||
*/
|
||||
export interface FilePolicyConfig {
|
||||
/**
|
||||
* 存储模式
|
||||
* @default "local"
|
||||
* @enum ["local", "remote", "hybrid"] - 混合模式需配合CDN配置
|
||||
*/
|
||||
mode: "local" | "remote" | "hybrid";
|
||||
|
||||
/**
|
||||
* 单文件大小限制(单位:MB)
|
||||
* @minimum 1 - 允许最小1MB
|
||||
* @maximum 2048 - 系统最大支持2GB
|
||||
*/
|
||||
single_limit: number;
|
||||
|
||||
/**
|
||||
* 总传输容量限制(单位:MB)
|
||||
* @note 当模式为remote时自动提升至5000MB
|
||||
*/
|
||||
total_limit: number;
|
||||
|
||||
/**
|
||||
* 并发传输数
|
||||
* @minimum 1
|
||||
* @maximum 50 - 受服务器性能限制
|
||||
*/
|
||||
nums: number;
|
||||
|
||||
/**
|
||||
* 禁止传输的文件类型
|
||||
* @pattern 以逗号分隔的扩展名,不包含点符号
|
||||
* @example "php,ext,exe" - 高危文件类型过滤
|
||||
*/
|
||||
exclude: string;
|
||||
|
||||
/**
|
||||
* 扩展校验规则(可选)
|
||||
* @beta 实验性功能
|
||||
*/
|
||||
advanced?: {
|
||||
/** 哈希校验算法 */
|
||||
hash_algorithm?: "MD5" | "SHA256";
|
||||
/** 病毒扫描开关 */
|
||||
virus_scan?: boolean;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* SMTP 邮件服务配置
|
||||
* @desc 包含加密协议、认证信息及发件人配置
|
||||
* @link [QQ邮箱SMTP文档](https://service.mail.qq.com/detail/0/75)
|
||||
*/
|
||||
export interface EmailConfig {
|
||||
/**
|
||||
* 传输层安全协议
|
||||
* @enum ["ssl", "tls"] - QQ邮箱强制要求SSL加密
|
||||
* @example "ssl" - 标准加密协议
|
||||
*/
|
||||
SMTPSecure: "ssl" | "tls";
|
||||
|
||||
/**
|
||||
* SMTP服务器地址
|
||||
* @pattern 域名或IP地址
|
||||
* @default "smtp.qq.com"
|
||||
*/
|
||||
Host: string;
|
||||
|
||||
/**
|
||||
* 服务端口号
|
||||
* @default 465 - SSL协议专用端口
|
||||
* @see 587 - TLS协议备用端口
|
||||
*/
|
||||
Port: 465 | 587;
|
||||
|
||||
/**
|
||||
* 认证邮箱账号
|
||||
* @format 需开启SMTP服务的完整邮箱
|
||||
* @example "kzhzjdyw888@qq.com"
|
||||
*/
|
||||
Username: `${string}@${string}.${string}`;
|
||||
|
||||
/**
|
||||
* SMTP授权码
|
||||
* @security Sensitive - 需加密存储
|
||||
* @note 非QQ邮箱登录密码
|
||||
*/
|
||||
Password: string;
|
||||
|
||||
/**
|
||||
* 发件人邮箱
|
||||
* @rule 需与认证账号一致
|
||||
* @example "service@company.com"
|
||||
*/
|
||||
From: `${string}@${string}.${string}`;
|
||||
|
||||
/**
|
||||
* 发件人显示名称
|
||||
* @maxLength 30 - 客户端显示限制
|
||||
* @example "【企业名称】系统通知"
|
||||
*/
|
||||
FromName: string;
|
||||
|
||||
/**
|
||||
* 高级连接配置(可选)
|
||||
*/
|
||||
advanced?: {
|
||||
/** 超时时间(毫秒) */
|
||||
timeout?: number;
|
||||
/** 调试模式日志级别 */
|
||||
debugLevel?: "none" | "basic" | "full";
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 短信服务全局配置
|
||||
* @desc 包含服务商认证、签名模板及服务开关
|
||||
*/
|
||||
export interface SMSConfig {
|
||||
/**
|
||||
* 服务开关
|
||||
* @enum ["0", "1"] - 0=禁用,1=启用
|
||||
* @security 敏感操作需二次验证
|
||||
*/
|
||||
enable: "0" | "1";
|
||||
|
||||
/**
|
||||
* 服务商访问密钥ID
|
||||
* @pattern 由服务商颁发的18-32位数字ID
|
||||
* @example "234813346262818816"
|
||||
*/
|
||||
access_key_id: string;
|
||||
|
||||
/**
|
||||
* 服务商访问密钥
|
||||
* @security Sensitive - 需加密存储
|
||||
* @note 泄漏将导致账号被盗风险
|
||||
*/
|
||||
access_key_secret: string;
|
||||
|
||||
/**
|
||||
* 短信签名模板
|
||||
* @template 必须包含 {code} 占位符
|
||||
* @example "【企业名称】,你的验证码是{code},有效期5分钟。"
|
||||
*/
|
||||
sign_name: string;
|
||||
|
||||
/**
|
||||
* 高级配置(可选)
|
||||
*/
|
||||
advanced?: {
|
||||
/** 请求超时时间(毫秒) */
|
||||
timeout?: number;
|
||||
/** 失败重试次数 */
|
||||
retry_times?: number;
|
||||
/** 地域节点 */
|
||||
region_id?: "cn-hangzhou" | "ap-southeast-1";
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 本地文件上传配置
|
||||
* @desc 包含存储路径、域名配置等核心参数
|
||||
*/
|
||||
export interface LocalUploadConfig {
|
||||
/**
|
||||
* 文件存储根目录
|
||||
* @example "public" - 对应项目根目录下的/public文件夹
|
||||
* @pattern 仅允许字母、数字、下划线组合
|
||||
*/
|
||||
root: string;
|
||||
|
||||
/**
|
||||
* 动态子目录名称
|
||||
* @example "upload" - 最终路径为 /public/upload/
|
||||
* @rule 禁止包含路径分隔符(/或\)
|
||||
*/
|
||||
dirname: string;
|
||||
|
||||
/**
|
||||
* 资源访问域名
|
||||
* @format 需包含协议头和端口号(非80/443端口时必须显式声明)
|
||||
* @example "http://cdn.example.com:8080"
|
||||
*/
|
||||
domain: `http://${string}` | `https://${string}`;
|
||||
|
||||
/**
|
||||
* 高级存储策略(可选)
|
||||
*/
|
||||
advanced?: {
|
||||
/** 文件名生成策略 */
|
||||
filenameStrategy?: "timestamp" | "hash" | "original";
|
||||
/** 目录深度分级 */
|
||||
depthLevel?: number;
|
||||
/** 自动清理过期文件(单位:天) */
|
||||
autoPurgeDays?: number;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 阿里云OSS上传配置
|
||||
* @desc 包含身份认证、存储桶配置及访问策略
|
||||
* @link [OSS官方文档](https://help.aliyun.com/zh/oss)
|
||||
*/
|
||||
interface AliyunOSSConfig {
|
||||
/**
|
||||
* 访问密钥ID
|
||||
* @security Sensitive - 需加密存储
|
||||
* @pattern 由阿里云颁发的LTAI开头的32位字符
|
||||
* @example "LTAI5txxxxxxxxxxxxxxx"
|
||||
*/
|
||||
accessKeyId: string;
|
||||
|
||||
/**
|
||||
* 访问密钥凭证
|
||||
* @security Sensitive - 需二次加密
|
||||
* @note 泄漏将导致存储桶被恶意操控
|
||||
*/
|
||||
accessKeySecret: string;
|
||||
|
||||
/**
|
||||
* 存储桶名称
|
||||
* @rule 全局唯一且符合OSS命名规范
|
||||
* @pattern 全小写字母、数字及连字符组合
|
||||
* @example "madong-tech-prod"
|
||||
*/
|
||||
bucket: string;
|
||||
|
||||
/**
|
||||
* 存储目录路径
|
||||
* @default "upload"
|
||||
* @rule 禁止包含../等路径穿越符号
|
||||
*/
|
||||
dirname: string;
|
||||
|
||||
/**
|
||||
* 自定义访问域名
|
||||
* @format 需包含协议头及CNAME解析
|
||||
* @example "https://oss.madong.tech"
|
||||
*/
|
||||
domain: `http://${string}` | `https://${string}`;
|
||||
|
||||
/**
|
||||
* 区域终端节点
|
||||
* @enum 常用节点
|
||||
* @example "oss-cn-hangzhou.aliyuncs.com" - 杭州地域
|
||||
* @example "oss-ap-southeast-1.aliyuncs.com" - 新加坡地域
|
||||
*/
|
||||
endpoint: string;
|
||||
|
||||
/**
|
||||
* 配置备注说明(可选)
|
||||
* @maxLength 200 - 控制台显示限制
|
||||
*/
|
||||
remark?: string;
|
||||
|
||||
/**
|
||||
* 高级传输策略(可选)
|
||||
*/
|
||||
advanced?: {
|
||||
/** 分片上传阈值(单位:MB) */
|
||||
multipartThreshold?: number;
|
||||
/** 并行上传线程数 */
|
||||
parallel?: number;
|
||||
/** 超时重试次数 */
|
||||
maxRetries?: number;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 腾讯云COS存储配置
|
||||
* @desc 包含密钥管理、存储桶策略及访问控制
|
||||
* @link [COS官方文档](https://cloud.tencent.com/document/product/436)
|
||||
*/
|
||||
interface TencentCOSConfig {
|
||||
/**
|
||||
* 云API密钥ID
|
||||
* @security Sensitive - 需加密存储
|
||||
* @pattern 腾讯云标准的AKID开头32位字符
|
||||
* @example "AKID5dxxxxxxxxxxxxxxxxxxxxxx"
|
||||
*/
|
||||
secretId: string;
|
||||
|
||||
/**
|
||||
* 云API密钥凭证
|
||||
* @security Sensitive - 需二次加密
|
||||
* @note 泄漏将导致存储桶数据泄露风险
|
||||
*/
|
||||
secretKey: string;
|
||||
|
||||
/**
|
||||
* 存储桶名称
|
||||
* @rule 命名格式为 [bucket]-[appid]
|
||||
* @example "webassets-1250000000"
|
||||
* @pattern ^[a-z0-9-]+-\d+$
|
||||
*/
|
||||
bucket: string;
|
||||
|
||||
/**
|
||||
* 存储目录路径
|
||||
* @default "uploads"
|
||||
* @rule 禁止使用 ../ 等路径穿越符号
|
||||
*/
|
||||
dirname: string;
|
||||
|
||||
/**
|
||||
* 自定义访问域名
|
||||
* @format 需包含协议头及CDN加速配置
|
||||
* @example "https://cos.madong.tech"
|
||||
*/
|
||||
domain: `http://${string}` | `https://${string}`;
|
||||
|
||||
/**
|
||||
* 存储地域标识
|
||||
* @enum ["ap-beijing", "ap-shanghai", "ap-guangzhou"]
|
||||
* @example "ap-guangzhou" - 广州地域
|
||||
*/
|
||||
region: string;
|
||||
|
||||
/**
|
||||
* 配置描述信息(可选)
|
||||
* @maxLength 100 - 控制台显示限制
|
||||
*/
|
||||
remark?: string;
|
||||
|
||||
/**
|
||||
* 高级传输配置(可选)
|
||||
*/
|
||||
advanced?: {
|
||||
/** 分块上传阈值(单位:MB) */
|
||||
chunkSize?: number;
|
||||
/** 并发上传线程数 */
|
||||
concurrency?: number;
|
||||
/** 临时密钥有效期(秒) */
|
||||
tempKeyDuration?: number;
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 七牛云Kodo存储配置
|
||||
* @desc 包含访问凭证、存储区域策略及域名配置
|
||||
* @link [七牛云开发者文档](https://developer.qiniu.com/)
|
||||
*/
|
||||
interface QiniuConfig {
|
||||
/**
|
||||
* 访问密钥(AK)
|
||||
* @security Sensitive - 需加密存储
|
||||
* @pattern 七牛云标准的32位字符密钥
|
||||
* @example "AK-5dxxxxxxxxxxxxxxxxxxxxxx"
|
||||
*/
|
||||
accessKey: string;
|
||||
|
||||
/**
|
||||
* 安全密钥(SK)
|
||||
* @security Sensitive - 需硬件加密模块保护
|
||||
* @note 泄漏将导致存储桶完全失控
|
||||
*/
|
||||
secretKey: string;
|
||||
|
||||
/**
|
||||
* 存储空间名称
|
||||
* @rule 全局唯一且符合DNS命名规范
|
||||
* @pattern ^[a-z0-9-]{3,63}$ - 全小写字母、数字、连字符
|
||||
* @example "madong-prod-bucket"
|
||||
*/
|
||||
bucket: string;
|
||||
|
||||
/**
|
||||
* 存储目录路径
|
||||
* @default "uploads"
|
||||
* @rule 禁止使用路径操作符(如../)
|
||||
*/
|
||||
dirname: string;
|
||||
|
||||
/**
|
||||
* 自定义加速域名
|
||||
* @format 需完成CNAME解析的HTTPS域名
|
||||
* @example "https://kodo.madong.tech"
|
||||
*/
|
||||
domain: `https://${string}`;
|
||||
|
||||
/**
|
||||
* 存储区域标识
|
||||
* @enum ["z0", "z1", "z2", "na0", "as0"]
|
||||
* @example "z0" - 华东区域
|
||||
* @example "na0" - 北美区域
|
||||
*/
|
||||
region: string;
|
||||
|
||||
/**
|
||||
* 配置备注(可选)
|
||||
* @maxLength 100 - 控制台显示限制
|
||||
*/
|
||||
remark?: string;
|
||||
|
||||
/**
|
||||
* 高级上传策略(可选)
|
||||
*/
|
||||
advanced?: {
|
||||
/** 分片上传阈值(单位:MB) */
|
||||
chunkSize?: 4 | 8 | 16;
|
||||
/** 直传/代理上传模式 */
|
||||
uploadMode?: "direct" | "proxy";
|
||||
/** 防盗链Token有效期(秒) */
|
||||
antiLeechTTL?: number;
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* 亚马逊S3存储配置
|
||||
* @desc 包含IAM凭证、存储桶策略及跨区域复制配置
|
||||
* @link [AWS S3官方文档](https://docs.aws.amazon.com/s3/)
|
||||
*/
|
||||
export interface AmazonS3Config {
|
||||
/**
|
||||
* IAM访问密钥ID
|
||||
* @security Sensitive - 需KMS加密存储
|
||||
* @pattern AWS标准的20位字母数字组合
|
||||
* @example "AKIAIOSFODNN7EXAMPLE"
|
||||
*/
|
||||
key: string;
|
||||
|
||||
/**
|
||||
* IAM安全访问密钥
|
||||
* @security Sensitive - 需硬件安全模块保护
|
||||
* @note 泄漏将导致存储桶完全失控
|
||||
*/
|
||||
secret: string;
|
||||
|
||||
/**
|
||||
* 存储桶名称
|
||||
* @rule 全局唯一且符合DNS命名规范
|
||||
* @pattern ^[a-z0-9.-]{3,63}$ - 全小写字母、数字、点、连字符
|
||||
* @example "madong.enterprise.data"
|
||||
*/
|
||||
bucket: string;
|
||||
|
||||
/**
|
||||
* 存储路径前缀
|
||||
* @default "uploads"
|
||||
* @rule 禁止使用路径操作符(如../)
|
||||
*/
|
||||
dirname: string;
|
||||
|
||||
/**
|
||||
* 自定义访问域名
|
||||
* @format 需完成CNAME解析的HTTPS域名
|
||||
* @example "https://s3.madong.tech"
|
||||
*/
|
||||
domain: `https://${string}`;
|
||||
|
||||
/**
|
||||
* 服务终端节点
|
||||
* @enum ["s3.amazonaws.com", "s3.us-east-2.amazonaws.com"]
|
||||
* @example "s3.ap-southeast-1.amazonaws.com" - 新加坡节点
|
||||
*/
|
||||
endpoint: string;
|
||||
|
||||
/**
|
||||
* 存储区域标识
|
||||
* @enum ["us-east-1", "eu-central-1", "ap-northeast-1"]
|
||||
* @example "ap-northeast-1" - 东京区域
|
||||
*/
|
||||
region: string;
|
||||
|
||||
/**
|
||||
* 访问控制策略
|
||||
* @enum ["private", "public-read", "bucket-owner-full-control"]
|
||||
* @default "private" - 默认私有读写
|
||||
*/
|
||||
acl: string;
|
||||
|
||||
/**
|
||||
* 配置描述(可选)
|
||||
* @maxLength 200 - 控制台显示限制
|
||||
*/
|
||||
remark?: string;
|
||||
|
||||
/**
|
||||
* 高级安全策略(可选)
|
||||
*/
|
||||
advanced?: {
|
||||
/** 服务端加密算法 */
|
||||
encryption?: "AES256" | "aws:kms";
|
||||
/** 跨区域复制配置 */
|
||||
replication?: {
|
||||
destinationRegion: string;
|
||||
syncDelayMinutes: number;
|
||||
};
|
||||
/** 生命周期规则(天数) */
|
||||
lifecycleRules?: {
|
||||
transitionToGlacier?: number;
|
||||
expiration?: number;
|
||||
};
|
||||
};
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import BaseService from "@/api/core/base";
|
||||
import type { DeptRow, DeptTree } from "./dept-model";
|
||||
import request from '@/utils/http';
|
||||
|
||||
const baseUrl = '/org/dept';
|
||||
|
||||
export const DeptService = {
|
||||
...BaseService<DeptRow>({ baseUrl }),
|
||||
|
||||
getTree(args?: Record<string, any>): Promise<DeptTree[]> {
|
||||
const params = { ...args, format: 'tree' };
|
||||
return request.get<DeptTree[]>({ url: baseUrl, params });
|
||||
},
|
||||
};
|
||||
|
||||
export default DeptService;
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './dept';
|
||||
export type * from './dept-model';
|
||||
@@ -0,0 +1,51 @@
|
||||
import { DeptTree } from './dept-model'
|
||||
/**
|
||||
* 部门实体接口
|
||||
*/
|
||||
export interface DeptRow {
|
||||
id: string
|
||||
pid: string | number
|
||||
level: string
|
||||
code: string
|
||||
name: string
|
||||
main_leader_id: string
|
||||
phone: string
|
||||
enabled: string
|
||||
sort: string
|
||||
children?: DeptRow[]
|
||||
remark: string
|
||||
}
|
||||
|
||||
export interface DeptTree {
|
||||
[key: string]: any
|
||||
children?: DeptRow[]
|
||||
id: string
|
||||
name: string
|
||||
remark?: string
|
||||
enabled: number
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 部门树
|
||||
*/
|
||||
export interface DeptTree {
|
||||
id: number | string
|
||||
/**
|
||||
* antd组件必须要这个属性 实际是没有这个属性的
|
||||
*/
|
||||
key?: string
|
||||
pid: number | string
|
||||
name: string
|
||||
sort: number
|
||||
children?: DeptTableTree[]
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
export interface DeptOptionsTree {
|
||||
id?: number | string
|
||||
label: string
|
||||
value: string | number
|
||||
children?: DeptOptionsTree[]
|
||||
}
|
||||
@@ -0,0 +1,10 @@
|
||||
import BaseService from '@/api/core/base'
|
||||
import type { DictItemRow } from './types'
|
||||
|
||||
const baseUrl = '/dict/item'
|
||||
|
||||
export const DictItemService = {
|
||||
...BaseService<DictItemRow>({ baseUrl })
|
||||
}
|
||||
|
||||
export default DictItemService
|
||||
@@ -0,0 +1,28 @@
|
||||
import BaseService from "@/api/core/base";
|
||||
import type { DictOptions, DictRow } from "./types";
|
||||
import request from '@/utils/http';
|
||||
|
||||
const baseUrl = '/dict/dict';
|
||||
|
||||
export const DictService = {
|
||||
...BaseService<DictRow>({ baseUrl }),
|
||||
|
||||
enumDictList(params: any): Promise<DictRow[]> {
|
||||
return request.get({ url: `${baseUrl}/enum-dict-list`, params });
|
||||
},
|
||||
|
||||
customDictList(params: any): Promise<DictRow[]> {
|
||||
return request.get({ url: `${baseUrl}/custom/list`, params });
|
||||
},
|
||||
|
||||
getByDictType(params: any): Promise<DictOptions[]> {
|
||||
return request.get({ url: `${baseUrl}/options/by-type`, params });
|
||||
},
|
||||
|
||||
getAllDict(params: any): Promise<any[]> {
|
||||
return request.get({ url: `${baseUrl}/enum/list`, params });
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
export default DictService;
|
||||
@@ -0,0 +1,4 @@
|
||||
export type * from './dict-item-types';
|
||||
export type * from './types';
|
||||
export * from './dict-item';
|
||||
export * from './dict';
|
||||
@@ -0,0 +1,44 @@
|
||||
|
||||
export interface DictRow {
|
||||
id: string;
|
||||
group_code: string;
|
||||
name: string;
|
||||
code: string;
|
||||
data_type: string;
|
||||
description: string;
|
||||
enabled: number;
|
||||
sort: number;
|
||||
created_by: string;
|
||||
updated_by: string;
|
||||
updated_at: string;
|
||||
}
|
||||
|
||||
export interface DictOptions extends DictItemRow {
|
||||
label: string;
|
||||
value: string | number;
|
||||
}
|
||||
|
||||
export type ApiResponse<T> = Promise<{
|
||||
code: number;
|
||||
data: T;
|
||||
msg: string;
|
||||
}>;
|
||||
|
||||
|
||||
export interface DictItemRow {
|
||||
label: string;
|
||||
value: string | number;
|
||||
id?: string | number;
|
||||
dict_id: string | number;
|
||||
code: string;
|
||||
sort: number;
|
||||
enabled: number;
|
||||
color: string;
|
||||
other_class: string;
|
||||
is_default?: number;
|
||||
created_by: string | number;
|
||||
updated_by: string | number;
|
||||
created_at: string | number;
|
||||
updated_at: string | number;
|
||||
remark: string;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import BaseService from '@/api/core/base'
|
||||
import request from '@/utils/http'
|
||||
import type { FeedbackRow } from './types'
|
||||
|
||||
const baseUrl = '/feedback'
|
||||
|
||||
export const FeedbackService = {
|
||||
...BaseService<FeedbackRow>({ baseUrl }),
|
||||
reply(id: string, replyContent: string) {
|
||||
return request.post<FeedbackRow>({
|
||||
url: `${baseUrl}/${id}/reply`,
|
||||
params: { reply_content: replyContent }
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export type * from './types'
|
||||
export default FeedbackService
|
||||
@@ -0,0 +1,18 @@
|
||||
/**
|
||||
* 问题反馈
|
||||
*/
|
||||
export interface FeedbackRow {
|
||||
id: string
|
||||
type: 'bug' | 'suggestion' | 'correction'
|
||||
content: string
|
||||
contact: string | null
|
||||
images: string[] | null
|
||||
source: 'app' | 'web'
|
||||
reply_content: string | null
|
||||
reply_by: string | null
|
||||
reply_name: string | null
|
||||
reply_at: number | null
|
||||
reply_date: string | null
|
||||
created_at: string | null
|
||||
created_date: string | null
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
|
||||
import BaseService from "@/api/core/base";
|
||||
import type { GatewayLimiterRow, GatewayBlacklistRow } from "./types";
|
||||
|
||||
|
||||
const baseUrl = '/gateway/limiter'
|
||||
|
||||
export const GatewayLimiterService = {
|
||||
...BaseService<GatewayLimiterRow>({ baseUrl })
|
||||
}
|
||||
|
||||
|
||||
export const GatewayBlacklistService = {
|
||||
...BaseService<GatewayBlacklistRow>({ baseUrl: '/gateway/blacklist' })
|
||||
}
|
||||
|
||||
|
||||
export type * from './types';
|
||||
|
||||
@@ -0,0 +1,37 @@
|
||||
|
||||
/**
|
||||
* 限流接口
|
||||
*/
|
||||
export interface GatewayLimiterRow {
|
||||
id: string;
|
||||
name: string;
|
||||
match_type: string;
|
||||
ip: string;
|
||||
priority: number;
|
||||
methods: string;
|
||||
path: string;
|
||||
limit_type: string;
|
||||
limit_value: number;
|
||||
period: number;
|
||||
enabled: number;
|
||||
message: string;
|
||||
created_date: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 限访接口
|
||||
*/
|
||||
export interface GatewayBlacklistRow {
|
||||
id: string;
|
||||
ip: string;
|
||||
name: string;
|
||||
enabled: number;
|
||||
priority: number;
|
||||
methods: string;
|
||||
path: string;
|
||||
message: string;
|
||||
start_date: string;
|
||||
end_date: string;
|
||||
created_date: string;
|
||||
remark: string;
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import BaseService from '@/api/core/base';
|
||||
import type { MessageRow } from "./types";
|
||||
import request from '@/utils/http';
|
||||
|
||||
const baseUrl = '/message';
|
||||
|
||||
export const MessageService = {
|
||||
...BaseService<MessageRow>({ baseUrl }),
|
||||
|
||||
updateRead(params: string | string[]) {
|
||||
const data = {
|
||||
id: params,
|
||||
status: 'read',
|
||||
};
|
||||
return request.put({ url: `${baseUrl}/${params}/update-read`, data });
|
||||
},
|
||||
|
||||
emptyMessage(params: string | string[]) {
|
||||
const data = {
|
||||
id: params,
|
||||
status: 'deleted',
|
||||
};
|
||||
return request.put({ url: `${baseUrl}/empty`, data });
|
||||
},
|
||||
|
||||
notifyOnFirstLoginToAll(data: any) {
|
||||
return request.post({ url: `${baseUrl}/notify-on-first-login-to-all`, data });
|
||||
},
|
||||
};
|
||||
|
||||
export type * from './types';
|
||||
export default MessageService;
|
||||
@@ -0,0 +1,38 @@
|
||||
/**
|
||||
* 系统消息
|
||||
*/
|
||||
export interface MessageRow {
|
||||
id: string;
|
||||
title: string;
|
||||
type: number;
|
||||
content: any;
|
||||
enabled: number;
|
||||
sender_id: string;
|
||||
receiver_id: string;
|
||||
status: string;
|
||||
priority?: string;
|
||||
channel: string;
|
||||
related_id: string;
|
||||
related_type: string;
|
||||
action_url: string;
|
||||
action_params: string;
|
||||
created_by: string | null;
|
||||
created_at: string | null;
|
||||
created_date: string | null;
|
||||
expired_at: string | null;
|
||||
sender?: any;
|
||||
}
|
||||
|
||||
export interface NotificationItem {
|
||||
id?: string;
|
||||
uid?: string;
|
||||
type?: string;
|
||||
avatar: string;
|
||||
date: string;
|
||||
isRead?: boolean;
|
||||
message: string;
|
||||
title: string;
|
||||
send?: any;
|
||||
related_id?: string;
|
||||
channel?: string;
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import BaseService from "@/api/core/base";
|
||||
import type { NoticeRow } from "./types";
|
||||
import request from '@/utils/http';
|
||||
|
||||
const baseUrl = '/notice';
|
||||
|
||||
export const NoticeService = {
|
||||
...BaseService<NoticeRow>({ baseUrl }),
|
||||
|
||||
publish(data: any) {
|
||||
return request.post({ url: `${baseUrl}/publish`, data });
|
||||
},
|
||||
};
|
||||
|
||||
export type * from './types';
|
||||
export default NoticeService;
|
||||
@@ -0,0 +1,17 @@
|
||||
/**
|
||||
* 通知公告
|
||||
*/
|
||||
export interface NoticeRow {
|
||||
id: string;
|
||||
message_id?: string | null;
|
||||
title: string;
|
||||
type: number;
|
||||
content: any;
|
||||
enabled: number;
|
||||
created_by: string | null;
|
||||
updated_by: string | null;
|
||||
created_date: string;
|
||||
updated_date: string | null;
|
||||
created_dept: string | null;
|
||||
remark: string | null;
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './post';
|
||||
export type * from './types';
|
||||
@@ -0,0 +1,11 @@
|
||||
import BaseService from '@/api/core/base';
|
||||
import type { PostRow } from './types';
|
||||
|
||||
const baseUrl = '/org/post';
|
||||
|
||||
export const PostService = {
|
||||
...BaseService<PostRow>({ baseUrl }),
|
||||
getList: BaseService<PostRow>({ baseUrl }).list,
|
||||
};
|
||||
|
||||
export default PostService;
|
||||
@@ -0,0 +1,13 @@
|
||||
|
||||
/**
|
||||
* 职位接口
|
||||
*/
|
||||
export interface PostRow {
|
||||
id: number | string;
|
||||
dept_id: string | number;
|
||||
code: string;
|
||||
name: string;
|
||||
sort: number;
|
||||
enabled: number;
|
||||
remark: string;
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
import BaseService from '@/api/core/base';
|
||||
import request from '@/utils/http'
|
||||
import type { ReviewListRequest, ReviewRow, ReviewCreateRequest, ReviewUpdateRequest } from './types'
|
||||
|
||||
|
||||
const baseUrl = '/review';
|
||||
|
||||
export const ReviewService = {
|
||||
...BaseService<ReviewRow>({ baseUrl }),
|
||||
|
||||
/**
|
||||
* 审核通过
|
||||
*/
|
||||
approve(id: number, reason: string = '') {
|
||||
return request.put<any>({
|
||||
url: `/review/${id}/approve`,
|
||||
data: { reason }
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 审核拒绝
|
||||
*/
|
||||
reject(id: number, reason: string = '') {
|
||||
return request.put<any>({
|
||||
url: `/review/${id}/reject`,
|
||||
data: { reason }
|
||||
})
|
||||
},
|
||||
|
||||
|
||||
batchApprove(ids: number[], reason: string = '') {
|
||||
return request.post<any>({
|
||||
url: `/review/batch/approve`,
|
||||
data: { ids, reason }
|
||||
})
|
||||
},
|
||||
|
||||
batchReject(ids: number[], reason: string = '') {
|
||||
return request.post<any>({
|
||||
url: `/review/batch/reject`,
|
||||
data: { ids, reason }
|
||||
})
|
||||
},
|
||||
|
||||
/**
|
||||
* 反审核
|
||||
*/
|
||||
unapprove(id: number) {
|
||||
return request.put<any>({
|
||||
url: `/setting/review/${id}/unapprove`,
|
||||
method: 'post'
|
||||
})
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
@@ -0,0 +1,93 @@
|
||||
// 审核列表请求参数
|
||||
export interface ReviewListRequest {
|
||||
page?: number
|
||||
limit?: number
|
||||
title?: string
|
||||
type?: string // 改为string类型,对应reviewable_type
|
||||
status?: number
|
||||
applicant?: string
|
||||
LIKE_title?: string
|
||||
LIKE_applicant?: string
|
||||
reviewable_type?: string // 新增:审核类型(morph_map别名)
|
||||
}
|
||||
|
||||
// 审核项(支持插件字段映射)
|
||||
export interface ReviewRow {
|
||||
// 基础字段
|
||||
id: number
|
||||
status: number
|
||||
status_text: string
|
||||
reason?: string
|
||||
reviewer_id?: number
|
||||
reviewer_name?: string
|
||||
reviewed_at?: number
|
||||
created_at: number
|
||||
updated_at: number
|
||||
|
||||
// 关联信息
|
||||
reviewable_type: string
|
||||
reviewable_id: number
|
||||
display_name?: string // 审核类型显示名称
|
||||
morph_alias?: string // morph_map别名
|
||||
|
||||
// 映射字段(通过配置动态生成)
|
||||
title: string
|
||||
content: string
|
||||
applicant: string
|
||||
|
||||
// 额外映射字段(插件自定义)
|
||||
[key: string]: any
|
||||
|
||||
// 关联对象
|
||||
reviewer?: {
|
||||
id: number
|
||||
user_name: string
|
||||
real_name: string
|
||||
nick_name: string
|
||||
}
|
||||
}
|
||||
|
||||
// 审核类型配置
|
||||
export interface ReviewTypeConfig {
|
||||
display_name: string
|
||||
model?: string
|
||||
morph_alias?: string
|
||||
priority?: number
|
||||
icon?: string
|
||||
color?: string
|
||||
fields?: Record<string, ReviewFieldConfig>
|
||||
}
|
||||
|
||||
// 字段配置
|
||||
export interface ReviewFieldConfig {
|
||||
type: 'attribute' | 'relation' | 'callback' | 'fixed'
|
||||
source?: string
|
||||
attribute?: string
|
||||
label: string
|
||||
fallback?: string
|
||||
format?: string
|
||||
multiple?: boolean
|
||||
callback?: Function
|
||||
}
|
||||
|
||||
// 创建审核请求参数
|
||||
export interface ReviewCreateRequest {
|
||||
reviewable_type: string // 审核类型(morph_map别名)
|
||||
reviewable_id: number // 关联对象ID
|
||||
reason?: string // 审核原因
|
||||
flow_instance_id?: string // 审批流实例ID
|
||||
extra_data?: Record<string, any> // 扩展数据
|
||||
}
|
||||
|
||||
// 更新审核请求参数
|
||||
export interface ReviewUpdateRequest {
|
||||
reason?: string
|
||||
review_note?: string
|
||||
}
|
||||
|
||||
// 审核操作响应
|
||||
export interface ReviewActionResult {
|
||||
success: boolean
|
||||
message: string
|
||||
data?: any
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
import BaseService from '@/api/core/base';
|
||||
import request from '@/utils/http';
|
||||
import axios from 'axios';
|
||||
|
||||
const { VITE_API_URL } = import.meta.env
|
||||
|
||||
const baseUrl = '/system/auth';
|
||||
|
||||
// 独立的 axios 实例用于刷新 token,避免与主请求拦截器关联
|
||||
const refreshAxios = axios.create({
|
||||
baseURL: VITE_API_URL,
|
||||
timeout: 10000,
|
||||
});
|
||||
|
||||
/**
|
||||
* 使用 refresh token 刷新 access token(独立实例)
|
||||
* @param refreshToken - refresh token
|
||||
*/
|
||||
export function refreshAccessTokenByRefreshToken(refreshToken: string) {
|
||||
return refreshAxios.post(`${baseUrl}/refresh-token`, {}, {
|
||||
headers: {
|
||||
Authorization: `Bearer ${refreshToken}`
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
export const AuthService = {
|
||||
...BaseService<any>({ baseUrl }),
|
||||
|
||||
getPublicKey() {
|
||||
return request.get({ url: `/login/auth/public-key` });
|
||||
},
|
||||
|
||||
login(params: any) {
|
||||
return request.post({ url: '/login/login', params });
|
||||
},
|
||||
|
||||
logout(params: any) {
|
||||
return request.post({ url: '/login/logout', params });
|
||||
},
|
||||
|
||||
userInfo() {
|
||||
return request.get({ url: `${baseUrl}/user-info` });
|
||||
},
|
||||
|
||||
roleMenuIds(params: any) {
|
||||
return request.get({ url: `${baseUrl}/role-menu-ids`, params });
|
||||
},
|
||||
|
||||
roleScopeIds(params: any) {
|
||||
return request.get({ url: `${baseUrl}/role-scope-ids`, params });
|
||||
},
|
||||
|
||||
userListByRoleId(params: any) {
|
||||
return request.get({ url: `${baseUrl}/user-list-by-role-id`, params });
|
||||
},
|
||||
|
||||
userListExcludeRoleId(params: any) {
|
||||
return request.get({ url: `${baseUrl}/user-list-exclude-role-id`, params });
|
||||
},
|
||||
|
||||
removeUserRole(data: any) {
|
||||
return request.post({ url: `${baseUrl}/remove-user-role`, data });
|
||||
},
|
||||
|
||||
saveRoleMenu(data: any) {
|
||||
return request.post({ url: `${baseUrl}/save-role-menu`, data });
|
||||
},
|
||||
|
||||
saveUserRole(data: any) {
|
||||
return request.post({ url: `${baseUrl}/save-user-role`, data });
|
||||
},
|
||||
|
||||
getUserTenant() {
|
||||
return request.get({ url: `${baseUrl}/user-tenant` });
|
||||
},
|
||||
|
||||
changeTenant(data: any) {
|
||||
return request.put({ url: `${baseUrl}/change-tenant`, data });
|
||||
},
|
||||
|
||||
tenantGrant(data: any) {
|
||||
return request.put({ url: `${baseUrl}/tenant-grant`, data });
|
||||
},
|
||||
|
||||
getUserPermission() {
|
||||
return request.get({ url: `${baseUrl}/user-permissions`, params: {} });
|
||||
},
|
||||
fetchGetMenuList() {
|
||||
// 获取用户菜单列表-返回前端路由格式
|
||||
return request.get({ url: `${baseUrl}/user-menus`, params: { format: 'art' } });
|
||||
},
|
||||
|
||||
getCaptcha() {
|
||||
return request.get({ url: `/login/captcha` });
|
||||
},
|
||||
|
||||
//刷新token
|
||||
refreshAccessToken(refreshToken: string) {
|
||||
return refreshAccessTokenByRefreshToken(refreshToken);
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
export default AuthService;
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './auth';
|
||||
// export type * from './rbac-model';
|
||||
@@ -0,0 +1,4 @@
|
||||
import * as deptApi from '../org/dept/dept'
|
||||
import * as userApi from './user/user'
|
||||
|
||||
export { deptApi, userApi }
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './menu';
|
||||
export type * from './types'
|
||||
@@ -0,0 +1,32 @@
|
||||
import BaseService from '@/api/core/base';
|
||||
import type { MenuRow } from './types';
|
||||
import request from '@/utils/http';
|
||||
|
||||
const baseUrl = '/system/menu';
|
||||
|
||||
export const BadgeVariants = [
|
||||
'default',
|
||||
'destructive',
|
||||
'primary',
|
||||
'success',
|
||||
'warning',
|
||||
] as const;
|
||||
|
||||
export const BadgeTypes = [
|
||||
'dot',
|
||||
'normal',
|
||||
] as const;
|
||||
|
||||
export const MenuTypes = [
|
||||
1, 2, 3, 4, 5, 6,
|
||||
] as const;
|
||||
|
||||
export const MenuService = {
|
||||
...BaseService<MenuRow>({ baseUrl }),
|
||||
|
||||
batchStore(data: Record<string, any>) {
|
||||
return request.post({ url: `${baseUrl}/batch-store`, data });
|
||||
},
|
||||
};
|
||||
|
||||
export default MenuService;
|
||||
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* 菜单项基础接口
|
||||
*/
|
||||
export interface MenuItemBase {
|
||||
/** 菜单项唯一标识 */
|
||||
id: string;
|
||||
/** 父级菜单ID */
|
||||
pid: number;
|
||||
/** 所属应用标识 */
|
||||
app?: string | null;
|
||||
/** 菜单显示标题 */
|
||||
title: string;
|
||||
/** 菜单编码(唯一) */
|
||||
code: string;
|
||||
/** 菜单层级(未使用) */
|
||||
level?: null;
|
||||
/** 菜单类型:1-目录 2-页面 */
|
||||
type: 1 | 2 | 3 | 4 | 5 | 6;
|
||||
/** 排序值 */
|
||||
sort: number;
|
||||
/** 路由路径 */
|
||||
path?: string;
|
||||
/** 组件路径 */
|
||||
component?: string;
|
||||
/** 重定向路径 */
|
||||
redirect?: null;
|
||||
/** 菜单图标 */
|
||||
icon?: string;
|
||||
/** 是否显示 */
|
||||
is_show: 0 | 1;
|
||||
/** 是否为外链 */
|
||||
is_link?: 0 | 1;
|
||||
/** 外链地址 */
|
||||
link_url?: null;
|
||||
/** 是否启用 */
|
||||
enabled: 0 | 1;
|
||||
/** 打开方式 */
|
||||
open_type?: 0;
|
||||
/** 是否缓存 */
|
||||
is_cache?: 0 | 1;
|
||||
/** 是否同步 */
|
||||
is_sync?: 0 | 1;
|
||||
/** 是否固定 */
|
||||
is_affix?: 0 | 1;
|
||||
/** 动态参数 */
|
||||
variable?: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 时间字段扩展接口
|
||||
*/
|
||||
export interface TimeFields {
|
||||
/** 创建时间 */
|
||||
created_at?: string;
|
||||
/** 创建人 */
|
||||
created_by?: null;
|
||||
/** 更新时间 */
|
||||
updated_at?: string;
|
||||
/** 更新人 */
|
||||
updated_by?: null;
|
||||
/** 删除时间 */
|
||||
deleted_at?: null;
|
||||
}
|
||||
|
||||
/**
|
||||
* 完整菜单项接口
|
||||
*/
|
||||
export interface MenuRow extends MenuItemBase, TimeFields {
|
||||
/** HTTP方法 */
|
||||
methods: string;
|
||||
/** 创建时间(格式化) */
|
||||
created_date: string;
|
||||
/** 更新时间(格式化) */
|
||||
updated_date: string;
|
||||
/** 子菜单项 */
|
||||
children?: MenuRow[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 根菜单项接口(可选子菜单)
|
||||
*/
|
||||
export interface RootMenuItem extends Omit<MenuItem, 'children'> {
|
||||
children?: MenuItem[];
|
||||
}
|
||||
|
||||
@@ -0,0 +1,75 @@
|
||||
import request from '@/utils/http';
|
||||
import type { UpdateAvatarParam, UpdatePasswordParam, UpdateUserInfoParam, UserSession } from './types';
|
||||
|
||||
/**
|
||||
* 个人中心 API 服务
|
||||
* 符合 RESTful API 规范
|
||||
*/
|
||||
const BASE_URL = '/profile';
|
||||
|
||||
export const ProfileService = {
|
||||
/**
|
||||
* 获取当前用户信息
|
||||
* @returns {Promise<any>} 用户信息
|
||||
*/
|
||||
getProfile() {
|
||||
return request.get({ url: BASE_URL });
|
||||
},
|
||||
|
||||
/**
|
||||
* 更新个人信息
|
||||
* @param {UpdateUserInfoParam} data - 个人信息数据
|
||||
* @returns {Promise<any>} 更新结果
|
||||
*/
|
||||
updateProfile(data: UpdateUserInfoParam) {
|
||||
return request.put({ url: BASE_URL, data });
|
||||
},
|
||||
|
||||
/**
|
||||
* 更新头像
|
||||
* @param {UpdateAvatarParam} data - 头像数据
|
||||
* @returns {Promise<any>} 更新结果
|
||||
*/
|
||||
updateAvatar(data: UpdateAvatarParam) {
|
||||
return request.put({ url: `${BASE_URL}/avatar`, data });
|
||||
},
|
||||
|
||||
/**
|
||||
* 修改密码
|
||||
* @param {UpdatePasswordParam} data - 密码数据
|
||||
* @returns {Promise<any>} 更新结果
|
||||
*/
|
||||
updatePassword(data: UpdatePasswordParam) {
|
||||
return request.put({ url: `${BASE_URL}/password`, data });
|
||||
},
|
||||
|
||||
/**
|
||||
* 获取在线设备列表
|
||||
* @param {Record<string, any>} params - 查询参数
|
||||
* @returns {Promise<UserSession>} 在线设备列表
|
||||
*/
|
||||
getOnlineDevices(params?: Record<string, any>): Promise<UserSession> {
|
||||
return request.get({ url: `${BASE_URL}/sessions`, params });
|
||||
},
|
||||
|
||||
/**
|
||||
* 强制下线指定设备
|
||||
* @param {string | number} id - 会话ID
|
||||
* @param {Record<string, any>} data - 额外数据
|
||||
* @returns {Promise<any>} 操作结果
|
||||
*/
|
||||
kickoutSession(id: string | number, data?: Record<string, any>): Promise<any> {
|
||||
return request.del({ url: `${BASE_URL}/sessions/${id}`, data });
|
||||
},
|
||||
|
||||
/**
|
||||
* 更新用户偏好设置
|
||||
* @param {Record<string, any>} preferences - 偏好设置
|
||||
* @returns {Promise<any>} 更新结果
|
||||
*/
|
||||
updatePreferences(preferences: Record<string, any>) {
|
||||
return request.put({ url: `${BASE_URL}/update-preferences`, data: { preferences } });
|
||||
},
|
||||
};
|
||||
|
||||
export default ProfileService;
|
||||
@@ -0,0 +1,66 @@
|
||||
/**
|
||||
* 更新密码请求参数
|
||||
*/
|
||||
export interface UpdatePasswordParam {
|
||||
/** 当前密码 */
|
||||
old_password: string;
|
||||
/** 新密码 */
|
||||
new_password: string;
|
||||
/** 确认新密码 */
|
||||
confirm_password: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新个人信息请求参数
|
||||
*/
|
||||
export interface UpdateUserInfoParam {
|
||||
/** 真实姓名 */
|
||||
real_name: string;
|
||||
/** 昵称 */
|
||||
nick_name: string;
|
||||
/** 邮箱 */
|
||||
email: string;
|
||||
/** 手机号 */
|
||||
mobile_phone: string;
|
||||
/** 性别:0=未知,1=男,2=女 */
|
||||
sex: number;
|
||||
/** 个人签名 */
|
||||
signed?: string;
|
||||
/** 地址 */
|
||||
address?: string;
|
||||
/** 头像 */
|
||||
avatar?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新头像请求参数
|
||||
*/
|
||||
export interface UpdateAvatarParam {
|
||||
/** 头像URL */
|
||||
avatar: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户会话信息
|
||||
*/
|
||||
export interface UserSession {
|
||||
id: string;
|
||||
user_name: string;
|
||||
ip: string;
|
||||
ip_location: string;
|
||||
os: string;
|
||||
browser: string;
|
||||
status: number;
|
||||
message: string;
|
||||
login_time: number;
|
||||
key: string;
|
||||
created_at: string;
|
||||
expires_at: string;
|
||||
updated_at: string | null;
|
||||
deleted_at: number | null;
|
||||
remark: string | null;
|
||||
created_date: string;
|
||||
updated_date: string;
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './role';
|
||||
export type * from './types';
|
||||
@@ -0,0 +1,20 @@
|
||||
import BaseService from '@/api/core/base'
|
||||
import request from '@/utils/http'
|
||||
import type { RoleRow } from './types'
|
||||
import type { DeptTree } from '../../org/dept/dept-model'
|
||||
|
||||
const baseUrl = '/system/role'
|
||||
|
||||
export const RoleService = {
|
||||
...BaseService<RoleRow>({ baseUrl }),
|
||||
|
||||
dataScope(id: number | string, data: Record<string, any>): Promise<RoleRow> {
|
||||
return request.put({ url: `${baseUrl}/${id}/data-scope`, data })
|
||||
},
|
||||
|
||||
getScopeDeptTree(): Promise<DeptTree[]> {
|
||||
return request.get({ url: `${baseUrl}/scope/dept` })
|
||||
}
|
||||
}
|
||||
|
||||
export default RoleService
|
||||
@@ -0,0 +1,34 @@
|
||||
|
||||
export interface RoleRow {
|
||||
[key: string]: any;
|
||||
id: string;
|
||||
pid?: string;
|
||||
name: string
|
||||
code: string;
|
||||
is_super_admin: 0 | 1;
|
||||
role_type: number;
|
||||
data_scope: DataScopeEnum;
|
||||
enabled: 0 | 1;
|
||||
sort: number;
|
||||
permissions?: string[];
|
||||
remark?: string | null;
|
||||
created_by: number;
|
||||
updated_by: number;
|
||||
created_at: string;
|
||||
updated_at: string;
|
||||
deleted_at?: string | null;
|
||||
created_date: string;
|
||||
updated_date: string;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 数据权限范围枚举
|
||||
*/
|
||||
export enum DataScopeEnum {
|
||||
ALL = 1, // 全部数据权限
|
||||
CUSTOM = 2, // 自定义权限
|
||||
DEPT = 3, // 本部门
|
||||
DEPT_WITH_CHILD = 4, // 本部门及下级
|
||||
SELF = 5 // 仅本人
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './rule';
|
||||
export type * from './types'
|
||||
@@ -0,0 +1,39 @@
|
||||
import BaseService from '@/api/core/base';
|
||||
import request from '@/utils/http';
|
||||
import type { RouteRow } from './types';
|
||||
|
||||
const baseUrl = '/system/rule';
|
||||
|
||||
export const RuleService = {
|
||||
...BaseService<RouteRow>({ baseUrl }),
|
||||
|
||||
/**
|
||||
* 列表(扫描模式)
|
||||
* @param params
|
||||
* @returns
|
||||
*/
|
||||
list(params?: Record<string, any>): Promise<RouteRow[]> {
|
||||
return request.get<RouteRow[]>({ url: `system/rule`, params });
|
||||
},
|
||||
|
||||
/**
|
||||
* 类型(扫描模式,基于tags)
|
||||
* @param params
|
||||
* @returns
|
||||
*/
|
||||
cate(params?: Record<string, any>): Promise<any[]> {
|
||||
return request.get({ url: `system/rule-cate`, params });
|
||||
},
|
||||
|
||||
|
||||
/**
|
||||
* 刷新扫描缓存
|
||||
* @param data
|
||||
* @returns
|
||||
*/
|
||||
sync(data?: any): Promise<void> {
|
||||
return request.post({ url: `system/rule/refresh`, data });
|
||||
}
|
||||
};
|
||||
|
||||
export default RuleService;
|
||||
@@ -0,0 +1,13 @@
|
||||
export type HttpMethod = "get" | "post" | "put" | "delete" | "patch";
|
||||
|
||||
/**
|
||||
* 路由列表
|
||||
*/
|
||||
export interface RouteRow {
|
||||
name: string;
|
||||
method: Uppercase<HttpMethod>;
|
||||
path: string;
|
||||
title?: string;
|
||||
type?: number;
|
||||
code?: string;
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export * from './user';
|
||||
export type * from './types';
|
||||
@@ -0,0 +1,39 @@
|
||||
import { TenantRow } from '#/api/platform/tenant'
|
||||
import { Dept } from '../../org/dept'
|
||||
|
||||
/**
|
||||
* 用户
|
||||
*/
|
||||
export interface User extends Api.Auth.UserInfo {
|
||||
id: string
|
||||
user_name: string
|
||||
real_name: string
|
||||
nick_name: string
|
||||
is_super?: number
|
||||
mobile_phone: string
|
||||
email: string
|
||||
avatar: string
|
||||
signed: string
|
||||
dashboard: string
|
||||
dept_id: string
|
||||
enabled: number
|
||||
login_ip: string
|
||||
login_time: string
|
||||
backend_setting: string
|
||||
created_by: string
|
||||
updated_by: string
|
||||
created_at: string
|
||||
updated_at: string
|
||||
deleted_at: string
|
||||
sex: string
|
||||
remark: string
|
||||
birthday: string
|
||||
tel: string
|
||||
is_locked?: string
|
||||
depts?: Dept
|
||||
posts?: Post[]
|
||||
roles?: Role[]
|
||||
tenant?: any
|
||||
login_date?: string
|
||||
address?: string
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import BaseService from '@/api/core/base';
|
||||
import request from '@/utils/http';
|
||||
import type { User } from './types';
|
||||
|
||||
/**
|
||||
* 公共接口
|
||||
*/
|
||||
const baseUrl = '/system/admin';
|
||||
|
||||
export const UserService = {
|
||||
...BaseService<User>({
|
||||
baseUrl,
|
||||
forbiddenMethods: ['export'],
|
||||
}),
|
||||
|
||||
resetPassword(data: Record<string, any>): Promise<User[]> {
|
||||
return request.put({ url: `${baseUrl}/reset-password`, data });
|
||||
},
|
||||
|
||||
playUser(data: Record<string, any>) {
|
||||
return request.put({ url: `${baseUrl}/play-user`, data });
|
||||
},
|
||||
|
||||
unPlayUser(data: any) {
|
||||
return request.put({ url: `${baseUrl}/unplay-user`, data });
|
||||
},
|
||||
|
||||
grantRole(data: any) {
|
||||
return request.post({ url: `${baseUrl}/grant-role`, data });
|
||||
},
|
||||
|
||||
locked(data: any) {
|
||||
return request.put({ url: `${baseUrl}/locked`, data });
|
||||
},
|
||||
|
||||
unLocked(data: any) {
|
||||
return request.put({ url: `${baseUrl}/un-locked`, data });
|
||||
},
|
||||
|
||||
preferences(data: any) {
|
||||
return request.put({ url: `${baseUrl}/preferences`, data });
|
||||
},
|
||||
|
||||
getUserInfo() {
|
||||
return request.get<Api.Auth.UserInfo>({ url: `/system/auth/user-info` });
|
||||
},
|
||||
|
||||
getPermCode() {
|
||||
return request.get<string[]>({ url: `/system/auth/perm-code` });
|
||||
},
|
||||
|
||||
};
|
||||
|
||||
export default UserService;
|
||||
@@ -0,0 +1,18 @@
|
||||
import BaseService from '@/api/core/base'
|
||||
import request from '@/utils/http'
|
||||
import type { ApplicationAuthRow, ResetSecretResponse } from './types'
|
||||
|
||||
const baseUrl = '/plugin/application-auth'
|
||||
|
||||
export const ApplicationAuthService = {
|
||||
...BaseService<ApplicationAuthRow>({ baseUrl }),
|
||||
|
||||
resetSecret(id: string | number) {
|
||||
return request.post<ResetSecretResponse>({
|
||||
url: `${baseUrl}/${id}/reset-secret`
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
export type * from './types'
|
||||
export default ApplicationAuthService
|
||||
@@ -0,0 +1,17 @@
|
||||
export interface ApplicationAuthRow {
|
||||
id: string
|
||||
name: string
|
||||
access_key: string
|
||||
secret_key: string
|
||||
expired_at?: number | null
|
||||
enabled: number
|
||||
description?: string | null
|
||||
created_at?: number | null
|
||||
created_date?: string | null
|
||||
updated_date?: string | null
|
||||
}
|
||||
|
||||
export interface ResetSecretResponse {
|
||||
id: string
|
||||
secret_key: string
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user