feat: cache

This commit is contained in:
zc 2025-10-11 22:36:50 +08:00
parent bd203f2f22
commit beded95be0
21 changed files with 1010 additions and 125 deletions

32
components.d.ts vendored Normal file
View File

@ -0,0 +1,32 @@
/* eslint-disable */
// @ts-nocheck
// Generated by unplugin-vue-components
// Read more: https://github.com/vuejs/core/pull/3399
// biome-ignore lint: disable
export {}
/* prettier-ignore */
declare module 'vue' {
export interface GlobalComponents {
RouterLink: typeof import('vue-router')['RouterLink']
RouterView: typeof import('vue-router')['RouterView']
VanArea: typeof import('vant/es')['Area']
VanButton: typeof import('vant/es')['Button']
VanCell: typeof import('vant/es')['Cell']
VanCellGroup: typeof import('vant/es')['CellGroup']
VanCheckbox: typeof import('vant/es')['Checkbox']
VanCheckboxGroup: typeof import('vant/es')['CheckboxGroup']
VanField: typeof import('vant/es')['Field']
VanForm: typeof import('vant/es')['Form']
VanIcon: typeof import('vant/es')['Icon']
VanNoticeBar: typeof import('vant/es')['NoticeBar']
VanPopover: typeof import('vant/es')['Popover']
VanPopup: typeof import('vant/es')['Popup']
VanRate: typeof import('vant/es')['Rate']
VanSearch: typeof import('vant/es')['Search']
VanTab: typeof import('vant/es')['Tab']
VanTabs: typeof import('vant/es')['Tabs']
VanTag: typeof import('vant/es')['Tag']
VanUploader: typeof import('vant/es')['Uploader']
}
}

View File

