feat(project): 添加vben5前端
This commit is contained in:
4
Yi.Vben5.Vue3/.browserslistrc
Normal file
4
Yi.Vben5.Vue3/.browserslistrc
Normal file
@@ -0,0 +1,4 @@
|
||||
> 1%
|
||||
last 2 versions
|
||||
not dead
|
||||
not ie 11
|
||||
5
Yi.Vben5.Vue3/.changeset/README.md
Normal file
5
Yi.Vben5.Vue3/.changeset/README.md
Normal file
@@ -0,0 +1,5 @@
|
||||
# Changesets
|
||||
|
||||
Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works with multi-package repos, or single-package repos to help you version and publish your code. You can find the full documentation for it [in our repository](https://github.com/changesets/changesets)
|
||||
|
||||
We have a quick list of common questions to get you started engaging with this project in [our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
|
||||
18
Yi.Vben5.Vue3/.changeset/config.json
Normal file
18
Yi.Vben5.Vue3/.changeset/config.json
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json",
|
||||
"changelog": [
|
||||
"@changesets/changelog-github",
|
||||
{ "repo": "vbenjs/vue-vben-admin" }
|
||||
],
|
||||
"commit": false,
|
||||
"fixed": [["@vben-core/*", "@vben/*"]],
|
||||
"snapshot": {
|
||||
"prereleaseTemplate": "{tag}-{datetime}"
|
||||
},
|
||||
"privatePackages": { "version": true, "tag": true },
|
||||
"linked": [],
|
||||
"access": "public",
|
||||
"baseBranch": "main",
|
||||
"updateInternalDependencies": "patch",
|
||||
"ignore": []
|
||||
}
|
||||
1
Yi.Vben5.Vue3/.commitlintrc.js
Normal file
1
Yi.Vben5.Vue3/.commitlintrc.js
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from '@vben/commitlint-config';
|
||||
7
Yi.Vben5.Vue3/.dockerignore
Normal file
7
Yi.Vben5.Vue3/.dockerignore
Normal file
@@ -0,0 +1,7 @@
|
||||
node_modules
|
||||
.git
|
||||
.gitignore
|
||||
*.md
|
||||
dist
|
||||
.turbo
|
||||
dist.zip
|
||||
18
Yi.Vben5.Vue3/.editorconfig
Normal file
18
Yi.Vben5.Vue3/.editorconfig
Normal file
@@ -0,0 +1,18 @@
|
||||
root = true
|
||||
|
||||
[*]
|
||||
charset=utf-8
|
||||
end_of_line=lf
|
||||
insert_final_newline=true
|
||||
indent_style=space
|
||||
indent_size=2
|
||||
max_line_length = 100
|
||||
trim_trailing_whitespace = true
|
||||
quote_type = single
|
||||
|
||||
[*.{yml,yaml,json}]
|
||||
indent_style = space
|
||||
indent_size = 2
|
||||
|
||||
[*.md]
|
||||
trim_trailing_whitespace = false
|
||||
11
Yi.Vben5.Vue3/.gitattributes
vendored
Normal file
11
Yi.Vben5.Vue3/.gitattributes
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
# https://docs.github.com/cn/get-started/getting-started-with-git/configuring-git-to-handle-line-endings
|
||||
|
||||
# Automatically normalize line endings (to LF) for all text-based files.
|
||||
* text=auto eol=lf
|
||||
|
||||
# Declare files that will always have CRLF line endings on checkout.
|
||||
*.{cmd,[cC][mM][dD]} text eol=crlf
|
||||
*.{bat,[bB][aA][tT]} text eol=crlf
|
||||
|
||||
# Denote all files that are truly binary and should not be modified.
|
||||
*.{ico,png,jpg,jpeg,gif,webp,svg,woff,woff2} binary
|
||||
2
Yi.Vben5.Vue3/.gitconfig
Normal file
2
Yi.Vben5.Vue3/.gitconfig
Normal file
@@ -0,0 +1,2 @@
|
||||
[core]
|
||||
ignorecase = false
|
||||
54
Yi.Vben5.Vue3/.gitignore
vendored
Normal file
54
Yi.Vben5.Vue3/.gitignore
vendored
Normal file
@@ -0,0 +1,54 @@
|
||||
node_modules
|
||||
.DS_Store
|
||||
dist
|
||||
dist-ssr
|
||||
dist.zip
|
||||
dist.tar
|
||||
dist.war
|
||||
.nitro
|
||||
.output
|
||||
*-dist.zip
|
||||
*-dist.tar
|
||||
*-dist.war
|
||||
coverage
|
||||
*.local
|
||||
**/.vitepress/cache
|
||||
.cache
|
||||
.turbo
|
||||
.temp
|
||||
dev-dist
|
||||
.stylelintcache
|
||||
yarn.lock
|
||||
package-lock.json
|
||||
pnpm-lock.yaml
|
||||
.VSCodeCounter
|
||||
**/backend-mock/data
|
||||
|
||||
# local env files
|
||||
.env.local
|
||||
.env.*.local
|
||||
.eslintcache
|
||||
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
yarn-debug.log*
|
||||
yarn-error.log*
|
||||
pnpm-debug.log*
|
||||
lerna-debug.log*
|
||||
vite.config.mts.*
|
||||
vite.config.mjs.*
|
||||
vite.config.js.*
|
||||
vite.config.ts.*
|
||||
|
||||
# Editor directories and files
|
||||
.idea
|
||||
# .vscode
|
||||
*.suo
|
||||
*.ntvs*
|
||||
*.njsproj
|
||||
*.sln
|
||||
*.sw?
|
||||
# 排除自动生成的类型文件
|
||||
apps/web-antd/types/components.d.ts
|
||||
.history
|
||||
6
Yi.Vben5.Vue3/.gitpod.yml
Normal file
6
Yi.Vben5.Vue3/.gitpod.yml
Normal file
@@ -0,0 +1,6 @@
|
||||
ports:
|
||||
- port: 5555
|
||||
onOpen: open-preview
|
||||
tasks:
|
||||
- init: npm i -g corepack && pnpm install
|
||||
command: pnpm run dev:play
|
||||
1
Yi.Vben5.Vue3/.node-version
Normal file
1
Yi.Vben5.Vue3/.node-version
Normal file
@@ -0,0 +1 @@
|
||||
22.1.0
|
||||
13
Yi.Vben5.Vue3/.npmrc
Normal file
13
Yi.Vben5.Vue3/.npmrc
Normal file
@@ -0,0 +1,13 @@
|
||||
registry = "https://registry.npmmirror.com"
|
||||
public-hoist-pattern[]=lefthook
|
||||
public-hoist-pattern[]=eslint
|
||||
public-hoist-pattern[]=prettier
|
||||
public-hoist-pattern[]=prettier-plugin-tailwindcss
|
||||
public-hoist-pattern[]=stylelint
|
||||
public-hoist-pattern[]=*postcss*
|
||||
public-hoist-pattern[]=@commitlint/*
|
||||
public-hoist-pattern[]=czg
|
||||
|
||||
strict-peer-dependencies=false
|
||||
auto-install-peers=true
|
||||
dedupe-peer-dependents=true
|
||||
18
Yi.Vben5.Vue3/.prettierignore
Normal file
18
Yi.Vben5.Vue3/.prettierignore
Normal file
@@ -0,0 +1,18 @@
|
||||
dist
|
||||
dev-dist
|
||||
.local
|
||||
.output.js
|
||||
node_modules
|
||||
.nvmrc
|
||||
coverage
|
||||
CODEOWNERS
|
||||
.nitro
|
||||
.output
|
||||
|
||||
|
||||
**/*.svg
|
||||
**/*.sh
|
||||
|
||||
public
|
||||
.npmrc
|
||||
*-lock.yaml
|
||||
1
Yi.Vben5.Vue3/.prettierrc.mjs
Normal file
1
Yi.Vben5.Vue3/.prettierrc.mjs
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from '@vben/prettier-config';
|
||||
4
Yi.Vben5.Vue3/.stylelintignore
Normal file
4
Yi.Vben5.Vue3/.stylelintignore
Normal file
@@ -0,0 +1,4 @@
|
||||
dist
|
||||
public
|
||||
__tests__
|
||||
coverage
|
||||
323
Yi.Vben5.Vue3/CHANGELOG.md
Normal file
323
Yi.Vben5.Vue3/CHANGELOG.md
Normal file
@@ -0,0 +1,323 @@
|
||||
# 1.4.1
|
||||
|
||||
**FEATURES**
|
||||
|
||||
- Tinymce添加在antd原生表单/useVbenForm下的校验样式
|
||||
- useVbenForm 增加 Cascader(级联选择器) 组件
|
||||
|
||||
**BUG FIX**
|
||||
|
||||
- 菜单管理 路由地址的必填项不生效
|
||||
- withDefaultPlaceholder中placeholder 在keepalive & 语言切换 & tab切换 显示不变的问题
|
||||
|
||||
**REFACTOR**
|
||||
|
||||
- 字典接口抛出异常(为什么会抛出异常?)无限调用接口 兼容处理
|
||||
- 代码生成 字典下拉加载 改为每次进入编辑页面都加载
|
||||
- ~~个人中心 账号绑定 样式/逻辑重构~~(回滚了 既要又要的问题)
|
||||
- ~~个人中心 下拉卡片 昵称超长省略显示~~(回滚了 既要又要的问题)
|
||||
|
||||
# 1.4.0
|
||||
|
||||
**FEATURES**
|
||||
|
||||
- 菜单管理(通用方法) 保存表格滚动/展开状态并执行回调 用于树表在执行 新增/编辑/删除等操作后 依然在当前位置(体验优化)
|
||||
-
|
||||
- 菜单管理 级联删除 删除菜单和children
|
||||
|
||||
**REFACTOR**
|
||||
|
||||
- 除个人中心外所有本地路由改为从后端返回(需要执行更新sql)
|
||||
- 流程图预览改为logicflow预览而非图片 ...然后后端又更新了 又改成iframe了
|
||||
- 菜单管理 新增角色校验(与后端权限保持一致) 只有superadmin可进行增删改
|
||||
|
||||
# 1.3.6
|
||||
|
||||
**BUG FIX**
|
||||
|
||||
- oss配置switch切换 导致报错`存储类型找不到`
|
||||
- 文件上传无法正确清除(innerList)
|
||||
|
||||
# 1.3.5
|
||||
|
||||
**BUG FIX**
|
||||
|
||||
- 某些带Vxe表格弹窗 关闭后没有正常清理表格数据的问题
|
||||
|
||||
# 1.3.4
|
||||
|
||||
**BUG FIX**
|
||||
|
||||
- 文件上传多次触发导致数据不一致 https://gitee.com/dapppp/ruoyi-plus-vben5/issues/IC3BK6
|
||||
|
||||
**PREFORMANCE**
|
||||
|
||||
- 浏览器返回按钮/手势操作时 弹窗不会被关闭(keepAlive导致)
|
||||
|
||||
# 1.3.3
|
||||
|
||||
**BUG FIX**
|
||||
|
||||
- 工作流list展示在开启缩放会有误差导致触底逻辑不会触发
|
||||
|
||||
**OTHER**
|
||||
|
||||
- 代码生成预览对模板的提示...(下载都懒得点一下吗)
|
||||
|
||||
# 1.3.2
|
||||
|
||||
**REFACTOR**
|
||||
|
||||
- 所有表格操作列宽度调整为'auto', 这样会根据子元素宽度适配(比如没有分配权限的情况)
|
||||
- 菜单图标更新了一部分 sql同步更新
|
||||
|
||||
**OTHER**
|
||||
|
||||
- 暂时锁死vite依赖 i18n会报错
|
||||
|
||||
# 1.3.1
|
||||
|
||||
**REFACTOR**
|
||||
|
||||
- 所有Modal/Drawer表单关闭前会进行表单数据对比来弹出提示框
|
||||
- 字典项颜色选择从`原生input type=color`改为`vue3-colorpicker`组件
|
||||
- 全局Header: ClientID 更改大小写 [spring的问题导致](https://gitee.com/dapppp/ruoyi-plus-vben5/issues/IC0BDS)
|
||||
|
||||
**BUG FIX**
|
||||
|
||||
- getVxePopupContainer逻辑调整 解决表格固定高度展开不全的问题
|
||||
|
||||
**FEATURES**
|
||||
|
||||
- 字典渲染支持loading(length为0情况)
|
||||
|
||||
**OTHERS**
|
||||
|
||||
- useForm的组件改为异步导入(官方更新) bootstrap.js体积从2M降到600K 首屏加载速度提升
|
||||
|
||||
# 1.3.0
|
||||
|
||||
注意: 如果你使用老版本的`文件上传`/`图片上传` 可暂时使用
|
||||
|
||||
- `component: 'ImageUploadOld'`
|
||||
- `component: 'FileUploadOld'`
|
||||
|
||||
代替 **建议替换为新版本**
|
||||
|
||||
大致变动:
|
||||
|
||||
- `accept string[] -> string`
|
||||
- `resultField 已经移除 统一使用ossId`
|
||||
- `maxNumber -> maxCount`
|
||||
|
||||
具体参数查看: `apps/web-antd/src/components/upload/src/props.d.ts`
|
||||
|
||||
不再推荐使用useDescription, 这个版本会标记为@deprecated, 下个次版本将会移除
|
||||
|
||||
框架所有使用useDescription组件的会替换为原生(TODO)
|
||||
|
||||
**REFACTOR**
|
||||
|
||||
- **文件上传/图片上传重构(破坏性更新 不兼容之前的api)**
|
||||
- **文件上传/图片上传不再支持url用法 强制使用ossId**
|
||||
- TableSwitch组件重构
|
||||
- 管理员租户切换不再返回首页 直接刷新当前页(除特殊页面外会回到首页)
|
||||
- 租户切换Select增加loading
|
||||
- ~~modalLoading/drawerLoading改为调用内部的lock/unlock方法~~ 有待商榷暂时按老版本逻辑不变
|
||||
- 登录验证码 增加loading
|
||||
- DictEnum使用const代替enum
|
||||
- TinyMCE组件重构 移除冗余代码/功能 增加loading
|
||||
|
||||
**ALPHA功能**
|
||||
|
||||
- 弹窗表单数据更改关闭时的提示框(可能最终不会加入) 测试页面: 参数管理
|
||||
|
||||
**BUG FIX**
|
||||
|
||||
- 重新登录 字典会unknown的情况[详细分析](https://gitee.com/dapppp/ruoyi-plus-vben5/issues/IBY27D)
|
||||
- 测试菜单 请假申请 选中删除 需要根据状态判断
|
||||
- 修复文件/图片在Safari中无法上传 file-type库与Safari不兼容导致
|
||||
- 头像裁剪 图片加载失败一直处于loading无法上传
|
||||
- 头像裁剪 私有桶会拼接timestamp参数导致sign计算异常无法上传 感谢cropperjs作者 https://github.com/fengyuanchen/cropperjs/issues/1230
|
||||
- 租户选择下拉框会跟随body滚动(将下拉框样式的默认absolute改为fixed)
|
||||
|
||||
**OTHER**
|
||||
|
||||
- 字典管理 字典类型 表格选中行增加bold效果
|
||||
- 全局圆角修改 与antd保持一致
|
||||
- vditor(Markdown)升级3.10.9
|
||||
- 老版本的文件/图片上传将于下个版本移除
|
||||
- useDescription将于下个版本移除
|
||||
- getVxePopupContainer与新版Vxe不兼容 先返回body(会导致滚动不跟随)后续版本再优化
|
||||
|
||||
# 1.2.3
|
||||
|
||||
**BUG FIX**
|
||||
|
||||
- `withDefaultPlaceholder`中将`placeholder`修改为computed, 解决后续使用`updateSchema`无法正常更新显示placeholder(响应式问题)
|
||||
|
||||
- 流程定义 修改accept类型 解决无法拖拽上传
|
||||
|
||||
**FEATURES**
|
||||
|
||||
- 增加`环境变量`打包配置demo -> build:antd:test
|
||||
- 角色管理 勾选权限组件添加对错误用法的校验提示
|
||||
|
||||
**REFACTOR**
|
||||
|
||||
- OAuth内部逻辑重构 增加新的默认OAuth登录方式
|
||||
- 重构部分setup组件为setup语法糖形式
|
||||
|
||||
# 1.2.2
|
||||
|
||||
**FEATURES**
|
||||
|
||||
- 代码生成支持路径方式生成
|
||||
- 代码生成 支持选择表单生成类型(需要模板支持)
|
||||
- 工作流 支持按钮权限
|
||||
|
||||
# 1.2.1
|
||||
|
||||
# BUG FIXES
|
||||
|
||||
- 客户端管理 错误的status disabled
|
||||
- modal/drawer升级后zIndex(2000)会遮挡Tinymce的下拉框zIndex(1300)
|
||||
|
||||
# 1.2.0
|
||||
|
||||
**REFACTOR**
|
||||
|
||||
- 菜单选择组件重构为Table形式
|
||||
- 字典相关功能重构 采用一个Map储存字典(之前为两个Map)
|
||||
- 代码生成配置页面重构 去除步骤条
|
||||
|
||||
**Features**
|
||||
|
||||
- 对接后端工作流
|
||||
- ~~通用的vxe-table排序事件(排序逻辑改为在排序事件中处理而非在api处理)~~
|
||||
- getDict/getDictOptions 提取公共逻辑 减少冗余代码
|
||||
- 字典新增对Number类型的支持 -> `getDictOptions('', true);`即可获取number类型的value
|
||||
- 文件上传 增加上传进度条 下方上传提示
|
||||
- 图片上传 增加上传进度条 下方上传提示
|
||||
- oss下载进度提示
|
||||
|
||||
**BUG FIXES**
|
||||
|
||||
- 字典项为空时getDict方法无限调用接口(无奈兼容 不给字典item本来就是错误用法)
|
||||
- 表格排序翻页会丢失排序参数
|
||||
- 下载文件时(responseType === 'blob')需要判断下载失败(返回json而非二进制)的情况
|
||||
- requestClient缺失i18n内容
|
||||
|
||||
**OTHERS**
|
||||
|
||||
- 用户管理 新增只获取一次(mounted)默认密码而非每次打开modal都获取
|
||||
- `apps/web-antd/src/utils/dict.ts` `getDict`方法将于下个版本删除 使用`getDictOptions`替代
|
||||
- VxeTable升级V4.10.0
|
||||
- 移除`@deprecated` `apps/web-antd/src/adapter/vxe-table.ts`的`tableCheckboxEvent`方法
|
||||
- 移除`由于更新方案弃用的` `apps/web-antd/src/adapter/vxe-table.ts`的`vxeSortEvent`方法
|
||||
- 移除apps下的ele和naive目录
|
||||
|
||||
# 1.1.3
|
||||
|
||||
**REFACTOR**
|
||||
|
||||
- 重构: 判断vxe-table的复选框是否选中
|
||||
|
||||
**Bug Fixes**
|
||||
|
||||
- 节点树在编辑 & 空数组(不勾选)情况 勾选节点会造成watch延迟触发 导致会带上父节点id造成id重复
|
||||
- 节点树在节点独立情况下的控制台warning: Invalid prop: type check failed for prop "value". Expected Array, got Object
|
||||
|
||||
**Others**
|
||||
|
||||
- 角色管理 优化Drawer布局
|
||||
- unplugin-vue-components插件(默认未开启) 需要排除Button组件 全局已经默认导入了
|
||||
|
||||
**BUG FIXES**
|
||||
|
||||
- 操作日志详情 在description组件中json预览样式异常
|
||||
- 微服务版本 区间查询和中文搜索条件一起使用 无法正确查询
|
||||
|
||||
# 1.1.2
|
||||
|
||||
**Features**
|
||||
|
||||
- Options转Enum工具函数
|
||||
|
||||
**OTHERS**
|
||||
|
||||
- 菜单管理 改为虚拟滚动
|
||||
- 移除requestClient的一些冗余参数
|
||||
- 主动退出登录(右上角个人选项)不需要带跳转地址
|
||||
|
||||
**BUG FIXES**
|
||||
|
||||
- 语言 漏加Content-Language请求头
|
||||
- 用户管理/岗位管理 左边部门树错误emit导致会调用两次列表api
|
||||
|
||||
# 1.1.1
|
||||
|
||||
**REFACTOR**
|
||||
|
||||
- 使用VxeTable重构OAuth账号绑定列表(替代antdv的Table)
|
||||
- commonDownloadExcel方法 支持处理区间选择器字段导出excel
|
||||
|
||||
**BUG FIXES**
|
||||
|
||||
- 修复在Modal/Drawer中使用VxeTable时, 第二次打开表单参数依旧为第一次提交的参数
|
||||
|
||||
**OTHERS**
|
||||
|
||||
- 废弃downloadExcel方法 统一使用commonDownloadExcel方法
|
||||
|
||||
# 1.1.0
|
||||
|
||||
**FEATURES**
|
||||
|
||||
- 支持离线图标功能(全局可在内网环境中使用)
|
||||
|
||||
**BUG FIXES**
|
||||
|
||||
- 在VxeTable固定列时, getPopupContainer会导致宽度不够, 弹出层样式异常 解决办法(将弹窗元素挂载到VXe滚动容器上)
|
||||
|
||||
**OTHERS**
|
||||
|
||||
- 代码生成 - 字段信息修改 改为minWidth 防止在高分辨率屏幕出现空白
|
||||
|
||||
# 1.0.0
|
||||
|
||||
**FEATURES**
|
||||
|
||||
- 用户管理 新增从参数取默认密码
|
||||
- 全局表格加上id 方便进行缓存列排序的操作
|
||||
- 支持菜单名称i18n
|
||||
- 登录页 验证码登录
|
||||
- Markdown编辑/预览组件(基于vditor)
|
||||
- VxeTable搜索表单 enter提交
|
||||
|
||||
**BUG FIXES**
|
||||
|
||||
- 登录页面 关闭租户后下拉框没有正常隐藏
|
||||
- 字典管理 关闭租户不应显示`同步租户字典`按钮
|
||||
- 登录日志 漏掉了登录日志日期查询
|
||||
- 登出相关逻辑在并发(非await)情况下重复执行的问题
|
||||
- VxeTable在开启/关闭查询表单时 需要使用不同的padding
|
||||
- VxeTable表格刷新 默认为reload 修改为在当前页刷新(query)
|
||||
- 岗位管理 部门参数错误
|
||||
- 角色管理 菜单分配 节点独立下的回显及提交问题
|
||||
- 租户管理 套餐管理 回显时候`已选中节点`数量为0
|
||||
- 用户管理 更新用户时打开drawer需要加载该部门下的岗位信息
|
||||
|
||||
**OTHERS**
|
||||
|
||||
- 登录页 租户选择框浮层固定高度[256px] 超过高度自动滚动
|
||||
- 表单的Label默认方向改为`top` 支持\n换行
|
||||
- 所有表格的搜索加上allowClear属性 支持清除
|
||||
- vxe表格loading 只加载表格 不加载上面的表单
|
||||
|
||||
# 1.0.0-beta (2024-10-8)
|
||||
|
||||
**FEATURES**
|
||||
|
||||
- 基础功能已经开发完毕
|
||||
- 工作流相关模块等待后端重构后开发
|
||||
21
Yi.Vben5.Vue3/LICENSE
Normal file
21
Yi.Vben5.Vue3/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2026 dubai
|
||||
|
||||
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.
|
||||
128
Yi.Vben5.Vue3/README.md
Normal file
128
Yi.Vben5.Vue3/README.md
Normal file
@@ -0,0 +1,128 @@
|
||||
## **简介**
|
||||
|
||||
基于 [ruoyi-plus-vben & vben5 & ant-design-vue ](https://gitee.com/dapppp/ruoyi-plus-vben.git) 的 前端项目
|
||||
|
||||
**完全兼容意框架[Yi.Admin](https://gitee.com/ccnetcore/Yi) rbac模块**
|
||||
|
||||
| 组件/框架 | 版本 |
|
||||
| :------------- | :----- |
|
||||
| vben | 5.5.6 |
|
||||
| ant-design-vue | 4.2.6 |
|
||||
| vue | 3.5.13 |
|
||||
|
||||
[](LICENSE)
|
||||
|
||||
## 提示
|
||||
|
||||
该仓库使用vben最新版本v5开发
|
||||
|
||||
v5版本采用分仓(包)目录结构, 具体开发路径为: `根目录/apps/web-antd`
|
||||
|
||||
**后端需要开启”furion格式的规范化api“**:路径在Yi.Abp.Net8/src/Yi.Abp.Web/YiAbpWebModule.cs
|
||||
|
||||
## 预览
|
||||
|
||||
[预览地址点这里](https://yi.wjys.top/)
|
||||
|
||||
|
||||
## 文档
|
||||
|
||||
[原ruoyi-plus-vben 框架文档](https://dapdap.top/)
|
||||
|
||||
[Vben V5 文档地址](https://doc.vben.pro/)
|
||||
|
||||
[后端 Yi 框架 文档地址](https://gitee.com/ccnetcore/Yi)
|
||||
|
||||
## 🚀系统截图
|
||||
|
||||

|
||||
|
||||

|
||||
|
||||

|
||||
|
||||
|
||||
## 安装使用
|
||||
|
||||
前置准备环境(只能用pnpm)
|
||||
|
||||
```json
|
||||
"packageManager": "pnpm",
|
||||
"engines": {
|
||||
"node": ">=20.15.0",
|
||||
"pnpm": "latest"
|
||||
},
|
||||
```
|
||||
|
||||
- 获取项目代码
|
||||
|
||||
```bash
|
||||
git clone -b only-front --single-branch https://gitee.com/vichen2021/yiabp-mini.git
|
||||
```
|
||||
|
||||
2. 安装依赖
|
||||
|
||||
```bash
|
||||
cd yiabp-mini
|
||||
|
||||
pnpm install
|
||||
```
|
||||
|
||||
- 菜单图标替换
|
||||
|
||||
参考 [菜单图标替换](https://dapdap.top/guide/quick-start.html#%E8%8F%9C%E5%8D%95%E5%9B%BE%E6%A0%87%E5%AF%BC%E5%85%A5)
|
||||
|
||||
|
||||
2. **推荐** 使用菜单自行配置 (跟 cloud 版本打开方式一致)
|
||||
|
||||

|
||||
|
||||
使用内嵌 iframe 方式需要解决跨域问题 可参考[nginx.conf](https://gitee.com/dromara/RuoYi-Vue-Plus/blob/5.X/script/docker/nginx/conf/nginx.conf#LC87)配置
|
||||
|
||||
- 运行
|
||||
|
||||
```bash
|
||||
pnpm dev:antd
|
||||
```
|
||||
|
||||
4. 打包
|
||||
|
||||
```bash
|
||||
pnpm build:antd
|
||||
```
|
||||
|
||||
## 这是一个特性 而不是一个bug!
|
||||
|
||||
1. 菜单管理可分配 但只有`admin`/`superadmin`角色能访问 其他角色访问会到403页面
|
||||
2. 租户相关菜单可分配 但只有`superadmin`角色能访问 其他角色访问会到403页面
|
||||
3. 分配的租户管理员无法修改自己的角色的菜单(即管理员角色的菜单) 防止自己把自己权限弄没了
|
||||
|
||||
## Git 贡献提交规范
|
||||
|
||||
参考 [vue](https://github.com/vuejs/vue/blob/dev/.github/COMMIT_CONVENTION.md) 规范 ([Angular](https://github.com/conventional-changelog/conventional-changelog/tree/master/packages/conventional-changelog-angular))
|
||||
|
||||
- `feat` 增加新功能
|
||||
- `fix` 修复问题/BUG
|
||||
- `style` 代码风格相关无影响运行结果的
|
||||
- `perf` 优化/性能提升
|
||||
- `refactor` 重构
|
||||
- `revert` 撤销修改
|
||||
- `test` 测试相关
|
||||
- `docs` 文档/注释
|
||||
- `chore` 依赖更新/脚手架配置修改等
|
||||
- `workflow` 工作流改进
|
||||
- `ci` 持续集成
|
||||
- `types` 类型定义文件更改
|
||||
- `wip` 开发中
|
||||
|
||||
## 浏览器支持
|
||||
|
||||
最低适配应该为`Chrome 88+`以上浏览器 详见 [css - where](https://developer.mozilla.org/en-US/docs/Web/CSS/:where#browser_compatibility)
|
||||
|
||||
本地开发推荐使用`Chrome` 最新版本浏览器
|
||||
|
||||
支持现代浏览器,不支持 IE
|
||||
|
||||
| [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt=" Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>IE | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/edge/edge_48x48.png" alt=" Edge" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Edge | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/firefox/firefox_48x48.png" alt="Firefox" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Firefox | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/chrome/chrome_48x48.png" alt="Chrome" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Chrome | [<img src="https://raw.githubusercontent.com/alrra/browser-logos/master/src/safari/safari_48x48.png" alt="Safari" width="24px" height="24px" />](http://godban.github.io/browsers-support-badges/)</br>Safari |
|
||||
| :-: | :-: | :-: | :-: | :-: |
|
||||
| not support | last 2 versions | last 2 versions | last 2 versions | last 2 versions |
|
||||
3
Yi.Vben5.Vue3/apps/backend-mock/.env
Normal file
3
Yi.Vben5.Vue3/apps/backend-mock/.env
Normal file
@@ -0,0 +1,3 @@
|
||||
PORT=5320
|
||||
ACCESS_TOKEN_SECRET=access_token_secret
|
||||
REFRESH_TOKEN_SECRET=refresh_token_secret
|
||||
15
Yi.Vben5.Vue3/apps/backend-mock/README.md
Normal file
15
Yi.Vben5.Vue3/apps/backend-mock/README.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# @vben/backend-mock
|
||||
|
||||
## Description
|
||||
|
||||
Vben Admin 数据 mock 服务,没有对接任何的数据库,所有数据都是模拟的,用于前端开发时提供数据支持。线上环境不再提供 mock 集成,可自行部署服务或者对接真实数据,由于 `mock.js` 等工具有一些限制,比如上传文件不行、无法模拟复杂的逻辑等,所以这里使用了真实的后端服务来实现。唯一麻烦的是本地需要同时启动后端服务和前端服务,但是这样可以更好的模拟真实环境。该服务不需要手动启动,已经集成在 vite 插件内,随应用一起启用。
|
||||
|
||||
## Running the app
|
||||
|
||||
```bash
|
||||
# development
|
||||
$ pnpm run start
|
||||
|
||||
# production mode
|
||||
$ pnpm run build
|
||||
```
|
||||
14
Yi.Vben5.Vue3/apps/backend-mock/api/auth/codes.ts
Normal file
14
Yi.Vben5.Vue3/apps/backend-mock/api/auth/codes.ts
Normal file
@@ -0,0 +1,14 @@
|
||||
import { verifyAccessToken } from '~/utils/jwt-utils';
|
||||
import { unAuthorizedResponse } from '~/utils/response';
|
||||
|
||||
export default eventHandler((event) => {
|
||||
const userinfo = verifyAccessToken(event);
|
||||
if (!userinfo) {
|
||||
return unAuthorizedResponse(event);
|
||||
}
|
||||
|
||||
const codes =
|
||||
MOCK_CODES.find((item) => item.username === userinfo.username)?.codes ?? [];
|
||||
|
||||
return useResponseSuccess(codes);
|
||||
});
|
||||
36
Yi.Vben5.Vue3/apps/backend-mock/api/auth/login.post.ts
Normal file
36
Yi.Vben5.Vue3/apps/backend-mock/api/auth/login.post.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import {
|
||||
clearRefreshTokenCookie,
|
||||
setRefreshTokenCookie,
|
||||
} from '~/utils/cookie-utils';
|
||||
import { generateAccessToken, generateRefreshToken } from '~/utils/jwt-utils';
|
||||
import { forbiddenResponse } from '~/utils/response';
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const { password, username } = await readBody(event);
|
||||
if (!password || !username) {
|
||||
setResponseStatus(event, 400);
|
||||
return useResponseError(
|
||||
'BadRequestException',
|
||||
'Username and password are required',
|
||||
);
|
||||
}
|
||||
|
||||
const findUser = MOCK_USERS.find(
|
||||
(item) => item.username === username && item.password === password,
|
||||
);
|
||||
|
||||
if (!findUser) {
|
||||
clearRefreshTokenCookie(event);
|
||||
return forbiddenResponse(event, 'Username or password is incorrect.');
|
||||
}
|
||||
|
||||
const accessToken = generateAccessToken(findUser);
|
||||
const refreshToken = generateRefreshToken(findUser);
|
||||
|
||||
setRefreshTokenCookie(event, refreshToken);
|
||||
|
||||
return useResponseSuccess({
|
||||
...findUser,
|
||||
accessToken,
|
||||
});
|
||||
});
|
||||
15
Yi.Vben5.Vue3/apps/backend-mock/api/auth/logout.post.ts
Normal file
15
Yi.Vben5.Vue3/apps/backend-mock/api/auth/logout.post.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import {
|
||||
clearRefreshTokenCookie,
|
||||
getRefreshTokenFromCookie,
|
||||
} from '~/utils/cookie-utils';
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const refreshToken = getRefreshTokenFromCookie(event);
|
||||
if (!refreshToken) {
|
||||
return useResponseSuccess('');
|
||||
}
|
||||
|
||||
clearRefreshTokenCookie(event);
|
||||
|
||||
return useResponseSuccess('');
|
||||
});
|
||||
33
Yi.Vben5.Vue3/apps/backend-mock/api/auth/refresh.post.ts
Normal file
33
Yi.Vben5.Vue3/apps/backend-mock/api/auth/refresh.post.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
import {
|
||||
clearRefreshTokenCookie,
|
||||
getRefreshTokenFromCookie,
|
||||
setRefreshTokenCookie,
|
||||
} from '~/utils/cookie-utils';
|
||||
import { verifyRefreshToken } from '~/utils/jwt-utils';
|
||||
import { forbiddenResponse } from '~/utils/response';
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
const refreshToken = getRefreshTokenFromCookie(event);
|
||||
if (!refreshToken) {
|
||||
return forbiddenResponse(event);
|
||||
}
|
||||
|
||||
clearRefreshTokenCookie(event);
|
||||
|
||||
const userinfo = verifyRefreshToken(refreshToken);
|
||||
if (!userinfo) {
|
||||
return forbiddenResponse(event);
|
||||
}
|
||||
|
||||
const findUser = MOCK_USERS.find(
|
||||
(item) => item.username === userinfo.username,
|
||||
);
|
||||
if (!findUser) {
|
||||
return forbiddenResponse(event);
|
||||
}
|
||||
const accessToken = generateAccessToken(findUser);
|
||||
|
||||
setRefreshTokenCookie(event, refreshToken);
|
||||
|
||||
return accessToken;
|
||||
});
|
||||
28
Yi.Vben5.Vue3/apps/backend-mock/api/demo/bigint.ts
Normal file
28
Yi.Vben5.Vue3/apps/backend-mock/api/demo/bigint.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
export default eventHandler(async (event) => {
|
||||
const userinfo = verifyAccessToken(event);
|
||||
if (!userinfo) {
|
||||
return unAuthorizedResponse(event);
|
||||
}
|
||||
const data = `
|
||||
{
|
||||
"code": 0,
|
||||
"message": "success",
|
||||
"data": [
|
||||
{
|
||||
"id": 123456789012345678901234567890123456789012345678901234567890,
|
||||
"name": "John Doe",
|
||||
"age": 30,
|
||||
"email": "john-doe@demo.com"
|
||||
},
|
||||
{
|
||||
"id": 987654321098765432109876543210987654321098765432109876543210,
|
||||
"name": "Jane Smith",
|
||||
"age": 25,
|
||||
"email": "jane@demo.com"
|
||||
}
|
||||
]
|
||||
}
|
||||
`;
|
||||
setHeader(event, 'Content-Type', 'application/json');
|
||||
return data;
|
||||
});
|
||||
13
Yi.Vben5.Vue3/apps/backend-mock/api/menu/all.ts
Normal file
13
Yi.Vben5.Vue3/apps/backend-mock/api/menu/all.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { verifyAccessToken } from '~/utils/jwt-utils';
|
||||
import { unAuthorizedResponse } from '~/utils/response';
|
||||
|
||||
export default eventHandler(async (event) => {
|
||||
const userinfo = verifyAccessToken(event);
|
||||
if (!userinfo) {
|
||||
return unAuthorizedResponse(event);
|
||||
}
|
||||
|
||||
const menus =
|
||||
MOCK_MENUS.find((item) => item.username === userinfo.username)?.menus ?? [];
|
||||
return useResponseSuccess(menus);
|
||||
});
|
||||
5
Yi.Vben5.Vue3/apps/backend-mock/api/status.ts
Normal file
5
Yi.Vben5.Vue3/apps/backend-mock/api/status.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export default eventHandler((event) => {
|
||||
const { status } = getQuery(event);
|
||||
setResponseStatus(event, Number(status));
|
||||
return useResponseError(`${status}`);
|
||||
});
|
||||
15
Yi.Vben5.Vue3/apps/backend-mock/api/system/dept/.post.ts
Normal file
15
Yi.Vben5.Vue3/apps/backend-mock/api/system/dept/.post.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { verifyAccessToken } from '~/utils/jwt-utils';
|
||||
import {
|
||||
sleep,
|
||||
unAuthorizedResponse,
|
||||
useResponseSuccess,
|
||||
} from '~/utils/response';
|
||||
|
||||
export default eventHandler(async (event) => {
|
||||
const userinfo = verifyAccessToken(event);
|
||||
if (!userinfo) {
|
||||
return unAuthorizedResponse(event);
|
||||
}
|
||||
await sleep(600);
|
||||
return useResponseSuccess(null);
|
||||
});
|
||||
@@ -0,0 +1,15 @@
|
||||
import { verifyAccessToken } from '~/utils/jwt-utils';
|
||||
import {
|
||||
sleep,
|
||||
unAuthorizedResponse,
|
||||
useResponseSuccess,
|
||||
} from '~/utils/response';
|
||||
|
||||
export default eventHandler(async (event) => {
|
||||
const userinfo = verifyAccessToken(event);
|
||||
if (!userinfo) {
|
||||
return unAuthorizedResponse(event);
|
||||
}
|
||||
await sleep(1000);
|
||||
return useResponseSuccess(null);
|
||||
});
|
||||
15
Yi.Vben5.Vue3/apps/backend-mock/api/system/dept/[id].put.ts
Normal file
15
Yi.Vben5.Vue3/apps/backend-mock/api/system/dept/[id].put.ts
Normal file
@@ -0,0 +1,15 @@
|
||||
import { verifyAccessToken } from '~/utils/jwt-utils';
|
||||
import {
|
||||
sleep,
|
||||
unAuthorizedResponse,
|
||||
useResponseSuccess,
|
||||
} from '~/utils/response';
|
||||
|
||||
export default eventHandler(async (event) => {
|
||||
const userinfo = verifyAccessToken(event);
|
||||
if (!userinfo) {
|
||||
return unAuthorizedResponse(event);
|
||||
}
|
||||
await sleep(2000);
|
||||
return useResponseSuccess(null);
|
||||
});
|
||||
61
Yi.Vben5.Vue3/apps/backend-mock/api/system/dept/list.ts
Normal file
61
Yi.Vben5.Vue3/apps/backend-mock/api/system/dept/list.ts
Normal file
@@ -0,0 +1,61 @@
|
||||
import { faker } from '@faker-js/faker';
|
||||
import { verifyAccessToken } from '~/utils/jwt-utils';
|
||||
import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response';
|
||||
|
||||
const formatterCN = new Intl.DateTimeFormat('zh-CN', {
|
||||
timeZone: 'Asia/Shanghai',
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
});
|
||||
|
||||
function generateMockDataList(count: number) {
|
||||
const dataList = [];
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
const dataItem: Record<string, any> = {
|
||||
id: faker.string.uuid(),
|
||||
pid: 0,
|
||||
name: faker.commerce.department(),
|
||||
status: faker.helpers.arrayElement([0, 1]),
|
||||
createTime: formatterCN.format(
|
||||
faker.date.between({ from: '2021-01-01', to: '2022-12-31' }),
|
||||
),
|
||||
remark: faker.lorem.sentence(),
|
||||
};
|
||||
if (faker.datatype.boolean()) {
|
||||
dataItem.children = Array.from(
|
||||
{ length: faker.number.int({ min: 1, max: 5 }) },
|
||||
() => ({
|
||||
id: faker.string.uuid(),
|
||||
pid: dataItem.id,
|
||||
name: faker.commerce.department(),
|
||||
status: faker.helpers.arrayElement([0, 1]),
|
||||
createTime: formatterCN.format(
|
||||
faker.date.between({ from: '2023-01-01', to: '2023-12-31' }),
|
||||
),
|
||||
remark: faker.lorem.sentence(),
|
||||
}),
|
||||
);
|
||||
}
|
||||
dataList.push(dataItem);
|
||||
}
|
||||
|
||||
return dataList;
|
||||
}
|
||||
|
||||
const mockData = generateMockDataList(10);
|
||||
|
||||
export default eventHandler(async (event) => {
|
||||
const userinfo = verifyAccessToken(event);
|
||||
if (!userinfo) {
|
||||
return unAuthorizedResponse(event);
|
||||
}
|
||||
|
||||
const listData = structuredClone(mockData);
|
||||
|
||||
return useResponseSuccess(listData);
|
||||
});
|
||||
12
Yi.Vben5.Vue3/apps/backend-mock/api/system/menu/list.ts
Normal file
12
Yi.Vben5.Vue3/apps/backend-mock/api/system/menu/list.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import { verifyAccessToken } from '~/utils/jwt-utils';
|
||||
import { MOCK_MENU_LIST } from '~/utils/mock-data';
|
||||
import { unAuthorizedResponse, useResponseSuccess } from '~/utils/response';
|
||||
|
||||
export default eventHandler(async (event) => {
|
||||
const userinfo = verifyAccessToken(event);
|
||||
if (!userinfo) {
|
||||
return unAuthorizedResponse(event);
|
||||
}
|
||||
|
||||
return useResponseSuccess(MOCK_MENU_LIST);
|
||||
});
|
||||
@@ -0,0 +1,28 @@
|
||||
import { verifyAccessToken } from '~/utils/jwt-utils';
|
||||
import { MOCK_MENU_LIST } from '~/utils/mock-data';
|
||||
import { unAuthorizedResponse } from '~/utils/response';
|
||||
|
||||
const namesMap: Record<string, any> = {};
|
||||
|
||||
function getNames(menus: any[]) {
|
||||
menus.forEach((menu) => {
|
||||
namesMap[menu.name] = String(menu.id);
|
||||
if (menu.children) {
|
||||
getNames(menu.children);
|
||||
}
|
||||
});
|
||||
}
|
||||
getNames(MOCK_MENU_LIST);
|
||||
|
||||
export default eventHandler(async (event) => {
|
||||
const userinfo = verifyAccessToken(event);
|
||||
if (!userinfo) {
|
||||
return unAuthorizedResponse(event);
|
||||
}
|
||||
const { id, name } = getQuery(event);
|
||||
|
||||
return (name as string) in namesMap &&
|
||||
(!id || namesMap[name as string] !== String(id))
|
||||
? useResponseSuccess(true)
|
||||
: useResponseSuccess(false);
|
||||
});
|
||||
@@ -0,0 +1,28 @@
|
||||
import { verifyAccessToken } from '~/utils/jwt-utils';
|
||||
import { MOCK_MENU_LIST } from '~/utils/mock-data';
|
||||
import { unAuthorizedResponse } from '~/utils/response';
|
||||
|
||||
const pathMap: Record<string, any> = { '/': 0 };
|
||||
|
||||
function getPaths(menus: any[]) {
|
||||
menus.forEach((menu) => {
|
||||
pathMap[menu.path] = String(menu.id);
|
||||
if (menu.children) {
|
||||
getPaths(menu.children);
|
||||
}
|
||||
});
|
||||
}
|
||||
getPaths(MOCK_MENU_LIST);
|
||||
|
||||
export default eventHandler(async (event) => {
|
||||
const userinfo = verifyAccessToken(event);
|
||||
if (!userinfo) {
|
||||
return unAuthorizedResponse(event);
|
||||
}
|
||||
const { id, path } = getQuery(event);
|
||||
|
||||
return (path as string) in pathMap &&
|
||||
(!id || pathMap[path as string] !== String(id))
|
||||
? useResponseSuccess(true)
|
||||
: useResponseSuccess(false);
|
||||
});
|
||||
83
Yi.Vben5.Vue3/apps/backend-mock/api/system/role/list.ts
Normal file
83
Yi.Vben5.Vue3/apps/backend-mock/api/system/role/list.ts
Normal file
@@ -0,0 +1,83 @@
|
||||
import { faker } from '@faker-js/faker';
|
||||
import { verifyAccessToken } from '~/utils/jwt-utils';
|
||||
import { getMenuIds, MOCK_MENU_LIST } from '~/utils/mock-data';
|
||||
import { unAuthorizedResponse, usePageResponseSuccess } from '~/utils/response';
|
||||
|
||||
const formatterCN = new Intl.DateTimeFormat('zh-CN', {
|
||||
timeZone: 'Asia/Shanghai',
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
});
|
||||
|
||||
const menuIds = getMenuIds(MOCK_MENU_LIST);
|
||||
|
||||
function generateMockDataList(count: number) {
|
||||
const dataList = [];
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
const dataItem: Record<string, any> = {
|
||||
id: faker.string.uuid(),
|
||||
name: faker.commerce.product(),
|
||||
status: faker.helpers.arrayElement([0, 1]),
|
||||
createTime: formatterCN.format(
|
||||
faker.date.between({ from: '2022-01-01', to: '2025-01-01' }),
|
||||
),
|
||||
permissions: faker.helpers.arrayElements(menuIds),
|
||||
remark: faker.lorem.sentence(),
|
||||
};
|
||||
|
||||
dataList.push(dataItem);
|
||||
}
|
||||
|
||||
return dataList;
|
||||
}
|
||||
|
||||
const mockData = generateMockDataList(100);
|
||||
|
||||
export default eventHandler(async (event) => {
|
||||
const userinfo = verifyAccessToken(event);
|
||||
if (!userinfo) {
|
||||
return unAuthorizedResponse(event);
|
||||
}
|
||||
|
||||
const {
|
||||
page = 1,
|
||||
MaxResultCount = 20,
|
||||
name,
|
||||
id,
|
||||
remark,
|
||||
startTime,
|
||||
endTime,
|
||||
status,
|
||||
} = getQuery(event);
|
||||
let listData = structuredClone(mockData);
|
||||
if (name) {
|
||||
listData = listData.filter((item) =>
|
||||
item.name.toLowerCase().includes(String(name).toLowerCase()),
|
||||
);
|
||||
}
|
||||
if (id) {
|
||||
listData = listData.filter((item) =>
|
||||
item.id.toLowerCase().includes(String(id).toLowerCase()),
|
||||
);
|
||||
}
|
||||
if (remark) {
|
||||
listData = listData.filter((item) =>
|
||||
item.remark?.toLowerCase()?.includes(String(remark).toLowerCase()),
|
||||
);
|
||||
}
|
||||
if (startTime) {
|
||||
listData = listData.filter((item) => item.createTime >= startTime);
|
||||
}
|
||||
if (endTime) {
|
||||
listData = listData.filter((item) => item.createTime <= endTime);
|
||||
}
|
||||
if (['0', '1'].includes(status as string)) {
|
||||
listData = listData.filter((item) => item.status === Number(status));
|
||||
}
|
||||
return usePageResponseSuccess(page as string, MaxResultCount as string, listData);
|
||||
});
|
||||
73
Yi.Vben5.Vue3/apps/backend-mock/api/table/list.ts
Normal file
73
Yi.Vben5.Vue3/apps/backend-mock/api/table/list.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
import { faker } from '@faker-js/faker';
|
||||
import { verifyAccessToken } from '~/utils/jwt-utils';
|
||||
import { unAuthorizedResponse, usePageResponseSuccess } from '~/utils/response';
|
||||
|
||||
function generateMockDataList(count: number) {
|
||||
const dataList = [];
|
||||
|
||||
for (let i = 0; i < count; i++) {
|
||||
const dataItem = {
|
||||
id: faker.string.uuid(),
|
||||
imageUrl: faker.image.avatar(),
|
||||
imageUrl2: faker.image.avatar(),
|
||||
open: faker.datatype.boolean(),
|
||||
status: faker.helpers.arrayElement(['success', 'error', 'warning']),
|
||||
productName: faker.commerce.productName(),
|
||||
price: faker.commerce.price(),
|
||||
currency: faker.finance.currencyCode(),
|
||||
quantity: faker.number.int({ min: 1, max: 100 }),
|
||||
available: faker.datatype.boolean(),
|
||||
category: faker.commerce.department(),
|
||||
releaseDate: faker.date.past(),
|
||||
rating: faker.number.float({ min: 1, max: 5 }),
|
||||
description: faker.commerce.productDescription(),
|
||||
weight: faker.number.float({ min: 0.1, max: 10 }),
|
||||
color: faker.color.human(),
|
||||
inProduction: faker.datatype.boolean(),
|
||||
tags: Array.from({ length: 3 }, () => faker.commerce.productAdjective()),
|
||||
};
|
||||
|
||||
dataList.push(dataItem);
|
||||
}
|
||||
|
||||
return dataList;
|
||||
}
|
||||
|
||||
const mockData = generateMockDataList(100);
|
||||
|
||||
export default eventHandler(async (event) => {
|
||||
const userinfo = verifyAccessToken(event);
|
||||
if (!userinfo) {
|
||||
return unAuthorizedResponse(event);
|
||||
}
|
||||
|
||||
await sleep(600);
|
||||
|
||||
const { page, MaxResultCount, sortBy, sortOrder } = getQuery(event);
|
||||
const listData = structuredClone(mockData);
|
||||
if (sortBy && Reflect.has(listData[0], sortBy as string)) {
|
||||
listData.sort((a, b) => {
|
||||
if (sortOrder === 'asc') {
|
||||
if (sortBy === 'price') {
|
||||
return (
|
||||
Number.parseFloat(a[sortBy as string]) -
|
||||
Number.parseFloat(b[sortBy as string])
|
||||
);
|
||||
} else {
|
||||
return a[sortBy as string] > b[sortBy as string] ? 1 : -1;
|
||||
}
|
||||
} else {
|
||||
if (sortBy === 'price') {
|
||||
return (
|
||||
Number.parseFloat(b[sortBy as string]) -
|
||||
Number.parseFloat(a[sortBy as string])
|
||||
);
|
||||
} else {
|
||||
return a[sortBy as string] < b[sortBy as string] ? 1 : -1;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return usePageResponseSuccess(page as string, MaxResultCount as string, listData);
|
||||
});
|
||||
1
Yi.Vben5.Vue3/apps/backend-mock/api/test.get.ts
Normal file
1
Yi.Vben5.Vue3/apps/backend-mock/api/test.get.ts
Normal file
@@ -0,0 +1 @@
|
||||
export default defineEventHandler(() => 'Test get handler');
|
||||
1
Yi.Vben5.Vue3/apps/backend-mock/api/test.post.ts
Normal file
1
Yi.Vben5.Vue3/apps/backend-mock/api/test.post.ts
Normal file
@@ -0,0 +1 @@
|
||||
export default defineEventHandler(() => 'Test post handler');
|
||||
13
Yi.Vben5.Vue3/apps/backend-mock/api/upload.ts
Normal file
13
Yi.Vben5.Vue3/apps/backend-mock/api/upload.ts
Normal file
@@ -0,0 +1,13 @@
|
||||
import { verifyAccessToken } from '~/utils/jwt-utils';
|
||||
import { unAuthorizedResponse } from '~/utils/response';
|
||||
|
||||
export default eventHandler((event) => {
|
||||
const userinfo = verifyAccessToken(event);
|
||||
if (!userinfo) {
|
||||
return unAuthorizedResponse(event);
|
||||
}
|
||||
return useResponseSuccess({
|
||||
url: 'https://unpkg.com/@vbenjs/static-source@0.1.7/source/logo-v1.webp',
|
||||
});
|
||||
// return useResponseError("test")
|
||||
});
|
||||
10
Yi.Vben5.Vue3/apps/backend-mock/api/user/info.ts
Normal file
10
Yi.Vben5.Vue3/apps/backend-mock/api/user/info.ts
Normal file
@@ -0,0 +1,10 @@
|
||||
import { verifyAccessToken } from '~/utils/jwt-utils';
|
||||
import { unAuthorizedResponse } from '~/utils/response';
|
||||
|
||||
export default eventHandler((event) => {
|
||||
const userinfo = verifyAccessToken(event);
|
||||
if (!userinfo) {
|
||||
return unAuthorizedResponse(event);
|
||||
}
|
||||
return useResponseSuccess(userinfo);
|
||||
});
|
||||
7
Yi.Vben5.Vue3/apps/backend-mock/error.ts
Normal file
7
Yi.Vben5.Vue3/apps/backend-mock/error.ts
Normal file
@@ -0,0 +1,7 @@
|
||||
import type { NitroErrorHandler } from 'nitropack';
|
||||
|
||||
const errorHandler: NitroErrorHandler = function (error, event) {
|
||||
event.node.res.end(`[Error Handler] ${error.stack}`);
|
||||
};
|
||||
|
||||
export default errorHandler;
|
||||
19
Yi.Vben5.Vue3/apps/backend-mock/middleware/1.api.ts
Normal file
19
Yi.Vben5.Vue3/apps/backend-mock/middleware/1.api.ts
Normal file
@@ -0,0 +1,19 @@
|
||||
import { forbiddenResponse, sleep } from '~/utils/response';
|
||||
|
||||
export default defineEventHandler(async (event) => {
|
||||
event.node.res.setHeader(
|
||||
'Access-Control-Allow-Origin',
|
||||
event.headers.get('Origin') ?? '*',
|
||||
);
|
||||
if (event.method === 'OPTIONS') {
|
||||
event.node.res.statusCode = 204;
|
||||
event.node.res.statusMessage = 'No Content.';
|
||||
return 'OK';
|
||||
} else if (
|
||||
['DELETE', 'PATCH', 'POST', 'PUT'].includes(event.method) &&
|
||||
event.path.startsWith('/api/')
|
||||
) {
|
||||
await sleep(Math.floor(Math.random() * 2000));
|
||||
return forbiddenResponse(event, '演示环境,禁止修改');
|
||||
}
|
||||
});
|
||||
20
Yi.Vben5.Vue3/apps/backend-mock/nitro.config.ts
Normal file
20
Yi.Vben5.Vue3/apps/backend-mock/nitro.config.ts
Normal file
@@ -0,0 +1,20 @@
|
||||
import errorHandler from './error';
|
||||
|
||||
process.env.COMPATIBILITY_DATE = new Date().toISOString();
|
||||
export default defineNitroConfig({
|
||||
devErrorHandler: errorHandler,
|
||||
errorHandler: '~/error',
|
||||
routeRules: {
|
||||
'/api/**': {
|
||||
cors: true,
|
||||
headers: {
|
||||
'Access-Control-Allow-Credentials': 'true',
|
||||
'Access-Control-Allow-Headers':
|
||||
'Accept, Authorization, Content-Length, Content-Type, If-Match, If-Modified-Since, If-None-Match, If-Unmodified-Since, X-CSRF-TOKEN, X-Requested-With',
|
||||
'Access-Control-Allow-Methods': 'GET,HEAD,PUT,PATCH,POST,DELETE',
|
||||
'Access-Control-Allow-Origin': '*',
|
||||
'Access-Control-Expose-Headers': '*',
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
21
Yi.Vben5.Vue3/apps/backend-mock/package.json
Normal file
21
Yi.Vben5.Vue3/apps/backend-mock/package.json
Normal file
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"name": "@vben/backend-mock",
|
||||
"version": "0.0.1",
|
||||
"description": "",
|
||||
"private": true,
|
||||
"license": "MIT",
|
||||
"author": "",
|
||||
"scripts": {
|
||||
"build": "nitro build",
|
||||
"start": "nitro dev"
|
||||
},
|
||||
"dependencies": {
|
||||
"@faker-js/faker": "catalog:",
|
||||
"jsonwebtoken": "catalog:",
|
||||
"nitropack": "catalog:"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jsonwebtoken": "catalog:",
|
||||
"h3": "catalog:"
|
||||
}
|
||||
}
|
||||
13
Yi.Vben5.Vue3/apps/backend-mock/routes/[...].ts
Normal file
13
Yi.Vben5.Vue3/apps/backend-mock/routes/[...].ts
Normal file
@@ -0,0 +1,13 @@
|
||||
export default defineEventHandler(() => {
|
||||
return `
|
||||
<h1>Hello Vben Admin</h1>
|
||||
<h2>Mock service is starting</h2>
|
||||
<ul>
|
||||
<li><a href="/api/user">/api/user/info</a></li>
|
||||
<li><a href="/api/menu">/api/menu/all</a></li>
|
||||
<li><a href="/api/auth/codes">/api/auth/codes</a></li>
|
||||
<li><a href="/api/auth/login">/api/auth/login</a></li>
|
||||
<li><a href="/api/upload">/api/upload</a></li>
|
||||
</ul>
|
||||
`;
|
||||
});
|
||||
4
Yi.Vben5.Vue3/apps/backend-mock/tsconfig.build.json
Normal file
4
Yi.Vben5.Vue3/apps/backend-mock/tsconfig.build.json
Normal file
@@ -0,0 +1,4 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"exclude": ["node_modules", "test", "dist", "**/*spec.ts"]
|
||||
}
|
||||
3
Yi.Vben5.Vue3/apps/backend-mock/tsconfig.json
Normal file
3
Yi.Vben5.Vue3/apps/backend-mock/tsconfig.json
Normal file
@@ -0,0 +1,3 @@
|
||||
{
|
||||
"extends": "./.nitro/types/tsconfig.json"
|
||||
}
|
||||
26
Yi.Vben5.Vue3/apps/backend-mock/utils/cookie-utils.ts
Normal file
26
Yi.Vben5.Vue3/apps/backend-mock/utils/cookie-utils.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
import type { EventHandlerRequest, H3Event } from 'h3';
|
||||
|
||||
export function clearRefreshTokenCookie(event: H3Event<EventHandlerRequest>) {
|
||||
deleteCookie(event, 'jwt', {
|
||||
httpOnly: true,
|
||||
sameSite: 'none',
|
||||
secure: true,
|
||||
});
|
||||
}
|
||||
|
||||
export function setRefreshTokenCookie(
|
||||
event: H3Event<EventHandlerRequest>,
|
||||
refreshToken: string,
|
||||
) {
|
||||
setCookie(event, 'jwt', refreshToken, {
|
||||
httpOnly: true,
|
||||
maxAge: 24 * 60 * 60, // unit: seconds
|
||||
sameSite: 'none',
|
||||
secure: true,
|
||||
});
|
||||
}
|
||||
|
||||
export function getRefreshTokenFromCookie(event: H3Event<EventHandlerRequest>) {
|
||||
const refreshToken = getCookie(event, 'jwt');
|
||||
return refreshToken;
|
||||
}
|
||||
59
Yi.Vben5.Vue3/apps/backend-mock/utils/jwt-utils.ts
Normal file
59
Yi.Vben5.Vue3/apps/backend-mock/utils/jwt-utils.ts
Normal file
@@ -0,0 +1,59 @@
|
||||
import type { EventHandlerRequest, H3Event } from 'h3';
|
||||
|
||||
import jwt from 'jsonwebtoken';
|
||||
|
||||
import { UserInfo } from './mock-data';
|
||||
|
||||
// TODO: Replace with your own secret key
|
||||
const ACCESS_TOKEN_SECRET = 'access_token_secret';
|
||||
const REFRESH_TOKEN_SECRET = 'refresh_token_secret';
|
||||
|
||||
export interface UserPayload extends UserInfo {
|
||||
iat: number;
|
||||
exp: number;
|
||||
}
|
||||
|
||||
export function generateAccessToken(user: UserInfo) {
|
||||
return jwt.sign(user, ACCESS_TOKEN_SECRET, { expiresIn: '7d' });
|
||||
}
|
||||
|
||||
export function generateRefreshToken(user: UserInfo) {
|
||||
return jwt.sign(user, REFRESH_TOKEN_SECRET, {
|
||||
expiresIn: '30d',
|
||||
});
|
||||
}
|
||||
|
||||
export function verifyAccessToken(
|
||||
event: H3Event<EventHandlerRequest>,
|
||||
): null | Omit<UserInfo, 'password'> {
|
||||
const authHeader = getHeader(event, 'Authorization');
|
||||
if (!authHeader?.startsWith('Bearer')) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const token = authHeader.split(' ')[1];
|
||||
try {
|
||||
const decoded = jwt.verify(token, ACCESS_TOKEN_SECRET) as UserPayload;
|
||||
|
||||
const username = decoded.username;
|
||||
const user = MOCK_USERS.find((item) => item.username === username);
|
||||
const { password: _pwd, ...userinfo } = user;
|
||||
return userinfo;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
export function verifyRefreshToken(
|
||||
token: string,
|
||||
): null | Omit<UserInfo, 'password'> {
|
||||
try {
|
||||
const decoded = jwt.verify(token, REFRESH_TOKEN_SECRET) as UserPayload;
|
||||
const username = decoded.username;
|
||||
const user = MOCK_USERS.find((item) => item.username === username);
|
||||
const { password: _pwd, ...userinfo } = user;
|
||||
return userinfo;
|
||||
} catch {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
390
Yi.Vben5.Vue3/apps/backend-mock/utils/mock-data.ts
Normal file
390
Yi.Vben5.Vue3/apps/backend-mock/utils/mock-data.ts
Normal file
@@ -0,0 +1,390 @@
|
||||
export interface UserInfo {
|
||||
id: number;
|
||||
password: string;
|
||||
realName: string;
|
||||
roles: string[];
|
||||
username: string;
|
||||
homePath?: string;
|
||||
}
|
||||
|
||||
export const MOCK_USERS: UserInfo[] = [
|
||||
{
|
||||
id: 0,
|
||||
password: '123456',
|
||||
realName: 'Vben',
|
||||
roles: ['super'],
|
||||
username: 'vben',
|
||||
},
|
||||
{
|
||||
id: 1,
|
||||
password: '123456',
|
||||
realName: 'Admin',
|
||||
roles: ['admin'],
|
||||
username: 'admin',
|
||||
homePath: '/workspace',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
password: '123456',
|
||||
realName: 'Jack',
|
||||
roles: ['user'],
|
||||
username: 'jack',
|
||||
homePath: '/analytics',
|
||||
},
|
||||
];
|
||||
|
||||
export const MOCK_CODES = [
|
||||
// super
|
||||
{
|
||||
codes: ['AC_100100', 'AC_100110', 'AC_100120', 'AC_100010'],
|
||||
username: 'vben',
|
||||
},
|
||||
{
|
||||
// admin
|
||||
codes: ['AC_100010', 'AC_100020', 'AC_100030'],
|
||||
username: 'admin',
|
||||
},
|
||||
{
|
||||
// user
|
||||
codes: ['AC_1000001', 'AC_1000002'],
|
||||
username: 'jack',
|
||||
},
|
||||
];
|
||||
|
||||
const dashboardMenus = [
|
||||
{
|
||||
meta: {
|
||||
order: -1,
|
||||
title: 'page.dashboard.title',
|
||||
},
|
||||
name: 'Dashboard',
|
||||
path: '/dashboard',
|
||||
redirect: '/analytics',
|
||||
children: [
|
||||
{
|
||||
name: 'Analytics',
|
||||
path: '/analytics',
|
||||
component: '/dashboard/analytics/index',
|
||||
meta: {
|
||||
affixTab: true,
|
||||
title: 'page.dashboard.analytics',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'Workspace',
|
||||
path: '/workspace',
|
||||
component: '/dashboard/workspace/index',
|
||||
meta: {
|
||||
title: 'page.dashboard.workspace',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
const createDemosMenus = (role: 'admin' | 'super' | 'user') => {
|
||||
const roleWithMenus = {
|
||||
admin: {
|
||||
component: '/demos/access/admin-visible',
|
||||
meta: {
|
||||
icon: 'mdi:button-cursor',
|
||||
title: 'demos.access.adminVisible',
|
||||
},
|
||||
name: 'AccessAdminVisibleDemo',
|
||||
path: '/demos/access/admin-visible',
|
||||
},
|
||||
super: {
|
||||
component: '/demos/access/super-visible',
|
||||
meta: {
|
||||
icon: 'mdi:button-cursor',
|
||||
title: 'demos.access.superVisible',
|
||||
},
|
||||
name: 'AccessSuperVisibleDemo',
|
||||
path: '/demos/access/super-visible',
|
||||
},
|
||||
user: {
|
||||
component: '/demos/access/user-visible',
|
||||
meta: {
|
||||
icon: 'mdi:button-cursor',
|
||||
title: 'demos.access.userVisible',
|
||||
},
|
||||
name: 'AccessUserVisibleDemo',
|
||||
path: '/demos/access/user-visible',
|
||||
},
|
||||
};
|
||||
|
||||
return [
|
||||
{
|
||||
meta: {
|
||||
icon: 'ic:baseline-view-in-ar',
|
||||
keepAlive: true,
|
||||
order: 1000,
|
||||
title: 'demos.title',
|
||||
},
|
||||
name: 'Demos',
|
||||
path: '/demos',
|
||||
redirect: '/demos/access',
|
||||
children: [
|
||||
{
|
||||
name: 'AccessDemos',
|
||||
path: '/demosaccess',
|
||||
meta: {
|
||||
icon: 'mdi:cloud-key-outline',
|
||||
title: 'demos.access.backendPermissions',
|
||||
},
|
||||
redirect: '/demos/access/page-control',
|
||||
children: [
|
||||
{
|
||||
name: 'AccessPageControlDemo',
|
||||
path: '/demos/access/page-control',
|
||||
component: '/demos/access/index',
|
||||
meta: {
|
||||
icon: 'mdi:page-previous-outline',
|
||||
title: 'demos.access.pageAccess',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'AccessButtonControlDemo',
|
||||
path: '/demos/access/button-control',
|
||||
component: '/demos/access/button-control',
|
||||
meta: {
|
||||
icon: 'mdi:button-cursor',
|
||||
title: 'demos.access.buttonControl',
|
||||
},
|
||||
},
|
||||
{
|
||||
name: 'AccessMenuVisible403Demo',
|
||||
path: '/demos/access/menu-visible-403',
|
||||
component: '/demos/access/menu-visible-403',
|
||||
meta: {
|
||||
authority: ['no-body'],
|
||||
icon: 'mdi:button-cursor',
|
||||
menuVisibleWithForbidden: true,
|
||||
title: 'demos.access.menuVisible403',
|
||||
},
|
||||
},
|
||||
roleWithMenus[role],
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
};
|
||||
|
||||
export const MOCK_MENUS = [
|
||||
{
|
||||
menus: [...dashboardMenus, ...createDemosMenus('super')],
|
||||
username: 'vben',
|
||||
},
|
||||
{
|
||||
menus: [...dashboardMenus, ...createDemosMenus('admin')],
|
||||
username: 'admin',
|
||||
},
|
||||
{
|
||||
menus: [...dashboardMenus, ...createDemosMenus('user')],
|
||||
username: 'jack',
|
||||
},
|
||||
];
|
||||
|
||||
export const MOCK_MENU_LIST = [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Workspace',
|
||||
status: 1,
|
||||
type: 'menu',
|
||||
icon: 'mdi:dashboard',
|
||||
path: '/workspace',
|
||||
component: '/dashboard/workspace/index',
|
||||
meta: {
|
||||
icon: 'carbon:workspace',
|
||||
title: 'page.dashboard.workspace',
|
||||
affixTab: true,
|
||||
order: 0,
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
meta: {
|
||||
icon: 'carbon:settings',
|
||||
order: 9997,
|
||||
title: 'system.title',
|
||||
badge: 'new',
|
||||
badgeType: 'normal',
|
||||
badgeVariants: 'primary',
|
||||
},
|
||||
status: 1,
|
||||
type: 'catalog',
|
||||
name: 'System',
|
||||
path: '/system',
|
||||
children: [
|
||||
{
|
||||
id: 201,
|
||||
pid: 2,
|
||||
path: '/system/menu',
|
||||
name: 'SystemMenu',
|
||||
authCode: 'System:Menu:List',
|
||||
status: 1,
|
||||
type: 'menu',
|
||||
meta: {
|
||||
icon: 'carbon:menu',
|
||||
title: 'system.menu.title',
|
||||
},
|
||||
component: '/system/menu/list',
|
||||
children: [
|
||||
{
|
||||
id: 20_101,
|
||||
pid: 201,
|
||||
name: 'SystemMenuCreate',
|
||||
status: 1,
|
||||
type: 'button',
|
||||
authCode: 'System:Menu:Create',
|
||||
meta: { title: 'common.create' },
|
||||
},
|
||||
{
|
||||
id: 20_102,
|
||||
pid: 201,
|
||||
name: 'SystemMenuEdit',
|
||||
status: 1,
|
||||
type: 'button',
|
||||
authCode: 'System:Menu:Edit',
|
||||
meta: { title: 'common.edit' },
|
||||
},
|
||||
{
|
||||
id: 20_103,
|
||||
pid: 201,
|
||||
name: 'SystemMenuDelete',
|
||||
status: 1,
|
||||
type: 'button',
|
||||
authCode: 'System:Menu:Delete',
|
||||
meta: { title: 'common.delete' },
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 202,
|
||||
pid: 2,
|
||||
path: '/system/dept',
|
||||
name: 'SystemDept',
|
||||
status: 1,
|
||||
type: 'menu',
|
||||
authCode: 'System:Dept:List',
|
||||
meta: {
|
||||
icon: 'carbon:container-services',
|
||||
title: 'system.dept.title',
|
||||
},
|
||||
component: '/system/dept/list',
|
||||
children: [
|
||||
{
|
||||
id: 20_401,
|
||||
pid: 201,
|
||||
name: 'SystemDeptCreate',
|
||||
status: 1,
|
||||
type: 'button',
|
||||
authCode: 'System:Dept:Create',
|
||||
meta: { title: 'common.create' },
|
||||
},
|
||||
{
|
||||
id: 20_402,
|
||||
pid: 201,
|
||||
name: 'SystemDeptEdit',
|
||||
status: 1,
|
||||
type: 'button',
|
||||
authCode: 'System:Dept:Edit',
|
||||
meta: { title: 'common.edit' },
|
||||
},
|
||||
{
|
||||
id: 20_403,
|
||||
pid: 201,
|
||||
name: 'SystemDeptDelete',
|
||||
status: 1,
|
||||
type: 'button',
|
||||
authCode: 'System:Dept:Delete',
|
||||
meta: { title: 'common.delete' },
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 9,
|
||||
meta: {
|
||||
badgeType: 'dot',
|
||||
order: 9998,
|
||||
title: 'demos.vben.title',
|
||||
icon: 'carbon:data-center',
|
||||
},
|
||||
name: 'Project',
|
||||
path: '/vben-admin',
|
||||
type: 'catalog',
|
||||
status: 1,
|
||||
children: [
|
||||
{
|
||||
id: 901,
|
||||
pid: 9,
|
||||
name: 'VbenDocument',
|
||||
path: '/vben-admin/document',
|
||||
component: 'IFrameView',
|
||||
type: 'embedded',
|
||||
status: 1,
|
||||
meta: {
|
||||
icon: 'carbon:book',
|
||||
iframeSrc: 'https://doc.vben.pro',
|
||||
title: 'demos.vben.document',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 902,
|
||||
pid: 9,
|
||||
name: 'VbenGithub',
|
||||
path: '/vben-admin/github',
|
||||
component: 'IFrameView',
|
||||
type: 'link',
|
||||
status: 1,
|
||||
meta: {
|
||||
icon: 'carbon:logo-github',
|
||||
link: 'https://github.com/vbenjs/vue-vben-admin',
|
||||
title: 'Github',
|
||||
},
|
||||
},
|
||||
{
|
||||
id: 903,
|
||||
pid: 9,
|
||||
name: 'VbenAntdv',
|
||||
path: '/vben-admin/antdv',
|
||||
component: 'IFrameView',
|
||||
type: 'link',
|
||||
status: 0,
|
||||
meta: {
|
||||
icon: 'carbon:hexagon-vertical-solid',
|
||||
badgeType: 'dot',
|
||||
link: 'https://ant.vben.pro',
|
||||
title: 'demos.vben.antdv',
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 10,
|
||||
component: '_core/about/index',
|
||||
type: 'menu',
|
||||
status: 1,
|
||||
meta: {
|
||||
icon: 'lucide:copyright',
|
||||
order: 9999,
|
||||
title: 'demos.vben.about',
|
||||
},
|
||||
name: 'About',
|
||||
path: '/about',
|
||||
},
|
||||
];
|
||||
|
||||
export function getMenuIds(menus: any[]) {
|
||||
const ids: number[] = [];
|
||||
menus.forEach((item) => {
|
||||
ids.push(item.id);
|
||||
if (item.children && item.children.length > 0) {
|
||||
ids.push(...getMenuIds(item.children));
|
||||
}
|
||||
});
|
||||
return ids;
|
||||
}
|
||||
68
Yi.Vben5.Vue3/apps/backend-mock/utils/response.ts
Normal file
68
Yi.Vben5.Vue3/apps/backend-mock/utils/response.ts
Normal file
@@ -0,0 +1,68 @@
|
||||
import type { EventHandlerRequest, H3Event } from 'h3';
|
||||
|
||||
export function useResponseSuccess<T = any>(data: T) {
|
||||
return {
|
||||
code: 0,
|
||||
data,
|
||||
error: null,
|
||||
message: 'ok',
|
||||
};
|
||||
}
|
||||
|
||||
export function usePageResponseSuccess<T = any>(
|
||||
page: number | string,
|
||||
MaxResultCount: number | string,
|
||||
list: T[],
|
||||
{ message = 'ok' } = {},
|
||||
) {
|
||||
const pageData = pagination(
|
||||
Number.parseInt(`${page}`),
|
||||
Number.parseInt(`${MaxResultCount}`),
|
||||
list,
|
||||
);
|
||||
|
||||
return {
|
||||
...useResponseSuccess({
|
||||
items: pageData,
|
||||
total: list.length,
|
||||
}),
|
||||
message,
|
||||
};
|
||||
}
|
||||
|
||||
export function useResponseError(message: string, error: any = null) {
|
||||
return {
|
||||
code: -1,
|
||||
data: null,
|
||||
error,
|
||||
message,
|
||||
};
|
||||
}
|
||||
|
||||
export function forbiddenResponse(
|
||||
event: H3Event<EventHandlerRequest>,
|
||||
message = 'Forbidden Exception',
|
||||
) {
|
||||
setResponseStatus(event, 403);
|
||||
return useResponseError(message, message);
|
||||
}
|
||||
|
||||
export function unAuthorizedResponse(event: H3Event<EventHandlerRequest>) {
|
||||
setResponseStatus(event, 401);
|
||||
return useResponseError('Unauthorized Exception', 'Unauthorized Exception');
|
||||
}
|
||||
|
||||
export function sleep(ms: number) {
|
||||
return new Promise((resolve) => setTimeout(resolve, ms));
|
||||
}
|
||||
|
||||
export function pagination<T = any>(
|
||||
pageNo: number,
|
||||
MaxResultCount: number,
|
||||
array: T[],
|
||||
): T[] {
|
||||
const offset = (pageNo - 1) * Number(MaxResultCount);
|
||||
return offset + Number(MaxResultCount) >= array.length
|
||||
? array.slice(offset)
|
||||
: array.slice(offset, offset + Number(MaxResultCount));
|
||||
}
|
||||
8
Yi.Vben5.Vue3/apps/web-antd/.env
Normal file
8
Yi.Vben5.Vue3/apps/web-antd/.env
Normal file
@@ -0,0 +1,8 @@
|
||||
# 应用标题
|
||||
VITE_APP_TITLE=Yi Admin
|
||||
|
||||
# 应用命名空间,用于缓存、store等功能的前缀,确保隔离
|
||||
VITE_APP_NAMESPACE=vben-web-antd
|
||||
|
||||
# 对store进行加密的密钥,在将store持久化到localStorage时会使用该密钥进行加密
|
||||
VITE_APP_STORE_SECURE_KEY=please-replace-me-with-your-own-key
|
||||
7
Yi.Vben5.Vue3/apps/web-antd/.env.analyze
Normal file
7
Yi.Vben5.Vue3/apps/web-antd/.env.analyze
Normal file
@@ -0,0 +1,7 @@
|
||||
# public path
|
||||
VITE_BASE=/
|
||||
|
||||
# Basic interface address SPA
|
||||
VITE_GLOB_API_URL=/api/app
|
||||
|
||||
VITE_VISUALIZER=true
|
||||
25
Yi.Vben5.Vue3/apps/web-antd/.env.development
Normal file
25
Yi.Vben5.Vue3/apps/web-antd/.env.development
Normal file
@@ -0,0 +1,25 @@
|
||||
# 端口号
|
||||
VITE_PORT=5666
|
||||
|
||||
VITE_BASE=/
|
||||
# 是否开启 Nitro Mock服务,true 为开启,false 为关闭
|
||||
VITE_NITRO_MOCK=false
|
||||
# 是否打开 devtools,true 为打开,false 为关闭
|
||||
VITE_DEVTOOLS=false
|
||||
# 是否注入全局loading
|
||||
VITE_INJECT_APP_LOADING=true
|
||||
|
||||
# 后台请求路径 具体在vite.config.mts配置代理
|
||||
# VITE_GLOB_API_URL=http://101.37.70.137:19001/api/app
|
||||
VITE_GLOB_API_URL=http://192.168.1.101:19001/api/app
|
||||
|
||||
# 全局加密开关(即开启了加解密功能才会生效 不是全部接口加密 需要和后端对应)
|
||||
VITE_GLOB_ENABLE_ENCRYPT=false
|
||||
# RSA公钥 请求加密使用 注意这两个是两对RSA公私钥 请求加密-后端解密是一对 响应解密-后端加密是一对
|
||||
VITE_GLOB_RSA_PUBLIC_KEY=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ==
|
||||
# RSA私钥 响应解密使用 注意这两个是两对RSA公私钥 请求加密-后端解密是一对 响应解密-后端加密是一对
|
||||
VITE_GLOB_RSA_PRIVATE_KEY=MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAmc3CuPiGL/LcIIm7zryCEIbl1SPzBkr75E2VMtxegyZ1lYRD+7TZGAPkvIsBcaMs6Nsy0L78n2qh+lIZMpLH8wIDAQABAkEAk82Mhz0tlv6IVCyIcw/s3f0E+WLmtPFyR9/WtV3Y5aaejUkU60JpX4m5xNR2VaqOLTZAYjW8Wy0aXr3zYIhhQQIhAMfqR9oFdYw1J9SsNc+CrhugAvKTi0+BF6VoL6psWhvbAiEAxPPNTmrkmrXwdm/pQQu3UOQmc2vCZ5tiKpW10CgJi8kCIFGkL6utxw93Ncj4exE/gPLvKcT+1Emnoox+O9kRXss5AiAMtYLJDaLEzPrAWcZeeSgSIzbL+ecokmFKSDDcRske6QIgSMkHedwND1olF8vlKsJUGK3BcdtM8w4Xq7BpSBwsloE=
|
||||
# 客户端id
|
||||
VITE_GLOB_APP_CLIENT_ID=e5cd7e4891bf95d1d19206ce24a7b32e
|
||||
|
||||
VITE_GLOB_DEMO_MODE=false
|
||||
35
Yi.Vben5.Vue3/apps/web-antd/.env.production
Normal file
35
Yi.Vben5.Vue3/apps/web-antd/.env.production
Normal file
@@ -0,0 +1,35 @@
|
||||
VITE_BASE=/
|
||||
|
||||
# 是否开启压缩,可以设置为 none, brotli, gzip
|
||||
VITE_COMPRESS=gzip
|
||||
|
||||
# 是否开启 PWA
|
||||
VITE_PWA=false
|
||||
|
||||
# vue-router 的模式
|
||||
VITE_ROUTER_HISTORY=history
|
||||
|
||||
# 是否注入全局loading
|
||||
VITE_INJECT_APP_LOADING=true
|
||||
|
||||
# 打包后是否生成dist.zip
|
||||
VITE_ARCHIVER=true
|
||||
|
||||
# 后端接口地址
|
||||
# VITE_GLOB_API_URL=/prod-api
|
||||
VITE_GLOB_API_URL=https://yiapi.wjys.top/api
|
||||
|
||||
# 全局加密开关(即开启了加解密功能才会生效 不是全部接口加密 需要和后端对应)
|
||||
VITE_GLOB_ENABLE_ENCRYPT=false
|
||||
# RSA公钥 请求加密使用 注意这两个是两对RSA公私钥 请求加密-后端解密是一对 响应解密-后端加密是一对
|
||||
VITE_GLOB_RSA_PUBLIC_KEY=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ==
|
||||
# RSA私钥 响应解密使用 注意这两个是两对RSA公私钥 请求加密-后端解密是一对 响应解密-后端加密是一对
|
||||
VITE_GLOB_RSA_PRIVATE_KEY=MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAmc3CuPiGL/LcIIm7zryCEIbl1SPzBkr75E2VMtxegyZ1lYRD+7TZGAPkvIsBcaMs6Nsy0L78n2qh+lIZMpLH8wIDAQABAkEAk82Mhz0tlv6IVCyIcw/s3f0E+WLmtPFyR9/WtV3Y5aaejUkU60JpX4m5xNR2VaqOLTZAYjW8Wy0aXr3zYIhhQQIhAMfqR9oFdYw1J9SsNc+CrhugAvKTi0+BF6VoL6psWhvbAiEAxPPNTmrkmrXwdm/pQQu3UOQmc2vCZ5tiKpW10CgJi8kCIFGkL6utxw93Ncj4exE/gPLvKcT+1Emnoox+O9kRXss5AiAMtYLJDaLEzPrAWcZeeSgSIzbL+ecokmFKSDDcRske6QIgSMkHedwND1olF8vlKsJUGK3BcdtM8w4Xq7BpSBwsloE=
|
||||
# 客户端id
|
||||
VITE_GLOB_APP_CLIENT_ID=e5cd7e4891bf95d1d19206ce24a7b32e
|
||||
|
||||
# 开启SSE
|
||||
VITE_GLOB_SSE_ENABLE=false
|
||||
|
||||
VITE_GLOB_DEMO_MODE=true
|
||||
|
||||
35
Yi.Vben5.Vue3/apps/web-antd/.env.test
Normal file
35
Yi.Vben5.Vue3/apps/web-antd/.env.test
Normal file
@@ -0,0 +1,35 @@
|
||||
# 该文件是为了给一个增加环境变量打包的例子
|
||||
# 对应在根目录package.json -> build:antd:test 命令
|
||||
|
||||
VITE_BASE=/
|
||||
|
||||
# 是否开启压缩,可以设置为 none, brotli, gzip
|
||||
VITE_COMPRESS=gzip
|
||||
|
||||
# 是否开启 PWA
|
||||
VITE_PWA=false
|
||||
|
||||
# vue-router 的模式
|
||||
VITE_ROUTER_HISTORY=history
|
||||
|
||||
# 是否注入全局loading
|
||||
VITE_INJECT_APP_LOADING=true
|
||||
|
||||
# 打包后是否生成dist.zip
|
||||
VITE_ARCHIVER=true
|
||||
|
||||
# 后端接口地址
|
||||
VITE_GLOB_API_URL=/test-api
|
||||
|
||||
# 全局加密开关(即开启了加解密功能才会生效 不是全部接口加密 需要和后端对应)
|
||||
VITE_GLOB_ENABLE_ENCRYPT=true
|
||||
# RSA公钥 请求加密使用 注意这两个是两对RSA公私钥 请求加密-后端解密是一对 响应解密-后端加密是一对
|
||||
VITE_GLOB_RSA_PUBLIC_KEY=MFwwDQYJKoZIhvcNAQEBBQADSwAwSAJBAKoR8mX0rGKLqzcWmOzbfj64K8ZIgOdHnzkXSOVOZbFu/TJhZ7rFAN+eaGkl3C4buccQd/EjEsj9ir7ijT7h96MCAwEAAQ==
|
||||
# RSA私钥 响应解密使用 注意这两个是两对RSA公私钥 请求加密-后端解密是一对 响应解密-后端加密是一对
|
||||
VITE_GLOB_RSA_PRIVATE_KEY=MIIBVAIBADANBgkqhkiG9w0BAQEFAASCAT4wggE6AgEAAkEAmc3CuPiGL/LcIIm7zryCEIbl1SPzBkr75E2VMtxegyZ1lYRD+7TZGAPkvIsBcaMs6Nsy0L78n2qh+lIZMpLH8wIDAQABAkEAk82Mhz0tlv6IVCyIcw/s3f0E+WLmtPFyR9/WtV3Y5aaejUkU60JpX4m5xNR2VaqOLTZAYjW8Wy0aXr3zYIhhQQIhAMfqR9oFdYw1J9SsNc+CrhugAvKTi0+BF6VoL6psWhvbAiEAxPPNTmrkmrXwdm/pQQu3UOQmc2vCZ5tiKpW10CgJi8kCIFGkL6utxw93Ncj4exE/gPLvKcT+1Emnoox+O9kRXss5AiAMtYLJDaLEzPrAWcZeeSgSIzbL+ecokmFKSDDcRske6QIgSMkHedwND1olF8vlKsJUGK3BcdtM8w4Xq7BpSBwsloE=
|
||||
# 客户端id
|
||||
VITE_GLOB_APP_CLIENT_ID=e5cd7e4891bf95d1d19206ce24a7b32e
|
||||
|
||||
# 开启SSE
|
||||
VITE_GLOB_SSE_ENABLE=true
|
||||
|
||||
22
Yi.Vben5.Vue3/apps/web-antd/index.html
Normal file
22
Yi.Vben5.Vue3/apps/web-antd/index.html
Normal file
@@ -0,0 +1,22 @@
|
||||
<!doctype html>
|
||||
<html lang="zh">
|
||||
<head>
|
||||
<meta charset="UTF-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1" />
|
||||
<meta name="renderer" content="webkit" />
|
||||
<meta name="description" content="A Modern Back-end Management System" />
|
||||
<meta name="keywords" content="Vben Admin Vue3 Vite" />
|
||||
<meta name="author" content="Vben" />
|
||||
<meta
|
||||
name="viewport"
|
||||
content="width=device-width,initial-scale=1.0,minimum-scale=1.0,maximum-scale=1.0,user-scalable=0"
|
||||
/>
|
||||
<!-- 由 vite 注入 VITE_APP_TITLE 变量,在 .env 文件内配置 -->
|
||||
<title><%= VITE_APP_TITLE %></title>
|
||||
<link rel="icon" href="/favicon.ico" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="app"></div>
|
||||
<script type="module" src="/src/main.ts"></script>
|
||||
</body>
|
||||
</html>
|
||||
64
Yi.Vben5.Vue3/apps/web-antd/package.json
Normal file
64
Yi.Vben5.Vue3/apps/web-antd/package.json
Normal file
@@ -0,0 +1,64 @@
|
||||
{
|
||||
"name": "@vben/web-antd",
|
||||
"version": "1.4.1",
|
||||
"homepage": "https://vben.pro",
|
||||
"bugs": "https://github.com/vbenjs/vue-vben-admin/issues",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/vbenjs/vue-vben-admin.git",
|
||||
"directory": "apps/web-antd"
|
||||
},
|
||||
"license": "MIT",
|
||||
"author": {
|
||||
"name": "vben",
|
||||
"email": "ann.vben@gmail.com",
|
||||
"url": "https://github.com/anncwb"
|
||||
},
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"build": "pnpm vite build",
|
||||
"build:analyze": "pnpm vite build --mode analyze",
|
||||
"dev": "pnpm vite --mode development",
|
||||
"preview": "vite preview",
|
||||
"typecheck": "vue-tsc --noEmit --skipLibCheck"
|
||||
},
|
||||
"imports": {
|
||||
"#/*": "./src/*"
|
||||
},
|
||||
"dependencies": {
|
||||
"@ant-design/icons-vue": "^7.0.1",
|
||||
"@tinymce/tinymce-vue": "^6.0.1",
|
||||
"@vben/access": "workspace:*",
|
||||
"@vben/common-ui": "workspace:*",
|
||||
"@vben/constants": "workspace:*",
|
||||
"@vben/hooks": "workspace:*",
|
||||
"@vben/icons": "workspace:*",
|
||||
"@vben/layouts": "workspace:*",
|
||||
"@vben/locales": "workspace:*",
|
||||
"@vben/plugins": "workspace:*",
|
||||
"@vben/preferences": "workspace:*",
|
||||
"@vben/request": "workspace:*",
|
||||
"@vben/stores": "workspace:*",
|
||||
"@vben/styles": "workspace:*",
|
||||
"@vben/types": "workspace:*",
|
||||
"@vben/utils": "workspace:*",
|
||||
"@vueuse/core": "catalog:",
|
||||
"ant-design-vue": "catalog:",
|
||||
"cropperjs": "^1.6.2",
|
||||
"crypto-js": "^4.2.0",
|
||||
"dayjs": "catalog:",
|
||||
"echarts": "^5.5.1",
|
||||
"jsencrypt": "^3.3.2",
|
||||
"lodash-es": "^4.17.21",
|
||||
"pinia": "catalog:",
|
||||
"tinymce": "^7.3.0",
|
||||
"unplugin-vue-components": "^0.27.3",
|
||||
"vue": "catalog:",
|
||||
"vue-router": "catalog:",
|
||||
"vue3-colorpicker": "^2.3.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/crypto-js": "^4.2.2",
|
||||
"@types/lodash-es": "^4.17.12"
|
||||
}
|
||||
}
|
||||
1
Yi.Vben5.Vue3/apps/web-antd/postcss.config.mjs
Normal file
1
Yi.Vben5.Vue3/apps/web-antd/postcss.config.mjs
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from '@vben/tailwind-config/postcss';
|
||||
258
Yi.Vben5.Vue3/apps/web-antd/src/adapter/component/index.ts
Normal file
258
Yi.Vben5.Vue3/apps/web-antd/src/adapter/component/index.ts
Normal file
@@ -0,0 +1,258 @@
|
||||
/**
|
||||
* 通用组件共同的使用的基础组件,原先放在 adapter/form 内部,限制了使用范围,这里提取出来,方便其他地方使用
|
||||
* 可用于 vben-form、vben-modal、vben-drawer 等组件使用,
|
||||
*/
|
||||
|
||||
import type { Component } from 'vue';
|
||||
|
||||
import type { BaseFormComponentType } from '@vben/common-ui';
|
||||
import type { Recordable } from '@vben/types';
|
||||
|
||||
import {
|
||||
computed,
|
||||
defineAsyncComponent,
|
||||
defineComponent,
|
||||
getCurrentInstance,
|
||||
h,
|
||||
ref,
|
||||
} from 'vue';
|
||||
|
||||
import { ApiComponent, globalShareState, IconPicker } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import { notification } from 'ant-design-vue';
|
||||
|
||||
import { FileUploadOld, ImageUploadOld } from '#/components/upload-old';
|
||||
|
||||
const RichTextarea = defineAsyncComponent(() =>
|
||||
import('#/components/tinymce/index').then((res) => res.Tinymce),
|
||||
);
|
||||
|
||||
const FileUpload = defineAsyncComponent(() =>
|
||||
import('#/components/upload').then((res) => res.FileUpload),
|
||||
);
|
||||
|
||||
const ImageUpload = defineAsyncComponent(() =>
|
||||
import('#/components/upload').then((res) => res.ImageUpload),
|
||||
);
|
||||
|
||||
const AutoComplete = defineAsyncComponent(
|
||||
() => import('ant-design-vue/es/auto-complete'),
|
||||
);
|
||||
const Button = defineAsyncComponent(() => import('ant-design-vue/es/button'));
|
||||
const Cascader = defineAsyncComponent(
|
||||
() => import('ant-design-vue/es/cascader'),
|
||||
);
|
||||
const Checkbox = defineAsyncComponent(
|
||||
() => import('ant-design-vue/es/checkbox'),
|
||||
);
|
||||
const CheckboxGroup = defineAsyncComponent(() =>
|
||||
import('ant-design-vue/es/checkbox').then((res) => res.CheckboxGroup),
|
||||
);
|
||||
const DatePicker = defineAsyncComponent(
|
||||
() => import('ant-design-vue/es/date-picker'),
|
||||
);
|
||||
const Divider = defineAsyncComponent(() => import('ant-design-vue/es/divider'));
|
||||
const Input = defineAsyncComponent(() => import('ant-design-vue/es/input'));
|
||||
const InputNumber = defineAsyncComponent(
|
||||
() => import('ant-design-vue/es/input-number'),
|
||||
);
|
||||
const InputPassword = defineAsyncComponent(() =>
|
||||
import('ant-design-vue/es/input').then((res) => res.InputPassword),
|
||||
);
|
||||
const Mentions = defineAsyncComponent(
|
||||
() => import('ant-design-vue/es/mentions'),
|
||||
);
|
||||
const Radio = defineAsyncComponent(() => import('ant-design-vue/es/radio'));
|
||||
const RadioGroup = defineAsyncComponent(() =>
|
||||
import('ant-design-vue/es/radio').then((res) => res.RadioGroup),
|
||||
);
|
||||
const RangePicker = defineAsyncComponent(() =>
|
||||
import('ant-design-vue/es/date-picker').then((res) => res.RangePicker),
|
||||
);
|
||||
const Rate = defineAsyncComponent(() => import('ant-design-vue/es/rate'));
|
||||
const Select = defineAsyncComponent(() => import('ant-design-vue/es/select'));
|
||||
const Space = defineAsyncComponent(() => import('ant-design-vue/es/space'));
|
||||
const Switch = defineAsyncComponent(() => import('ant-design-vue/es/switch'));
|
||||
const Textarea = defineAsyncComponent(() =>
|
||||
import('ant-design-vue/es/input').then((res) => res.Textarea),
|
||||
);
|
||||
const TimePicker = defineAsyncComponent(
|
||||
() => import('ant-design-vue/es/time-picker'),
|
||||
);
|
||||
const TreeSelect = defineAsyncComponent(
|
||||
() => import('ant-design-vue/es/tree-select'),
|
||||
);
|
||||
const Upload = defineAsyncComponent(() => import('ant-design-vue/es/upload'));
|
||||
|
||||
const withDefaultPlaceholder = <T extends Component>(
|
||||
component: T,
|
||||
type: 'input' | 'select',
|
||||
componentProps: Recordable<any> = {},
|
||||
) => {
|
||||
return defineComponent({
|
||||
name: component.name,
|
||||
inheritAttrs: false,
|
||||
setup: (props: any, { attrs, expose, slots }) => {
|
||||
// 改为placeholder 解决在keepalive & 语言切换 & tab切换 显示不变的问题
|
||||
const computedPlaceholder = computed(
|
||||
() =>
|
||||
props?.placeholder ||
|
||||
attrs?.placeholder ||
|
||||
$t(`ui.placeholder.${type}`),
|
||||
);
|
||||
|
||||
// 透传组件暴露的方法
|
||||
const innerRef = ref();
|
||||
const publicApi: Recordable<any> = {};
|
||||
expose(publicApi);
|
||||
const instance = getCurrentInstance();
|
||||
instance?.proxy?.$nextTick(() => {
|
||||
for (const key in innerRef.value) {
|
||||
if (typeof innerRef.value[key] === 'function') {
|
||||
publicApi[key] = innerRef.value[key];
|
||||
}
|
||||
}
|
||||
});
|
||||
return () =>
|
||||
h(
|
||||
component,
|
||||
{
|
||||
...componentProps,
|
||||
placeholder: computedPlaceholder.value,
|
||||
...props,
|
||||
...attrs,
|
||||
ref: innerRef,
|
||||
},
|
||||
slots,
|
||||
);
|
||||
},
|
||||
});
|
||||
};
|
||||
|
||||
// 这里需要自行根据业务组件库进行适配,需要用到的组件都需要在这里类型说明
|
||||
export type ComponentType =
|
||||
| 'ApiSelect'
|
||||
| 'ApiTreeSelect'
|
||||
| 'AutoComplete'
|
||||
| 'Cascader'
|
||||
| 'Checkbox'
|
||||
| 'CheckboxGroup'
|
||||
| 'DatePicker'
|
||||
| 'DefaultButton'
|
||||
| 'Divider'
|
||||
| 'FileUpload'
|
||||
| 'FileUploadOld'
|
||||
| 'IconPicker'
|
||||
| 'ImageUpload'
|
||||
| 'ImageUploadOld'
|
||||
| 'Input'
|
||||
| 'InputNumber'
|
||||
| 'InputPassword'
|
||||
| 'Mentions'
|
||||
| 'PrimaryButton'
|
||||
| 'Radio'
|
||||
| 'RadioGroup'
|
||||
| 'RangePicker'
|
||||
| 'Rate'
|
||||
| 'RichTextarea'
|
||||
| 'Select'
|
||||
| 'Space'
|
||||
| 'Switch'
|
||||
| 'Textarea'
|
||||
| 'TimePicker'
|
||||
| 'TreeSelect'
|
||||
| 'Upload'
|
||||
| BaseFormComponentType;
|
||||
|
||||
async function initComponentAdapter() {
|
||||
const components: Partial<Record<ComponentType, Component>> = {
|
||||
// 如果你的组件体积比较大,可以使用异步加载
|
||||
// Button: () =>
|
||||
// import('xxx').then((res) => res.Button),
|
||||
ApiSelect: withDefaultPlaceholder(
|
||||
{
|
||||
...ApiComponent,
|
||||
name: 'ApiSelect',
|
||||
},
|
||||
'select',
|
||||
{
|
||||
component: Select,
|
||||
loadingSlot: 'suffixIcon',
|
||||
visibleEvent: 'onDropdownVisibleChange',
|
||||
modelPropName: 'value',
|
||||
},
|
||||
),
|
||||
ApiTreeSelect: withDefaultPlaceholder(
|
||||
{
|
||||
...ApiComponent,
|
||||
name: 'ApiTreeSelect',
|
||||
},
|
||||
'select',
|
||||
{
|
||||
component: TreeSelect,
|
||||
fieldNames: { label: 'label', value: 'value', children: 'children' },
|
||||
loadingSlot: 'suffixIcon',
|
||||
modelPropName: 'value',
|
||||
optionsPropName: 'treeData',
|
||||
visibleEvent: 'onVisibleChange',
|
||||
},
|
||||
),
|
||||
AutoComplete,
|
||||
Cascader: withDefaultPlaceholder(Cascader, 'select'),
|
||||
Checkbox,
|
||||
CheckboxGroup,
|
||||
DatePicker,
|
||||
// 自定义默认按钮
|
||||
DefaultButton: (props, { attrs, slots }) => {
|
||||
return h(Button, { ...props, attrs, type: 'default' }, slots);
|
||||
},
|
||||
Divider,
|
||||
IconPicker: withDefaultPlaceholder(IconPicker, 'select', {
|
||||
iconSlot: 'addonAfter',
|
||||
inputComponent: Input,
|
||||
modelValueProp: 'value',
|
||||
}),
|
||||
Input: withDefaultPlaceholder(Input, 'input'),
|
||||
InputNumber: withDefaultPlaceholder(InputNumber, 'input'),
|
||||
InputPassword: withDefaultPlaceholder(InputPassword, 'input'),
|
||||
Mentions: withDefaultPlaceholder(Mentions, 'input'),
|
||||
// 自定义主要按钮
|
||||
PrimaryButton: (props, { attrs, slots }) => {
|
||||
return h(Button, { ...props, attrs, type: 'primary' }, slots);
|
||||
},
|
||||
Radio,
|
||||
RadioGroup,
|
||||
RangePicker,
|
||||
Rate,
|
||||
Select: withDefaultPlaceholder(Select, 'select'),
|
||||
Space,
|
||||
Switch,
|
||||
Textarea: withDefaultPlaceholder(Textarea, 'input'),
|
||||
TimePicker,
|
||||
TreeSelect: withDefaultPlaceholder(TreeSelect, 'select'),
|
||||
Upload,
|
||||
ImageUpload,
|
||||
FileUpload,
|
||||
RichTextarea,
|
||||
ImageUploadOld,
|
||||
FileUploadOld,
|
||||
};
|
||||
|
||||
// 将组件注册到全局共享状态中
|
||||
globalShareState.setComponents(components);
|
||||
|
||||
// 定义全局共享状态中的消息提示
|
||||
globalShareState.defineMessage({
|
||||
// 复制成功消息提示
|
||||
copyPreferencesSuccess: (title, content) => {
|
||||
notification.success({
|
||||
description: content,
|
||||
message: title,
|
||||
placement: 'bottomRight',
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
export { initComponentAdapter };
|
||||
56
Yi.Vben5.Vue3/apps/web-antd/src/adapter/form.ts
Normal file
56
Yi.Vben5.Vue3/apps/web-antd/src/adapter/form.ts
Normal file
@@ -0,0 +1,56 @@
|
||||
import type {
|
||||
VbenFormSchema as FormSchema,
|
||||
VbenFormProps,
|
||||
} from '@vben/common-ui';
|
||||
|
||||
import type { ComponentType } from './component';
|
||||
|
||||
import { setupVbenForm, useVbenForm as useForm, z } from '@vben/common-ui';
|
||||
import { $t } from '@vben/locales';
|
||||
|
||||
import { isArray } from 'lodash-es';
|
||||
|
||||
async function initSetupVbenForm() {
|
||||
setupVbenForm<ComponentType>({
|
||||
config: {
|
||||
// ant design vue组件库默认都是 v-model:value
|
||||
baseModelPropName: 'value',
|
||||
|
||||
// 一些组件是 v-model:checked 或者 v-model:fileList
|
||||
modelPropNameMap: {
|
||||
Checkbox: 'checked',
|
||||
Radio: 'checked',
|
||||
RichTextarea: 'modelValue',
|
||||
Switch: 'checked',
|
||||
Upload: 'fileList',
|
||||
},
|
||||
},
|
||||
defineRules: {
|
||||
// 输入项目必填国际化适配
|
||||
required: (value, _params, ctx) => {
|
||||
if (value === undefined || value === null || value.length === 0) {
|
||||
return $t('ui.formRules.required', [ctx.label]);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
// 选择项目必填国际化适配
|
||||
selectRequired: (value, _params, ctx) => {
|
||||
if (
|
||||
[false, null, undefined].includes(value) ||
|
||||
(isArray(value) && value.length === 0)
|
||||
) {
|
||||
return $t('ui.formRules.selectRequired', [ctx.label]);
|
||||
}
|
||||
return true;
|
||||
},
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
const useVbenForm = useForm<ComponentType>;
|
||||
|
||||
export { initSetupVbenForm, useVbenForm, z };
|
||||
|
||||
export type VbenFormSchema = FormSchema<ComponentType>;
|
||||
export type { VbenFormProps };
|
||||
export type FormSchemaGetter = () => VbenFormSchema[];
|
||||
137
Yi.Vben5.Vue3/apps/web-antd/src/adapter/vxe-table.ts
Normal file
137
Yi.Vben5.Vue3/apps/web-antd/src/adapter/vxe-table.ts
Normal file
@@ -0,0 +1,137 @@
|
||||
import type { VxeGridPropTypes } from '@vben/plugins/vxe-table';
|
||||
|
||||
import { h } from 'vue';
|
||||
|
||||
import { setupVbenVxeTable, useVbenVxeGrid } from '@vben/plugins/vxe-table';
|
||||
|
||||
import { Button, Image } from 'ant-design-vue';
|
||||
|
||||
import { useVbenForm } from './form';
|
||||
|
||||
setupVbenVxeTable({
|
||||
configVxeTable: (vxeUI) => {
|
||||
vxeUI.setConfig({
|
||||
grid: {
|
||||
align: 'center',
|
||||
border: false,
|
||||
minHeight: 180,
|
||||
formConfig: {
|
||||
// 全局禁用vxe-table的表单配置,使用formOptions
|
||||
enabled: false,
|
||||
},
|
||||
proxyConfig: {
|
||||
autoLoad: true,
|
||||
response: {
|
||||
result: 'items',
|
||||
total: 'totalCount',
|
||||
list: 'items',
|
||||
},
|
||||
showActiveMsg: true,
|
||||
showResponseMsg: false,
|
||||
},
|
||||
// 溢出展示形式
|
||||
showOverflow: true,
|
||||
pagerConfig: {
|
||||
// 默认条数
|
||||
pageSize: 10,
|
||||
// 分页可选条数
|
||||
pageSizes: [10, 20, 30, 40, 50],
|
||||
},
|
||||
rowConfig: {
|
||||
// 鼠标移入行显示 hover 样式
|
||||
isHover: true,
|
||||
// 点击行高亮
|
||||
isCurrent: false,
|
||||
},
|
||||
columnConfig: {
|
||||
// 可拖拽列宽
|
||||
resizable: true,
|
||||
},
|
||||
// 右上角工具栏
|
||||
toolbarConfig: {
|
||||
// 自定义列
|
||||
custom: true,
|
||||
customOptions: {
|
||||
icon: 'vxe-icon-setting',
|
||||
},
|
||||
// 最大化
|
||||
zoom: true,
|
||||
// 刷新
|
||||
refresh: true,
|
||||
refreshOptions: {
|
||||
// 默认为reload 修改为在当前页刷新
|
||||
code: 'query',
|
||||
},
|
||||
},
|
||||
// 圆角按钮
|
||||
round: true,
|
||||
// 表格尺寸
|
||||
size: 'medium',
|
||||
customConfig: {
|
||||
// 表格右上角自定义列配置 是否保存到localStorage
|
||||
// 必须存在id参数才能使用
|
||||
storage: false,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
// 表格配置项可以用 cellRender: { name: 'CellImage' },
|
||||
vxeUI.renderer.add('CellImage', {
|
||||
renderTableDefault(_renderOpts, params) {
|
||||
const { column, row } = params;
|
||||
return h(Image, { src: row[column.field] });
|
||||
},
|
||||
});
|
||||
|
||||
// 表格配置项可以用 cellRender: { name: 'CellLink' },
|
||||
vxeUI.renderer.add('CellLink', {
|
||||
renderTableDefault(renderOpts) {
|
||||
const { props } = renderOpts;
|
||||
return h(
|
||||
Button,
|
||||
{ size: 'small', type: 'link' },
|
||||
{ default: () => props?.text },
|
||||
);
|
||||
},
|
||||
});
|
||||
|
||||
// 这里可以自行扩展 vxe-table 的全局配置,比如自定义格式化
|
||||
// vxeUI.formats.add
|
||||
},
|
||||
useVbenForm,
|
||||
});
|
||||
|
||||
export { useVbenVxeGrid };
|
||||
|
||||
export type * from '@vben/plugins/vxe-table';
|
||||
|
||||
/**
|
||||
* 判断vxe-table的复选框是否选中
|
||||
* @param tableApi api
|
||||
* @returns boolean
|
||||
*/
|
||||
export function vxeCheckboxChecked(
|
||||
tableApi: ReturnType<typeof useVbenVxeGrid>[1],
|
||||
) {
|
||||
return tableApi?.grid?.getCheckboxRecords?.()?.length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通用的 排序参数添加到请求参数中
|
||||
* @param params 请求参数
|
||||
* @param sortList vxe-table的排序参数
|
||||
*/
|
||||
export function addSortParams(
|
||||
params: Record<string, any>,
|
||||
sortList: VxeGridPropTypes.ProxyAjaxQuerySortCheckedParams[],
|
||||
) {
|
||||
// 这里是排序取消 length为0 就不添加参数了
|
||||
if (sortList.length === 0) {
|
||||
return;
|
||||
}
|
||||
// 支持单/多字段排序
|
||||
const orderByColumn = sortList.map((item) => item.field).join(',');
|
||||
const isAsc = sortList.map((item) => item.order).join(',');
|
||||
params.orderByColumn = orderByColumn;
|
||||
params.isAsc = isAsc;
|
||||
}
|
||||
42
Yi.Vben5.Vue3/apps/web-antd/src/api/common.d.ts
vendored
Normal file
42
Yi.Vben5.Vue3/apps/web-antd/src/api/common.d.ts
vendored
Normal file
@@ -0,0 +1,42 @@
|
||||
export type ID = number | string;
|
||||
export type IDS = (number | string)[];
|
||||
|
||||
export interface BaseEntity {
|
||||
createBy?: string;
|
||||
createDept?: string;
|
||||
createTime?: string;
|
||||
updateBy?: string;
|
||||
updateTime?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页信息
|
||||
* @param rows 结果集
|
||||
* @param total 总数
|
||||
*/
|
||||
export interface PageResult<T = any> {
|
||||
items: T[];
|
||||
totalCount: number;
|
||||
}
|
||||
|
||||
/**
|
||||
* 分页查询参数
|
||||
*
|
||||
* 排序支持的用法如下:
|
||||
* {isAsc:"asc",orderByColumn:"id"} order by id asc
|
||||
* {isAsc:"asc",orderByColumn:"id,createTime"} order by id asc,create_time asc
|
||||
* {isAsc:"desc",orderByColumn:"id,createTime"} order by id desc,create_time desc
|
||||
* {isAsc:"asc,desc",orderByColumn:"id,createTime"} order by id asc,create_time desc
|
||||
*
|
||||
* @param SkipCount 当前页
|
||||
* @param MaxResultCount 每页大小
|
||||
* @param orderByColumn 排序字段
|
||||
* @param isAsc 是否升序
|
||||
*/
|
||||
export interface PageQuery {
|
||||
isAsc?: string;
|
||||
orderByColumn?: string;
|
||||
SkipCount?: number;
|
||||
MaxResultCount?: number;
|
||||
[key: string]: any;
|
||||
}
|
||||
204
Yi.Vben5.Vue3/apps/web-antd/src/api/core/auth.ts
Normal file
204
Yi.Vben5.Vue3/apps/web-antd/src/api/core/auth.ts
Normal file
@@ -0,0 +1,204 @@
|
||||
import type { GrantType } from '@vben/common-ui';
|
||||
import type { HttpResponse } from '@vben/request';
|
||||
|
||||
import { h } from 'vue';
|
||||
|
||||
import { useAppConfig } from '@vben/hooks';
|
||||
|
||||
import { Modal } from 'ant-design-vue';
|
||||
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
const { clientId, sseEnable } = useAppConfig(
|
||||
import.meta.env,
|
||||
import.meta.env.PROD,
|
||||
);
|
||||
|
||||
export namespace AuthApi {
|
||||
/**
|
||||
* @description: 所有登录类型都需要用到的
|
||||
* @param clientId 客户端ID 这里为必填项 但是在loginApi内部处理了 所以为可选
|
||||
* @param grantType 授权/登录类型
|
||||
* @param tenantId 租户id
|
||||
*/
|
||||
export interface BaseLoginParams {
|
||||
clientId?: string;
|
||||
grantType: GrantType;
|
||||
tenantId: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: oauth登录需要用到的参数
|
||||
* @param socialCode 第三方参数
|
||||
* @param socialState 第三方参数
|
||||
* @param source 与后端的 justauth.type.xxx的回调地址的source对应
|
||||
*/
|
||||
export interface OAuthLoginParams extends BaseLoginParams {
|
||||
socialCode: string;
|
||||
socialState: string;
|
||||
source: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 验证码登录需要用到的参数
|
||||
* @param code 验证码 可选(未开启验证码情况)
|
||||
* @param uuid 验证码ID 可选(未开启验证码情况)
|
||||
* @param username 用户名
|
||||
* @param password 密码
|
||||
*/
|
||||
export interface SimpleLoginParams extends BaseLoginParams {
|
||||
code?: string;
|
||||
uuid?: string;
|
||||
username: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export type LoginParams = OAuthLoginParams | SimpleLoginParams;
|
||||
|
||||
// /** 登录接口参数 */
|
||||
// export interface LoginParams {
|
||||
// code?: string;
|
||||
// grantType: string;
|
||||
// password: string;
|
||||
// tenantId: string;
|
||||
// username: string;
|
||||
// uuid?: string;
|
||||
// }
|
||||
|
||||
/** 登录接口返回值 */
|
||||
export interface LoginResult {
|
||||
access_token: string;
|
||||
client_id: string;
|
||||
expire_in: number;
|
||||
}
|
||||
|
||||
export interface RefreshTokenResult {
|
||||
data: string;
|
||||
status: number;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录
|
||||
*/
|
||||
export async function loginApi(data: AuthApi.LoginParams) {
|
||||
return requestClient.post<AuthApi.LoginResult>(
|
||||
'/account/login',
|
||||
{ ...data, clientId },
|
||||
{
|
||||
encrypt: true,
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户登出
|
||||
* @returns void
|
||||
*/
|
||||
export async function doLogout() {
|
||||
const resp = await requestClient.post<HttpResponse<void>>(
|
||||
'account/logout',
|
||||
null,
|
||||
{
|
||||
isTransformResponse: false,
|
||||
},
|
||||
);
|
||||
// 无奈之举 对错误用法的提示
|
||||
if (resp.code === 401 && import.meta.env.DEV) {
|
||||
Modal.destroyAll();
|
||||
Modal.warn({
|
||||
title: '后端配置出现错误',
|
||||
centered: true,
|
||||
content: h('div', { class: 'flex flex-col gap-2' }, [
|
||||
`检测到你的logout接口返回了401, 导致前端一直进入循环逻辑???`,
|
||||
...Array.from({ length: 3 }, () =>
|
||||
h(
|
||||
'span',
|
||||
{ class: 'font-bold text-red-500 text-[18px]' },
|
||||
'去检查你的后端配置!别盯着前端找问题了!这不是前端问题!',
|
||||
),
|
||||
),
|
||||
]),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭sse连接
|
||||
* @returns void
|
||||
*/
|
||||
export function seeConnectionClose() {
|
||||
/**
|
||||
* 未开启sse 不需要处理
|
||||
*/
|
||||
if (!sseEnable) {
|
||||
return;
|
||||
}
|
||||
return requestClient.get<void>('/resource/sse/close');
|
||||
}
|
||||
|
||||
/**
|
||||
* @param companyName 租户/公司名称
|
||||
* @param domain 绑定域名(不带http(s)://) 可选
|
||||
* @param tenantId 租户id
|
||||
*/
|
||||
export interface TenantOption {
|
||||
companyName: string;
|
||||
domain?: string;
|
||||
tenantId: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param tenantEnabled 是否启用租户
|
||||
* @param voList 租户列表
|
||||
*/
|
||||
export interface TenantResp {
|
||||
tenantEnabled: boolean;
|
||||
voList: TenantOption[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取租户列表 下拉框使用
|
||||
*/
|
||||
export function tenantList() {
|
||||
return requestClient.get<TenantResp>('/tenant/select');
|
||||
}
|
||||
|
||||
/**
|
||||
* vben的 先不删除
|
||||
* @returns string[]
|
||||
*/
|
||||
export async function getAccessCodesApi() {
|
||||
return requestClient.get<string[]>('/auth/codes');
|
||||
}
|
||||
|
||||
/**
|
||||
* 绑定第三方账号
|
||||
* @param source 绑定的来源
|
||||
* @returns 跳转url
|
||||
*/
|
||||
export function authBinding(source: string, tenantId: string) {
|
||||
return requestClient.get<string>(`/auth/binding/${source}`, {
|
||||
params: {
|
||||
domain: window.location.host,
|
||||
tenantId,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 取消绑定
|
||||
* @param id id
|
||||
*/
|
||||
export function authUnbinding(id: string) {
|
||||
return requestClient.deleteWithMsg<void>(`/auth/unlock/${id}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* oauth授权回调
|
||||
* @param data oauth授权
|
||||
* @returns void
|
||||
*/
|
||||
export function authCallback(data: AuthApi.OAuthLoginParams) {
|
||||
return requestClient.post<void>('/auth/social/callback', data);
|
||||
}
|
||||
42
Yi.Vben5.Vue3/apps/web-antd/src/api/core/captcha.ts
Normal file
42
Yi.Vben5.Vue3/apps/web-antd/src/api/core/captcha.ts
Normal file
@@ -0,0 +1,42 @@
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
/**
|
||||
* 发送短信验证码
|
||||
* @param phonenumber 手机号
|
||||
* @returns void
|
||||
*/
|
||||
export function sendSmsCode(phonenumber: string) {
|
||||
return requestClient.get<void>('/resource/sms/code', {
|
||||
params: { phonenumber },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 发送邮件验证码
|
||||
* @param email 邮箱
|
||||
* @returns void
|
||||
*/
|
||||
export function sendEmailCode(email: string) {
|
||||
return requestClient.get<void>('/resource/email/code', {
|
||||
params: { email },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* @param img 图片验证码 需要和base64拼接
|
||||
* @param isEnableCaptcha 是否开启
|
||||
* @param uuid 验证码ID
|
||||
*/
|
||||
export interface CaptchaResponse {
|
||||
isEnableCaptcha: boolean;
|
||||
img: string;
|
||||
uuid: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 图片验证码
|
||||
* @returns resp
|
||||
*/
|
||||
export function captchaImage() {
|
||||
return requestClient.get<CaptchaResponse>('/account/captcha-image');
|
||||
}
|
||||
4
Yi.Vben5.Vue3/apps/web-antd/src/api/core/index.ts
Normal file
4
Yi.Vben5.Vue3/apps/web-antd/src/api/core/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export * from './auth';
|
||||
export * from './menu';
|
||||
export * from './upload';
|
||||
export * from './user';
|
||||
45
Yi.Vben5.Vue3/apps/web-antd/src/api/core/menu.ts
Normal file
45
Yi.Vben5.Vue3/apps/web-antd/src/api/core/menu.ts
Normal file
@@ -0,0 +1,45 @@
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
/**
|
||||
* @description: 菜单meta
|
||||
* @param title 菜单名
|
||||
* @param icon 菜单图标
|
||||
* @param noCache 是否不缓存
|
||||
* @param link 外链链接
|
||||
*/
|
||||
export interface MenuMeta {
|
||||
icon: string;
|
||||
link?: string;
|
||||
noCache: boolean;
|
||||
title: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description: 菜单
|
||||
* @param name 菜单名
|
||||
* @param path 菜单路径
|
||||
* @param hidden 是否隐藏
|
||||
* @param component 组件名称 Layout
|
||||
* @param alwaysShow 总是显示
|
||||
* @param query 路由参数(json形式)
|
||||
* @param meta 路由信息
|
||||
* @param children 子路由信息
|
||||
*/
|
||||
export interface Menu {
|
||||
alwaysShow?: boolean;
|
||||
children: Menu[];
|
||||
component: string;
|
||||
hidden: boolean;
|
||||
meta: MenuMeta;
|
||||
name: string;
|
||||
path: string;
|
||||
query?: string;
|
||||
redirect?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户所有菜单
|
||||
*/
|
||||
export async function getAllMenusApi() {
|
||||
return requestClient.get<Menu[]>('/account/Vue3Router/vben5');
|
||||
}
|
||||
47
Yi.Vben5.Vue3/apps/web-antd/src/api/core/upload.ts
Normal file
47
Yi.Vben5.Vue3/apps/web-antd/src/api/core/upload.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import type { AxiosRequestConfig } from '@vben/request';
|
||||
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
/**
|
||||
* Axios上传进度事件
|
||||
*/
|
||||
export type AxiosProgressEvent = AxiosRequestConfig['onUploadProgress'];
|
||||
|
||||
/**
|
||||
* 默认上传结果
|
||||
*/
|
||||
export interface UploadResult {
|
||||
url: string;
|
||||
fileName: string;
|
||||
ossId: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* 通过单文件上传接口
|
||||
* @param file 上传的文件
|
||||
* @param options 一些配置项
|
||||
* @param options.onUploadProgress 上传进度事件
|
||||
* @param options.signal 上传取消信号
|
||||
* @param options.otherData 其他请求参数 后端拓展可能会用到
|
||||
* @returns 上传结果
|
||||
*/
|
||||
export function uploadApi(
|
||||
file: Blob | File,
|
||||
options?: {
|
||||
onUploadProgress?: AxiosProgressEvent;
|
||||
otherData?: Record<string, any>;
|
||||
signal?: AbortSignal;
|
||||
},
|
||||
) {
|
||||
const { onUploadProgress, signal, otherData = {} } = options ?? {};
|
||||
return requestClient.upload<UploadResult>(
|
||||
'/resource/oss/upload',
|
||||
{ file, ...otherData },
|
||||
{ onUploadProgress, signal, timeout: 60_000 },
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 上传api type
|
||||
*/
|
||||
export type UploadApi = typeof uploadApi;
|
||||
47
Yi.Vben5.Vue3/apps/web-antd/src/api/core/user.ts
Normal file
47
Yi.Vben5.Vue3/apps/web-antd/src/api/core/user.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
export interface Role {
|
||||
dataScope: string;
|
||||
flag: boolean;
|
||||
roleId: number;
|
||||
roleKey: string;
|
||||
roleName: string;
|
||||
roleSort: number;
|
||||
status: string;
|
||||
superAdmin: boolean;
|
||||
}
|
||||
|
||||
export interface User {
|
||||
avatar: string;
|
||||
createTime: string;
|
||||
deptId: number;
|
||||
deptName: string;
|
||||
email: string;
|
||||
loginDate: string;
|
||||
loginIp: string;
|
||||
nickName: string;
|
||||
phonenumber: string;
|
||||
remark: string;
|
||||
roles: Role[];
|
||||
sex: string;
|
||||
status: string;
|
||||
tenantId: string;
|
||||
userId: number;
|
||||
userName: string;
|
||||
userType: string;
|
||||
}
|
||||
|
||||
export interface UserInfoResp {
|
||||
permissionCodes: string[];
|
||||
roles: string[];
|
||||
roleCodes: string[];
|
||||
user: User;
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取用户信息
|
||||
* 存在返回null的情况(401) 不会抛出异常 需要手动抛异常
|
||||
*/
|
||||
export async function getUserInfoApi() {
|
||||
return requestClient.get<null | UserInfoResp>('account');
|
||||
}
|
||||
28
Yi.Vben5.Vue3/apps/web-antd/src/api/helper.ts
Normal file
28
Yi.Vben5.Vue3/apps/web-antd/src/api/helper.ts
Normal file
@@ -0,0 +1,28 @@
|
||||
import { requestClient } from './request';
|
||||
|
||||
/**
|
||||
* @description: contentType
|
||||
*/
|
||||
export const ContentTypeEnum = {
|
||||
// form-data upload
|
||||
FORM_DATA: 'multipart/form-data;charset=UTF-8',
|
||||
// form-data qs
|
||||
FORM_URLENCODED: 'application/x-www-form-urlencoded;charset=UTF-8',
|
||||
// json
|
||||
JSON: 'application/json;charset=UTF-8',
|
||||
} as const;
|
||||
|
||||
/**
|
||||
* 通用下载接口 封装一层
|
||||
* @param url 请求地址
|
||||
* @param data 请求参数
|
||||
* @returns blob二进制
|
||||
*/
|
||||
export function commonExport(url: string, data: Record<string, any>) {
|
||||
return requestClient.post<Blob>(url, data, {
|
||||
data,
|
||||
headers: { 'Content-Type': ContentTypeEnum.FORM_URLENCODED },
|
||||
isTransformResponse: false,
|
||||
responseType: 'blob',
|
||||
});
|
||||
}
|
||||
1
Yi.Vben5.Vue3/apps/web-antd/src/api/index.ts
Normal file
1
Yi.Vben5.Vue3/apps/web-antd/src/api/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export * from './core';
|
||||
90
Yi.Vben5.Vue3/apps/web-antd/src/api/monitor/cache/index.ts
vendored
Normal file
90
Yi.Vben5.Vue3/apps/web-antd/src/api/monitor/cache/index.ts
vendored
Normal file
@@ -0,0 +1,90 @@
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
export interface CommandStats {
|
||||
name: string;
|
||||
value: string;
|
||||
}
|
||||
|
||||
export interface RedisInfo {
|
||||
[key: string]: string;
|
||||
}
|
||||
|
||||
export interface CacheInfo {
|
||||
commandStats: CommandStats[];
|
||||
dbSize: number;
|
||||
info: RedisInfo;
|
||||
}
|
||||
|
||||
export interface CacheName {
|
||||
cacheName: string;
|
||||
remark: string;
|
||||
}
|
||||
|
||||
export interface CacheValue {
|
||||
cacheName: string;
|
||||
cacheKey: string;
|
||||
cacheValue: string;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @returns redis信息
|
||||
*/
|
||||
export function redisCacheInfo() {
|
||||
return requestClient.get<CacheInfo>('/monitor/cache');
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询缓存名称列表
|
||||
* @returns 缓存名称列表
|
||||
*/
|
||||
export function listCacheName() {
|
||||
return requestClient.get<CacheName[]>('/monitor-cache/name');
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询缓存键名列表
|
||||
* @param cacheName 缓存名称
|
||||
* @returns 缓存键名列表
|
||||
*/
|
||||
export function listCacheKey(cacheName: string) {
|
||||
return requestClient.get<string[]>(`/monitor-cache/key/${cacheName}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询缓存内容
|
||||
* @param cacheName 缓存名称
|
||||
* @param cacheKey 缓存键名
|
||||
* @returns 缓存内容
|
||||
*/
|
||||
export function getCacheValue(cacheName: string, cacheKey: string) {
|
||||
return requestClient.get<CacheValue>(
|
||||
`/monitor-cache/value/${cacheName}/${cacheKey}`,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理指定名称缓存
|
||||
* @param cacheName 缓存名称
|
||||
*/
|
||||
export function clearCacheName(cacheName: string) {
|
||||
return requestClient.deleteWithMsg<void>(`/monitor-cache/key/${cacheName}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理指定键名缓存
|
||||
* @param cacheName 缓存名称
|
||||
* @param cacheKey 缓存键名
|
||||
*/
|
||||
export function clearCacheKey(cacheName: string, cacheKey: string) {
|
||||
return requestClient.deleteWithMsg<void>(
|
||||
`/monitor-cache/value/${cacheName}/${cacheKey}`,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理全部缓存
|
||||
*/
|
||||
export function clearCacheAll() {
|
||||
return requestClient.deleteWithMsg<void>('/monitor-cache/clear');
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
import type { LoginLog } from './model';
|
||||
|
||||
import type { IDS, PageQuery, PageResult } from '#/api/common';
|
||||
|
||||
import { commonExport } from '#/api/helper';
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
enum Api {
|
||||
loginInfoClean = '/login-log/clean',
|
||||
loginInfoExport = '/login-log/export',
|
||||
root = '/login-log',
|
||||
userUnlock = '/login-log/unlock',
|
||||
}
|
||||
|
||||
/**
|
||||
* 登录日志列表
|
||||
* @param params 查询参数
|
||||
* @returns list[]
|
||||
*/
|
||||
export function loginInfoList(params?: PageQuery) {
|
||||
return requestClient.get<PageResult<LoginLog>>(Api.root, { params });
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出登录日志
|
||||
* @param data 表单参数
|
||||
* @returns excel
|
||||
*/
|
||||
export function loginInfoExport(data: any) {
|
||||
return commonExport(Api.loginInfoExport, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 移除登录日志
|
||||
* @param infoIds 登录日志id数组
|
||||
* @returns void
|
||||
*/
|
||||
export function loginInfoRemove(infoIds: IDS) {
|
||||
return requestClient.deleteWithMsg<void>(Api.root, {
|
||||
params: { ids: infoIds.join(',') },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 账号解锁
|
||||
* @param username 用户名(账号)
|
||||
* @returns void
|
||||
*/
|
||||
export function userUnlock(username: string) {
|
||||
return requestClient.get<void>(`${Api.userUnlock}/${username}`, {
|
||||
successMessageMode: 'message',
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空全部登录日志
|
||||
* @returns void
|
||||
*/
|
||||
export function loginInfoClean() {
|
||||
return requestClient.deleteWithMsg<void>(Api.loginInfoClean);
|
||||
}
|
||||
11
Yi.Vben5.Vue3/apps/web-antd/src/api/monitor/logininfo/model.d.ts
vendored
Normal file
11
Yi.Vben5.Vue3/apps/web-antd/src/api/monitor/logininfo/model.d.ts
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
export interface LoginLog {
|
||||
id: string;
|
||||
loginUser: string;
|
||||
loginLocation: string;
|
||||
loginIp: string;
|
||||
browser: string;
|
||||
os: string;
|
||||
logMsg: string;
|
||||
creationTime: string;
|
||||
creatorId: string | null;
|
||||
}
|
||||
46
Yi.Vben5.Vue3/apps/web-antd/src/api/monitor/online/index.ts
Normal file
46
Yi.Vben5.Vue3/apps/web-antd/src/api/monitor/online/index.ts
Normal file
@@ -0,0 +1,46 @@
|
||||
import type { OnlineUser } from './model';
|
||||
|
||||
import type { IDS, PageQuery, PageResult } from '#/api/common';
|
||||
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
enum Api {
|
||||
root = '/online',
|
||||
}
|
||||
|
||||
/**
|
||||
* 当前账号的在线设备 个人中心使用
|
||||
* @returns OnlineUser[]
|
||||
*/
|
||||
export function onlineDeviceList() {
|
||||
return requestClient.get<PageResult<OnlineUser>>(Api.root);
|
||||
}
|
||||
|
||||
/**
|
||||
* 这里的分页参数无效 返回的是全部的分页
|
||||
* @param params 请求参数
|
||||
* @returns 结果
|
||||
*/
|
||||
export function onlineList(params?: PageQuery) {
|
||||
return requestClient.get<PageResult<OnlineUser>>(Api.root, { params });
|
||||
}
|
||||
|
||||
/**
|
||||
* 强制下线
|
||||
* @param tokenId 连接Id
|
||||
* @returns void
|
||||
*/
|
||||
export function forceLogout(tokenId: IDS) {
|
||||
return requestClient.deleteWithMsg<void>(Api.root, {
|
||||
params: { ids: tokenId.join(',') },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 个人中心用的 跟上面的不同是用的Post
|
||||
* @param tokenId 连接Id
|
||||
* @returns void
|
||||
*/
|
||||
export function forceLogout2(tokenId: string) {
|
||||
return requestClient.deleteWithMsg<void>(`${Api.root}/myself/${tokenId}`);
|
||||
}
|
||||
10
Yi.Vben5.Vue3/apps/web-antd/src/api/monitor/online/model.d.ts
vendored
Normal file
10
Yi.Vben5.Vue3/apps/web-antd/src/api/monitor/online/model.d.ts
vendored
Normal file
@@ -0,0 +1,10 @@
|
||||
export interface OnlineUser {
|
||||
connnectionId?: string;
|
||||
userId?: string;
|
||||
userName?: string;
|
||||
loginTime: number;
|
||||
ipaddr?: string;
|
||||
loginLocation?: string;
|
||||
os?: string;
|
||||
browser?: string;
|
||||
}
|
||||
48
Yi.Vben5.Vue3/apps/web-antd/src/api/monitor/operlog/index.ts
Normal file
48
Yi.Vben5.Vue3/apps/web-antd/src/api/monitor/operlog/index.ts
Normal file
@@ -0,0 +1,48 @@
|
||||
import type { OperationLog } from './model';
|
||||
|
||||
import type { IDS, PageQuery, PageResult } from '#/api/common';
|
||||
|
||||
import { commonExport } from '#/api/helper';
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
enum Api {
|
||||
operLogClean = '/operation-log/clean',
|
||||
operLogExport = '/operation-log/export',
|
||||
root = '/operation-log',
|
||||
}
|
||||
|
||||
/**
|
||||
* 操作日志分页
|
||||
* @param params 查询参数
|
||||
* @returns 分页结果
|
||||
*/
|
||||
export function operLogList(params?: PageQuery) {
|
||||
return requestClient.get<PageResult<OperationLog>>(Api.root, {
|
||||
params,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除操作日志
|
||||
* @param operIds id/ids
|
||||
*/
|
||||
export function operLogRemove(operIds: IDS) {
|
||||
return requestClient.deleteWithMsg<void>(Api.root, {
|
||||
params: { ids: operIds.join(',') },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 清空全部分页日志
|
||||
*/
|
||||
export function operLogClean() {
|
||||
return requestClient.deleteWithMsg<void>(Api.operLogClean);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出操作日志
|
||||
* @param data 查询参数
|
||||
*/
|
||||
export function operLogExport(data: Partial<OperationLog>) {
|
||||
return commonExport(Api.operLogExport, data);
|
||||
}
|
||||
14
Yi.Vben5.Vue3/apps/web-antd/src/api/monitor/operlog/model.d.ts
vendored
Normal file
14
Yi.Vben5.Vue3/apps/web-antd/src/api/monitor/operlog/model.d.ts
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
export interface OperationLog {
|
||||
id: string;
|
||||
title: string;
|
||||
operType: string;
|
||||
requestMethod: string;
|
||||
operUser: string;
|
||||
operIp: string;
|
||||
operLocation: string;
|
||||
method: string;
|
||||
requestParam: string;
|
||||
requestResult: string;
|
||||
creationTime: string;
|
||||
creatorId: string | null;
|
||||
}
|
||||
322
Yi.Vben5.Vue3/apps/web-antd/src/api/request.ts
Normal file
322
Yi.Vben5.Vue3/apps/web-antd/src/api/request.ts
Normal file
@@ -0,0 +1,322 @@
|
||||
/**
|
||||
* 该文件可自行根据业务逻辑进行调整
|
||||
*/
|
||||
|
||||
import type { HttpResponse } from '@vben/request';
|
||||
|
||||
import { useAppConfig } from '@vben/hooks';
|
||||
import { $t } from '@vben/locales';
|
||||
import { preferences } from '@vben/preferences';
|
||||
import {
|
||||
authenticateResponseInterceptor,
|
||||
errorMessageResponseInterceptor,
|
||||
RequestClient,
|
||||
stringify,
|
||||
} from '@vben/request';
|
||||
import { useAccessStore } from '@vben/stores';
|
||||
|
||||
import { message, Modal } from 'ant-design-vue';
|
||||
|
||||
import { useAuthStore } from '#/store';
|
||||
import {
|
||||
decryptBase64,
|
||||
decryptWithAes,
|
||||
encryptBase64,
|
||||
encryptWithAes,
|
||||
generateAesKey,
|
||||
} from '#/utils/encryption/crypto';
|
||||
import * as encryptUtil from '#/utils/encryption/jsencrypt';
|
||||
|
||||
const { apiURL, clientId, enableEncrypt, demoMode } = useAppConfig(
|
||||
import.meta.env,
|
||||
import.meta.env.PROD,
|
||||
);
|
||||
|
||||
/**
|
||||
* 是否已经处在登出过程中了 一个标志位
|
||||
* 主要是防止一个页面会请求多个api 都401 会导致登出执行多次
|
||||
*/
|
||||
let isLogoutProcessing = false;
|
||||
|
||||
/**
|
||||
* 定义一个401专用异常 用于可能会用到的区分场景?
|
||||
*/
|
||||
export class UnauthorizedException extends Error {}
|
||||
|
||||
/**
|
||||
* 演示模式错误,用于标识演示环境禁止修改的错误
|
||||
*/
|
||||
export class DemoModeException extends Error {
|
||||
constructor(message: string) {
|
||||
super(message);
|
||||
this.name = 'DemoModeException';
|
||||
// 添加标记,用于错误拦截器识别
|
||||
(this as any).__isDemoModeError = true;
|
||||
}
|
||||
}
|
||||
|
||||
function createRequestClient(baseURL: string) {
|
||||
const client = new RequestClient({
|
||||
// 后端地址
|
||||
baseURL,
|
||||
// 消息提示类型
|
||||
errorMessageMode: 'message',
|
||||
// 是否返回原生响应 比如:需要获取响应头时使用该属性
|
||||
isReturnNativeResponse: false,
|
||||
// 需要对返回数据进行处理
|
||||
isTransformResponse: true,
|
||||
});
|
||||
|
||||
/**
|
||||
* 重新认证逻辑
|
||||
*/
|
||||
async function doReAuthenticate() {
|
||||
console.warn('Access token or refresh token is invalid or expired. ');
|
||||
const accessStore = useAccessStore();
|
||||
const authStore = useAuthStore();
|
||||
accessStore.setAccessToken(null);
|
||||
if (
|
||||
preferences.app.loginExpiredMode === 'modal' &&
|
||||
accessStore.isAccessChecked
|
||||
) {
|
||||
accessStore.setLoginExpired(true);
|
||||
} else {
|
||||
await authStore.logout();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新token逻辑
|
||||
*/
|
||||
async function doRefreshToken() {
|
||||
// 不需要
|
||||
// 保留此方法只是为了合并方便
|
||||
return '';
|
||||
}
|
||||
|
||||
function formatToken(token: null | string) {
|
||||
return token ? `Bearer ${token}` : null;
|
||||
}
|
||||
|
||||
client.addRequestInterceptor({
|
||||
fulfilled: (config) => {
|
||||
// 演示模式:拦截所有修改操作
|
||||
if (demoMode) {
|
||||
const method = config.method?.toUpperCase() || '';
|
||||
const isModifyMethod = ['DELETE', 'PATCH', 'POST', 'PUT'].includes(
|
||||
method,
|
||||
);
|
||||
// 排除登录等认证接口,允许通过
|
||||
const isAuthPath =
|
||||
config.url?.includes('/auth/') ||
|
||||
config.url?.includes('/login') ||
|
||||
config.url?.includes('/logout');
|
||||
if (isModifyMethod && !isAuthPath) {
|
||||
// 显示错误提示
|
||||
message.error('演示环境,禁止修改');
|
||||
// 抛出演示模式错误,错误拦截器会识别并跳过处理
|
||||
throw new DemoModeException('演示环境,禁止修改');
|
||||
}
|
||||
}
|
||||
|
||||
const accessStore = useAccessStore();
|
||||
// 添加token
|
||||
config.headers.Authorization = formatToken(accessStore.accessToken);
|
||||
/**
|
||||
* locale跟后台不一致 需要转换
|
||||
*/
|
||||
const language = preferences.app.locale.replace('-', '_');
|
||||
config.headers['Accept-Language'] = language;
|
||||
config.headers['Content-Language'] = language;
|
||||
/**
|
||||
* 添加全局clientId
|
||||
* 关于header的clientId被错误绑定到实体类
|
||||
* https://gitee.com/dapppp/ruoyi-plus-vben5/issues/IC0BDS
|
||||
*/
|
||||
config.headers.ClientID = clientId;
|
||||
/**
|
||||
* 格式化get/delete参数
|
||||
* 如果包含自定义的paramsSerializer则不走此逻辑
|
||||
*/
|
||||
if (
|
||||
['DELETE', 'GET'].includes(config.method?.toUpperCase() || '') &&
|
||||
config.params &&
|
||||
!config.paramsSerializer
|
||||
) {
|
||||
/**
|
||||
* 1. 格式化参数 微服务在传递区间时间选择(后端的params Map类型参数)需要格式化key 否则接收不到
|
||||
* 2. 数组参数需要格式化 后端才能正常接收 会变成arr=1&arr=2&arr=3的格式来接收
|
||||
*/
|
||||
config.paramsSerializer = (params) =>
|
||||
stringify(params, { arrayFormat: 'repeat' });
|
||||
}
|
||||
|
||||
const { encrypt } = config;
|
||||
// 全局开启请求加密功能 && 该请求开启 && 是post/put请求
|
||||
if (
|
||||
enableEncrypt &&
|
||||
encrypt &&
|
||||
['POST', 'PUT'].includes(config.method?.toUpperCase() || '')
|
||||
) {
|
||||
const aesKey = generateAesKey();
|
||||
config.headers['encrypt-key'] = encryptUtil.encrypt(
|
||||
encryptBase64(aesKey),
|
||||
);
|
||||
|
||||
config.data =
|
||||
typeof config.data === 'object'
|
||||
? encryptWithAes(JSON.stringify(config.data), aesKey)
|
||||
: encryptWithAes(config.data, aesKey);
|
||||
}
|
||||
return config;
|
||||
},
|
||||
});
|
||||
|
||||
// 通用的错误处理, 如果没有进入上面的错误处理逻辑,就会进入这里
|
||||
// 主要处理http状态码不为200(如网络异常/离线)的情况 必须放在在下面的响应拦截器之前
|
||||
const errorInterceptor = errorMessageResponseInterceptor(
|
||||
(msg: string, error: any) => {
|
||||
// 如果是演示模式错误,已经在请求拦截器中提示过了,这里不再提示
|
||||
if (error?.__isDemoModeError || error?.name === 'DemoModeException') {
|
||||
return;
|
||||
}
|
||||
message.error(msg);
|
||||
},
|
||||
);
|
||||
client.addResponseInterceptor(errorInterceptor);
|
||||
|
||||
client.addResponseInterceptor<HttpResponse>({
|
||||
fulfilled: async (response) => {
|
||||
const encryptKey = (response.headers ?? {})['encrypt-key'];
|
||||
if (encryptKey) {
|
||||
/** RSA私钥解密 拿到解密秘钥的base64 */
|
||||
const base64Str = encryptUtil.decrypt(encryptKey);
|
||||
/** base64 解码 得到请求头的 AES 秘钥 */
|
||||
const aesSecret = decryptBase64(base64Str.toString());
|
||||
/** 使用aesKey解密 responseData */
|
||||
const decryptData = decryptWithAes(
|
||||
response.data as unknown as string,
|
||||
aesSecret,
|
||||
);
|
||||
/** 赋值 需要转为对象 */
|
||||
response.data = JSON.parse(decryptData);
|
||||
}
|
||||
|
||||
const { isReturnNativeResponse, isTransformResponse } = response.config;
|
||||
// 是否返回原生响应 比如:需要获取响应时使用该属性
|
||||
if (isReturnNativeResponse) {
|
||||
return response;
|
||||
}
|
||||
// 不进行任何处理,直接返回
|
||||
// 用于页面代码可能需要直接获取code,data,message这些信息时开启
|
||||
if (!isTransformResponse) {
|
||||
/**
|
||||
* 需要判断下载二进制的情况 正常是返回二进制 报错会返回json
|
||||
* 当type为blob且content-type为application/json时 则判断已经下载出错
|
||||
*/
|
||||
if (
|
||||
response.config.responseType === 'blob' &&
|
||||
response.headers['content-type']?.includes?.('application/json')
|
||||
) {
|
||||
// 这时候的data为blob类型
|
||||
const blob = response.data as unknown as Blob;
|
||||
// 拿到字符串转json对象
|
||||
response.data = JSON.parse(await blob.text());
|
||||
// 然后按正常逻辑执行下面的代码(判断业务状态码)
|
||||
} else {
|
||||
// 其他情况 直接返回
|
||||
return response.data;
|
||||
}
|
||||
}
|
||||
|
||||
const axiosResponseData = response.data;
|
||||
if (!axiosResponseData) {
|
||||
throw new Error($t('http.apiRequestFailed'));
|
||||
}
|
||||
|
||||
console.log('axiosResponseData', axiosResponseData);
|
||||
// 适配新的后端数据结构: { statusCode, data, succeeded, errors, extras, timestamp }
|
||||
const { statusCode, data, succeeded, errors, extras, timestamp } =
|
||||
axiosResponseData;
|
||||
|
||||
// 业务状态码为200且succeeded为true则请求成功
|
||||
const hasSuccess = statusCode === 200 && succeeded === true;
|
||||
if (hasSuccess) {
|
||||
const successMsg = $t(`http.operationSuccess`);
|
||||
|
||||
if (response.config.successMessageMode === 'modal') {
|
||||
Modal.success({
|
||||
content: successMsg,
|
||||
title: $t('http.successTip'),
|
||||
});
|
||||
} else if (response.config.successMessageMode === 'message') {
|
||||
message.success(successMsg);
|
||||
}
|
||||
|
||||
// 直接返回data字段
|
||||
return data;
|
||||
}
|
||||
|
||||
// 在此处根据自己项目的实际情况对不同的statusCode执行不同的操作
|
||||
// 如果不希望中断当前请求,请return数据,否则直接抛出异常即可
|
||||
let timeoutMsg = '';
|
||||
switch (statusCode) {
|
||||
case 401: {
|
||||
// 已经在登出过程中 不再执行
|
||||
if (isLogoutProcessing) {
|
||||
throw new UnauthorizedException(timeoutMsg);
|
||||
}
|
||||
isLogoutProcessing = true;
|
||||
const _msg = $t('http.loginTimeout');
|
||||
const userStore = useAuthStore();
|
||||
userStore.logout().finally(() => {
|
||||
message.error(_msg);
|
||||
isLogoutProcessing = false;
|
||||
});
|
||||
// 不再执行下面逻辑
|
||||
throw new UnauthorizedException(_msg);
|
||||
}
|
||||
default: {
|
||||
// 优先使用errors字段作为错误信息
|
||||
if (errors && Array.isArray(errors) && errors.length > 0) {
|
||||
timeoutMsg = errors.join(', ');
|
||||
} else if (typeof errors === 'string') {
|
||||
timeoutMsg = errors;
|
||||
} else {
|
||||
timeoutMsg = $t('http.apiRequestFailed');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// errorMessageMode='modal'的时候会显示modal错误弹窗,而不是消息提示,用于一些比较重要的错误
|
||||
// errorMessageMode='none' 一般是调用时明确表示不希望自动弹出错误提示
|
||||
if (response.config.errorMessageMode === 'modal') {
|
||||
Modal.error({
|
||||
content: timeoutMsg,
|
||||
title: $t('http.errorTip'),
|
||||
});
|
||||
} else if (response.config.errorMessageMode === 'message') {
|
||||
message.error(timeoutMsg);
|
||||
}
|
||||
|
||||
throw new Error(timeoutMsg || $t('http.apiRequestFailed'));
|
||||
},
|
||||
});
|
||||
|
||||
// token过期的处理
|
||||
client.addResponseInterceptor(
|
||||
authenticateResponseInterceptor({
|
||||
client,
|
||||
doReAuthenticate,
|
||||
doRefreshToken,
|
||||
enableRefreshToken: preferences.app.enableRefreshToken,
|
||||
formatToken,
|
||||
}),
|
||||
);
|
||||
|
||||
return client;
|
||||
}
|
||||
|
||||
export const requestClient = createRequestClient(apiURL);
|
||||
|
||||
export const baseRequestClient = new RequestClient({ baseURL: apiURL });
|
||||
12
Yi.Vben5.Vue3/apps/web-antd/src/api/service/index.ts
Normal file
12
Yi.Vben5.Vue3/apps/web-antd/src/api/service/index.ts
Normal file
@@ -0,0 +1,12 @@
|
||||
import type { ServerInfo } from './model';
|
||||
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
/**
|
||||
* 获取服务器信息
|
||||
* @returns 服务器信息
|
||||
*/
|
||||
export function getServerInfo() {
|
||||
return requestClient.get<ServerInfo>('/monitor-server/info');
|
||||
}
|
||||
|
||||
46
Yi.Vben5.Vue3/apps/web-antd/src/api/service/model.d.ts
vendored
Normal file
46
Yi.Vben5.Vue3/apps/web-antd/src/api/service/model.d.ts
vendored
Normal file
@@ -0,0 +1,46 @@
|
||||
export interface CpuInfo {
|
||||
coreTotal: number;
|
||||
logicalProcessors: number;
|
||||
cpuRate: number;
|
||||
}
|
||||
|
||||
export interface MemoryInfo {
|
||||
totalRAM: string;
|
||||
usedRam: string;
|
||||
freeRam: string;
|
||||
ramRate: number;
|
||||
}
|
||||
|
||||
export interface SystemInfo {
|
||||
computerName: string;
|
||||
osName: string;
|
||||
serverIP: string;
|
||||
osArch: string;
|
||||
}
|
||||
|
||||
export interface AppInfo {
|
||||
name: string;
|
||||
version: string;
|
||||
startTime: string;
|
||||
runTime: string;
|
||||
rootPath: string;
|
||||
webRootPath: string;
|
||||
}
|
||||
|
||||
export interface DiskInfo {
|
||||
diskName: string;
|
||||
typeName: string;
|
||||
totalSize: string;
|
||||
availableFreeSpace: string;
|
||||
used: string;
|
||||
availablePercent: number;
|
||||
}
|
||||
|
||||
export interface ServerInfo {
|
||||
cpu: CpuInfo;
|
||||
memory: MemoryInfo;
|
||||
sys: SystemInfo;
|
||||
app: AppInfo;
|
||||
disk: DiskInfo[];
|
||||
}
|
||||
|
||||
77
Yi.Vben5.Vue3/apps/web-antd/src/api/system/client/index.ts
Normal file
77
Yi.Vben5.Vue3/apps/web-antd/src/api/system/client/index.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import type { Client } from './model';
|
||||
|
||||
import type { ID, IDS, PageQuery, PageResult } from '#/api/common';
|
||||
|
||||
import { commonExport } from '#/api/helper';
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
enum Api {
|
||||
clientChangeStatus = '/client/changeStatus',
|
||||
clientExport = '/client/export',
|
||||
clientList = '/client/list',
|
||||
root = '/client',
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询客户端分页列表
|
||||
* @param params 请求参数
|
||||
* @returns 列表
|
||||
*/
|
||||
export function clientList(params?: PageQuery) {
|
||||
return requestClient.get<PageResult<Client>>(Api.clientList, { params });
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出客户端excel
|
||||
* @param data 请求参数
|
||||
*/
|
||||
export function clientExport(data: Partial<Client>) {
|
||||
return commonExport(Api.clientExport, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 客户端详情
|
||||
* @param id id
|
||||
* @returns 详情
|
||||
*/
|
||||
export function clientInfo(id: ID) {
|
||||
return requestClient.get<Client>(`${Api.root}/${id}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 客户端新增
|
||||
* @param data 参数
|
||||
*/
|
||||
export function clientAdd(data: Partial<Client>) {
|
||||
return requestClient.postWithMsg<void>(Api.root, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 客户端修改
|
||||
* @param data 参数
|
||||
*/
|
||||
export function clientUpdate(data: Partial<Client>) {
|
||||
return requestClient.putWithMsg<void>(`${Api.root}/${data.id}`, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 客户端状态修改
|
||||
* @param data 状态
|
||||
*/
|
||||
export function clientChangeStatus(data: any) {
|
||||
const requestData = {
|
||||
clientId: data.clientId,
|
||||
status: data.status,
|
||||
};
|
||||
return requestClient.putWithMsg<void>(Api.clientChangeStatus, requestData);
|
||||
}
|
||||
|
||||
/**
|
||||
* 客户端删除
|
||||
* @param ids id集合
|
||||
*/
|
||||
export function clientRemove(ids: IDS) {
|
||||
return requestClient.deleteWithMsg<void>(Api.root, {
|
||||
params: { ids: ids.join(',') },
|
||||
});
|
||||
}
|
||||
12
Yi.Vben5.Vue3/apps/web-antd/src/api/system/client/model.d.ts
vendored
Normal file
12
Yi.Vben5.Vue3/apps/web-antd/src/api/system/client/model.d.ts
vendored
Normal file
@@ -0,0 +1,12 @@
|
||||
export interface Client {
|
||||
id: number;
|
||||
clientId: string;
|
||||
clientKey: string;
|
||||
clientSecret: string;
|
||||
grantTypeList: string[];
|
||||
grantType: string;
|
||||
deviceType: string;
|
||||
activeTimeout: number;
|
||||
timeout: number;
|
||||
status: string;
|
||||
}
|
||||
78
Yi.Vben5.Vue3/apps/web-antd/src/api/system/config/index.ts
Normal file
78
Yi.Vben5.Vue3/apps/web-antd/src/api/system/config/index.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import type { SysConfig } from './model';
|
||||
|
||||
import type { ID, IDS, PageQuery, PageResult } from '#/api/common';
|
||||
|
||||
import { commonExport } from '#/api/helper';
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
enum Api {
|
||||
configExport = '/config/export',
|
||||
configInfoByKey = '/config/config-key',
|
||||
configList = '/config/list',
|
||||
configRefreshCache = '/config/refreshCache',
|
||||
root = '/config',
|
||||
}
|
||||
|
||||
/**
|
||||
* 系统参数分页列表
|
||||
* @param params 请求参数
|
||||
* @returns 列表
|
||||
*/
|
||||
export function configList(params?: PageQuery) {
|
||||
return requestClient.get<PageResult<SysConfig>>(Api.root, { params });
|
||||
}
|
||||
|
||||
export function configInfo(configId: ID) {
|
||||
return requestClient.get<SysConfig>(`${Api.root}/${configId}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出
|
||||
* @param data 参数
|
||||
*/
|
||||
export function configExport(data: Partial<SysConfig>) {
|
||||
return commonExport(Api.configExport, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新缓存
|
||||
* @returns void
|
||||
*/
|
||||
export function configRefreshCache() {
|
||||
return requestClient.deleteWithMsg<void>(Api.configRefreshCache);
|
||||
}
|
||||
|
||||
/**
|
||||
* 更新系统配置
|
||||
* @param data 参数
|
||||
*/
|
||||
export function configUpdate(data: Partial<SysConfig>) {
|
||||
return requestClient.putWithMsg<void>(`${Api.root}/${data.id}`, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增系统配置
|
||||
* @param data 参数
|
||||
*/
|
||||
export function configAdd(data: Partial<SysConfig>) {
|
||||
return requestClient.postWithMsg<void>(Api.root, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除配置
|
||||
* @param configIds ids
|
||||
*/
|
||||
export function configRemove(configIds: IDS) {
|
||||
return requestClient.deleteWithMsg<void>(Api.root, {
|
||||
params: { ids: configIds.join(',') },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取配置信息
|
||||
* @param configKey configKey
|
||||
* @returns value
|
||||
*/
|
||||
export function configInfoByKey(configKey: string) {
|
||||
return requestClient.get<string>(`${Api.configInfoByKey}/${configKey}`);
|
||||
}
|
||||
14
Yi.Vben5.Vue3/apps/web-antd/src/api/system/config/model.d.ts
vendored
Normal file
14
Yi.Vben5.Vue3/apps/web-antd/src/api/system/config/model.d.ts
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
export interface SysConfig {
|
||||
id: string;
|
||||
configName: string;
|
||||
configKey: string;
|
||||
configValue: string;
|
||||
configType: string | null;
|
||||
orderNum: number;
|
||||
remark: string | null;
|
||||
isDeleted: boolean;
|
||||
creationTime: string;
|
||||
creatorId: string | null;
|
||||
lastModifierId: string | null;
|
||||
lastModificationTime: string | null;
|
||||
}
|
||||
64
Yi.Vben5.Vue3/apps/web-antd/src/api/system/dept/index.ts
Normal file
64
Yi.Vben5.Vue3/apps/web-antd/src/api/system/dept/index.ts
Normal file
@@ -0,0 +1,64 @@
|
||||
import type { Dept } from './model';
|
||||
|
||||
import type { ID } from '#/api/common';
|
||||
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
enum Api {
|
||||
deptList = '/dept/list',
|
||||
deptNodeInfo = '/dept/list/exclude',
|
||||
root = '/dept',
|
||||
}
|
||||
|
||||
/**
|
||||
* 部门列表
|
||||
* @returns list
|
||||
*/
|
||||
export function deptList(params?: { deptName?: string; status?: string }) {
|
||||
return requestClient.get<Dept[]>(Api.deptList, { params });
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询部门列表(排除节点)
|
||||
* @param deptId 部门ID
|
||||
* @returns void
|
||||
*/
|
||||
export function deptNodeList(deptId: ID) {
|
||||
return requestClient.get<Dept[]>(`${Api.deptNodeInfo}/${deptId}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 部门详情
|
||||
* @param deptId 部门id
|
||||
* @returns 部门信息
|
||||
*/
|
||||
export function deptInfo(deptId: ID) {
|
||||
return requestClient.get<Dept>(`${Api.root}/${deptId}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 部门新增
|
||||
* @param data 参数
|
||||
*/
|
||||
export function deptAdd(data: Partial<Dept>) {
|
||||
return requestClient.postWithMsg<void>(Api.root, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 部门更新
|
||||
* @param data 参数
|
||||
*/
|
||||
export function deptUpdate(data: Partial<Dept>) {
|
||||
return requestClient.putWithMsg<void>(`${Api.root}/${data.id}`, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 注意这里只允许单删除
|
||||
* @param deptId ID
|
||||
* @returns void
|
||||
*/
|
||||
export function deptRemove(deptId: ID) {
|
||||
return requestClient.deleteWithMsg<void>(Api.root, {
|
||||
params: { ids: deptId },
|
||||
});
|
||||
}
|
||||
14
Yi.Vben5.Vue3/apps/web-antd/src/api/system/dept/model.d.ts
vendored
Normal file
14
Yi.Vben5.Vue3/apps/web-antd/src/api/system/dept/model.d.ts
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
export interface Dept {
|
||||
creationTime: string;
|
||||
creatorId?: string | null;
|
||||
state: boolean;
|
||||
deptName: string;
|
||||
deptCode?: string;
|
||||
leader?: string;
|
||||
leaderName?: string;
|
||||
parentId: string | null;
|
||||
remark?: string;
|
||||
orderNum: number;
|
||||
id: string;
|
||||
children?: Dept[];
|
||||
}
|
||||
17
Yi.Vben5.Vue3/apps/web-antd/src/api/system/dict/dict-data-model.d.ts
vendored
Normal file
17
Yi.Vben5.Vue3/apps/web-antd/src/api/system/dict/dict-data-model.d.ts
vendored
Normal file
@@ -0,0 +1,17 @@
|
||||
export interface DictData {
|
||||
id: string;
|
||||
isDeleted: boolean;
|
||||
orderNum: number;
|
||||
state: boolean;
|
||||
remark: string | null;
|
||||
listClass: string | null;
|
||||
cssClass: string | null;
|
||||
dictType: string;
|
||||
dictLabel: string | null;
|
||||
dictValue: string;
|
||||
isDefault: boolean;
|
||||
creationTime: string;
|
||||
creatorId: string | null;
|
||||
lastModifierId: string | null;
|
||||
lastModificationTime: string | null;
|
||||
}
|
||||
78
Yi.Vben5.Vue3/apps/web-antd/src/api/system/dict/dict-data.ts
Normal file
78
Yi.Vben5.Vue3/apps/web-antd/src/api/system/dict/dict-data.ts
Normal file
@@ -0,0 +1,78 @@
|
||||
import type { DictData } from './dict-data-model';
|
||||
|
||||
import type { ID, IDS, PageQuery } from '#/api/common';
|
||||
|
||||
import { commonExport } from '#/api/helper';
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
enum Api {
|
||||
dictDataExport = '/dict/data/export',
|
||||
dictDataInfo = '/dictionary/dic-type',
|
||||
dictDataList = '/dict/data/list',
|
||||
root = '/dictionary',
|
||||
}
|
||||
|
||||
/**
|
||||
* 主要是DictTag组件使用
|
||||
* @param dictType 字典类型
|
||||
* @returns 字典数据
|
||||
*/
|
||||
export function dictDataInfo(dictType: string) {
|
||||
return requestClient.get<DictData[]>(`${Api.dictDataInfo}/${dictType}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 字典数据
|
||||
* @param params 查询参数
|
||||
* @returns 字典数据列表
|
||||
*/
|
||||
export function dictDataList(params?: PageQuery) {
|
||||
return requestClient.get<DictData[]>(Api.root, { params });
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出字典数据
|
||||
* @param data 表单参数
|
||||
* @returns blob
|
||||
*/
|
||||
export function dictDataExport(data: Partial<DictData>) {
|
||||
return commonExport(Api.dictDataExport, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除
|
||||
* @param dictIds 字典ID Array
|
||||
* @returns void
|
||||
*/
|
||||
export function dictDataRemove(dictIds: IDS) {
|
||||
return requestClient.deleteWithMsg<void>(Api.root, {
|
||||
params: { ids: dictIds.join(',') },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增
|
||||
* @param data 表单参数
|
||||
* @returns void
|
||||
*/
|
||||
export function dictDataAdd(data: Partial<DictData>) {
|
||||
return requestClient.postWithMsg<void>(Api.root, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改
|
||||
* @param data 表单参数
|
||||
* @returns void
|
||||
*/
|
||||
export function dictDataUpdate(data: Partial<DictData>) {
|
||||
return requestClient.putWithMsg<void>(`${Api.root}/${data.id}`, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询字典数据详细
|
||||
* @param id 字典ID
|
||||
* @returns 字典数据
|
||||
*/
|
||||
export function dictDetailInfo(id: ID) {
|
||||
return requestClient.get<DictData>(`${Api.root}/${id}`);
|
||||
}
|
||||
13
Yi.Vben5.Vue3/apps/web-antd/src/api/system/dict/dict-type-model.d.ts
vendored
Normal file
13
Yi.Vben5.Vue3/apps/web-antd/src/api/system/dict/dict-type-model.d.ts
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
export interface DictType {
|
||||
id: string;
|
||||
isDeleted: boolean;
|
||||
orderNum: number;
|
||||
state: boolean | null;
|
||||
dictName: string;
|
||||
dictType: string;
|
||||
remark: string | null;
|
||||
creationTime: string;
|
||||
creatorId: string | null;
|
||||
lastModifierId: string | null;
|
||||
lastModificationTime: string | null;
|
||||
}
|
||||
87
Yi.Vben5.Vue3/apps/web-antd/src/api/system/dict/dict-type.ts
Normal file
87
Yi.Vben5.Vue3/apps/web-antd/src/api/system/dict/dict-type.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import type { DictType } from './dict-type-model';
|
||||
|
||||
import type { ID, IDS, PageQuery, PageResult } from '#/api/common';
|
||||
|
||||
import { commonExport } from '#/api/helper';
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
enum Api {
|
||||
dictOptionSelectList = '/dictionary-type/select-data-list',
|
||||
dictTypeExport = '/dictionary-type/export',
|
||||
dictTypeList = '/dictionary-type/list',
|
||||
dictTypeRefreshCache = '/dictionary-type/refreshCache',
|
||||
root = '/dictionary-type',
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取字典类型列表
|
||||
* @param params 请求参数
|
||||
* @returns list
|
||||
*/
|
||||
export function dictTypeList(params?: PageQuery) {
|
||||
return requestClient.get<PageResult<DictType>>(Api.root, { params });
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出字典类型列表
|
||||
* @param data 表单参数
|
||||
* @returns blob
|
||||
*/
|
||||
export function dictTypeExport(data: Partial<DictType>) {
|
||||
return commonExport(Api.dictTypeExport, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除字典类型
|
||||
* @param dictIds 字典类型id数组
|
||||
* @returns void
|
||||
*/
|
||||
export function dictTypeRemove(dictIds: IDS) {
|
||||
return requestClient.deleteWithMsg<void>(Api.root, {
|
||||
params: { ids: dictIds.join(',') },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 刷新字典缓存
|
||||
* @returns void
|
||||
*/
|
||||
export function refreshDictTypeCache() {
|
||||
return requestClient.deleteWithMsg<void>(Api.dictTypeRefreshCache);
|
||||
}
|
||||
|
||||
/**
|
||||
* 新增
|
||||
* @param data 表单参数
|
||||
* @returns void
|
||||
*/
|
||||
export function dictTypeAdd(data: Partial<DictType>) {
|
||||
return requestClient.postWithMsg<void>(Api.root, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 修改
|
||||
* @param data 表单参数
|
||||
* @returns void
|
||||
*/
|
||||
export function dictTypeUpdate(data: Partial<DictType>) {
|
||||
return requestClient.putWithMsg<void>(`${Api.root}/${data.id}`, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询详情
|
||||
* @param dictId 字典类型id
|
||||
* @returns 信息
|
||||
*/
|
||||
export function dictTypeInfo(dictId: ID) {
|
||||
return requestClient.get<DictType>(`${Api.root}/${dictId}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 这个在ele用到 v5用不上
|
||||
* 下拉框 返回值和list一样
|
||||
* @returns options
|
||||
*/
|
||||
export function dictOptionSelectList() {
|
||||
return requestClient.get<DictType[]>(Api.dictOptionSelectList);
|
||||
}
|
||||
84
Yi.Vben5.Vue3/apps/web-antd/src/api/system/menu/index.ts
Normal file
84
Yi.Vben5.Vue3/apps/web-antd/src/api/system/menu/index.ts
Normal file
@@ -0,0 +1,84 @@
|
||||
import type { Menu, MenuOption, MenuQuery, MenuResp } from './model';
|
||||
|
||||
import type { ID, IDS } from '#/api/common';
|
||||
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
enum Api {
|
||||
menuList = '/menu/list',
|
||||
menuTreeSelect = '/menu/tree',
|
||||
root = '/menu',
|
||||
tenantPackageMenuTreeselect = '/menu/tenantPackageMenuTreeselect',
|
||||
}
|
||||
|
||||
/**
|
||||
* 菜单列表
|
||||
* @param params 参数
|
||||
* @returns 列表
|
||||
*/
|
||||
export function menuList(params?: MenuQuery) {
|
||||
return requestClient.get<Menu[]>(Api.menuList, { params });
|
||||
}
|
||||
|
||||
/**
|
||||
* 菜单详情
|
||||
* @param menuId 菜单id
|
||||
* @returns 菜单详情
|
||||
*/
|
||||
export function menuInfo(menuId: ID) {
|
||||
return requestClient.get<Menu>(`${Api.root}/${menuId}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 菜单新增
|
||||
* @param data 参数
|
||||
*/
|
||||
export function menuAdd(data: Partial<Menu>) {
|
||||
return requestClient.postWithMsg<void>(Api.root, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 菜单更新
|
||||
* @param data 参数
|
||||
*/
|
||||
export function menuUpdate(data: Partial<Menu>) {
|
||||
return requestClient.putWithMsg<void>(`${Api.root}/${data.id}`, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 菜单删除
|
||||
* @param menuIds ids
|
||||
*/
|
||||
export function menuRemove(menuIds: IDS) {
|
||||
return requestClient.deleteWithMsg<void>(Api.root, {
|
||||
params: { ids: menuIds.join(',') },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 下拉框使用 返回所有的菜单
|
||||
* @returns []
|
||||
*/
|
||||
export function menuTreeSelect() {
|
||||
return requestClient.get<MenuOption[]>(Api.menuTreeSelect);
|
||||
}
|
||||
|
||||
/**
|
||||
* 租户套餐使用
|
||||
* @param packageId packageId
|
||||
* @returns resp
|
||||
*/
|
||||
export function tenantPackageMenuTreeSelect(packageId: ID) {
|
||||
return requestClient.get<MenuResp>(
|
||||
`${Api.tenantPackageMenuTreeselect}/${packageId}`,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 批量删除菜单
|
||||
* @param menuIds 菜单ids
|
||||
* @returns void
|
||||
*/
|
||||
export function menuCascadeRemove(menuIds: IDS) {
|
||||
return requestClient.deleteWithMsg<void>(`${Api.root}/cascade/${menuIds}`);
|
||||
}
|
||||
57
Yi.Vben5.Vue3/apps/web-antd/src/api/system/menu/model.d.ts
vendored
Normal file
57
Yi.Vben5.Vue3/apps/web-antd/src/api/system/menu/model.d.ts
vendored
Normal file
@@ -0,0 +1,57 @@
|
||||
export interface Menu {
|
||||
id: string;
|
||||
isDeleted: boolean;
|
||||
creationTime: string;
|
||||
creatorId: string | null;
|
||||
lastModifierId: string | null;
|
||||
lastModificationTime: string | null;
|
||||
orderNum: number;
|
||||
state: boolean;
|
||||
menuName: string;
|
||||
routerName?: string | null;
|
||||
menuType: string;
|
||||
permissionCode?: string | null;
|
||||
parentId: string;
|
||||
menuIcon?: string | null;
|
||||
router?: string | null;
|
||||
isLink: boolean;
|
||||
isCache: boolean;
|
||||
isShow: boolean;
|
||||
remark?: string | null;
|
||||
component?: string | null;
|
||||
query?: string | null;
|
||||
children?: Menu[];
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 菜单信息
|
||||
* @param menuName 菜单名称
|
||||
*/
|
||||
export interface MenuOption {
|
||||
id: string;
|
||||
parentId: string;
|
||||
orderNum: number;
|
||||
menuName: string;
|
||||
menuType: string;
|
||||
menuIcon?: string | null;
|
||||
children?: MenuOption[] | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* @description 菜单返回
|
||||
* @param checkedKeys 选中的菜单id
|
||||
* @param menus 菜单信息
|
||||
*/
|
||||
export interface MenuResp {
|
||||
checkedKeys: string[];
|
||||
menus: MenuOption[];
|
||||
}
|
||||
|
||||
/**
|
||||
* 菜单表单查询
|
||||
*/
|
||||
export interface MenuQuery {
|
||||
menuName?: string;
|
||||
isShow?: boolean;
|
||||
state?: boolean;
|
||||
}
|
||||
53
Yi.Vben5.Vue3/apps/web-antd/src/api/system/notice/index.ts
Normal file
53
Yi.Vben5.Vue3/apps/web-antd/src/api/system/notice/index.ts
Normal file
@@ -0,0 +1,53 @@
|
||||
import type { Notice } from './model';
|
||||
|
||||
import type { ID, IDS, PageQuery, PageResult } from '#/api/common';
|
||||
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
enum Api {
|
||||
root = '/notice',
|
||||
}
|
||||
|
||||
/**
|
||||
* 通知公告分页
|
||||
* @param params 分页参数
|
||||
* @returns 分页结果
|
||||
*/
|
||||
export function noticeList(params?: PageQuery) {
|
||||
return requestClient.get<PageResult<Notice>>(Api.root, { params });
|
||||
}
|
||||
|
||||
/**
|
||||
* 通知公告详情
|
||||
* @param id id
|
||||
* @returns 详情
|
||||
*/
|
||||
export function noticeInfo(id: ID) {
|
||||
return requestClient.get<Notice>(`${Api.root}/${id}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通知公告新增
|
||||
* @param data 参数
|
||||
*/
|
||||
export function noticeAdd(data: Partial<Notice>) {
|
||||
return requestClient.postWithMsg<void>(Api.root, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通知公告更新
|
||||
* @param data 参数
|
||||
*/
|
||||
export function noticeUpdate(data: any) {
|
||||
return requestClient.putWithMsg<void>(`${Api.root}/${data.id}`, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 通知公告删除
|
||||
* @param ids ids
|
||||
*/
|
||||
export function noticeRemove(ids: IDS) {
|
||||
return requestClient.deleteWithMsg<void>(Api.root, {
|
||||
params: { ids: ids.join(',') },
|
||||
});
|
||||
}
|
||||
13
Yi.Vben5.Vue3/apps/web-antd/src/api/system/notice/model.d.ts
vendored
Normal file
13
Yi.Vben5.Vue3/apps/web-antd/src/api/system/notice/model.d.ts
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
export interface Notice {
|
||||
id: string;
|
||||
title: string;
|
||||
type: string;
|
||||
content: string;
|
||||
state: boolean;
|
||||
isDeleted: boolean;
|
||||
creationTime: string;
|
||||
creatorId?: string | null;
|
||||
lastModifierId?: string | null;
|
||||
lastModificationTime?: string | null;
|
||||
orderNum: number;
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
import type { OssConfig } from './model';
|
||||
|
||||
import type { ID, IDS, PageQuery } from '#/api/common';
|
||||
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
enum Api {
|
||||
ossConfigChangeStatus = '/resource/oss/config/changeStatus',
|
||||
ossConfigList = '/resource/oss/config/list',
|
||||
root = '/resource/oss/config',
|
||||
}
|
||||
|
||||
// 获取OSS配置列表
|
||||
export function ossConfigList(params?: PageQuery) {
|
||||
return requestClient.get<OssConfig[]>(Api.ossConfigList, { params });
|
||||
}
|
||||
|
||||
// 获取OSS配置的信息
|
||||
export function ossConfigInfo(ossConfigId: ID) {
|
||||
return requestClient.get<OssConfig>(`${Api.root}/${ossConfigId}`);
|
||||
}
|
||||
|
||||
// 添加新的OSS配置
|
||||
export function ossConfigAdd(data: Partial<OssConfig>) {
|
||||
return requestClient.postWithMsg<void>(Api.root, data);
|
||||
}
|
||||
|
||||
// 更新现有的OSS配置
|
||||
export function ossConfigUpdate(data: Partial<OssConfig>) {
|
||||
return requestClient.putWithMsg<void>(`${Api.root}/${data.id}`, data);
|
||||
}
|
||||
|
||||
// 删除OSS配置
|
||||
export function ossConfigRemove(ossConfigIds: IDS) {
|
||||
return requestClient.deleteWithMsg<void>(Api.root, {
|
||||
params: { ids: ossConfigIds.join(',') },
|
||||
});
|
||||
}
|
||||
|
||||
// 更改OSS配置的状态
|
||||
export function ossConfigChangeStatus(data: any) {
|
||||
const requestData: Partial<OssConfig> = {
|
||||
ossConfigId: data.ossConfigId,
|
||||
status: data.status,
|
||||
configKey: data.configKey,
|
||||
};
|
||||
return requestClient.putWithMsg(Api.ossConfigChangeStatus, requestData);
|
||||
}
|
||||
16
Yi.Vben5.Vue3/apps/web-antd/src/api/system/oss-config/model.d.ts
vendored
Normal file
16
Yi.Vben5.Vue3/apps/web-antd/src/api/system/oss-config/model.d.ts
vendored
Normal file
@@ -0,0 +1,16 @@
|
||||
export interface OssConfig {
|
||||
ossConfigId: number;
|
||||
configKey: string;
|
||||
accessKey: string;
|
||||
secretKey: string;
|
||||
bucketName: string;
|
||||
prefix: string;
|
||||
endpoint: string;
|
||||
domain: string;
|
||||
isHttps: string;
|
||||
region: string;
|
||||
status: string;
|
||||
ext1: string;
|
||||
remark: string;
|
||||
accessPolicy: string;
|
||||
}
|
||||
77
Yi.Vben5.Vue3/apps/web-antd/src/api/system/oss/index.ts
Normal file
77
Yi.Vben5.Vue3/apps/web-antd/src/api/system/oss/index.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import type { AxiosRequestConfig } from '@vben/request';
|
||||
|
||||
import type { OssFile } from './model';
|
||||
|
||||
import type { ID, IDS, PageQuery, PageResult } from '#/api/common';
|
||||
|
||||
import { ContentTypeEnum } from '#/api/helper';
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
enum Api {
|
||||
ossDownload = '/resource/oss/download',
|
||||
ossInfo = '/resource/oss/listByIds',
|
||||
ossList = '/resource/oss/list',
|
||||
ossUpload = '/resource/oss/upload',
|
||||
root = '/resource/oss',
|
||||
}
|
||||
|
||||
/**
|
||||
* 文件list
|
||||
* @param params 参数
|
||||
* @returns 分页
|
||||
*/
|
||||
export function ossList(params?: PageQuery) {
|
||||
return requestClient.get<PageResult<OssFile>>(Api.ossList, { params });
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询文件信息 返回为数组
|
||||
* @param ossIds id数组
|
||||
* @returns 信息数组
|
||||
*/
|
||||
export function ossInfo(ossIds: ID | IDS) {
|
||||
return requestClient.get<OssFile[]>(`${Api.ossInfo}/${ossIds}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated 使用apps/web-antd/src/api/core/upload.ts uploadApi方法
|
||||
* @param file 文件
|
||||
* @returns void
|
||||
*/
|
||||
export function ossUpload(file: Blob | File) {
|
||||
const formData = new FormData();
|
||||
formData.append('file', file);
|
||||
return requestClient.postWithMsg(Api.ossUpload, formData, {
|
||||
headers: { 'Content-Type': ContentTypeEnum.FORM_DATA },
|
||||
timeout: 30 * 1000,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载文件 返回为二进制
|
||||
* @param ossId ossId
|
||||
* @param onDownloadProgress 下载进度(可选)
|
||||
* @returns blob
|
||||
*/
|
||||
export function ossDownload(
|
||||
ossId: ID,
|
||||
onDownloadProgress?: AxiosRequestConfig['onDownloadProgress'],
|
||||
) {
|
||||
return requestClient.get<Blob>(`${Api.ossDownload}/${ossId}`, {
|
||||
responseType: 'blob',
|
||||
timeout: 30 * 1000,
|
||||
isTransformResponse: false,
|
||||
onDownloadProgress,
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除文件
|
||||
* @param ossIds id数组
|
||||
* @returns void
|
||||
*/
|
||||
export function ossRemove(ossIds: IDS) {
|
||||
return requestClient.deleteWithMsg<void>(Api.root, {
|
||||
params: { ids: ossIds.join(',') },
|
||||
});
|
||||
}
|
||||
28
Yi.Vben5.Vue3/apps/web-antd/src/api/system/oss/model.d.ts
vendored
Normal file
28
Yi.Vben5.Vue3/apps/web-antd/src/api/system/oss/model.d.ts
vendored
Normal file
@@ -0,0 +1,28 @@
|
||||
export interface OssFile {
|
||||
ossId: string;
|
||||
fileName: string;
|
||||
originalName: string;
|
||||
fileSuffix: string;
|
||||
url: string;
|
||||
createTime: string;
|
||||
createBy: number;
|
||||
createByName: string;
|
||||
service: string;
|
||||
}
|
||||
|
||||
export interface OssConfig {
|
||||
ossConfigId: number;
|
||||
configKey: string;
|
||||
accessKey: string;
|
||||
secretKey: string;
|
||||
bucketName: string;
|
||||
prefix: string;
|
||||
endpoint: string;
|
||||
domain: string;
|
||||
isHttps: string;
|
||||
region: string;
|
||||
status: string;
|
||||
ext1: string;
|
||||
remark: string;
|
||||
accessPolicy: string;
|
||||
}
|
||||
77
Yi.Vben5.Vue3/apps/web-antd/src/api/system/post/index.ts
Normal file
77
Yi.Vben5.Vue3/apps/web-antd/src/api/system/post/index.ts
Normal file
@@ -0,0 +1,77 @@
|
||||
import type { Post } from './model';
|
||||
|
||||
import type { ID, IDS, PageQuery } from '#/api/common';
|
||||
|
||||
import { commonExport } from '#/api/helper';
|
||||
import { requestClient } from '#/api/request';
|
||||
|
||||
enum Api {
|
||||
postExport = '/post/export',
|
||||
postList = '/post/list',
|
||||
postSelect = '/post/select-data-list',
|
||||
root = '/post',
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取岗位列表
|
||||
* @param params 参数
|
||||
* @returns Post[]
|
||||
*/
|
||||
export function postList(params?: PageQuery) {
|
||||
return requestClient.get<Post[]>(Api.root, { params });
|
||||
}
|
||||
|
||||
/**
|
||||
* 导出岗位信息
|
||||
* @param data 请求参数
|
||||
* @returns blob
|
||||
*/
|
||||
export function postExport(data: Partial<Post>) {
|
||||
return commonExport(Api.postExport, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 查询岗位信息
|
||||
* @param id 岗位id
|
||||
* @returns 岗位信息
|
||||
*/
|
||||
export function postInfo(id: ID) {
|
||||
return requestClient.get<Post>(`${Api.root}/${id}`);
|
||||
}
|
||||
|
||||
/**
|
||||
* 岗位新增
|
||||
* @param data 参数
|
||||
* @returns void
|
||||
*/
|
||||
export function postAdd(data: Partial<Post>) {
|
||||
return requestClient.postWithMsg<void>(Api.root, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 岗位更新
|
||||
* @param data 参数
|
||||
* @returns void
|
||||
*/
|
||||
export function postUpdate(data: Partial<Post>) {
|
||||
return requestClient.putWithMsg<void>(`${Api.root}/${data.id}`, data);
|
||||
}
|
||||
|
||||
/**
|
||||
* 岗位删除
|
||||
* @param postIds ids
|
||||
* @returns void
|
||||
*/
|
||||
export function postRemove(postIds: IDS) {
|
||||
return requestClient.deleteWithMsg<void>(Api.root, {
|
||||
params: { ids: postIds.join(',') },
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取岗位下拉列表
|
||||
* @returns 岗位
|
||||
*/
|
||||
export function postOptionSelect(deptId: string) {
|
||||
return requestClient.get<Post[]>(`${Api.postSelect}?keywords=${deptId}`);
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user