feat(M08-B): 补保洁照片上传和自动建单

This commit is contained in:
Codex
2026-06-26 15:36:05 +08:00
parent f7e0c8d063
commit fe1a08880e
16 changed files with 373 additions and 57 deletions
+51 -10
View File
@@ -1,4 +1,4 @@
const { request, ensureLogin, cents } = require('../../utils/api.js')
const { request, uploadImage, ensureLogin, cents } = require('../../utils/api.js')
Page({
data: {
@@ -8,7 +8,7 @@ Page({
hallTasks: [],
myTasks: [],
stats: { byStatus: {}, pendingSettlementText: cents(0) },
submitPhotoText: '',
selectedPhotosByTask: {},
},
onShow() {
@@ -50,18 +50,44 @@ Page({
await this.mutateTask(event.currentTarget.dataset.taskId, 'start')
},
onPhotoTextInput(event) {
this.setData({ submitPhotoText: event.detail.value })
async choosePhotos(event) {
const taskId = event.currentTarget.dataset.taskId
if (!taskId) return
try {
const result = await wxChooseMedia(9)
const current = this.data.selectedPhotosByTask[taskId] || []
const next = current.concat(result.tempFiles.map((item) => item.tempFilePath)).slice(0, 9)
this.setData({ [`selectedPhotosByTask.${taskId}`]: next })
} catch (error) {
this.setData({ errorMessage: error.message || '选择照片失败' })
}
},
async submitTask(event) {
const photoUrls = this.data.submitPhotoText
.split('\n')
.map((item) => item.trim())
.filter(Boolean)
.slice(0, 9)
const taskId = event.currentTarget.dataset.taskId
const localPhotos = this.data.selectedPhotosByTask[taskId] || []
if (localPhotos.length === 0) {
this.setData({ errorMessage: '请先上传至少一张保洁照片' })
return
}
this.setData({ loading: true, errorMessage: '' })
let photoUrls = []
try {
await ensureLogin()
const uploaded = await Promise.all(localPhotos.map((filePath) =>
uploadImage(`/cleaning/tasks/${encodeURIComponent(taskId)}/photos`, filePath)
))
photoUrls = uploaded.map((item) => item.data.publicUrl).filter(Boolean)
} catch (error) {
this.setData({ loading: false, errorMessage: error.message || '照片上传失败' })
return
}
await this.mutateTask(event.currentTarget.dataset.taskId, 'submit', { photoUrls })
this.setData({ submitPhotoText: '' })
this.setData({ [`selectedPhotosByTask.${taskId}`]: [] })
},
async reworkTask(event) {
await this.mutateTask(event.currentTarget.dataset.taskId, 'rework')
},
async mutateTask(taskId, action, data = {}) {
@@ -90,6 +116,8 @@ function formatTask(task) {
canClaim: task.status === 'WAITING',
canStart: task.status === 'CLAIMED',
canSubmit: task.status === 'STARTED',
canRework: task.status === 'REJECTED',
canUpload: task.status === 'STARTED' || task.status === 'REJECTED',
}
}
@@ -100,6 +128,19 @@ function formatStats(stats) {
}
}
function wxChooseMedia(count) {
return new Promise((resolve, reject) => {
wx.chooseMedia({
count,
mediaType: ['image'],
sourceType: ['album', 'camera'],
sizeType: ['compressed'],
success: resolve,
fail: reject,
})
})
}
function formatDate(value) {
if (!value) return ''
const date = new Date(value)
+5 -2
View File
@@ -46,9 +46,12 @@
<text class="status">{{item.status}}</text>
<text class="amount">{{item.rewardText}}</text>
</view>
<view wx:if="{{item.rejectReason}}" class="warning">驳回:{{item.rejectReason}}</view>
<button wx:if="{{item.canStart}}" data-task-id="{{item.id}}" bindtap="startTask">开始</button>
<view wx:if="{{item.canSubmit}}" class="submit-box">
<textarea value="{{submitPhotoText}}" bindinput="onPhotoTextInput" placeholder="每行一个照片 URL" />
<button wx:if="{{item.canRework}}" data-task-id="{{item.id}}" bindtap="reworkTask">重做</button>
<view wx:if="{{item.canUpload}}" class="submit-box">
<button data-task-id="{{item.id}}" bindtap="choosePhotos">选择照片</button>
<view class="muted">至少 1 张,最多 9 张</view>
<button data-task-id="{{item.id}}" bindtap="submitTask">提交验收</button>
</view>
</view>
+6 -8
View File
@@ -83,15 +83,13 @@
margin-top: 16rpx;
}
textarea {
box-sizing: border-box;
width: 100%;
min-height: 144rpx;
margin-bottom: 16rpx;
padding: 18rpx;
border: 1rpx solid #d0d5dd;
.warning {
margin: 16rpx 0;
padding: 16rpx;
border-radius: 12rpx;
background: #f9fafb;
background: #fff4e5;
color: #9a3412;
font-size: 24rpx;
}
.error {
+45
View File
@@ -26,6 +26,43 @@ function request(path, options = {}) {
})
}
function uploadImage(path, filePath, headers = {}) {
return new Promise((resolve, reject) => {
const token = wx.getStorageSync(TOKEN_KEY)
wx.getFileSystemManager().readFile({
filePath,
success(result) {
wx.request({
url: `${APP_API_BASE_URL}${path}`,
method: 'POST',
data: result.data,
header: {
'x-wechat-appid': WECHAT_APP_ID,
'x-file-name': filePath.split('/').pop() || `cleaning-${Date.now()}.jpg`,
'x-image-content-type': guessImageType(filePath),
'content-type': 'application/octet-stream',
...(token ? { authorization: `Bearer ${token}` } : {}),
...(headers || {}),
},
success(response) {
if (response.statusCode >= 200 && response.statusCode < 300) {
try {
resolve(typeof response.data === 'string' ? JSON.parse(response.data) : response.data)
} catch (error) {
reject(error)
}
return
}
reject(new Error(response.data?.message || `上传失败:${response.statusCode}`))
},
fail: reject,
})
},
fail: reject,
})
})
}
function login() {
return new Promise((resolve, reject) => {
wx.login({
@@ -82,8 +119,16 @@ function requestWechatPayment(params) {
})
}
function guessImageType(filePath) {
const lower = String(filePath || '').toLowerCase()
if (lower.endsWith('.png')) return 'image/png'
if (lower.endsWith('.webp')) return 'image/webp'
return 'image/jpeg'
}
module.exports = {
request,
uploadImage,
login,
ensureLogin,
clearSession,