@ -16,6 +16,7 @@
"format": "prettier --write src/"
},
"dependencies": {
"@vant/area-data": "^2.1.0",
"axios": "^1.11.0",
"lodash-es": "^4.17.21",
"lz-utils-lib": "^1.0.60",
@ -30,6 +31,7 @@
"devDependencies": {
"@tsconfig/node22": "^22.0.2",
"@types/node": "^22.16.5",
"@vant/auto-import-resolver": "^1.3.0",
"@vitejs/plugin-vue": "^6.0.1",
"@vue/eslint-config-prettier": "^10.2.0",
"@vue/eslint-config-typescript": "^14.6.0",
@ -44,6 +46,7 @@
"typescript": "~5.8.0",
"unocss": "^66.4.2",
"unplugin-auto-import": "^20.0.0",
"unplugin-vue-components": "^29.1.0",
"vite": "^7.0.6",
"vite-plugin-vue-devtools": "^8.0.0",
"vue-tsc": "^3.0.4"

View File

@ -1,2 +1,2 @@
@import "./base.scss";
@use './base.scss' as *;
@use './variables.scss' as *;

16
src/assets/variables.scss Normal file
View File

@ -0,0 +1,16 @@
$theme-color: #01cf24;
$theme-disabled-color: #cefad5;
.theme-bg-color {
background: $theme-color !important;
}
.theme-border-color {
border-color: $theme-color !important;
}
.theme-disabled-color {
background: $theme-disabled-color !important;
}
.theme-color {
color: $theme-color !important;
}

View File

@ -1,9 +1,16 @@
import { createRouter, createWebHistory } from 'vue-router'
const router = createRouter({
history: createWebHistory(import.meta.env.BASE_URL),
routes: [
{
path: '/demo',
name: 'demo',
// route level code-splitting
// this generates a separate chunk (About.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import('../views/demo/index.vue'),
},
{
path: '/commodity-detail',
name: 'commodity-detail',
@ -12,6 +19,46 @@ const router = createRouter({
// which is lazy-loaded when the route is visited.
component: () => import('../views/commodity-detail/index.vue'),
},
{
path: '/order',
name: 'order',
// route level code-splitting
// this generates a separate chunk (About.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import('../views/order/index.vue'),
},
{
path: '/order/detail',
name: 'order/detail',
// route level code-splitting
// this generates a separate chunk (About.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import('../views/order/detail.vue'),
},
{
path: '/search-container',
name: 'search-container',
// route level code-splitting
// this generates a separate chunk (About.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import('../views/search-container/index.vue'),
},
{
path: '/review/center',
name: 'review/center',
// route level code-splitting
// this generates a separate chunk (About.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import('../views/review/center.vue'),
},
{
path: '/review/write',
name: 'review/write',
// route level code-splitting
// this generates a separate chunk (About.[hash].js) for this route
// which is lazy-loaded when the route is visited.
component: () => import('../views/review/write.vue'),
},
],
})

7
src/stores/app.ts Normal file
View File

@ -0,0 +1,7 @@
import { defineStore } from 'pinia'
export const useAppStore = defineStore('app', () => {
const themeColor = '#01CF24'
return { themeColor }
})

View File

@ -1,12 +0,0 @@
import { ref, computed } from 'vue'
import { defineStore } from 'pinia'
export const useCounterStore = defineStore('counter', () => {
const count = ref(0)
const doubleCount = computed(() => count.value * 2)
function increment() {
count.value++
}
return { count, doubleCount, increment }
})

View File

@ -0,0 +1,87 @@
<template>
<van-popup id="address-form" class="flex flex-col" v-model:show="model" closeable round>
<h3 class="text-center pt-5 text-base">{{ `${isEdit ? '修改' : '新增'}收货地址` }}</h3>
<div class="pt-3 flex-1 overflow-y-scroll">
<van-form @submit="onSubmit">
<van-cell-group inset>
<van-field v-model="formData.name" label="名字" placeholder="请输入收货人名称" :rules="[{ required: true, message: '请输入收货人名称' }]" />
<van-field v-model="formData.mobile" label="手机号" placeholder="请输入收货人手机号" :rules="[{ required: true, message: '请输入收货人手机号' }]" />
<van-field v-model="formData.area" is-link readonly name="area" label="地区选择" placeholder="点击选择省市区" @click="areaData.show = true" />
<van-field v-model="formData.addressDetail" rows="3" autosize label="详细地址" type="textarea" placeholder="请输入详细地址" :rules="[{ required: true, message: '请输入详细地址' }]" />
<van-field name="checkboxGroup" label="" class="py-1!">
<template #input>
<van-checkbox-group v-model="formData.default" direction="horizontal">
<van-checkbox :name="1" shape="square" class="text-xs">默认地址</van-checkbox>
</van-checkbox-group>
</template>
</van-field>
</van-cell-group>
<div class="bg-white w-full h-12 p-2 box-border">
<button class="block w-full h-full border-none theme-bg-color color-white rounded-[.04rem]" native-type="submit">确定</button>
</div>
</van-form>
</div>
</van-popup>
<van-popup v-model:show="areaData.show" destroy-on-close position="bottom">
<van-area :area-list="areaList" :model-value="areaData.pickerValue" @confirm="onSelectArea" @cancel="areaData.show = false" />
</van-popup>
</template>
<script setup lang="ts">
import { areaList } from '@vant/area-data'
const model = defineModel<boolean>()
defineProps<{ isEdit: boolean }>()
const formData = reactive({ name: '', mobile: '', area: '', addressDetail: '', province: '', city: '', region: '', default: [] })
const areaData = reactive({ show: false, pickerValue: '' })
const onSelectArea = ({ selectedValues, selectedOptions }: { selectedOptions: any; selectedValues: any }) => {
areaData.pickerValue = selectedValues.length ? selectedValues[selectedValues.length - 1] : ''
areaData.show = false
formData.area = selectedOptions.map((item: any) => item.text).join(' ')
}
const onSubmit = () => {
model.value = false
}
</script>
<style scoped lang="scss">
#address-form {
&.van-popup--bottom.van-popup--round {
border-radius: 0.12rem 0.12rem 0 0;
:deep(.van-popup__close-icon) {
top: 0.1rem;
font-size: 0.2rem;
color: #666;
}
}
.van-cell-group--inset {
margin: 0 0.12rem;
}
:deep(.van-cell) {
padding-left: 0;
padding-right: 0;
.van-checkbox__icon {
height: auto;
}
.van-checkbox__icon .van-icon {
width: 1em;
height: 1em;
border-radius: 0.04rem;
}
.van-checkbox__label {
margin-left: 0.04rem !important;
}
.van-cell__title {
// width: 0;
}
.van-cell__value {
// flex: none;
}
.van-checkbox__label {
color: #666;
}
}
}
</style>

View File

@ -0,0 +1,77 @@
<template>
<van-popup id="address-list" class="flex flex-col" v-model:show="model" position="bottom" :style="{ height: '70%' }" closeable round>
<h3 class="text-center pt-3 text-base">收货地址管理</h3>
<h4 class="flex justify-between items-center px-4 py-4 text-sm font-bold">
<span>常用地址</span>
<p>
<b @click="isManage = !isManage" class="font-bold">{{ !isManage ? '管理' : '退出管理' }}</b
><b class="ml-3 theme-color font-bold" @click="showAddAddressFormDialog = true">新增地址</b>
</p>
</h4>
<div class="flex-1 overflow-y-scroll">
<van-cell-group class="address-list__container">
<van-cell :class="['address-list__item']" v-for="item in addressList" :key="item.id">
<template #title>
<div class="item__info">
<h6 class="text-[.1rem]">浙江省 杭州市 萧山区 宁围街道</h6>
<p class="item__info__detail">湘湖路与事假达到交叉口 新希望宁问哦小区2幢2单元202</p>
<p class="item__info__subtitle">张丹 15100001111</p>
</div>
</template>
<template #value>
<div class="item__editor text-base">
<van-icon v-if="!isManage" name="edit" color="#999" @click="onEditAddress" />
<van-icon v-else name="delete-o" />
</div>
</template>
</van-cell>
</van-cell-group>
<div class="bg-white w-full h-12 p-2 box-border">
<button class="block w-full h-full border-none bg-red color-white rounded-[.04rem]">确定</button>
</div>
</div>
</van-popup>
</template>
<script setup lang="ts">
const model = defineModel<boolean>()
const addressFormType = defineModel<'add' | 'edit'>('addressFormType')
const showAddAddressFormDialog = defineModel<boolean>('showAddAddressFormDialog')
const addressList = [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 5 }]
const isManage = ref(false)
const onEditAddress = () => {
showAddAddressFormDialog.value = true
addressFormType.value = 'edit'
}
</script>
<style scoped lang="scss">
#address-list {
&.van-popup--bottom.van-popup--round {
border-radius: 0.12rem 0.12rem 0 0;
:deep(.van-popup__close-icon) {
top: 0.1rem;
font-size: 0.2rem;
color: #666;
}
}
:deep(.van-cell) {
.van-cell__title {
width: 0;
}
.item__info__detail {
font-weight: bold;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
}
.van-cell__value {
flex: none;
display: flex;
align-items: center;
}
}
}
</style>

View File

@ -0,0 +1,59 @@
<template>
<van-popup id="all-review" class="flex flex-col" v-model:show="model" position="bottom" :style="{ height: '90%' }" closeable round>
<h3 class="text-center pt-3 text-base">评价</h3>
<ul class="flex all-review__tab">
<li :class="['all-review__tab-li', { active: item.active }]" v-for="item in tabs" :key="item.value">{{ item.name }}</li>
</ul>
<ul class="all-review__contain flex-1 overflow-y-scroll">
<li class="p-3" v-for="item in reviews" :key="item.id">
<review-single />
</li>
</ul>
</van-popup>
</template>
<script setup lang="ts">
import reviewSingle from './review-single.vue'
const model = defineModel<boolean>()
const tabs = [
{ name: '全部', value: 0, active: true },
{ name: '最新评价', value: 3, active: false },
{ name: '5/4星好评', value: 1, active: false },
{ name: '图/视频', value: 2, active: false },
]
const reviews = [{ id: 1 }, { id: 2 }, { id: 3 }, { id: 5 }]
</script>
<style scoped lang="scss">
#all-review {
&.van-popup--bottom.van-popup--round {
border-radius: 0.12rem 0.12rem 0 0;
:deep(.van-popup__close-icon) {
top: 0.1rem;
font-size: 0.2rem;
color: #666;
}
}
.all-review__tab {
gap: 0.08rem;
flex-wrap: wrap;
padding: 0.12rem;
.all-review__tab-li {
padding: 0.08rem 0.12rem;
background: #f2f5f9;
border-radius: 0.04rem;
&.active {
background: #fdf0e4;
color: #f3802e;
font-weight: bold;
}
}
}
.all-review__contain {
li + li {
border-top: 6px solid #f1f5f8;
}
}
}
</style>

