feat: harden phase one workflows

This commit is contained in:
王鹏
2026-07-14 16:04:51 +08:00
parent 47f4a24760
commit 767534df48
103 changed files with 4849 additions and 388 deletions

View File

@@ -1,10 +1,10 @@
const { createSecondGoods } = require('../../api/secondGoodsApi')
const { setField } = require('../../utils/request')
const { setField, uploadFile } = require('../../utils/request')
Page({
data: {
submitting: false,
imageText: '',
uploading: false,
form: {
title: '',
priceCent: 0,
@@ -28,14 +28,43 @@ Page({
this.setData({ 'form.tradeMethod': event.detail.value })
},
onImageInput(event) {
const imageText = event.detail.value
this.setData({
imageText,
'form.imageUrls': imageText ? imageText.split(',').map((item) => item.trim()).filter(Boolean) : []
chooseImages() {
if (this.data.uploading) {
return
}
wx.chooseMedia({
count: Math.max(1, 6 - this.data.form.imageUrls.length),
mediaType: ['image'],
sourceType: ['album', 'camera'],
success: (result) => {
const files = result.tempFiles || []
this.uploadImages(files.map((item) => item.tempFilePath))
}
})
},
uploadImages(filePaths) {
if (!filePaths.length) {
return
}
this.setData({ uploading: true })
Promise.all(filePaths.map((filePath) => uploadFile(filePath)))
.then((results) => {
const urls = results.map((item) => item.url).filter(Boolean)
this.setData({
'form.imageUrls': this.data.form.imageUrls.concat(urls).slice(0, 6)
})
})
.catch((error) => wx.showToast({ title: error.message, icon: 'none' }))
.finally(() => this.setData({ uploading: false }))
},
removeImage(event) {
const index = Number(event.currentTarget.dataset.index)
const next = this.data.form.imageUrls.filter((item, itemIndex) => itemIndex !== index)
this.setData({ 'form.imageUrls': next })
},
submit() {
if (this.data.submitting) {
return

View File

@@ -1,16 +1,40 @@
<view class="page">
<view class="section-title">发布闲置</view>
<view class="card">
<view class="page-heading">
<view class="heading-eyebrow">发布审核</view>
<view class="heading-title">发布闲置</view>
<view class="heading-subtitle">填写真实信息,审核通过后展示给邻居</view>
</view>
<view class="form-card publish-form">
<view class="field-label">标题</view>
<input class="input" placeholder="标题" data-field="title" bindinput="onFieldInput" />
<view class="field-label">价格</view>
<input class="input" placeholder="价格(元)" type="digit" bindinput="onPriceInput" />
<view class="field-label">分类</view>
<input class="input" placeholder="分类" data-field="category" bindinput="onFieldInput" />
<view class="field-label">联系电话</view>
<input class="input" placeholder="联系电话" data-field="contactPhone" bindinput="onFieldInput" />
<radio-group bindchange="onTradeMethodChange">
<view class="field-label">交易方式</view>
<radio-group class="radio-grid" bindchange="onTradeMethodChange">
<label class="radio"><radio value="FACE_TO_FACE" checked="{{form.tradeMethod === 'FACE_TO_FACE'}}" />面交</label>
<label class="radio"><radio value="DELIVERY" checked="{{form.tradeMethod === 'DELIVERY'}}" />可配送</label>
</radio-group>
<input class="input" placeholder="图片 URL多个用英文逗号分隔" value="{{imageText}}" bindinput="onImageInput" />
<view class="field-label">图片</view>
<view class="image-grid">
<view wx:for="{{form.imageUrls}}" wx:key="*this" class="image-item">
<image src="{{item}}" mode="aspectFill" />
<button size="mini" class="remove" data-index="{{index}}" bindtap="removeImage">删除</button>
</view>
<button wx:if="{{form.imageUrls.length < 6}}" class="image-picker" loading="{{uploading}}" bindtap="chooseImages">上传图片</button>
</view>
<view class="field-label">描述</view>
<textarea class="textarea" placeholder="描述" data-field="description" bindinput="onFieldInput" />
<button class="primary" loading="{{submitting}}" bindtap="submit">提交审核</button>
<button class="primary action-button" loading="{{submitting}}" bindtap="submit">提交审核</button>
</view>
</view>

View File

@@ -1,5 +1,78 @@
.radio {
display: block;
height: 60rpx;
line-height: 60rpx;
.page-heading {
margin-bottom: 22rpx;
}
.publish-form {
padding-bottom: 28rpx;
}
.radio-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 12rpx;
margin-bottom: 20rpx;
}
.radio {
min-height: 72rpx;
display: flex;
align-items: center;
padding: 0 14rpx;
color: #334155;
background: #f8fafc;
border: 1rpx solid #e2e8f0;
border-radius: 14rpx;
font-size: 24rpx;
}
.image-grid {
display: grid;
grid-template-columns: repeat(3, 1fr);
gap: 12rpx;
margin: 20rpx 0;
}
.image-item,
.image-picker {
width: 100%;
aspect-ratio: 1;
margin: 0;
}
.image-item {
position: relative;
overflow: hidden;
border-radius: 14rpx;
background: #eef3f7;
}
.image-item image {
width: 100%;
height: 100%;
}
.remove {
position: absolute;
right: 8rpx;
bottom: 8rpx;
margin: 0;
font-size: 22rpx;
color: #ffffff;
background: rgba(15, 23, 42, 0.62);
}
.image-picker {
display: flex;
align-items: center;
justify-content: center;
border: 1rpx dashed #8aa0b5;
color: #33536f;
background: #f8fafc;
border-radius: 14rpx;
font-size: 26rpx;
font-weight: 700;
}
.action-button {
margin-top: 8rpx;
}

View File

@@ -3,6 +3,7 @@ const { listSecondGoods } = require('../../api/secondGoodsApi')
Page({
data: {
loading: false,
keyword: '',
goods: []
},
@@ -16,12 +17,20 @@ Page({
load() {
this.setData({ loading: true })
listSecondGoods()
listSecondGoods(null, this.data.keyword)
.then((goods) => this.setData({ goods: goods || [] }))
.catch((error) => wx.showToast({ title: error.message, icon: 'none' }))
.finally(() => this.setData({ loading: false }))
},
onKeywordInput(event) {
this.setData({ keyword: event.detail.value })
},
search() {
this.load()
},
goCreate() {
wx.navigateTo({ url: '/pages/second-hand/create' })
},

View File

@@ -1,18 +1,36 @@
<view class="page">
<view class="row between section">
<view class="section-title">二手闲置</view>
<button size="mini" class="primary" bindtap="goCreate">发布</button>
</view>
<view wx:for="{{goods}}" wx:key="id" class="card">
<view class="row between">
<view>
<view>{{item.title}}</view>
<view class="muted">{{item.category}} / {{item.tradeMethod}}</view>
</view>
<view class="price">¥{{item.priceCent / 100}}</view>
<view class="page-heading row between">
<view>
<view class="heading-eyebrow">邻里流转</view>
<view class="heading-title">二手闲置</view>
<view class="heading-subtitle">发布闲置,线下联系交易</view>
</view>
<view class="muted">{{item.description}}</view>
<button size="mini" class="ghost" data-phone="{{item.contactPhone}}" bindtap="callSeller">联系卖家</button>
<button size="mini" class="primary publish-button" bindtap="goCreate">发布</button>
</view>
<view wx:if="{{!goods.length && !loading}}" class="card muted">暂无闲置</view>
<view class="search-row">
<input class="input search-input" placeholder="搜索闲置" value="{{keyword}}" bindinput="onKeywordInput" confirm-type="search" bindconfirm="search" />
<button size="mini" class="primary search-button" bindtap="search">搜索</button>
</view>
<view class="second-grid">
<view wx:for="{{goods}}" wx:key="id" class="second-card">
<image wx:if="{{item.imageUrls.length}}" class="second-cover" src="{{item.imageUrls[0]}}" mode="aspectFill" />
<view wx:else class="second-cover media-placeholder">闲</view>
<view class="second-body">
<view class="card-title">{{item.title}}</view>
<view class="row between">
<view class="pill">{{item.category}}</view>
<view class="price">¥{{item.priceCent / 100}}</view>
</view>
<view class="muted second-desc">{{item.description}}</view>
<view class="row between">
<view class="muted">{{item.tradeMethod}}</view>
<button size="mini" class="ghost contact-button" data-phone="{{item.contactPhone}}" bindtap="callSeller">联系</button>
</view>
</view>
</view>
</view>
<view wx:if="{{!goods.length && !loading}}" class="empty-state">暂无闲置</view>
</view>

View File

@@ -1,3 +1,68 @@
.ghost {
margin-top: 16rpx;
.page-heading {
align-items: flex-start;
gap: 18rpx;
}
.search-row {
display: flex;
gap: 14rpx;
align-items: center;
margin-bottom: 22rpx;
}
.search-input {
flex: 1;
margin: 0;
}
.search-button {
width: 132rpx;
margin: 0;
min-height: 80rpx;
}
.publish-button {
width: 124rpx;
margin: 8rpx 0 0;
}
.second-grid {
display: grid;
grid-template-columns: repeat(2, 1fr);
gap: 18rpx;
}
.second-card {
overflow: hidden;
background: #ffffff;
border: 1rpx solid #e4eaf0;
border-radius: 16rpx;
box-shadow: 0 8rpx 24rpx rgba(20, 36, 58, 0.05);
}
.second-cover {
width: 100%;
height: 210rpx;
}
.second-body {
padding: 18rpx;
display: flex;
flex-direction: column;
gap: 12rpx;
}
.second-desc {
min-height: 72rpx;
display: -webkit-box;
overflow: hidden;
text-overflow: ellipsis;
-webkit-line-clamp: 2;
-webkit-box-orient: vertical;
}
.contact-button {
min-width: 92rpx;
margin: 0;
padding: 0 12rpx;
}