feat: harden phase one workflows
This commit is contained in:
@@ -4,11 +4,21 @@ function listCategories() {
|
||||
return request({ url: '/api/mini/product-categories' })
|
||||
}
|
||||
|
||||
function listProducts(categoryId) {
|
||||
function listProducts(categoryId, keyword) {
|
||||
return request({
|
||||
url: '/api/mini/products',
|
||||
data: categoryId ? { categoryId } : {}
|
||||
})
|
||||
url: '/api/mini/products/page',
|
||||
data: buildProductQuery(categoryId, keyword)
|
||||
}).then((page) => page && page.records ? page.records : [])
|
||||
}
|
||||
|
||||
function buildProductQuery(categoryId, keyword) {
|
||||
const data = {
|
||||
pageNo: 1,
|
||||
pageSize: 20
|
||||
}
|
||||
if (categoryId) data.categoryId = categoryId
|
||||
if (keyword) data.keyword = keyword
|
||||
return data
|
||||
}
|
||||
|
||||
function getProductDetail(id) {
|
||||
@@ -26,6 +36,7 @@ function createGoodsOrder(data) {
|
||||
module.exports = {
|
||||
listCategories,
|
||||
listProducts,
|
||||
buildProductQuery,
|
||||
getProductDetail,
|
||||
createGoodsOrder
|
||||
}
|
||||
|
||||
12
miniapp/miniprogram/api/productApi.test.js
Normal file
12
miniapp/miniprogram/api/productApi.test.js
Normal file
@@ -0,0 +1,12 @@
|
||||
const { buildProductQuery } = require('./productApi')
|
||||
|
||||
describe('buildProductQuery', () => {
|
||||
test('keeps pagination and optional filters stable', () => {
|
||||
expect(buildProductQuery(2, '苹果')).toEqual({
|
||||
pageNo: 1,
|
||||
pageSize: 20,
|
||||
categoryId: 2,
|
||||
keyword: '苹果'
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,10 +1,20 @@
|
||||
const { request } = require('../utils/request')
|
||||
|
||||
function listSecondGoods(category) {
|
||||
function listSecondGoods(category, keyword) {
|
||||
return request({
|
||||
url: '/api/mini/second-goods',
|
||||
data: category ? { category } : {}
|
||||
})
|
||||
url: '/api/mini/second-goods/page',
|
||||
data: buildSecondGoodsQuery(category, keyword)
|
||||
}).then((page) => page && page.records ? page.records : [])
|
||||
}
|
||||
|
||||
function buildSecondGoodsQuery(category, keyword) {
|
||||
const data = {
|
||||
pageNo: 1,
|
||||
pageSize: 20
|
||||
}
|
||||
if (category) data.category = category
|
||||
if (keyword) data.keyword = keyword
|
||||
return data
|
||||
}
|
||||
|
||||
function getSecondGoodsDetail(id) {
|
||||
@@ -21,6 +31,7 @@ function createSecondGoods(data) {
|
||||
|
||||
module.exports = {
|
||||
listSecondGoods,
|
||||
buildSecondGoodsQuery,
|
||||
getSecondGoodsDetail,
|
||||
createSecondGoods
|
||||
}
|
||||
|
||||
12
miniapp/miniprogram/api/secondGoodsApi.test.js
Normal file
12
miniapp/miniprogram/api/secondGoodsApi.test.js
Normal file
@@ -0,0 +1,12 @@
|
||||
const { buildSecondGoodsQuery } = require('./secondGoodsApi')
|
||||
|
||||
describe('buildSecondGoodsQuery', () => {
|
||||
test('keeps pagination and optional filters stable', () => {
|
||||
expect(buildSecondGoodsQuery('母婴', '儿童')).toEqual({
|
||||
pageNo: 1,
|
||||
pageSize: 20,
|
||||
category: '母婴',
|
||||
keyword: '儿童'
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -1,6 +1,7 @@
|
||||
App({
|
||||
globalData: {
|
||||
apiBaseUrl: 'http://localhost:8080',
|
||||
useDevLogin: true,
|
||||
token: '',
|
||||
user: null
|
||||
}
|
||||
|
||||
@@ -1,31 +1,87 @@
|
||||
page {
|
||||
background: #f4f6f8;
|
||||
color: #17212b;
|
||||
background: #f3f6f8;
|
||||
color: #16212f;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Helvetica Neue", sans-serif;
|
||||
font-size: 28rpx;
|
||||
line-height: 1.5;
|
||||
}
|
||||
|
||||
view,
|
||||
text,
|
||||
input,
|
||||
textarea,
|
||||
button {
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.page {
|
||||
min-height: 100vh;
|
||||
padding: 24rpx;
|
||||
padding: 28rpx 24rpx 48rpx;
|
||||
}
|
||||
|
||||
.section {
|
||||
.page-heading {
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.heading-eyebrow {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
height: 40rpx;
|
||||
padding: 0 14rpx;
|
||||
color: #0f766e;
|
||||
background: #e6f4f1;
|
||||
border-radius: 999rpx;
|
||||
font-size: 22rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.heading-title {
|
||||
margin-top: 10rpx;
|
||||
color: #111827;
|
||||
font-size: 40rpx;
|
||||
font-weight: 800;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.heading-subtitle {
|
||||
margin-top: 8rpx;
|
||||
color: #64748b;
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.section {
|
||||
margin-bottom: 28rpx;
|
||||
}
|
||||
|
||||
.section-header {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
margin-bottom: 16rpx;
|
||||
}
|
||||
|
||||
.section-title {
|
||||
margin: 0 0 16rpx;
|
||||
font-size: 32rpx;
|
||||
font-weight: 700;
|
||||
margin: 0;
|
||||
color: #111827;
|
||||
font-size: 34rpx;
|
||||
font-weight: 800;
|
||||
line-height: 1.25;
|
||||
}
|
||||
|
||||
.card {
|
||||
background: #ffffff;
|
||||
border: 1rpx solid #e5e9ef;
|
||||
border-radius: 8rpx;
|
||||
padding: 20rpx;
|
||||
border: 1rpx solid #e4eaf0;
|
||||
border-radius: 16rpx;
|
||||
padding: 24rpx;
|
||||
margin-bottom: 16rpx;
|
||||
box-shadow: 0 8rpx 24rpx rgba(20, 36, 58, 0.05);
|
||||
}
|
||||
|
||||
.card-title {
|
||||
color: #111827;
|
||||
font-size: 30rpx;
|
||||
font-weight: 700;
|
||||
line-height: 1.35;
|
||||
}
|
||||
|
||||
.row {
|
||||
@@ -38,6 +94,12 @@ page {
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.stack {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 10rpx;
|
||||
}
|
||||
|
||||
.muted {
|
||||
color: #64748b;
|
||||
font-size: 24rpx;
|
||||
@@ -49,12 +111,29 @@ page {
|
||||
}
|
||||
|
||||
.tag {
|
||||
display: inline-block;
|
||||
padding: 4rpx 12rpx;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 36rpx;
|
||||
padding: 4rpx 14rpx;
|
||||
background: #e6f4f1;
|
||||
color: #0f766e;
|
||||
border-radius: 6rpx;
|
||||
border-radius: 999rpx;
|
||||
font-size: 22rpx;
|
||||
font-weight: 700;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.pill {
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
height: 44rpx;
|
||||
padding: 0 16rpx;
|
||||
color: #33536f;
|
||||
background: #edf4f8;
|
||||
border-radius: 999rpx;
|
||||
font-size: 22rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
.grid {
|
||||
@@ -72,7 +151,7 @@ page {
|
||||
justify-content: center;
|
||||
background: #ffffff;
|
||||
border: 1rpx solid #e5e9ef;
|
||||
border-radius: 8rpx;
|
||||
border-radius: 16rpx;
|
||||
font-weight: 600;
|
||||
}
|
||||
|
||||
@@ -80,17 +159,41 @@ page {
|
||||
.textarea,
|
||||
.picker {
|
||||
width: 100%;
|
||||
min-height: 76rpx;
|
||||
padding: 0 18rpx;
|
||||
min-height: 84rpx;
|
||||
padding: 0 22rpx;
|
||||
background: #ffffff;
|
||||
border: 1rpx solid #d9e0e8;
|
||||
border-radius: 6rpx;
|
||||
margin-bottom: 16rpx;
|
||||
border: 1rpx solid #d9e2ea;
|
||||
border-radius: 14rpx;
|
||||
margin-bottom: 20rpx;
|
||||
color: #17212b;
|
||||
font-size: 28rpx;
|
||||
}
|
||||
|
||||
.picker {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
color: #334155;
|
||||
}
|
||||
|
||||
.textarea {
|
||||
min-height: 140rpx;
|
||||
padding-top: 16rpx;
|
||||
padding-top: 18rpx;
|
||||
}
|
||||
|
||||
.field-label {
|
||||
display: block;
|
||||
margin: 6rpx 0 12rpx;
|
||||
color: #334155;
|
||||
font-size: 24rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.form-card {
|
||||
background: #ffffff;
|
||||
border: 1rpx solid #e4eaf0;
|
||||
border-radius: 16rpx;
|
||||
padding: 24rpx;
|
||||
box-shadow: 0 8rpx 24rpx rgba(20, 36, 58, 0.05);
|
||||
}
|
||||
|
||||
.button-row {
|
||||
@@ -99,12 +202,19 @@ page {
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 6rpx;
|
||||
border-radius: 14rpx;
|
||||
font-size: 28rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
button::after {
|
||||
border: none;
|
||||
}
|
||||
|
||||
.primary {
|
||||
background: #0f766e;
|
||||
color: #ffffff;
|
||||
border: 1rpx solid #0f766e;
|
||||
}
|
||||
|
||||
.ghost {
|
||||
@@ -112,3 +222,38 @@ button {
|
||||
color: #0f766e;
|
||||
border: 1rpx solid #0f766e;
|
||||
}
|
||||
|
||||
.action-button {
|
||||
min-height: 88rpx;
|
||||
margin: 0;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.empty-state {
|
||||
display: flex;
|
||||
min-height: 180rpx;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #64748b;
|
||||
background: #ffffff;
|
||||
border: 1rpx dashed #cbd5e1;
|
||||
border-radius: 16rpx;
|
||||
font-size: 26rpx;
|
||||
}
|
||||
|
||||
.media-placeholder {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #0f766e;
|
||||
background: #e6f4f1;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.divider {
|
||||
height: 1rpx;
|
||||
margin: 20rpx 0;
|
||||
background: #edf1f5;
|
||||
}
|
||||
|
||||
@@ -1,19 +1,41 @@
|
||||
<view class="page">
|
||||
<view class="section-title">地址管理</view>
|
||||
<view class="card">
|
||||
<input class="input" placeholder="联系人" data-field="contactName" value="{{form.contactName}}" bindinput="onFieldInput" />
|
||||
<input class="input" placeholder="手机号" data-field="phone" value="{{form.phone}}" bindinput="onFieldInput" />
|
||||
<input class="input" placeholder="楼栋" data-field="building" value="{{form.building}}" bindinput="onFieldInput" />
|
||||
<input class="input" placeholder="房号" data-field="room" value="{{form.room}}" bindinput="onFieldInput" />
|
||||
<input class="input" placeholder="详细地址" data-field="detail" value="{{form.detail}}" bindinput="onFieldInput" />
|
||||
<button class="primary" loading="{{submitting}}" bindtap="submit">保存地址</button>
|
||||
<view class="page-heading">
|
||||
<view class="heading-eyebrow">配送资料</view>
|
||||
<view class="heading-title">地址管理</view>
|
||||
<view class="heading-subtitle">保存常用住址,提交订单更快</view>
|
||||
</view>
|
||||
<view wx:for="{{addresses}}" wx:key="id" class="card">
|
||||
<view class="row between">
|
||||
<view>{{item.contactName}} {{item.phone}}</view>
|
||||
|
||||
<view class="address-form form-card">
|
||||
<view class="card-title form-title">新增地址</view>
|
||||
<view class="field-label">联系人</view>
|
||||
<input class="input" placeholder="联系人" data-field="contactName" value="{{form.contactName}}" bindinput="onFieldInput" />
|
||||
|
||||
<view class="field-label">手机号</view>
|
||||
<input class="input" placeholder="手机号" data-field="phone" value="{{form.phone}}" bindinput="onFieldInput" />
|
||||
|
||||
<view class="field-label">楼栋</view>
|
||||
<input class="input" placeholder="楼栋" data-field="building" value="{{form.building}}" bindinput="onFieldInput" />
|
||||
|
||||
<view class="field-label">房号</view>
|
||||
<input class="input" placeholder="房号" data-field="room" value="{{form.room}}" bindinput="onFieldInput" />
|
||||
|
||||
<view class="field-label">详细地址</view>
|
||||
<input class="input" placeholder="详细地址" data-field="detail" value="{{form.detail}}" bindinput="onFieldInput" />
|
||||
<button class="primary action-button" loading="{{submitting}}" bindtap="submit">保存地址</button>
|
||||
</view>
|
||||
|
||||
<view class="section-header address-header">
|
||||
<view class="section-title">已保存地址</view>
|
||||
</view>
|
||||
|
||||
<view wx:for="{{addresses}}" wx:key="id" class="address-card">
|
||||
<view class="row between address-top">
|
||||
<view class="card-title">{{item.contactName}} {{item.phone}}</view>
|
||||
<view wx:if="{{item.default}}" class="tag">默认</view>
|
||||
</view>
|
||||
<view class="muted">{{item.building}} {{item.room}} {{item.detail}}</view>
|
||||
<button wx:if="{{!item.default}}" size="mini" class="ghost" data-id="{{item.id}}" bindtap="setDefault">设为默认</button>
|
||||
<view class="address-detail">{{item.building}} {{item.room}} {{item.detail}}</view>
|
||||
<button wx:if="{{!item.default}}" size="mini" class="ghost default-button" data-id="{{item.id}}" bindtap="setDefault">设为默认</button>
|
||||
</view>
|
||||
|
||||
<view wx:if="{{!addresses.length}}" class="empty-state">暂无地址</view>
|
||||
</view>
|
||||
|
||||
@@ -1,3 +1,41 @@
|
||||
.ghost {
|
||||
margin-top: 16rpx;
|
||||
.page-heading {
|
||||
margin-bottom: 22rpx;
|
||||
}
|
||||
|
||||
.address-form {
|
||||
margin-bottom: 28rpx;
|
||||
}
|
||||
|
||||
.form-title {
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.address-header {
|
||||
margin-bottom: 14rpx;
|
||||
}
|
||||
|
||||
.address-card {
|
||||
padding: 24rpx;
|
||||
margin-bottom: 16rpx;
|
||||
background: #ffffff;
|
||||
border: 1rpx solid #e4eaf0;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 8rpx 24rpx rgba(20, 36, 58, 0.05);
|
||||
}
|
||||
|
||||
.address-top {
|
||||
align-items: flex-start;
|
||||
gap: 18rpx;
|
||||
}
|
||||
|
||||
.address-detail {
|
||||
margin-top: 12rpx;
|
||||
color: #475569;
|
||||
font-size: 26rpx;
|
||||
line-height: 42rpx;
|
||||
}
|
||||
|
||||
.default-button {
|
||||
margin: 18rpx 0 0;
|
||||
min-width: 154rpx;
|
||||
}
|
||||
|
||||
@@ -1,23 +1,46 @@
|
||||
<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="express-tips">
|
||||
<view class="tips-title">收费规则</view>
|
||||
<view class="muted">首件 3 元,第二件起每件加 1 元,异常订单后台可调整。</view>
|
||||
</view>
|
||||
|
||||
<view class="form-card express-form">
|
||||
<view class="field-label">快递公司</view>
|
||||
<input class="input" placeholder="快递公司" data-field="expressCompany" bindinput="onFieldInput" />
|
||||
|
||||
<view class="field-label">取件码</view>
|
||||
<input class="input" placeholder="取件码" data-field="pickupCode" bindinput="onFieldInput" />
|
||||
|
||||
<view class="field-label">快递站地址</view>
|
||||
<input class="input" placeholder="快递站地址" data-field="pickupAddress" bindinput="onFieldInput" />
|
||||
|
||||
<view class="field-label">手机号</view>
|
||||
<input class="input" placeholder="手机号" data-field="receiverPhone" bindinput="onFieldInput" />
|
||||
<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 class="field-label">件数</view>
|
||||
<input class="input" type="number" value="{{form.packageCount}}" bindinput="onPackageCountChange" />
|
||||
<view class="card">
|
||||
|
||||
<view class="fee-panel">
|
||||
<view class="row between">
|
||||
<text>预估服务费</text>
|
||||
<text class="price">¥{{feeCent / 100}}</text>
|
||||
</view>
|
||||
<view class="muted">首件 3 元,续件每件 1 元</view>
|
||||
</view>
|
||||
|
||||
<view class="field-label">备注</view>
|
||||
<textarea class="textarea" placeholder="备注" data-field="remark" bindinput="onFieldInput" />
|
||||
<button class="primary" loading="{{submitting}}" bindtap="submit">提交代取</button>
|
||||
<button class="primary action-button" loading="{{submitting}}" bindtap="submit">提交代取</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -1,3 +1,46 @@
|
||||
.card .card {
|
||||
margin-top: 0;
|
||||
.page-heading {
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.express-tips {
|
||||
padding: 22rpx;
|
||||
margin-bottom: 18rpx;
|
||||
background: #fff7ed;
|
||||
border: 1rpx solid #fed7aa;
|
||||
border-radius: 16rpx;
|
||||
}
|
||||
|
||||
.tips-title {
|
||||
margin-bottom: 8rpx;
|
||||
color: #9a3412;
|
||||
font-size: 28rpx;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.form-card {
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.express-form {
|
||||
padding-bottom: 28rpx;
|
||||
}
|
||||
|
||||
.field-label {
|
||||
margin-top: 4rpx;
|
||||
}
|
||||
|
||||
.fee-panel {
|
||||
padding: 22rpx;
|
||||
margin-bottom: 20rpx;
|
||||
background: #f8fafc;
|
||||
border: 1rpx solid #e2e8f0;
|
||||
border-radius: 16rpx;
|
||||
}
|
||||
|
||||
.fee-panel .price {
|
||||
font-size: 36rpx;
|
||||
}
|
||||
|
||||
.action-button {
|
||||
margin-top: 6rpx;
|
||||
}
|
||||
|
||||
@@ -1,25 +1,42 @@
|
||||
<view class="page" wx:if="{{groupBuy}}">
|
||||
<view class="card">
|
||||
<view class="section-title">{{groupBuy.title}}</view>
|
||||
<view class="price">¥{{groupBuy.priceCent / 100}}</view>
|
||||
<view class="muted">{{groupBuy.description || '社区团购'}}</view>
|
||||
<view class="muted">自提点:{{groupBuy.pickupAddress}}</view>
|
||||
<view class="page detail-page" wx:if="{{groupBuy}}">
|
||||
<view class="detail-hero">
|
||||
<image wx:if="{{groupBuy.coverUrl}}" class="detail-cover" src="{{groupBuy.coverUrl}}" mode="aspectFill" />
|
||||
<view wx:else class="detail-cover media-placeholder">团</view>
|
||||
<view class="detail-info">
|
||||
<view class="tag">{{groupBuy.status}}</view>
|
||||
<view class="detail-title">{{groupBuy.title}}</view>
|
||||
<view class="detail-price">¥{{groupBuy.priceCent / 100}}</view>
|
||||
<view wx:if="{{groupBuy.originPriceCent}}" class="origin-price">原价 ¥{{groupBuy.originPriceCent / 100}}</view>
|
||||
<view class="muted">{{groupBuy.description || '社区团购'}}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="card">
|
||||
<view class="muted">数量</view>
|
||||
|
||||
<view class="delivery-panel">
|
||||
<view class="panel-label">自提点</view>
|
||||
<view class="panel-value">{{groupBuy.pickupAddress}}</view>
|
||||
<view class="muted">截单 {{groupBuy.endTime}}</view>
|
||||
</view>
|
||||
|
||||
<view class="purchase-card form-card">
|
||||
<view class="card-title form-title">团购下单</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="SELF_PICKUP" checked="{{form.deliveryMethod === 'SELF_PICKUP'}}" />自提</label>
|
||||
<label class="radio"><radio value="IMMEDIATE" checked="{{form.deliveryMethod === 'IMMEDIATE'}}" />配送</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 class="field-label">备注</view>
|
||||
<textarea class="textarea" placeholder="备注" bindinput="onRemarkInput" />
|
||||
<button class="primary" loading="{{submitting}}" bindtap="submit">提交团购订单</button>
|
||||
<button class="primary action-button" loading="{{submitting}}" bindtap="submit">提交团购订单</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -1,5 +1,91 @@
|
||||
.radio {
|
||||
display: block;
|
||||
height: 60rpx;
|
||||
line-height: 60rpx;
|
||||
.detail-page {
|
||||
padding-bottom: 60rpx;
|
||||
}
|
||||
|
||||
.detail-hero {
|
||||
overflow: hidden;
|
||||
margin-bottom: 18rpx;
|
||||
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: 16rpx;
|
||||
color: #c2410c;
|
||||
font-size: 44rpx;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.origin-price {
|
||||
margin-bottom: 12rpx;
|
||||
color: #94a3b8;
|
||||
font-size: 24rpx;
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
.delivery-panel {
|
||||
padding: 22rpx;
|
||||
margin-bottom: 18rpx;
|
||||
background: #eff6ff;
|
||||
border: 1rpx solid #bfdbfe;
|
||||
border-radius: 16rpx;
|
||||
}
|
||||
|
||||
.panel-label {
|
||||
color: #1d4ed8;
|
||||
font-size: 22rpx;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.panel-value {
|
||||
margin: 8rpx 0;
|
||||
color: #111827;
|
||||
font-size: 30rpx;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.purchase-card {
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.form-title {
|
||||
margin-bottom: 20rpx;
|
||||
}
|
||||
|
||||
.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;
|
||||
}
|
||||
|
||||
@@ -1,14 +1,29 @@
|
||||
<view class="page">
|
||||
<view class="section-title">今日团购</view>
|
||||
<view wx:for="{{groupBuys}}" wx:key="id" class="card" bindtap="goDetail" data-id="{{item.id}}">
|
||||
<view class="row between">
|
||||
<view>
|
||||
<view>{{item.title}}</view>
|
||||
<view class="muted">截单 {{item.endTime}}</view>
|
||||
</view>
|
||||
<view class="price">¥{{item.priceCent / 100}}</view>
|
||||
</view>
|
||||
<view class="muted">库存 {{item.stock}} / 已售 {{item.soldCount}}</view>
|
||||
<view class="page-heading">
|
||||
<view class="heading-eyebrow">社区拼单</view>
|
||||
<view class="heading-title">今日团购</view>
|
||||
<view class="heading-subtitle">集中采购,邻里共享实惠</view>
|
||||
</view>
|
||||
<view wx:if="{{!groupBuys.length && !loading}}" class="card muted">暂无团购</view>
|
||||
|
||||
<view class="group-list">
|
||||
<view wx:for="{{groupBuys}}" wx:key="id" class="group-card" bindtap="goDetail" data-id="{{item.id}}">
|
||||
<image wx:if="{{item.coverUrl}}" class="group-cover" src="{{item.coverUrl}}" mode="aspectFill" />
|
||||
<view wx:else class="group-cover media-placeholder">团</view>
|
||||
<view class="group-body">
|
||||
<view class="row between">
|
||||
<view class="card-title">{{item.title}}</view>
|
||||
<view class="price">¥{{item.priceCent / 100}}</view>
|
||||
</view>
|
||||
<view class="muted">截单 {{item.endTime}}</view>
|
||||
<view class="stock-line">
|
||||
<view class="stock-track">
|
||||
<view class="stock-fill"></view>
|
||||
</view>
|
||||
<view class="muted">库存 {{item.stock}} / 已售 {{item.soldCount}}</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view wx:if="{{!groupBuys.length && !loading}}" class="empty-state">暂无团购</view>
|
||||
</view>
|
||||
|
||||
@@ -1,4 +1,51 @@
|
||||
.page-heading {
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.group-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 18rpx;
|
||||
}
|
||||
|
||||
.group-card {
|
||||
overflow: hidden;
|
||||
background: #ffffff;
|
||||
border: 1rpx solid #e4eaf0;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 8rpx 24rpx rgba(20, 36, 58, 0.05);
|
||||
}
|
||||
|
||||
.group-cover {
|
||||
width: 100%;
|
||||
height: 280rpx;
|
||||
}
|
||||
|
||||
.group-body {
|
||||
padding: 22rpx;
|
||||
}
|
||||
|
||||
.price {
|
||||
min-width: 120rpx;
|
||||
text-align: right;
|
||||
font-size: 34rpx;
|
||||
}
|
||||
|
||||
.stock-line {
|
||||
margin-top: 18rpx;
|
||||
}
|
||||
|
||||
.stock-track {
|
||||
height: 12rpx;
|
||||
overflow: hidden;
|
||||
margin-bottom: 12rpx;
|
||||
background: #edf2f7;
|
||||
border-radius: 999rpx;
|
||||
}
|
||||
|
||||
.stock-fill {
|
||||
width: 46%;
|
||||
height: 100%;
|
||||
background: #0f766e;
|
||||
border-radius: 999rpx;
|
||||
}
|
||||
|
||||
@@ -1,57 +1,126 @@
|
||||
<view class="page">
|
||||
<view class="section">
|
||||
<view class="section-title">邻小帮</view>
|
||||
<view class="muted">社区服务 30 分钟到家</view>
|
||||
<view class="home-hero">
|
||||
<view>
|
||||
<view class="hero-eyebrow">社区生活服务</view>
|
||||
<view class="hero-title">邻小帮</view>
|
||||
<view class="hero-subtitle">商品预定、快递代取、团购闲置都在这里</view>
|
||||
</view>
|
||||
<view class="hero-badge">30 分钟到家</view>
|
||||
</view>
|
||||
|
||||
<view class="grid section">
|
||||
<view class="shortcut" bindtap="go" data-url="/pages/products/index">商品预定</view>
|
||||
<view class="shortcut" bindtap="go" data-url="/pages/express/create">代取快递</view>
|
||||
<view class="shortcut" bindtap="goTab" data-url="/pages/group-buy/index">今日团购</view>
|
||||
<view class="shortcut" bindtap="goTab" data-url="/pages/second-hand/index">二手闲置</view>
|
||||
<view class="shortcut" bindtap="go" data-url="/pages/notices/index">社区公告</view>
|
||||
<view class="shortcut" bindtap="go" data-url="/pages/orders/index">我的订单</view>
|
||||
<view class="home-search" bindtap="go" data-url="/pages/products/index">
|
||||
<text>搜索社区商品、团购、闲置</text>
|
||||
<text class="search-action">去看看</text>
|
||||
</view>
|
||||
|
||||
<swiper wx:if="{{banners.length}}" class="banner-swiper" indicator-dots autoplay circular>
|
||||
<swiper-item wx:for="{{banners}}" wx:key="id">
|
||||
<view class="banner-card">
|
||||
<image wx:if="{{item.imageUrl}}" src="{{item.imageUrl}}" mode="aspectFill" />
|
||||
<view wx:else class="banner-fallback">邻小帮</view>
|
||||
<view class="banner-mask">
|
||||
<view class="banner-title">{{item.title}}</view>
|
||||
</view>
|
||||
</view>
|
||||
</swiper-item>
|
||||
</swiper>
|
||||
|
||||
<view class="service-grid section">
|
||||
<view class="shortcut service-card service-product" bindtap="go" data-url="/pages/products/index">
|
||||
<view class="service-icon">货</view>
|
||||
<view class="service-name">商品预定</view>
|
||||
</view>
|
||||
<view class="shortcut service-card service-express" bindtap="go" data-url="/pages/express/create">
|
||||
<view class="service-icon">快</view>
|
||||
<view class="service-name">代取快递</view>
|
||||
</view>
|
||||
<view class="shortcut service-card service-group" bindtap="goTab" data-url="/pages/group-buy/index">
|
||||
<view class="service-icon">团</view>
|
||||
<view class="service-name">今日团购</view>
|
||||
</view>
|
||||
<view class="shortcut service-card service-second" bindtap="goTab" data-url="/pages/second-hand/index">
|
||||
<view class="service-icon">闲</view>
|
||||
<view class="service-name">二手闲置</view>
|
||||
</view>
|
||||
<view class="shortcut service-card service-notice" bindtap="go" data-url="/pages/notices/index">
|
||||
<view class="service-icon">告</view>
|
||||
<view class="service-name">社区公告</view>
|
||||
</view>
|
||||
<view class="shortcut service-card service-order" bindtap="go" data-url="/pages/orders/index">
|
||||
<view class="service-icon">单</view>
|
||||
<view class="service-name">我的订单</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="section">
|
||||
<view class="section-title">公告</view>
|
||||
<view wx:for="{{notices}}" wx:key="id" class="card" bindtap="go" data-url="/pages/notices/index">
|
||||
<view>{{item.title}}</view>
|
||||
<view class="section-header">
|
||||
<view class="section-title">公告</view>
|
||||
<view class="pill" bindtap="go" data-url="/pages/notices/index">全部</view>
|
||||
</view>
|
||||
<view wx:for="{{notices}}" wx:key="id" class="content-card notice-brief" bindtap="go" data-url="/pages/notices/index">
|
||||
<view class="card-title">{{item.title}}</view>
|
||||
<view class="muted">{{item.category}}</view>
|
||||
</view>
|
||||
<view wx:if="{{!notices.length}}" class="card muted">暂无公告</view>
|
||||
<view wx:if="{{!notices.length}}" class="empty-state">暂无公告</view>
|
||||
</view>
|
||||
|
||||
<view class="section">
|
||||
<view class="section-title">热门商品</view>
|
||||
<view wx:for="{{products}}" wx:key="id" class="card" bindtap="go" data-url="/pages/products/detail?id={{item.id}}">
|
||||
<view class="row between">
|
||||
<view>{{item.name}}</view>
|
||||
<view class="section-header">
|
||||
<view class="section-title">热门商品</view>
|
||||
<view class="pill" bindtap="go" data-url="/pages/products/index">去选购</view>
|
||||
</view>
|
||||
<view wx:for="{{products}}" wx:key="id" class="content-card product-brief" bindtap="go" data-url="/pages/products/detail?id={{item.id}}">
|
||||
<view class="brief-main">
|
||||
<image wx:if="{{item.coverUrl}}" class="brief-cover" src="{{item.coverUrl}}" mode="aspectFill" />
|
||||
<view wx:else class="brief-cover media-placeholder">货</view>
|
||||
<view class="brief-body">
|
||||
<view class="card-title">{{item.name}}</view>
|
||||
<view class="muted">{{item.description || '社区精选商品'}}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="brief-side">
|
||||
<view class="tag">{{item.status}}</view>
|
||||
<view wx:if="{{item.skus.length}}" class="price">¥{{item.skus[0].priceCent / 100}} 起</view>
|
||||
</view>
|
||||
<view class="muted">{{item.description || '社区商品'}}</view>
|
||||
</view>
|
||||
<view wx:if="{{!products.length}}" class="empty-state">暂无热门商品</view>
|
||||
</view>
|
||||
|
||||
<view class="section">
|
||||
<view class="section-title">今日团购</view>
|
||||
<view wx:for="{{groupBuys}}" wx:key="id" class="card" bindtap="go" data-url="/pages/group-buy/detail?id={{item.id}}">
|
||||
<view class="row between">
|
||||
<view>{{item.title}}</view>
|
||||
<view class="price">¥{{item.priceCent / 100}}</view>
|
||||
</view>
|
||||
<view class="muted">已售 {{item.soldCount}} / 库存 {{item.stock}}</view>
|
||||
<view class="section-header">
|
||||
<view class="section-title">今日团购</view>
|
||||
<view class="pill" bindtap="goTab" data-url="/pages/group-buy/index">更多</view>
|
||||
</view>
|
||||
<view wx:for="{{groupBuys}}" wx:key="id" class="content-card group-brief" bindtap="go" data-url="/pages/group-buy/detail?id={{item.id}}">
|
||||
<view class="brief-main">
|
||||
<image wx:if="{{item.coverUrl}}" class="brief-cover" src="{{item.coverUrl}}" mode="aspectFill" />
|
||||
<view wx:else class="brief-cover media-placeholder">团</view>
|
||||
<view class="brief-body">
|
||||
<view class="card-title">{{item.title}}</view>
|
||||
<view class="muted">已售 {{item.soldCount}} / 库存 {{item.stock}}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="price">¥{{item.priceCent / 100}}</view>
|
||||
</view>
|
||||
<view wx:if="{{!groupBuys.length}}" class="empty-state">暂无团购</view>
|
||||
</view>
|
||||
|
||||
<view class="section">
|
||||
<view class="section-title">最新闲置</view>
|
||||
<view wx:for="{{secondGoods}}" wx:key="id" class="card">
|
||||
<view class="row between">
|
||||
<view>{{item.title}}</view>
|
||||
<view class="price">¥{{item.priceCent / 100}}</view>
|
||||
</view>
|
||||
<view class="muted">{{item.category}}</view>
|
||||
<view class="section-header">
|
||||
<view class="section-title">最新闲置</view>
|
||||
<view class="pill" bindtap="goTab" data-url="/pages/second-hand/index">逛闲置</view>
|
||||
</view>
|
||||
<view wx:for="{{secondGoods}}" wx:key="id" class="content-card second-brief">
|
||||
<view class="brief-main">
|
||||
<image wx:if="{{item.imageUrls.length}}" class="brief-cover" src="{{item.imageUrls[0]}}" mode="aspectFill" />
|
||||
<view wx:else class="brief-cover media-placeholder">闲</view>
|
||||
<view class="brief-body">
|
||||
<view class="card-title">{{item.title}}</view>
|
||||
<view class="muted">{{item.category}}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="price">¥{{item.priceCent / 100}}</view>
|
||||
</view>
|
||||
<view wx:if="{{!secondGoods.length}}" class="empty-state">暂无闲置</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -1,3 +1,218 @@
|
||||
.page {
|
||||
padding-bottom: 40rpx;
|
||||
padding-bottom: 56rpx;
|
||||
}
|
||||
|
||||
.home-hero {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: flex-start;
|
||||
min-height: 220rpx;
|
||||
padding: 30rpx;
|
||||
margin-bottom: 20rpx;
|
||||
color: #ffffff;
|
||||
background: #0f766e;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 14rpx 34rpx rgba(15, 118, 110, 0.22);
|
||||
}
|
||||
|
||||
.hero-eyebrow {
|
||||
display: inline-flex;
|
||||
height: 42rpx;
|
||||
align-items: center;
|
||||
padding: 0 14rpx;
|
||||
background: rgba(255, 255, 255, 0.16);
|
||||
border-radius: 999rpx;
|
||||
font-size: 22rpx;
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.hero-title {
|
||||
margin-top: 22rpx;
|
||||
font-size: 48rpx;
|
||||
font-weight: 900;
|
||||
line-height: 1.1;
|
||||
}
|
||||
|
||||
.hero-subtitle {
|
||||
margin-top: 14rpx;
|
||||
color: rgba(255, 255, 255, 0.86);
|
||||
font-size: 25rpx;
|
||||
}
|
||||
|
||||
.hero-badge {
|
||||
flex-shrink: 0;
|
||||
padding: 10rpx 16rpx;
|
||||
color: #854d0e;
|
||||
background: #fef3c7;
|
||||
border-radius: 999rpx;
|
||||
font-size: 22rpx;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.home-search {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
height: 82rpx;
|
||||
padding: 0 24rpx;
|
||||
margin-bottom: 20rpx;
|
||||
color: #64748b;
|
||||
background: #ffffff;
|
||||
border: 1rpx solid #dfe7ee;
|
||||
border-radius: 16rpx;
|
||||
}
|
||||
|
||||
.search-action {
|
||||
color: #0f766e;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.banner-swiper {
|
||||
height: 220rpx;
|
||||
margin-bottom: 24rpx;
|
||||
}
|
||||
|
||||
.banner-card {
|
||||
position: relative;
|
||||
height: 220rpx;
|
||||
overflow: hidden;
|
||||
border-radius: 16rpx;
|
||||
background: #33536f;
|
||||
}
|
||||
|
||||
.banner-card image {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.banner-fallback {
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding-left: 32rpx;
|
||||
color: #ffffff;
|
||||
background: #33536f;
|
||||
font-size: 36rpx;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.banner-mask {
|
||||
position: absolute;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
padding: 34rpx 28rpx 24rpx;
|
||||
background: linear-gradient(180deg, rgba(0, 0, 0, 0), rgba(17, 24, 39, 0.68));
|
||||
}
|
||||
|
||||
.banner-title {
|
||||
color: #ffffff;
|
||||
font-size: 30rpx;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.service-grid {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.service-card {
|
||||
width: 100%;
|
||||
height: 150rpx;
|
||||
align-items: flex-start;
|
||||
padding: 20rpx;
|
||||
border: 1rpx solid #e2e8f0;
|
||||
box-shadow: 0 8rpx 20rpx rgba(20, 36, 58, 0.05);
|
||||
}
|
||||
|
||||
.service-icon {
|
||||
width: 48rpx;
|
||||
height: 48rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-bottom: 14rpx;
|
||||
color: #ffffff;
|
||||
border-radius: 14rpx;
|
||||
font-size: 24rpx;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.service-name {
|
||||
color: #111827;
|
||||
font-size: 26rpx;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.service-product .service-icon {
|
||||
background: #0f766e;
|
||||
}
|
||||
|
||||
.service-express .service-icon {
|
||||
background: #2563eb;
|
||||
}
|
||||
|
||||
.service-group .service-icon {
|
||||
background: #c2410c;
|
||||
}
|
||||
|
||||
.service-second .service-icon {
|
||||
background: #7c3aed;
|
||||
}
|
||||
|
||||
.service-notice .service-icon {
|
||||
background: #33536f;
|
||||
}
|
||||
|
||||
.service-order .service-icon {
|
||||
background: #be123c;
|
||||
}
|
||||
|
||||
.content-card {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 18rpx;
|
||||
padding: 22rpx;
|
||||
margin-bottom: 16rpx;
|
||||
background: #ffffff;
|
||||
border: 1rpx solid #e4eaf0;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 8rpx 24rpx rgba(20, 36, 58, 0.05);
|
||||
}
|
||||
|
||||
.notice-brief {
|
||||
display: block;
|
||||
}
|
||||
|
||||
.brief-main {
|
||||
flex: 1;
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
gap: 18rpx;
|
||||
}
|
||||
|
||||
.brief-cover {
|
||||
width: 112rpx;
|
||||
height: 112rpx;
|
||||
flex-shrink: 0;
|
||||
overflow: hidden;
|
||||
border-radius: 14rpx;
|
||||
}
|
||||
|
||||
.brief-body {
|
||||
min-width: 0;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
gap: 8rpx;
|
||||
}
|
||||
|
||||
.brief-side {
|
||||
min-width: 112rpx;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: flex-end;
|
||||
justify-content: space-between;
|
||||
gap: 14rpx;
|
||||
}
|
||||
|
||||
@@ -1,12 +1,23 @@
|
||||
<view class="page">
|
||||
<view class="section-title">社区公告</view>
|
||||
<view wx:for="{{notices}}" wx:key="id" class="card">
|
||||
<view class="row between">
|
||||
<view>{{item.title}}</view>
|
||||
<view wx:if="{{item.pinned}}" class="tag">置顶</view>
|
||||
</view>
|
||||
<view class="muted">{{item.category}} {{item.publishedAt}}</view>
|
||||
<view>{{item.content}}</view>
|
||||
<view class="page-heading">
|
||||
<view class="heading-eyebrow">社区信息</view>
|
||||
<view class="heading-title">社区公告</view>
|
||||
<view class="heading-subtitle">物业通知、活动消息和服务说明</view>
|
||||
</view>
|
||||
<view wx:if="{{!notices.length && !loading}}" class="card muted">暂无公告</view>
|
||||
|
||||
<view class="notice-list">
|
||||
<view wx:for="{{notices}}" wx:key="id" class="notice-card">
|
||||
<view class="row between notice-head">
|
||||
<view class="card-title">{{item.title}}</view>
|
||||
<view wx:if="{{item.pinned}}" class="tag">置顶</view>
|
||||
</view>
|
||||
<view class="notice-meta">
|
||||
<view class="pill">{{item.category}}</view>
|
||||
<view class="muted">{{item.publishedAt}}</view>
|
||||
</view>
|
||||
<view class="notice-content">{{item.content}}</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view wx:if="{{!notices.length && !loading}}" class="empty-state">暂无公告</view>
|
||||
</view>
|
||||
|
||||
@@ -1,4 +1,37 @@
|
||||
.card > view:last-child {
|
||||
margin-top: 12rpx;
|
||||
line-height: 44rpx;
|
||||
.page-heading {
|
||||
margin-bottom: 22rpx;
|
||||
}
|
||||
|
||||
.notice-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 18rpx;
|
||||
}
|
||||
|
||||
.notice-card {
|
||||
padding: 24rpx;
|
||||
background: #ffffff;
|
||||
border: 1rpx solid #e4eaf0;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 8rpx 24rpx rgba(20, 36, 58, 0.05);
|
||||
}
|
||||
|
||||
.notice-head {
|
||||
align-items: flex-start;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.notice-meta {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 12rpx;
|
||||
margin: 16rpx 0;
|
||||
}
|
||||
|
||||
.notice-content {
|
||||
color: #334155;
|
||||
font-size: 27rpx;
|
||||
line-height: 46rpx;
|
||||
padding-top: 18rpx;
|
||||
border-top: 1rpx solid #edf1f5;
|
||||
}
|
||||
|
||||
@@ -1,29 +1,34 @@
|
||||
<view class="page" wx:if="{{order}}">
|
||||
<view class="card">
|
||||
<view class="section-title">{{order.orderNo}}</view>
|
||||
<view class="summary-card">
|
||||
<view>
|
||||
<view class="muted">订单编号</view>
|
||||
<view class="summary-title">{{order.orderNo}}</view>
|
||||
<view class="muted">类型:{{type}}</view>
|
||||
</view>
|
||||
<view class="tag">{{order.statusText}}</view>
|
||||
<view class="muted">类型:{{type}}</view>
|
||||
</view>
|
||||
<view class="card">
|
||||
|
||||
<view class="detail-card">
|
||||
<view wx:if="{{type === 'goods'}}">
|
||||
<view>商品金额:{{order.totalAmountText}}</view>
|
||||
<view>配送费:{{order.deliveryFeeText}}</view>
|
||||
<view class="price">应收:{{order.payableAmountText}}</view>
|
||||
<view class="detail-line"><text>商品金额</text><text>{{order.totalAmountText}}</text></view>
|
||||
<view class="detail-line"><text>配送费</text><text>{{order.deliveryFeeText}}</text></view>
|
||||
<view class="detail-line total"><text>应收</text><text class="price">{{order.payableAmountText}}</text></view>
|
||||
<view class="muted">{{order.deliveryMethod}}</view>
|
||||
</view>
|
||||
<view wx:if="{{type === 'express'}}">
|
||||
<view>{{order.expressCompany}} / {{order.pickupCode}}</view>
|
||||
<view class="muted">{{order.pickupAddress}}</view>
|
||||
<view class="price">服务费:{{order.feeText}}</view>
|
||||
<view class="detail-line"><text>快递信息</text><text>{{order.expressCompany}} / {{order.pickupCode}}</text></view>
|
||||
<view class="detail-line"><text>取件地址</text><text>{{order.pickupAddress}}</text></view>
|
||||
<view class="detail-line total"><text>服务费</text><text class="price">{{order.feeText}}</text></view>
|
||||
</view>
|
||||
<view wx:if="{{type === 'group'}}">
|
||||
<view>{{order.groupBuyTitle}}</view>
|
||||
<view>数量:{{order.quantity}}</view>
|
||||
<view class="price">金额:{{order.amountText}}</view>
|
||||
<view class="detail-line"><text>团购商品</text><text>{{order.groupBuyTitle}}</text></view>
|
||||
<view class="detail-line"><text>数量</text><text>{{order.quantity}}</text></view>
|
||||
<view class="detail-line total"><text>金额</text><text class="price">{{order.amountText}}</text></view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="button-row">
|
||||
<button class="ghost" bindtap="cancel">取消订单</button>
|
||||
<button wx:if="{{type === 'express'}}" class="primary" bindtap="completeExpress">确认完成</button>
|
||||
<button class="ghost action-button" bindtap="cancel">取消订单</button>
|
||||
<button wx:if="{{type === 'express'}}" class="primary action-button" bindtap="completeExpress">确认完成</button>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
@@ -1,3 +1,58 @@
|
||||
.summary-card {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
gap: 18rpx;
|
||||
padding: 28rpx;
|
||||
margin-bottom: 18rpx;
|
||||
color: #ffffff;
|
||||
background: #33536f;
|
||||
border-radius: 16rpx;
|
||||
}
|
||||
|
||||
.summary-card .muted {
|
||||
color: rgba(255, 255, 255, 0.76);
|
||||
}
|
||||
|
||||
.summary-title {
|
||||
margin: 10rpx 0;
|
||||
font-size: 36rpx;
|
||||
font-weight: 900;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.detail-card {
|
||||
padding: 24rpx;
|
||||
margin-bottom: 20rpx;
|
||||
background: #ffffff;
|
||||
border: 1rpx solid #e4eaf0;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 8rpx 24rpx rgba(20, 36, 58, 0.05);
|
||||
}
|
||||
|
||||
.detail-line {
|
||||
display: flex;
|
||||
align-items: flex-start;
|
||||
justify-content: space-between;
|
||||
gap: 24rpx;
|
||||
padding: 18rpx 0;
|
||||
color: #334155;
|
||||
border-bottom: 1rpx solid #edf1f5;
|
||||
}
|
||||
|
||||
.detail-line text:first-child {
|
||||
flex-shrink: 0;
|
||||
color: #64748b;
|
||||
}
|
||||
|
||||
.detail-line text:last-child {
|
||||
text-align: right;
|
||||
}
|
||||
|
||||
.detail-line.total {
|
||||
border-bottom: none;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.button-row button {
|
||||
flex: 1;
|
||||
}
|
||||
|
||||
@@ -1,17 +1,25 @@
|
||||
<view class="page">
|
||||
<view class="section-title">我的订单</view>
|
||||
<view wx:for="{{orders}}" wx:key="id" class="card" bindtap="goDetail" data-type="{{item.type}}" data-id="{{item.id}}">
|
||||
<view class="row between">
|
||||
<view>
|
||||
<view>{{item.title}}</view>
|
||||
<view class="muted">{{item.typeText}} {{item.orderNo}}</view>
|
||||
<view class="page-heading">
|
||||
<view class="heading-eyebrow">履约进度</view>
|
||||
<view class="heading-title">我的订单</view>
|
||||
<view class="heading-subtitle">查看商品、快递和团购订单</view>
|
||||
</view>
|
||||
|
||||
<view class="order-list">
|
||||
<view wx:for="{{orders}}" wx:key="id" class="order-card" bindtap="goDetail" data-type="{{item.type}}" data-id="{{item.id}}">
|
||||
<view class="row between order-head">
|
||||
<view class="order-title-wrap">
|
||||
<view class="card-title">{{item.title}}</view>
|
||||
<view class="order-meta">{{item.typeText}} {{item.orderNo}}</view>
|
||||
</view>
|
||||
<view class="tag">{{item.statusText}}</view>
|
||||
</view>
|
||||
<view class="order-footer row between">
|
||||
<view class="muted">状态:{{item.statusText}}</view>
|
||||
<view class="price">{{item.amountText}}</view>
|
||||
</view>
|
||||
<view class="tag">{{item.statusText}}</view>
|
||||
</view>
|
||||
<view class="row between">
|
||||
<view class="muted">状态:{{item.statusText}}</view>
|
||||
<view class="price">{{item.amountText}}</view>
|
||||
</view>
|
||||
</view>
|
||||
<view wx:if="{{!orders.length && !loading}}" class="card muted">暂无订单</view>
|
||||
|
||||
<view wx:if="{{!orders.length && !loading}}" class="empty-state">暂无订单</view>
|
||||
</view>
|
||||
|
||||
@@ -1,3 +1,42 @@
|
||||
.page-heading {
|
||||
margin-bottom: 22rpx;
|
||||
}
|
||||
|
||||
.order-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 18rpx;
|
||||
}
|
||||
|
||||
.order-card {
|
||||
padding: 24rpx;
|
||||
background: #ffffff;
|
||||
border: 1rpx solid #e4eaf0;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 8rpx 24rpx rgba(20, 36, 58, 0.05);
|
||||
}
|
||||
|
||||
.order-head {
|
||||
align-items: flex-start;
|
||||
gap: 18rpx;
|
||||
}
|
||||
|
||||
.order-title-wrap {
|
||||
min-width: 0;
|
||||
}
|
||||
|
||||
.order-meta {
|
||||
margin-top: 8rpx;
|
||||
color: #64748b;
|
||||
font-size: 23rpx;
|
||||
}
|
||||
|
||||
.order-footer {
|
||||
margin-top: 20rpx;
|
||||
padding-top: 18rpx;
|
||||
border-top: 1rpx solid #edf1f5;
|
||||
}
|
||||
|
||||
.tag {
|
||||
min-width: 120rpx;
|
||||
text-align: center;
|
||||
|
||||
68
miniapp/miniprogram/pages/pageStyles.test.js
Normal file
68
miniapp/miniprogram/pages/pageStyles.test.js
Normal file
@@ -0,0 +1,68 @@
|
||||
const fs = require('fs')
|
||||
const path = require('path')
|
||||
|
||||
const root = path.resolve(__dirname, '..')
|
||||
|
||||
function readPage(page, ext, name = 'index') {
|
||||
return fs.readFileSync(path.join(root, 'pages', page, `${name}.${ext}`), 'utf8')
|
||||
}
|
||||
|
||||
function readPageFile(page, name, ext) {
|
||||
return fs.readFileSync(path.join(root, 'pages', page, `${name}.${ext}`), 'utf8')
|
||||
}
|
||||
|
||||
function selectorExists(wxss, selector) {
|
||||
return new RegExp(`${selector.replace('.', '\\.')}\\s*[{,]`).test(wxss)
|
||||
}
|
||||
|
||||
describe('miniapp page styling', () => {
|
||||
const pageStyleExpectations = [
|
||||
['home', 'index', ['.home-hero', '.service-grid', '.banner-card', '.content-card']],
|
||||
['products', 'index', ['.page-heading', '.product-card', '.product-thumb', '.category-item']],
|
||||
['express', 'create', ['.page-heading', '.form-card', '.fee-panel', '.field-label']],
|
||||
['group-buy', 'index', ['.page-heading', '.group-card', '.group-cover', '.stock-line']],
|
||||
['second-hand', 'index', ['.page-heading', '.second-card', '.second-cover', '.publish-button']],
|
||||
['notices', 'index', ['.page-heading', '.notice-card', '.notice-content']],
|
||||
['orders', 'index', ['.page-heading', '.order-card', '.order-meta']],
|
||||
['profile', 'index', ['.profile-hero', '.menu-card', '.menu-icon']],
|
||||
['address', 'index', ['.page-heading', '.address-card', '.address-form']]
|
||||
]
|
||||
|
||||
test.each(pageStyleExpectations)('%s page has dedicated visual styles', (page, name, selectors) => {
|
||||
const wxss = readPage(page, 'wxss', name)
|
||||
|
||||
expect(wxss.length).toBeGreaterThan(300)
|
||||
selectors.forEach((selector) => {
|
||||
expect(selectorExists(wxss, selector)).toBe(true)
|
||||
})
|
||||
})
|
||||
|
||||
test('detail pages have order and purchase layouts', () => {
|
||||
const productDetail = readPageFile('products', 'detail', 'wxss')
|
||||
const groupDetail = readPageFile('group-buy', 'detail', 'wxss')
|
||||
const orderDetail = readPageFile('orders', 'detail', 'wxss')
|
||||
|
||||
expect(selectorExists(productDetail, '.detail-hero')).toBe(true)
|
||||
expect(selectorExists(productDetail, '.purchase-card')).toBe(true)
|
||||
expect(selectorExists(groupDetail, '.detail-hero')).toBe(true)
|
||||
expect(selectorExists(groupDetail, '.purchase-card')).toBe(true)
|
||||
expect(selectorExists(orderDetail, '.summary-card')).toBe(true)
|
||||
expect(selectorExists(orderDetail, '.detail-line')).toBe(true)
|
||||
})
|
||||
|
||||
test('global stylesheet provides shared design primitives', () => {
|
||||
const appWxss = fs.readFileSync(path.join(root, 'app.wxss'), 'utf8')
|
||||
|
||||
;[
|
||||
'.page-heading',
|
||||
'.card-title',
|
||||
'.empty-state',
|
||||
'.form-card',
|
||||
'.action-button',
|
||||
'.pill',
|
||||
'.media-placeholder'
|
||||
].forEach((selector) => {
|
||||
expect(selectorExists(appWxss, selector)).toBe(true)
|
||||
})
|
||||
})
|
||||
})
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -1,11 +1,37 @@
|
||||
<view class="page">
|
||||
<view class="card">
|
||||
<view class="section-title">我的</view>
|
||||
<view class="muted">邻小帮居民端</view>
|
||||
<view class="profile-hero">
|
||||
<view class="avatar">邻</view>
|
||||
<view>
|
||||
<view class="profile-title">我的</view>
|
||||
<view class="profile-subtitle">邻小帮居民端</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<view class="menu-list">
|
||||
<view class="menu-card" bindtap="go" data-url="/pages/orders/index">
|
||||
<view class="menu-icon icon-order">单</view>
|
||||
<view class="menu-text">我的订单</view>
|
||||
<view class="menu-arrow">›</view>
|
||||
</view>
|
||||
<view class="menu-card" bindtap="go" data-url="/pages/address/index">
|
||||
<view class="menu-icon icon-address">址</view>
|
||||
<view class="menu-text">地址管理</view>
|
||||
<view class="menu-arrow">›</view>
|
||||
</view>
|
||||
<view class="menu-card" bindtap="go" data-url="/pages/express/create">
|
||||
<view class="menu-icon icon-express">快</view>
|
||||
<view class="menu-text">代取快递</view>
|
||||
<view class="menu-arrow">›</view>
|
||||
</view>
|
||||
<view class="menu-card" bindtap="go" data-url="/pages/products/index">
|
||||
<view class="menu-icon icon-product">货</view>
|
||||
<view class="menu-text">商品预定</view>
|
||||
<view class="menu-arrow">›</view>
|
||||
</view>
|
||||
<view class="menu-card" bindtap="go" data-url="/pages/notices/index">
|
||||
<view class="menu-icon icon-notice">告</view>
|
||||
<view class="menu-text">社区公告</view>
|
||||
<view class="menu-arrow">›</view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="card menu" bindtap="go" data-url="/pages/orders/index">我的订单</view>
|
||||
<view class="card menu" bindtap="go" data-url="/pages/address/index">地址管理</view>
|
||||
<view class="card menu" bindtap="go" data-url="/pages/express/create">代取快递</view>
|
||||
<view class="card menu" bindtap="go" data-url="/pages/products/index">商品预定</view>
|
||||
<view class="card menu" bindtap="go" data-url="/pages/notices/index">社区公告</view>
|
||||
</view>
|
||||
|
||||
@@ -1,3 +1,99 @@
|
||||
.menu {
|
||||
font-weight: 600;
|
||||
.profile-hero {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 22rpx;
|
||||
padding: 30rpx;
|
||||
margin-bottom: 22rpx;
|
||||
color: #ffffff;
|
||||
background: #0f766e;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 14rpx 34rpx rgba(15, 118, 110, 0.2);
|
||||
}
|
||||
|
||||
.avatar {
|
||||
width: 96rpx;
|
||||
height: 96rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #0f766e;
|
||||
background: #ffffff;
|
||||
border-radius: 50%;
|
||||
font-size: 34rpx;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.profile-title {
|
||||
font-size: 40rpx;
|
||||
font-weight: 900;
|
||||
line-height: 1.2;
|
||||
}
|
||||
|
||||
.profile-subtitle {
|
||||
margin-top: 8rpx;
|
||||
color: rgba(255, 255, 255, 0.82);
|
||||
font-size: 24rpx;
|
||||
}
|
||||
|
||||
.menu-list {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 16rpx;
|
||||
}
|
||||
|
||||
.menu-card {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 18rpx;
|
||||
min-height: 104rpx;
|
||||
padding: 20rpx 22rpx;
|
||||
background: #ffffff;
|
||||
border: 1rpx solid #e4eaf0;
|
||||
border-radius: 16rpx;
|
||||
box-shadow: 0 8rpx 24rpx rgba(20, 36, 58, 0.05);
|
||||
}
|
||||
|
||||
.menu-icon {
|
||||
width: 56rpx;
|
||||
height: 56rpx;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
color: #ffffff;
|
||||
border-radius: 16rpx;
|
||||
font-size: 24rpx;
|
||||
font-weight: 900;
|
||||
}
|
||||
|
||||
.menu-text {
|
||||
flex: 1;
|
||||
color: #111827;
|
||||
font-size: 29rpx;
|
||||
font-weight: 800;
|
||||
}
|
||||
|
||||
.menu-arrow {
|
||||
color: #94a3b8;
|
||||
font-size: 42rpx;
|
||||
line-height: 1;
|
||||
}
|
||||
|
||||
.icon-order {
|
||||
background: #be123c;
|
||||
}
|
||||
|
||||
.icon-address {
|
||||
background: #33536f;
|
||||
}
|
||||
|
||||
.icon-express {
|
||||
background: #2563eb;
|
||||
}
|
||||
|
||||
.icon-product {
|
||||
background: #0f766e;
|
||||
}
|
||||
|
||||
.icon-notice {
|
||||
background: #c2410c;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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' })
|
||||
},
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
28
miniapp/miniprogram/project.config.json
Normal file
28
miniapp/miniprogram/project.config.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"appid": "wxf634a69e89290cc7",
|
||||
"compileType": "miniprogram",
|
||||
"libVersion": "3.16.2",
|
||||
"packOptions": {
|
||||
"ignore": [],
|
||||
"include": []
|
||||
},
|
||||
"setting": {
|
||||
"coverView": true,
|
||||
"es6": true,
|
||||
"postcss": true,
|
||||
"minified": true,
|
||||
"enhance": true,
|
||||
"showShadowRootInWxmlPanel": true,
|
||||
"packNpmRelationList": [],
|
||||
"babelSetting": {
|
||||
"ignore": [],
|
||||
"disablePlugins": [],
|
||||
"outputPath": ""
|
||||
}
|
||||
},
|
||||
"condition": {},
|
||||
"editorSetting": {
|
||||
"tabIndent": "insertSpaces",
|
||||
"tabSize": 2
|
||||
}
|
||||
}
|
||||
@@ -3,6 +3,13 @@ function ensureToken() {
|
||||
if (app.globalData.token) {
|
||||
return Promise.resolve(app.globalData.token)
|
||||
}
|
||||
if (app.globalData.useDevLogin) {
|
||||
return devLogin(app)
|
||||
}
|
||||
return wxLogin(app)
|
||||
}
|
||||
|
||||
function devLogin(app) {
|
||||
return new Promise((resolve, reject) => {
|
||||
wx.request({
|
||||
url: app.globalData.apiBaseUrl + '/api/auth/dev-miniapp-login',
|
||||
@@ -25,6 +32,37 @@ function ensureToken() {
|
||||
})
|
||||
}
|
||||
|
||||
function wxLogin(app) {
|
||||
return new Promise((resolve, reject) => {
|
||||
wx.login({
|
||||
success(loginResult) {
|
||||
if (!loginResult.code) {
|
||||
reject(new Error('微信登录失败'))
|
||||
return
|
||||
}
|
||||
wx.request({
|
||||
url: app.globalData.apiBaseUrl + '/api/auth/miniapp-login',
|
||||
method: 'POST',
|
||||
data: {
|
||||
code: loginResult.code
|
||||
},
|
||||
success(res) {
|
||||
if (res.statusCode >= 200 && res.statusCode < 300 && res.data && res.data.code === 0) {
|
||||
app.globalData.token = res.data.data.token
|
||||
app.globalData.user = res.data.data
|
||||
resolve(app.globalData.token)
|
||||
return
|
||||
}
|
||||
reject(new Error((res.data && res.data.message) || '登录失败'))
|
||||
},
|
||||
fail: reject
|
||||
})
|
||||
},
|
||||
fail: reject
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
function request(options) {
|
||||
const app = getApp()
|
||||
return new Promise((resolve, reject) => {
|
||||
@@ -50,6 +88,40 @@ function request(options) {
|
||||
})
|
||||
}
|
||||
|
||||
function uploadFile(filePath) {
|
||||
const app = getApp()
|
||||
return new Promise((resolve, reject) => {
|
||||
ensureToken().then((token) => {
|
||||
wx.uploadFile({
|
||||
url: app.globalData.apiBaseUrl + '/api/files/upload',
|
||||
filePath,
|
||||
name: 'file',
|
||||
header: {
|
||||
Authorization: token,
|
||||
satoken: token
|
||||
},
|
||||
success(res) {
|
||||
let body = res.data
|
||||
if (typeof body === 'string') {
|
||||
try {
|
||||
body = JSON.parse(body)
|
||||
} catch (error) {
|
||||
reject(new Error('上传失败'))
|
||||
return
|
||||
}
|
||||
}
|
||||
if (res.statusCode >= 200 && res.statusCode < 300 && body && body.code === 0) {
|
||||
resolve(body.data)
|
||||
return
|
||||
}
|
||||
reject(new Error((body && body.message) || '上传失败'))
|
||||
},
|
||||
fail: reject
|
||||
})
|
||||
}).catch(reject)
|
||||
})
|
||||
}
|
||||
|
||||
function yuan(cent) {
|
||||
return ((cent || 0) / 100).toFixed(2)
|
||||
}
|
||||
@@ -85,6 +157,7 @@ function setField(page, event) {
|
||||
|
||||
module.exports = {
|
||||
request,
|
||||
uploadFile,
|
||||
yuan,
|
||||
statusText,
|
||||
setField
|
||||
|
||||
Reference in New Issue
Block a user