View File

@ -1,14 +1,14 @@
<template>
<div id="review-single">
<h3 class="flex items-center ml-2 text-sm">
<img class="w-8 h-8 rounded-full" src="" alt="">
<h3 class="flex items-center text-sm">
<img class="w-8 h-8 rounded-full" src="" alt="" />
<div class="ml-1">
<p class="">**</p>
<p class="mt-0.5 text-xs color-[#666]">2025.08.23</p>
</div>
</h3>
<div class="review-content mt-3">
<p class="pl-2">公司大丰收东方闪电防守打法四大分三大胜多负少的</p>
<p>公司大丰收东方闪电防守打法四大分三大胜多负少的</p>
<p class="flex items-center mt-2">
<img class="w-16 h-16 mr-1 rounded-[4px]" src="" alt="" v-for="(item, index) in list" :key="index" />
</p>
@ -17,9 +17,7 @@
</template>
<script lang="ts" setup>
const list = ref(['',''])
const list = ref(['', ''])
</script>
<style>
</style>
<style></style>

View File

@ -1,73 +1,87 @@
<template>
<div id="shopping-cart" class="p-2 relative">
<div class="mb-8">
<div class="cart-wrap">
<p class="flex justify-between"><span class="max-w-12 mr-1">张四</span> <b class="font- flex-1">我的详细地址是什么东西的地方的好的么</b></p>
</div>
<div class="cart-wrap mt-2">
<div class="flex">
<img class="w-16 h-16 mr-4" style="border-radius: .04rem" src="" alt="">
<p>¥<span>29.9</span></p>
<van-popup id="shopping-cart" v-model:show="model" position="bottom" :style="{ height: '70%' }" closeable round>
<van-notice-bar class="text-xs! h-8!" left-icon="volume-o" text="平台保证 | 假一赔四·极速退款" />
<div class="p-2 bg-[#f5f5f5]">
<div class="mb-8">
<div class="cart-wrap">
<p class="flex justify-between" @click="showAddressList = true">
<van-icon name="location-o" class="mr-1" /><span class="max-w-12 mr-1">张四</span> <b class="font- flex-1">我的详细地址是什么东西的地方的好的么</b><van-icon name="arrow" />
</p>
</div>
<div class="cart-cagetory-wrap">
<h4>更多系列</h4>
<div class="cagetory">
<p>手表系列</p>
<p>手表系列水</p>
<p>手表</p>
<p>手表123</p>
<p>手表12</p>
<div class="cart-wrap mt-2">
<div class="flex">
<img class="w-16 h-16 mr-4" style="border-radius: 0.04rem" src="" alt="" />
<p>¥<span>29.9</span></p>
</div>
<h4>颜色</h4>
<div class="cagetory">
<p>手表系列</p>
<p>手表系列水</p>
<p>手表</p>
<p>手表123</p>
<p>手表12</p>
<div class="cart-cagetory-wrap">
<h4>更多系列</h4>
<div class="cagetory">
<p>手表系列</p>
<p>手表系列水</p>
<p>手表</p>
<p>手表123</p>
<p>手表12</p>
</div>
<h4>颜色</h4>
<div class="cagetory">
<p>手表系列</p>
<p>手表系列水</p>
<p>手表</p>
<p>手表123</p>
<p>手表12</p>
</div>
</div>
</div>
</div>
<div class="fixed bg-white w-full h-12 left-0 bottom-0 p-2 box-border">
<button class="block w-full h-full border-none theme-bg-color color-white rounded-[.04rem]">确定</button>
</div>
</div>
<div class="absolute w-full h-8 left-0 bottom-0 px-2 box-border">
<button class="block w-full h-full border-none bg-red color-white rounded-[.04rem]">确定</button>
</div>
</div>
</van-popup>
</template>
<script lang="ts" setup>
const model = defineModel<boolean>()
const showAddressList = defineModel<boolean>('showAddressList')
</script>
<style lang="scss" scoped>
#shopping-cart{
.cart-wrap{
padding: .12rem .08rem;
#shopping-cart {
.cart-wrap {
padding: 0.12rem 0.08rem;
background: #fff;
border-radius: .04rem;
border-radius: 0.04rem;
overflow: hidden;
}
.cart-cagetory-wrap{
h4{
.cart-cagetory-wrap {
h4 {
font-weight: bold;
margin-top: .16rem;
margin-bottom: .12rem;
margin-top: 0.16rem;
margin-bottom: 0.12rem;
}
.cagetory {
display: flex;
flex-wrap: wrap;
& >p{
flex-shrink: 0;
height: .28rem;
line-height: .28rem;
& > p {
flex-shrink: 0;
height: 0.28rem;
line-height: 0.28rem;
background: #f2f2f2;
padding: 0 .2rem;
padding: 0 0.2rem;
color: #666;
border-radius: .04rem;
margin-right: .08rem;
margin-bottom: .08rem;
border-radius: 0.04rem;
margin-right: 0.08rem;
margin-bottom: 0.08rem;
}
}
}
&.van-popup--bottom.van-popup--round {
border-radius: 0.08rem 0.08rem 0 0;
:deep(.van-popup__close-icon) {
top: 0.06rem;
font-size: 0.2rem;
color: #666;
}
}
}
</style>
</style>

View File

@ -1,8 +1,6 @@
<template>
<div id="commodity-detail">
<header class="h-70 bg-black">
轮播图
</header>
<header class="h-70 bg-[#aaa]"></header>
<main>
<div class="detail-info wrap">
<h2 class="text-2xl color-[#000]">陕西珍珠首饰项链</h2>
@ -13,7 +11,7 @@
</ul>
</div>
<div class="review-info wrap">
<h3 class="wrap-title"><b>用户评价(45.6+)</b><span>全部评价</span></h3>
<h3 class="wrap-title"><b>用户评价(45.6+)</b><span @click="onShowAllReview">全部评价 ></span></h3>
<div class="mt-5">
<review-single />
</div>
@ -32,107 +30,127 @@
<instruction-info class="mt3" />
</div>
</main>
<shopping-cart class="mb-10"></shopping-cart>
<footer class="flex fixed bottom-0 left-0 w-full h-10 justify-between items-center pr-2 box-border bg-white">
<div class="flex items-center w-[1.6rem] justify-around">
<div class="flex items-center w-[1.4rem] justify-around">
<p class="icon-wrap">
<img src="" alt="">
<span>首页</span>
</p>
<p class="icon-wrap">
<img src="" alt="">
<van-icon name="phone-o" size=".14rem" />
<span>客服</span>
</p>
<p class="icon-wrap">
<img src="" alt="">
<van-icon name="like-o" size=".14rem" />
<span>收藏</span>
</p>
<p class="icon-wrap">
<van-icon name="shopping-cart-o" size=".14rem" />
<span>购物车</span>
</p>
</div>
<div class="flex-1 button-wrap">
<button class="bg-orange">加入购物车</button>
<button class="bg-red color-white">立即购买</button>
<button class="bg-orange text-white w-30!">加入购物车</button>
<button class="theme-bg-color color-white" @click="onPayNowBtn">立即购买</button>
</div>
</footer>
<shopping-cart v-model="showShopCart" v-model:showAddressList="showAddressList"></shopping-cart>
<all-review v-model="showAllReview"></all-review>
<address-list v-model="showAddressList" v-model:showAddAddressFormDialog="showAddAddressFormDialog" v-model:addressFormType="addressFormType"></address-list>
<address-form v-model="showAddAddressFormDialog" :isEdit="addressFormType === 'edit'"></address-form>
</div>
</template>
<script lang="ts" setup>
import allReview from './components/all-review.vue'
import reviewSingle from './components/review-single.vue'
import infoTable from './components/info-table.vue'
import instructionInfo from './components/instruction-info.vue'
import shoppingCart from './components/shopping-cart.vue'
import addressList from './components/address-list.vue'
import addressForm from './components/address-form.vue'
const showShopCart = ref(false) //
const showAllReview = ref(false) //
const showAddressList = ref(false) //
const showAddAddressFormDialog = ref(false) // form
const addressFormType = ref<'add' | 'edit'>('add')
//
const onShowAllReview = () => {
showAllReview.value = true
}
//
const onPayNowBtn = () => {
showShopCart.value = true
}
</script>
<style scope lang="scss">
#commodity-detail{
#commodity-detail {
background: #f2f2f2;
.wrap{
margin: .08rem .12rem;
border-radius: .12rem;
padding: .16rem;
.wrap {
margin: 0.08rem 0.12rem;
border-radius: 0.12rem;
padding: 0.16rem;
background: #fff;
.wrap-title{
.wrap-title {
display: flex;
justify-content: space-between;
align-items: center;
b{
font-size: .16rem;
b {
font-size: 0.16rem;
color: #000;
}
span{
font-size: .12rem;
span {
font-size: 0.12rem;
color: #777;
}
}
}
.detail-info{
li{
margin-top: .12rem;
span{
margin-right: .1rem;
.detail-info {
li {
margin-top: 0.12rem;
span {
margin-right: 0.1rem;
color: #888;
}
em{
em {
color: #000;
font-style: normal;
}
button{
button {
border: 1px solid #aaa;
border-radius: .02rem;
border-radius: 0.02rem;
background: #fff;
padding: .02rem .04rem;
padding: 0.02rem 0.04rem;
}
}
}
footer{
.icon-wrap{
footer {
.icon-wrap {
display: flex;
flex-direction: column;
justify-content: center;
img{
width: .1rem;
height: .1rem;
align-items: center;
.van-icon {
width: 0.14rem;
height: 0.16rem;
}
span{
font-size: .11rem;
span {
font-size: 0.11rem;
}
}
.button-wrap{
.button-wrap {
display: flex;
align-items: center;
border-radius: .04rem;
border-radius: 0.04rem;
overflow: hidden;
height: .32rem;
button{
height: 0.32rem;
button {
display: block;
height: 100% !important;
width: 50%;
font-size: .14rem;
font-size: 0.14rem;
border: none;
height: .28rem;
height: 0.28rem;
}
}
}
}
</style>
</style>

25
src/views/demo/index.vue Normal file
View File

@ -0,0 +1,25 @@
<template>
<div>
<van-cell-group>
<van-cell v-for="item in list" :title="item.title" :value="item.path" :key="item.path" @click="onClick(item)" is-link />
</van-cell-group>
</div>
</template>
<script setup lang="ts">
const list = [
{ title: '搜索结果', path: 'search-container' },
{ title: '商品详情', path: 'commodity-detail' },
{ title: '我的订单', path: 'order' },
{ title: '订单详情', path: 'order/detail' },
{ title: '评价中心', path: 'review/center' },
{ title: '填写评价', path: 'review/write' },
]
const router = useRouter()
const onClick = (item: any) => {
router.push({ name: item.path })
}
</script>
<style scoped></style>

View File

@ -0,0 +1,79 @@
<template>
<div id="order-detail" class="bg-[#f5f5f5] p-3 pb-0">
<div class="bg-white rounded-[.08rem]">
<!-- 下单时间 -->
<div class="flex justify-between items-center pt-3 px-3">
<p class="text-sm">2025-10-20 20:10:20</p>
<p class="text-sm text-[#FB6406]">{{ orderDetail.status }}</p>
</div>
<div class="px-3 pt-3 flex">
<img class="w-20 h-20 round-[0.08rem] bg-[#f5f5f5]" />
<div class="flex flex-col justify-between flex-1 w-0 ml-3">
<div>
<p class="product-name text-[.13rem]">{{ orderDetail.productName }}</p>
<p class="text-xs text-[#888] mt-2">{{ orderDetail.spec }}</p>
</div>
<!-- <p class="text-[.13rem] text-right">
<span class="">{{ orderDetail.num }}件商品</span>
<span class="ml-3"
>实付款<i class="text-xs">¥</i><b class="text-base ml-1">{{ orderDetail.price }}</b></span
>
</p> -->
</div>
</div>
<van-cell-group class="mt-3">
<van-cell title="实付款" value="700.12" />
<van-cell title="订单编号" value="4124324234" />
<van-cell title="支付方式" value="在线支付" />
<van-cell title="支付时间" value="2025-10-12 12:12:12" />
<van-cell title="下单时间" value="2025-10-12 12:12:12" />
<van-cell title="收货地址" value="浙江省杭州市萧山区信息港小镇" />
</van-cell-group>
<van-cell-group class="mt-3">
<van-cell title="配送快递" value="申通快递" />
<van-cell title="快递状态" value="已发货" />
<van-cell title="快递单号" value="sdfsdfsdfs23423423423" />
<van-cell title="收货人" value="张三" />
<van-cell title="收货联系方式" value="13122223333" />
<van-cell title="收货地址" value="浙江省杭州市萧山区信息港小镇" />
</van-cell-group>
</div>
</div>
</template>
<script setup lang="ts">
const orderDetail = ref({
id: 1,
storeName: '天猫 得力官方旗舰店',
status: '交易成功',
productName: '得力纳米胶双面胶高粘度防粘防尘始发地水电费水电费',
spec: '[基础款长1米] 粘厚1mm宽24mm(1卷装)',
num: 1,
price: 4.89,
})
</script>
<style scoped lang="scss">
#order-detail {
.product-name {
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
overflow: hidden;
}
.van-hairline--top-bottom:after {
border-bottom: 0;
}
:deep(.van-cell) {
.van-cell__value {
color: #333;
}
.van-cell__title {
color: #999;
}
}
.van-cell:after {
border: 0;
}
}
</style>

171
src/views/order/index.vue Normal file
View File

@ -0,0 +1,171 @@
<template>
<div class="order-page">
<!-- 搜索栏 -->
<div>
<van-search show-action v-model="searchValue" placeholder="搜索订单" @search="onSearch" @cancel="onCancel" />
</div>
<!-- 标签页 -->
<van-tabs>
<van-tab v-for="tab in tabs" :key="tab.value" :title="tab.name" :name="tab.value" class="tab-item"> </van-tab>
</van-tabs>
<!-- 订单列表 -->
<div class="order-list bg-[#f5f5f5]">
<template v-if="orders.length > 0">
<div v-for="order in orders" :key="order.id" class="order-card bg-white round-[0.08rem]">
<!-- 下单时间 -->
<div class="flex justify-between items-center pt-3 px-3">
<p class="text-sm">2025-10-20 20:10:20</p>
<p class="text-sm text-[#FB6406]">{{ order.status }}</p>
</div>
<!-- 商品信息 -->
<div class="px-3 pt-3 flex" @click="goDetail(order.id)">
<img class="w-20 h-20 round-[0.08rem] bg-[#f5f5f5]" />
<div class="flex flex-col justify-between flex-1 w-0 ml-3">
<div>
<p class="product-name text-[.13rem]">{{ order.productName }}</p>
<p class="text-xs text-[#888] mt-2">{{ order.spec }}</p>
</div>
<p class="text-[.13rem] text-right">
<span class="">{{ order.num }}件商品</span>
<span class="ml-3"
>实付款<i class="text-xs">¥</i><b class="text-base ml-1">{{ order.price }}</b></span
>
</p>
</div>
</div>
<!-- 操作区域 -->
<div class="order-footer flex items-center justify-between p-3">
<van-popover class="order-page__popover" v-model:show="showPopover" placement="top-start" :actions="actions" @select="onSelect">
<template #reference>
<span class="text-[#666]">更多</span>
</template>
</van-popover>
<div class="flex items-center">
<van-button size="small" class="h-7!" @click="handleAddToCart(order.id)">加入购物车</van-button>
<van-button size="small" type="warning" class="ml-2! h-7! theme-bg-color theme-border-color" @click="handleBuyAgain(order.id)">再买一单</van-button>
</div>
</div>
</div>
</template>
<div v-else class="empty-state">
<div class="empty-icon">
<van-icon name="orders-o" size="48" />
</div>
<div>暂无订单</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { showToast } from 'vant'
const searchValue = ref('')
//
const activeTab = ref(0)
const showActionMenu = ref(false)
const orders = ref([
{
id: 1,
storeName: '天猫 得力官方旗舰店',
status: '交易成功',
productName: '得力纳米胶双面胶高粘度防粘防尘始发地水电费水电费',
spec: '[基础款长1米] 粘厚1mm宽24mm(1卷装)',
num: 1,
price: 4.89,
},
{
id: 2,
storeName: '天猫 得力官方旗舰店',
status: '交易成功',
productName: '得力纳米胶双面胶高粘度防粘防尘始发地水电费水电费',
spec: '[基础款长1米] 粘厚1mm宽24mm(1卷装)',
num: 2,
price: 4.89,
},
])
const tabs = reactive([
{ name: '全部', value: 0 },
{ name: '待付款', value: 1 },
{ name: '待发货', value: 2 },
{ name: '已发货', value: 3 },
{ name: '退款', value: 4 },
])
const onSearch = () => {}
const onCancel = () => {}
const usePopover = () => {
const showPopover = ref(false)
const actions = [
{ text: '取消订单', value: 'cancel' },
{ text: '删除订单', value: 'delete' },
]
const onSelect = () => {}
return {
showPopover,
actions,
onSelect,
}
}
const { showPopover, actions, onSelect } = usePopover()
//
const handleBuyAgain = (orderId) => {
router.push({ name: 'commodity-detail', query: { id: orderId } })
}
const handleAddToCart = (orderId) => {}
const router = useRouter()
const goDetail = (id: number) => {
router.push({ path: '/order/detail', query: { id } })
}
</script>
<style scoped lang="scss">
.order-page {
min-height: 100vh;
:deep(.van-tabs) {
&.van-tabs--line .van-tabs__wrap {
height: 0.3rem;
}
}
.order-card {
& + .order-card {
margin-top: 0.12rem;
}
}
.product-name {
display: -webkit-box;
-webkit-line-clamp: 1;
-webkit-box-orient: vertical;
overflow: hidden;
}
.empty-state {
text-align: center;
padding: 60px 20px;
color: var(--text-color-light);
}
.empty-icon {
font-size: 48px;
margin-bottom: 16px;
opacity: 0.5;
}
}
.order-page__popover {
:deep(.van-popover__content) {
.van-popover__action {
width: 0.8rem;
height: 0.3rem;
line-height: 0.3rem;
}
.van-popover__action-text {
font-size: 0.12rem;
}
}
}
</style>

View File

@ -0,0 +1,64 @@
<template>
<div id="review-center" class="bg-[#FFF3CD] min-h-screen flex flex-col">
<header class="flex items-center p-5">
<img class="w-10 h-10 rounded-full bg-black" src="" alt="" style="border: 2px solid #fff" />
<p class="ml-2">用户名</p>
</header>
<ul class="flex items-center justify-center mx-3 rounded-[.08rem] bg-white">
<li v-for="item in nums" :key="item.name" class="flex-1 text-center py-4">
<p class="text-lg font-bold">{{ item.num }}</p>
<p class="mt-1 text-[#d1ac35]">{{ item.name }}</p>
</li>
</ul>
<main class="flex-1 rounded-[.1rem] overflow-scroll bg-white mt-3">
<van-tabs v-model:active="tabActive" sticky>
<van-tab title="待评价" name="0"></van-tab>
<van-tab title="已评价" name="1"></van-tab>
</van-tabs>
<ul>
<li v-for="item in reviews" :key="item.id" class="flex items-center p-2 relative">
<img class="w-16 h-16" src="" alt="" />
<div class="ml-2 h-16 flex flex-col justify-between">
<p class="">{{ item.name }}</p>
<div class="flex items-center">
<p class="text-xs text-[#666]">一键评分</p>
<van-rate v-model="item.rate" allow-half size=".12rem" />
</div>
</div>
<van-button size="small" class="absolute! bottom-2 right-2 w-[.54rem]! h-[.26rem]! theme-color theme-disabled-color font-bold" color="#F4E1E8" @click="onGoReview(item.id)"
>去评价</van-button
>
</li>
</ul>
</main>
</div>
</template>
<script setup lang="ts">
const tabActive = ref('0')
const reviews = ref([
{ id: 1, name: '新疆大柚子水电费水电费水电费手动阀是是否神鼎飞丹砂', rate: 0 },
{ id: 2, name: '新疆大柚子水电费水电费水电费手动阀是是否神鼎飞丹砂', rate: 0 },
{ id: 3, name: '新疆大柚子水电费水电费水电费手动阀是是否神鼎飞丹砂', rate: 0 },
{ id: 1, name: '新疆大柚子水电费水电费水电费手动阀是是否神鼎飞丹砂', rate: 0 },
{ id: 2, name: '新疆大柚子水电费水电费水电费手动阀是是否神鼎飞丹砂', rate: 0 },
{ id: 3, name: '新疆大柚子水电费水电费水电费手动阀是是否神鼎飞丹砂', rate: 0 },
{ id: 1, name: '新疆大柚子水电费水电费水电费手动阀是是否神鼎飞丹砂', rate: 0 },
{ id: 2, name: '新疆大柚子水电费水电费水电费手动阀是是否神鼎飞丹砂', rate: 0 },
{ id: 3, name: '新疆大柚子水电费水电费水电费手动阀是是否神鼎飞丹砂', rate: 0 },
])
const nums = ref([
{ name: '全部', num: 0 },
{ name: '已评价', num: 0 },
{ name: '待评价', num: 0 },
])
const router = useRouter()
const onGoReview = (id: number) => {
router.push({ name: 'review/write', query: { id } })
}
</script>
<style scoped lang="scss">
#review-center {
}
</style>

View File

@ -0,0 +1,46 @@
<template>
<div id="review-write" class="bg-[#f5f5f5] min-h-screen">
<h3 class="pl-3 py-3 text-[#666]">您的评价将帮助其他用户</h3>
<van-cell-group>
<van-cell v-for="item in stars" :title="item.name" :key="item.name">
<template #value>
<div class="flex items-center justify-end">
<p class="text-xs">{{ item.starMap }}</p>
<van-rate class="ml-2" v-model="item.value" allow-half size=".16rem" @change="onChangeStar(item, item.value)" />
</div>
</template>
</van-cell>
</van-cell-group>
<van-field class="mt-2" left-icon="edit" v-model="message" rows="3" autosize label="" type="textarea" maxlength="200" placeholder="请输入文字" show-word-limit />
<div class="bg-white pl-3">
<van-uploader v-model="fileList" multiple />
</div>
<footer class="mx-3 mt-3">
<van-button type="primary" size="large" class="h-10! text-sm! theme-bg-color theme-border-color">发布</van-button>
</footer>
</div>
</template>
<script setup lang="ts">
const stars = reactive([
{ name: '商品评价', value: 0, starMap: '' },
{ name: '快递包装', value: 0, starMap: '' },
{ name: '发货速度', value: 0, starMap: '' },
{ name: '售后服务', value: 0, starMap: '' },
])
const onChangeStar = (item: any, v: any) => {
if (v >= 4.5) item.starMap = '非常好'
else if (v >= 3) item.starMap = '挺好'
else if (v >= 2) item.starMap = '一般'
else item.starMap = '差'
}
const message = ref('')
const fileList = ref([])
</script>
<style scoped lang="scss">
#review-write {
}
</style>

View File

@ -0,0 +1,84 @@
<template>
<div id="search-container" class="h-screen bg-[#f2f2f2] flex flex-col">
<header class="search-header flex items-center p-2">
<van-search class="flex-1 p-0!" background="#f2f2f2" v-model="searchValue" placeholder="请输入搜索关键词" />
<van-icon name="shopping-cart-o" size=".2rem" class="ml-2" />
</header>
<van-tabs v-model:active="searchSort" sticky :color="appStore.themeColor">
<van-tab title="综合" name="0"></van-tab>
<van-tab title="销量" name="1"></van-tab>
<van-tab title="价格" name="2"></van-tab>
</van-tabs>
<ul class="search-condition bg-white pt-2 flex-1 overflow-y-scroll">
<li v-for="item in searchList" :key="item.id" class="flex item-center p-2 relative" @click="onGoDetail(item.id)">
<img src="" alt="" class="w-24 h-24" />
<div class="ml-2">
<h4 class="text-[.13rem]">{{ item.name }}</h4>
<p class="text-[#888] mt-1">新鲜优质 | 酸甜多汁 | 足斤足两</p>
<p class="mt-1">
<van-tag plain type="primary" color="#ec4d3c">标签1</van-tag>
<van-tag plain type="primary" color="#ec4d3c">标签2</van-tag>
<van-tag plain type="primary" color="#ec4d3c">标签3</van-tag>
</p>
<p class="mt-3">
<span class="text-[#e71e1e] font-bold">¥<b class="font-bold text-base ml-0.5">0</b>.01</span>
</p>
</div>
<van-icon name="add" :color="appStore.themeColor" size=".2rem" class="absolute! right-3 bottom-6 h-5" />
</li>
</ul>
</div>
</template>
<script setup lang="ts">
import { useAppStore } from '@/stores/app.ts'
const searchValue = ref('')
const searchSort = ref('0')
const searchList = ref([
{ id: 1, name: '新疆大柚子水电费水电费水电费手动阀是是否神鼎飞丹砂' },
{ id: 2, name: '新疆大柚子水电费水电费水电费手动阀是是否神鼎飞丹砂' },
{ id: 3, name: '新疆大柚子水电费水电费水电费手动阀是是否神鼎飞丹砂' },
{ id: 1, name: '新疆大柚子水电费水电费水电费手动阀是是否神鼎飞丹砂' },
{ id: 2, name: '新疆大柚子水电费水电费水电费手动阀是是否神鼎飞丹砂' },
{ id: 3, name: '新疆大柚子水电费水电费水电费手动阀是是否神鼎飞丹砂' },
{ id: 1, name: '新疆大柚子水电费水电费水电费手动阀是是否神鼎飞丹砂' },
{ id: 2, name: '新疆大柚子水电费水电费水电费手动阀是是否神鼎飞丹砂' },
{ id: 3, name: '新疆大柚子水电费水电费水电费手动阀是是否神鼎飞丹砂' },
])
const appStore = useAppStore()
const router = useRouter()
const onGoDetail = (id: number) => {
router.push({ name: 'commodity-detail', query: { id } })
}
</script>
<style scoped lang="scss">
#search-container {
.search-header {
:deep(.van-search) {
.van-search__content {
border-radius: 0.16rem;
background: #fff;
.van-field__body {
padding-right: 0.06rem;
}
.van-cell {
height: 0.3rem;
}
}
}
}
:deep(.van-tabs) {
.van-tabs__wrap {
height: 0.32rem;
}
}
:deep(.van-tag) {
font-size: 0.1rem;
margin-right: 0.04rem;
border-color: #ddd !important;
}
}
</style>

View File

@ -4,6 +4,8 @@ import UnoCSS from 'unocss/vite'
import { defineConfig } from 'vite'
import vue from '@vitejs/plugin-vue'
import AutoImport from 'unplugin-auto-import/vite'
import Components from 'unplugin-vue-components/vite'
import { VantResolver } from '@vant/auto-import-resolver'
import vueDevTools from 'vite-plugin-vue-devtools'
// https://vite.dev/config/
@ -11,18 +13,30 @@ export default defineConfig({
plugins: [
UnoCSS(),
vue(),
vueDevTools(),
// vueDevTools(),
AutoImport({
imports: [
'vue',
'vue-router',
],
dts: 'src/auto-import.d.ts'
resolvers: [VantResolver()],
imports: ['vue', 'vue-router'],
dts: 'src/auto-import.d.ts',
}),
Components({
resolvers: [VantResolver()],
}),
],
server: {
host: '0.0.0.0',
port: 3000,
},
resolve: {
alias: {
'@': fileURLToPath(new URL('./src', import.meta.url))
'@': fileURLToPath(new URL('./src', import.meta.url)),
},
},
css: {
preprocessorOptions: {
scss: {
additionalData: `@use "@/assets/variables.scss" as *;`,
},
},
},
})

View File

@ -1133,6 +1133,16 @@
tinyglobby "^0.2.14"
unplugin-utils "^0.2.4"
"@vant/area-data@^2.1.0":
version "2.1.0"
resolved "https://registry.npmmirror.com/@vant/area-data/-/area-data-2.1.0.tgz#77dc6b3ddde18dc25ee8bfd9431b2c67f37233cd"
integrity sha512-wx9PrUX7wSUJiFcz8UrcvZfTjV6sTc+7SHcbjGQQzEcv5y+EwOo5uV4ZKdfrR5Hzcw4MA08LQdvXPSEb4nWbug==
"@vant/auto-import-resolver@^1.3.0":
version "1.3.0"
resolved "https://registry.npmmirror.com/@vant/auto-import-resolver/-/auto-import-resolver-1.3.0.tgz#d9ab270edb641e9ab5d7c5841c2d721bd52c4133"
integrity sha512-lJyWtCyFizR4bHZvMiNMF3w+WTFTUWAvka1eqTnPK9ticUcKTCOx6qEmHcm8JPb3g1t3GaD2W3MnHkBp/nHamw==
"@vant/popperjs@^1.3.0":
version "1.3.0"
resolved "https://registry.npmmirror.com/@vant/popperjs/-/popperjs-1.3.0.tgz#e0eff017124b5b2352ef3b36a6df06277f4400f2"
@ -1682,6 +1692,13 @@ debug@^4.1.0, debug@^4.3.1, debug@^4.3.2, debug@^4.3.4, debug@^4.4.0, debug@^4.4
dependencies:
ms "^2.1.3"
debug@^4.4.3:
version "4.4.3"
resolved "https://registry.npmmirror.com/debug/-/debug-4.4.3.tgz#c6ae432d9bd9662582fce08709b038c58e9e3d6a"
integrity sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==
dependencies:
ms "^2.1.3"
deep-is@^0.1.3:
version "0.1.4"
resolved "https://registry.npmmirror.com/deep-is/-/deep-is-0.1.4.tgz#a6f2dce612fadd2ef1f519b73551f17e85199831"
@ -2386,7 +2403,7 @@ levn@^0.4.1:
prelude-ls "^1.2.1"
type-check "~0.4.0"
local-pkg@^1.1.1:
local-pkg@^1.1.1, local-pkg@^1.1.2:
version "1.1.2"
resolved "https://registry.npmmirror.com/local-pkg/-/local-pkg-1.1.2.tgz#c03d208787126445303f8161619dc701afa4abb5"
integrity sha512-arhlxbFRmoQHl33a0Zkle/YWlmNwoyt6QNZEIJcqNbdrsix5Lvc4HyyI3EnwxTYlZYc32EbYrQ8SzEZ7dqgg9A==
@ -2435,6 +2452,13 @@ magic-string@^0.30.17, magic-string@^0.30.4:
dependencies:
"@jridgewell/sourcemap-codec" "^1.5.5"
magic-string@^0.30.19:
version "0.30.19"
resolved "https://registry.npmmirror.com/magic-string/-/magic-string-0.30.19.tgz#cebe9f104e565602e5d2098c5f2e79a77cc86da9"
integrity sha512-2N21sPY9Ws53PZvsEpVtNuSW+ScYbQdp4b9qUaL+9QkHUrGFKo56Lg9Emg5s9V/qrtNBmiR01sYhUOwu3H+VOw==
dependencies:
"@jridgewell/sourcemap-codec" "^1.5.5"
math-intrinsics@^1.1.0:
version "1.1.0"
resolved "https://registry.npmmirror.com/math-intrinsics/-/math-intrinsics-1.1.0.tgz#a0dd74be81e2aa5c2f27e65ce283605ee4e2b7f9"
@ -2494,7 +2518,7 @@ mitt@^3.0.1:
resolved "https://registry.npmmirror.com/mitt/-/mitt-3.0.1.tgz#ea36cf0cc30403601ae074c8f77b7092cdab36d1"
integrity sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==
mlly@^1.7.4:
mlly@^1.7.4, mlly@^1.8.0:
version "1.8.0"
resolved "https://registry.npmmirror.com/mlly/-/mlly-1.8.0.tgz#e074612b938af8eba1eaf43299cbc89cb72d824e"
integrity sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==
@ -3026,6 +3050,14 @@ tinyglobby@^0.2.14:
fdir "^6.4.4"
picomatch "^4.0.2"
tinyglobby@^0.2.15:
version "0.2.15"
resolved "https://registry.npmmirror.com/tinyglobby/-/tinyglobby-0.2.15.tgz#e228dd1e638cea993d2fdb4fcd2d4602a79951c2"
integrity sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==
dependencies:
fdir "^6.5.0"
picomatch "^4.0.3"
to-regex-range@^5.0.1:
version "5.0.1"
resolved "https://registry.npmmirror.com/to-regex-range/-/to-regex-range-5.0.1.tgz#1648c44aae7c8d988a326018ed72f5b4dd0392e4"
@ -3163,6 +3195,30 @@ unplugin-utils@^0.3.0:
pathe "^2.0.3"
picomatch "^4.0.3"
unplugin-vue-components@^29.1.0:
version "29.1.0"
resolved "https://registry.npmmirror.com/unplugin-vue-components/-/unplugin-vue-components-29.1.0.tgz#6064dfd43ebfc2d54fc04d6c391d30c9f66d3360"
integrity sha512-z/9ACPXth199s9aCTCdKZAhe5QGOpvzJYP+Hkd0GN1/PpAmsu+W3UlRY3BJAewPqQxh5xi56+Og6mfiCV1Jzpg==
dependencies:
chokidar "^3.6.0"
debug "^4.4.3"
local-pkg "^1.1.2"
magic-string "^0.30.19"
mlly "^1.8.0"
tinyglobby "^0.2.15"
unplugin "^2.3.10"
unplugin-utils "^0.3.0"
unplugin@^2.3.10:
version "2.3.10"
resolved "https://registry.npmmirror.com/unplugin/-/unplugin-2.3.10.tgz#15e75fec9384743335be7e54e5c88b5c187a3e94"
integrity sha512-6NCPkv1ClwH+/BGE9QeoTIl09nuiAt0gS28nn1PvYXsGKRwM2TCbFA2QiilmehPDTXIe684k4rZI1yl3A1PCUw==
dependencies:
"@jridgewell/remapping" "^2.3.5"
acorn "^8.15.0"
picomatch "^4.0.3"
webpack-virtual-modules "^0.6.2"
unplugin@^2.3.5:
version "2.3.8"
resolved "https://registry.npmmirror.com/unplugin/-/unplugin-2.3.8.tgz#617198a50ec3467f28d03e4096167339747405e6"