Files
guilixu-admin/src/views/shop/shopGoods/components/search.vue
赵忠林 e2f6dce7a1 feat(shopGoods): 新增商品置顶功能及批量上下架操作
- 在商品模型中新增 isTop 字段支持置顶状态(1置顶,0不置顶)
- 商品列表新增置顶列,支持列表内联切换置顶状态
- 启用排序号列排序功能,置顶列参与前端排序
- 新增单个商品上架/下架切换接口及批量上下架操作
- 优化商品列表工具栏和选中操作栏,加入批量上架、下架按钮
- 商品分类及图片列表调用 OSS 图片压缩接口处理展示图片
- 新增删除商品导入模板及图片导入模板下载链接与使用说明
- 优化图片压缩工具,新增去除 URL 中 x-oss-process 参数功能,确保调用参数生效
2026-07-24 10:57:23 +08:00

293 lines
6.9 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 } 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);
};
watch(
() => props.merchantId,
(id) => {
if (Number(id) > 0) {
where.merchantId = id;
reload();
} else {
where.merchantId = undefined;
reload();
}
},
{ immediate: true }
);
</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>