提交
This commit is contained in:
1 parent
8be55c08e9
commit
a903fcf6d4
75 files changed
+9547
-124
No files matched your search
@@ -7,6 +7,7 @@
|
||||
- 新增 OpenSpec 变更 `zkxw-engineering-standards`,包含提案、设计说明、**八项能力规范**及落地任务清单。
|
||||
- **不修改**业务代码;规范正文以需求 V0.3 第 3 节「技术要求」为硬约束,UI 细节以两份 UI 规范文档为权威展开(本变更中的 UI 条目为**跨端统一原则与引用约束**,像素级 Token 仍以原文档为准)。
|
||||
- 工作流/审批与权限条目直接对齐需求文档 **§2.1.11 流程管理** 与 **§2.1.5 角色管理** 等章节。
|
||||
- **增补(2026-07)**:`ui-conventions` / `api-conventions` / `architecture-conventions` 明确小程序附件基线为**仅拍照凭证**(`uni.chooseImage` + `uni.uploadFile` + 业务提交 `imageUrls`),与 `web-admin` 通用附件卡片化布局分工;参考叁强 `sqxp-wx` 凭证上传。
|
||||
|
||||
## Capabilities
|
||||
|
||||
|
||||
@@ -382,6 +382,44 @@ FastAPI 路由 Query 参数若 Python 参数名为 `page_size`,SHALL 声明 `Q
|
||||
- **WHEN** 在 `app/api/` 增加新的路由模块
|
||||
- **THEN** 该模块 SHALL 在 `app/api/__init__.py` 与 `app/main.py` 中完成注册,并符合现有认证与租户上下文注入模式
|
||||
|
||||
### Requirement: 小程序拍照凭证上传接口
|
||||
|
||||
`miniprogram` 拍照凭证(仅图片,见 `ui-conventions`「小程序拍照凭证上传(基线)」)相关接口 SHALL 遵循下列约定;权威行为对齐叁强鲜配 `POST /api/upload/image` + 业务接口携带 `imageUrls`。
|
||||
|
||||
#### 上传接口
|
||||
|
||||
1. **方法与编码**:`POST` + `multipart/form-data`(文件上传例外,见上文「文件上传下载例外」)。
|
||||
2. **表单字段**:文件字段名 SHALL 为 `file`;业务目录字段 SHALL 为 `folder`(`Form` 字符串,如 `delivery-evidence`、`avatars`)。
|
||||
3. **鉴权**:SHALL 要求登录态(Bearer JWT 或与小程序一致的现有鉴权);未登录 SHALL 返回 401。
|
||||
4. **成功响应**:统一信封下 `data` SHALL 至少包含 `url`(对象存储可访问地址);MAY 含 `filename`、`contentType`、`size`。前端以 `data.url` 为唯一回填依据。
|
||||
5. **类型与大小**:SHALL 仅接受图片 MIME(至少覆盖 `image/jpeg`、`image/png`、`image/webp`;建议兼容 `image/heic`/`image/heif`);单文件大小上限建议 **10MB**(与叁强一致,业务另有约定时在接口文档写明)。
|
||||
6. **路径落点**:山峡落地时 MAY 复用/对齐 `POST /api/upload/image`,或提供 `POST /api/mini/.../upload/image`;无论路径如何,小程序 `uni.uploadFile` 的 `url` 与后端注册路径 SHALL 一致,并完成路由注册(`app/api/__init__.py`、`app/main.py`)。
|
||||
|
||||
#### 加解密排除(强制)
|
||||
|
||||
`multipart` 上传路径 SHALL 列入服务端 `API_ENCRYPT_EXCLUDE_PATHS`(及前端若启用 API 加密时的排除清单)。微信小程序端若因运行时不保证 WebCrypto 而关闭客户端加密(叁强 `sqxp-wx/utils/request.js` 在 `MP-WEIXIN` 下 `API_ENCRYPT_ENABLED = false`),上传仍 SHALL 依赖 HTTPS + Bearer,SHALL NOT 对文件字节做 JSON AES 包装。
|
||||
|
||||
#### 业务关联
|
||||
|
||||
1. 业务确认类接口(如取货、送达、备货完成)请求体 SHALL 使用 JSON,图片以 **URL 数组** 传递(建议字段名 `imageUrls: string[]`)。
|
||||
2. 服务端收到 URL 列表后 SHALL 校验归属与数量上限,再更新业务状态;需要附件台账时 SHALL 写入附件表(或等价持久化),`file_url` 存完整 URL;多 URL 落单字段时 SHALL 使用英文逗号分隔(见 `architecture-conventions`「附件与对象存储」)。
|
||||
3. 业务接口 SHALL NOT 再接收同一确认动作中的 multipart 文件流。
|
||||
|
||||
#### Scenario: 小程序上传一张凭证图
|
||||
|
||||
- **WHEN** 已登录小程序用户对图片上传接口提交 `file` + `folder`
|
||||
- **THEN** 响应 `code` SHALL 为成功码且 `data.url` 非空,对象存储中可按该 URL 访问图片
|
||||
|
||||
#### Scenario: 业务提交携带 imageUrls
|
||||
|
||||
- **WHEN** 用户确认取货/送达等动作且本地已有已上传 URL
|
||||
- **THEN** 业务 POST 体 SHALL 含 `imageUrls` 数组,服务端 SHALL 据此落库/关联,SHALL NOT 要求客户端再次上传二进制
|
||||
|
||||
#### Scenario: 上传路径未排除加密
|
||||
|
||||
- **WHEN** 上传路径未加入 `API_ENCRYPT_EXCLUDE_PATHS` 且全局 API 加密开启
|
||||
- **THEN** 视为规范违规;实现与评审 SHALL 在合入前补齐排除项
|
||||
|
||||
### Requirement: 细胞存储样本跨表关联键
|
||||
|
||||
细胞存储场景下,将协议样本行(`biz_protocol_sample`)与下游业务(样本接收 `biz_sample_reception`、质检工单 `biz_qc_work_order` 等)进行**业务匹配**时,关联键 SHALL 仅为 `tenant_no` + `sample_code`(租户内样本编码唯一且不变;接收/工单表须持久化 `sample_code` 快照)。**不得**使用 `collection_protocol_id` 或 `protocol_sample_id` 作为查询、列表排除、详情回显的关联依据(协议单可换号/转科研、编辑保存会重建样本行 `id`,但样本编码不变)。
|
||||
|
||||
@@ -22,11 +22,21 @@
|
||||
|
||||
业务附件 SHALL 通过对象存储(如 MinIO)保存可访问 URL;多附件在同一字段内存储时 SHALL 使用英文逗号分隔多个 URL(与需求 §3 一致)。
|
||||
|
||||
端侧分工:
|
||||
|
||||
- **`web-admin`**:通用附件(图片/视频/办公文档等)按 `ui-conventions`「附件上传卡片化布局」上传与展示。
|
||||
- **`miniprogram`(当前基线)**:仅拍照/相册**图片凭证**,按 `ui-conventions`「小程序拍照凭证上传(基线)」与 `api-conventions`「小程序拍照凭证上传接口」;文档类附件不在小程序基线范围内。
|
||||
|
||||
#### Scenario: 上传列表回显
|
||||
|
||||
- **WHEN** 业务实体包含多个附件
|
||||
- **THEN** 持久化字段 SHALL 为逗号分隔的 URL 字符串,且接口契约与前端解析逻辑一致
|
||||
|
||||
#### Scenario: 小程序与 Web 附件能力边界
|
||||
|
||||
- **WHEN** 同一业务在 Web 与小程序均需留存材料
|
||||
- **THEN** 小程序侧 SHALL 仅实现拍照凭证 URL 链路;若业务必须上传 PDF 等文档,SHALL 引导在 Web 完成或先扩展 OpenSpec 后再实现
|
||||
|
||||
### Requirement: 权威文档索引
|
||||
|
||||
实现类变更 SHALL 将 `工程规范`、`docs/web-admin-ui-spec-fresh-mint.md`、`docs/miniprogram-ui-spec-sanqiang.md` 及 `AGENTS.md` 视为架构与规范的索引入口;多系统 SQL 与 CRM/地磅边界 SHALL 以 `ecosystem-landscape` spec 为准;OpenSpec 变更 SHALL 在 `proposal.md` / `design.md` 中保持对上述路径的引用。
|
||||
|
||||
@@ -923,6 +923,129 @@ watchDebounced(
|
||||
- **WHEN** PR 新增或修改附件上传 UI
|
||||
- **THEN** 审查者 SHALL 对照 `ProtocolFormDialog.vue` 确认卡片化布局与立即上传逻辑
|
||||
- **THEN** SHALL 拒绝 `picture-card`、`show-file-list` 默认列表、与规范不一致的尺寸/色值
|
||||
|
||||
---
|
||||
|
||||
### Requirement: 小程序拍照凭证上传(基线)
|
||||
|
||||
`miniprogram` 业务附件在当前产品范围内 **仅支持拍照/相册图片凭证**(如取货、送达、备货、现场确认等),**SHALL NOT** 实现 PDF/Word/Excel 等文档类附件上传(文档类仍以 `web-admin`「附件上传卡片化布局」为准)。实现与交互以叁强鲜配 `sqxp-wx` 配送/供应商凭证上传为权威参考(如 `pages/deliverer/task/detail/detail.vue`、`pages/supplier/delivery/detail/detail.vue`、`pages/supplier/settings/avatar.vue`);山峡侧后续落地时 SHALL 抽公共工具(建议 `miniprogram/utils/upload.js`),业务页只负责数量上限、`folder` 与提交字段。
|
||||
|
||||
#### 范围与禁止项
|
||||
|
||||
1. **允许来源**:`uni.chooseImage`(或等价的 `uni.chooseMedia` 且仅 `mediaType: ['image']`),`sourceType` 含 `album` 与 `camera`,`sizeType` 优先 `compressed`。
|
||||
2. **禁止**:SHALL NOT 使用 `wx.chooseMessageFile` / `uni.chooseFile` 选聊天文件或任意文档;SHALL NOT 复用 Web 端 `el-upload`、`FormData` + `uni.request`/`fetch` 上传二进制;SHALL NOT 把图片转 Base64 塞进 JSON 业务接口(除非产品书面要求且接口单独评审)。
|
||||
3. **色板与触控**:上传入口、删除热区 SHALL 遵守小程序 Token(品牌绿 `#0a9f6d` 系)与最小触控 44×44;SHALL NOT 使用 Web Fresh Mint `#10b981` 作为小程序主色。
|
||||
|
||||
#### 两步流程(强制)
|
||||
|
||||
1. **选图并立即上传**:用户选择临时路径后,逐张(或受控并发)调用 `uni.uploadFile` 上传到图片上传接口,成功后将返回的 **URL 字符串** 推入本地数组(如 `imageUrls`)。
|
||||
2. **业务提交**:用户确认业务动作(取货/送达/保存等)时,再调用业务 API,请求体携带已上传的 URL 列表(字段名建议 `imageUrls`,camelCase);服务端再关联业务并(如需要)写入附件表。SHALL NOT 在业务提交接口中直接接收 multipart 文件流(与叁强「先 URL、后业务」一致)。
|
||||
|
||||
#### `uni.uploadFile` 约定
|
||||
|
||||
| 项 | 要求 |
|
||||
|----|------|
|
||||
| 调用方式 | `uni.uploadFile`(微信小程序原生 multipart) |
|
||||
| `url` | 图片上传接口绝对或与 `API_BASE` 拼接后的完整 URL(见 `api-conventions`「小程序拍照凭证上传接口」) |
|
||||
| `filePath` | `chooseImage` / `chooseMedia` 返回的临时路径 |
|
||||
| `name` | 固定为 `file`(与后端 `UploadFile` 字段名一致) |
|
||||
| `header.Authorization` | `Bearer ${token}`;token 来自本地存储(与登录态同一 key) |
|
||||
| `formData.folder` | 业务目录字符串(如 `delivery-evidence`、`avatars`、山峡业务自定义 kebab/短路径) |
|
||||
| 响应解析 | `uploadFile` 的 `data` 多为 **字符串**,SHALL `JSON.parse` 后再读 `code` / `data.url`;成功条件与统一响应一致(`code === 200` 且存在 `data.url`) |
|
||||
| Loading | 上传过程 SHALL `uni.showLoading`,结束 `uni.hideLoading`;失败 SHALL `uni.showToast` 提示 |
|
||||
|
||||
上传逻辑 **SHALL NOT** 走会做 AES/包体加密的通用 `request()` 封装(multipart 无法套 JSON 加密封装);鉴权仅靠 Header Bearer。
|
||||
|
||||
#### UI 与数量
|
||||
|
||||
1. **展示**:已上传项以缩略图网格/横向列表展示;点击预览 SHALL 使用 `uni.previewImage`(`urls` 为当前列表,`current` 为点击项)。
|
||||
2. **删除**:未提交业务前可从本地数组移除;已随业务提交锁定的凭证按业务只读展示,SHALL NOT 在只读态提供删除。
|
||||
3. **数量上限**:单次业务凭证默认最多 **6** 张(与叁强配送凭证一致);若业务需要其他上限,SHALL 在页面文案与选图 `count` 中写死同一常量。选图前 `count = 上限 - 已有张数`,已满则 toast「最多上传 N 张」。
|
||||
4. **上传入口文案**:建议「+ 上传」或「选择图片并上传」;入口热区 ≥ 44×44。
|
||||
|
||||
#### 参考实现骨架(工具函数形态)
|
||||
|
||||
```javascript
|
||||
// miniprogram/utils/upload.js(后续落地时抽取;业务页传入 folder / maxCount)
|
||||
export async function chooseAndUploadImages({ folder, maxCount, currentUrls, token, uploadUrl }) {
|
||||
const remain = maxCount - currentUrls.length
|
||||
if (remain <= 0) {
|
||||
uni.showToast({ title: `最多上传${maxCount}张`, icon: 'none' })
|
||||
return []
|
||||
}
|
||||
const choose = await new Promise((resolve) => {
|
||||
uni.chooseImage({
|
||||
count: remain,
|
||||
sizeType: ['compressed'],
|
||||
sourceType: ['album', 'camera'],
|
||||
success: (res) => resolve(res),
|
||||
fail: () => resolve(null)
|
||||
})
|
||||
})
|
||||
if (!choose?.tempFilePaths?.length) return []
|
||||
|
||||
const urls = []
|
||||
uni.showLoading({ title: '上传中...' })
|
||||
try {
|
||||
for (const filePath of choose.tempFilePaths) {
|
||||
const uploadRes = await new Promise((ok, fail) => {
|
||||
uni.uploadFile({
|
||||
url: uploadUrl,
|
||||
filePath,
|
||||
name: 'file',
|
||||
header: { Authorization: token ? `Bearer ${token}` : '' },
|
||||
formData: { folder },
|
||||
success: ok,
|
||||
fail
|
||||
})
|
||||
})
|
||||
const raw = typeof uploadRes.data === 'string' ? uploadRes.data : String(uploadRes.data || '')
|
||||
let body = {}
|
||||
try {
|
||||
body = JSON.parse(raw)
|
||||
} catch {
|
||||
body = {}
|
||||
}
|
||||
if (body.code !== 200 || !body.data?.url) {
|
||||
uni.showToast({ title: body.message || '上传失败', icon: 'none' })
|
||||
break
|
||||
}
|
||||
urls.push(String(body.data.url))
|
||||
}
|
||||
} finally {
|
||||
uni.hideLoading()
|
||||
}
|
||||
return urls
|
||||
}
|
||||
```
|
||||
|
||||
业务页在确认动作时示例:
|
||||
|
||||
```javascript
|
||||
await request({
|
||||
url: `/mini/<role>/tasks/${taskId}/pickup`,
|
||||
method: 'POST',
|
||||
data: { remark: '已取货', imageUrls: pickupImages.value }
|
||||
})
|
||||
```
|
||||
|
||||
#### Scenario: 配送/现场凭证上传
|
||||
|
||||
- **WHEN** 用户在小程序业务页点击「上传」且未达上限
|
||||
- **THEN** SHALL 调起相册或拍照选图,并对每张临时文件调用 `uni.uploadFile`
|
||||
- **THEN** 成功后本地列表增加可预览的图片 URL,业务提交时携带 `imageUrls`
|
||||
|
||||
#### Scenario: 禁止文档类附件
|
||||
|
||||
- **WHEN** 需求表述为「附件」但场景为小程序端
|
||||
- **THEN** 实现 SHALL 仅提供拍照凭证能力;若确需 PDF 等文档,SHALL 先走独立 OpenSpec 变更扩展本 Requirement,不得静默加入 `chooseMessageFile`
|
||||
|
||||
#### Scenario: Code review 小程序凭证上传
|
||||
|
||||
- **WHEN** PR 新增或修改 `miniprogram` 图片/凭证上传
|
||||
- **THEN** 审查者 SHALL 确认使用 `uni.chooseImage`(或仅图片的 `chooseMedia`)+ `uni.uploadFile`,且业务提交为 URL 列表
|
||||
- **THEN** SHALL 拒绝 `FormData`+`request`、Base64 塞业务 JSON、以及未排除加解密的上传路径误用
|
||||
|
||||
### Requirement: 按钮须携带图标
|
||||
|
||||
`web-admin` 中所有带文字标签的 `el-button`(或 `ElButton`)SHALL 在文字**前方**包含 `<el-icon>` 子元素,使用 `@element-plus/icons-vue` 提供的图标组件;SHALL NOT 出现仅有文字而无图标的按钮。该规则适用于列表工具栏、表格操作列、`el-dialog` / 抽屉的 `#footer` 底栏、表单内 `link` / `plain` / `size="small"` 等**凡渲染可见文案的按钮**,不限于 `type="primary"`。图标的引入方式为在文件 `<script setup>` 中显式 `import` 所需图标组件(`@element-plus/icons-vue` 不属于 auto-import 范围)。图标与文字间距由全局样式保证(与 Fresh Mint 弹窗底栏约定一致,约 6px)。
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
- [x] 2.1 在 `openspec/config.yaml` 的 `context` 中摘要本变更及 `ecosystem-landscape`(多系统 SQL 与 CRM 路径);OpenCode 见 `.opencode/ecosystem-landscape.md`
|
||||
- [ ] 2.2 在 PR 模板或评审清单中增加一条:涉及租户/软删/排序的改动必须对照 `api-conventions` 与 `database-conventions`
|
||||
- [ ] 2.3 工作流专项启动前,将 `workflow-conventions` 中 SQL 审批人、版本迁移等「Open Questions」拆成独立变更
|
||||
- [x] 2.4 将小程序「仅拍照凭证」上传写法写入 `ui-conventions` / `api-conventions` / `architecture-conventions`,并在 `AGENTS.md` 摘要索引(后续 `miniprogram` 业务开发以此为基线)
|
||||
|
||||
## 3. 归档(实现稳定后)
|
||||
|
||||
|
||||
Reference in new issue
Block a user