Merge remote-tracking branch 'origin/main'

This commit is contained in:
2026-07-06 11:14:45 +08:00
4 changed files with 158 additions and 14 deletions

View File

@@ -3,6 +3,12 @@ import type { ApiResult, PageResult } from '@/api';
import type { ShopGoods, ShopGoodsParam } from './model'; import type { ShopGoods, ShopGoodsParam } from './model';
import { MODULES_API_URL } from '@/config/setting'; import { MODULES_API_URL } from '@/config/setting';
export interface ConvertGoodsContentResult {
total: number;
success: number;
updatedGoodsIds: number[];
}
/** /**
* 分页查询商品 * 分页查询商品
*/ */
@@ -115,6 +121,30 @@ export async function getCount(params: ShopGoodsParam) {
return Promise.reject(new Error(res.data.message)); return Promise.reject(new Error(res.data.message));
} }
/**
* 商品详情Markdown图片转HTML
*/
export async function convertMarkdownImagesToHtml(params?: {
tenantId?: number;
goodsId?: number;
code?: string;
}) {
const res = await request.post<ApiResult<ConvertGoodsContentResult>>(
MODULES_API_URL + '/shop/shop-goods/content/markdown-images/to-html',
null,
{
params
}
);
if (res.data.code === 0) {
return {
message: res.data.message,
data: res.data.data
};
}
return Promise.reject(new Error(res.data.message));
}
/** /**
* 导入商品 * 导入商品
*/ */
@@ -146,3 +176,19 @@ export async function importShopGoodsImages(file: File) {
} }
return Promise.reject(new Error(res.data.message)); return Promise.reject(new Error(res.data.message));
} }
/**
* 导入商品详情
*/
export async function importShopGoodsDetails(file: File) {
const formData = new FormData();
formData.append('file', file);
const res = await request.post<ApiResult<unknown>>(
MODULES_API_URL + '/shop/shop-goods/import/details',
formData
);
if (res.data.code === 0) {
return res.data.message;
}
return Promise.reject(new Error(res.data.message));
}

View File

