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:
pikachu1995@126.com
2026-10-20 16:13:28 +08:00
parent d87e0c5f6b
commit d29ad9c99b
78 changed files with 8441 additions and 1350 deletions

View File

@@ -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"

View File

@@ -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) {

View File

@@ -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>

View File

@@ -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>

View File

@@ -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) => {
//对象去重

View File

@@ -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>

View File

@@ -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>

View File

@@ -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;

View File

@@ -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>