diff --git a/README.md b/README.md index 9fedc308a..26ef03fca 100644 --- a/README.md +++ b/README.md @@ -88,27 +88,27 @@ ### 系统功能 -| | 功能 | 描述 | -| --- | --- | --- | -| | 用户管理 | 用户是系统操作者,该功能主要完成系统用户配置 | -| ⭐️ | 在线用户 | 当前系统中活跃用户状态监控,支持手动踢下线 | -| | 角色管理 | 角色菜单权限分配、设置角色按机构进行数据范围权限划分 | -| | 菜单管理 | 配置系统菜单、操作权限、按钮权限标识等,本地缓存提供性能 | -| | 部门管理 | 配置系统组织机构(公司、部门、小组),树结构展现支持数据权限 | -| | 岗位管理 | 配置系统用户所属担任职务 | -| 🚀 | 租户管理 | 配置系统租户,支持 SaaS 场景下的多租户功能 | -| 🚀 | 租户套餐 | 配置租户套餐,自定每个租户的菜单、操作、按钮的权限 | -| | 字典管理 | 对系统中经常使用的一些较为固定的数据进行维护 | -| 🚀 | 短信管理 | 短信渠道、短息模板、短信日志,对接阿里云、腾讯云等主流短信平台 | -| 🚀 | 邮件管理 | 邮箱账号、邮件模版、邮件发送日志,支持所有邮件平台 | -| 🚀 | 站内信 | 系统内的消息通知,提供站内信模版、站内信消息 | -| 🚀 | 操作日志 | 系统正常操作日志记录和查询,集成 Swagger 生成日志内容 | -| ⭐️ | 登录日志 | 系统登录日志记录查询,包含登录异常 | -| 🚀 | 错误码管理 | 系统所有错误码的管理,可在线修改错误提示,无需重启服务 | -| | 通知公告 | 系统通知公告信息发布维护 | -| 🚀 | 敏感词 | 配置系统敏感词,支持标签分组 | -| 🚀 | 应用管理 | 管理 SSO 单点登录的应用,支持多种 OAuth2 授权方式 | -| 🚀 | 地区管理 | 展示省份、城市、区镇等城市信息,支持 IP 对应城市 | +| | 功能 | 描述 | +|----|-------|---------------------------------| +| | 用户管理 | 用户是系统操作者,该功能主要完成系统用户配置 | +| ⭐️ | 在线用户 | 当前系统中活跃用户状态监控,支持手动踢下线 | +| | 角色管理 | 角色菜单权限分配、设置角色按机构进行数据范围权限划分 | +| | 菜单管理 | 配置系统菜单、操作权限、按钮权限标识等,本地缓存提供性能 | +| | 部门管理 | 配置系统组织机构(公司、部门、小组),树结构展现支持数据权限 | +| | 岗位管理 | 配置系统用户所属担任职务 | +| 🚀 | 租户管理 | 配置系统租户,支持 SaaS 场景下的多租户功能 | +| 🚀 | 租户套餐 | 配置租户套餐,自定每个租户的菜单、操作、按钮的权限 | +| | 字典管理 | 对系统中经常使用的一些较为固定的数据进行维护 | +| 🚀 | 短信管理 | 短信渠道、短息模板、短信日志,对接阿里云、腾讯云等主流短信平台 | +| 🚀 | 邮件管理 | 邮箱账号、邮件模版、邮件发送日志,支持所有邮件平台 | +| 🚀 | 站内信 | 系统内的消息通知,提供站内信模版、站内信消息 | +| 🚀 | 操作日志 | 系统正常操作日志记录和查询,集成 Swagger 生成日志内容 | +| ⭐️ | 登录日志 | 系统登录日志记录查询,包含登录异常 | +| 🚀 | 错误码管理 | 系统所有错误码的管理,可在线修改错误提示,无需重启服务 | +| | 通知公告 | 系统通知公告信息发布维护 | +| 🚀 | 敏感词 | 配置系统敏感词,支持标签分组 | +| 🚀 | 应用管理 | 管理 SSO 单点登录的应用,支持多种 OAuth2 授权方式 | +| 🚀 | 地区管理 | 展示省份、城市、区镇等城市信息,支持 IP 对应城市 | ![功能图](/.gitee/image/common/system-feature.png) @@ -126,32 +126,32 @@ > > 前者支持轻量配置简单流程,后者实现复杂场景深度编排 -| 功能列表 | 功能描述 | 是否完成 | -| --- | --- | --- | -| SIMPLE 设计器 | 仿钉钉/飞书设计器,支持拖拽搭建表单流程,10 分钟快速完成审批流程配置 | ✅ | -| BPMN 设计器 | 基于 BPMN 标准开发,适配复杂业务场景,满足多层级审批及流程自动化需求 | ✅ | -| 会签 | 同一个审批节点设置多个人(如 A、B、C 三人,三人会同时收到待办任务),需全部同意之后,审批才可到下一审批节点 | ✅ | -| 或签 | 同一个审批节点设置多个人,任意一个人处理后,就能进入下一个节点 | ✅ | -| 依次审批 | (顺序会签)同一个审批节点设置多个人(如 A、B、C 三人),三人按顺序依次收到待办,即 A 先审批,A 提交后 B 才能审批,需全部同意之后,审批才可到下一审批节点 | ✅ | -| 抄送 | 将审批结果通知给抄送人,同一个审批默认排重,不重复抄送给同一人 | ✅ | -| 驳回 | (退回)将审批重置发送给某节点,重新审批。可驳回至发起人、上一节点、任意节点 | ✅ | -| 转办 | A 转给其 B 审批,B 审批后,进入下一节点 | ✅ | -| 委派 | A 转给其 B 审批,B 审批后,转给 A,A 继续审批后进入下一节点 | ✅ | -| 加签 | 允许当前审批人根据需要,自行增加当前节点的审批人,支持向前、向后加签 | ✅ | -| 减签 | (取消加签)在当前审批人操作之前,减少审批人 | ✅ | -| 撤销 | (取消流程)流程发起人,可以对流程进行撤销处理 | ✅ | -| 终止 | 系统管理员,在任意节点终止流程实例 | ✅ | -| 表单权限 | 支持拖拉拽配置表单,每个审批节点可配置只读、编辑、隐藏权限 | ✅ | -| 超时审批 | 配置超时审批时间,超时后自动触发审批通过、不通过、驳回等操作 | ✅ | -| 自动提醒 | 配置提醒时间,到达时间后自动触发短信、邮箱、站内信等通知提醒,支持自定义重复提醒频次 | ✅ | -| 父子流程 | 主流程设置子流程节点,子流程节点会自动触发子流程。子流程结束后,主流程才会执行(继续往下下执行),支持同步子流程、异步子流程 | ✅ | -| 条件分支 | (排它分支)用于在流程中实现决策,即根据条件选择一个分支执行 | ✅ | -| 并行分支 | 允许将流程分成多条分支,不进行条件判断,所有分支都会执行 | ✅ | -| 包容分支 | (条件分支 + 并行分支的结合体)允许基于条件选择多条分支执行,但如果没有任何一个分支满足条件,则可以选择默认分支 | ✅ | -| 路由分支 | 根据条件选择一个分支执行(重定向到指定配置节点),也可以选择默认分支执行(继续往下执行) | ✅ | -| 触发节点 | 执行到该节点,触发 HTTP 请求、HTTP 回调、更新数据、删除数据等 | ✅ | -| 延迟节点 | 执行到该节点,审批等待一段时间再执行,支持固定时长、固定日期等 | ✅ | -| 拓展设置 | 流程前置/后置通知,节点(任务)前置、后置通知,流程报表,自动审批去重,自定流程编号、标题、摘要,流程报表等 | ✅ | +| 功能列表 | 功能描述 | 是否完成 | +|------------|-------------------------------------------------------------------------------------|------| +| SIMPLE 设计器 | 仿钉钉/飞书设计器,支持拖拽搭建表单流程,10 分钟快速完成审批流程配置 | ✅ | +| BPMN 设计器 | 基于 BPMN 标准开发,适配复杂业务场景,满足多层级审批及流程自动化需求 | ✅ | +| 会签 | 同一个审批节点设置多个人(如 A、B、C 三人,三人会同时收到待办任务),需全部同意之后,审批才可到下一审批节点 | ✅ | +| 或签 | 同一个审批节点设置多个人,任意一个人处理后,就能进入下一个节点 | ✅ | +| 依次审批 | (顺序会签)同一个审批节点设置多个人(如 A、B、C 三人),三人按顺序依次收到待办,即 A 先审批,A 提交后 B 才能审批,需全部同意之后,审批才可到下一审批节点 | ✅ | +| 抄送 | 将审批结果通知给抄送人,同一个审批默认排重,不重复抄送给同一人 | ✅ | +| 驳回 | (退回)将审批重置发送给某节点,重新审批。可驳回至发起人、上一节点、任意节点 | ✅ | +| 转办 | A 转给其 B 审批,B 审批后,进入下一节点 | ✅ | +| 委派 | A 转给其 B 审批,B 审批后,转给 A,A 继续审批后进入下一节点 | ✅ | +| 加签 | 允许当前审批人根据需要,自行增加当前节点的审批人,支持向前、向后加签 | ✅ | +| 减签 | (取消加签)在当前审批人操作之前,减少审批人 | ✅ | +| 撤销 | (取消流程)流程发起人,可以对流程进行撤销处理 | ✅ | +| 终止 | 系统管理员,在任意节点终止流程实例 | ✅ | +| 表单权限 | 支持拖拉拽配置表单,每个审批节点可配置只读、编辑、隐藏权限 | ✅ | +| 超时审批 | 配置超时审批时间,超时后自动触发审批通过、不通过、驳回等操作 | ✅ | +| 自动提醒 | 配置提醒时间,到达时间后自动触发短信、邮箱、站内信等通知提醒,支持自定义重复提醒频次 | ✅ | +| 父子流程 | 主流程设置子流程节点,子流程节点会自动触发子流程。子流程结束后,主流程才会执行(继续往下下执行),支持同步子流程、异步子流程 | ✅ | +| 条件分支 | (排它分支)用于在流程中实现决策,即根据条件选择一个分支执行 | ✅ | +| 并行分支 | 允许将流程分成多条分支,不进行条件判断,所有分支都会执行 | ✅ | +| 包容分支 | (条件分支 + 并行分支的结合体)允许基于条件选择多条分支执行,但如果没有任何一个分支满足条件,则可以选择默认分支 | ✅ | +| 路由分支 | 根据条件选择一个分支执行(重定向到指定配置节点),也可以选择默认分支执行(继续往下执行) | ✅ | +| 触发节点 | 执行到该节点,触发 HTTP 请求、HTTP 回调、更新数据、删除数据等 | ✅ | +| 延迟节点 | 执行到该节点,审批等待一段时间再执行,支持固定时长、固定日期等 | ✅ | +| 拓展设置 | 流程前置/后置通知,节点(任务)前置、后置通知,流程报表,自动审批去重,自定流程编号、标题、摘要,流程报表等 | ✅ | ### 支付系统 @@ -165,26 +165,26 @@ ### 基础设施 -| | 功能 | 描述 | -| --- | --- | --- | -| 🚀 | 代码生成 | 前后端代码的生成(Java、Vue、SQL、单元测试),支持 CRUD 下载 | -| 🚀 | 系统接口 | 基于 Swagger 自动生成相关的 RESTful API 接口文档 | -| 🚀 | 数据库文档 | 基于 Screw 自动生成数据库文档,支持导出 Word、HTML、MD 格式 | -| | 表单构建 | 拖动表单元素生成相应的 HTML 代码,支持导出 JSON、Vue 文件 | -| 🚀 | 配置管理 | 对系统动态配置常用参数,支持 SpringBoot 加载 | -| ⭐️ | 定时任务 | 在线(添加、修改、删除)任务调度包含执行结果日志 | -| 🚀 | 文件服务 | 支持将文件存储到 S3(MinIO、阿里云、腾讯云、七牛云)、本地、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 实现单元测试,保证功能的正确性、代码的质量等 | +| | 功能 | 描述 | +|----|-----------|----------------------------------------------| +| 🚀 | 代码生成 | 前后端代码的生成(Java、Vue、SQL、单元测试),支持 CRUD 下载 | +| 🚀 | 系统接口 | 基于 Swagger 自动生成相关的 RESTful API 接口文档 | +| 🚀 | 数据库文档 | 基于 Screw 自动生成数据库文档,支持导出 Word、HTML、MD 格式 | +| | 表单构建 | 拖动表单元素生成相应的 HTML 代码,支持导出 JSON、Vue 文件 | +| 🚀 | 配置管理 | 对系统动态配置常用参数,支持 SpringBoot 加载 | +| ⭐️ | 定时任务 | 在线(添加、修改、删除)任务调度包含执行结果日志 | +| 🚀 | 文件服务 | 支持将文件存储到 S3(MinIO、阿里云、腾讯云、七牛云)、本地、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 实现单元测试,保证功能的正确性、代码的质量等 | ![功能图](/.gitee/image/common/infra-feature.png) @@ -197,18 +197,19 @@ ### 微信公众号 -| | 功能 | 描述 | -| --- | --- | --- | -| 🚀 | 账号管理 | 配置接入的微信公众号,可支持多个公众号 | -| 🚀 | 数据统计 | 统计公众号的用户增减、累计用户、消息概况、接口分析等数据 | -| 🚀 | 粉丝管理 | 查看已关注、取关的粉丝列表,可对粉丝进行同步、打标签等操作 | -| 🚀 | 消息管理 | 查看粉丝发送的消息列表,可主动回复粉丝消息 | -| 🚀 | 自动回复 | 自动回复粉丝发送的消息,支持关注回复、消息回复、关键字回复 | -| 🚀 | 标签管理 | 对公众号的标签进行创建、查询、修改、删除等操作 | -| 🚀 | 菜单管理 | 自定义公众号的菜单,也可以从公众号同步菜单 | -| 🚀 | 素材管理 | 管理公众号的图片、语音、视频等素材,支持在线播放语音、视频 | -| 🚀 | 图文草稿箱 | 新增常用的图文素材到草稿箱,可发布到公众号 | -| 🚀 | 图文发表记录 | 查看已发布成功的图文素材,支持删除操作 | +| | 功能 | 描述 | +|----|--------|-------------------------------| +| 🚀 | 账号管理 | 配置接入的微信公众号,可支持多个公众号 | +| 🚀 | 数据统计 | 统计公众号的用户增减、累计用户、消息概况、接口分析等数据 | +| 🚀 | 粉丝管理 | 查看已关注、取关的粉丝列表,可对粉丝进行同步、打标签等操作 | +| 🚀 | 消息管理 | 查看粉丝发送的消息列表,可主动回复粉丝消息 | +| 🚀 | 模版消息 | 配置和发送模版消息,用于向粉丝推送通知类消息 | +| 🚀 | 自动回复 | 自动回复粉丝发送的消息,支持关注回复、消息回复、关键字回复 | +| 🚀 | 标签管理 | 对公众号的标签进行创建、查询、修改、删除等操作 | +| 🚀 | 菜单管理 | 自定义公众号的菜单,也可以从公众号同步菜单 | +| 🚀 | 素材管理 | 管理公众号的图片、语音、视频等素材,支持在线播放语音、视频 | +| 🚀 | 图文草稿箱 | 新增常用的图文素材到草稿箱,可发布到公众号 | +| 🚀 | 图文发表记录 | 查看已发布成功的图文素材,支持删除操作 | ### 商城系统 diff --git a/apps/web-antd/src/api/mall/product/spu/index.ts b/apps/web-antd/src/api/mall/product/spu/index.ts index 4b37151c6..0c9f4ce94 100644 --- a/apps/web-antd/src/api/mall/product/spu/index.ts +++ b/apps/web-antd/src/api/mall/product/spu/index.ts @@ -62,13 +62,6 @@ export namespace MallSpuApi { valueName?: string; // 属性值名称 } - // TODO @puhui999:这个还要么? - /** 优惠券模板 */ - export interface GiveCouponTemplate { - id?: number; // 优惠券编号 - name?: string; // 优惠券名称 - } - /** 商品状态更新请求 */ export interface SpuStatusUpdateReqVO { id: number; // 商品编号 diff --git a/apps/web-antd/src/api/mall/promotion/bargain/bargainActivity.ts b/apps/web-antd/src/api/mall/promotion/bargain/bargainActivity.ts index 97917ec2d..f13b64916 100644 --- a/apps/web-antd/src/api/mall/promotion/bargain/bargainActivity.ts +++ b/apps/web-antd/src/api/mall/promotion/bargain/bargainActivity.ts @@ -1,7 +1,5 @@ import type { PageParam, PageResult } from '@vben/request'; -import type { MallSpuApi } from '#/api/mall/product/spu'; - import { requestClient } from '#/api/request'; export namespace MallBargainActivityApi { @@ -32,17 +30,6 @@ export namespace MallBargainActivityApi { bargainMinPrice: number; // 砍价底价 stock: number; // 活动库存 } - - // TODO @puhui999:要不要删除? - /** 扩展 SKU 配置 */ - export type SkuExtension = { - productConfig: BargainProduct; // 砍价活动配置 - } & MallSpuApi.Sku; - - /** 扩展 SPU 配置 */ - export interface SpuExtension extends MallSpuApi.Spu { - skus: SkuExtension[]; // SKU 列表 - } } /** 查询砍价活动列表 */ diff --git a/apps/web-antd/src/api/mall/promotion/combination/combinationActivity.ts b/apps/web-antd/src/api/mall/promotion/combination/combinationActivity.ts index 00471bd65..e497ae204 100644 --- a/apps/web-antd/src/api/mall/promotion/combination/combinationActivity.ts +++ b/apps/web-antd/src/api/mall/promotion/combination/combinationActivity.ts @@ -1,7 +1,5 @@ import type { PageParam, PageResult } from '@vben/request'; -import type { MallSpuApi } from '#/api/mall/product/spu'; - import { requestClient } from '#/api/request'; export namespace MallCombinationActivityApi { @@ -25,23 +23,12 @@ export namespace MallCombinationActivityApi { products: CombinationProduct[]; // 商品列表 } - // TODO @puhui999:要不要删除? /** 拼团活动所需属性 */ export interface CombinationProduct { spuId: number; // 商品 SPU 编号 skuId: number; // 商品 SKU 编号 combinationPrice: number; // 拼团价格 } - - /** 扩展 SKU 配置 */ - export type SkuExtension = { - productConfig: CombinationProduct; // 拼团活动配置 - } & MallSpuApi.Sku; - - /** 扩展 SPU 配置 */ - export interface SpuExtension extends MallSpuApi.Spu { - skus: SkuExtension[]; // SKU 列表 - } } /** 查询拼团活动列表 */ diff --git a/apps/web-antd/src/api/mall/promotion/discount/discountActivity.ts b/apps/web-antd/src/api/mall/promotion/discount/discountActivity.ts index 3beaa1aef..1236140f4 100644 --- a/apps/web-antd/src/api/mall/promotion/discount/discountActivity.ts +++ b/apps/web-antd/src/api/mall/promotion/discount/discountActivity.ts @@ -1,7 +1,5 @@ import type { PageParam, PageResult } from '@vben/request'; -import type { MallSpuApi } from '#/api/mall/product/spu'; - import { requestClient } from '#/api/request'; export namespace MallDiscountActivityApi { @@ -25,17 +23,6 @@ export namespace MallDiscountActivityApi { endTime?: Date; // 结束时间 products?: DiscountProduct[]; // 商品列表 } - - // TODO @puhui999:要不要删除? - /** 扩展 SKU 配置 */ - export type SkuExtension = { - productConfig: DiscountProduct; // 限时折扣配置 - } & MallSpuApi.Sku; - - /** 扩展 SPU 配置 */ - export interface SpuExtension extends MallSpuApi.Spu { - skus: SkuExtension[]; // SKU 列表 - } } /** 查询限时折扣活动列表 */ diff --git a/apps/web-antd/src/api/mall/promotion/point/index.ts b/apps/web-antd/src/api/mall/promotion/point/index.ts index 99f39bced..3cf24a4b2 100644 --- a/apps/web-antd/src/api/mall/promotion/point/index.ts +++ b/apps/web-antd/src/api/mall/promotion/point/index.ts @@ -36,17 +36,6 @@ export namespace MallPointActivityApi { price: number; // 兑换金额,单位:分 } - // TODO @puhui999:这些还需要么? - /** 扩展 SKU 配置 */ - export type SkuExtension = { - productConfig: PointProduct; // 积分商城商品配置 - } & MallSpuApi.Sku; - - /** 扩展 SPU 配置 */ - export interface SpuExtension extends MallSpuApi.Spu { - skus: SkuExtension[]; // SKU 列表 - } - /** 扩展 SPU 配置(带积分信息) */ export interface SpuExtensionWithPoint extends MallSpuApi.Spu { pointStock: number; // 积分商城活动库存 diff --git a/apps/web-antd/src/api/mall/promotion/seckill/seckillActivity.ts b/apps/web-antd/src/api/mall/promotion/seckill/seckillActivity.ts index 51da7d275..82297effd 100644 --- a/apps/web-antd/src/api/mall/promotion/seckill/seckillActivity.ts +++ b/apps/web-antd/src/api/mall/promotion/seckill/seckillActivity.ts @@ -1,7 +1,5 @@ import type { PageParam, PageResult } from '@vben/request'; -import type { MallSpuApi } from '#/api/mall/product/spu'; - import { requestClient } from '#/api/request'; export namespace MallSeckillActivityApi { @@ -34,17 +32,6 @@ export namespace MallSeckillActivityApi { seckillPrice?: number; // 秒杀价格 products?: SeckillProduct[]; // 秒杀商品列表 } - - // TODO @puhui999:这些还需要么? - /** 扩展 SKU 配置 */ - export type SkuExtension = { - productConfig: SeckillProduct; // 秒杀商品配置 - } & MallSpuApi.Sku; - - /** 扩展 SPU 配置 */ - export interface SpuExtension extends MallSpuApi.Spu { - skus: SkuExtension[]; // SKU 列表 - } } /** 查询秒杀活动列表 */ diff --git a/apps/web-antd/src/api/mp/messageTemplate/index.ts b/apps/web-antd/src/api/mp/messageTemplate/index.ts new file mode 100644 index 000000000..6b0d68294 --- /dev/null +++ b/apps/web-antd/src/api/mp/messageTemplate/index.ts @@ -0,0 +1,57 @@ +import { requestClient } from '#/api/request'; + +export namespace MpMessageTemplateApi { + /** 消息模板信息 */ + export interface MessageTemplate { + id: number; + accountId: number; + appId: string; + templateId: string; + title: string; + content: string; + example: string; + primaryIndustry: string; + deputyIndustry: string; + createTime?: Date; + } + + /** 发送消息模板请求 */ + export interface MessageTemplateSendVO { + id: number; + userId: number; + data?: Record; + url?: string; + miniProgramAppId?: string; + miniProgramPagePath?: string; + miniprogram?: string; + } +} + +/** 查询消息模板列表 */ +export function getMessageTemplateList(params: { accountId: number }) { + return requestClient.get( + '/mp/message-template/list', + { params }, + ); +} + +/** 删除消息模板 */ +export function deleteMessageTemplate(id: number) { + return requestClient.delete('/mp/message-template/delete', { + params: { id }, + }); +} + +/** 同步公众号模板 */ +export function syncMessageTemplate(accountId: number) { + return requestClient.post('/mp/message-template/sync', null, { + params: { accountId }, + }); +} + +/** 发送消息模板 */ +export function sendMessageTemplate( + data: MpMessageTemplateApi.MessageTemplateSendVO, +) { + return requestClient.post('/mp/message-template/send', data); +} diff --git a/apps/web-antd/src/views/mall/product/comment/data.ts b/apps/web-antd/src/views/mall/product/comment/data.ts index 62f9f956d..223491884 100644 --- a/apps/web-antd/src/views/mall/product/comment/data.ts +++ b/apps/web-antd/src/views/mall/product/comment/data.ts @@ -3,7 +3,6 @@ import type { VxeTableGridOptions } from '#/adapter/vxe-table'; import type { MallCommentApi } from '#/api/mall/product/comment'; import { z } from '#/adapter/form'; -import { getSpuSimpleList } from '#/api/mall/product/spu'; import { getRangePickerDefaultProps } from '#/utils'; /** 新增/修改的表单 */ @@ -17,19 +16,16 @@ export function useFormSchema(): VbenFormSchema[] { show: () => false, }, }, - // TODO @puhui999:商品的选择 { fieldName: 'spuId', label: '商品', - component: 'ApiSelect', + component: 'Input', componentProps: { - api: getSpuSimpleList, - labelField: 'name', - valueField: 'id', placeholder: '请选择商品', }, rules: 'required', }, + // TODO @puhui999:商品的选择:上面 spuId 可以选择了,下面的 skuId 打开后,没商品。 { fieldName: 'skuId', label: '商品规格', diff --git a/apps/web-antd/src/views/mall/product/comment/modules/form.vue b/apps/web-antd/src/views/mall/product/comment/modules/form.vue index e00983689..3709d2bfc 100644 --- a/apps/web-antd/src/views/mall/product/comment/modules/form.vue +++ b/apps/web-antd/src/views/mall/product/comment/modules/form.vue @@ -1,21 +1,31 @@ diff --git a/apps/web-antd/src/views/mall/product/spu/components/spu-and-sku-list.vue b/apps/web-antd/src/views/mall/product/spu/components/spu-and-sku-list.vue new file mode 100644 index 000000000..ea5cc0b8a --- /dev/null +++ b/apps/web-antd/src/views/mall/product/spu/components/spu-and-sku-list.vue @@ -0,0 +1,165 @@ + + + diff --git a/apps/web-antd/src/views/mall/product/spu/components/spu-select-data.ts b/apps/web-antd/src/views/mall/product/spu/components/spu-select-data.ts new file mode 100644 index 000000000..5003a765d --- /dev/null +++ b/apps/web-antd/src/views/mall/product/spu/components/spu-select-data.ts @@ -0,0 +1,120 @@ +import type { Ref } from 'vue'; + +import type { VbenFormSchema } from '#/adapter/form'; +import type { VxeTableGridOptions } from '#/adapter/vxe-table'; +import type { MallCategoryApi } from '#/api/mall/product/category'; + +import { computed } from 'vue'; + +import { getRangePickerDefaultProps } from '#/utils'; + +/** 列表的搜索表单 */ +export function useGridFormSchema( + categoryTreeList: Ref, +): VbenFormSchema[] { + return [ + { + fieldName: 'name', + label: '商品名称', + component: 'Input', + componentProps: { + placeholder: '请输入商品名称', + allowClear: true, + }, + }, + { + fieldName: 'categoryId', + label: '商品分类', + component: 'TreeSelect', + componentProps: { + treeData: computed(() => categoryTreeList.value), + fieldNames: { + label: 'name', + value: 'id', + }, + treeCheckStrictly: true, + placeholder: '请选择商品分类', + allowClear: true, + showSearch: true, + treeNodeFilterProp: 'name', + }, + }, + { + fieldName: 'createTime', + label: '创建时间', + component: 'RangePicker', + componentProps: { + ...getRangePickerDefaultProps(), + allowClear: true, + }, + }, + ]; +} + +/** 列表的字段 */ +export function useGridColumns( + isSelectSku: boolean, +): VxeTableGridOptions['columns'] { + return [ + { + type: 'expand', + width: 30, + visible: isSelectSku, + slots: { content: 'expand_content' }, + }, + { type: 'checkbox', width: 55 }, + { + field: 'id', + title: '商品编号', + minWidth: 100, + align: 'center', + }, + { + field: 'picUrl', + title: '商品图', + width: 100, + align: 'center', + cellRender: { + name: 'CellImage', + }, + }, + { + field: 'name', + title: '商品名称', + minWidth: 300, + showOverflow: 'tooltip', + }, + { + field: 'price', + title: '商品售价', + minWidth: 90, + align: 'center', + formatter: 'formatAmount2', + }, + { + field: 'salesCount', + title: '销量', + minWidth: 90, + align: 'center', + }, + { + field: 'stock', + title: '库存', + minWidth: 90, + align: 'center', + }, + { + field: 'sort', + title: '排序', + minWidth: 70, + align: 'center', + }, + { + field: 'createTime', + title: '创建时间', + width: 180, + align: 'center', + formatter: 'formatDateTime', + }, + ] as VxeTableGridOptions['columns']; +} diff --git a/apps/web-antd/src/views/mall/product/spu/components/spu-select.vue b/apps/web-antd/src/views/mall/product/spu/components/spu-select.vue new file mode 100644 index 000000000..7b25f8879 --- /dev/null +++ b/apps/web-antd/src/views/mall/product/spu/components/spu-select.vue @@ -0,0 +1,319 @@ + + + diff --git a/apps/web-antd/src/views/mall/product/spu/components/spu-table-select.vue b/apps/web-antd/src/views/mall/product/spu/components/spu-table-select.vue index 6647ae649..c06027b24 100644 --- a/apps/web-antd/src/views/mall/product/spu/components/spu-table-select.vue +++ b/apps/web-antd/src/views/mall/product/spu/components/spu-table-select.vue @@ -5,11 +5,12 @@ import type { VxeGridProps } from '#/adapter/vxe-table'; import type { MallCategoryApi } from '#/api/mall/product/category'; import type { MallSpuApi } from '#/api/mall/product/spu'; -import { computed, onMounted, ref } from 'vue'; +import { computed, nextTick, onMounted, ref } from 'vue'; -import { useVbenModal } from '@vben/common-ui'; import { handleTree } from '@vben/utils'; +import { Modal } from 'ant-design-vue'; + import { useVbenVxeGrid } from '#/adapter/vxe-table'; import { getCategoryList } from '#/api/mall/product/category'; import { getSpuPage } from '#/api/mall/product/spu'; @@ -30,12 +31,16 @@ const emit = defineEmits<{ const categoryList = ref([]); // 分类列表 const categoryTreeList = ref([]); // 分类树 +/** 弹窗显示状态 */ +const visible = ref(false); +const initData = ref(); + /** 单选:处理选中变化 */ function handleRadioChange() { const selectedRow = gridApi.grid.getRadioRecord() as MallSpuApi.Spu; if (selectedRow) { emit('change', selectedRow); - modalApi.close(); + closeModal(); } } @@ -159,25 +164,16 @@ const [Grid, gridApi] = useVbenVxeGrid({ }, }); -const [Modal, modalApi] = useVbenModal({ - destroyOnClose: true, - showConfirmButton: props.multiple, // 特殊:radio 单选情况下,走 handleRadioChange 处理。 - onConfirm: () => { - const selectedRows = gridApi.grid.getCheckboxRecords() as MallSpuApi.Spu[]; - emit('change', selectedRows); - modalApi.close(); - }, - async onOpenChange(isOpen: boolean) { - if (!isOpen) { - await gridApi.grid.clearCheckboxRow(); - await gridApi.grid.clearRadioRow(); - return; - } - +/** 打开弹窗 */ +async function openModal(data?: MallSpuApi.Spu | MallSpuApi.Spu[]) { + initData.value = data; + visible.value = true; + // 等待 Grid 组件完全初始化后再查询数据 + await nextTick(); + if (gridApi.grid) { // 1. 先查询数据 await gridApi.query(); // 2. 设置已选中行 - const data = modalApi.getData(); if (props.multiple && Array.isArray(data) && data.length > 0) { setTimeout(() => { const tableData = gridApi.grid.getTableData().fullData; @@ -201,15 +197,27 @@ const [Modal, modalApi] = useVbenModal({ } }, 300); } - }, -}); + } +} + +/** 关闭弹窗 */ +async function closeModal() { + visible.value = false; + await gridApi.grid.clearCheckboxRow(); + await gridApi.grid.clearRadioRow(); + initData.value = undefined; +} + +/** 确认选择(多选模式) */ +function handleConfirm() { + const selectedRows = gridApi.grid.getCheckboxRecords() as MallSpuApi.Spu[]; + emit('change', selectedRows); + closeModal(); +} -/** 对外暴露的方法 */ defineExpose({ - open: (data?: MallSpuApi.Spu | MallSpuApi.Spu[]) => { - modalApi.setData(data).open(); - }, -}); + open: openModal, +}); // 对外暴露的方法 /** 初始化分类数据 */ onMounted(async () => { @@ -219,7 +227,15 @@ onMounted(async () => { diff --git a/apps/web-antd/src/views/mall/product/spu/components/type.ts b/apps/web-antd/src/views/mall/product/spu/components/type.ts new file mode 100644 index 000000000..3f7a9c4e0 --- /dev/null +++ b/apps/web-antd/src/views/mall/product/spu/components/type.ts @@ -0,0 +1,33 @@ +import type { MallSpuApi } from '#/api/mall/product/spu'; + +/** 商品属性及其值的树形结构(用于前端展示和操作) */ +export interface PropertyAndValues { + id: number; + name: string; + values?: PropertyAndValues[]; +} + +export interface RuleConfig { + // 需要校验的字段 + // 例:name: 'name' 则表示校验 sku.name 的值 + // 例:name: 'productConfig.stock' 则表示校验 sku.productConfig.name 的值,此处 productConfig 表示我在 Sku 上扩展的属性 + name: string; + // 校验规格为一个毁掉函数,其中 arg 为需要校验的字段的值。 + // 例:需要校验价格必须大于0.01 + // { + // name:'price', + // rule:(arg: number) => arg > 0.01 + // } + rule: (arg: any) => boolean; + // 校验不通过时的消息提示 + message: string; +} + +export interface SpuProperty { + propertyList: PropertyAndValues[]; + spuDetail: T; + spuId: number; +} + +// Re-export for use in generic constraint +export type { MallSpuApi }; diff --git a/apps/web-antd/src/views/mall/product/spu/form/index.ts b/apps/web-antd/src/views/mall/product/spu/form/index.ts deleted file mode 100644 index 3ba57c4d9..000000000 --- a/apps/web-antd/src/views/mall/product/spu/form/index.ts +++ /dev/null @@ -1,63 +0,0 @@ -/* eslint-disable @typescript-eslint/no-non-null-assertion */ -import type { MallSpuApi } from '#/api/mall/product/spu'; - -// TODO @puhui999:这个是不是 api 后端有定义类似的?如果是,是不是放到 api 哈? -export interface PropertyAndValues { - id: number; - name: string; - values?: PropertyAndValues[]; -} - -export interface RuleConfig { - // 需要校验的字段 - // 例:name: 'name' 则表示校验 sku.name 的值 - // 例:name: 'productConfig.stock' 则表示校验 sku.productConfig.name 的值,此处 productConfig 表示我在 Sku 上扩展的属性 - name: string; - // 校验规格为一个毁掉函数,其中 arg 为需要校验的字段的值。 - // 例:需要校验价格必须大于0.01 - // { - // name:'price', - // rule:(arg: number) => arg > 0.01 - // } - rule: (arg: any) => boolean; - // 校验不通过时的消息提示 - message: string; -} - -// TODO @puhui999:这个是只有 index.ts 在用么?还是别的模块也会用 -/** 获得商品的规格列表 - 商品相关的公共函数 */ -const getPropertyList = (spu: MallSpuApi.Spu): PropertyAndValues[] => { - // 直接拿返回的 skus 属性逆向生成出 propertyList - const properties: PropertyAndValues[] = []; - // 只有是多规格才处理 - if (spu.specType) { - spu.skus?.forEach((sku) => { - sku.properties?.forEach( - ({ propertyId, propertyName, valueId, valueName }) => { - // 添加属性 - if (!properties?.some((item) => item.id === propertyId)) { - properties.push({ - id: propertyId!, - name: propertyName!, - values: [], - }); - } - // 添加属性值 - const index = properties?.findIndex((item) => item.id === propertyId); - if ( - !properties[index]?.values?.some((value) => value.id === valueId) - ) { - properties[index]?.values?.push({ id: valueId!, name: valueName! }); - } - }, - ); - }); - } - return properties; -}; - -export { getPropertyList }; - -// 导出组件 -// TODO @puhui999:如果 sku-list.vue 要对外,可以考虑在 spu 下面,搞个 components 模块;(目前看,别的模块应该会用到哈。);modules 是当前模块用到的,components 是跨模块要用到的。 -export { default as SkuList } from './modules/sku-list.vue'; diff --git a/apps/web-antd/src/views/mall/product/spu/form/index.vue b/apps/web-antd/src/views/mall/product/spu/form/index.vue index 78c5d6a4b..7048776bb 100644 --- a/apps/web-antd/src/views/mall/product/spu/form/index.vue +++ b/apps/web-antd/src/views/mall/product/spu/form/index.vue @@ -1,7 +1,9 @@ diff --git a/apps/web-antd/src/views/mp/components/wx-msg/msg.vue b/apps/web-antd/src/views/mp/components/wx-msg/msg.vue index 199513c9b..d2749d84b 100644 --- a/apps/web-antd/src/views/mp/components/wx-msg/msg.vue +++ b/apps/web-antd/src/views/mp/components/wx-msg/msg.vue @@ -12,11 +12,16 @@ import { import MsgEvent from './msg-event.vue'; -defineOptions({ name: 'Msg' }); +defineOptions({ name: 'WxMsg' }); -defineProps<{ - item: any; -}>(); +withDefaults( + defineProps<{ + item?: any; + }>(), + { + item: {}, + }, +);