feat: 初始化

This commit is contained in:
2026-07-27 15:54:32 +08:00
parent 6bc64eb408
commit c98187dff1
39 changed files with 10795 additions and 0 deletions
+204
View File
@@ -0,0 +1,204 @@
<template>
<main class="shop" :class="`shop--${shop.layout}`" :style="{ '--theme': shop.theme }">
<van-nav-bar :title="shop.name" left-arrow @click-left="$router.push('/')" />
<section class="hero" :style="{ background: shop.gradient }">
<div>
<span class="hero__eyebrow">官方品质供应商</span>
<h1>{{ shop.slogan }}</h1>
<p>满 {{ shop.freeShipping }} 元免配送费</p>
</div>
<van-icon :name="shop.icon" size="64" />
</section>
<van-notice-bar class="notice" left-icon="volume-o" :text="shop.notice" />
<van-search v-model="keyword" placeholder="搜索商品" shape="round" />
<van-tabs v-model="activeCategory" sticky :color="shop.theme" title-active-color="var(--theme)">
<van-tab v-for="category in categories" :key="category" :title="category" />
</van-tabs>
<van-empty v-if="filteredProducts.length === 0" description="暂无相关商品" />
<section v-else class="goods-list">
<van-card
v-for="item in filteredProducts"
:key="item.id"
:price="item.price.toFixed(2)"
:desc="item.desc"
:title="item.name"
:thumb="item.image"
:origin-price="item.originPrice.toFixed(2)"
>
<template #tags>
<van-tag round plain :color="shop.theme">{{ item.tag }}</van-tag>
</template>
<template #footer>
<van-stepper v-model="cart[item.id]" theme="round" :button-size="25" :disable-input="true" />
</template>
</van-card>
</section>
<div class="bottom-space" />
<van-submit-bar :price="totalPrice" :button-text="submitText" :disabled="totalCount === 0" @submit="openCheckout">
<van-icon name="shopping-cart-o" size="26" :badge="totalCount || ''" class="cart-icon" />
<span class="selected">已选 {{ totalCount }} 件</span>
</van-submit-bar>
<van-popup v-model="checkoutVisible" round position="bottom" :style="{ minHeight: '68%' }" closeable>
<section class="checkout">
<h2>确认订单</h2>
<van-contact-card type="add" name="商城收货人" tel="138****8888" @click="Toast('当前使用默认收货地址')" />
<van-cell-group inset title="商品清单">
<van-cell
v-for="item in selectedProducts"
:key="item.id"
:title="item.name"
:label="`¥ ${item.price.toFixed(2)} × ${cart[item.id]}`"
:value="`¥ ${(item.price * cart[item.id]).toFixed(2)}`"
/>
</van-cell-group>
<van-cell-group inset>
<van-cell title="配送方式" value="供应商配送" />
<van-cell title="配送费" :value="freight ? `¥ ${freight.toFixed(2)}` : '免配送费'" />
</van-cell-group>
<div class="checkout__total">合计 <b>¥ {{ (totalPrice / 100).toFixed(2) }}</b></div>
<van-button block round :color="shop.gradient" :loading="submitting" @click="submitOrder">提交订单</van-button>
</section>
</van-popup>
<van-popup v-model="successVisible" round closeable class="result-popup">
<van-icon name="checked" color="#23b65d" size="62" />
<h2>订单提交成功</h2>
<p>订单号:{{ submittedOrderId }}</p>
<van-button round block type="primary" @click="successVisible = false">继续选购</van-button>
</van-popup>
</main>
</template>
<script>
import { Toast } from 'vant'
const apiBase = process.env.VUE_APP_API_BASE || 'http://localhost:8050'
const formatTime = date => date.toISOString().slice(0, 19).replace('T', ' ')
export default {
props: {
shop: { type: Object, required: true }
},
data() {
return {
keyword: '',
activeCategory: 0,
cart: this.shop.products.reduce((result, item) => ({ ...result, [item.id]: 0 }), {}),
checkoutVisible: false,
successVisible: false,
submitting: false,
submittedOrderId: ''
}
},
computed: {
categories() {
return ['全部', ...this.shop.categories]
},
filteredProducts() {
const category = this.categories[this.activeCategory]
const keyword = this.keyword.trim()
return this.shop.products.filter(item => (category === '全部' || item.category === category) && (!keyword || item.name.includes(keyword)))
},
selectedProducts() {
return this.shop.products.filter(item => this.cart[item.id] > 0)
},
totalCount() {
return this.selectedProducts.reduce((sum, item) => sum + this.cart[item.id], 0)
},
goodsAmount() {
return this.selectedProducts.reduce((sum, item) => sum + item.price * this.cart[item.id], 0)
},
freight() {
return this.goodsAmount > 0 && this.goodsAmount < this.shop.freeShipping ? 12 : 0
},
totalPrice() {
return Math.round((this.goodsAmount + this.freight) * 100)
},
submitText() {
return this.totalCount ? `提交订单(${this.totalCount} 件)` : '提交订单'
}
},
methods: {
openCheckout() {
this.checkoutVisible = true
},
createOrderId() {
return `SUP${new Date().toISOString().replace(/\D/g, '').slice(0, 14)}${Math.floor(Math.random() * 9000 + 1000)}`
},
async submitOrder() {
const orderId = this.createOrderId()
const now = formatTime(new Date())
const orderInfo = {
orderId,
orderNumberId: `TRADE${Date.now()}`,
supplierId: this.shop.id,
aplName: this.shop.name,
createTime: now,
updateTime: now,
deliveryStatus: '0',
orderState: '0',
totalPrice: +(this.totalPrice / 100).toFixed(2),
pointsPrice: +(this.totalPrice / 100).toFixed(2),
wPayOrAPay: 0,
freightCost: this.freight,
orderProInfos: this.selectedProducts.map(item => ({
sku: item.sku,
number: this.cart[item.id],
price: +(item.price * this.cart[item.id]).toFixed(2),
name: item.name,
taxRate: 0.13,
picInfo: [item.image]
}))
}
this.submitting = true
try {
const response = await fetch(`${apiBase}/general/receive/pending`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ supplierId: this.shop.id, userId: 'h5-demo-user', token: 'h5-demo-token', orderInfo })
})
const result = await response.json()
if (!response.ok || result.resultCode !== 0) throw new Error(result.message || '订单提交失败')
this.submittedOrderId = result.data && result.data.orderId ? result.data.orderId : orderId
this.checkoutVisible = false
this.successVisible = true
this.selectedProducts.forEach(item => { this.cart[item.id] = 0 })
} catch (error) {
Toast.fail(error.message || '无法连接订单服务,请确认服务已启动')
} finally {
this.submitting = false
}
},
Toast
}
}
</script>
<style scoped lang="less">
.shop { min-height: 100vh; background: #f6f7f9; }
.hero { display: flex; align-items: center; justify-content: space-between; margin: 12px; padding: 22px; border-radius: 18px; color: #fff; box-shadow: 0 10px 22px rgba(30, 80, 90, .14); }
.hero h1 { margin: 7px 0; font-size: 23px; }
.hero p, .hero__eyebrow { margin: 0; font-size: 13px; opacity: .9; }
.notice { margin: 0 12px 8px; overflow: hidden; border-radius: 9px; }
.goods-list { padding: 0 12px; }
.van-card { margin: 10px 0; padding: 12px; border-radius: 12px; background: #fff; }
.van-card__thumb { overflow: hidden; border-radius: 8px; }
.bottom-space { height: 72px; }
.cart-icon { margin: 0 10px 0 4px; }
.selected { color: #646566; font-size: 13px; }
.checkout { padding: 22px 16px 28px; }
.checkout h2 { margin: 2px 0 18px; }
.checkout__total { padding: 18px 8px; text-align: right; }
.checkout__total b { color: #ee0a24; font-size: 22px; }
.result-popup { width: 78%; padding: 32px 22px 24px; text-align: center; }
.result-popup h2 { margin: 14px 0 8px; }
.result-popup p { margin: 0 0 22px; color: #969799; font-size: 12px; }
</style>