mirror of
https://github.com/crmeb/CRMEB.git
synced 2026-10-28 13:31:04 +00:00
fix(同步): 对齐赠券发放幂等,并补齐文档与七牛 PHP8.4 补丁
This commit is contained in:
parent
a73256142c
commit
6491946882
@ -69,7 +69,7 @@ description: 前端强制红线、接口契约变更同步清单与管理端 UI
|
||||
| 表单控件 | 单行控件高 32px;页面编辑控件基准宽 460px,筛选控件基准宽 280px(均指控件,不含 label);窄容器不超过可用宽度,多行文本、富文本按内容扩展 |
|
||||
| 标签与说明 | label 在控件左侧右对齐、带中文冒号、距控件 6px;表单 label 预留 7 个汉字,超出折行;单位/后缀在框内右侧,距右 14px,避免与清空/计数图标重叠 |
|
||||
| 垂直间距 | 输入/下拉/文本项间距 20px,开关/单选/复选项间距 30px,说明文字距控件 10px;校验消息出现时不遮挡下一个字段 |
|
||||
| 筛选交互 | 搜索、重置固定在搜索卡右上操作区并预留空间,不覆盖字段;完整条件占用不超过 3 行时全显,超过 3 行默认只显示首行并提供展开/收起;按容器实际换行判断,不能把条件个数当行数 |
|
||||
| 筛选交互 | 搜索、重置与展开/收起跟排在筛选项末尾,随条件增减与容器换行自然流动,不固定在搜索卡右上,不遮挡字段;完整条件占用不超过 3 行时全显,超过 3 行默认只显示首行并提供展开/收起;按容器实际换行判断,不能把条件个数当行数 |
|
||||
| 筛选状态 | 展开/收起保留已输入条件;搜索和重置回到第一页,重置恢复默认条件;容器变窄时操作区仍可见、可用且不重叠 |
|
||||
|
||||
### 4.4 表格与辅助组件
|
||||
|
||||
@ -52,10 +52,22 @@ CRMEB 开源版(KY)移动端,基于 **uni-app(Vue 3 + `<script setup>`
|
||||
│ ├── center/ # 【v8 新增】订单中心子包:order/create|detail|aftersale、pay/index|status
|
||||
│ └── goods/order_list # 旧路径保留,内部已替换为 OrderListEngine
|
||||
├── subpackage/diyComponents/ # DIY 装修组件分包;【v8 新增】productSections/(详情页展示块)
|
||||
├── pages-modules/ # 免注册业务模块分包:丢目录即自动注册路由(见下文专节;_template/ 为复制模板)
|
||||
├── components/ store/ utils/ libs/ config/ # 常规公共层(components/mp-html 为富文本组件,勿改内部实现)
|
||||
└── pages.json # 路由配置(带注释 JSON)
|
||||
```
|
||||
|
||||
## pages-modules 自动分包注册(免改 pages.json)
|
||||
|
||||
`src/pages-modules/` 是业务模块的"丢入即用"投放目录:新增页面不再手工编辑 `src/pages.json`,把模块目录放进去即可编译、打包、上传。机制与边界:
|
||||
|
||||
- **原理**:本版本 uni 编译器(`@dcloudio` 3.0.0-5020420260813003)没有官方目录注册机制,`src/pages.json` 是路由唯一输入。工程在 `vite.config.js` 里调用 `scripts/uni-auto-subpackages.mjs`,对"读取 src/pages.json"做路径级 `fs.readFileSync` 拦截,把 `pages-modules/` 扫描出的分包条目**在内存中 append-only 合并**进返回文本——磁盘上的 `pages.json` 保持字节不变(255 处注释与 `// #ifdef` 块全部保留)。uni 官方在 `@dcloudio/vite-plugin-uni` 内部用同样手法拦截 scss 读取(`rewriteScssReadFileSync`)。
|
||||
- **约定**:`src/pages-modules/<snake_case 模块名>/` = 一个独立分包,root 为 `pages-modules/<模块名>`;模块内 `.vue` 自动注册为页面(路径 = 去扩展名的相对路径,如 `/pages-modules/lottery/index`),排除 `components/`、`component/`、`static/` 角色目录与 `_` 前缀文件/目录(`_template/` 模板因此永不参与编译)。页面目录同样遵守 snake_case + `index.vue`(`lint:naming` 已覆盖本目录并强制页面入口唯一形态)。**模块目录名即插件标识**(与后端 `modules/<标识>`、自有表前缀 `eb_{标识}_*` 对齐);营销插件(秒杀等)命名细则与对照表见 `docs/命名规范.md` §2.1。
|
||||
- **片段模式**:模块根放自己的 `pages.json`(JSONC,`{ "pages": [{ "path", "style" }] }`)显式声明页面与标题/导航样式;提供后该模块不再自动扫描,完全按片段注册。
|
||||
- **跳转**:模块自带 `routes.js` 路由常量(参考 `_template/routes.js`),模块内外互跳各自引用常量、禁止硬编码路径,也不改系统 `dynamic-pages/api/routes.js`。
|
||||
- **边界**:只能新增分包页面;tabBar、easycom、globalStyle、preloadRule 等全局配置变更仍须改 `src/pages.json`。微信小程序单包 2MB / 总包 30MB 限制照常适用(每模块 = 一分包)。dev 长跑进程中 easycom 等次要消费点有一次缓存,极端情况重启 dev server;路由主链路与 build 产物不受影响(dev watch 下丢文件 15s 内自动重编,已实测)。
|
||||
- **回归口径**:`@dcloudio/*` 整组锁版升级后,须复验注入仍生效——`npm run build:mp-weixin` 后检查 `dist/build/mp-weixin/app.json` 的 subPackages 含 `pages-modules/*`。日常调试:`node scripts/uni-auto-subpackages.mjs [--print]`。
|
||||
|
||||
## 核心架构:dynamic-pages 动态渲染
|
||||
|
||||
订单相关页面由**后端下发 `page_config`(sections + extensions)驱动**,前端只负责渲染,新增订单/产品类型时前端尽量零改动。
|
||||
|
||||
@ -41,4 +41,4 @@ description: 开发和修改 frontend/admin 下的 CRMEB Admin 管理端:Vue 3
|
||||
|
||||
## 验证
|
||||
|
||||
Node 版本与依赖安装约定见 [admin 端约定](../../scopes/admin.md)。尽量对变更文件执行定向 lint(`npm run eslint`);修改路由、配置或共享组件时运行 `npm run build`。扩展用 `npm run build:ext -- --ext <key>` 构建,确认 `dist/static/extensions/<key>/` 下只有 `app.umd.js` 和 `app.umd.css`。
|
||||
Node 版本与依赖安装约定见 [admin 端约定](../../scopes/admin.md)。**日常修改完成后不做构建检查**:每次改动都跑 `npm run build` 既慢也无必要,编译错误由 dev server 实时暴露,验证以定向 lint(`npm run eslint`)+ dev server 实际操作为准。构建仅在交付/发布(需更新 `backend/public/admin` 静态资源)或用户明确要求时执行。扩展 UMD 包在交付扩展时用 `npm run build:ext -- --ext <key>` 构建,确认 `dist/static/extensions/<key>/` 下只有 `app.umd.js` 和 `app.umd.css`。
|
||||
|
||||
@ -96,6 +96,14 @@ GET /api/center/order/confirm
|
||||
| `params_list` | bool | 商品参数展示(由产品类型 Handler 决定) |
|
||||
| `visitor_form` | bool | 游客信息表单(门票专属,由产品类型 Handler 的 `visitor_form_config` 决定) |
|
||||
|
||||
#### 优惠券列表与自动领券口径
|
||||
|
||||
单品直购(`confirm()`)与购物车合单(`cartConfirm()`)共用 `CenterOrderController::buildConfirmCouponList()`:
|
||||
|
||||
- 用户有可用券时原样返回;没有可用券时自动领取整单最优券并入 `coupon_list`(**预览即真实领取**,与 `computed`/`create` 的自动用券同口径)。
|
||||
- `computed`(`POST /api/center/order/computed`)重算时同样自动匹配并返回 `coupon_list`/`coupon_id`/`coupon_title`;已选中但不在列表查询结果内的券会按 `StoreCouponUserServices::getUsableCouponRow()` 补一行,避免“入口显示已用券、弹窗列表为空”。
|
||||
- 可用券查询的时间边界必须是 `start_time <= now`(`StoreCouponUserDao::getUserAllCoupon()`):领取后 N 天可用的券在领取当秒 `start_time == time()`,用 `<` 会漏掉刚自动领取并已用于计价的券,必须与 `OrderComputedServices::autoMatchCoupon()` 的口径一致。
|
||||
|
||||
### 2.2 下单提交
|
||||
|
||||
```
|
||||
|
||||
@ -23,6 +23,7 @@ Node >= 18;全部 `@dcloudio/*` 锁定同一版本,升级必须整体同步
|
||||
## 确定改动位置
|
||||
|
||||
- 普通页面:在 `src/pages/` 下创建或修改页面,在 `src/pages.json` 注册,并放入合适的分包(主包只保留 tab/guide 页)。
|
||||
- 独立业务模块页面:复制 `src/pages-modules/_template/` 为 `<snake_case 模块名>/` 即自动注册为分包,免改 `src/pages.json`;约定与边界见 `frontend/uni-app/AGENTS.md`「pages-modules 自动分包注册」。
|
||||
- 共享行为:优先复用相近组件、composables、`utils/`、`libs/`,不要把平台逻辑复制到多个页面。
|
||||
- HTTP 接口:在 `src/api/` 下添加领域函数并调用 `@/utils/request.js`;路径不要带 `/api/`,封装层会自动添加。
|
||||
- 订单、下单、支付、售后:走 `src/dynamic-pages/` 引擎与契约,阅读 [references/dynamic-pages.md](references/dynamic-pages.md)。
|
||||
@ -52,4 +53,4 @@ Node >= 18;全部 `@dcloudio/*` 锁定同一版本,升级必须整体同步
|
||||
|
||||
## 验证
|
||||
|
||||
用 CLI 构建受影响的平台(至少 H5 + 需求指定的端),检查导航、认证过期、空数据/错误状态和条件编译分支。改到命名/契约相关文件时跑 `npm run lint:naming`。若缺少平台凭据或真机环境,必须明确说明限制。
|
||||
**日常修改完成后不做构建检查**:不必每次改动都跑 `build:*`,编译错误由对应 dev 命令(如 `dev:h5`)实时暴露,功能验证在已运行的 dev 环境里检查导航、认证过期、空数据/错误状态和条件编译分支。构建仅在交付/发布(H5 发布到 `backend/public`、小程序/APP 出正式包)或用户明确要求时执行。改到命名/契约相关文件时跑 `npm run lint:naming`。若缺少平台凭据或真机环境,必须明确说明限制。
|
||||
|
||||
7
.gitignore
vendored
7
.gitignore
vendored
@ -7,6 +7,12 @@
|
||||
|
||||
# backend 本地文件与目录
|
||||
/backend/tests/
|
||||
/backend/public/admin/
|
||||
/backend/public/assets/
|
||||
/backend/public/pages/
|
||||
/backend/public/static/
|
||||
/backend/public/index.html
|
||||
/backend/public/theme/
|
||||
/backend/public/uploads/
|
||||
/backend/public/install.lock
|
||||
/backend/public/install/install.log
|
||||
@ -32,3 +38,4 @@
|
||||
#docker
|
||||
/docker/mysql/data/*
|
||||
/docker/mysql/log/*
|
||||
.DS_Store
|
||||
|
||||
@ -259,14 +259,39 @@ class StoreCouponIssueServices extends BaseService
|
||||
public function userFirstSubGiveCoupon(int $uid)
|
||||
{
|
||||
$giveCoupon = sys_config('reward_coupon', []);
|
||||
if (count($giveCoupon)) {
|
||||
// 兼容两种存储格式:纯 id 数组(通用页面表单)/ 旧版对象数组 [{id,title}]
|
||||
$couponIds = is_array($giveCoupon[0] ?? null) ? array_column($giveCoupon, 'id') : $giveCoupon;
|
||||
$couponList = $this->dao->getGiveCoupon([['id', 'in', $couponIds]]);
|
||||
$this->giveUserCoupon($uid, $couponList ?: []);
|
||||
// 兼容异常存储:初始值曾写成 "\"[]\"",json_decode 后是字符串 "[]"
|
||||
if (is_string($giveCoupon)) {
|
||||
$decoded = json_decode($giveCoupon, true);
|
||||
$giveCoupon = is_array($decoded) ? $decoded : [];
|
||||
} elseif (!is_array($giveCoupon)) {
|
||||
$giveCoupon = [];
|
||||
}
|
||||
if (!count($giveCoupon)) {
|
||||
return false;
|
||||
}
|
||||
// 兼容两种存储格式:纯 id 数组(通用页面表单)/ 旧版对象数组 [{id,title}]
|
||||
$couponIds = is_array($giveCoupon[0] ?? null) ? array_column($giveCoupon, 'id') : $giveCoupon;
|
||||
$couponIds = array_values(array_filter(array_map('intval', (array)$couponIds)));
|
||||
if (!$couponIds) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// 幂等:UserRegister 在带推广码注册时可能触发两次,已领过的券不再发
|
||||
/** @var \app\dao\activity\coupon\StoreCouponIssueUserDao $issueUserDao */
|
||||
$issueUserDao = app()->make(\app\dao\activity\coupon\StoreCouponIssueUserDao::class);
|
||||
$pendingIds = [];
|
||||
foreach ($couponIds as $couponId) {
|
||||
if (!$issueUserDao->getIssueUserCount($uid, $couponId)) {
|
||||
$pendingIds[] = $couponId;
|
||||
}
|
||||
}
|
||||
if (!$pendingIds) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
$couponList = $this->dao->getGiveCoupon([['id', 'in', $pendingIds]]);
|
||||
$this->giveUserCoupon($uid, $couponList ?: []);
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@ -11,3 +11,41 @@ index 26812b9..f767930 100644
|
||||
{
|
||||
$this->zone = $z;
|
||||
$this->useHTTPS = false;
|
||||
diff --git a/src/Qiniu/Storage/UploadManager.php b/src/Qiniu/Storage/UploadManager.php
|
||||
--- a/src/Qiniu/Storage/UploadManager.php
|
||||
+++ b/src/Qiniu/Storage/UploadManager.php
|
||||
@@ -24,7 +24,7 @@ class UploadManager
|
||||
* @param Config|null $config
|
||||
* @param RequestOptions|null $reqOpt
|
||||
*/
|
||||
- public function __construct(Config $config = null, RequestOptions $reqOpt = null)
|
||||
+ public function __construct(?Config $config = null, ?RequestOptions $reqOpt = null)
|
||||
{
|
||||
if ($config === null) {
|
||||
$config = new Config();
|
||||
diff --git a/src/Qiniu/Storage/BucketManager.php b/src/Qiniu/Storage/BucketManager.php
|
||||
--- a/src/Qiniu/Storage/BucketManager.php
|
||||
+++ b/src/Qiniu/Storage/BucketManager.php
|
||||
@@ -22,7 +22,7 @@ final class BucketManager
|
||||
|
||||
public function __construct(
|
||||
Auth $auth,
|
||||
- Config $config = null,
|
||||
+ ?Config $config = null,
|
||||
$proxy = null,
|
||||
$proxy_auth = null,
|
||||
$proxy_user_password = null
|
||||
) {
|
||||
diff --git a/src/Qiniu/Storage/ArgusManager.php b/src/Qiniu/Storage/ArgusManager.php
|
||||
--- a/src/Qiniu/Storage/ArgusManager.php
|
||||
+++ b/src/Qiniu/Storage/ArgusManager.php
|
||||
@@ -22,7 +22,7 @@ final class ArgusManager
|
||||
|
||||
public function __construct(
|
||||
Auth $auth,
|
||||
- Config $config = null,
|
||||
+ ?Config $config = null,
|
||||
$proxy = null,
|
||||
$proxy_auth = null,
|
||||
$proxy_user_password = null
|
||||
) {
|
||||
|
||||
@ -6,7 +6,7 @@
|
||||
"package": "qiniu/php-sdk",
|
||||
"description": "Fix PHP 8.4 implicit nullable parameter deprecation",
|
||||
"url": "crmeb/patches/qiniu-php-sdk-php84-nullable.patch",
|
||||
"sha256": "9361c863c56d1693a082f6fa94d738e2eeb6d21e46c64a0080f5ee30be56f23a",
|
||||
"sha256": "73053c9bc38fb0407595e25b49d9f71d54130ae6c605f400e955e9874073e87a",
|
||||
"depth": 1,
|
||||
"extra": {
|
||||
"provenance": "root"
|
||||
|
||||
@ -44,7 +44,7 @@
|
||||
| 文档 | 主要内容 |
|
||||
|---|---|
|
||||
| [项目说明](项目说明.md) | 项目定位、适用场景、角色、术语、目录总览、能力边界 |
|
||||
| [项目功能](项目功能.md) | 管理端、用户端、交易、营销、渠道、客服、开放平台等功能 |
|
||||
| [项目功能](项目功能.md) | 管理端、用户端、交易等核心与默认安装模块功能 |
|
||||
| [项目架构](项目架构.md) | 技术栈、逻辑分层、核心链路、扩展机制、运行时组件 |
|
||||
| [模块说明](模块与驱动.md) | 22 个模块、30 个驱动、默认安装清单、安装生命周期与扩展规则 |
|
||||
| [支付驱动二开指南](支付驱动二开指南.md) | 支付驱动定位、清单字段、PayInterface、下单/回调/退款链路、配置表、红线与验收排障 |
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user