diff --git a/.idea/modules.xml b/.idea/modules.xml
new file mode 100644
index 0000000..476dc6f
--- /dev/null
+++ b/.idea/modules.xml
@@ -0,0 +1,8 @@
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
new file mode 100644
index 0000000..35eb1dd
--- /dev/null
+++ b/.idea/vcs.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/yunxinwei-uniapp.iml b/.idea/yunxinwei-uniapp.iml
new file mode 100644
index 0000000..24643cc
--- /dev/null
+++ b/.idea/yunxinwei-uniapp.iml
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.workbuddy/memory/2026-07-26.md b/.workbuddy/memory/2026-07-26.md
new file mode 100644
index 0000000..fad1133
--- /dev/null
+++ b/.workbuddy/memory/2026-07-26.md
@@ -0,0 +1,45 @@
+# 2026-07-26
+
+## 修复 `pages/order/delivery.vue` 身份证上传失败 Bug
+
+**用户报告**:页面提交时弹"请上传身份证正面照片.FAILUREFP03333"。
+
+**根因分析**:
+- 错误码 `FAILUREFP03333` 和提示文本"请上传身份证正面照片"都是后端 `POST /open/equipment/receipt` 返回的,前端代码无此内容。
+- `orderSourceData` 数组下标与图片含义是固定绑定:0=身份证正面、1=身份证反面、2=人车合照、3=车子照片、4=安装照片。
+- 截图里只看到"其他"模块 3 张图,未见"身份证信息"模块——用户大概率跳过了身份证正反面上传就点了提交。
+
+**修复内容**(`pages/order/delivery.vue`):
+1. 在 `handleSubmit` 提交前增加客户端必传校验:5 个图片槽位任一为空就拦截,并按"身份证正面 > 身份证反面 > 其他"优先级提示用户补图。
+2. 过滤后端返回的错误码尾巴(如 `.FAILUREFP03333`),新增 `formatErrorMsg()` 工具函数。
+3. 优化占位图:未上传时显示"+ 点击上传身份证正面(人像面)"等说明,替代原来的灰色 `not-dealer.png` 占位。
+4. `chooseImage` 加 `uni.showLoading` 上传中提示,成功/失败分别 toast 明确文案。
+5. 字段标签明确化:"正面/反面"→"身份证正面(人像面)/身份证反面(国徽面)"。
+
+**关于 `FAILUREFP03333` 的判断**:
+- 非前端错误码(前端 grep 无任何匹配)。
+- 推测是后端在调用阿里云内容安全 / 第三方 OCR(Face++、百度、腾讯云天御等)做身份证内容审核时返回的状态码。
+- 真正根因在后端,前端只能加校验和友好提示,无法根本解决"图片本身被识别为非身份证"的场景。
+- 建议后端同事确认 `/open/equipment/receipt` 接口的身份证正反面校验逻辑,以及是否依赖第三方图片识别服务。
+
+## 后端 `yunxinwei-api` 同步修复(FAILUREFP03333 根因)
+
+**项目路径**:`/Users/gxwebsoft/JAVA/yunxinwei-api`
+**文件**:`src/main/java/com/gxwebsoft/open/controller/OpenEquipmentController.java`
+
+**根因确认**:
+- `receipt()` 接口(原 402-461 行)调用 `idcardService.verify(front, back)` 做阿里云市场 OCR(`miitangs14.market.alicloudapi.com/v1/tools/ocr/idCard`),非 `FP00000` 时把 `verify.getMessage() + verify.getCode()` 直接拼进用户提示 → 用户看到"请上传身份证正面照片.FAILUREFP03333"。
+- `idcardService.verify` 在 OCR 接口异常时返回 `null`,原代码紧接着 `verify.getCode()` 会 NPE → 500。
+- `images.get(0/1)` 在图片不足 2 张时会 `IndexOutOfBounds`;用户跳过身份证上传只传 3 张其他图时,`images[0]` 被当成身份证人像面去 OCR → 失败但提示语仍是"请上传身份证正面照片"。
+
+**修复内容**:
+1. 抽出私有方法 `verifyIdCard(orderSourceData, order)`,把身份证识别与实名回填逻辑内聚。
+2. JSON 解析加 try-catch,失败返回"身份证图片数据格式有误"。
+3. 校验 `images` 至少 2 张且 `images[0]`/`images[1]` 非空,否则返回"请上传身份证正面和反面照片"(不再越界)。
+4. `verify` 调用加 try-catch + `null` 判断,避免 NPE;OCR 异常返回友好提示。
+5. **内部错误码 `FAILUREFP03333` 不再回吐给用户**,仅 `log.warn` 记录;用户提示改为"身份证正面照片识别失败,请重新上传清晰的身份证人像面照片" / "身份证反面照片识别失败,请重新上传清晰的身份证国徽面照片"。
+6. `userService.getById(...)` 补 `null` 防护,避免回填空指针。
+
+**验证**:`./mvnw -o -q compile` 离线编译通过(EXIT=0,依赖已本地缓存)。
+
+**前后端配合结论**:前端(delivery.vue)做提交前必传校验 + 友好占位,后端(OpenEquipmentController)做空安全 + 内部码隔离 + 分正反面提示,两端共同消除该 Bug。
diff --git a/pages/order/delivery.vue b/pages/order/delivery.vue
index ea752b3..85e1755 100644
--- a/pages/order/delivery.vue
+++ b/pages/order/delivery.vue
@@ -6,34 +6,49 @@
-
+
- 身份证信息
+ 身份证信息(必传)
-
-
-
+
+
+
+ +
+ 点击上传身份证正面(人像面)
+
-
-
-
+
+
+
+ +
+ 点击上传身份证反面(国徽面)
+
- 其他
+ 其他凭证(必传)
-
-
-
+
+
+
+ +
+ 点击上传人车合照
+
-
-
-
+
+
+
+ +
+ 点击上传车子照片
+
-
-
-
+
+
+
+ +
+ 点击上传安装完成照片
+
@@ -118,6 +133,15 @@
trigger: ['blur', 'change']
}],
}
+
+ // 图片槽位说明(与 orderSourceData 下标一一对应)
+ const imageSlotLabels = ['身份证正面', '身份证反面', '人车合照', '车子照片', '安装照片']
+
+ // 过滤后端返回的错误码尾巴(如 ".FAILUREFP03333"),让提示更友好
+ function formatErrorMsg(msg) {
+ if (!msg) return '操作失败,请稍后重试'
+ return String(msg).replace(/\.[A-Z0-9]{4,}\s*$/, '').trim() || '操作失败,请稍后重试'
+ }
export default {
data() {
@@ -241,70 +265,87 @@
app.getData()
})
}, */
- // 表单提交
- handleSubmit() {
- // if(orderSourceData.length < 5) {
- // return
- // }
- const that = this
- const app = this
- const { orderId,orderSourceData,captcha,receiptPhone,emergentUser,officeAddress, homeAddress } = app
-
-
- receiptOrder({
- orderId: orderId,
- orderSourceData: JSON.stringify(orderSourceData),
- captcha: captcha,
- receiptPhone: receiptPhone,
- emergentUser: emergentUser,
- officeAddress: officeAddress,
- homeAddress: homeAddress
- }).then(result => {
- if(result.code == 0) {
- that.$success("确认收货成功!")
- // uni.navigateBack()
- setTimeout(()=>{
- that.$navTo('pages/order/index',{}, 'redirectTo')
- }, 1000)
- } else {
- that.$toast(result.message)
- }
- }).catch(err => {
- that.$toast(err.message)
- })
- },
-
- // 上传图片
- chooseImage(id) {
- const app = this
- const { orderId } = this
- const type = 'photo' + id
- // 选择图片
- uni.chooseImage({
- count: 1,
- sizeType: ['original', 'compressed'], // 可以指定是原图还是压缩图,默认二者都有
- sourceType: ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有
- success(chooseImageRes) {
- const tempFilePaths = chooseImageRes.tempFilePaths;
-
- uploadFile({
- filePath: tempFilePaths[0],
- fileType: 'image',
- name: 'file'
- }).then(res => {
+ // 表单提交
+ handleSubmit() {
+ const that = this
+ const app = this
+ const { orderId, orderSourceData, captcha, receiptPhone, emergentUser, officeAddress, homeAddress } = app
+
+ // 1) 校验 5 张图是否都已上传
+ const missingIndex = []
+ for (let i = 0; i < imageSlotLabels.length; i++) {
+ if (!orderSourceData[i]) {
+ missingIndex.push(i)
+ }
+ }
+ if (missingIndex.length > 0) {
+ // 优先提示最关键的那张(身份证正面)
+ const firstMissing = missingIndex.includes(0) ? 0 : missingIndex[0]
+ that.$toast(`请先上传${imageSlotLabels[firstMissing]}`, 2500)
+ return
+ }
+
+ receiptOrder({
+ orderId: orderId,
+ orderSourceData: JSON.stringify(orderSourceData),
+ captcha: captcha,
+ receiptPhone: receiptPhone,
+ emergentUser: emergentUser,
+ officeAddress: officeAddress,
+ homeAddress: homeAddress
+ }).then(result => {
+ if(result.code == 0) {
+ that.$success("确认收货成功!")
+ // uni.navigateBack()
+ setTimeout(()=>{
+ that.$navTo('pages/order/index',{}, 'redirectTo')
+ }, 1000)
+ } else {
+ that.$toast(formatErrorMsg(result.message), 2500)
+ }
+ }).catch(err => {
+ that.$toast(formatErrorMsg((err && err.message) || '提交失败,请稍后重试'), 2500)
+ })
+ },
+
+ // 上传图片
+ chooseImage(id) {
+ const app = this
+ const { orderId } = this
+ const type = 'photo' + id
+ // 选择图片
+ uni.chooseImage({
+ count: 1,
+ sizeType: ['original', 'compressed'], // 可以指定是原图还是压缩图,默认二者都有
+ sourceType: ['album', 'camera'], // 可以指定来源是相册还是相机,默认二者都有
+ success(chooseImageRes) {
+ const tempFilePaths = chooseImageRes.tempFilePaths;
+ const slotLabel = imageSlotLabels[id - 1] || '图片'
+ uni.showLoading({ title: '上传中...', mask: true })
+
+ uploadFile({
+ filePath: tempFilePaths[0],
+ fileType: 'image',
+ name: 'file'
+ }).then(res => {
console.log('res', res)
- // app.orderSourceData.push(res.data.url)
- if(app.orderSourceData[id-1]){
- app.orderSourceData.splice(id-1, 1, res.data.url)
- } else {
- app.orderSourceData.push(res.data.url)
- }
- console.log('app.orderSourceData', app.orderSourceData)
- })
-
- }
- });
- },
+ // app.orderSourceData.push(res.data.url)
+ if(app.orderSourceData[id-1]){
+ app.orderSourceData.splice(id-1, 1, res.data.url)
+ } else {
+ app.orderSourceData.push(res.data.url)
+ }
+ console.log('app.orderSourceData', app.orderSourceData)
+ app.$success(`${slotLabel}上传成功`)
+ }).catch(err => {
+ app.$toast(formatErrorMsg((err && (err.errMsg || err.message)) || `${slotLabel}上传失败,请重试`), 2500)
+ }).finally(() => {
+ uni.hideLoading()
+ })
+
+ }
+ });
+ },
}
@@ -334,6 +375,39 @@
background: #fff;
}
+ /* 上传图片(已上传时) */
+ .upload-img {
+ width: 300rpx;
+ height: 200rpx;
+ border-radius: 8rpx;
+ }
+
+ /* 上传占位符(未上传时) */
+ .upload-placeholder {
+ width: 300rpx;
+ height: 200rpx;
+ border: 2rpx dashed #c8c8c8;
+ border-radius: 8rpx;
+ background: #fafafa;
+ display: flex;
+ flex-direction: column;
+ justify-content: center;
+ align-items: center;
+ color: #999;
+
+ .placeholder-icon {
+ font-size: 56rpx;
+ line-height: 1;
+ color: #c0c4cc;
+ }
+
+ .placeholder-text {
+ font-size: 22rpx;
+ margin-top: 10rpx;
+ color: #909399;
+ }
+ }
+
/* 底部操作栏 */
.footer {
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
new file mode 100644
index 0000000..8472722
--- /dev/null
+++ b/pnpm-lock.yaml
@@ -0,0 +1,86 @@
+lockfileVersion: '9.0'
+
+settings:
+ autoInstallPeers: true
+ excludeLinksFromLockfile: false
+
+importers:
+
+ .:
+ dependencies:
+ '@dcloudio/uni-ui':
+ specifier: ^1.4.28
+ version: 1.5.12
+ async-validator:
+ specifier: ^4.2.5
+ version: 4.2.5
+ dayjs:
+ specifier: ^1.11.10
+ version: 1.11.21
+ luch-request:
+ specifier: ^3.0.8
+ version: 3.1.1
+ devDependencies:
+ js:
+ specifier: ^0.1.0
+ version: 0.1.0
+ js-md5:
+ specifier: ^0.7.3
+ version: 0.7.3
+
+packages:
+
+ '@dcloudio/types@2.6.12':
+ resolution: {integrity: sha512-mrCMwcINy1IFjU9VUqLeWBkj404yWs5paLDttBcA+eqUjanuUQbBcTVPqlrGgkyzLXDcV2oDDZRSNxNpXi4kMQ==}
+
+ '@dcloudio/uni-ui@1.5.12':
+ resolution: {integrity: sha512-mGDl2OZSz7D8xcUAzJegWDHOqB4MEFBSW9Esb/oJiu2/3Gk9+P/Z4bA4JZ9jv9VWBYbMrYwaTfK1Z728kABdYg==}
+
+ async-validator@4.2.5:
+ resolution: {integrity: sha512-7HhHjtERjqlNbZtqNqy2rckN/SpOOlmDliet+lP7k+eKZEjPk3DgyeU9lIXLdeLz0uBbbVp+9Qdow9wJWgwwfg==}
+
+ commander@1.1.1:
+ resolution: {integrity: sha512-71Rod2AhcH3JhkBikVpNd0pA+fWsmAaVoti6OR38T76chA7vE3pSerS0Jor4wDw+tOueD2zLVvFOw5H0Rcj7rA==}
+ engines: {node: '>= 0.6.x'}
+
+ dayjs@1.11.21:
+ resolution: {integrity: sha512-98IT+HOahAisibz/yjKbzuOBwYcjJ7BCLPzARyHiyEBmRz4fatF+KPJszEHXsGYjUG234aH/cOjW1wwTbKUZlA==}
+
+ js-md5@0.7.3:
+ resolution: {integrity: sha512-ZC41vPSTLKGwIRjqDh8DfXoCrdQIyBgspJVPXHBGu4nZlAEvG3nf+jO9avM9RmLiGakg7vz974ms99nEV0tmTQ==}
+
+ js@0.1.0:
+ resolution: {integrity: sha512-ZBbGYOpact8QAH9RprFWL4RAESYwbDodxiuDjOnzwzzk9pBzKycoifGuUrHHcDixE/eLMKPHRaXenTgu1qXBqA==}
+ hasBin: true
+
+ keypress@0.1.0:
+ resolution: {integrity: sha512-x0yf9PL/nx9Nw9oLL8ZVErFAk85/lslwEP7Vz7s5SI1ODXZIgit3C5qyWjw4DxOuO/3Hb4866SQh28a1V1d+WA==}
+
+ luch-request@3.1.1:
+ resolution: {integrity: sha512-p7+mlcEtgRcd0OfXC4XZbyiwSr1XgCeqNT7LlVUjnk7InYl/8d5Rk7BUqAYNA2WRafI1wRIUQWRWZRpeUwWR0w==}
+
+snapshots:
+
+ '@dcloudio/types@2.6.12': {}
+
+ '@dcloudio/uni-ui@1.5.12': {}
+
+ async-validator@4.2.5: {}
+
+ commander@1.1.1:
+ dependencies:
+ keypress: 0.1.0
+
+ dayjs@1.11.21: {}
+
+ js-md5@0.7.3: {}
+
+ js@0.1.0:
+ dependencies:
+ commander: 1.1.1
+
+ keypress@0.1.0: {}
+
+ luch-request@3.1.1:
+ dependencies:
+ '@dcloudio/types': 2.6.12