打包文件

This commit is contained in:
dqz 2025-03-08 08:09:31 +08:00
parent 64d0010303
commit be76c9ccc4
9 changed files with 502 additions and 247 deletions

View File

@ -1,7 +1,7 @@
# 预发布环境的环境变量(命名必须以 VITE_ 开头)
## 后端接口地址(如果解决跨域问题采用 CORS 就需要写绝对路径)
VITE_BASE_URL = https://apifoxmock.com/m1/2930465-2145633-default/api/v1
VITE_BASE_URL = '/shop-api/api'
## 打包构建静态资源公共路径(例如部署到 https://un-pany.github.io/ 域名下就需要填写 /
VITE_PUBLIC_PATH = /
VITE_PUBLIC_PATH = /shop/

View File

@ -1,5 +1,5 @@
<script setup lang="ts">
import { demoRoutes, emptyDemoRoutes, systemRoutes } from "@/router"
import { systemRoutes } from "@/router"
import Description from "@@/components/Description.vue"
import Cell from "./components/Cell.vue"
import Title from "./components/Title.vue"
@ -11,17 +11,7 @@ import Title from "./components/Title.vue"
<div un-mt-40px>
<Title text="示例集合" />
<Cell
v-for="route in [...demoRoutes, ...systemRoutes]"
:key="route.path"
:title="route.meta?.title || ''"
:path="route.path"
un-mt-12px
/>
</div>
<div un-my-20px>
<Title text="更多示例" />
<Cell
v-for="route in emptyDemoRoutes"
v-for="route in [...systemRoutes]"
:key="route.path"
:title="route.meta?.title || ''"
:path="route.path"

View File

