mirror of
https://gitee.com/beijing_hongye_huicheng/lilishop-ui.git
synced 2026-11-07 09:28:53 +08:00
refactor(page-decoration): 重构装修页面组件与交互方式
- 统一 liliDialog ref 命名为 liliDialogRef,避免与组件 import 冲突 - 商品选择弹窗改为 el-table 表格形式展示 - 链接选择器从 el-dialog 改为 el-drawer 抽屉形式 - 为所有 img 标签添加 alt 属性提升可访问性 - 新增拼团、直播、秒杀、砍价、积分商城、优惠券等营销组件默认配置 - 删除商品分类组件,新增商品列表组件配置 - i18n 配置从 legacy 模式切换为 composition 模式 - 注册 PanelSearch、PanelNotice、PanelVideo 等新面板组件 - 优化画布预览区域样式并添加页面设置入口
This commit is contained in:
@@ -2,7 +2,7 @@
|
||||
<div class="hotzone-box">
|
||||
<div class="hotzone-item">
|
||||
<div ref="content" class="hz-m-wrap">
|
||||
<img class="hz-u-img" :src="image" />
|
||||
<img alt="热区背景图" class="hz-u-img" :src="image" />
|
||||
<ul class="hz-m-area" v-add-item>
|
||||
<zone
|
||||
class="hz-m-item"
|
||||
|
||||
@@ -36,10 +36,10 @@
|
||||
@mouseup.stop
|
||||
@mousemove.stop
|
||||
>
|
||||
<img width="17" height="17" src="../assets/styles/icons8-edit-64.png"></img>
|
||||
<img alt="编辑" width="17" height="17" src="../assets/styles/icons8-edit-64.png"></img>
|
||||
</li>
|
||||
<li class="hz-flex-img">
|
||||
<img class="hz-u-img" :src="zoneForm.img" />
|
||||
<img alt="热区图片" class="hz-u-img" :src="zoneForm.img" />
|
||||
</li>
|
||||
<li class="hz-u-square hz-u-square-tl" data-pointer="dealTL"></li>
|
||||
<li class="hz-u-square hz-u-square-tc" data-pointer="dealTC"></li>
|
||||
@@ -79,7 +79,7 @@
|
||||
</template>
|
||||
</el-dialog>
|
||||
<!-- 选择商品。链接 -->
|
||||
<liliDialog ref="liliDialog" @selectedLink="selectedLink"></liliDialog>
|
||||
<liliDialog ref="liliDialogRef" @selectedLink="selectedLink"></liliDialog>
|
||||
<!-- 选择图片 -->
|
||||
<el-dialog width="1200px" v-model="picModelFlag" append-to-body destroy-on-close>
|
||||
<ossManage
|
||||
@@ -124,7 +124,7 @@ const zoneForm = reactive({
|
||||
// 未在原 data 中声明、直接挂载在 this 上的遗留字段(仅赋值,未被读取)
|
||||
let selectedNav;
|
||||
|
||||
const liliDialog = ref(null);
|
||||
const liliDialogRef = ref(null);
|
||||
// 模板 ref 与 import 组件同名会互相覆盖,故改名 ossManageRef
|
||||
const ossManageRef = ref(null);
|
||||
|
||||
@@ -179,7 +179,7 @@ function callbackSelected(item) {
|
||||
// 调起选择链接弹窗
|
||||
function handleSelectLink(item, index) {
|
||||
if (item) selectedNav = item;
|
||||
liliDialog.value.open("link");
|
||||
liliDialogRef.value.open("link");
|
||||
}
|
||||
// 已选链接
|
||||
function selectedLink(val) {
|
||||
|
||||
@@ -28,49 +28,60 @@
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div class="wap-content-list">
|
||||
<div
|
||||
class="wap-content-item"
|
||||
:class="{ active: item.selected }"
|
||||
@click="checkedGoods(item, index)"
|
||||
v-for="(item, index) in goodsData"
|
||||
:key="index"
|
||||
>
|
||||
<div>
|
||||
<img :src="item.thumbnail" alt="" />
|
||||
</div>
|
||||
<div class="wap-content-desc">
|
||||
<div class="wap-content-desc-title">{{ item.goodsName }}</div>
|
||||
|
||||
<div class="wap-sku">
|
||||
{{ item.goodsUnit }}
|
||||
<el-tag
|
||||
v-if="item.goodsType"
|
||||
style="margin-left: 6px"
|
||||
size="small"
|
||||
:type="goodsTypeTagType(item.goodsType)"
|
||||
>
|
||||
{{ goodsTypeLabel(item.goodsType) }}
|
||||
</el-tag>
|
||||
<el-tag
|
||||
style="margin-left: 6px"
|
||||
:type="item.salesModel === 'RETAIL' ? 'info' : 'primary'"
|
||||
>
|
||||
{{ item.salesModel === "RETAIL" ? "零售型" : "批发型" }}
|
||||
</el-tag>
|
||||
</div>
|
||||
<div class="wap-content-desc-bottom">
|
||||
<div>¥{{ $filters.unitPrice(item.price) }}</div>
|
||||
<div v-if="item.goodsType === 'E_COUPON'" class="pool-stock-hint">
|
||||
库存 {{ item.quantity != null ? item.quantity : "—" }}(卡池)
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div v-if="loading" v-loading="loading" class="loading-mask" />
|
||||
|
||||
<div v-if="empty" class="empty">暂无商品信息</div>
|
||||
</div>
|
||||
<el-table
|
||||
ref="tableRef"
|
||||
v-loading="loading"
|
||||
class="goods-select-table"
|
||||
:data="goodsData"
|
||||
row-key="id"
|
||||
height="340"
|
||||
empty-text="暂无商品信息"
|
||||
@selection-change="onSelectionChange"
|
||||
>
|
||||
<el-table-column
|
||||
type="selection"
|
||||
label="选择"
|
||||
width="60"
|
||||
align="center"
|
||||
reserve-selection
|
||||
/>
|
||||
<el-table-column label="商品图片" width="90" align="center">
|
||||
<template #default="{ row }">
|
||||
<img class="goods-thumb" :src="row.thumbnail" alt="" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="goodsName"
|
||||
label="商品名称"
|
||||
min-width="200"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column label="商品规格" min-width="140" show-overflow-tooltip>
|
||||
<template #default="{ row }">
|
||||
{{ row.simpleSpecs || "默认规格" }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="价格" width="110">
|
||||
<template #default="{ row }">
|
||||
<span class="goods-price">¥{{ $filters.unitPrice(row.price) }}</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="库存" width="90">
|
||||
<template #default="{ row }">
|
||||
{{ row.quantity != null ? row.quantity : "—" }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="销量" width="90">
|
||||
<template #default="{ row }">
|
||||
{{ row.buyCount != null ? row.buyCount : 0 }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="商品类型" width="110">
|
||||
<template #default="{ row }">
|
||||
{{ goodsTypeLabel(row.goodsType) }}
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination
|
||||
v-model:current-page="goodsParams.pageNumber"
|
||||
class="pageration"
|
||||
@@ -85,9 +96,9 @@
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { onMounted, reactive, ref, watch } from "vue";
|
||||
import { nextTick, onMounted, reactive, ref, watch } from "vue";
|
||||
import * as API_Goods from "@/api/goods";
|
||||
import { goodsTypeLabel, goodsTypeTagType } from "@/constants/goodsType";
|
||||
import { goodsTypeLabel } from "@/constants/goodsType";
|
||||
|
||||
const props = defineProps({
|
||||
selectedWay: {
|
||||
@@ -114,8 +125,8 @@ const goodsParams = reactive({
|
||||
});
|
||||
const category = ref([]);
|
||||
const goodsData = ref([]);
|
||||
const empty = ref(false);
|
||||
const loading = ref(false);
|
||||
const tableRef = ref(null);
|
||||
|
||||
watch(category, (val) => {
|
||||
goodsParams.categoryPath = val && val.length ? val[2] : "";
|
||||
@@ -124,6 +135,7 @@ watch(
|
||||
() => props.selectedWay,
|
||||
(val) => {
|
||||
selectedList.value = Array.isArray(val) ? val.slice() : [];
|
||||
nextTick(syncTableSelection);
|
||||
},
|
||||
{ deep: true, immediate: true }
|
||||
);
|
||||
@@ -162,28 +174,46 @@ function getQueryGoodsList() {
|
||||
}
|
||||
function initGoods(res) {
|
||||
const records = res?.result?.records || [];
|
||||
if (records.length) {
|
||||
records.forEach((item) => {
|
||||
item.selected = false;
|
||||
item.___type = "goods";
|
||||
selectedList.value.forEach((e) => {
|
||||
if (e.id && e.id === item.id) {
|
||||
item.selected = true;
|
||||
}
|
||||
});
|
||||
});
|
||||
total.value = res.result.total || 0;
|
||||
goodsData.value = records;
|
||||
empty.value = false;
|
||||
} else {
|
||||
goodsData.value = [];
|
||||
empty.value = true;
|
||||
records.forEach((item) => {
|
||||
item.___type = "goods";
|
||||
});
|
||||
total.value = res?.result?.total || 0;
|
||||
goodsData.value = records;
|
||||
nextTick(syncTableSelection);
|
||||
}
|
||||
function syncTableSelection() {
|
||||
const table = tableRef.value;
|
||||
if (!table) return;
|
||||
if (!selectedList.value.length) {
|
||||
table.clearSelection();
|
||||
return;
|
||||
}
|
||||
goodsData.value.forEach((item) => {
|
||||
const selected = selectedList.value.some((e) => e.id && e.id === item.id);
|
||||
table.toggleRowSelection(item, selected);
|
||||
});
|
||||
}
|
||||
function emitSelected(list) {
|
||||
selectedList.value = list;
|
||||
emit("selected", selectedList.value);
|
||||
}
|
||||
function onSelectionChange(rows) {
|
||||
// 选择链接时为单选:保留最后勾选的一项,多选框交互不变
|
||||
if (type.value !== "multiple") {
|
||||
if (!rows.length) {
|
||||
emitSelected([]);
|
||||
return;
|
||||
}
|
||||
const row = rows[rows.length - 1];
|
||||
if (rows.length > 1) {
|
||||
tableRef.value?.clearSelection();
|
||||
tableRef.value?.toggleRowSelection(row, true);
|
||||
}
|
||||
emitSelected([row]);
|
||||
return;
|
||||
}
|
||||
emitSelected(rows);
|
||||
}
|
||||
function init() {
|
||||
API_Goods.getGoodsSkuData(goodsParams).then((res) => {
|
||||
initGoods(res);
|
||||
@@ -221,24 +251,6 @@ function deepGroup(val) {
|
||||
});
|
||||
});
|
||||
}
|
||||
function checkedGoods(val) {
|
||||
if (type.value !== "multiple") {
|
||||
goodsData.value.forEach((item) => {
|
||||
item.selected = false;
|
||||
});
|
||||
val.selected = true;
|
||||
emitSelected([val]);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!val.selected) {
|
||||
val.selected = true;
|
||||
emitSelected([...selectedList.value, val]);
|
||||
} else {
|
||||
val.selected = false;
|
||||
emitSelected(selectedList.value.filter((item) => item.id !== val.id));
|
||||
}
|
||||
}
|
||||
|
||||
// 父组件 lili-dialog/index.vue 通过 $refs.goodsDialog.type = "single" 切换单选;
|
||||
// expose 的 ref 外部读写等价于原 data 字段
|
||||
@@ -248,31 +260,19 @@ defineExpose({ type });
|
||||
@import "./style.scss";
|
||||
.wap-content {
|
||||
width: 100%;
|
||||
}
|
||||
.empty {
|
||||
text-align: center;
|
||||
padding: 8px 0;
|
||||
width: 100%;
|
||||
}
|
||||
.wap-content {
|
||||
flex: 1;
|
||||
padding: 0;
|
||||
}
|
||||
.wap-content-list {
|
||||
display: flex;
|
||||
position: relative;
|
||||
flex-wrap: wrap;
|
||||
height: 340px;
|
||||
.goods-thumb {
|
||||
display: block;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
margin: 0 auto;
|
||||
border-radius: 4px;
|
||||
object-fit: cover;
|
||||
}
|
||||
.wap-content-item {
|
||||
width: 210px;
|
||||
margin: 10px 7px;
|
||||
padding: 6px 0;
|
||||
}
|
||||
.active {
|
||||
background: url("../../assets/selected.png") no-repeat;
|
||||
background-position: right;
|
||||
background-size: 10%;
|
||||
.goods-price {
|
||||
color: $theme_color;
|
||||
}
|
||||
.pageration {
|
||||
display: flex;
|
||||
@@ -283,10 +283,3 @@ defineExpose({ type });
|
||||
box-sizing: border-box;
|
||||
}
|
||||
</style>
|
||||
<style lang="scss">
|
||||
.pool-stock-hint {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
margin-top: 4px;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
<template>
|
||||
<el-dialog
|
||||
v-if="goodsFlag"
|
||||
v-model="flag"
|
||||
width="1160px"
|
||||
top="120px"
|
||||
@@ -9,32 +10,49 @@
|
||||
destroy-on-close
|
||||
@close="clickClose"
|
||||
>
|
||||
<template v-if="flag">
|
||||
<goodsDialog
|
||||
@selected="
|
||||
(val) => {
|
||||
goodsData = val;
|
||||
}
|
||||
"
|
||||
v-if="goodsFlag"
|
||||
ref="goodsDialogRef"
|
||||
:selectedWay="goodsData"
|
||||
/>
|
||||
<linkDialog
|
||||
@selectedLink="
|
||||
(val) => {
|
||||
linkData = val;
|
||||
}
|
||||
"
|
||||
v-else
|
||||
class="linkDialog"
|
||||
/>
|
||||
</template>
|
||||
<goodsDialog
|
||||
v-if="flag"
|
||||
ref="goodsDialogRef"
|
||||
:selectedWay="goodsData"
|
||||
@selected="
|
||||
(val) => {
|
||||
goodsData = val;
|
||||
}
|
||||
"
|
||||
/>
|
||||
<template #footer>
|
||||
<el-button @click="clickClose">取消</el-button>
|
||||
<el-button type="primary" @click="clickOK">确定</el-button>
|
||||
</template>
|
||||
</el-dialog>
|
||||
|
||||
<el-drawer
|
||||
v-else
|
||||
v-model="flag"
|
||||
class="link-selector-drawer"
|
||||
direction="rtl"
|
||||
size="1160px"
|
||||
:with-header="false"
|
||||
:z-index="10000"
|
||||
:close-on-click-modal="false"
|
||||
append-to-body
|
||||
destroy-on-close
|
||||
@close="clickClose"
|
||||
>
|
||||
<linkDialog
|
||||
v-if="flag"
|
||||
class="linkDialog"
|
||||
@selectedLink="
|
||||
(val) => {
|
||||
linkData = val;
|
||||
}
|
||||
"
|
||||
/>
|
||||
<template #footer>
|
||||
<el-button @click="clickClose">取消</el-button>
|
||||
<el-button type="primary" @click="clickOK">确定</el-button>
|
||||
</template>
|
||||
</el-drawer>
|
||||
</template>
|
||||
<script setup>
|
||||
import { ref } from "vue";
|
||||
@@ -120,3 +138,22 @@ defineExpose({
|
||||
overflow: hidden;
|
||||
}
|
||||
</style>
|
||||
<style lang="scss">
|
||||
// 抽屉 append-to-body 后不在当前组件 DOM 内,不能使用 scoped 样式
|
||||
.link-selector-drawer {
|
||||
max-width: 96vw;
|
||||
.el-drawer__body {
|
||||
overflow: hidden;
|
||||
}
|
||||
.goods-select-table,
|
||||
.shops-select-table,
|
||||
.articles-select-table,
|
||||
.specials-select-table {
|
||||
height: calc(100vh - 240px) !important;
|
||||
}
|
||||
}
|
||||
// 商品分类下拉挂载到 body,需要高于抽屉的 10000 层级
|
||||
.goods-dialog-cascader-popper {
|
||||
z-index: 10001 !important;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -34,8 +34,8 @@ const route = useRoute();
|
||||
const changed = ref(""); // 变更模板
|
||||
const selected = ref(0); // 已选数据
|
||||
const selectedLink = ref(""); //选中的链接
|
||||
// wap 为共享的模块级数组,保持原 data 引用同一数组的行为
|
||||
const wap = ref(wapList); // tab标签
|
||||
// 选择链接不再提供“活动”tab,wap.js 中的配置保留用于已有链接的名称展示
|
||||
const wap = ref(wapList.filter((item) => item.name !== "marketing")); // tab标签
|
||||
const activeTab = ref(wapList[0]?.title || "");
|
||||
// v-for 内模板 ref 收集为数组;原名 lili-component 含连字符,改名并同步 script
|
||||
const liliComponentRef = ref([]);
|
||||
@@ -75,11 +75,6 @@ onMounted(() => {
|
||||
});
|
||||
}
|
||||
} else {
|
||||
wap.value.push({
|
||||
title: "活动",
|
||||
url: "3",
|
||||
name: "marketing",
|
||||
});
|
||||
let obj = {};
|
||||
wap.value = wap.value.reduce((cur, next) => {
|
||||
//对象去重
|
||||
|
||||
@@ -1,36 +1,45 @@
|
||||
<template>
|
||||
<div>
|
||||
<el-row :gutter="30">
|
||||
<template v-for="(item, index) in linkList" :key="index">
|
||||
<el-col
|
||||
v-if="showLinkItem(item)"
|
||||
:span="4"
|
||||
>
|
||||
<div
|
||||
class="card"
|
||||
:class="{ active: selectedIndex == index }"
|
||||
@click="handleLink(item, index)"
|
||||
>
|
||||
<el-icon :size="24">
|
||||
<component :is="item.icon" />
|
||||
</el-icon>
|
||||
<p>{{ item.title }}</p>
|
||||
</div>
|
||||
</el-col>
|
||||
</template>
|
||||
<el-col v-if="linkVisible" :span="4">
|
||||
<div
|
||||
class="card"
|
||||
:class="{ active: selectedIndex == linkList.length }"
|
||||
@click="handleLink(linkItem, linkList.length)"
|
||||
>
|
||||
<el-icon :size="24">
|
||||
<component :is="linkItem.icon" />
|
||||
<div class="link-list">
|
||||
<template v-for="(item, index) in linkList" :key="index">
|
||||
<div
|
||||
v-if="showLinkItem(item)"
|
||||
class="card"
|
||||
:class="{ active: selectedIndex == index }"
|
||||
@click="handleLink(item, index)"
|
||||
>
|
||||
<span class="link-icon">
|
||||
<el-icon :size="18">
|
||||
<component :is="item.icon" />
|
||||
</el-icon>
|
||||
<p>{{ linkItem.title }}</p>
|
||||
</div>
|
||||
</el-col>
|
||||
</el-row>
|
||||
</span>
|
||||
<p>{{ item.title }}</p>
|
||||
<el-icon v-if="selectedIndex == index" class="selected-icon" :size="18">
|
||||
<Check />
|
||||
</el-icon>
|
||||
<el-icon v-else class="arrow-icon" :size="16">
|
||||
<ArrowRight />
|
||||
</el-icon>
|
||||
</div>
|
||||
</template>
|
||||
<div
|
||||
v-if="linkVisible"
|
||||
class="card"
|
||||
:class="{ active: selectedIndex == linkList.length }"
|
||||
@click="handleLink(linkItem, linkList.length)"
|
||||
>
|
||||
<span class="link-icon">
|
||||
<el-icon :size="18">
|
||||
<component :is="linkItem.icon" />
|
||||
</el-icon>
|
||||
</span>
|
||||
<p>{{ linkItem.title }}</p>
|
||||
<el-icon v-if="selectedIndex == linkList.length" class="selected-icon" :size="18">
|
||||
<Check />
|
||||
</el-icon>
|
||||
<el-icon v-else class="arrow-icon" :size="16">
|
||||
<ArrowRight />
|
||||
</el-icon>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
@@ -49,6 +58,8 @@ import {
|
||||
Share,
|
||||
ShoppingBag,
|
||||
Link,
|
||||
ArrowRight,
|
||||
Check,
|
||||
} from "@element-plus/icons-vue";
|
||||
|
||||
const emit = defineEmits(["selected"]);
|
||||
@@ -109,25 +120,74 @@ function handleLink(val, index) {
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@import "../style.scss";
|
||||
.link-list {
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, minmax(0, 1fr));
|
||||
gap: 12px;
|
||||
max-height: calc(100vh - 170px);
|
||||
padding: 4px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.card {
|
||||
position: relative;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
padding: 20px 0;
|
||||
margin: 10px 0;
|
||||
text-align: center;
|
||||
transition: 0.35s;
|
||||
flex-direction: column;
|
||||
min-height: 104px;
|
||||
padding: 16px 10px 14px;
|
||||
margin: 0;
|
||||
cursor: pointer;
|
||||
:deep(p) {
|
||||
margin: 10px 0;
|
||||
text-align: center;
|
||||
background: #fff;
|
||||
border: 1px solid #ebeef5;
|
||||
border-radius: 8px;
|
||||
transition: all 0.2s ease;
|
||||
p {
|
||||
margin: 10px 0 0;
|
||||
color: #303133;
|
||||
font-size: 14px;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
}
|
||||
border: 1px solid #ededed;
|
||||
}
|
||||
.card:hover {
|
||||
background: #ededed;
|
||||
border-color: #409eff;
|
||||
box-shadow: 0 2px 8px rgba(64, 158, 255, 0.12);
|
||||
transform: translateY(-1px);
|
||||
}
|
||||
.active {
|
||||
background: #ededed;
|
||||
.link-icon {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
flex-shrink: 0;
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
color: #409eff;
|
||||
background: #ecf5ff;
|
||||
border-radius: 10px;
|
||||
}
|
||||
.arrow-icon {
|
||||
display: none;
|
||||
}
|
||||
.selected-icon {
|
||||
position: absolute;
|
||||
top: 8px;
|
||||
right: 8px;
|
||||
color: #409eff;
|
||||
}
|
||||
.active,
|
||||
.active:hover {
|
||||
color: #409eff;
|
||||
background: #ecf5ff;
|
||||
border-color: #409eff;
|
||||
box-shadow: none;
|
||||
p {
|
||||
color: #409eff;
|
||||
}
|
||||
.link-icon {
|
||||
color: #fff;
|
||||
background: #409eff;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -1,72 +1,211 @@
|
||||
<template>
|
||||
<div class="wrapper">
|
||||
<!-- TODO 目前数据少暂且不用 -->
|
||||
<!-- <div class="list">
|
||||
<div class="list-item active">
|
||||
文章页
|
||||
</div>
|
||||
</div> -->
|
||||
<div class="content">
|
||||
<Article @callbacked="callbackArticle" :selected="true" />
|
||||
</div>
|
||||
<div class="query-wrapper">
|
||||
<div class="query-item">
|
||||
<div>文章标题</div>
|
||||
<el-input
|
||||
v-model="params.title"
|
||||
placeholder="请输入文章标题"
|
||||
clearable
|
||||
style="width: 180px"
|
||||
@clear="resetSearch"
|
||||
@keyup.enter="resetSearch"
|
||||
/>
|
||||
</div>
|
||||
<div class="query-item">
|
||||
<el-select
|
||||
v-model="params.categoryId"
|
||||
placeholder="全部分类"
|
||||
clearable
|
||||
filterable
|
||||
style="width: 220px"
|
||||
@change="resetSearch"
|
||||
>
|
||||
<el-option
|
||||
v-for="item in categories"
|
||||
:key="item.id"
|
||||
:label="item.name"
|
||||
:value="item.id"
|
||||
/>
|
||||
</el-select>
|
||||
</div>
|
||||
<div class="query-item">
|
||||
<el-button type="primary" @click="resetSearch">搜索</el-button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<el-table
|
||||
ref="tableRef"
|
||||
v-loading="loading"
|
||||
class="articles-select-table"
|
||||
:data="data"
|
||||
row-key="id"
|
||||
height="340"
|
||||
empty-text="暂无文章信息"
|
||||
@selection-change="onSelectionChange"
|
||||
>
|
||||
<el-table-column
|
||||
type="selection"
|
||||
label="选择"
|
||||
width="60"
|
||||
align="center"
|
||||
reserve-selection
|
||||
/>
|
||||
<el-table-column
|
||||
prop="title"
|
||||
label="文章标题"
|
||||
min-width="240"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column
|
||||
prop="articleCategoryName"
|
||||
label="分类名称"
|
||||
width="140"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column label="是否显示" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.openStatus ? 'success' : 'info'" size="small">
|
||||
{{ row.openStatus ? "展示" : "隐藏" }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column prop="sort" label="排序" width="80" align="center" />
|
||||
<el-table-column
|
||||
prop="id"
|
||||
label="文章ID"
|
||||
width="180"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
</el-table>
|
||||
|
||||
<el-pagination
|
||||
v-model:current-page="params.pageNumber"
|
||||
class="pageration"
|
||||
size="small"
|
||||
layout="total, prev, pager, next, jumper"
|
||||
:total="total"
|
||||
:page-size="params.pageSize"
|
||||
@current-change="changePage"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import Article from "@/views/page/article-manage/articleList.vue";
|
||||
import { nextTick, onMounted, reactive, ref } from "vue";
|
||||
import { getArticle, getArticleCategory } from "@/api/pages";
|
||||
|
||||
const emit = defineEmits(["selected"]);
|
||||
|
||||
// 无 defineExpose:grep manager/src 全量确认无外部 ref/$refs/$parent 访问本组件成员
|
||||
// (link-dialog.vue 的 $refs["lili-component"][0].type 写入指向首个 tab 的 goods-dialog,与本组件无关)
|
||||
function callbackArticle(val) {
|
||||
val.___type = "pages";
|
||||
const loading = ref(false);
|
||||
const total = ref(0);
|
||||
const params = reactive({
|
||||
pageNumber: 1,
|
||||
pageSize: 20,
|
||||
sort: "createTime",
|
||||
order: "desc",
|
||||
categoryId: "",
|
||||
title: "",
|
||||
});
|
||||
const data = ref([]);
|
||||
const categories = ref([]);
|
||||
const tableRef = ref(null);
|
||||
const selectedArticle = ref(null);
|
||||
|
||||
val.___path = "/pages/passport/article";
|
||||
onMounted(() => {
|
||||
loadCategories();
|
||||
getDataList();
|
||||
});
|
||||
|
||||
emit("selected", [val]);
|
||||
function flattenCategories(tree, level = 0) {
|
||||
const result = [];
|
||||
(tree || []).forEach((node) => {
|
||||
result.push({
|
||||
id: node.id,
|
||||
name: `${" ".repeat(level)}${node.articleCategoryName}`,
|
||||
});
|
||||
if (node.children && node.children.length) {
|
||||
result.push(...flattenCategories(node.children, level + 1));
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
function loadCategories() {
|
||||
getArticleCategory().then((res) => {
|
||||
if (res.success) {
|
||||
categories.value = flattenCategories(res.result);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
function resetSearch() {
|
||||
params.pageNumber = 1;
|
||||
getDataList();
|
||||
}
|
||||
|
||||
function changePage(v) {
|
||||
params.pageNumber = v;
|
||||
getDataList();
|
||||
}
|
||||
|
||||
function getDataList() {
|
||||
loading.value = true;
|
||||
const query = { ...params };
|
||||
if (!query.categoryId) delete query.categoryId;
|
||||
if (!query.title) delete query.title;
|
||||
getArticle(query)
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
total.value = res.result.total;
|
||||
data.value = res.result.records || [];
|
||||
nextTick(syncTableSelection);
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
function syncTableSelection() {
|
||||
const table = tableRef.value;
|
||||
if (!table || !selectedArticle.value) return;
|
||||
data.value.forEach((row) => {
|
||||
table.toggleRowSelection(row, row.id === selectedArticle.value.id);
|
||||
});
|
||||
}
|
||||
|
||||
function onSelectionChange(rows) {
|
||||
// 选择链接只能保留一篇文章,勾选新文章时自动取消上一篇
|
||||
if (!rows.length) {
|
||||
selectedArticle.value = null;
|
||||
emit("selected", []);
|
||||
return;
|
||||
}
|
||||
const row = rows[rows.length - 1];
|
||||
if (rows.length > 1) {
|
||||
tableRef.value?.clearSelection();
|
||||
tableRef.value?.toggleRowSelection(row, true);
|
||||
}
|
||||
selectedArticle.value = row;
|
||||
emit("selected", [
|
||||
{ ...row, ___type: "pages", ___path: "/pages/passport/article" },
|
||||
]);
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
:deep(.el-card__body) {
|
||||
height: 414px;
|
||||
overflow: auto;
|
||||
}
|
||||
.list {
|
||||
margin: 0 1.5%;
|
||||
height: 400px;
|
||||
overflow: auto;
|
||||
> .list-item {
|
||||
padding: 10px;
|
||||
transition: 0.35s;
|
||||
cursor: pointer;
|
||||
}
|
||||
.list-item:hover {
|
||||
background: #ededed;
|
||||
}
|
||||
}
|
||||
.list {
|
||||
flex: 2;
|
||||
width: auto;
|
||||
}
|
||||
@import "../style.scss";
|
||||
.content {
|
||||
overflow: hidden;
|
||||
flex: 8;
|
||||
height: 431px;
|
||||
flex: 1;
|
||||
}
|
||||
.active {
|
||||
background: #ededed;
|
||||
}
|
||||
.wrapper {
|
||||
height: 416px;
|
||||
overflow: hidden;
|
||||
}
|
||||
:deep(.el-table) {
|
||||
height: 300px !important;
|
||||
overflow: auto;
|
||||
}
|
||||
:deep(.el-card__body) {
|
||||
padding: 0;
|
||||
height: auto;
|
||||
.pageration {
|
||||
margin-top: 12px;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
</style>
|
||||
|
||||
@@ -18,31 +18,55 @@
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<div v-loading="loading" class="wap-content-list">
|
||||
<div
|
||||
v-for="(item, index) in shopsData"
|
||||
:key="index"
|
||||
class="wap-content-item"
|
||||
:class="{ active: selected == index }"
|
||||
@click="clickShop(item, index)"
|
||||
>
|
||||
<div>
|
||||
<img class="shop-logo" :src="item.storeLogo" alt="" />
|
||||
</div>
|
||||
<div class="wap-content-desc">
|
||||
<div class="wap-content-desc-title">{{ item.storeName }}</div>
|
||||
<div class="self-operated" :class="{ theme_color: item.selfOperated }">
|
||||
{{ item.selfOperated ? "自营" : "非自营" }}
|
||||
</div>
|
||||
<div
|
||||
class="wap-sku"
|
||||
:class="{ theme_color: item.storeDisable === 'OPEN' }"
|
||||
>
|
||||
{{ item.storeDisable === "OPEN" ? "开启中" : "未开启" }}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-table
|
||||
ref="tableRef"
|
||||
v-loading="loading"
|
||||
class="shops-select-table"
|
||||
:data="shopsData"
|
||||
row-key="id"
|
||||
height="340"
|
||||
empty-text="暂无店铺信息"
|
||||
@selection-change="onSelectionChange"
|
||||
>
|
||||
<el-table-column
|
||||
type="selection"
|
||||
label="选择"
|
||||
width="60"
|
||||
align="center"
|
||||
reserve-selection
|
||||
/>
|
||||
<el-table-column label="店铺图片" width="100" align="center">
|
||||
<template #default="{ row }">
|
||||
<img class="shop-logo" :src="row.storeLogo" alt="" />
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column
|
||||
prop="storeName"
|
||||
label="店铺名称"
|
||||
min-width="240"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column
|
||||
prop="createTime"
|
||||
label="创建时间"
|
||||
width="180"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column label="店铺类型" width="120">
|
||||
<template #default="{ row }">
|
||||
<span :class="{ theme_color: row.selfOperated }">
|
||||
{{ row.selfOperated ? "自营" : "非自营" }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
<el-table-column label="店铺状态" width="120">
|
||||
<template #default="{ row }">
|
||||
<span :class="{ theme_color: row.storeDisable === 'OPEN' }">
|
||||
{{ row.storeDisable === "OPEN" ? "开启中" : "未开启" }}
|
||||
</span>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-pagination
|
||||
class="pageration"
|
||||
size="small"
|
||||
@@ -57,7 +81,7 @@
|
||||
</div>
|
||||
</template>
|
||||
<script setup>
|
||||
import { ref, reactive } from "vue";
|
||||
import { nextTick, reactive, ref } from "vue";
|
||||
import { getShopListData } from "@/api/shops.js";
|
||||
|
||||
const emit = defineEmits(["selected"]);
|
||||
@@ -71,7 +95,8 @@ const params = reactive({
|
||||
storeName: "",
|
||||
});
|
||||
const shopsData = ref([]);
|
||||
const selected = ref(9999999999);
|
||||
const tableRef = ref(null);
|
||||
const selectedShop = ref(null);
|
||||
|
||||
// 无 defineExpose:grep manager/src 全量确认无外部 ref/$refs/$parent 访问本组件成员
|
||||
// (link-dialog.vue 的 $refs["lili-component"][0].type 写入指向首个 tab 的 goods-dialog,与本组件无关)
|
||||
@@ -92,19 +117,41 @@ function changePageSize(v) {
|
||||
|
||||
function init() {
|
||||
loading.value = true;
|
||||
getShopListData(params).then((res) => {
|
||||
if (res.success) {
|
||||
total.value = res.result.total;
|
||||
shopsData.value = res.result.records;
|
||||
}
|
||||
loading.value = false;
|
||||
getShopListData(params)
|
||||
.then((res) => {
|
||||
if (res.success) {
|
||||
total.value = res.result.total;
|
||||
shopsData.value = res.result.records;
|
||||
nextTick(syncTableSelection);
|
||||
}
|
||||
})
|
||||
.finally(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
}
|
||||
|
||||
function syncTableSelection() {
|
||||
const table = tableRef.value;
|
||||
if (!table || !selectedShop.value) return;
|
||||
shopsData.value.forEach((row) => {
|
||||
table.toggleRowSelection(row, row.id === selectedShop.value.id);
|
||||
});
|
||||
}
|
||||
|
||||
function clickShop(val, i) {
|
||||
selected.value = i;
|
||||
val = { ...val, ___type: "shops" };
|
||||
emit("selected", [val]);
|
||||
function onSelectionChange(rows) {
|
||||
// 选择链接只能保留一个店铺,勾选新店铺时自动取消上一个
|
||||
if (!rows.length) {
|
||||
selectedShop.value = null;
|
||||
emit("selected", []);
|
||||
return;
|
||||
}
|
||||
const row = rows[rows.length - 1];
|
||||
if (rows.length > 1) {
|
||||
tableRef.value?.clearSelection();
|
||||
tableRef.value?.toggleRowSelection(row, true);
|
||||
}
|
||||
selectedShop.value = row;
|
||||
emit("selected", [{ ...row, ___type: "shops" }]);
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@@ -112,23 +159,13 @@ function clickShop(val, i) {
|
||||
.shop {
|
||||
display: flex;
|
||||
}
|
||||
.self-operated {
|
||||
font-size: 12px;
|
||||
color: #999;
|
||||
}
|
||||
.wap-content-list {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
height: 340px;
|
||||
min-height: 120px;
|
||||
}
|
||||
.shop-logo {
|
||||
object-fit: cover;
|
||||
}
|
||||
.active {
|
||||
background: url("../../../assets/selected.png") no-repeat;
|
||||
background-position: right;
|
||||
background-size: 10%;
|
||||
display: block;
|
||||
width: 48px;
|
||||
height: 48px;
|
||||
margin: 0 auto;
|
||||
border-radius: 4px;
|
||||
object-fit: contain;
|
||||
}
|
||||
.pageration {
|
||||
margin-top: 12px;
|
||||
|
||||
@@ -1,50 +1,67 @@
|
||||
<template>
|
||||
<div class="wrapper">
|
||||
<div class="content">
|
||||
<div>
|
||||
<div class="tables">
|
||||
<el-table v-loading="loading" border height="350" :data="data" style="width: 100%">
|
||||
<el-table-column prop="name" label="专题名称" show-overflow-tooltip />
|
||||
<el-table-column label="操作" width="100" fixed="right" align="center">
|
||||
<template #default="{ row, $index }">
|
||||
<el-button
|
||||
v-if="row"
|
||||
:type="index === $index ? 'primary' : 'default'"
|
||||
size="small"
|
||||
@click="selectRow(row, $index)"
|
||||
>
|
||||
{{ index === $index ? "已选" : "选择" }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
<el-table
|
||||
ref="tableRef"
|
||||
v-loading="loading"
|
||||
class="specials-select-table"
|
||||
:data="data"
|
||||
row-key="id"
|
||||
height="340"
|
||||
empty-text="暂无专题信息"
|
||||
@selection-change="onSelectionChange"
|
||||
>
|
||||
<el-table-column
|
||||
type="selection"
|
||||
label="选择"
|
||||
width="60"
|
||||
align="center"
|
||||
reserve-selection
|
||||
/>
|
||||
<el-table-column
|
||||
prop="id"
|
||||
label="专题ID"
|
||||
width="240"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column
|
||||
prop="name"
|
||||
label="专题名称"
|
||||
min-width="260"
|
||||
show-overflow-tooltip
|
||||
/>
|
||||
<el-table-column label="状态" width="120" align="center">
|
||||
<template #default="{ row }">
|
||||
<el-tag :type="row.pageShow === 'OPEN' ? 'success' : 'info'" size="small">
|
||||
{{ row.pageShow === "OPEN" ? "开启" : "关闭" }}
|
||||
</el-tag>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</el-table>
|
||||
|
||||
<el-pagination
|
||||
v-model:current-page="params.pageNumber"
|
||||
v-model:page-size="params.pageSize"
|
||||
class="mt_10"
|
||||
:total="Number(total)"
|
||||
layout="prev, pager, next, jumper"
|
||||
size="small"
|
||||
@current-change="changePageNum"
|
||||
@size-change="changePageSize"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<el-pagination
|
||||
v-model:current-page="params.pageNumber"
|
||||
v-model:page-size="params.pageSize"
|
||||
class="pageration"
|
||||
:total="Number(total)"
|
||||
layout="total, prev, pager, next, jumper"
|
||||
size="small"
|
||||
@current-change="changePageNum"
|
||||
@size-change="changePageSize"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { onMounted, reactive, ref } from "vue";
|
||||
import { nextTick, onMounted, reactive, ref } from "vue";
|
||||
import { useRoute } from "vue-router";
|
||||
import { getHomeList } from "@/api/other.js";
|
||||
|
||||
const emit = defineEmits(["selected"]);
|
||||
|
||||
const route = useRoute();
|
||||
|
||||
const loading = ref(true);
|
||||
const index = ref(999);
|
||||
const data = ref([]);
|
||||
const params = reactive({
|
||||
sort: "createTime",
|
||||
@@ -55,12 +72,9 @@ const params = reactive({
|
||||
pageType: "SPECIAL",
|
||||
});
|
||||
const total = ref(0);
|
||||
const tableRef = ref(null);
|
||||
const selectedSpecial = ref(null);
|
||||
|
||||
function selectRow(row, idx) {
|
||||
index.value = idx;
|
||||
const payload = { ...row, pageType: "special", ___type: "special" };
|
||||
emit("selected", [payload]);
|
||||
}
|
||||
function changePageNum(val) {
|
||||
params.pageNumber = val;
|
||||
init();
|
||||
@@ -71,43 +85,54 @@ function changePageSize(val) {
|
||||
init();
|
||||
}
|
||||
async function init() {
|
||||
loading.value = true;
|
||||
params.pageClientType = route.name === "renovation" ? "PC" : "H5";
|
||||
const res = await getHomeList(params);
|
||||
if (res.success) {
|
||||
loading.value = false;
|
||||
data.value = res.result.records;
|
||||
data.value = res.result.records || [];
|
||||
total.value = res.result.total;
|
||||
} else {
|
||||
loading.value = false;
|
||||
nextTick(syncTableSelection);
|
||||
}
|
||||
loading.value = false;
|
||||
}
|
||||
|
||||
function syncTableSelection() {
|
||||
const table = tableRef.value;
|
||||
if (!table || !selectedSpecial.value) return;
|
||||
data.value.forEach((row) => {
|
||||
table.toggleRowSelection(row, row.id === selectedSpecial.value.id);
|
||||
});
|
||||
}
|
||||
|
||||
function onSelectionChange(rows) {
|
||||
// 选择链接只能保留一个专题,勾选新专题时自动取消上一个
|
||||
if (!rows.length) {
|
||||
selectedSpecial.value = null;
|
||||
emit("selected", []);
|
||||
return;
|
||||
}
|
||||
const row = rows[rows.length - 1];
|
||||
if (rows.length > 1) {
|
||||
tableRef.value?.clearSelection();
|
||||
tableRef.value?.toggleRowSelection(row, true);
|
||||
}
|
||||
selectedSpecial.value = row;
|
||||
emit("selected", [{ ...row, pageType: "special", ___type: "special" }]);
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
init();
|
||||
});
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
img {
|
||||
max-width: 100% !important;
|
||||
}
|
||||
.search {
|
||||
width: 300px;
|
||||
}
|
||||
.page {
|
||||
margin-top: 2vh;
|
||||
text-align: right;
|
||||
}
|
||||
.tables {
|
||||
height: 400px;
|
||||
margin-top: 20px;
|
||||
overflow: auto;
|
||||
width: 100%;
|
||||
}
|
||||
.content {
|
||||
overflow: hidden;
|
||||
flex: 4;
|
||||
flex: 1;
|
||||
}
|
||||
.wrapper {
|
||||
overflow: hidden;
|
||||
.pageration {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 12px;
|
||||
}
|
||||
</style>
|
||||
|
||||
Reference in New Issue
Block a user