Compare commits

...

16 Commits

Author SHA1 Message Date
af6643e65e feat: 更新各前端项目应用标题为"AIOTAGRO管理系统
Some checks failed
Lock Threads / action (push) Has been cancelled
Issue Close Require / close-issues (push) Has been cancelled
Close stale issues / stale (push) Has been cancelled
CodeQL / Analyze (javascript-typescript) (push) Has been cancelled
2025-10-04 18:47:49 +08:00
dbccc1078a refactor: 替换项目中的"yudao"为"AIOTAGRO",并清理相关配置文件 2025-10-04 18:38:56 +08:00
2292615a3b fix:【web-antd】调整开发环境配置,更新后端服务地址为 111.3.47.177 2025-10-04 17:43:29 +08:00
YunaiV
1bf4238609 Merge branch 'master' of https://github.com/yudaocode/yudao-ui-admin-vben into dev 2025-10-02 16:17:02 +08:00
芋道源码
63f58fa84d !219 【antd】CRM 迁移彻底完成
Merge pull request !219 from 芋道源码/dev
2025-10-02 08:10:55 +00:00
YunaiV
463a9fb509 fix:【pay 支付】微信支付 publicKeyContent 调整为非必填,兼容 https://t.zsxq.com/ODR5V、https://gitee.com/yudaocode/yudao-ui-admin-vue3/issues/ICUE53 2025-10-02 10:24:10 +08:00
YunaiV
783f510229 fix:【bpm 工作流】已办任务的审批状态过滤不正确 2025-10-02 09:35:46 +08:00
YunaiV
92c433a6aa feat:【antd】【ele】统一 infra 和 system 的代码风格(demo03/normal) 2025-10-01 13:00:13 +08:00
YunaiV
599e1b342a feat:【antd】【ele】统一 infra 和 system 的代码风格(demo03/inner) 2025-10-01 12:44:06 +08:00
YunaiV
2e2a147815 feat:【antd】【ele】统一 infra 和 system 的代码风格(demo03/erp) 2025-10-01 10:48:23 +08:00
YunaiV
67b39cfe8a feat:【antd】【ele】统一 infra 和 system 的代码风格(demo01、demo02) 2025-10-01 09:49:04 +08:00
YunaiV
cdc0cbc431 feat:【antd】【crm】团队成员的代码实现 2025-09-30 22:23:54 +08:00
芋道源码
d21031ecf4 Merge pull request #125 from inside5545/master
fix:流程表单字典选择器无法正常回显
2025-09-21 11:22:46 +08:00
苏俊言
c4babbecf0 fix:流程表单字典选择器无法正常回显 2025-09-20 16:55:41 +08:00
xingyu
45a5c9bc8e !214 merge dev
Merge pull request !214 from xingyu/dev
2025-09-19 08:20:33 +00:00
YunaiV
06ea290306 (〃'▽'〃) v2025.09 发布:新增 AI 支持联网搜索、推理、文件/图片、MCP 等功能,完善 IoT 场景联动 2025-08-31 11:38:45 +08:00
200 changed files with 5197 additions and 1059 deletions

View File

@@ -1,5 +0,0 @@
# 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)

View File

@@ -1,18 +0,0 @@
{
"$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": []
}

View File

@@ -1,2 +0,0 @@
[core]
ignorecase = false

View File

