vue3-andsign 中实现实物电商列表的页面

发布于:2025-06-09 ⋅ 阅读:(14) ⋅ 点赞:(0)

这里自己做一个代码整理 做了一个实物电商 选品中心的页面 看里面有些效果挺好 这里记录一下

直接粘贴代码了 我自己能看懂  做了一个列表显示 骨架屏等 效果 使用了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>