|
|
@@ -30,9 +30,6 @@
|
|
|
<h4 class="case-name">{{ item.title }}</h4>
|
|
|
<p class="case-desc">{{ item.description }}</p>
|
|
|
</div>
|
|
|
- <div class="case-actions" v-if="false">
|
|
|
- <el-button type="primary" size="small" @click.stop="handleConfigProduct(item)">配置商品</el-button>
|
|
|
- </div>
|
|
|
</div>
|
|
|
</div>
|
|
|
</el-carousel-item>
|
|
|
@@ -97,7 +94,7 @@
|
|
|
<el-button type="primary" @click="loadSelectList">搜索</el-button>
|
|
|
</div>
|
|
|
<el-table :data="selectList" border style="margin-top: 15px">
|
|
|
- <el-table-column label="案例编号" align="center" prop="programNo" width="120" />
|
|
|
+ <el-table-column label="案例编号" align="center" prop="serviceCaseNo" width="120" />
|
|
|
<el-table-column label="案例图片" align="center" width="100">
|
|
|
<template #default="scope">
|
|
|
<el-image :src="scope.row.imageUrl" fit="cover" style="width: 60px; height: 60px; border-radius: 4px" lazy>
|
|
|
@@ -108,14 +105,14 @@
|
|
|
<el-table-column label="案例名称" align="center" prop="title" :show-overflow-tooltip="true" min-width="200" />
|
|
|
<el-table-column label="首页推荐" align="center" width="100">
|
|
|
<template #default="scope">
|
|
|
- <span :class="scope.row.isShow === '1' ? 'status-show' : 'status-hide'">
|
|
|
- {{ scope.row.isShow === '1' ? '推荐' : '不推荐' }}
|
|
|
+ <span :class="scope.row.isLinked ? 'status-show' : 'status-hide'">
|
|
|
+ {{ scope.row.isLinked ? '推荐' : '不推荐' }}
|
|
|
</span>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
<el-table-column label="操作" align="center" width="120">
|
|
|
<template #default="scope">
|
|
|
- <span v-if="scope.row.isShow === '1'" class="action-link danger" @click="handleUnrecommend(scope.row)">不推荐</span>
|
|
|
+ <span v-if="scope.row.isLinked" class="action-link danger" @click="handleUnrecommend(scope.row)">不推荐</span>
|
|
|
<span v-else class="action-link primary" @click="handleRecommend(scope.row)">推 荐</span>
|
|
|
</template>
|
|
|
</el-table-column>
|
|
|
@@ -132,61 +129,6 @@
|
|
|
</template>
|
|
|
</el-dialog>
|
|
|
|
|
|
- <!-- 配置商品对话框 -->
|
|
|
- <el-dialog v-model="productDialog.visible" :title="`配置商品 - ${productDialog.title}`" width="900px" append-to-body>
|
|
|
- <div class="product-dialog-header">
|
|
|
- <el-button type="primary" @click="handleAddProduct">新增商品</el-button>
|
|
|
- <el-button icon="Refresh" circle size="small" @click="loadLinkedProducts" style="margin-left: auto" />
|
|
|
- </div>
|
|
|
- <el-table v-loading="productDialog.loading" :data="linkedProducts" border>
|
|
|
- <el-table-column label="商品编号" align="center" prop="productNo" width="120" />
|
|
|
- <el-table-column label="商品名称" align="center" prop="productName" min-width="200" show-overflow-tooltip />
|
|
|
- <el-table-column label="商品图片" align="center" width="100">
|
|
|
- <template #default="scope">
|
|
|
- <el-image v-if="scope.row.productImage" :src="scope.row.productImage" fit="cover" style="width: 60px; height: 60px" />
|
|
|
- <span v-else>-</span>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- <el-table-column label="操作" align="center" width="80">
|
|
|
- <template #default="scope">
|
|
|
- <span class="action-link danger" @click="handleRemoveLinked(scope.row)">删除</span>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- </el-table>
|
|
|
- <template #footer>
|
|
|
- <el-button @click="productDialog.visible = false">关 闭</el-button>
|
|
|
- </template>
|
|
|
- </el-dialog>
|
|
|
-
|
|
|
- <!-- 选择商品对话框 -->
|
|
|
- <el-dialog v-model="productSelectDialog.visible" title="选择商品" width="900px" append-to-body>
|
|
|
- <div class="search-bar">
|
|
|
- <el-input v-model="productSelectDialog.keyword" placeholder="请输入商品编号或名称" clearable style="width: 280px" />
|
|
|
- <el-button type="primary" @click="loadProductList">搜索</el-button>
|
|
|
- </div>
|
|
|
- <el-table v-loading="productSelectDialog.loading" :data="productList" border style="margin-top: 15px" @selection-change="handleProductSelectionChange">
|
|
|
- <el-table-column type="selection" width="50" align="center" />
|
|
|
- <el-table-column label="商品编号" align="center" prop="productNo" width="120" />
|
|
|
- <el-table-column label="商品名称" align="center" prop="itemName" min-width="180" show-overflow-tooltip />
|
|
|
- <el-table-column label="商品图片" align="center" width="100">
|
|
|
- <template #default="scope">
|
|
|
- <el-image v-if="scope.row.productImage" :src="scope.row.productImage" fit="cover" style="width: 60px; height: 60px" />
|
|
|
- <span v-else>-</span>
|
|
|
- </template>
|
|
|
- </el-table-column>
|
|
|
- </el-table>
|
|
|
- <pagination
|
|
|
- v-show="productSelectDialog.total > 0"
|
|
|
- v-model:page="productSelectDialog.pageNum"
|
|
|
- v-model:limit="productSelectDialog.pageSize"
|
|
|
- :total="productSelectDialog.total"
|
|
|
- @pagination="loadProductList"
|
|
|
- />
|
|
|
- <template #footer>
|
|
|
- <el-button type="primary" @click="confirmSelectProducts">确 定</el-button>
|
|
|
- <el-button @click="productSelectDialog.visible = false">取 消</el-button>
|
|
|
- </template>
|
|
|
- </el-dialog>
|
|
|
</div>
|
|
|
</template>
|
|
|
|
|
|
@@ -195,13 +137,13 @@ import { ref, reactive, computed, onMounted } from 'vue';
|
|
|
import { ElMessage, ElMessageBox } from 'element-plus';
|
|
|
import { Picture, CircleClose } from '@element-plus/icons-vue';
|
|
|
import { getFloorTitle, addFloorTitle, updateFloorTitle } from '@/api/system/floorTitle';
|
|
|
-import { listProgram, updateProgram, listProgramLink, addProgramLink, delProgramLink } from '@/api/product/program';
|
|
|
-import { listProduct } from '@/api/product/base';
|
|
|
+import { listServiceCase } from '@/api/product/serviceCase';
|
|
|
+import { listRecommend, listRecommendLink, addRecommendLink, delRecommendLink } from '@/api/product/recommend';
|
|
|
|
|
|
-// 分类标识
|
|
|
-const CATEGORY = 'case';
|
|
|
// 标题配置ID(项目案例用id=8)
|
|
|
const TITLE_ID = 8;
|
|
|
+// 推荐位编号
|
|
|
+const RECOMMEND_NO = 'decoration_case';
|
|
|
|
|
|
// 标题配置
|
|
|
const headerConfig = ref({
|
|
|
@@ -273,6 +215,10 @@ const handleLinkClick = () => {
|
|
|
}
|
|
|
};
|
|
|
|
|
|
+// 推荐位
|
|
|
+const recommendId = ref<number | null>(null);
|
|
|
+const linkedCaseIds = ref<Set<string>>(new Set());
|
|
|
+
|
|
|
// 项目案例展示列表
|
|
|
const caseList = ref<any[]>([]);
|
|
|
|
|
|
@@ -285,17 +231,48 @@ const caseGroups = computed(() => {
|
|
|
return groups;
|
|
|
});
|
|
|
|
|
|
+// 获取推荐位ID
|
|
|
+const loadRecommendId = async () => {
|
|
|
+ try {
|
|
|
+ const res: any = await listRecommend({ recommendNo: RECOMMEND_NO, pageSize: 1 });
|
|
|
+ if (res.rows && res.rows.length > 0) {
|
|
|
+ recommendId.value = res.rows[0].id;
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('获取推荐位失败', error);
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
// 加载已推荐的案例
|
|
|
const loadCaseList = async () => {
|
|
|
try {
|
|
|
- const res: any = await listProgram({ category: CATEGORY, isShow: '1', pageSize: 100 });
|
|
|
- caseList.value = (res.rows || []).map((item: any) => ({
|
|
|
- id: item.id,
|
|
|
- programNo: item.programNo,
|
|
|
- title: item.title,
|
|
|
- description: item.describe || '',
|
|
|
- imageUrl: item.coverImage || item.coverImageUrl
|
|
|
- }));
|
|
|
+ if (!recommendId.value) await loadRecommendId();
|
|
|
+ if (!recommendId.value) {
|
|
|
+ caseList.value = [];
|
|
|
+ linkedCaseIds.value = new Set();
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const linkRes: any = await listRecommendLink({ recommendId: recommendId.value, pageSize: 100 });
|
|
|
+ const links = linkRes.rows || [];
|
|
|
+ linkedCaseIds.value = new Set(links.map((l: any) => String(l.serviceCaseId)));
|
|
|
+ if (links.length === 0) {
|
|
|
+ caseList.value = [];
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ const caseIds = links.map((link: any) => link.serviceCaseId);
|
|
|
+ const caseRes: any = await listServiceCase({ ids: caseIds.join(','), pageSize: 100 });
|
|
|
+ const caseMap = new Map((caseRes.rows || []).map((c: any) => [String(c.id), c]));
|
|
|
+ caseList.value = links.map((link: any) => {
|
|
|
+ const serviceCase: any = caseMap.get(String(link.serviceCaseId)) || {};
|
|
|
+ return {
|
|
|
+ id: serviceCase.id || link.serviceCaseId,
|
|
|
+ linkId: link.id,
|
|
|
+ serviceCaseNo: serviceCase.serviceCaseNo || '',
|
|
|
+ title: serviceCase.caseTitle || `案例${link.serviceCaseId}`,
|
|
|
+ description: serviceCase.projectBrief || '',
|
|
|
+ imageUrl: serviceCase.caseImage
|
|
|
+ };
|
|
|
+ });
|
|
|
} catch (error) {
|
|
|
console.error('加载案例列表失败', error);
|
|
|
caseList.value = [];
|
|
|
@@ -324,19 +301,28 @@ const handleOpenSelect = () => {
|
|
|
|
|
|
const loadSelectList = async () => {
|
|
|
try {
|
|
|
- const params: any = { category: CATEGORY, pageNum: selectDialog.pageNum, pageSize: selectDialog.pageSize };
|
|
|
+ const params: any = { pageNum: selectDialog.pageNum, pageSize: selectDialog.pageSize };
|
|
|
if (selectDialog.keyword) {
|
|
|
- // 用title做模糊查询
|
|
|
- params.title = selectDialog.keyword;
|
|
|
+ // 用caseTitle做模糊查询
|
|
|
+ params.caseTitle = selectDialog.keyword;
|
|
|
}
|
|
|
- const res: any = await listProgram(params);
|
|
|
+ const res: any = await listServiceCase(params);
|
|
|
selectList.value = (res.rows || []).map((item: any) => ({
|
|
|
id: item.id,
|
|
|
- programNo: item.programNo,
|
|
|
- title: item.title,
|
|
|
- imageUrl: item.coverImage || item.coverImageUrl,
|
|
|
- isShow: item.isShow
|
|
|
+ serviceCaseNo: item.serviceCaseNo,
|
|
|
+ title: item.caseTitle,
|
|
|
+ imageUrl: item.caseImage,
|
|
|
+ isLinked: linkedCaseIds.value.has(String(item.id)),
|
|
|
+ linkId: null
|
|
|
}));
|
|
|
+ // 查找linkId
|
|
|
+ if (recommendId.value) {
|
|
|
+ const linkRes: any = await listRecommendLink({ recommendId: recommendId.value, pageSize: 100 });
|
|
|
+ const linkMap = new Map((linkRes.rows || []).map((l: any) => [String(l.serviceCaseId), l.id]));
|
|
|
+ selectList.value.forEach((c: any) => {
|
|
|
+ c.linkId = linkMap.get(String(c.id)) || null;
|
|
|
+ });
|
|
|
+ }
|
|
|
selectDialog.total = res.total || 0;
|
|
|
} catch (error) {
|
|
|
console.error('加载案例失败', error);
|
|
|
@@ -353,8 +339,13 @@ const resetSelectQuery = () => {
|
|
|
// 推荐案例
|
|
|
const handleRecommend = async (row: any) => {
|
|
|
try {
|
|
|
- await updateProgram({ id: row.id, isShow: '1' });
|
|
|
- row.isShow = '1';
|
|
|
+ if (!recommendId.value) {
|
|
|
+ ElMessage.error('推荐位不存在');
|
|
|
+ return;
|
|
|
+ }
|
|
|
+ await addRecommendLink({ recommendId: recommendId.value, serviceCaseId: row.id });
|
|
|
+ row.isLinked = true;
|
|
|
+ linkedCaseIds.value.add(String(row.id));
|
|
|
await loadCaseList();
|
|
|
ElMessage.success('已推荐');
|
|
|
} catch (error) {
|
|
|
@@ -365,8 +356,11 @@ const handleRecommend = async (row: any) => {
|
|
|
// 取消推荐
|
|
|
const handleUnrecommend = async (row: any) => {
|
|
|
try {
|
|
|
- await updateProgram({ id: row.id, isShow: '0' });
|
|
|
- row.isShow = '0';
|
|
|
+ if (row.linkId) {
|
|
|
+ await delRecommendLink(row.linkId);
|
|
|
+ }
|
|
|
+ row.isLinked = false;
|
|
|
+ linkedCaseIds.value.delete(String(row.id));
|
|
|
await loadCaseList();
|
|
|
ElMessage.success('已取消推荐');
|
|
|
} catch (error) {
|
|
|
@@ -377,9 +371,12 @@ const handleUnrecommend = async (row: any) => {
|
|
|
// 从展示区隐藏案例
|
|
|
const handleHideCase = async (item: any) => {
|
|
|
try {
|
|
|
- await updateProgram({ id: item.id, isShow: '0' });
|
|
|
- await loadCaseList();
|
|
|
- ElMessage.success('已移除');
|
|
|
+ if (item.linkId) {
|
|
|
+ await delRecommendLink(item.linkId);
|
|
|
+ linkedCaseIds.value.delete(String(item.id));
|
|
|
+ await loadCaseList();
|
|
|
+ ElMessage.success('已移除');
|
|
|
+ }
|
|
|
} catch (error) {
|
|
|
ElMessage.error('移除失败');
|
|
|
}
|
|
|
@@ -387,129 +384,8 @@ const handleHideCase = async (item: any) => {
|
|
|
|
|
|
onMounted(() => {
|
|
|
loadHeaderConfig();
|
|
|
- loadCaseList();
|
|
|
-});
|
|
|
-
|
|
|
-// 配置商品相关
|
|
|
-const productDialog = reactive({
|
|
|
- visible: false,
|
|
|
- loading: false,
|
|
|
- programId: null as number | null,
|
|
|
- title: ''
|
|
|
-});
|
|
|
-const linkedProducts = ref<any[]>([]);
|
|
|
-
|
|
|
-// 打开配置商品弹框
|
|
|
-const handleConfigProduct = (item: any) => {
|
|
|
- productDialog.programId = item.id;
|
|
|
- productDialog.title = item.title;
|
|
|
- productDialog.visible = true;
|
|
|
- loadLinkedProducts();
|
|
|
-};
|
|
|
-
|
|
|
-// 加载已关联的商品
|
|
|
-const loadLinkedProducts = async () => {
|
|
|
- if (!productDialog.programId) return;
|
|
|
- productDialog.loading = true;
|
|
|
- try {
|
|
|
- const res: any = await listProgramLink({ programId: productDialog.programId, pageSize: 100 });
|
|
|
- linkedProducts.value = res.rows || [];
|
|
|
- } catch (error) {
|
|
|
- console.error('加载关联商品失败', error);
|
|
|
- linkedProducts.value = [];
|
|
|
- } finally {
|
|
|
- productDialog.loading = false;
|
|
|
- }
|
|
|
-};
|
|
|
-
|
|
|
-// 删除关联商品
|
|
|
-const handleRemoveLinked = (row: any) => {
|
|
|
- ElMessageBox.confirm('是否确认删除该商品?', '提示', {
|
|
|
- confirmButtonText: '确定',
|
|
|
- cancelButtonText: '取消',
|
|
|
- type: 'warning'
|
|
|
- }).then(async () => {
|
|
|
- await delProgramLink(row.id);
|
|
|
- ElMessage.success('删除成功');
|
|
|
- loadLinkedProducts();
|
|
|
- }).catch(() => {});
|
|
|
-};
|
|
|
-
|
|
|
-// 选择商品弹框
|
|
|
-const productSelectDialog = reactive({
|
|
|
- visible: false,
|
|
|
- loading: false,
|
|
|
- keyword: '',
|
|
|
- pageNum: 1,
|
|
|
- pageSize: 10,
|
|
|
- total: 0
|
|
|
+ loadRecommendId().then(() => loadCaseList());
|
|
|
});
|
|
|
-const productList = ref<any[]>([]);
|
|
|
-const selectedProducts = ref<any[]>([]);
|
|
|
-
|
|
|
-// 新增商品
|
|
|
-const handleAddProduct = () => {
|
|
|
- productSelectDialog.keyword = '';
|
|
|
- productSelectDialog.pageNum = 1;
|
|
|
- productSelectDialog.visible = true;
|
|
|
- loadProductList();
|
|
|
-};
|
|
|
-
|
|
|
-// 加载商品列表
|
|
|
-const loadProductList = async () => {
|
|
|
- productSelectDialog.loading = true;
|
|
|
- try {
|
|
|
- const res: any = await listProduct({
|
|
|
- keyword: productSelectDialog.keyword,
|
|
|
- pageNum: productSelectDialog.pageNum,
|
|
|
- pageSize: productSelectDialog.pageSize
|
|
|
- });
|
|
|
- productList.value = res.rows || [];
|
|
|
- productSelectDialog.total = res.total || 0;
|
|
|
- } catch (error) {
|
|
|
- console.error('加载商品列表失败', error);
|
|
|
- productList.value = [];
|
|
|
- } finally {
|
|
|
- productSelectDialog.loading = false;
|
|
|
- }
|
|
|
-};
|
|
|
-
|
|
|
-// 选择变化
|
|
|
-const handleProductSelectionChange = (selection: any[]) => {
|
|
|
- selectedProducts.value = selection;
|
|
|
-};
|
|
|
-
|
|
|
-// 确认选择商品
|
|
|
-const confirmSelectProducts = async () => {
|
|
|
- if (selectedProducts.value.length === 0) {
|
|
|
- ElMessage.warning('请选择商品');
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- // 检查是否有重复商品
|
|
|
- const existingProductIds = linkedProducts.value.map((item: any) => String(item.productId));
|
|
|
- const duplicates = selectedProducts.value.filter((p: any) => existingProductIds.includes(String(p.id)));
|
|
|
-
|
|
|
- if (duplicates.length > 0) {
|
|
|
- const duplicateNames = duplicates.map((p: any) => p.itemName || p.productNo).join('、');
|
|
|
- ElMessage.warning(`商品 ${duplicateNames} 已存在,请勿重复添加`);
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- try {
|
|
|
- for (const product of selectedProducts.value) {
|
|
|
- await addProgramLink({
|
|
|
- programId: productDialog.programId,
|
|
|
- productId: product.id
|
|
|
- });
|
|
|
- }
|
|
|
- ElMessage.success('添加成功');
|
|
|
- productSelectDialog.visible = false;
|
|
|
- loadLinkedProducts();
|
|
|
- } catch (error) {
|
|
|
- ElMessage.error('添加失败');
|
|
|
- }
|
|
|
-};
|
|
|
</script>
|
|
|
|
|
|
<style scoped lang="scss">
|
|
|
@@ -699,11 +575,4 @@ const confirmSelectProducts = async () => {
|
|
|
color: #ccc;
|
|
|
font-size: 20px;
|
|
|
}
|
|
|
-
|
|
|
-.product-dialog-header {
|
|
|
- display: flex;
|
|
|
- align-items: center;
|
|
|
- gap: 10px;
|
|
|
- margin-bottom: 15px;
|
|
|
-}
|
|
|
</style>
|