@ -1,12 +1,12 @@
<script setup lang="ts">
import { useCartStore } from "@/pinia/stores/cart"
import { useProductStore } from "@/pinia/stores/product"
import { throttle } from "lodash-es"
import { storeToRefs } from "pinia"
import VanPopup from "vant/es/popup"
import { computed, onBeforeUnmount, onMounted, ref } from "vue"
import Detail from "./components/detail.vue"
import Cart from "./components/cart.vue"
import { useProductStore } from "@/pinia/stores/product"
import { useCartStore } from "@/pinia/stores/cart"
import Detail from "./components/detail.vue"
//
const productStore = useProductStore()
@ -49,13 +49,16 @@ function handleCategoryClick(index: number) {
//
const throttledUpdate = throttle(() => {
if (!scrollContainer.value) return
if (!scrollContainer.value || !categoryRefs.value.length) return
//
const validRefs = categoryRefs.value.filter(Boolean)
if (!validRefs.length) return
const containerTop = scrollContainer.value.getBoundingClientRect().top
const offsets = categoryRefs.value.map((el) => {
const offsets = validRefs.map((el) => {
return el.getBoundingClientRect().top - containerTop
})
let activeIndex = 0
for (let i = offsets.length - 1; i >= 0; i--) {
if (offsets[i] < 100) {
@ -87,7 +90,7 @@ function handleRemoveFromCart(product: any) {
cartStore.removeFromCart(product.id)
}
const getCartItemCount = (productId: number) => {
function getCartItemCount(productId: number) {
const item = cartItems.value.find(item => item.product.id === productId)
return item ? item.quantity : 0
}
@ -105,11 +108,11 @@ onBeforeUnmount(() => {
})
//
function handleCheckout() {
//
console.log('去结算', totalPrice.value)
}
const router = useRouter()
function handleCheckout() {
router.push("/product/checkout")
}
</script>
<template>
@ -180,7 +183,7 @@ function handleCheckout() {
:style="{
opacity: product.stock === 0 ? 0.6 : 1,
}"
></van-button>
/>
</div>
</div>
</div>
@ -190,8 +193,10 @@ function handleCheckout() {
<!-- 底部购物车栏 -->
<div v-if="cartItems.length" class="shopping-cart-bar">
<div class="cart-info">
<van-badge :content="totalQuantity"
@click.stop="showCartPopup = true">
<van-badge
:content="totalQuantity"
@click.stop="showCartPopup = true"
>
<van-icon name="shopping-cart-o" size="24" />
</van-badge>
<div class="total-price">
@ -410,7 +415,7 @@ function handleCheckout() {
align-items: center;
justify-content: space-between;
padding: 0 16px;
box-shadow: 0 -2px 8px rgba(0,0,0,0.05);
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.05);
z-index: 100;
}

View File

@ -1,16 +1,17 @@
<script setup lang="ts">
import { useCartStore } from "@/pinia/stores/cart"
import { storeToRefs } from "pinia"
import { showConfirmDialog } from 'vant'
import { showConfirmDialog } from "vant"
import { useRouter } from "vue-router"
//
const cartStore = useCartStore()
const { cartItems, totalPrice, totalQuantity } = storeToRefs(cartStore) //
//
//
const emit = defineEmits(["cartClose"])
//
const router = useRouter()
//
const cartStore = useCartStore()
const { cartItems, totalPrice, totalQuantity } = storeToRefs(cartStore)//
function handleClose() {
emit("cartClose")
}
@ -24,20 +25,20 @@ function handleRemoveFromCart(product: any) {
function handleClearCart() {
showConfirmDialog({
title: '确认清空购物车吗?',
title: "确认清空购物车吗?"
})
.then(() => {
cartStore.clearCart()
})
.catch(() => {
// on cancel
});
})
}
//
function handleCheckout() {
//
console.log('去结算', totalPrice.value)
handleClose()
router.push("/product/checkout")
}
</script>
@ -46,8 +47,12 @@ function handleCheckout() {
<!-- 修改后的header结构 -->
<div class="flex-header">
<div class="clear-header">
<van-button size="small" type="primary" class="clear-btn" plain
@click="handleClearCart">清空购物车</van-button>
<van-button
size="small" type="primary" class="clear-btn" plain
@click="handleClearCart"
>
清空购物车
</van-button>
</div>
</div>
<div class="product-list">
@ -76,8 +81,10 @@ function handleCheckout() {
<div class="cart-actions">
<van-button size="mini" icon="minus" @click.stop="handleRemoveFromCart(item.product)" />
<span class="cart-count">{{ item.quantity }}</span>
<van-button size="mini" type="primary" class="add-cart-btn" icon="plus"
@click.stop="handleAddToCart(item.product)"></van-button>
<van-button
size="mini" type="primary" class="add-cart-btn" icon="plus"
@click.stop="handleAddToCart(item.product)"
/>
</div>
</div>
</div>

View File

@ -0,0 +1,251 @@
<script setup lang="ts">
import { useCartStore } from "@/pinia/stores/cart"
import { storeToRefs } from "pinia"
import { showConfirmDialog } from "vant"
import { ref } from "vue"
const cartStore = useCartStore()
const { cartItems, totalPrice } = storeToRefs(cartStore)
//
const paymentMethods = [
{ value: 1, label: "微信支付", icon: "wechat" },
{ value: 2, label: "支付宝", icon: "alipay" },
{ value: 3, label: "银行卡支付", icon: "credit-pay" }
]
const selectedPayment = ref<number>()
const contact = ref("")
const remark = ref("")
const submitting = ref(false)
//
async function handleSubmit() {
if (!cartItems.value.length) {
return showConfirmDialog({
title: "提示",
message: "请先选择商品后再结算"
})
}
if (!selectedPayment.value) {
return showConfirmDialog({
title: "提示",
message: "请选择支付方式"
})
}
submitting.value = true
try {
// TODO: API
console.log("提交订单", {
items: cartItems.value,
total: totalPrice.value,
paymentMethod: selectedPayment.value,
contact: contact.value,
remark: remark.value
})
await showConfirmDialog({
title: "提交成功",
message: "订单已创建,正在跳转支付..."
})
//
cartStore.clearCart()
} finally {
submitting.value = false
}
}
</script>
<template>
<div class="checkout-container">
<!-- 新增固定导航栏 -->
<van-nav-bar
title="结算页面"
left-text="返回"
left-arrow
fixed
@click-left="() => $router.go(-1)"
/>
<!-- 原有内容容器 -->
<div class="content-wrapper">
<!-- 原有商品列表等代码保持不动 -->
<van-cell-group class="product-list">
<van-cell
v-for="item in cartItems"
:key="item.product.id"
class="product-item"
>
<template #icon>
<van-image
:src="item.product.image"
width="60"
height="60"
class="product-image"
/>
</template>
<div class="product-info">
<div class="product-name van-ellipsis">
{{ item.product.name }}
</div>
<div class="price-row">
<span class="product-price">
¥{{ item.product.price.toFixed(2) }}
</span>
<span class="quantity">
×{{ item.quantity }}
</span>
</div>
</div>
</van-cell>
</van-cell-group>
<!-- 联系方式与备注 -->
<van-cell-group class="contact-form">
<van-field
v-model="contact"
label="联系方式"
placeholder="请输入手机号"
:rules="[
{ required: true, message: '请填写联系方式' },
{ pattern: /^1[3-9]\d{9}$/, message: '手机号格式不正确' },
]"
/>
<van-field
v-model="remark"
label="备注"
type="textarea"
placeholder="选填,可备注特殊需求"
rows="2"
autosize
/>
</van-cell-group>
<!-- 支付方式选择 -->
<van-cell-group title="支付方式" class="payment-methods">
<van-radio-group v-model="selectedPayment">
<van-cell
v-for="method in paymentMethods"
:key="method.value"
clickable
@click="selectedPayment = method.value"
>
<template #icon>
<van-icon :name="method.icon" class="method-icon" />
</template>
<template #title>
<span class="method-label">{{ method.label }}</span>
</template>
<template #right-icon>
<van-radio :name="method.value" />
</template>
</van-cell>
</van-radio-group>
</van-cell-group>
<!-- 提交订单栏 -->
<div class="submit-bar">
<div class="total-price">
合计¥{{ totalPrice.toFixed(2) }}
</div>
<van-button
type="primary"
size="large"
:loading="submitting"
loading-text="提交中..."
@click="handleSubmit"
>
提交订单
</van-button>
</div>
</div>
</div>
</template>
<style scoped>
.van-nav-bar {
position: fixed;
top: 0;
width: 100%;
z-index: 999;
background: #fff;
border-bottom: 1px solid #ebedf0;
}
.content-wrapper {
padding-top: 46px; /* 导航栏高度 */
}
.checkout-container {
padding: 12px 16px 80px;
}
.product-list {
margin-bottom: 20px;
}
.product-item {
align-items: flex-start;
}
.product-image {
margin-right: 12px;
border-radius: 4px;
}
.product-info {
display: flex;
flex-direction: column;
justify-content: space-between;
height: 60px;
}
.price-row {
display: flex;
justify-content: space-between;
align-items: center;
}
.product-price {
color: #e95d5d;
font-weight: bold;
font-size: 14px;
}
.quantity {
color: #666;
font-size: 13px;
}
.method-icon {
font-size: 24px;
margin-right: 10px;
}
.method-label {
font-size: 15px;
}
.submit-bar {
position: fixed;
bottom: 0;
left: 0;
right: 0;
background: #fff;
padding: 10px 16px;
display: flex;
align-items: center;
justify-content: space-between;
box-shadow: 0 -2px 8px rgba(0, 0, 0, 0.05);
}
.total-price {
font-size: 16px;
color: #e95d5d;
font-weight: bold;
}
</style>

View File

@ -8,33 +8,13 @@ export interface Product {
stock: number // 商品库存
description: string // 商品描述
image: string // 商品图片URL
label: number // 商品标签
}
export const useProductStore = defineStore("product", () => {
// 商品数据
const labels = ref([
{ id: 1, name: "热门推荐" },
{ id: 2, name: "手机数码" },
{ id: 3, name: "家用电器" },
{ id: 4, name: "电脑办公" }
])
const categories = ref([
{ id: 1, name: "商品1", price: 99.9, image: "/img/1.jpg", label: 1, stock: 10, description: "主动降噪30小时续航IPX4防水等级" },
{ id: 2, name: "商品2", price: 199.9, image: "/img/2.jpg", label: 1, stock: 990, description: "主动降噪30小时续航IPX4防水等级" },
{ id: 3, name: "旗舰手机", price: 5999, image: "/img/3.jpg", label: 2, stock: -1, description: "骁龙8 Gen2处理器1亿像素主摄120Hz刷新率屏幕" },
{ id: 4, name: "旗舰手机", price: 5999, image: "/img/4.jpg", label: 2, stock: -1, description: "骁龙8 Gen2处理器1亿像素主摄120Hz刷新率屏幕" },
{ id: 5, name: "旗舰手机", price: 5999, image: "/img/5.jpg", label: 2, stock: -1, description: "骁龙8 Gen2处理器1亿像素主摄120Hz刷新率屏幕" },
{ id: 6, name: "旗舰手机", price: 5999, image: "/img/6.jpg", label: 2, stock: -1, description: "骁龙8 Gen2处理器1亿像素主摄120Hz刷新率屏幕" },
{ id: 7, name: "旗舰手机", price: 5999, image: "/img/7.jpg", label: 2, stock: 999, description: "骁龙8 Gen2处理器1亿像素主摄120Hz刷新率屏幕" },
{ id: 10, name: "旗舰手机", price: 5999, image: "/img/10.jpg", label: 3, stock: 999, description: "骁龙8 Gen2处理器1亿像素主摄120Hz刷新率屏幕" },
{ id: 13, name: "旗舰手机", price: 5999, image: "/img/13.jpg", label: 3, stock: 999, description: "骁龙8 Gen2处理器1亿像素主摄120Hz刷新率屏幕" },
{ id: 14, name: "旗舰手机", price: 5999, image: "/img/14.jpg", label: 3, stock: 999, description: "骁龙8 Gen2处理器1亿像素主摄120Hz刷新率屏幕" },
{ id: 15, name: "旗舰手机", price: 5999, image: "/img/15.jpg", label: 3, stock: 0, description: "骁龙8 Gen2处理器1亿像素主摄120Hz刷新率屏幕" },
{ id: 16, name: "旗舰手机", price: 5999, image: "/img/16.jpg", label: 3, stock: 0, description: "骁龙8 Gen2处理器1亿像素主摄120Hz刷新率屏幕" },
{ id: 17, name: "旗舰手机", price: 5999, image: "/img/17.jpg", label: 3, stock: 0, description: "骁龙8 Gen2处理器1亿像素主摄120Hz刷新率屏幕" },
{ id: 18, name: "旗舰手机", price: 5999, image: "/img/18.jpg", label: 4, stock: 50, description: "骁龙8 Gen2处理器1亿像素主摄120Hz刷新率屏幕" },
{ id: 20, name: "旗舰手机", price: 5999, image: "/img/20.jpg", label: 4, stock: 10, description: "骁龙8 Gen2处理器1亿像素主摄120Hz刷新率屏幕" }
])
const labels = ref<Array<{ id: number, name: string }>>([])
const categories = ref<Product[]>([])
const getGoods = async () => {
try {
@ -57,11 +37,11 @@ export const useProductStore = defineStore("product", () => {
label: g.categoryId
}))
} catch (error) {
console.error('获取商品数据失败:', error)
console.error("获取商品数据失败:", error)
}
}
getGoods();
return { labels: labels, categories, getGoods }
getGoods()
return { labels, categories, getGoods }
})
/**

View File

@ -29,6 +29,32 @@ export const systemRoutes: RouteRecordRaw[] = [
/** 业务页面 */
export const routes: RouteRecordRaw[] = [
{
path: "/product/checkout",
component: () => import("@/pages/product/components/checkout.vue"),
name: "Checkout",
meta: {
title: "订单结算"
}
},
{
path: "/",
component: () => import("@/pages/product/ProductList.vue"),
name: "ProductList",
meta: {
title: "商品列表",
keepAlive: true,
layout: {
navBar: {
showNavBar: false,
showLeftArrow: true
}
}
}
}
]
/* export const routes: RouteRecordRaw[] = [
{
path: "/login",
component: () => import("@/pages/login/index.vue"),
@ -76,7 +102,7 @@ export const routes: RouteRecordRaw[] = [
}
]
/** 示例页面 */
// 示例页面
export const demoRoutes: RouteRecordRaw[] = [
{
path: "/product",
@ -160,9 +186,9 @@ export const demoRoutes: RouteRecordRaw[] = [
}
}
]
*/
/** 空示例页面 */
export const emptyDemoRoutes: RouteRecordRaw[] = [
/* export const emptyDemoRoutes: RouteRecordRaw[] = [
{
path: "/i18n",
component: () => import("@/pages/demo/i18n.vue"),
@ -205,12 +231,12 @@ export const emptyDemoRoutes: RouteRecordRaw[] = [
}
}
}
]
] */
/** 路由实例 */
export const router = createRouter({
history: VITE_ROUTER_HISTORY === "hash" ? createWebHashHistory(VITE_PUBLIC_PATH) : createWebHistory(VITE_PUBLIC_PATH),
routes: [...systemRoutes, ...routes, ...demoRoutes, ...emptyDemoRoutes]
routes: [...systemRoutes, ...routes]
})
// 注册路由导航守卫

View File

@ -16,19 +16,15 @@ declare module 'vue' {
VanCellGroup: typeof import('vant/es')['CellGroup']
VanConfigProvider: typeof import('vant/es')['ConfigProvider']
VanDivider: typeof import('vant/es')['Divider']
VanEmpty: typeof import('vant/es')['Empty']
VanField: typeof import('vant/es')['Field']
VanForm: typeof import('vant/es')['Form']
VanIcon: typeof import('vant/es')['Icon']
VanImage: typeof import('vant/es')['Image']
VanLoading: typeof import('vant/es')['Loading']
VanNavBar: typeof import('vant/es')['NavBar']
VanNoticeBar: typeof import('vant/es')['NoticeBar']
VanRadio: typeof import('vant/es')['Radio']
VanRadioGroup: typeof import('vant/es')['RadioGroup']
VanSidebar: typeof import('vant/es')['Sidebar']
VanSidebarItem: typeof import('vant/es')['SidebarItem']
VanSwitch: typeof import('vant/es')['Switch']
VanTabbar: typeof import('vant/es')['Tabbar']
VanTabbarItem: typeof import('vant/es')['TabbarItem']
}

View File

@ -15,7 +15,7 @@ export default defineConfig(({ mode }) => {
const { VITE_PUBLIC_PATH } = loadEnv(mode, process.cwd(), "") as ImportMetaEnv
return {
// 开发或打包构建时用到的公共基础路径
base: "./",
base: VITE_PUBLIC_PATH,
resolve: {
alias: {
// @ 符号指向 src 目录