Files
guilixu-admin/src/views/shop/shopGoods/components/search.vue
T
gxwebsoft 3bc856fc95 feat(shopGoods): 优化商品列表加载及封面图性能
- image.ts 中 getCompressedImageUrl 增加缓存,避免列表重渲染时重复计算压缩 URL
- shopGoods 页面用原生 img 标签替换 antdv a-image,支持浏览器懒加载减少首屏网络压力
- shopGoods 页面数据加载流程优化,取消表格自动首屏加载,改为手动触发,合并请求避免重复调用
- 首屏分类数据延迟加载,不阻塞首屏渲染,提升页面响应速度
- search.vue 中用 onMounted 替代 watch immediate,防止首次入场重复触发请求
- 新增商品封面图样式,保证图片大小固定且样式统一
2026-08-06 17:19:15 +08:00

295 lines
7.1 KiB
Vue
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!-- 搜索表单 -->
<template>
<div class="goods-toolbar">
<!-- 常驻工具栏 -->
<a-space :size="10" style="flex-wrap: wrap">
<a-button type="primary" class="ele-btn-icon" @click="add">
<template #icon>
<PlusOutlined />
</template>
<span>添加</span>
</a-button>
<!-- 导入(合并为下拉菜单) -->
<a-dropdown>
<a-button type="dashed" class="ele-btn-icon">
<template #icon>
<UploadOutlined />
</template>
<span>导入</span>
<DownOutlined class="ele-btn-icon-arrow" />
</a-button>
<template #overlay>
<a-menu>
<a-menu-item key="goods" @click="openImport">
导入商品
</a-menu-item>
<a-menu-item key="image" @click="openImageImport">
导入图片
</a-menu-item>
<a-menu-item key="detail" @click="openDetailImport">
导入详情
</a-menu-item>
</a-menu>
</template>
</a-dropdown>
<a-button type="dashed" class="ele-btn-icon" @click="handleExport">
导出xls
</a-button>
<a-radio-group v-model:value="type" @change="handleSearch">
<a-radio-button value="出售中"
>出售中({{ goodsCount?.totalNum }})
</a-radio-button>
<a-radio-button value="待上架"
>待上架({{ goodsCount?.totalNum2 }})
</a-radio-button>
<a-radio-button value="已售罄"
>已售罄({{ goodsCount?.totalNum3 }})
</a-radio-button>
</a-radio-group>
<a-tree-select
allow-clear
:tree-data="navigationList"
tree-default-expand-all
style="width: 180px"
:listHeight="700"
placeholder="请选择分类"
:value="where.categoryId || undefined"
:dropdown-style="{ overflow: 'auto' }"
@update:value="(value?: number) => (where.categoryId = value)"
@change="onCategoryId"
/>
<a-input-search
allow-clear
placeholder="请输入关键词"
style="width: 220px"
v-model:value="where.keywords"
@pressEnter="reload"
@search="reload"
/>
<a-button type="text" @click="reset">重置</a-button>
</a-space>
<!-- 选中操作栏:勾选商品后才显示 -->
<div v-if="selection && selection.length > 0" class="selection-bar">
<span class="selection-bar-count">已选 {{ selection.length }} 项</span>
<a-button
danger
type="primary"
class="ele-btn-icon"
@click="removeBatch"
>
<template #icon>
<DeleteOutlined />
</template>
<span>批量删除</span>
</a-button>
<a-button type="primary" class="ele-btn-icon" @click="batchUp">
<template #icon>
<ArrowUpOutlined />
</template>
<span>批量上架</span>
</a-button>
<a-button danger class="ele-btn-icon" @click="batchDown">
<template #icon>
<ArrowDownOutlined />
</template>
<span>批量下架</span>
</a-button>
<a-button type="dashed" class="ele-btn-icon" @click="convertContent">
<template #icon>
<SyncOutlined />
</template>
<span>转换详情</span>
</a-button>
</div>
</div>
</template>
<script lang="ts" setup>
import {
PlusOutlined,
DeleteOutlined,
UploadOutlined,
SyncOutlined,
ArrowUpOutlined,
ArrowDownOutlined,
DownOutlined
} from '@ant-design/icons-vue';
import { ref, watch, onMounted } from 'vue';
import { getCount } from '@/api/shop/shopGoods';
import type { GoodsCount, ShopGoodsParam } from '@/api/shop/shopGoods/model';
import useSearch from '@/utils/use-search';
import { getMerchantId } from '@/utils/merchant';
import { ShopGoodsCategory } from '@/api/shop/shopGoodsCategory/model';
const props = withDefaults(
defineProps<{
// 选中的商品
selection?: any[];
merchantId?: number;
navigationList?: ShopGoodsCategory[];
}>(),
{
merchantId: getMerchantId()
}
);
const type = ref<string>();
// 统计数据
const goodsCount = ref<GoodsCount>();
// 表单数据
const { where, resetFields } = useSearch<ShopGoodsParam>({
goodsId: undefined,
isShow: undefined,
status: undefined,
stock: undefined,
categoryId: undefined,
merchantId: undefined,
keywords: ''
});
const emit = defineEmits<{
(e: 'search', where?: ShopGoodsParam): void;
(e: 'add'): void;
(e: 'remove'): void;
(e: 'batchUp'): void;
(e: 'batchDown'): void;
(e: 'batchMove'): void;
(e: 'import'): void;
(e: 'imageImport'): void;
(e: 'detailImport'): void;
(e: 'convertContent'): void;
(e: 'export'): void;
}>();
// 新增
const add = () => {
emit('add');
};
// 批量删除
const removeBatch = () => {
emit('remove');
};
// 批量上架
const batchUp = () => {
emit('batchUp');
};
// 批量下架
const batchDown = () => {
emit('batchDown');
};
// 打开导入
const openImport = () => {
emit('import');
};
const openImageImport = () => {
emit('imageImport');
};
const openDetailImport = () => {
emit('detailImport');
};
const convertContent = () => {
emit('convertContent');
};
// 导出
const handleExport = () => {
emit('export');
};
const handleSearch = (e) => {
const text = e.target.value;
resetFields();
if (text === '出售中') {
where.sceneType = 'on_sale';
}
if (text === '待上架') {
where.sceneType = 'pending';
}
if (text === '已售罄') {
where.sceneType = 'sold_out';
}
emit('search', where);
};
const reload = () => {
getCount(where).then((data: any) => {
goodsCount.value = data;
});
emit('search', where);
};
/* 重置 */
const reset = () => {
resetFields();
type.value = '';
reload();
};
// 按分类查询
const onCategoryId = (id: number) => {
where.categoryId = id;
emit('search', where);
};
// 首次进入:设置商户条件并触发统计 + 列表加载。
// 用 onMounted 替代 watch 的 immediate,避免与 ele-pro-table 的自动加载(initLoad)重复发请求。
onMounted(() => {
const id = props.merchantId;
where.merchantId = Number(id) > 0 ? id : undefined;
reload();
});
watch(
() => props.merchantId,
(id) => {
where.merchantId = Number(id) > 0 ? id : undefined;
reload();
}
);
</script>
<style lang="less" scoped>
.goods-toolbar {
display: flex;
flex-direction: column;
gap: 12px;
}
/* 选中后浮现的操作栏 */
.selection-bar {
display: flex;
align-items: center;
flex-wrap: wrap;
gap: 10px;
padding: 10px 14px;
background: #e6f4ff;
border: 1px solid #91caff;
border-radius: 6px;
}
.selection-bar-count {
font-weight: 600;
color: #1677ff;
margin-right: 4px;
white-space: nowrap;
}
.ele-btn-icon-arrow {
font-size: 12px;
margin-left: 2px;
}
</style>