@@ -31,6 +31,23 @@
</template> </template>
<span>导入图片</span> <span>导入图片</span>
</a-button> </a-button>
<a-button type="dashed" class="ele-btn-icon" @click="openDetailImport">
<template #icon>
<UploadOutlined />
</template>
<span>导入详情</span>
</a-button>
<a-button
type="dashed"
class="ele-btn-icon"
:disabled="selection?.length === 0"
@click="convertContent"
>
<template #icon>
<SyncOutlined />
</template>
<span>转换详情</span>
</a-button>
<a-button type="dashed" class="ele-btn-icon" @click="handleExport"> <a-button type="dashed" class="ele-btn-icon" @click="handleExport">
导出xls 导出xls
</a-button> </a-button>
@@ -70,7 +87,12 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import { PlusOutlined, DeleteOutlined, UploadOutlined } from '@ant-design/icons-vue'; import {
PlusOutlined,
DeleteOutlined,
UploadOutlined,
SyncOutlined
} from '@ant-design/icons-vue';
import { ref, watch } from 'vue'; import { ref, watch } from 'vue';
import { getCount } from '@/api/shop/shopGoods'; import { getCount } from '@/api/shop/shopGoods';
import type { GoodsCount, ShopGoodsParam } from '@/api/shop/shopGoods/model'; import type { GoodsCount, ShopGoodsParam } from '@/api/shop/shopGoods/model';
@@ -112,6 +134,8 @@
(e: 'batchMove'): void; (e: 'batchMove'): void;
(e: 'import'): void; (e: 'import'): void;
(e: 'imageImport'): void; (e: 'imageImport'): void;
(e: 'detailImport'): void;
(e: 'convertContent'): void;
(e: 'export'): void; (e: 'export'): void;
}>(); }>();
@@ -134,6 +158,14 @@
emit('imageImport'); emit('imageImport');
}; };
const openDetailImport = () => {
emit('detailImport');
};
const convertContent = () => {
emit('convertContent');
};
// 导出 // 导出
const handleExport = () => { const handleExport = () => {
emit('export'); emit('export');

View File

@@ -24,9 +24,14 @@
> >
<a-tabs type="card" v-model:active-key="active" @change="onChange"> <a-tabs type="card" v-model:active-key="active" @change="onChange">
<a-tab-pane tab="基本信息" key="base"> <a-tab-pane tab="基本信息" key="base">
<!-- <a-form-item label="商品ID" name="goodsId">--> <a-form-item label="商品名称" name="name">
<!-- {{ form.goodsId }}--> <a-input
<!-- </a-form-item>--> allow-clear
style="width: 558px"
placeholder="请输入商品名称"
v-model:value="form.name"
/>
</a-form-item>
<a-form-item label="商品分类" name="categoryId"> <a-form-item label="商品分类" name="categoryId">
<a-tree-select <a-tree-select
allow-clear allow-clear
@@ -41,12 +46,12 @@
@change="onCategoryId" @change="onCategoryId"
/> />
</a-form-item> </a-form-item>
<a-form-item label="商品名称" name="name"> <a-form-item label="商品编码" name="code">
<a-input <a-input
allow-clear allow-clear
style="width: 558px" style="width: 558px"
placeholder="请输入商品名称" placeholder="请输入商品编码"
v-model:value="form.name" v-model:value="form.code"
/> />
</a-form-item> </a-form-item>
<a-form-item label="商品卖点" name="comments"> <a-form-item label="商品卖点" name="comments">

View File

@@ -24,6 +24,8 @@
@batchMove="openMove" @batchMove="openMove"
@import="openImport" @import="openImport"
@imageImport="openImageImport" @imageImport="openImageImport"
@detailImport="openDetailImport"
@convertContent="convertSelectedContent"
@export="handleExport" @export="handleExport"
/> />
</template> </template>
@@ -91,6 +93,11 @@
v-model:visible="showImageImport" v-model:visible="showImageImport"
@done="reload" @done="reload"
/> />
<ShopGoodsDetailImport
v-model:visible="showDetailImport"
@done="reload"
/>
</a-page-header> </a-page-header>
</template> </template>
@@ -114,7 +121,15 @@
import Extra from '@/views/shop/shopGoods/components/extra.vue'; import Extra from '@/views/shop/shopGoods/components/extra.vue';
import ShopGoodsImport from './components/shop-goods-import.vue'; import ShopGoodsImport from './components/shop-goods-import.vue';
import ShopGoodsImageImport from './components/shop-goods-image-import.vue'; import ShopGoodsImageImport from './components/shop-goods-image-import.vue';
import { listShopGoods, pageShopGoods, removeShopGoods, removeBatchShopGoods, updateShopGoods } from '@/api/shop/shopGoods'; import ShopGoodsDetailImport from './components/shop-goods-detail-import.vue';
import {
convertMarkdownImagesToHtml,
listShopGoods,
pageShopGoods,
removeShopGoods,
removeBatchShopGoods,
updateShopGoods
} from '@/api/shop/shopGoods';
import type { ShopGoods, ShopGoodsParam } from '@/api/shop/shopGoods/model'; import type { ShopGoods, ShopGoodsParam } from '@/api/shop/shopGoods/model';
import { getPageTitle } from '@/utils/common'; import { getPageTitle } from '@/utils/common';
import { ShopGoodsCategory } from '@/api/shop/shopGoodsCategory/model'; import { ShopGoodsCategory } from '@/api/shop/shopGoodsCategory/model';
@@ -137,6 +152,8 @@
const showImport = ref(false); const showImport = ref(false);
// 是否显示图片导入弹窗 // 是否显示图片导入弹窗
const showImageImport = ref(false); const showImageImport = ref(false);
// 是否显示详情导入弹窗
const showDetailImport = ref(false);
// 是否显示批量移动弹窗 // 是否显示批量移动弹窗
const showMove = ref(false); const showMove = ref(false);
// 加载状态 // 加载状态
@@ -176,12 +193,12 @@
key: 'name', key: 'name',
width: 300 width: 300
}, },
// { {
// title: '编号', title: '编号',
// dataIndex: 'code', dataIndex: 'code',
// key: 'code', key: 'code',
// align: 'center', align: 'center',
// }, },
{ {
title: '价格', title: '价格',
dataIndex: 'price', dataIndex: 'price',
@@ -273,6 +290,50 @@
showImageImport.value = true; showImageImport.value = true;
}; };
/* 打开详情导入弹窗 */
const openDetailImport = () => {
showDetailImport.value = true;
};
/* 转换选中商品详情Markdown图片为HTML */
const convertSelectedContent = () => {
const goodsIds = selection.value
.map((goods) => goods.goodsId)
.filter((goodsId): goodsId is number => goodsId != null);
if (!goodsIds.length) {
message.error('请至少选择一条商品');
return;
}
Modal.confirm({
title: '转换商品详情内容',
content: `确定要转换选中的 ${goodsIds.length} 个商品详情内容吗?`,
icon: createVNode(ExclamationCircleOutlined),
maskClosable: true,
onOk: async () => {
const hide = message.loading('正在转换商品详情..', 0);
try {
const results = await Promise.all(
goodsIds.map((goodsId) => convertMarkdownImagesToHtml({ goodsId }))
);
const total = results.reduce(
(sum, item) => sum + (item.data?.total || 0),
0
);
const success = results.reduce(
(sum, item) => sum + (item.data?.success || 0),
0
);
hide();
message.success(`转换完成,匹配${total}个商品,成功转换${success}`);
reload();
} catch (e: any) {
hide();
message.error(e.message || '转换失败,请重试');
}
}
});
};
/* 导出商品列表 */ /* 导出商品列表 */
const handleExport = async () => { const handleExport = async () => {
if (loading.value) { if (loading.value) {