| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583 |
- <template>
- <div class="pc-edit">
- <!-- 内容 -->
- <div class="content-wrap" v-show="diyStore.editTab == 'content'">
- <div class="edit-attr-item-wrap">
- <h3 class="mb-[10px]">购买按钮</h3>
- <el-form label-width="90px" class="px-[10px]">
- <el-form-item label="是否显示">
- <el-switch v-model="diyStore.editComponent.btnShow" />
- </el-form-item>
- <el-form-item label="样式">
- <div class="flex-row-start">
- <div
- @click="diyStore.editComponent.btnStyle = 1"
- class="btnStyle flex-row-center"
- :class="{ 'btnStyle1': diyStore.editComponent.btnStyle == 1 }"
- >
- <div class="btn1">购买</div>
- </div>
- <div
- @click="diyStore.editComponent.btnStyle = 2"
- class="btnStyle flex-row-center"
- :class="{ 'btnStyle1': diyStore.editComponent.btnStyle == 2 }"
- >
- <div class="btn2 flex-row-center">
- <el-icon size="14"><Plus /></el-icon>
- </div>
- </div>
- <div
- @click="diyStore.editComponent.btnStyle = 3"
- class="btnStyle flex-row-center"
- :class="{ 'btnStyle1': diyStore.editComponent.btnStyle == 3 }"
- >
- <div class="btn2 flex-row-center">
- <icon name="iconfont icongouwuche" size="14px" />
- </div>
- </div>
- </div>
- </el-form-item>
- <el-form-item label="按钮文字">
- <el-input v-model="diyStore.editComponent.btnText" placeholder="请输入按钮文字" :maxlength="4" show-word-limit />
- </el-form-item>
- </el-form>
- </div>
- <div class="edit-attr-item-wrap">
- <h3 class="mb-[10px]">商品数据</h3>
- <el-form label-width="90px" class="px-[10px]">
- <el-form-item label="显示内容">
- <el-checkbox-group v-model="diyStore.editComponent.goodsShow">
- <el-checkbox label="商品名称" :value="1" />
- <el-checkbox label="销售价格" :value="2" />
- <el-checkbox label="划线价格" :value="3" />
- </el-checkbox-group>
- </el-form-item>
- <el-form-item label="商品数量">
- <div>
- <el-input-number v-model="diyStore.editComponent.goodsNumber" :min="0" />
- <div class="annotation3 mt-[5px]">(0代表不限)</div>
- </div>
- </el-form-item>
- <el-form-item label="商品排序">
- <el-radio-group v-model="diyStore.editComponent.goodsSort" fill="#409eff">
- <el-radio-button label="综合" :value="1" />
- <el-radio-button label="销量" :value="2" />
- <el-radio-button label="价格" :value="3" />
- </el-radio-group>
- </el-form-item>
- </el-form>
- </div>
- <div class="edit-attr-item-wrap">
- <div class="edit-attr-title flex-row-between">
- <div>
- <span>选项卡</span>
- <span class="title2">鼠标拖拽可以改变顺序</span>
- </div>
- </div>
- <el-form label-width="80px" class="px-[10px]">
- <draggable v-model="diyStore.editComponent.tabList" item-key="id">
- <template #item="{ element, index }">
- <div class="edit-attr-box" @click="diyStore.editComponent.tabIndex = index">
- <el-icon @click.stop="onDel(index)" color="#F56C6C" size="18px" class="circleClose">
- <CircleCloseFilled />
- </el-icon>
- <el-form-item label="名称">
- <el-input v-model="element.title" placeholder="请输入选项卡名称" :maxlength="10" show-word-limit />
- </el-form-item>
- <el-form-item label="选择方式">
- <el-radio-group v-model="element.goodsType">
- <el-radio :value="1">指定商品</el-radio>
- <el-radio :value="2">商品分类</el-radio>
- </el-radio-group>
- </el-form-item>
- <el-form-item label="指定商品" v-if="element.goodsType == 1">
- <div class="data-num">
- <span @click="openDialog(index)" v-if="element.goodsIds.length == 0">请选择</span>
- <span @click="openDialog(index)" v-else>已选择{{ element.goodsIds.length }}个</span>
- <el-icon><ArrowRight /></el-icon>
- </div>
- </el-form-item>
- <el-form-item label="商品分类" v-if="element.goodsType == 2">
- <el-tree-select
- v-model="element.goodsClassify"
- :data="categoryOptions1"
- :props="treeProps"
- value-key="id"
- placeholder="请选择商品分类"
- clearable
- check-strictly
- @change="(res: any) => goodsClassifyChange(res, element)"
- />
- </el-form-item>
- </div>
- </template>
- </draggable>
- <el-button @click="onAdd" style="width: 100%; margin-top: 10px">新增选项卡</el-button>
- </el-form>
- </div>
- </div>
- <!-- 样式 -->
- <div class="style-wrap" v-show="diyStore.editTab == 'style'">
- <div class="edit-attr-item-wrap">
- <h3 class="mb-[10px]">选项卡样式</h3>
- <el-form label-width="80px" class="px-[10px]">
- <el-form-item label="背景">
- <span class="mr-[10px]">{{ diyStore.editComponent.tabbackgroundColor1 }}</span>
- <el-color-picker class="mr-[10px]" v-model="diyStore.editComponent.tabbackgroundColor1" />
- <el-button @click="diyStore.editComponent.tabbackgroundColor1 = '#ffffff'" size="small">重置</el-button>
- </el-form-item>
- <el-form-item label="选中背景">
- <span class="mr-[10px]">{{ diyStore.editComponent.tabbackgroundColor2 }}</span>
- <el-color-picker class="mr-[10px]" v-model="diyStore.editComponent.tabbackgroundColor2" />
- <el-button @click="diyStore.editComponent.tabbackgroundColor2 = '#E7000B'" size="small">重置</el-button>
- </el-form-item>
- <el-form-item label="文字颜色">
- <span class="mr-[10px]">{{ diyStore.editComponent.tabColor1 }}</span>
- <el-color-picker class="mr-[10px]" v-model="diyStore.editComponent.tabColor1" />
- <el-button @click="diyStore.editComponent.tabColor1 = '#333333'" size="small">重置</el-button>
- </el-form-item>
- <el-form-item label="选中文字">
- <span class="mr-[10px]">{{ diyStore.editComponent.tabColor2 }}</span>
- <el-color-picker class="mr-[10px]" v-model="diyStore.editComponent.tabColor2" />
- <el-button @click="diyStore.editComponent.tabColor2 = '#ffffff'" size="small">重置</el-button>
- </el-form-item>
- <el-form-item label="圆角">
- <el-slider size="small" v-model="diyStore.editComponent.tabRadius" show-input :min="1" :max="50" />
- </el-form-item>
- </el-form>
- </div>
- <div class="edit-attr-item-wrap">
- <h3 class="mb-[10px]">商品样式</h3>
- <el-form label-width="80px" class="px-[10px]">
- <el-form-item label="商品背景">
- <span class="mr-[10px]">{{ diyStore.editComponent.goodsbackgroundColor }}</span>
- <el-color-picker class="mr-[10px]" v-model="diyStore.editComponent.goodsbackgroundColor" />
- <el-button @click="diyStore.editComponent.goodsbackgroundColor = '#ffffff'" size="small">重置</el-button>
- </el-form-item>
- <el-form-item label="商品名称">
- <el-radio-group v-model="diyStore.editComponent.goodsTitleType">
- <el-radio :value="1">加粗</el-radio>
- <el-radio :value="2">单行</el-radio>
- <el-radio :value="3">多行</el-radio>
- </el-radio-group>
- </el-form-item>
- <el-form-item label="名称颜色">
- <span class="mr-[10px]">{{ diyStore.editComponent.goodsTitleColor }}</span>
- <el-color-picker class="mr-[10px]" v-model="diyStore.editComponent.goodsTitleColor" />
- <el-button @click="diyStore.editComponent.goodsTitleColor = '#101828'" size="small">重置</el-button>
- </el-form-item>
- <el-form-item label="图片圆角">
- <el-slider size="small" v-model="diyStore.editComponent.imageRadius" show-input :min="1" :max="50" />
- </el-form-item>
- <el-form-item label="销售价">
- <span class="mr-[10px]">{{ diyStore.editComponent.priceColor }}</span>
- <el-color-picker class="mr-[10px]" v-model="diyStore.editComponent.priceColor" />
- <el-button @click="diyStore.editComponent.priceColor = '#E7000B'" size="small">重置</el-button>
- </el-form-item>
- <el-form-item label="上圆角">
- <el-slider size="small" v-model="diyStore.editComponent.goodstopRounded" show-input :min="1" :max="50" />
- </el-form-item>
- <el-form-item label="下圆角">
- <el-slider size="small" v-model="diyStore.editComponent.goodsbottomRounded" show-input :min="1" :max="50" />
- </el-form-item>
- </el-form>
- </div>
- <div class="edit-attr-item-wrap">
- <h3 class="mb-[10px]">商品样式</h3>
- <el-form label-width="80px" class="px-[10px]">
- <el-form-item label="购买按钮">
- <span class="mr-[10px]">{{ diyStore.editComponent.btnColor }}</span>
- <el-color-picker class="mr-[10px]" v-model="diyStore.editComponent.btnColor" />
- <el-button @click="diyStore.editComponent.btnColor = '#ffffff'" size="small">重置</el-button>
- </el-form-item>
- <el-form-item label="背景颜色">
- <span class="mr-[10px]">{{ diyStore.editComponent.btnbackgroundColor }}</span>
- <el-color-picker class="mr-[10px]" v-model="diyStore.editComponent.btnbackgroundColor" />
- <el-button @click="diyStore.editComponent.btnbackgroundColor = '#E7000B'" size="small">重置</el-button>
- </el-form-item>
- </el-form>
- </div>
- <!-- 组件样式 -->
- <slot name="style"></slot>
- </div>
- <!-- 手动选择 -->
- <el-dialog v-model="showDialog" title="选择商品" width="1400">
- <div class="dialog-bos">
- <el-input v-model="queryParams.itemName" placeholder="请输入商品名称" clearable style="width: 300px; margin-bottom: 10px">
- <template #append>
- <el-button :icon="Search" @click="handleQuery" />
- </template>
- </el-input>
- <div class="flex">
- <div class="tree-bos">
- <el-tree :data="categoryOptions" :props="defaultProps" @node-click="handleNodeClick" :highlight-current="true" />
- </div>
- <el-table ref="multipleTableRef" v-loading="loading" :data="tableData" border @selection-change="handleSelectionChange">
- <el-table-column type="selection" width="55" />
- <el-table-column label="商品图片" align="center" prop="productImage" width="100">
- <template #default="scope">
- <image-preview :src="scope.row.productImage" :width="60" :height="60" />
- </template>
- </el-table-column>
- <el-table-column label="商品信息" align="center" minWidth="250" show-overflow-tooltip>
- <template #default="scope">
- <div class="text-left">
- <div>{{ scope.row.itemName }}</div>
- <div class="text-gray-500" style="font-size: 12px">品牌: {{ scope.row.brandName || '-' }}</div>
- </div>
- </template>
- </el-table-column>
- <el-table-column label="SKU价格" align="center" width="180">
- <template #default="scope">
- <div class="text-left" style="font-size: 12px">
- <div>
- <span class="text-gray-500">市场价:</span>
- <span class="text-red-500">¥{{ scope.row.marketPrice || '0.00' }}</span>
- </div>
- <div>
- <span class="text-gray-500">会员价:</span>
- <span class="text-red-500">¥{{ scope.row.memberPrice || '0.00' }}</span>
- </div>
- <div>
- <span class="text-gray-500">最低价:</span>
- <span class="text-red-500">¥{{ scope.row.minSellingPrice || '0.00' }}</span>
- </div>
- </div>
- </template>
- </el-table-column>
- <el-table-column label="成本情况" align="center" width="150">
- <template #default="scope">
- <div class="text-left" style="font-size: 12px">
- <div>
- <span class="text-gray-500">采购价:</span>
- <span>¥{{ scope.row.purchasingPrice || '0.00' }}</span>
- </div>
- <div>
- <span class="text-gray-500">暂估毛利率:</span>
- <span>{{ scope.row.tempGrossMargin || '0.0000' }}%</span>
- </div>
- </div>
- </template>
- </el-table-column>
- </el-table>
- </div>
- <pagination v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList">
- <template #slotDiv>
- <div class="selected">已选择 {{ multipleSelection.length }} 个</div>
- </template>
- </pagination>
- </div>
- <template #footer>
- <span class="dialog-footer">
- <el-button @click="showDialog = false">取消</el-button>
- <el-button type="primary" @click="onConfirm">确认</el-button>
- </span>
- </template>
- </el-dialog>
- </div>
- </template>
- <script lang="ts" setup>
- import draggable from 'vuedraggable';
- import { categoryTree, listBase } from '@/api/pmsProduct/base';
- import usePcdiyStore from '@/store/modules/pcdiy';
- import { Search } from '@element-plus/icons-vue';
- import type { TableInstance } from 'element-plus';
- const diyStore = usePcdiyStore();
- const multipleTableRef = ref<TableInstance>();
- const showDialog = ref(false);
- const loading = ref(false);
- const tableData = ref<any[]>([]);
- const multipleSelection: any = ref([]); // 选中数据
- const total = ref(0);
- const queryParams = reactive({
- pageNum: 1,
- pageSize: 10,
- itemName: '',
- topCategoryId: '',
- mediumCategoryId: '',
- bottomCategoryId: ''
- });
- const resultList = ref<any>([]); //单页之前被选中的数据
- const categoryOptions = ref<any>([]);
- const categoryOptions1 = ref<any>([]);
- const navIndex = ref<any>(0);
- const defaultProps = {
- children: 'children',
- label: 'label'
- };
- const treeProps = {
- value: 'id',
- label: 'label',
- children: 'children'
- };
- onMounted(() => {
- getCategoryTree();
- });
- /** 搜索 */
- const handleQuery = () => {
- queryParams.pageNum = 1;
- getList();
- };
- /** 获取列表 */
- const getList = async () => {
- loading.value = true;
- try {
- const res = await listBase(queryParams);
- tableData.value = res.rows || [];
- const result = tableData.value.filter((item: any) => diyStore.editComponent.tabList[navIndex.value].goodsIds.includes(item.id));
- resultList.value = result;
- nextTick(() => {
- result.forEach((item: any) => {
- multipleTableRef.value?.toggleRowSelection(item, true);
- });
- });
- total.value = res.total || 0;
- } finally {
- loading.value = false;
- }
- };
- /** 查询分类树 */
- const getCategoryTree = async () => {
- categoryOptions.value = [];
- categoryOptions1.value = [];
- const res = await categoryTree();
- const list = res.data || [];
- categoryOptions.value = [...list];
- categoryOptions1.value = [...list];
- categoryOptions.value.unshift({
- id: '',
- label: '全部'
- });
- };
- //打开弹窗
- const openDialog = (res: any) => {
- navIndex.value = res;
- showDialog.value = true;
- getList();
- };
- const handleNodeClick = (data: any) => {
- queryParams.topCategoryId = '';
- queryParams.mediumCategoryId = '';
- queryParams.bottomCategoryId = '';
- if (data.parentId == 0) {
- queryParams.topCategoryId = data.id;
- } else if (data.children) {
- queryParams.mediumCategoryId = data.id;
- } else {
- queryParams.bottomCategoryId = data.id;
- }
- handleQuery();
- };
- // 监听表格单行选中
- const handleSelectionChange = (val: []) => {
- multipleSelection.value = val;
- };
- //确定
- const onConfirm = () => {
- const newIds = calculateNewIds(diyStore.editComponent.tabList[navIndex.value].goodsIds, tableData.value, multipleSelection.value);
- diyStore.editComponent.tabList[navIndex.value].goodsIds = newIds;
- showDialog.value = false;
- };
- const calculateNewIds = (cacheIds: any, allPageItems: any, selectedItems: any) => {
- // 1. 获取当前页所有存在的 ID 集合 (用于识别哪些旧数据属于当前页)
- const currentPageIdSet = new Set(allPageItems.map((item) => item.id));
- // 2. 获取最终选中项的 ID 集合
- const selectedIdSet = new Set(selectedItems.map((item) => item.id));
- // 3. 过滤旧的缓存 IDs
- const retainedOldIds = cacheIds.filter((id) => {
- // 情况 A: 该 ID 不在当前页数据中 (说明是其他页的数据,必须无条件保留)
- if (!currentPageIdSet.has(id)) {
- return true;
- }
- // 情况 B: 该 ID 在当前页数据中,且也在最终选中列表中 (说明用户保持了选中)
- if (selectedIdSet.has(id)) {
- return true;
- }
- // 情况 C: 该 ID 在当前页数据中,但不在最终选中列表中 (说明用户取消了选中,如 ID 4)
- // 返回 false,将其剔除
- return false;
- });
- // 4. 合并:保留的旧数据 + 当前页新选中的数据
- // 使用 Set 去重,虽然逻辑上 retainedOldIds 和 selectedIdSet 不会有交集,但以防万一
- const newIdsSet = new Set([...retainedOldIds, ...selectedIdSet]);
- // 转回数组 (如果需要保持原有顺序或特定排序,可以在此处调整)
- // 这里简单转为数组,通常建议按数字大小排序以便阅读,或者保持插入顺序
- return Array.from(newIdsSet).sort((a, b) => a - b);
- };
- //选择商品分类
- const goodsClassifyChange = (res: any, element: any) => {
- const foundNode = findNodeByKey(categoryOptions1.value, res);
- element.goodsClassify = res;
- element.topCategoryId = '';
- element.mediumCategoryId = '';
- element.bottomCategoryId = '';
- if (foundNode.parentId == 0) {
- element.topCategoryId = foundNode.id;
- } else if (foundNode.children) {
- element.mediumCategoryId = foundNode.id;
- } else {
- element.bottomCategoryId = foundNode.id;
- }
- };
- // 递归查找节点的辅助函数
- const findNodeByKey = (nodes: any, key: any) => {
- for (const node of nodes) {
- if (node.id === key) {
- return node;
- }
- if (node.children) {
- const found = findNodeByKey(node.children, key);
- if (found) return found;
- }
- }
- return null;
- };
- const onAdd = () => {
- diyStore.editComponent.tabList.push({
- title: '',
- goodsType: 1,
- goodsIds: [],
- goodsClassify: '',
- topCategoryId: '',
- mediumCategoryId: '',
- bottomCategoryId: '',
- id: Date.now()
- });
- };
- const onDel = (index: any) => {
- diyStore.editComponent.tabList.splice(index, 1);
- };
- </script>
- <style lang="scss" scoped>
- .pc-edit {
- .edit-attr-item-wrap {
- border-top: 2px solid var(--el-color-info-light-8);
- padding-top: 20px;
- &:first-of-type {
- border-top: none;
- padding-top: 0;
- }
- .edit-attr-box {
- padding: 18px 10px 0 10px;
- border: 1px solid #e5e6eb;
- border-radius: 4px;
- position: relative;
- margin-top: 18px;
- .images-bos {
- flex: 1;
- height: 98px;
- padding: 5px 0;
- }
- .images-box {
- font-size: 13px;
- color: #666;
- }
- .circleClose {
- position: absolute;
- top: -9px;
- right: -9px;
- cursor: pointer;
- }
- .annotation3 {
- font-size: 12px;
- color: #666;
- line-height: 14px;
- }
- }
- .edit-attr-title {
- display: flex;
- .title2 {
- font-size: 12px;
- color: #666;
- margin-left: 6px;
- }
- }
- .btnStyle {
- min-width: 60px;
- padding: 5px;
- cursor: pointer;
- border-radius: 4px;
- margin-right: 4px;
- &.btnStyle1 {
- border: 1px solid var(--el-color-primary);
- }
- .btn1 {
- background-color: var(--el-color-primary);
- padding: 5px 15px;
- border-radius: 15px;
- font-size: 12px;
- color: #ffffff;
- line-height: 1;
- }
- .btn2 {
- color: var(--el-color-primary);
- border: 1px solid var(--el-color-primary);
- height: 26px;
- width: 26px;
- border-radius: 50%;
- }
- }
- .data-num {
- width: 100%;
- font-size: 14px;
- display: flex;
- align-items: center;
- justify-content: flex-end;
- color: var(--el-color-primary);
- cursor: pointer;
- }
- }
- .selected {
- line-height: 32px;
- position: absolute;
- left: 0px;
- }
- :deep(.el-form-item__label) {
- font-weight: 400;
- }
- .annotation3 {
- font-size: 12px;
- color: #666;
- line-height: 14px;
- }
- .dialog-bos {
- .tree-bos {
- max-height: 900px;
- overflow: auto;
- width: 240px;
- margin-right: 10px;
- }
- }
- }
- </style>
|