@@ -1,65 +0,0 @@
name: Bug 反馈
description: 当你在代码中发现了一个 Bug导致应用崩溃或抛出异常或者有一个组件存在问题或者某些地方看起来不对劲。
title: '[Bug]: '
labels: [bug]
body:
- type: markdown
attributes:
value: |
感谢对项目的支持与关注。在提出问题之前,请确保你已查看相关开发或使用文档:
- https://doc.iocoder.cn/
- type: dropdown
id: version
attributes:
label: 分支
description: 你当前正在使用我们软件的哪个分支?
options:
- master (默认)
- dev (开发分支)
validations:
required: true
- type: dropdown
id: version
attributes:
label: 版本
description: 你当前正在使用我们软件的哪个版本?
options:
- antd-design-vue
- element-plus
- naiveui
validations:
required: true
- type: checkboxes
attributes:
label: 这个问题是否已经存在?
options:
- label: 我已经搜索过现有的问题 (https://gitee.com/yudaocode/yudao-ui-admin-vben/issues)
required: true
- type: textarea
attributes:
label: 如何复现
description: 请详细告诉我们如何复现你遇到的问题,如涉及代码,可提供一个最小代码示例,并使用反引号```附上它
placeholder: |
1. ...
2. ...
3. ...
validations:
required: true
- type: textarea
attributes:
label: 预期结果
description: 请告诉我们你预期会发生什么。
validations:
required: true
- type: textarea
attributes:
label: 实际结果
description: 请告诉我们实际发生了什么。
validations:
required: true
- type: textarea
attributes:
label: 截图或视频
description: 如果可以的话,上传任何关于 bug 的截图。
value: |
[在这里上传图片]

View File

@@ -1,5 +0,0 @@
blank_issues_enabled: false
contact_links:
- name: 项目开发文档
url: https://doc.iocoder.cn/
about: 提供项目启动、开发的相关文档

View File

@@ -1,43 +0,0 @@
name: 功能建议
description: 对本项目提出一个功能建议
title: '[功能建议]: '
labels: [enhancement]
body:
- type: markdown
attributes:
value: |
感谢提出功能建议,我们将仔细考虑!
- type: textarea
id: related-problem
attributes:
label: 你的功能建议是否和某个问题相关?
description: 清晰并简洁地描述问题是什么,例如,当我...时,我总是感到困扰。
validations:
required: false
- type: textarea
id: desired-solution
attributes:
label: 你希望看到什么解决方案?
description: 清晰并简洁地描述你希望发生的事情。
validations:
required: true
- type: textarea
id: alternatives
attributes:
label: 你考虑过哪些替代方案?
description: 清晰并简洁地描述你考虑过的任何替代解决方案或功能。
validations:
required: false
- type: textarea
id: additional-context
attributes:
label: 你有其他上下文或截图吗?
description: 在此处添加有关功能请求的任何其他上下文或截图。
validations:
required: false
- type: checkboxes
attributes:
label: 意向参与贡献
options:
- label: 我有意向参与具体功能的开发实现并将代码贡献回到上游社区
required: false

View File

@@ -1,6 +0,0 @@
ports:
- port: 5555
onOpen: open-preview
tasks:
- init: npm i -g corepack && pnpm install
command: pnpm run dev:play

Binary file not shown.

Before

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 348 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 46 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 16 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 238 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 139 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 69 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 32 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 201 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 60 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 196 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 495 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 177 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 126 KiB

View File

@@ -1 +0,0 @@
22.1.0

289
README.md
View File

@@ -1,237 +1,108 @@
# 严肃声明:现在、未来都不会有商业版本,所有代码全部开源
# aiotagro-ui-admin-vben - 企业级管理系统框架
**「我喜欢写代码,乐此不疲」**
**「我喜欢做开源,以此为乐」**
## 项目简介
我 🐶 在上海艰苦奋斗,早中晚在 top3 大厂认真搬砖,夜里为开源做贡献
aiotagro-ui-admin-vben 是基于 Vue3 + TypeScript + Vite 的现代化企业级管理系统框架,采用 monorepo 架构,提供完整的前端解决方案
如果这个项目让你有所收获,记得 Star 关注哦,这对我是非常不错的鼓励与支持。
## 核心特性
## 🐶 新手必读
- 🚀 **现代化技术栈**Vue3 + TypeScript + Vite + Pinia
- 📦 **Monorepo 架构**:基于 pnpm + turbo 的高效开发体验
- 🎨 **多主题支持**Ant Design、Element Plus、Naive UI 三套 UI 组件库
- 🔧 **完整工具链**ESLint、Prettier、StyleLint、TypeScript 检查
- 📱 **响应式设计**:完美适配桌面端和移动端
- 🌐 **国际化支持**:内置多语言解决方案
- 🔐 **权限管理**:完整的 RBAC 权限控制系统
- nodejs > 20.10.0 && pnpm > 10.14.0 (强制使用pnpm)
- 演示地址【Vue3 + element-plus】<http://dashboard-vue3.yudao.iocoder.cn>
- 演示地址【Vue3 + vben5(ant-design-vue)】:<http://dashboard-vben.yudao.iocoder.cn>
- 演示地址【Vue2 + element-ui】<http://dashboard.yudao.iocoder.cn>
- 启动文档:<https://doc.iocoder.cn/quick-start/>
- 视频教程:<https://doc.iocoder.cn/video/>
## 项目结构
## 🐯 平台简介
```
aiotagro-ui-admin-vben/
├── apps/ # 应用目录
│ ├── web-antd/ # Ant Design 版本
│ ├── web-ele/ # Element Plus 版本
│ ├── web-naive/ # Naive UI 版本
│ └── backend-mock/ # 后端模拟服务
├── packages/ # 共享包目录
│ ├── @core/ # 核心功能包
│ ├── constants/ # 常量定义
│ ├── effects/ # 副作用管理
│ ├── icons/ # 图标库
│ ├── locales/ # 国际化资源
│ ├── stores/ # 状态管理
│ ├── styles/ # 样式文件
│ ├── types/ # 类型定义
│ └── utils/ # 工具函数
├── internal/ # 内部配置
│ ├── lint-configs/ # 代码规范配置
│ ├── tailwind-config/ # Tailwind 配置
│ ├── tsconfig/ # TypeScript 配置
│ └── vite-config/ # Vite 配置
├── docs/ # 项目文档
├── playground/ # 开发测试环境
└── scripts/ # 脚本工具
```
**芋道**,以开发者为中心,打造中国第一流的快速开发平台,全部开源,个人与企业可 100% 免费使用。
## 快速开始
- 采用最新 [vue-vben-admin](https://github.com/vbenjs/vue-vben-admin) v5 实现
- 支持 [Ant Design Vue](https://www.antdv.com/) | [Element Plus](https://element-plus.org/zh-CN/) | [Naive UI](https://www.naiveui.com/) 多种免费开源的中后台模版,具备如下特性:
### 环境要求
![首页](.image/demo/vben.png)
- Node.js >= 18.0.0
- pnpm >= 8.0.0
- **最新技术栈**:使用 Vue3、Vite6 等前端前沿技术开发
- **TypeScript**: 应用程序级 JavaScript 的语言
- **主题**: 提供多套主题色彩,可配置自定义主题
- **国际化**:内置完善的国际化方案
- **权限**:内置完善的动态路由权限生成方案
- **组件**:二次封装了多个常用的组件
- **示例**:内置丰富的示例
### 安装依赖
## 外包项目请联系【非项目需求请勿扫码,非客服,不解答项目问题】
```bash
# 安装 pnpm
npm install -g pnpm
![alt 软件定制开发 数舵科技](.image/wx-xingyu.png)
# 安装项目依赖
pnpm install
```
## 技术栈
### 开发运行
| 框架 | 说明 | 版本 |
| --- | --- | --- |
| [Vue](https://staging-cn.vuejs.org/) | vue框架 | 3.5.17 |
| [Vite](https://cn.vitejs.dev//) | 开发与构建工具 | 7.1.2 |
| [Ant Design Vue](https://www.antdv.com/) | Ant Design Vue | 4.2.6 |
| [Element Plus](https://element-plus.org/zh-CN/) | Element Plus | 2.10.2 |
| [Naive UI](https://www.naiveui.com/) | Naive UI | 2.42.0 |
| [TypeScript](https://www.typescriptlang.org/docs/) | JavaScript 超集 | 5.8.3 |
| [pinia](https://pinia.vuejs.org/) | Vue 存储库替代 vuex5 | 3.0.3 |
| [vueuse](https://vueuse.org/) | 常用工具集 | 13.4.0 |
| [vue-i18n](https://kazupon.github.io/vue-i18n/zh/introduction.html/) | 国际化 | 11.1.7 |
| [vue-router](https://router.vuejs.org/) | Vue 路由 | 4.5.1 |
| [Tailwind CSS](https://tailwindcss.com/) | 原子 CSS | 3.4.17 |
| [Iconify](https://icon-sets.iconify.design/) | 在线图标库 | 2.2.354 |
| [TinyMCE](https://www.tiny.cloud/) | 富文本编辑器 | 6.1.0 |
| [Echarts](https://echarts.apache.org/) | 图表库 | 5.6.0 |
| [axios](https://axios-http.com/) | http客户端 | 1.10.0 |
| [dayjs](https://day.js.org/) | 日期处理库 | 1.11.13 |
| [vee-validate](https://vee-validate.logaretm.com/) | 表单验证 | 4.15.1 |
| [zod](https://zod.dev/) | 数据验证 | 3.25.67 |
```bash
# 启动 Ant Design 版本
pnpm dev:antd
## 🔥 后端架构
# 启动 Element Plus 版本
pnpm dev:ele
支持 Spring Boot、Spring Cloud 两种架构:
# 启动 Naive UI 版本
pnpm dev:naive
```
① Spring Boot 单体架构:<https://doc.iocoder.cn>
### 构建项目
![架构图](/.image/common/ruoyi-vue-pro-architecture.png)
```bash
# 构建所有应用
pnpm build
② Spring Cloud 微服务架构:<https://cloud.iocoder.cn>
# 构建指定应用
pnpm build:antd
pnpm build:ele
pnpm build:naive
```
![架构图](/.image/common/yudao-cloud-architecture.png)
## 文档目录
## 内置功能
详细文档请查看 [docs/](./docs/) 目录:
系统内置多种多种业务功能,可以用于快速你的业务系统:
- [📚 技术文档](./docs/) - 开发指南、API 文档
- [👥 用户手册](./docs/user-guide/) - 系统使用说明
- [🚀 部署指南](./docs/deployment/) - 生产环境部署
- [🔧 运维手册](./docs/operations/) - 系统维护指南
系统内置多种多种业务功能,可以用于快速你的业务系统:
## 贡献指南
![功能分层](/.image/common/ruoyi-vue-pro-biz.png)
欢迎提交 Issue 和 Pull Request请阅读 [贡献指南](./docs/contributing.md) 了解详细流程。
- 通用模块(必选):系统功能、基础设施
- 通用模块(可选):工作流程、支付系统、数据报表、会员中心
- 业务系统按需ERP 系统、CRM 系统、商城系统、微信公众号、AI 大模型
## 许可证
### 系统功能
本项目基于 MIT 许可证开源。
| | 功能 | 描述 |
| --- | --- | --- |
| | 用户管理 | 用户是系统操作者,该功能主要完成系统用户配置 |
| ⭐️ | 在线用户 | 当前系统中活跃用户状态监控,支持手动踢下线 |
| | 角色管理 | 角色菜单权限分配、设置角色按机构进行数据范围权限划分 |
| | 菜单管理 | 配置系统菜单、操作权限、按钮权限标识等,本地缓存提供性能 |
| | 部门管理 | 配置系统组织机构(公司、部门、小组),树结构展现支持数据权限 |
| | 岗位管理 | 配置系统用户所属担任职务 |
| 🚀 | 租户管理 | 配置系统租户,支持 SaaS 场景下的多租户功能 |
| 🚀 | 租户套餐 | 配置租户套餐,自定每个租户的菜单、操作、按钮的权限 |
| | 字典管理 | 对系统中经常使用的一些较为固定的数据进行维护 |
| 🚀 | 短信管理 | 短信渠道、短息模板、短信日志,对接阿里云、腾讯云等主流短信平台 |
| 🚀 | 邮件管理 | 邮箱账号、邮件模版、邮件发送日志,支持所有邮件平台 |
| 🚀 | 站内信 | 系统内的消息通知,提供站内信模版、站内信消息 |
| 🚀 | 操作日志 | 系统正常操作日志记录和查询,集成 Swagger 生成日志内容 |
| ⭐️ | 登录日志 | 系统登录日志记录查询,包含登录异常 |
| 🚀 | 错误码管理 | 系统所有错误码的管理,可在线修改错误提示,无需重启服务 |
| | 通知公告 | 系统通知公告信息发布维护 |
| 🚀 | 敏感词 | 配置系统敏感词,支持标签分组 |
| 🚀 | 应用管理 | 管理 SSO 单点登录的应用,支持多种 OAuth2 授权方式 |
| 🚀 | 地区管理 | 展示省份、城市、区镇等城市信息,支持 IP 对应城市 |
## 联系方式
![功能图](/.image/common/system-feature.png)
### 工作流程
![功能图](/.image/common/bpm-feature.png)
基于 Flowable 构建,可支持信创(国产)数据库,满足中国特色流程操作:
| BPMN 设计器 | 钉钉/飞书设计器 |
| --- | --- |
| ![工作流设计器](.image/工作流设计器-bpmn.jpg) | ![工作流设计器](.image/工作流设计器-simple.jpg) |
> 历经头部企业生产验证,工作流引擎须标配仿钉钉/飞书 + BPMN 双设计器!!!
>
> 前者支持轻量配置简单流程,后者实现复杂场景深度编排
| 功能列表 | 功能描述 | 是否完成 |
| --- | --- | --- |
| SIMPLE 设计器 | 仿钉钉/飞书设计器支持拖拽搭建表单流程10 分钟快速完成审批流程配置 | ✅ |
| BPMN 设计器 | 基于 BPMN 标准开发,适配复杂业务场景,满足多层级审批及流程自动化需求 | ✅ |
| 会签 | 同一个审批节点设置多个人(如 A、B、C 三人,三人会同时收到待办任务),需全部同意之后,审批才可到下一审批节点 | ✅ |
| 或签 | 同一个审批节点设置多个人,任意一个人处理后,就能进入下一个节点 | ✅ |
| 依次审批 | (顺序会签)同一个审批节点设置多个人(如 A、B、C 三人),三人按顺序依次收到待办,即 A 先审批A 提交后 B 才能审批,需全部同意之后,审批才可到下一审批节点 | ✅ |
| 抄送 | 将审批结果通知给抄送人,同一个审批默认排重,不重复抄送给同一人 | ✅ |
| 驳回 | (退回)将审批重置发送给某节点,重新审批。可驳回至发起人、上一节点、任意节点 | ✅ |
| 转办 | A 转给其 B 审批B 审批后,进入下一节点 | ✅ |
| 委派 | A 转给其 B 审批B 审批后,转给 AA 继续审批后进入下一节点 | ✅ |
| 加签 | 允许当前审批人根据需要,自行增加当前节点的审批人,支持向前、向后加签 | ✅ |
| 减签 | (取消加签)在当前审批人操作之前,减少审批人 | ✅ |
| 撤销 | (取消流程)流程发起人,可以对流程进行撤销处理 | ✅ |
| 终止 | 系统管理员,在任意节点终止流程实例 | ✅ |
| 表单权限 | 支持拖拉拽配置表单,每个审批节点可配置只读、编辑、隐藏权限 | ✅ |
| 超时审批 | 配置超时审批时间,超时后自动触发审批通过、不通过、驳回等操作 | ✅ |
| 自动提醒 | 配置提醒时间,到达时间后自动触发短信、邮箱、站内信等通知提醒,支持自定义重复提醒频次 | ✅ |
| 父子流程 | 主流程设置子流程节点,子流程节点会自动触发子流程。子流程结束后,主流程才会执行(继续往下下执行),支持同步子流程、异步子流程 | ✅ |
| 条件分支 | (排它分支)用于在流程中实现决策,即根据条件选择一个分支执行 | ✅ |
| 并行分支 | 允许将流程分成多条分支,不进行条件判断,所有分支都会执行 | ✅ |
| 包容分支 | (条件分支 + 并行分支的结合体)允许基于条件选择多条分支执行,但如果没有任何一个分支满足条件,则可以选择默认分支 | ✅ |
| 路由分支 | 根据条件选择一个分支执行(重定向到指定配置节点),也可以选择默认分支执行(继续往下执行) | ✅ |
| 触发节点 | 执行到该节点,触发 HTTP 请求、HTTP 回调、更新数据、删除数据等 | ✅ |
| 延迟节点 | 执行到该节点,审批等待一段时间再执行,支持固定时长、固定日期等 | ✅ |
| 拓展设置 | 流程前置/后置通知,节点(任务)前置、后置通知,流程报表,自动审批去重,自定流程编号、标题、摘要,流程报表等 | ✅ |
### 支付系统
| | 功能 | 描述 |
| --- | -------- | -------------------------------------------------- |
| 🚀 | 应用信息 | 配置商户的应用信息,对接支付宝、微信等多个支付渠道 |
| 🚀 | 支付订单 | 查看用户发起的支付宝、微信等的【支付】订单 |
| 🚀 | 退款订单 | 查看用户发起的支付宝、微信等的【退款】订单 |
| 🚀 | 回调通知 | 查看支付回调业务的【支付】【退款】的通知结果 |
| 🚀 | 接入示例 | 提供接入支付系统的【支付】【退款】的功能实战 |
### 基础设施
| | 功能 | 描述 |
| --- | --- | --- |
| 🚀 | 代码生成 | 前后端代码的生成Java、Vue、SQL、单元测试支持 CRUD 下载 |
| 🚀 | 系统接口 | 基于 Swagger 自动生成相关的 RESTful API 接口文档 |
| 🚀 | 数据库文档 | 基于 Screw 自动生成数据库文档,支持导出 Word、HTML、MD 格式 |
| | 表单构建 | 拖动表单元素生成相应的 HTML 代码,支持导出 JSON、Vue 文件 |
| 🚀 | 配置管理 | 对系统动态配置常用参数,支持 SpringBoot 加载 |
| ⭐️ | 定时任务 | 在线(添加、修改、删除)任务调度包含执行结果日志 |
| 🚀 | 文件服务 | 支持将文件存储到 S3MinIO、阿里云、腾讯云、七牛云、本地、FTP、数据库等 |
| 🚀 | WebSocket | 提供 WebSocket 接入示例,支持一对一、一对多发送方式 |
| 🚀 | API 日志 | 包括 RESTful API 访问日志、异常日志两部分,方便排查 API 相关的问题 |
| | MySQL 监控 | 监视当前系统数据库连接池状态可进行分析SQL找出系统性能瓶颈 |
| | Redis 监控 | 监控 Redis 数据库的使用情况,使用的 Redis Key 管理 |
| 🚀 | 消息队列 | 基于 Redis 实现消息队列Stream 提供集群消费Pub/Sub 提供广播消费 |
| 🚀 | Java 监控 | 基于 Spring Boot Admin 实现 Java 应用的监控 |
| 🚀 | 链路追踪 | 接入 SkyWalking 组件,实现链路追踪 |
| 🚀 | 日志中心 | 接入 SkyWalking 组件,实现日志中心 |
| 🚀 | 服务保障 | 基于 Redis 实现分布式锁、幂等、限流功能,满足高并发场景 |
| 🚀 | 日志服务 | 轻量级日志中心,查看远程服务器的日志 |
| 🚀 | 单元测试 | 基于 JUnit + Mockito 实现单元测试,保证功能的正确性、代码的质量等 |
![功能图](/.image/common/infra-feature.png)
### 数据报表
| | 功能 | 描述 |
| --- | ---------- | ------------------------------------ |
| 🚀 | 报表设计器 | 支持数据报表、图形报表、打印设计等 |
| 🚀 | 大屏设计器 | 拖拽生成数据大屏,内置几十种图表组件 |
### 微信公众号
| | 功能 | 描述 |
| --- | --- | --- |
| 🚀 | 账号管理 | 配置接入的微信公众号,可支持多个公众号 |
| 🚀 | 数据统计 | 统计公众号的用户增减、累计用户、消息概况、接口分析等数据 |
| 🚀 | 粉丝管理 | 查看已关注、取关的粉丝列表,可对粉丝进行同步、打标签等操作 |
| 🚀 | 消息管理 | 查看粉丝发送的消息列表,可主动回复粉丝消息 |
| 🚀 | 自动回复 | 自动回复粉丝发送的消息,支持关注回复、消息回复、关键字回复 |
| 🚀 | 标签管理 | 对公众号的标签进行创建、查询、修改、删除等操作 |
| 🚀 | 菜单管理 | 自定义公众号的菜单,也可以从公众号同步菜单 |
| 🚀 | 素材管理 | 管理公众号的图片、语音、视频等素材,支持在线播放语音、视频 |
| 🚀 | 图文草稿箱 | 新增常用的图文素材到草稿箱,可发布到公众号 |
| 🚀 | 图文发表记录 | 查看已发布成功的图文素材,支持删除操作 |
### 商城系统
演示地址:<https://doc.iocoder.cn/mall-preview/>
![功能图](/.image/common/mall-feature.png)
![功能图](/.image/common/mall-preview.png)
### ERP 系统
演示地址:<https://doc.iocoder.cn/erp-preview/>
![功能图](/.image/common/erp-feature.png)
### CRM 系统
演示地址:<https://doc.iocoder.cn/crm-preview/>
![功能图](/.image/common/crm-feature.png)
### AI 大模型
演示地址:<https://doc.iocoder.cn/ai-preview/>
![功能图](/.image/common/ai-feature.png)
![功能图](/.image/common/ai-preview.gif)
- 项目主页https://github.com/vbenjs/vue-vben-admin
- 问题反馈https://github.com/vbenjs/vue-vben-admin/issues

View File

@@ -2,8 +2,7 @@
## Description
Vben Admin 数据 mock 服务,没有对接任何的数据库,所有数据都是模拟的,用于前端开发时提供数据支持。线上环境不再提mock 集成,可自行部署服务或者对接真实数据,由于 `mock.js` 等工具有一些限制,比如上传文件不行、无法模拟复杂的逻辑等,所以这里使用了真实的后端服务来实现。唯一麻烦的是本地需要同时启动后端服务和前端服务,但是这样可以更好的模拟真实环境。该服务不需要手动启动,已经集成vite 插件内,随应用一起启用
Vben Admin 数据 mock 服务,没有对接任何的数据库,所有数据都是模拟的,用于前端开发时提供数据支持。线上环境不再提<EFBFBD><EFBFBD>?mock 集成,可自行部署服务或者对接真实数据,由于 `mock.js` 等工具有一些限制,比如上传文件不行、无法模拟复杂的逻辑等,所以这里使用了真实的后端服务来实现。唯一麻烦的是本地需要同时启动后端服务和前端服务,但是这样可以更好的模拟真实环境。该服务不需要手动启动,已经集成<EFBFBD><EFBFBD>?vite 插件内,随应用一起启用<EFBFBD><EFBFBD>?
## Running the app
```bash

View File

@@ -1,5 +1,5 @@
# 应用标题
VITE_APP_TITLE=芋道管理系统
VITE_APP_TITLE=AIOTAGRO管理系统
# 应用命名空间用于缓存、store等功能的前缀确保隔离
VITE_APP_NAMESPACE=yudao-vben-antd

View File

@@ -4,7 +4,7 @@ VITE_PORT=5666
VITE_BASE=/
# 请求路径
VITE_BASE_URL=http://127.0.0.1:48080
0VITE_BASE_URL=http://111.3.47.177:48080
# 接口地址
VITE_GLOB_API_URL=/admin-api
# 文件上传类型server - 后端上传, client - 前端直连上传仅支持S3服务

View File

@@ -1,6 +1,6 @@
# Software License Agreement
**TinyMCE** [<https://github.com/tinymce/tinymce>](https://github.com/tinymce/tinymce)
**TinyMCE** <EFBFBD><EFBFBD>?[<https://github.com/tinymce/tinymce>](https://github.com/tinymce/tinymce)
Copyright (c) 2024, Ephox Corporation DBA Tiny Technologies, Inc.
Licensed under the terms of [GNU General Public License Version 2 or later](http://www.gnu.org/licenses/gpl.html).

View File

@@ -161,7 +161,7 @@ setupVbenVxeTable({
});
// 表格配置项可以用 cellRender: { name: 'CellSwitch', props: { beforeChange: () => {} } },
// add by 芋艿from https://github.com/vbenjs/vue-vben-admin/blob/main/playground/src/adapter/vxe-table.ts#L97-L123
// add by AIOTAGROfrom https://github.com/vbenjs/vue-vben-admin/blob/main/playground/src/adapter/vxe-table.ts#L97-L123
vxeUI.renderer.add('CellSwitch', {
renderTableDefault({ attrs, props }, { column, row }) {
const loadingKey = `__loading_${column.field}`;
@@ -193,7 +193,7 @@ setupVbenVxeTable({
});
// 注册表格的操作按钮渲染器 cellRender: { name: 'CellOperation', options: ['edit', 'delete'] }
// add by 芋艿from https://github.com/vbenjs/vue-vben-admin/blob/main/playground/src/adapter/vxe-table.ts#L125-L255
// add by AIOTAGROfrom https://github.com/vbenjs/vue-vben-admin/blob/main/playground/src/adapter/vxe-table.ts#L125-L255
vxeUI.renderer.add('CellOperation', {
renderTableDefault({ attrs, options, props }, { column, row }) {
const defaultProps = { size: 'small', type: 'link', ...props };

View File

@@ -2,7 +2,7 @@ import { requestClient } from '#/api/request';
export namespace BpmModelApi {
/** 用户信息 TODO 这个是不是可以抽取出来定义在公共模块 */
// TODO @芋艿:一起看看。
// TODO @AIOTAGRO:一起看看。
export interface UserInfo {
id: number;
nickname: string;

View File

@@ -11,7 +11,7 @@ import type { BpmModelApi } from '#/api/bpm/model';
import { requestClient } from '#/api/request';
export namespace BpmProcessInstanceApi {
// TODO @芋艿:一些注释缺少或者不对;
// TODO @AIOTAGRO:一些注释缺少或者不对;
export interface Task {
id: number;
name: string;

View File

@@ -59,7 +59,7 @@ function createRequestClient(baseURL: string, options?: RequestClientOptions) {
}
const resp = await refreshTokenApi(refreshToken);
const newToken = resp?.data?.data?.accessToken;
// add by 芋艿:这里一定要抛出 resp.data从而触发 authenticateResponseInterceptor 中,刷新令牌失败!!!
// add by AIOTAGRO:这里一定要抛出 resp.data从而触发 authenticateResponseInterceptor 中,刷新令牌失败!!!
if (!newToken) {
throw resp.data;
}
@@ -154,7 +154,7 @@ function createRequestClient(baseURL: string, options?: RequestClientOptions) {
const responseData = error?.response?.data ?? {};
const errorMessage =
responseData?.error ?? responseData?.message ?? responseData.msg ?? '';
// add by 芋艿:特殊:避免 401 账号未登录,重复提示。因为,此时会跳转到登录界面,只需提示一次!!!
// add by AIOTAGRO:特殊:避免 401 "账号未登录",重复提示。因为,此时会跳转到登录界面,只需提示一次!!!
if (error?.data?.code === 401) {
return;
}

View File

@@ -493,7 +493,7 @@ const previewProcessJson = () => {
});
};
/* ------------------------------------------------ 芋道源码 methods ------------------------------------------------------ */
/* ------------------------------------------------ AIOTAGRO methods ------------------------------------------------------ */
onMounted(() => {
initBpmnModeler();
createNewDiagram(props.value);

View File

@@ -466,7 +466,7 @@ watch(
@change="() => updateLoopBase()"
/>
</FormItem>
<!-- add by 芋艿:由于「元素变量」暂时用不到,所以这里 display 为 none -->
<!-- add by AIOTAGRO:由于「元素变量」暂时用不到,所以这里 display 为 none -->
<FormItem label="元素变量" key="elementVariable" class="hidden">
<Input
v-model:value="loopInstanceForm.elementVariable"
@@ -484,7 +484,7 @@ watch(
"
/>
</FormItem>
<!-- add by 芋艿:由于「异步状态」暂时用不到,所以这里 display 为 none -->
<!-- add by AIOTAGRO:由于「异步状态」暂时用不到,所以这里 display 为 none -->
<FormItem label="异步状态" key="async" class="hidden">
<Checkbox
v-model:checked="loopInstanceForm.asyncBefore"

View File

@@ -64,7 +64,7 @@ watch(
<template>
<div class="panel-tab__content">
<Form>
<!-- add by 芋艿由于异步延续暂时用不到所以这里 display none -->
<!-- add by AIOTAGRO由于异步延续暂时用不到所以这里 display none -->
<FormItem label="异步延续" class="hidden">
<Checkbox
v-model:checked="taskConfigForm.asyncBefore"

View File

@@ -219,7 +219,7 @@ const resetTaskForm = () => {
const changeCandidateStrategy = () => {
userTaskForm.value.candidateParam = [];
deptLevel.value = 1;
// 注释 by 芋艿这个交互很多用户反馈费解https://t.zsxq.com/xNmas 所以暂时屏蔽
// 注释 by AIOTAGRO这个交互很多用户反馈费解https://t.zsxq.com/xNmas 所以暂时屏蔽
// if (userTaskForm.value.candidateStrategy === CandidateStrategy.FORM_USER) {
// // 特殊处理表单内用户字段,当只有发起人选项时应选中发起人
// if (!userFieldOnFormOptions.value || userFieldOnFormOptions.value.length <= 1) {

View File

@@ -121,9 +121,9 @@ export const useApiSelect = (option: ApiSelectProps) => {
parseOptions0(data);
return;
}
// 情况三:不是 yudao-vue-pro 标准返回
// 情况三:不是 aiotagro-vue-pro 标准返回
console.warn(
`接口[${props.url}] 返回结果不是 yudao-vue-pro 标准返回建议采用自定义解析函数处理`,
`接口[${props.url}] 返回结果不是 aiotagro-vue-pro 标准返回建议采用自定义解析函数处理`,
);
}

View File

@@ -39,6 +39,7 @@ export const useDictSelectRule = () => {
title: label,
info: '',
$required: false,
modelField: 'value',
};
},
props(_: any, { t }: any) {

View File

@@ -3,7 +3,7 @@ import type { Rule } from '@form-create/ant-design-vue';
/** 数据字典 Select 选择器组件 Props 类型 */
export interface DictSelectProps {
dictType: string; // 字典类型
valueType?: 'bool' | 'int' | 'str'; // 字典值类型 TODO @芋艿'boolean' | 'number' | 'string';需要和 vue3 一起统一!
valueType?: 'bool' | 'int' | 'str'; // 字典值类型 TODO @AIOTAGRO'boolean' | 'number' | 'string';需要和 vue3 一起统一!
selectType?: 'checkbox' | 'radio' | 'select'; // 选择器类型,下拉框 select、多选框 checkbox、单选框 radio
formCreateInject?: any;
}

View File

@@ -34,6 +34,7 @@ function getUserTypeColor(userType: number) {
</script>
<template>
<div>
<!-- TODO @xingyu有没可能美化下 -->
<Timeline>
<Timeline.Item
v-for="log in logList"

View File

@@ -1,4 +1,4 @@
// TODO @芋艿:是否有更好的组织形式?!
// TODO @AIOTAGRO:是否有更好的组织形式?!
<script lang="ts" setup>
import type { DataNode } from 'ant-design-vue/es/tree';

View File

@@ -1,5 +1,5 @@
<script lang="ts" setup>
// TODO @芋艿:是否有更好的组织形式?!
// TODO @AIOTAGRO:是否有更好的组织形式?!
// TODO @xingyu你感觉这个放到每个 system、infra 模块下,然后新建一个 components表示每个模块有一些共享的组件然后全局只放通用的无业务含义的可以哇
import type { Key } from 'ant-design-vue/es/table/interface';

View File

@@ -1,5 +1,5 @@
<script setup lang="ts">
// TODO @芋艿:后续是不是把业务组件,挪到每个模块里;待定;
// TODO @AIOTAGRO:后续是不是把业务组件,挪到每个模块里;待定;
import type { Ref } from 'vue';
import type { SimpleFlowNode } from '../../consts';

View File

@@ -178,7 +178,7 @@ export function useFormFields() {
return parseFormCreateFields(unref(formFields));
}
// TODO @芋艿:后续需要把各种类似 useFormFieldsPermission 的逻辑,抽成一个通用方法。
// TODO @AIOTAGRO:后续需要把各种类似 useFormFieldsPermission 的逻辑,抽成一个通用方法。
/**
* @description 获取流程表单的字段和发起人字段
*/

View File

@@ -78,7 +78,7 @@ export function useUploadType({
return { getAccept, getStringAccept, getHelpText };
}
// TODO @芋艿:目前保持和 admin-vue3 一致,后续可能重构
// TODO @AIOTAGRO:目前保持和 admin-vue3 一致,后续可能重构
export function useUpload(directory?: string) {
// 后端上传地址
const uploadUrl = getUploadUrl();

View File

@@ -1,3 +1,3 @@
# locale
每个app使用的国际化可能不同这里用于扩展国际化的功能例如扩展 dayjs、antd组件库的多语言切换以及app本身的国际化文件
每个app使用的国际化可能不同这里用于扩展国际化的功能例如扩展 dayjs、antd组件库的多语言切换以及app本身的国际化文件<EFBFBD><EFBFBD>?

View File

@@ -20,6 +20,6 @@ export const overridesPreferences = defineOverridesPreferences({
},
copyright: {
companyName: import.meta.env.VITE_APP_TITLE,
companySiteLink: 'https://gitee.com/yudaocode/yudao-ui-admin-vben',
companySiteLink: 'https://gitee.com/yudaocode/yudao-ui-admin-vben' // AIOTAGRO,
},
});

View File

@@ -25,7 +25,7 @@ async function generateAccess(options: GenerateMenuAndRoutesOptions) {
return await generateAccessible(preferences.app.accessMode, {
...options,
fetchMenuListAsync: async () => {
// 由于 yudao 通过 accessStore 读取,所以不在进行 message.loading 提示
// 由于 AIOTAGRO 通过 accessStore 读取,所以不在进行 message.loading 提示
// 补充说明accessStore.accessMenus 一开始是 AppRouteRecordRaw 类型(后端加载),后面被赋值成 MenuRecordRaw 类型(前端转换)
const accessMenus = accessStore.accessMenus as AppRouteRecordRaw[];
return convertServerMenuToRouteRecordStringComponent(accessMenus);

View File

@@ -102,7 +102,7 @@ function setupAccessGuard(router: Router) {
// 当前登录用户拥有的角色标识列表
let userInfo = userStore.userInfo;
if (!userInfo) {
// add by 芋艿:由于 yudao 是 fetchUserInfo 统一加载用户 + 权限信息,所以将 fetchMenuListAsync
// add by 芋艿:由于 AIOTAGRO 是 fetchUserInfo 统一加载用户 + 权限信息,所以将 fetchMenuListAsync
const loading = message.loading({
content: `${$t('common.loadingMenu')}...`,
});

View File

@@ -35,7 +35,7 @@ const coreRouteNames = traverseTreeValues(coreRoutes, (route) => route.name);
/** 有权限校验的路由列表,包含动态路由和静态路由 */
const accessRoutes = [...dynamicRoutes, ...staticRoutes];
// add by 芋艿from https://github.com/vbenjs/vue-vben-admin/blob/main/playground/src/router/routes/index.ts#L38-L45
// add by AIOTAGROfrom https://github.com/vbenjs/vue-vben-admin/blob/main/playground/src/router/routes/index.ts#L38-L45
const componentKeys: string[] = Object.keys(
import.meta.glob('../../views/**/*.vue'),
)

View File

@@ -78,4 +78,4 @@ const routes: RouteRecordRaw[] = [
// },
];
export default routes; // update by 芋艿:不展示
export default routes; // update by AIOTAGRO:不展示

View File

@@ -71,7 +71,7 @@ export const useAuthStore = defineStore('auth', () => {
accessStore.setRefreshToken(refreshToken);
// 获取用户信息并存储到 userStore、accessStore 中
// TODO @芋艿:清理掉 accessCodes 相关的逻辑
// TODO @AIOTAGRO:清理掉 accessCodes 相关的逻辑
// const [fetchUserInfoResult, accessCodes] = await Promise.all([
// fetchUserInfo(),
// // getAccessCodesApi(),

View File

@@ -1,7 +1,7 @@
/**
* 针对 https://github.com/xaboy/form-create-designer 封装的工具类
*/
// TODO @芋艿:后续这些 form-create 的优化;另外需要使用 form-create-helper 会好点
// TODO @AIOTAGRO:后续这些 form-create 的优化;另外需要使用 form-create-helper 会好点
import { isRef } from 'vue';
import formCreate from '@form-create/ant-design-vue';

View File

@@ -1,3 +1,3 @@
# \_core
此目录包含应用程序正常运行所需的基本视图。这些视图是应用程序布局中使用的视图
此目录包含应用程序正常运行所需的基本视图。这些视图是应用程序布局中使用的视图<EFBFBD><EFBFBD>?

View File

@@ -57,7 +57,7 @@ onMounted(loadProfile);
<Tabs.TabPane key="userSocial" tab="社交绑定" force-render>
<UserSocial @update:active-name="activeName = $event" />
</Tabs.TabPane>
<!-- TODO @芋艿在线设备 -->
<!-- TODO @AIOTAGRO在线设备 -->
</Tabs>
</Card>
</div>

View File

@@ -20,7 +20,7 @@ async function handlerPromptClick(prompt: any) {
<!-- center-container -->
<div class="flex flex-col justify-center">
<!-- title -->
<div class="text-center text-3xl font-bold">芋道 AI</div>
<div class="text-center text-3xl font-bold">AIOTAGRO AI</div>
<!-- role-list -->
<div class="mt-5 flex w-96 flex-wrap items-center justify-center">

View File

@@ -490,11 +490,11 @@ onMounted(async () => {
activeMessageListLoading.value = true;
await getMessageList();
});
// TODO @芋艿:深度思考
// TODO @芋艿:联网搜索
// TODO @芋艿:附件支持
// TODO @芋艿mcp 相关
// TODO @芋艿:异常消息的处理
// TODO @AIOTAGRO:深度思考
// TODO @AIOTAGRO:联网搜索
// TODO @AIOTAGRO:附件支持
// TODO @AIOTAGROmcp 相关
// TODO @AIOTAGRO:异常消息的处理
</script>
<template>

View File

@@ -42,14 +42,11 @@ export function useGridFormSchema(): VbenFormSchema[] {
},
{
fieldName: 'status',
label: '流程状态',
label: '审批状态',
component: 'Select',
componentProps: {
options: getDictOptions(
DICT_TYPE.BPM_PROCESS_INSTANCE_STATUS,
'number',
),
placeholder: '请选择流程状态',
options: getDictOptions(DICT_TYPE.BPM_TASK_STATUS, 'number'),
placeholder: '请选择审批状态',
allowClear: true,
},
},

View File

@@ -30,7 +30,6 @@ const [FormModal, formModalApi] = useVbenModal({
destroyOnClose: true,
});
/** 已选择的商机 */
const checkedRows = ref<CrmBusinessApi.Business[]>([]);
function setCheckedRows({ records }: { records: CrmBusinessApi.Business[] }) {
checkedRows.value = records;

View File

@@ -46,7 +46,6 @@ const [DetailListModal, detailListModalApi] = useVbenModal({
destroyOnClose: true,
});
/** 已选择的商机 */
const checkedRows = ref<CrmBusinessApi.Business[]>([]);
function setCheckedRows({ records }: { records: CrmBusinessApi.Business[] }) {
checkedRows.value = records;

View File

@@ -1,2 +1,2 @@
export { default as PermissionList } from './modules/permission-list.vue';
export { default as PermissionList } from './modules/list.vue';
export { default as TransferForm } from './modules/transfer-form.vue';

View File

@@ -19,7 +19,7 @@ import {
import { $t } from '#/locales';
import { useGridColumns } from './data';
import Form from './permission-form.vue';
import Form from './form.vue';
const props = defineProps<{
bizId: number; //
@@ -38,13 +38,14 @@ const [FormModal, formModalApi] = useVbenModal({
destroyOnClose: true,
});
//
const validateOwnerUser = ref(false);
const validateWrite = ref(false);
const isPool = ref(false);
const userStore = useUserStore();
const validateOwnerUser = ref(false); //
const validateWrite = ref(false); //
const isPool = ref(false); //
/** 刷新表格 */
function onRefresh() {
function handleRefresh() {
gridApi.query();
}
@@ -62,6 +63,7 @@ function setCheckedRows({
checkedRows.value = records;
}
/** 新建团队成员 */
function handleCreate() {
formModalApi
.setData({
@@ -71,6 +73,7 @@ function handleCreate() {
.open();
}
/** 编辑团队成员 */
function handleEdit() {
if (checkedRows.value.length === 0) {
message.error('请先选择团队成员后操作!');
@@ -90,6 +93,7 @@ function handleEdit() {
.open();
}
/** 删除团队成员 */
function handleDelete() {
if (checkedRows.value.length === 0) {
message.error('请先选择团队成员后操作!');
@@ -106,7 +110,7 @@ function handleDelete() {
if (res) {
//
message.success($t('ui.actionMessage.operationSuccess'));
onRefresh();
handleRefresh();
resolve(true);
} else {
reject(new Error('移出失败'));
@@ -118,8 +122,7 @@ function handleDelete() {
});
}
const userStore = useUserStore();
/** 退出团队 */
async function handleQuit() {
const permission = gridApi.grid
.getData()
@@ -208,6 +211,7 @@ watch(
}
});
} else {
//
isPool.value = true;
}
},
@@ -219,7 +223,7 @@ watch(
<template>
<div>
<FormModal @success="onRefresh" />
<FormModal @success="handleRefresh" />
<Grid>
<template #toolbar-tools>
<TableAction
@@ -237,6 +241,7 @@ watch(
icon: ACTION_ICON.EDIT,
ifShow: validateOwnerUser,
onClick: handleEdit,
disabled: checkedRows.length === 0,
},
{
label: $t('common.delete'),
@@ -245,6 +250,7 @@ watch(
icon: ACTION_ICON.DELETE,
ifShow: validateOwnerUser,
onClick: handleDelete,
disabled: checkedRows.length === 0,
},
{
label: '退出团队',

View File

@@ -99,7 +99,7 @@ const [Modal, modalApi] = useVbenModal({
},
async onOpenChange(isOpen: boolean) {
if (!isOpen) {
formApi.resetForm();
await formApi.resetForm();
return;
}
// 加载数据
@@ -108,7 +108,7 @@ const [Modal, modalApi] = useVbenModal({
return;
}
bizType.value = data.bizType;
formApi.setFieldValue('id', data.bizType);
await formApi.setFieldValue('id', data.bizType);
},
});
</script>

View File

@@ -37,7 +37,7 @@ function handleRefresh() {
/** 创建分类 */
function handleCreate() {
formModalApi.setData({}).open();
formModalApi.setData(null).open();
}
/** 添加下级分类 */

View File

@@ -180,7 +180,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
});
/** 获取数据源配置列表 */
// TODO @芋艿:这种场景的最佳实践;
// TODO @AIOTAGRO:这种场景的最佳实践;
async function initDataSourceConfig() {
try {
dataSourceConfigList.value = await getDataSourceConfigList();

View File

@@ -1,5 +1,5 @@
<script lang="ts" setup>
// TODO @芋艿待定vben2.0 有 CodeEditor不确定官方后续会不会迁移
// TODO @AIOTAGRO待定vben2.0 有 CodeEditor不确定官方后续会不会迁移
import type { InfraCodegenApi } from '#/api/infra/codegen';
import { h, ref } from 'vue';

View File

@@ -4,7 +4,7 @@ import type { Demo01ContactApi } from '#/api/infra/demo/demo01';
import { ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui';
import { confirm, Page, useVbenModal } from '@vben/common-ui';
import { downloadFileFromBlobPart, isEmpty } from '@vben/utils';
import { message } from 'ant-design-vue';
@@ -27,13 +27,19 @@ const [FormModal, formModalApi] = useVbenModal({
});
/** 刷新表格 */
function onRefresh() {
function handleRefresh() {
gridApi.query();
}
/** 导出表格 */
async function handleExport() {
const data = await exportDemo01Contact(await gridApi.formApi.getValues());
downloadFileFromBlobPart({ fileName: '示例联系人.xls', source: data });
}
/** 创建示例联系人 */
function handleCreate() {
formModalApi.setData({}).open();
formModalApi.setData(null).open();
}
/** 编辑示例联系人 */
@@ -46,12 +52,11 @@ async function handleDelete(row: Demo01ContactApi.Demo01Contact) {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.id]),
duration: 0,
key: 'action_process_msg',
});
try {
await deleteDemo01Contact(row.id!);
message.success($t('ui.actionMessage.deleteSuccess', [row.id]));
onRefresh();
handleRefresh();
} finally {
hideLoading();
}
@@ -59,16 +64,16 @@ async function handleDelete(row: Demo01ContactApi.Demo01Contact) {
/** 批量删除示例联系人 */
async function handleDeleteBatch() {
await confirm($t('ui.actionMessage.deleteBatchConfirm'));
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting'),
content: $t('ui.actionMessage.deletingBatch'),
duration: 0,
key: 'action_process_msg',
});
try {
await deleteDemo01ContactList(checkedIds.value);
checkedIds.value = [];
message.success($t('ui.actionMessage.deleteSuccess'));
onRefresh();
handleRefresh();
} finally {
hideLoading();
}
@@ -83,12 +88,6 @@ function handleRowCheckboxChange({
checkedIds.value = records.map((item) => item.id!);
}
/** 导出表格 */
async function handleExport() {
const data = await exportDemo01Contact(await gridApi.formApi.getValues());
downloadFileFromBlobPart({ fileName: '示例联系人.xls', source: data });
}
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: {
schema: useGridFormSchema(),
@@ -128,8 +127,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
<template>
<Page auto-content-height>
<FormModal @success="onRefresh" />
<FormModal @success="handleRefresh" />
<Grid table-title="示例联系人列表">
<template #toolbar-tools>
<TableAction
@@ -149,11 +147,11 @@ const [Grid, gridApi] = useVbenVxeGrid({
onClick: handleExport,
},
{
label: '批量删除',
label: $t('ui.actionTitle.deleteBatch'),
type: 'primary',
danger: true,
disabled: isEmpty(checkedIds),
icon: ACTION_ICON.DELETE,
disabled: isEmpty(checkedIds),
auth: ['infra:demo01-contact:delete'],
onClick: handleDeleteBatch,
},

View File

@@ -54,10 +54,7 @@ const [Modal, modalApi] = useVbenModal({
// 关闭并提示
await modalApi.close();
emit('success');
message.success({
content: $t('ui.actionMessage.operationSuccess'),
key: 'action_process_msg',
});
message.success($t('ui.actionMessage.operationSuccess'));
} finally {
modalApi.unlock();
}
@@ -67,23 +64,21 @@ const [Modal, modalApi] = useVbenModal({
formData.value = undefined;
return;
}
// 加载数据
let data = modalApi.getData<Demo01ContactApi.Demo01Contact>();
if (!data) {
const data = modalApi.getData<Demo01ContactApi.Demo01Contact>();
if (!data || !data.id) {
return;
}
if (data.id) {
modalApi.lock();
try {
data = await getDemo01Contact(data.id);
} finally {
modalApi.unlock();
modalApi.lock();
try {
formData.value = await getDemo01Contact(data.id);
// 设置到 values
if (formData.value) {
await formApi.setValues(formData.value);
}
} finally {
modalApi.unlock();
}
// 设置到 values
formData.value = data;
await formApi.setValues(formData.value);
},
});
</script>

View File

@@ -25,15 +25,8 @@ const [FormModal, formModalApi] = useVbenModal({
destroyOnClose: true,
});
/** 切换树形展开/收缩状态 */
const isExpanded = ref(true);
function toggleExpand() {
isExpanded.value = !isExpanded.value;
gridApi.grid.setAllTreeExpand(isExpanded.value);
}
/** 刷新表格 */
function onRefresh() {
function handleRefresh() {
gridApi.query();
}
@@ -61,20 +54,25 @@ function handleAppend(row: Demo02CategoryApi.Demo02Category) {
/** 删除示例分类 */
async function handleDelete(row: Demo02CategoryApi.Demo02Category) {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.id]),
content: $t('ui.actionMessage.deleting', [row.name]),
duration: 0,
});
try {
await deleteDemo02Category(row.id!);
message.success({
content: $t('ui.actionMessage.deleteSuccess', [row.name]),
});
onRefresh();
message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
handleRefresh();
} finally {
hideLoading();
}
}
/** 切换树形展开/收缩状态 */
const isExpanded = ref(true);
function handleExpand() {
isExpanded.value = !isExpanded.value;
gridApi.grid.setAllTreeExpand(isExpanded.value);
}
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: {
schema: useGridFormSchema(),
@@ -113,14 +111,13 @@ const [Grid, gridApi] = useVbenVxeGrid({
<template>
<Page auto-content-height>
<FormModal @success="onRefresh" />
<FormModal @success="handleRefresh" />
<Grid table-title="示例分类列表">
<template #toolbar-tools>
<TableAction
:actions="[
{
label: $t('ui.actionTitle.create', ['菜单']),
label: $t('ui.actionTitle.create', ['示例分类']),
type: 'primary',
icon: ACTION_ICON.ADD,
auth: ['infra:demo02-category:create'],
@@ -129,7 +126,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
{
label: isExpanded ? '收缩' : '展开',
type: 'primary',
onClick: toggleExpand,
onClick: handleExpand,
},
{
label: $t('ui.actionTitle.export'),

View File

@@ -19,14 +19,9 @@ import { useFormSchema } from '../data';
const emit = defineEmits(['success']);
const formData = ref<Demo02CategoryApi.Demo02Category>();
const parentId = ref<number>(); // 新增下级时的父级 ID
const getTitle = computed(() => {
if (formData.value?.id) {
return $t('ui.actionTitle.edit', ['示例分类']);
}
return parentId.value
? $t('ui.actionTitle.create', ['下级示例分类'])
return formData.value?.id
? $t('ui.actionTitle.edit', ['示例分类'])
: $t('ui.actionTitle.create', ['示例分类']);
});
@@ -36,7 +31,7 @@ const [Form, formApi] = useVbenForm({
class: 'w-full',
},
formItemClass: 'col-span-2',
labelWidth: 80,
labelWidth: 100,
},
layout: 'horizontal',
schema: useFormSchema(),
@@ -70,25 +65,23 @@ const [Modal, modalApi] = useVbenModal({
formData.value = undefined;
return;
}
// 加载数据
let data = modalApi.getData<Demo02CategoryApi.Demo02Category>();
if (!data) {
const data = modalApi.getData<Demo02CategoryApi.Demo02Category>();
if (!data || !data.id) {
// 设置上级
await formApi.setValues(data);
return;
}
if (data.id) {
// 编辑
modalApi.lock();
try {
data = await getDemo02Category(data.id);
} finally {
modalApi.unlock();
modalApi.lock();
try {
formData.value = await getDemo02Category(data.id);
// 设置到 values
if (formData.value) {
await formApi.setValues(formData.value);
}
} finally {
modalApi.unlock();
}
// 设置到 values
formData.value = data;
await formApi.setValues(formData.value);
},
});
</script>

View File

@@ -2,13 +2,12 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { Demo03StudentApi } from '#/api/infra/demo/demo03/erp';
import { h, ref } from 'vue';
import { ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui';
import { Download, Plus, Trash2 } from '@vben/icons';
import { confirm, Page, useVbenModal } from '@vben/common-ui';
import { downloadFileFromBlobPart, isEmpty } from '@vben/utils';
import { Button, message, Tabs } from 'ant-design-vue';
import { message, Tabs } from 'ant-design-vue';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import {
@@ -34,48 +33,53 @@ const [FormModal, formModalApi] = useVbenModal({
});
/** 刷新表格 */
function onRefresh() {
function handleRefresh() {
gridApi.query();
}
/** 导出表格 */
async function handleExport() {
const data = await exportDemo03Student(await gridApi.formApi.getValues());
downloadFileFromBlobPart({ fileName: '学生.xls', source: data });
}
/** 创建学生 */
function onCreate() {
formModalApi.setData({}).open();
function handleCreate() {
formModalApi.setData(null).open();
}
/** 编辑学生 */
function onEdit(row: Demo03StudentApi.Demo03Student) {
function handleEdit(row: Demo03StudentApi.Demo03Student) {
formModalApi.setData(row).open();
}
/** 删除学生 */
async function onDelete(row: Demo03StudentApi.Demo03Student) {
async function handleDelete(row: Demo03StudentApi.Demo03Student) {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.id]),
duration: 0,
key: 'action_process_msg',
});
try {
await deleteDemo03Student(row.id!);
message.success($t('ui.actionMessage.deleteSuccess', [row.id]));
onRefresh();
handleRefresh();
} finally {
hideLoading();
}
}
/** 批量删除学生 */
async function onDeleteBatch() {
async function handleDeleteBatch() {
await confirm($t('ui.actionMessage.deleteBatchConfirm'));
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting'),
content: $t('ui.actionMessage.deletingBatch'),
duration: 0,
key: 'action_process_msg',
});
try {
await deleteDemo03StudentList(checkedIds.value);
checkedIds.value = [];
message.success($t('ui.actionMessage.deleteSuccess'));
onRefresh();
handleRefresh();
} finally {
hideLoading();
}
@@ -85,17 +89,11 @@ const checkedIds = ref<number[]>([]);
function handleRowCheckboxChange({
records,
}: {
records: Demo03StudentApi.Demo03Grade[];
records: Demo03StudentApi.Demo03Student[];
}) {
checkedIds.value = records.map((item) => item.id!);
}
/** 导出表格 */
async function onExport() {
const data = await exportDemo03Student(await gridApi.formApi.getValues());
downloadFileFromBlobPart({ fileName: '学生.xls', source: data });
}
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: {
schema: useGridFormSchema(),
@@ -139,39 +137,37 @@ const [Grid, gridApi] = useVbenVxeGrid({
<template>
<Page auto-content-height>
<FormModal @success="onRefresh" />
<FormModal @success="handleRefresh" />
<div>
<Grid table-title="学生列表">
<template #toolbar-tools>
<Button
:icon="h(Plus)"
type="primary"
@click="onCreate"
v-access:code="['infra:demo03-student:create']"
>
{{ $t('ui.actionTitle.create', ['学生']) }}
</Button>
<Button
:icon="h(Download)"
type="primary"
class="ml-2"
@click="onExport"
v-access:code="['infra:demo03-student:export']"
>
{{ $t('ui.actionTitle.export') }}
</Button>
<Button
:icon="h(Trash2)"
type="primary"
danger
class="ml-2"
:disabled="isEmpty(checkedIds)"
@click="onDeleteBatch"
v-access:code="['infra:demo03-student:delete']"
>
批量删除
</Button>
<TableAction
:actions="[
{
label: $t('ui.actionTitle.create', ['学生']),
type: 'primary',
icon: ACTION_ICON.ADD,
auth: ['infra:demo03-student:create'],
onClick: handleCreate,
},
{
label: $t('ui.actionTitle.export'),
type: 'primary',
icon: ACTION_ICON.DOWNLOAD,
auth: ['infra:demo03-student:export'],
onClick: handleExport,
},
{
label: $t('ui.actionTitle.deleteBatch'),
type: 'primary',
danger: true,
icon: ACTION_ICON.DELETE,
disabled: isEmpty(checkedIds),
auth: ['infra:demo03-student:delete'],
onClick: handleDeleteBatch,
},
]"
/>
</template>
<template #actions="{ row }">
<TableAction
@@ -181,17 +177,17 @@ const [Grid, gridApi] = useVbenVxeGrid({
type: 'link',
icon: ACTION_ICON.EDIT,
auth: ['infra:demo03-student:update'],
onClick: onEdit.bind(null, row),
onClick: handleEdit.bind(null, row),
},
{
label: $t('common.delete'),
danger: true,
type: 'link',
danger: true,
icon: ACTION_ICON.DELETE,
auth: ['infra:demo03-student:delete'],
popConfirm: {
title: $t('ui.actionMessage.deleteConfirm', [row.id]),
confirm: onDelete.bind(null, row),
confirm: handleDelete.bind(null, row),
},
},
]"

View File

@@ -44,7 +44,6 @@ const [Modal, modalApi] = useVbenModal({
if (!valid) {
return;
}
modalApi.lock();
// 提交表单
const data = (await formApi.getValues()) as Demo03StudentApi.Demo03Course;
@@ -66,7 +65,6 @@ const [Modal, modalApi] = useVbenModal({
formData.value = undefined;
return;
}
// 加载数据
let data = modalApi.getData<Demo03StudentApi.Demo03Course>();
if (!data) {

View File

@@ -2,13 +2,12 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { Demo03StudentApi } from '#/api/infra/demo/demo03/erp';
import { h, nextTick, ref, watch } from 'vue';
import { nextTick, ref, watch } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { Plus, Trash2 } from '@vben/icons';
import { confirm, useVbenModal } from '@vben/common-ui';
import { isEmpty } from '@vben/utils';
import { Button, message } from 'ant-design-vue';
import { message } from 'ant-design-vue';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import {
@@ -34,7 +33,7 @@ const [FormModal, formModalApi] = useVbenModal({
});
/** 创建学生课程 */
function onCreate() {
function handleCreate() {
if (!props.studentId) {
message.warning('请先选择一个学生!');
return;
@@ -43,38 +42,37 @@ function onCreate() {
}
/** 编辑学生课程 */
function onEdit(row: Demo03StudentApi.Demo03Course) {
function handleEdit(row: Demo03StudentApi.Demo03Course) {
formModalApi.setData(row).open();
}
/** 删除学生课程 */
async function onDelete(row: Demo03StudentApi.Demo03Course) {
async function handleDelete(row: Demo03StudentApi.Demo03Course) {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.id]),
duration: 0,
key: 'action_process_msg',
});
try {
await deleteDemo03Course(row.id!);
message.success($t('ui.actionMessage.deleteSuccess', [row.id]));
onRefresh();
await handleRefresh();
} finally {
hideLoading();
}
}
/** 批量删除学生课程 */
async function onDeleteBatch() {
async function handleDeleteBatch() {
await confirm($t('ui.actionMessage.deleteBatchConfirm'));
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting'),
content: $t('ui.actionMessage.deletingBatch'),
duration: 0,
key: 'action_process_msg',
});
try {
await deleteDemo03CourseList(checkedIds.value);
checkedIds.value = [];
message.success($t('ui.actionMessage.deleteSuccess'));
onRefresh();
await handleRefresh();
} finally {
hideLoading();
}
@@ -130,9 +128,9 @@ const [Grid, gridApi] = useVbenVxeGrid({
});
/** 刷新表格 */
const onRefresh = async () => {
async function handleRefresh() {
await gridApi.query();
};
}
/** 监听主表的关联字段的变化,加载对应的子表数据 */
watch(
@@ -142,35 +140,36 @@ watch(
return;
}
await nextTick();
await onRefresh();
await handleRefresh();
},
{ immediate: true },
);
</script>
<template>
<FormModal @success="onRefresh" />
<FormModal @success="handleRefresh" />
<Grid table-title="学生课程列表">
<template #toolbar-tools>
<Button
:icon="h(Plus)"
type="primary"
@click="onCreate"
v-access:code="['infra:demo03-student:create']"
>
{{ $t('ui.actionTitle.create', ['学生课程']) }}
</Button>
<Button
:icon="h(Trash2)"
type="primary"
danger
class="ml-2"
:disabled="isEmpty(checkedIds)"
@click="onDeleteBatch"
v-access:code="['infra:demo03-student:delete']"
>
批量删除
</Button>
<TableAction
:actions="[
{
label: $t('ui.actionTitle.create', ['学生课程']),
type: 'primary',
icon: ACTION_ICON.ADD,
auth: ['infra:demo03-student:create'],
onClick: handleCreate,
},
{
label: $t('ui.actionTitle.deleteBatch'),
type: 'primary',
danger: true,
icon: ACTION_ICON.DELETE,
auth: ['infra:demo03-student:delete'],
disabled: isEmpty(checkedIds),
onClick: handleDeleteBatch,
},
]"
/>
</template>
<template #actions="{ row }">
<TableAction
@@ -180,17 +179,17 @@ watch(
type: 'link',
icon: ACTION_ICON.EDIT,
auth: ['infra:demo03-student:update'],
onClick: onEdit.bind(null, row),
onClick: handleEdit.bind(null, row),
},
{
label: $t('common.delete'),
danger: true,
type: 'link',
danger: true,
icon: ACTION_ICON.DELETE,
auth: ['infra:demo03-student:delete'],
popConfirm: {
title: $t('ui.actionMessage.deleteConfirm', [row.id]),
confirm: onDelete.bind(null, row),
confirm: handleDelete.bind(null, row),
},
},
]"

View File

@@ -44,7 +44,6 @@ const [Modal, modalApi] = useVbenModal({
if (!valid) {
return;
}
modalApi.lock();
// 提交表单
const data = (await formApi.getValues()) as Demo03StudentApi.Demo03Grade;
@@ -66,7 +65,6 @@ const [Modal, modalApi] = useVbenModal({
formData.value = undefined;
return;
}
// 加载数据
let data = modalApi.getData<Demo03StudentApi.Demo03Grade>();
if (!data) {

View File

@@ -2,13 +2,12 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { Demo03StudentApi } from '#/api/infra/demo/demo03/erp';
import { h, nextTick, ref, watch } from 'vue';
import { nextTick, ref, watch } from 'vue';
import { useVbenModal } from '@vben/common-ui';
import { Plus, Trash2 } from '@vben/icons';
import { confirm, useVbenModal } from '@vben/common-ui';
import { isEmpty } from '@vben/utils';
import { Button, message } from 'ant-design-vue';
import { message } from 'ant-design-vue';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import {
@@ -34,7 +33,7 @@ const [FormModal, formModalApi] = useVbenModal({
});
/** 创建学生班级 */
function onCreate() {
function handleCreate() {
if (!props.studentId) {
message.warning('请先选择一个学生!');
return;
@@ -43,38 +42,37 @@ function onCreate() {
}
/** 编辑学生班级 */
function onEdit(row: Demo03StudentApi.Demo03Grade) {
function handleEdit(row: Demo03StudentApi.Demo03Grade) {
formModalApi.setData(row).open();
}
/** 删除学生班级 */
async function onDelete(row: Demo03StudentApi.Demo03Grade) {
async function handleDelete(row: Demo03StudentApi.Demo03Grade) {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.id]),
duration: 0,
key: 'action_process_msg',
});
try {
await deleteDemo03Grade(row.id!);
message.success($t('ui.actionMessage.deleteSuccess', [row.id]));
onRefresh();
await handleRefresh();
} finally {
hideLoading();
}
}
/** 批量删除学生班级 */
async function onDeleteBatch() {
async function handleDeleteBatch() {
await confirm($t('ui.actionMessage.deleteBatchConfirm'));
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting'),
content: $t('ui.actionMessage.deletingBatch'),
duration: 0,
key: 'action_process_msg',
});
try {
await deleteDemo03GradeList(checkedIds.value);
checkedIds.value = [];
message.success($t('ui.actionMessage.deleteSuccess'));
await onRefresh();
await handleRefresh();
} finally {
hideLoading();
}
@@ -130,9 +128,9 @@ const [Grid, gridApi] = useVbenVxeGrid({
});
/** 刷新表格 */
const onRefresh = async () => {
async function handleRefresh() {
await gridApi.query();
};
}
/** 监听主表的关联字段的变化,加载对应的子表数据 */
watch(
@@ -142,35 +140,36 @@ watch(
return;
}
await nextTick();
await onRefresh();
await handleRefresh();
},
{ immediate: true },
);
</script>
<template>
<FormModal @success="onRefresh" />
<FormModal @success="handleRefresh" />
<Grid table-title="学生班级列表">
<template #toolbar-tools>
<Button
:icon="h(Plus)"
type="primary"
@click="onCreate"
v-access:code="['infra:demo03-student:create']"
>
{{ $t('ui.actionTitle.create', ['学生班级']) }}
</Button>
<Button
:icon="h(Trash2)"
type="primary"
danger
class="ml-2"
:disabled="isEmpty(checkedIds)"
@click="onDeleteBatch"
v-access:code="['infra:demo03-student:delete']"
>
批量删除
</Button>
<TableAction
:actions="[
{
label: $t('ui.actionTitle.create', ['学生班级']),
type: 'primary',
icon: ACTION_ICON.ADD,
auth: ['infra:demo03-student:create'],
onClick: handleCreate,
},
{
label: $t('ui.actionTitle.deleteBatch'),
type: 'primary',
danger: true,
icon: ACTION_ICON.DELETE,
auth: ['infra:demo03-student:delete'],
disabled: isEmpty(checkedIds),
onClick: handleDeleteBatch,
},
]"
/>
</template>
<template #actions="{ row }">
<TableAction
@@ -180,7 +179,7 @@ watch(
type: 'link',
icon: ACTION_ICON.EDIT,
auth: ['infra:demo03-student:update'],
onClick: onEdit.bind(null, row),
onClick: handleEdit.bind(null, row),
},
{
label: $t('common.delete'),
@@ -190,7 +189,7 @@ watch(
auth: ['infra:demo03-student:delete'],
popConfirm: {
title: $t('ui.actionMessage.deleteConfirm', [row.id]),
confirm: onDelete.bind(null, row),
confirm: handleDelete.bind(null, row),
},
},
]"

View File

@@ -65,21 +65,20 @@ const [Modal, modalApi] = useVbenModal({
return;
}
// 加载数据
let data = modalApi.getData<Demo03StudentApi.Demo03Student>();
if (!data) {
const data = modalApi.getData<Demo03StudentApi.Demo03Student>();
if (!data || !data.id) {
return;
}
if (data.id) {
modalApi.lock();
try {
data = await getDemo03Student(data.id);
} finally {
modalApi.unlock();
modalApi.lock();
try {
formData.value = await getDemo03Student(data.id);
// 设置到 values
if (formData.value) {
await formApi.setValues(formData.value);
}
} finally {
modalApi.unlock();
}
// 设置到 values
formData.value = data;
await formApi.setValues(formData.value);
},
});
</script>

View File

@@ -143,7 +143,6 @@ export function useGridColumns(): VxeTableGridOptions<Demo03StudentApi.Demo03Stu
formatter: 'formatDateTime',
},
{
field: 'actions',
title: '操作',
width: 280,
fixed: 'right',
@@ -170,7 +169,6 @@ export function useDemo03CourseGridEditColumns(): VxeTableGridOptions<Demo03Stud
slots: { default: 'score' },
},
{
field: 'actions',
title: '操作',
width: 280,
fixed: 'right',

View File

@@ -2,13 +2,12 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { Demo03StudentApi } from '#/api/infra/demo/demo03/inner';
import { h, ref } from 'vue';
import { ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui';
import { Download, Plus, Trash2 } from '@vben/icons';
import { confirm, Page, useVbenModal } from '@vben/common-ui';
import { downloadFileFromBlobPart, isEmpty } from '@vben/utils';
import { Button, message, Tabs } from 'ant-design-vue';
import { message, Tabs } from 'ant-design-vue';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import {
@@ -33,48 +32,53 @@ const [FormModal, formModalApi] = useVbenModal({
});
/** 刷新表格 */
function onRefresh() {
function handleRefresh() {
gridApi.reload();
}
/** 导出表格 */
async function handleExport() {
const data = await exportDemo03Student(await gridApi.formApi.getValues());
downloadFileFromBlobPart({ fileName: '学生.xls', source: data });
}
/** 创建学生 */
function onCreate() {
formModalApi.setData({}).open();
function handleCreate() {
formModalApi.setData(null).open();
}
/** 编辑学生 */
function onEdit(row: Demo03StudentApi.Demo03Student) {
function handleEdit(row: Demo03StudentApi.Demo03Student) {
formModalApi.setData(row).open();
}
/** 删除学生 */
async function onDelete(row: Demo03StudentApi.Demo03Student) {
async function handleDelete(row: Demo03StudentApi.Demo03Student) {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.id]),
duration: 0,
key: 'action_process_msg',
});
try {
await deleteDemo03Student(row.id!);
message.success($t('ui.actionMessage.deleteSuccess', [row.id]));
onRefresh();
handleRefresh();
} finally {
hideLoading();
}
}
/** 批量删除学生 */
async function onDeleteBatch() {
async function handleDeleteBatch() {
await confirm($t('ui.actionMessage.deleteBatchConfirm'));
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting'),
content: $t('ui.actionMessage.deletingBatch'),
duration: 0,
key: 'action_process_msg',
});
try {
await deleteDemo03StudentList(checkedIds.value);
checkedIds.value = [];
message.success($t('ui.actionMessage.deleteSuccess'));
onRefresh();
handleRefresh();
} finally {
hideLoading();
}
@@ -89,12 +93,6 @@ function handleRowCheckboxChange({
checkedIds.value = records.map((item) => item.id!);
}
/** 导出表格 */
async function onExport() {
const data = await exportDemo03Student(await gridApi.formApi.getValues());
downloadFileFromBlobPart({ fileName: '学生.xls', source: data });
}
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: {
schema: useGridFormSchema(),
@@ -134,8 +132,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
<template>
<Page auto-content-height>
<FormModal @success="onRefresh" />
<FormModal @success="handleRefresh" />
<Grid table-title="学生列表">
<template #expand_content="{ row }">
<!-- 子表的表单 -->
@@ -149,34 +146,33 @@ const [Grid, gridApi] = useVbenVxeGrid({
</Tabs>
</template>
<template #toolbar-tools>
<Button
:icon="h(Plus)"
type="primary"
@click="onCreate"
v-access:code="['infra:demo03-student:create']"
>
{{ $t('ui.actionTitle.create', ['学生']) }}
</Button>
<Button
:icon="h(Download)"
type="primary"
class="ml-2"
@click="onExport"
v-access:code="['infra:demo03-student:export']"
>
{{ $t('ui.actionTitle.export') }}
</Button>
<Button
:icon="h(Trash2)"
type="primary"
danger
class="ml-2"
:disabled="isEmpty(checkedIds)"
@click="onDeleteBatch"
v-access:code="['infra:demo03-student:delete']"
>
批量删除
</Button>
<TableAction
:actions="[
{
label: $t('ui.actionTitle.create', ['学生']),
type: 'primary',
icon: ACTION_ICON.ADD,
auth: ['infra:demo03-student:create'],
onClick: handleCreate,
},
{
label: $t('ui.actionTitle.export'),
type: 'primary',
icon: ACTION_ICON.DOWNLOAD,
auth: ['infra:demo03-student:export'],
onClick: handleExport,
},
{
label: $t('ui.actionTitle.deleteBatch'),
type: 'primary',
danger: true,
icon: ACTION_ICON.DELETE,
auth: ['infra:demo03-student:delete'],
disabled: isEmpty(checkedIds),
onClick: handleDeleteBatch,
},
]"
/>
</template>
<template #actions="{ row }">
<TableAction
@@ -186,7 +182,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
type: 'link',
icon: ACTION_ICON.EDIT,
auth: ['infra:demo03-student:update'],
onClick: onEdit.bind(null, row),
onClick: handleEdit.bind(null, row),
},
{
label: $t('common.delete'),
@@ -196,7 +192,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
auth: ['infra:demo03-student:delete'],
popConfirm: {
title: $t('ui.actionMessage.deleteConfirm', [row.id]),
confirm: onDelete.bind(null, row),
confirm: handleDelete.bind(null, row),
},
},
]"

View File

@@ -26,6 +26,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
keepSource: true,
rowConfig: {
keyField: 'id',
isHover: true,
},
pagerConfig: {
enabled: false,
@@ -37,14 +38,14 @@ const [Grid, gridApi] = useVbenVxeGrid({
});
/** 添加学生课程 */
const onAdd = async () => {
async function handleAdd() {
await gridApi.grid.insertAt({} as Demo03StudentApi.Demo03Course, -1);
};
}
/** 删除学生课程 */
const onDelete = async (row: Demo03StudentApi.Demo03Course) => {
async function handleDelete(row: Demo03StudentApi.Demo03Course) {
await gridApi.grid.remove(row);
};
}
/** 提供获取表格数据的方法供父组件调用 */
defineExpose({
@@ -98,7 +99,7 @@ watch(
auth: ['infra:demo03-student:delete'],
popConfirm: {
title: $t('ui.actionMessage.deleteConfirm', [row.id]),
confirm: onDelete.bind(null, row),
confirm: handleDelete.bind(null, row),
},
},
]"
@@ -110,7 +111,7 @@ watch(
:icon="h(Plus)"
type="primary"
ghost
@click="onAdd"
@click="handleAdd"
v-access:code="['infra:demo03-student:create']"
>
{{ $t('ui.actionTitle.create', ['学生课程']) }}

View File

@@ -31,7 +31,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
});
/** 刷新表格 */
async function onRefresh() {
async function handleRefresh() {
await gridApi.grid.loadData(
await getDemo03CourseListByStudentId(props.studentId!),
);
@@ -45,7 +45,7 @@ watch(
return;
}
await nextTick();
await onRefresh();
await handleRefresh();
},
{ immediate: true },
);

View File

@@ -31,7 +31,7 @@ const [Grid, gridApi] = useVbenVxeGrid({
});
/** 刷新表格 */
async function onRefresh() {
async function handleRefresh() {
await gridApi.grid.loadData([
await getDemo03GradeByStudentId(props.studentId!),
]);
@@ -45,7 +45,7 @@ watch(
return;
}
await nextTick();
await onRefresh();
await handleRefresh();
},
{ immediate: true },
);

View File

@@ -108,14 +108,17 @@ export function useGridColumns(): VxeTableGridOptions<Demo03StudentApi.Demo03Stu
{
field: 'id',
title: '编号',
minWidth: 120,
},
{
field: 'name',
title: '名字',
minWidth: 120,
},
{
field: 'sex',
title: '性别',
minWidth: 120,
cellRender: {
name: 'CellDict',
props: { type: DICT_TYPE.SYSTEM_USER_SEX },
@@ -124,21 +127,23 @@ export function useGridColumns(): VxeTableGridOptions<Demo03StudentApi.Demo03Stu
{
field: 'birthday',
title: '出生日期',
minWidth: 120,
formatter: 'formatDateTime',
},
{
field: 'description',
title: '简介',
minWidth: 120,
},
{
field: 'createTime',
title: '创建时间',
minWidth: 120,
formatter: 'formatDateTime',
},
{
field: 'actions',
title: '操作',
width: 280,
width: 200,
fixed: 'right',
slots: { default: 'actions' },
},
@@ -153,17 +158,18 @@ export function useDemo03CourseGridEditColumns(): VxeTableGridOptions<Demo03Stud
{
field: 'name',
title: '名字',
minWidth: 120,
slots: { default: 'name' },
},
{
field: 'score',
title: '分数',
minWidth: 120,
slots: { default: 'score' },
},
{
field: 'actions',
title: '操作',
width: 280,
width: 200,
fixed: 'right',
slots: { default: 'actions' },
},

View File

@@ -2,13 +2,12 @@
import type { VxeTableGridOptions } from '#/adapter/vxe-table';
import type { Demo03StudentApi } from '#/api/infra/demo/demo03/normal';
import { h, ref } from 'vue';
import { ref } from 'vue';
import { Page, useVbenModal } from '@vben/common-ui';
import { Download, Plus, Trash2 } from '@vben/icons';
import { confirm, Page, useVbenModal } from '@vben/common-ui';
import { downloadFileFromBlobPart, isEmpty } from '@vben/utils';
import { Button, message } from 'ant-design-vue';
import { message } from 'ant-design-vue';
import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
import {
@@ -28,31 +27,53 @@ const [FormModal, formModalApi] = useVbenModal({
});
/** 刷新表格 */
function onRefresh() {
function handleRefresh() {
gridApi.query();
}
/** 导出表格 */
async function handleExport() {
const data = await exportDemo03Student(await gridApi.formApi.getValues());
downloadFileFromBlobPart({ fileName: '学生.xls', source: data });
}
/** 创建学生 */
function onCreate() {
formModalApi.setData({}).open();
function handleCreate() {
formModalApi.setData(null).open();
}
/** 编辑学生 */
function onEdit(row: Demo03StudentApi.Demo03Student) {
function handleEdit(row: Demo03StudentApi.Demo03Student) {
formModalApi.setData(row).open();
}
/** 删除学生 */
async function onDelete(row: Demo03StudentApi.Demo03Student) {
async function handleDelete(row: Demo03StudentApi.Demo03Student) {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting', [row.id]),
duration: 0,
key: 'action_process_msg',
});
try {
await deleteDemo03Student(row.id!);
message.success($t('ui.actionMessage.deleteSuccess', [row.id]));
onRefresh();
handleRefresh();
} finally {
hideLoading();
}
}
/** 批量删除学生 */
async function handleDeleteBatch() {
await confirm($t('ui.actionMessage.deleteBatchConfirm'));
const hideLoading = message.loading({
content: $t('ui.actionMessage.deletingBatch'),
duration: 0,
});
try {
await deleteDemo03StudentList(checkedIds.value);
checkedIds.value = [];
message.success($t('ui.actionMessage.deleteSuccess'));
handleRefresh();
} finally {
hideLoading();
}
@@ -66,28 +87,6 @@ function handleRowCheckboxChange({
}) {
checkedIds.value = records.map((item) => item.id!);
}
/** 批量删除学生 */
async function onDeleteBatch() {
const hideLoading = message.loading({
content: $t('ui.actionMessage.deleting'),
duration: 0,
key: 'action_process_msg',
});
try {
await deleteDemo03StudentList(checkedIds.value);
checkedIds.value = [];
message.success($t('ui.actionMessage.deleteSuccess'));
onRefresh();
} finally {
hideLoading();
}
}
/** 导出表格 */
async function onExport() {
const data = await exportDemo03Student(await gridApi.formApi.getValues());
downloadFileFromBlobPart({ fileName: '学生.xls', source: data });
}
const [Grid, gridApi] = useVbenVxeGrid({
formOptions: {
@@ -128,38 +127,36 @@ const [Grid, gridApi] = useVbenVxeGrid({
<template>
<Page auto-content-height>
<FormModal @success="onRefresh" />
<FormModal @success="handleRefresh" />
<Grid table-title="学生列表">
<template #toolbar-tools>
<Button
:icon="h(Plus)"
type="primary"
@click="onCreate"
v-access:code="['infra:demo03-student:create']"
>
{{ $t('ui.actionTitle.create', ['学生']) }}
</Button>
<Button
:icon="h(Download)"
type="primary"
class="ml-2"
@click="onExport"
v-access:code="['infra:demo03-student:export']"
>
{{ $t('ui.actionTitle.export') }}
</Button>
<Button
:icon="h(Trash2)"
type="primary"
danger
class="ml-2"
:disabled="isEmpty(checkedIds)"
@click="onDeleteBatch"
v-access:code="['infra:demo03-student:delete']"
>
批量删除
</Button>
<TableAction
:actions="[
{
label: $t('ui.actionTitle.create', ['学生']),
type: 'primary',
icon: ACTION_ICON.ADD,
auth: ['infra:demo03-student:create'],
onClick: handleCreate,
},
{
label: $t('ui.actionTitle.export'),
type: 'primary',
icon: ACTION_ICON.DOWNLOAD,
auth: ['infra:demo03-student:export'],
onClick: handleExport,
},
{
label: $t('ui.actionTitle.deleteBatch'),
type: 'primary',
danger: true,
icon: ACTION_ICON.DELETE,
disabled: isEmpty(checkedIds),
auth: ['infra:demo03-student:delete'],
onClick: handleDeleteBatch,
},
]"
/>
</template>
<template #actions="{ row }">
<TableAction
@@ -169,17 +166,17 @@ const [Grid, gridApi] = useVbenVxeGrid({
type: 'link',
icon: ACTION_ICON.EDIT,
auth: ['infra:demo03-student:update'],
onClick: onEdit.bind(null, row),
onClick: handleEdit.bind(null, row),
},
{
label: $t('common.delete'),
danger: true,
type: 'link',
danger: true,
icon: ACTION_ICON.DELETE,
auth: ['infra:demo03-student:delete'],
popConfirm: {
title: $t('ui.actionMessage.deleteConfirm', [row.id]),
confirm: onDelete.bind(null, row),
confirm: handleDelete.bind(null, row),
},
},
]"

View File

@@ -37,14 +37,14 @@ const [Grid, gridApi] = useVbenVxeGrid({
});
/** 添加学生课程 */
const onAdd = async () => {
async function handleAdd() {
await gridApi.grid.insertAt({} as Demo03StudentApi.Demo03Course, -1);
};
}
/** 删除学生课程 */
const onDelete = async (row: Demo03StudentApi.Demo03Course) => {
async function handleDelete(row: Demo03StudentApi.Demo03Course) {
await gridApi.grid.remove(row);
};
}
/** 提供获取表格数据的方法供父组件调用 */
defineExpose({
@@ -98,7 +98,7 @@ watch(
auth: ['infra:demo03-student:delete'],
popConfirm: {
title: $t('ui.actionMessage.deleteConfirm', [row.id]),
confirm: onDelete.bind(null, row),
confirm: handleDelete.bind(null, row),
},
},
]"
@@ -110,7 +110,7 @@ watch(
:icon="h(Plus)"
type="primary"
ghost
@click="onAdd"
@click="handleAdd"
v-access:code="['infra:demo03-student:create']"
>
{{ $t('ui.actionTitle.create', ['学生课程']) }}

View File

@@ -87,7 +87,7 @@ const [FormModal, formModalApi] = useVbenModal({
/** 创建示例联系人 */
function handleCreate() {
formModalApi.setData({}).open();
formModalApi.setData(null).open();
}
/** 编辑示例联系人 */

View File

@@ -69,7 +69,7 @@ const [FormModal, formModalApi] = useVbenModal({
/** 创建示例分类 */
function onCreate() {
formModalApi.setData({}).open();
formModalApi.setData(null).open();
}
/** 编辑示例分类 */

View File

@@ -98,7 +98,7 @@ const [FormModal, formModalApi] = useVbenModal({
/** 创建学生 */
function onCreate() {
formModalApi.setData({}).open();
formModalApi.setData(null).open();
}
/** 编辑学生 */

View File

@@ -94,7 +94,7 @@ const [FormModal, formModalApi] = useVbenModal({
/** 创建学生 */
function onCreate() {
formModalApi.setData({}).open();
formModalApi.setData(null).open();
}
/** 编辑学生 */

View File

@@ -88,7 +88,7 @@ const [FormModal, formModalApi] = useVbenModal({
/** 创建学生 */
function onCreate() {
formModalApi.setData({}).open();
formModalApi.setData(null).open();
}
/** 编辑学生 */

View File

@@ -50,7 +50,7 @@ async function handleMaster(row: InfraFileConfigApi.FileConfig) {
});
try {
await updateFileConfigMaster(row.id!);
message.success($t('ui.actionMessage.updateSuccess', [row.name]));
message.success($t('ui.actionMessage.updateSuccess'));
handleRefresh();
} finally {
hideLoading();

View File

@@ -28,7 +28,7 @@ function onRefresh() {
/** 创建分类 */
function handleCreate() {
formModalApi.setData({}).open();
formModalApi.setData(null).open();
}
/** 添加下级分类 */

View File

@@ -454,7 +454,6 @@ export function channelSchema(formType: string): VbenFormSchema[] {
accept: ['pem'],
},
}),
rules: 'required',
dependencies: {
show(values) {
return values?.config?.apiVersion === 'v3';

View File

@@ -150,7 +150,7 @@ export function useTypeGridColumns(): VxeTableGridOptions['columns'] {
// ============================== 字典数据 ==============================
// TODO @芋艿:后续针对 antd增加
// TODO @AIOTAGRO:后续针对 antd增加
/**
* 颜色选项
*/

View File

@@ -29,7 +29,7 @@ function handleRefresh() {
/** 创建菜单 */
function handleCreate() {
formModalApi.setData({}).open();
formModalApi.setData(null).open();
}
/** 添加下级菜单 */

View File

@@ -125,7 +125,7 @@ export function useDetailSchema(): DescriptionItemSchema[] {
{
field: 'avatar',
label: '用户头像',
// TODO @芋艿:使用 antd 的 Image 组件
// TODO @AIOTAGRO:使用 antd 的 Image 组件
content: (data: SystemSocialUserApi.SocialUser) => {
if (data?.avatar) {
return h('img', {

View File

@@ -10,7 +10,7 @@ export default defineConfig(async () => {
changeOrigin: true,
rewrite: (path) => path.replace(/^\/admin-api/, ''),
// mock代理目标地址
target: 'http://localhost:48080/admin-api',
target: 'http://111.3.47.177:48080/admin-api',
ws: true,
},
},

Some files were not shown because too many files have changed in this diff Show More