这里自己做一个代码整理 做了一个实物电商 选品中心的页面 看里面有些效果挺好 这里记录一下
直接粘贴代码了 我自己能看懂 做了一个列表显示 骨架屏等 效果 使用了grid 布局 比媒体查询好使
<script setup lang="ts">
import { ref, onMounted, watch } from 'vue'
import { useRoute } from 'vue-router'
import { message, Skeleton } from 'ant-design-vue'
import * as echarts from 'echarts'
import Tip from './components/Tip.vue'
import { useBrandStore } from '@/store/modules/brand'
import { DownOutlined } from '@ant-design/icons-vue'
import Item from './components/item.vue'
import * as phyicalApi from '@/api/physical'
const route = useRoute()
let chart = ref<echarts.ECharts | null>(null)
const oneRef = ref(null)
const dataSource = ref([])
const loading = ref(false)
const skeletonLoading = ref(false)
const addGoodsRef = ref(null)
interface Pagination {
page: number
pageSize: number
total: number
current?: number
onChange: Function
showSizeChanger: boolean
showQuickJumper: boolean
}
const handlePageChange = (page: any, pageSize: any) => {
pagination.value.pageSize = pageSize
searchParams.value.pageSize = pageSize
}
const pagination = ref<Pagination>({
page: 1,
pageSize: 12,
total: 0,
onChange: handlePageChange,
showSizeChanger: true,
showQuickJumper: true,
})
const searchParams = ref<SearchParams>({
currentPage: pagination.value.page,
pageSize: pagination.value.pageSize,
pdrPutAwayTimeNum: 0,
fortyBelowPrice: 0,
})
interface SearchParams {
currentPage: number
pageSize: number
[propName: string]: any
}
const onChangePage = (page: any, pageSize: any) => {
searchParams.value.currentPage = page
skeletonLoading.value = true
getList()
}
onMounted(() => {
getList()
getCateList()
})
const getList = async () => {
try {
loading.value = true
skeletonLoading.value = true
const { state, data, message: msg } = await phyicalApi.getGoodsList(searchParams.value)
if (state == 200) {
dataSource.value = data.list
pagination.value.total = Number(data.totalCount)
} else {
message.error(msg)
}
} catch (error) {
message.error('网络请求连接失败~')
} finally {
loading.value = false
setTimeout(() => {
skeletonLoading.value = false
}, 500)
}
}
const drawOne = (chart: any) => {
let option = {
xAxis: {
type: 'category',
data: ['Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat', 'Sun'],
},
yAxis: {
type: 'value',
},
series: [
{
data: [
120,
{
value: 200,
itemStyle: {
color: '#a90000',
},
},
150,
80,
70,
110,
130,
],
type: 'bar',
},
],
}
chart.setOption(option)
}
const onResize = () => {
if (chart.value) {
chart.value.dispose()
chart.value = echarts.init(oneRef.value)
drawOne(chart.value)
}
}
window.addEventListener('resize', onResize)
let dxTokenRef = ref()
const add = () => {
dxTokenRef.value.callbackFn()
}
const getToken = (e: any) => {
console.log(e)
}
const getCateList = async () => {
try {
const { data, state, message: msg } = await phyicalApi.getProductCateList()
if (state == 200) {
filterList.value = data.map((item: any) => {
return {
label: item.cateName,
id: item.cateId
}
})
filterList.value.unshift({
label: "所有类目",
id: 0
})
} else {
message.error(msg)
}
} catch (error) {
console.error(error)
}
}
const filterList = ref([])
const filterSelected = ref(0)
const filterSelectedId = ref(0)
const filterClick = (item: any, index: any) => {
filterSelected.value = index
filterSelectedId.value = item.id
searchParams.value.cateId = item.id
if (item.id == 0) {
delete searchParams.value.cateId
}
pagination.value.page = 1
searchParams.value.currentPage = 1
getList()
}
let checked1 = ref(false)
let checked2 = ref(false)
watch(checked1, (newVal) => {
searchParams.value.fortyBelowPrice = newVal ? 1 : 0
pagination.value.page = 1
searchParams.value.currentPage = 1
getList()
})
watch(checked2, (newVal) => {
searchParams.value.pdrPutAwayTimeNum = newVal ? 1 : 0
pagination.value.page = 1
searchParams.value.currentPage = 1
getList()
})
const beforePrice = ref("")
const afterPrice = ref("")
import type { SelectProps } from 'ant-design-vue';
const value = ref("")
const options = ref<SelectProps['options']>([
{
value: '1',
label: '描述不符包退',
},
{
value: '2',
label: '二十四小时发货',
},
{
value: '3',
label: '四十八小时发货',
},
{
value: '4',
label: '假货包赔',
},
]);
const handleChange = (e) => {
value.value = e
}
const handleSearch = () => {
if (value.value) {
searchParams.value.serviceAssurance = Number(value.value)
}
searchParams.value.beforePrice = beforePrice.value * 100
searchParams.value.afterPrice = afterPrice.value * 100
if (!searchParams.value.beforePrice) {
delete searchParams.value.beforePrice
}
if (!searchParams.value.afterPrice) {
delete searchParams.value.afterPrice
}
pagination.value.page = 1
searchParams.value.currentPage = 1
getList()
}
const handleReset = () => {
delete searchParams.value.beforePrice
delete searchParams.value.afterPrice
delete searchParams.value.serviceAssurance
searchParams.value.currentPage = 1
checked1.value = false
checked2.value = false
beforePrice.value = ""
afterPrice.value = ""
value.value = ""
pagination.value.page = 1
filterClick(filterList.value[0], 0)
}
</script>
<template>
<page-container :title="route.meta.title">
<a-card >
<div class="category-list">
<div class="title">分销分类</div>
<div class="left">
<div class="box">
<div class="item hiddenText" v-for="(item, index) in filterList"
:class="{ active: filterSelected === index }" @click="filterClick(item, index)" :key="item.id">
{{ item.label }}
</div>
</div>
</div>
</div>
<div class="filter-list">
<div class="title">商品信息</div>
<div class="left">
<div class="price-between">
<div class="left-price">
<a-input-number placeholder="开始价格" v-model:value="beforePrice" style="width: 120px"
:disabled="loading"></a-input-number>
</div>
<div class="line">—</div>
<div class="right-price">
<a-input-number placeholder="结束价格" v-model:value="afterPrice" style="width: 120px"
:disabled="loading"></a-input-number>
</div>
</div>
<div>
<a-select ref="select" v-model:value="value" placeholder="服务类型" allowClear style="width: 150px"
:options="options" @change="handleChange" :disabled="loading"></a-select>
</div>
<div class="filter-actions">
<a-button @click="handleSearch" type="primary" :loading="loading">查询</a-button>
<a-button @click="handleReset" type="default" :disabled="loading">重置</a-button>
</div>
<div class="checkbox-group">
<a-checkbox v-model:checked="checked1" :disabled="loading">40元以下商品</a-checkbox>
<a-checkbox v-model:checked="checked2" :disabled="loading">新上架商品</a-checkbox>
</div>
</div>
</div>
<div class="good-list-container">
<template v-if="skeletonLoading && !loading">
<div class="skeleton-list">
<Skeleton v-for="i in 12" :key="i" active />
</div>
</template>
<template v-else-if="dataSource.length > 0">
<div class="good-list">
<div class="good-item" v-for="(item, index) in dataSource" :key="index">
<Item :info="item"></Item>
</div>
</div>
</template>
<template v-else>
<div class="empty-list">
<img src="@/assets/empty.png" alt="暂无数据">
<div class="empty-text">暂无商品数据</div>
</div>
</template>
</div>
<div class="pagination-container" v-if="dataSource.length > 0">
<a-pagination v-model:current="pagination.page" show-quick-jumper :total="pagination.total"
:show-total="(total) => `共 ${total} 件商品`" @change="onChangePage" :disabled="loading" />
</div>
</a-card>
</page-container>
</template>
<style lang="less" scoped>
.hiddenText {
display: inline-block;
width: 100px;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.good-list-container {
min-height: 500px;
position: relative;
}
.skeleton-list {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 24px;
padding: 16px;
}
.good-list {
width: 100%;
margin: 0 auto;
display: grid;
grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
gap: 24px;
padding: 16px;
}
.empty-list {
height: 400px;
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
color: #999;
img {
width: 200px;
height: auto;
margin-bottom: 20px;
}
.empty-text {
font-size: 16px;
}
}
.good-item {
border-radius: 8px;
overflow: hidden;
border: 1px solid #f0f0f0;
background: #fff;
transition: all 0.3s;
&:hover {
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
transform: translateY(-2px);
}
}
.filter-list {
display: flex;
align-items: flex-start;
margin: 24px 0;
padding: 16px;
background: #fafafa;
border-radius: 8px;
.title {
font-size: 14px;
color: #666;
margin-right: 25px;
min-width: 80px;
line-height: 32px;
}
.left {
flex: 1;
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 16px;
.price-between {
display: flex;
align-items: center;
.line {
color: #999;
margin: 0 10px;
}
}
.filter-actions {
display: flex;
gap: 8px;
}
.checkbox-group {
display: flex;
gap: 16px;
margin-left: 16px;
}
}
}
.category-list {
display: flex;
margin-bottom: 24px;
padding: 16px;
background: #fafafa;
border-radius: 8px;
.title {
font-size: 14px;
color: #666;
margin-right: 25px;
min-width: 80px;
line-height: 32px;
}
.left {
flex: 1;
.box {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 8px;
.item {
padding: 6px 12px;
border-radius: 16px;
cursor: pointer;
border: 1px solid #e8e8e8;
color: #666;
transition: all 0.3s;
font-size: 14px;
&:hover {
border-color: #1890ff;
color: #1890ff;
}
&.active {
background: #1890ff;
color: #fff;
border-color: #1890ff;
}
}
}
}
}
.pagination-container {
margin-top: 24px;
display: flex;
justify-content: center;
}
a:hover {
color: #1890ff;
}
</style>