feat: harden phase one workflows
This commit is contained in:
@@ -1,34 +1,48 @@
|
||||
<view class="page" wx:if="{{product}}">
|
||||
<view class="card">
|
||||
<view class="section-title">{{product.name}}</view>
|
||||
<view class="muted">{{product.description || '社区商品'}}</view>
|
||||
<view class="page detail-page" wx:if="{{product}}">
|
||||
<view class="detail-hero">
|
||||
<image wx:if="{{product.coverUrl}}" class="detail-cover" src="{{product.coverUrl}}" mode="aspectFill" />
|
||||
<view wx:else class="detail-cover media-placeholder">货</view>
|
||||
<view class="detail-info">
|
||||
<view class="tag">{{product.status}}</view>
|
||||
<view class="detail-title">{{product.name}}</view>
|
||||
<view class="muted">{{product.description || '社区商品'}}</view>
|
||||
<view wx:if="{{product.skus.length}}" class="detail-price">¥{{product.skus[skuIndex].priceCent / 100}}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="card">
|
||||
<view class="muted">规格</view>
|
||||
<view class="purchase-card form-card" wx:if="{{product.skus.length}}">
|
||||
<view class="card-title form-title">下单信息</view>
|
||||
<view class="field-label">规格</view>
|
||||
<picker mode="selector" range="{{product.skus}}" range-key="skuName" value="{{skuIndex}}" bindchange="onSkuChange">
|
||||
<view class="picker">{{product.skus[skuIndex].skuName}} / ¥{{product.skus[skuIndex].priceCent / 100}} / 库存 {{product.skus[skuIndex].stock}}</view>
|
||||
</picker>
|
||||
<view class="muted">数量</view>
|
||||
|
||||
<view class="field-label">数量</view>
|
||||
<input class="input" type="number" value="{{form.quantity}}" bindinput="onQuantityChange" />
|
||||
<view class="muted">配送方式</view>
|
||||
<radio-group bindchange="onMethodChange">
|
||||
|
||||
<view class="field-label">配送方式</view>
|
||||
<radio-group class="radio-grid" bindchange="onMethodChange">
|
||||
<label class="radio"><radio value="IMMEDIATE" checked="{{form.deliveryMethod === 'IMMEDIATE'}}" />立即配送</label>
|
||||
<label class="radio"><radio value="SCHEDULED" checked="{{form.deliveryMethod === 'SCHEDULED'}}" />预约配送</label>
|
||||
<label class="radio"><radio value="SELF_PICKUP" checked="{{form.deliveryMethod === 'SELF_PICKUP'}}" />自提</label>
|
||||
</radio-group>
|
||||
|
||||
<view wx:if="{{form.deliveryMethod !== 'SELF_PICKUP'}}">
|
||||
<view class="muted">收货地址</view>
|
||||
<view class="field-label">收货地址</view>
|
||||
<picker mode="selector" range="{{addresses}}" range-key="detail" value="{{addressIndex}}" bindchange="onAddressChange">
|
||||
<view class="picker">{{addresses[addressIndex] ? addresses[addressIndex].building + ' ' + addresses[addressIndex].room + ' ' + addresses[addressIndex].detail : '请选择地址'}}</view>
|
||||
</picker>
|
||||
</view>
|
||||
|
||||
<view wx:if="{{form.deliveryMethod === 'SCHEDULED'}}">
|
||||
<view class="muted">预约时间</view>
|
||||
<view class="field-label">预约时间</view>
|
||||
<input class="input" placeholder="2026-07-07T18:30:00" bindinput="onScheduledTimeChange" />
|
||||
</view>
|
||||
<view class="muted">备注</view>
|
||||
<textarea class="textarea" bindinput="onRemarkInput" />
|
||||
<button class="primary" loading="{{submitting}}" bindtap="submit">提交订单</button>
|
||||
|
||||
<view class="field-label">备注</view>
|
||||
<textarea class="textarea" placeholder="口味、送达说明等" bindinput="onRemarkInput" />
|
||||
<button class="primary action-button" loading="{{submitting}}" bindtap="submit">提交订单</button>
|
||||
</view>
|
||||
|
||||
<view wx:else class="empty-state">暂无可选规格</view>
|
||||
</view>
|
||||
|
||||
@@ -1,5 +1,63 @@
|
||||
.radio {
|
||||
display: block;
|
||||
height: 60rpx;
|
||||
line-height: 60rpx;
|
||||
.detail-page {
|
||||
padding-bottom: 60rpx;
|
||||
}
|
||||
|
||||
.detail-hero {
|
||||
overflow: hidden;
|
||||
margin-bottom: 22rpx;
|
||||
background: #ffffff;
|
||||
border: 1rpx solid #e4eaf0;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 8rpx 24rpx rgba(20, 36, 58, 0.05);
|
||||
}
|
||||
|
||||
.detail-cover {
|
||||
width: 100%;
|
||||
height: 360rpx;
|
||||
}
|
||||
|
||||
.detail-info {
|
||||
padding: 26rpx;
|
||||
}
|
||||
|
||||
.detail-title {
|
||||
margin-top: 14rpx;
|
||||
color: #111827;
|
||||
font-size: 40rpx;
|
||||
font-weight: 900;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.detail-price {
|
||||
margin-top: 18rpx;
|
||||
color: #c2410c;
|
||||
font-size: 42rpx;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.purchase-card {
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.form-title {
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.radio-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 12rpx;
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.radio {
|
||||
min-height: 72rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 12rpx;
|
||||
background: #f8fafc;
|
||||
border: 1rpx solid #e2e8f0;
|
||||
border-radius: 14rpx;
|
||||
color: #334155;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ Page({
|
||||
loading: false,
|
||||
categories: [],
|
||||
activeCategoryId: null,
|
||||
keyword: '',
|
||||
products: []
|
||||
},
|
||||
|
||||
@@ -14,7 +15,7 @@ Page({
|
||||
|
||||
load() {
|
||||
this.setData({ loading: true })
|
||||
Promise.all([listCategories(), listProducts()])
|
||||
Promise.all([listCategories(), listProducts(null, this.data.keyword)])
|
||||
.then(([categories, products]) => {
|
||||
this.setData({
|
||||
categories: categories || [],
|
||||
@@ -28,14 +29,24 @@ Page({
|
||||
chooseCategory(event) {
|
||||
const categoryId = event.currentTarget.dataset.id
|
||||
this.setData({ activeCategoryId: categoryId })
|
||||
listProducts(categoryId)
|
||||
listProducts(categoryId, this.data.keyword)
|
||||
.then((products) => this.setData({ products: products || [] }))
|
||||
.catch((error) => wx.showToast({ title: error.message, icon: 'none' }))
|
||||
},
|
||||
|
||||
clearCategory() {
|
||||
this.setData({ activeCategoryId: null })
|
||||
listProducts()
|
||||
listProducts(null, this.data.keyword)
|
||||
.then((products) => this.setData({ products: products || [] }))
|
||||
.catch((error) => wx.showToast({ title: error.message, icon: 'none' }))
|
||||
},
|
||||
|
||||
onKeywordInput(event) {
|
||||
this.setData({ keyword: event.detail.value })
|
||||
},
|
||||
|
||||
search() {
|
||||
listProducts(this.data.activeCategoryId, this.data.keyword)
|
||||
.then((products) => this.setData({ products: products || [] }))
|
||||
.catch((error) => wx.showToast({ title: error.message, icon: 'none' }))
|
||||
},
|
||||
|
||||
@@ -1,5 +1,15 @@
|
||||
<view class="page">
|
||||
<view class="section-title">商品预定</view>
|
||||
<view class="page-heading">
|
||||
<view class="heading-eyebrow">社区便利店</view>
|
||||
<view class="heading-title">商品预定</view>
|
||||
<view class="heading-subtitle">挑好商品,社区内配送或自提</view>
|
||||
</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>
|
||||
|
||||
<scroll-view scroll-x class="category-scroll">
|
||||
<view class="category-item {{!activeCategoryId ? 'active' : ''}}" bindtap="clearCategory">全部</view>
|
||||
<view
|
||||
@@ -13,16 +23,24 @@
|
||||
</view>
|
||||
</scroll-view>
|
||||
|
||||
<view wx:for="{{products}}" wx:key="id" class="card" bindtap="goDetail" data-id="{{item.id}}">
|
||||
<view class="row between">
|
||||
<view>
|
||||
<view>{{item.name}}</view>
|
||||
<view class="muted">{{item.description || '社区精选商品'}}</view>
|
||||
<view class="product-list">
|
||||
<view wx:for="{{products}}" wx:key="id" class="product-card" bindtap="goDetail" data-id="{{item.id}}">
|
||||
<image wx:if="{{item.coverUrl}}" class="product-thumb" src="{{item.coverUrl}}" mode="aspectFill" />
|
||||
<view wx:else class="product-thumb media-placeholder">货</view>
|
||||
<view class="product-info">
|
||||
<view class="row between">
|
||||
<view class="card-title">{{item.name}}</view>
|
||||
<view class="tag">{{item.status}}</view>
|
||||
</view>
|
||||
<view class="muted product-desc">{{item.description || '社区精选商品'}}</view>
|
||||
<view class="row between product-footer">
|
||||
<view wx:if="{{item.unitName}}" class="pill">{{item.unitName}}</view>
|
||||
<view wx:else class="pill">社区好物</view>
|
||||
<view wx:if="{{item.skus.length}}" class="price">¥{{item.skus[0].priceCent / 100}} 起</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="tag">{{item.status}}</view>
|
||||
</view>
|
||||
<view wx:if="{{item.skus.length}}" class="price">¥{{item.skus[0].priceCent / 100}} 起</view>
|
||||
</view>
|
||||
|
||||
<view wx:if="{{!products.length && !loading}}" class="card muted">暂无商品</view>
|
||||
<view wx:if="{{!products.length && !loading}}" class="empty-state">暂无商品</view>
|
||||
</view>
|
||||
|
||||
@@ -1,6 +1,28 @@
|
||||
.page-heading {
|
||||
padding-bottom: 4rpx;
|
||||
}
|
||||
|
||||
.category-scroll {
|
||||
white-space: nowrap;
|
||||
margin-bottom: 20rpx;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.search-row {
|
||||
display: flex;
|
||||
gap: 14rpx;
|
||||
align-items: center;
|
||||
margin-bottom: 18rpx;
|
||||
}
|
||||
|
||||
.search-input {
|
||||
flex: 1;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
.search-button {
|
||||
width: 132rpx;
|
||||
margin: 0;
|
||||
min-height: 80rpx;
|
||||
}
|
||||
|
||||
.category-item {
|
||||
@@ -8,12 +30,15 @@
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-width: 120rpx;
|
||||
height: 64rpx;
|
||||
padding: 0 20rpx;
|
||||
height: 68rpx;
|
||||
padding: 0 24rpx;
|
||||
margin-right: 12rpx;
|
||||
background: #ffffff;
|
||||
border: 1rpx solid #d9e0e8;
|
||||
border-radius: 6rpx;
|
||||
border-radius: 999rpx;
|
||||
color: #334155;
|
||||
font-size: 25rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.category-item.active {
|
||||
@@ -21,3 +46,43 @@
|
||||
background: #0f766e;
|
||||
border-color: #0f766e;
|
||||
}
|
||||
|
||||
.product-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 18rpx;
|
||||
}
|
||||
|
||||
.product-card {
|
||||
display: flex;
|
||||
gap: 20rpx;
|
||||
padding: 20rpx;
|
||||
background: #ffffff;
|
||||
border: 1rpx solid #e4eaf0;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 8rpx 24rpx rgba(20, 36, 58, 0.05);
|
||||
}
|
||||
|
||||
.product-thumb {
|
||||
width: 160rpx;
|
||||
height: 160rpx;
|
||||
flex-shrink: 0;
|
||||
overflow: hidden;
|
||||
border-radius: 14rpx;
|
||||
}
|
||||
|
||||
.product-info {
|
||||
min-width: 0;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 12rpx;
|
||||
}
|
||||
|
||||
.product-desc {
|
||||
min-height: 64rpx;
|
||||
}
|
||||
|
||||
.product-footer {
|
||||
margin-top: auto;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user