2025-03-26

This commit is contained in:
2025-03-26 11:00:21 +08:00
parent 927d7381b8
commit b45f4950d3
468 changed files with 54473 additions and 124 deletions

View File

@@ -0,0 +1,100 @@
<!-- 基本信息 -->
<template>
<div>
<el-form ref="ruleFormRef" :model="_ruleFormParam" label-width="140">
<el-form-item label="产品名称" required>
<el-input v-model="_ruleFormParam.name" style="width: 440px" />
</el-form-item>
<el-form-item label="副标题名称">
<el-input v-model="_ruleFormParam.short_name" style="width: 440px" />
</el-form-item>
<el-form-item label="型号" required>
<el-input v-model="_ruleFormParam.spu" style="width: 440px" />
</el-form-item>
<el-form-item label="产品分类" style="width: 440px" required>
<!-- <el-input v-model="_ruleFormParam.category_id" /> -->
<el-tree-select
clearable
v-model="_ruleFormParam.category_id"
:data="options"
:render-after-expand="false"
show-checkbox
check-strictly
/>
</el-form-item>
<el-form-item label="产品参数">
<el-input v-model="_ruleFormParam.params" style="width: 440px" />
</el-form-item>
<el-form-item label="产品排序" style="width: 440px" required>
<!-- <el-input v-model="_ruleFormParam.sort" /> -->
<el-input-number
:min="1"
:max="9999"
:controls="true"
v-model.trim="_ruleFormParam.sort"
step-strictly
:step="1"
controls-position="right"
></el-input-number>
</el-form-item>
<el-form-item label="是否上架" style="width: 440px">
<el-radio-group v-model="_ruleFormParam.is_show">
<el-radio :value="1" :label="1"></el-radio>
<el-radio :value="0" :label="0"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="是否新品" style="width: 440px">
<el-radio-group v-model="_ruleFormParam.is_new">
<el-radio :value="1" :label="1"></el-radio>
<el-radio :value="0" :label="0"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="是否推荐" style="width: 440px">
<el-radio-group v-model="_ruleFormParam.is_hot">
<el-radio :value="1" :label="1"></el-radio>
<el-radio :value="0" :label="0"></el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="SEO标题">
<el-input v-model="_ruleFormParam.seo_title" style="width: 440px" />
</el-form-item>
<el-form-item label="SEO关键字">
<el-input v-model="_ruleFormParam.seo_keywords" style="width: 440px" />
</el-form-item>
<el-form-item label="SEO描述">
<el-input v-model="_ruleFormParam.seo_desc" style="width: 440px" />
</el-form-item>
</el-form>
</div>
</template>
<script setup lang="ts" name="basicInfo">
import type { FormInstance } from "element-plus";
const ruleFormRef = ref<FormInstance>();
interface IProps {
data: { [key: string]: any };
options: any[];
}
const props = defineProps<IProps>();
//重置
const reset = () => {
_ruleFormParam = {};
};
let _ruleFormParam: any = computed(() => props.data);
// 暴露给父组件的参数和方法(外部需要什么,都可以从这里暴露出去)
defineExpose({
ruleForm: _ruleFormParam,
reset
});
</script>
<style scoped></style>

View File

@@ -0,0 +1,2 @@
export const FOLDER =
"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAIUAAABoCAYAAADfADNgAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAANmSURBVHhe7dpLb0xhHMfx/zlza4IQJIg0aoGIWwgSCWUiLNhga+Gy8gKwkmBF0peAqKUFKyxdupFY6MJCEIlYuHXhEmHaao/neeZ0dPxmVIgxnfP9LDrnnEm6mPn2uZzTKHEMmOSXUbz4nNiVZ9/C8d3XY+F1Kj0z4/Toz2xfFLvfEdmORbn0ClqtYRQ+gPKt4fSs9XwUh5fn7fT6QnoFrVQXxcTIcObhaHrl//Jx3NnbFV7ROnVRlG9V3Cgxnp61Bx/E5d4i00kL1RYA/W6EaLcgPD96HRkYsbOD7TF6ZUEYKfwHv/Tq1/RSe2Kd0TphpPjdncX/5MP1ax0/avhj/DshinttOG0046e58s1KmE6mQ8zTUZg+/NQxnf/62J38vp5Zkfu84nA/yC/eG312IYro0pf0FFnSbJ1GFAhxTL4f9Hf3pNERqtv+H3ewiQKBv0c1cS+IKFDT/7T68JMoUOOnEb/NJwrUufdmfOrdx4GenB1alrey29fOKnA/YDobqiR24+WYXXjyze6/a3zDcof7nn8ZxbWdpRAFOk/fo1E7+UAfMvptadPp4/iaAkF0sBPu+y03+XeEplH0beZpZKc7tjKfHtVrGMWGeaw/s2D/ksYjRcM1xa7FOevvLaZn6GSrr1Xs/ciPh6HdMyKLhseSpHSZZx+oKrrBg3kCgiggiAKCKCCIAoIoIIgCgiggiAKCKCCIAoIoIIgCgiggiAKCKCCIAoIoIIgCgiggiAKCKCCIAoIoIIgCgiggiAKCKCCIAoIoIIgCgiggiAKCKCCIAoIoIIgCgiggiAKCKCCIAoIoIIgCgiggiAKCKCCIAoIoIIgCgiggiAKCKCCIAoIoIIgCgiggiAKCKCCIAoIoIIgCgiggiAKCKCCIAoIoIIgCgiggiAKCKCDiossijtIzZN78UjpSzC1RBapWzI6rUWxZwCyCqq2uhVDDuY2FcAEZF0V2dEWhGsWqObFtW5gL15Fdp9blrGdmZFHi+AvPPo7b2usVq4yH95Ex87tiGzrYFY5ri4llboFxsbdkebYimTOnaDa4z/1I1UaKCc8/JXZ0YNgG3vrLdW+hA+3pztnN3W4fOolEMeHxh8T6Ho3a7Vdj9vpLYiNMKx0h7yaCNXNjW+5mhvObCmEN8bOmUSCrzL4DY7H3Znn6h1gAAAAASUVORK5CYII=";

View File

@@ -0,0 +1,45 @@
import { ArrowLeft, FolderAdd, Refresh, UploadFilled, Upload, DeleteFilled } from "@element-plus/icons-vue";
export const ICONS: any = [
{
icon: ArrowLeft,
placement: "top",
content: "上一级",
type: "primary",
is: true
},
{
icon: Refresh,
placement: "top",
content: "刷新",
type: "primary",
is: true
},
{
icon: UploadFilled,
placement: "top",
content: "上传文件",
type: "primary",
is: true
},
{
icon: Upload,
placement: "top",
content: "直接上传",
type: "primary",
is: true
},
{
icon: FolderAdd,
placement: "top",
content: "创建目录",
type: "primary",
is: true
},
{
icon: DeleteFilled,
placement: "top",
content: "删除",
type: "danger",
is: true
}
];

View File

@@ -0,0 +1,4 @@
import { IMG_INFO_COLUMNS, TABLE_ITEM, CAPACITY, COLOR } from "./table";
import { ICONS } from "./icons";
import { FOLDER } from "./folderBase64";
export { IMG_INFO_COLUMNS, TABLE_ITEM, CAPACITY, COLOR, ICONS, FOLDER };

View File

@@ -0,0 +1,55 @@
export const IMG_INFO_COLUMNS = [
{
label: "SKU",
prop: "sku",
disabled: false,
formType: "input",
maxLength: 50,
width: "180"
},
{
label: "主图",
prop: "main_image",
disabled: false,
formType: "img",
width: "130"
},
{
label: "图片",
prop: "photo_album_clone",
disabled: false,
formType: "imgs"
},
{
label: "操作",
prop: "operation",
disabled: false,
isHeaderIcon: false,
width: 160
}
];
//颜色初始值
export const COLOR = {
label: "颜色",
prop: "color",
disabled: false,
formType: "select",
options: [],
width: "240"
};
//容量初始值
export const CAPACITY = {
label: "容器",
prop: "capacity",
disabled: false,
formType: "input",
width: "240"
};
//添加行初始值
export const TABLE_ITEM = {
sku: "",
color: "",
capacity: "",
img: "",
imgs: []
};

View File

@@ -0,0 +1,222 @@
<template>
<div>
<!-- 封面图 -->
<div>
<h5 style="margin: 0; margin-bottom: 16px; font-size: 14px">封面图</h5>
<UploadImg v-model:image-url="imgInfoDataStore.cover_image">
<template #tip>
<div style="width: 150px; text-align: center">图片尺寸800x800</div>
</template>
</UploadImg>
</div>
<el-divider />
<!-- 属性 -->
<div>
<h5 style="margin-bottom: 16px; font-size: 14px">属性</h5>
<div>
<!-- 复选框组 -->
<el-checkbox-group v-model="selectedAttrIds" @change="handleCheckboxChange">
<el-checkbox v-for="attr in attrList" :key="attr.id" :label="attr.id">
{{ attr.attr_name }}
</el-checkbox>
</el-checkbox-group>
</div>
</div>
<el-divider />
<!-- 列表图片 -->
<div>
<div style="display: flex; align-items: center; justify-content: space-between; margin-bottom: 16px">
<h5 style="margin: 0; font-size: 14px">列表图信息</h5>
<el-button type="primary" size="small" @click="addRow()">添加行</el-button>
</div>
<!-- 表格 -->
<el-table :data="imgInfoDataStore.skus" border>
<el-table-column label="SKU" prop="sku" width="240px">
<template #default="{ row }">
<el-input style="width: 200px" placeholder="请输入" v-model="row.sku"></el-input>
</template>
</el-table-column>
<el-table-column label="主图" prop="main_image" width="130px">
<template #default="{ row }">
<UploadImg height="104px" width="104px" v-model:image-url="row.main_image"> </UploadImg>
</template>
</el-table-column>
<el-table-column v-for="attrId in selectedAttrIds" :key="attrId" :label="findAttrById(attrId)?.attr_name">
<template #default="{ row }">
<el-form :model="row">
<el-form-item v-if="findAttrById(attrId)?.attr_type === 1" style="margin: 0">
<el-select v-model="findAttrObjInRow(row, attrId).attr_value" placeholder="请选择">
<el-option
v-for="prop in findAttrById(attrId)?.props"
:key="prop.prop_value"
:label="prop.prop_name"
:value="prop.prop_value"
>
</el-option>
</el-select>
</el-form-item>
<el-form-item v-if="findAttrById(attrId)?.attr_type === 2" style="margin: 0">
<!-- 生成输入框 -->
<el-input v-model="findAttrObjInRow(row, attrId).attr_value" placeholder="请输入"></el-input>
</el-form-item>
</el-form>
</template>
</el-table-column>
<el-table-column label="图片">
<template #default="{ row }">
<UploadImgs height="80px" width="80px" v-model:fileList="row.photo_album_clone"> </UploadImgs>
</template>
</el-table-column>
<!-- 删除行操作列 -->
<el-table-column label="操作" style="display: flex; align-items: center" width="120px">
<template #default="{ $index }">
<el-button type="danger" @click="deleteRow($index)">删除</el-button>
</template>
</el-table-column>
</el-table>
</div>
<el-divider />
<!-- 视频 -->
<div style="margin-bottom: 40px">
<!-- 视频图片 -->
<div>
<h5 style="margin: 0; margin-bottom: 16px; font-size: 14px">视频图</h5>
<UploadImg v-model:image-url="imgInfoDataStore.video_img"> </UploadImg>
</div>
<!-- 视频 -->
<div>
<h5 style="margin: 16px 0; font-size: 14px">视频文件</h5>
<UploadVideo v-model:video-url="imgInfoDataStore.video_url"> </UploadVideo>
</div>
</div>
</div>
</template>
<script setup lang="ts" name="imgInfo">
import { ref, reactive, shallowRef, onMounted } from "vue";
import UploadImg from "@/components/Upload/UploadImg.vue";
import UploadImgs from "@/components/Upload/UploadImgs.vue";
import UploadVideo from "@/components/Upload/UploadVideo.vue";
interface IProps {
imgInfoData: { [key: string]: any };
attrList: any[];
}
const props = defineProps<IProps>();
const imgInfoDataRef = shallowRef(props.imgInfoData);
let imgInfoDataStore = reactive({ ...imgInfoDataRef.value });
// 存储选中的属性 ID
const selectedAttrIds = ref<any>([]);
// 根据 ID 查找属性
const findAttrById = (id: any) => {
return props.attrList.find(attr => attr.id === id);
};
// 在 row 的 attrs 数组中查找对应 attrId 的对象
const findAttrObjInRow = (row: any, attrId: any) => {
console.log(row.attrs, "=======row===========");
let obj = row.attrs.find((item: any) => item.attr_id === attrId.toString());
if (!obj) {
obj = { attr_id: attrId.toString(), attr_value: "" };
row.attrs = [...row.attrs, obj]; // 避免直接修改原数组
}
return obj;
};
// 处理复选框变化事件
const handleCheckboxChange = () => {
imgInfoDataStore.skus.forEach((row: any) => {
const newAttrs: any = [];
selectedAttrIds.value.forEach((attrId: any) => {
const existingAttr = row.attrs.find((item: any) => item.attr_id === attrId.toString());
if (existingAttr) {
newAttrs.push({ ...existingAttr });
} else {
newAttrs.push({ attr_id: attrId.toString(), attr_value: "" });
}
});
row.attrs = newAttrs;
});
};
// 添加新行的方法
const addRow = () => {
const newRow: any = {
attrs: []
};
selectedAttrIds.value.forEach((attrId: any) => {
newRow.attrs.push({ attr_id: attrId?.toString(), attr_value: "" });
});
imgInfoDataStore.skus.push({
sku: "", //SKU
main_image: "", //主图
photo_album: [], //副图 这个是传给后端的
photo_album_clone: [], //附图 这个是本地用的
attrs: []
});
};
// 删除行的方法
const deleteRow = (index: any) => {
imgInfoDataStore.skus.splice(index, 1);
};
const echoData = () => {
const newSkus: any[] = [];
imgInfoDataStore.skus.forEach((backendRow: any) => {
const newRow = {
sku: backendRow.sku,
main_image: backendRow.main_image,
photo_album: backendRow.photo_album,
photo_album_clone: backendRow.photo_album.map((url: any) => {
const name = url.split("/").pop();
return { name, url };
}),
attrs: []
};
backendRow.attrs.forEach((attr: any) => {
const targetAttr = findAttrObjInRow(newRow, attr.attr_id);
targetAttr.attr_value = attr.attr_value;
if (!selectedAttrIds.value.includes(attr.attr_id)) {
selectedAttrIds.value.push(attr.attr_id);
}
});
newSkus.push(newRow);
});
imgInfoDataStore.skus = newSkus;
console.log(imgInfoDataStore, "=imgInfoDataStore=");
handleCheckboxChange();
};
const callEchoDataIfHasValue = () => {
if (Object.keys(imgInfoDataStore).length > 0) {
echoData();
}
};
onMounted(() => {
callEchoDataIfHasValue();
});
watch(
() => props.imgInfoData,
newValue => {
imgInfoDataRef.value = newValue;
Object.keys(imgInfoDataStore).forEach(key => delete imgInfoDataStore[key]);
Object.assign(imgInfoDataStore, newValue);
callEchoDataIfHasValue();
},
{ deep: true }
);
defineExpose({
data: imgInfoDataStore
});
</script>
<style scoped>
/* 可以添加自定义样式 */
</style>

View File

@@ -0,0 +1,158 @@
interface FormItem {
prop: string;
label?: string;
placeholder?: string;
type: string;
isCopy?: boolean;
optionProps?: any;
startPlaceholder?: string;
endPlaceholder?: string;
options?: any;
isArray?: boolean;
startDate?: string; //开始时间(传入后台需要的参数)
endDate?: string; //结束时间(传入后台需要的参数)
startProp?: string;
endProp?: string;
isInteger?: boolean;
disabled?: boolean;
fileList?: any;
prompt?: any;
}
//basicInfoFormData
export const BASIC_INFO_FORM_DATA: FormItem[] = [
{
prop: "name",
placeholder: "请输入",
type: "input",
label: "产品名称: "
},
{
prop: "short_name",
placeholder: "请输入",
type: "input",
label: "副标题名称: "
},
{
prop: "spu",
placeholder: "请输入",
type: "input",
label: "型号: "
},
{
prop: "category_id",
placeholder: "请输入",
type: "input",
label: "产品分类: "
},
{
prop: "params",
placeholder: "请输入",
type: "input",
label: "产品参数: "
},
{
prop: "sort",
placeholder: "请输入",
type: "input",
label: "产品排序: "
},
{
prop: "is_sale",
placeholder: "",
type: "radio",
label: "是否上架: ",
options: [
{
label: "是",
value: 1
},
{
label: "否",
value: 0
}
]
},
{
prop: "is_new",
placeholder: "",
type: "radio",
label: "是否新品: ",
options: [
{
label: "是",
value: 1
},
{
label: "否",
value: 0
}
]
},
{
prop: "is_hot",
placeholder: "",
type: "radio",
label: "是否热门: ",
options: [
{
label: "是",
value: 1
},
{
label: "否",
value: 0
}
]
},
{
prop: "seo_title",
placeholder: "请输入",
type: "input",
label: "SEO标题: "
},
{
prop: "seo_keyword",
placeholder: "请输入",
type: "input",
label: "SEO关键字: "
},
{
prop: "seo_desc",
placeholder: "请输入",
type: "input",
label: "SEO描述: "
}
];
export const BASIC_INFO_RULE_FORM = {
seo_desc: "",
seo_keyword: "",
seo_title: "",
is_hot: "",
is_new: "",
is_sale: "",
is_show: "",
sort: 1,
params: "",
category_id: "",
spu: "",
short_name: "",
name: "",
detail: "",
video_url: "",
video_img: "",
desc: "",
cover_image: ""
};
export const BASIC_INFO_RULES = {
name: [{ required: true, message: "产品名称不能为空 ! ", trigger: "blur" }],
spu: [{ required: true, message: "型号不能为空 ! ", trigger: "blur" }],
category_id: [{ required: true, message: "产品分类不能为空 ! ", trigger: "blur" }],
sort: [{ required: true, message: "产品排序不能为空 ! ", trigger: "blur" }]
};
// editRuleForm: {},
//editFormData: [],

View File

@@ -0,0 +1,5 @@
import { FORM_DATA, RULE_FORM } from "./search";
import { COLUMNS } from "./table";
import { OPERATIONS } from "./operations";
import { BASIC_INFO_FORM_DATA, BASIC_INFO_RULE_FORM, BASIC_INFO_RULES } from "./edit";
export { FORM_DATA, RULE_FORM, COLUMNS, OPERATIONS, BASIC_INFO_RULE_FORM, BASIC_INFO_FORM_DATA, BASIC_INFO_RULES };

View File

@@ -0,0 +1,26 @@
export const OPERATIONS = [
{
name: "下架",
name1: "上架",
id: 1,
type: "primary"
},
{
name: "添加SKU",
name1: "添加SKU",
id: 2,
type: "info"
},
{
name: "编辑",
name1: "编辑",
id: 3,
type: "primary"
},
{
name: "删除",
name1: "删除",
id: 4,
type: "info"
}
];

View File

@@ -0,0 +1,22 @@
export const RELATED_INFO_COLUMNS = [
{
label: "型号",
prop: "spu",
disabled: false,
formType: "selectRemote",
options: []
},
{
label: "排序",
prop: "sort",
disabled: false,
formType: "inputNumber"
},
{
label: "操作",
prop: "operation",
disabled: false,
isHeaderIcon: false,
width: 160
}
];

View File

@@ -0,0 +1,90 @@
interface FormItem {
prop: string;
label?: string;
placeholder?: string;
type: string;
isCopy?: boolean;
optionProps?: any;
startPlaceholder?: string;
endPlaceholder?: string;
options?: any;
isArray?: boolean;
startDate?: string; //开始时间(传入后台需要的参数)
endDate?: string; //结束时间(传入后台需要的参数)
startProp?: string;
endProp?: string;
isInteger?: boolean;
}
export const FORM_DATA: FormItem[] = [
{
prop: "name",
placeholder: "请输入",
type: "input",
isArray: true,
label: "产品名称: "
},
{
prop: "spu",
placeholder: "请输入",
type: "input",
isArray: true,
label: "型号: "
},
{
prop: "category_id",
placeholder: "请选择",
type: "treeSelect",
isArray: true,
label: "产品分类: ",
options: [
{
value: "1",
label: "Level one 1",
children: [
{
value: "1-1",
label: "Level two 1-1",
children: [
{
value: "1-1-1",
label: "Level three 1-1-1"
}
]
}
]
}
]
},
{
prop: "Time",
type: "daterange",
options: [],
startPlaceholder: "开始日期",
endPlaceholder: "结束日期",
startDate: "created_at",
// endDate: "createEndDate",
label: "添加时间: "
},
{
prop: "is_show",
placeholder: "请选择",
type: "select",
options: [
{
value: 0,
label: "未上架"
},
{
value: 1,
label: "已上架"
}
],
label: "上架状态: "
}
];
export const RULE_FORM = {
page: 1,
size: 50
};

View File

@@ -0,0 +1,102 @@
import { RenderScope } from "@/components/ProTable/interface";
const YES_OR_NO: any = {
0: "❌",
1: "✔️"
};
export const COLUMNS = [
{
align: "center",
fixed: true,
label: "ID",
prop: "id",
width: 80
},
{
align: "center",
label: "图片",
prop: "cover_image",
width: 160
},
{
align: "left",
label: "产品名称",
prop: "name",
width: 160
},
{
align: "left",
label: "型号",
prop: "spu",
width: 160
},
{
align: "left",
label: "产品分类",
prop: "category_name",
width: 160
},
{
align: "left",
label: "产品排序",
prop: "sort",
width: 80
},
{
align: "center",
label: "新品",
prop: "is_new",
width: 80,
render: (scope: RenderScope<any>): VNode | string | any => {
return YES_OR_NO[scope.row.is_new];
}
},
{
align: "center",
label: "热门",
prop: "is_hot",
width: 80,
render: (scope: RenderScope<any>): VNode | string | any => {
return YES_OR_NO[scope.row.is_hot];
}
},
{
align: "center",
label: "添加时间",
prop: "created_at",
width: 160
},
{
align: "center",
label: "状态",
prop: "status",
width: 80
},
{
align: "center",
label: "在售",
prop: "is_sale",
width: 80,
render: (scope: RenderScope<any>): VNode | string | any => {
return YES_OR_NO[scope.row.is_sale];
}
},
{
align: "center",
label: "库存数量",
prop: "stock_qty",
width: 80,
render: (scope: RenderScope<any>): VNode | string | any => {
return scope.row.stock_qty === 0 ? "0" : scope.row.stock_qty;
}
},
{
align: "center",
label: "上架状态",
prop: "is_show",
width: 160
},
{ prop: "operation", label: "操作", fixed: "right", width: 300 }
];

View File

@@ -0,0 +1,174 @@
<template>
<!-- style="margin-bottom: 16px" -->
<div class="table-box">
<div style="padding-bottom: 16px">
<el-button @click="handleReset(dataStore)"> 重置 </el-button>
<el-button type="primary" @click="handleSubmit(infoRef, imgInfoRef, dataStore)"> 提交 </el-button>
</div>
<div class="card table-main">
<el-tabs v-model="activeName" class="demo-tabs">
<el-tab-pane label="基本信息" name="basicInfo">
<basicInfo ref="infoRef" :data="dataStore.basicInfoRuleForm" :options="dataStore.options" />
</el-tab-pane>
<el-tab-pane label="图片信息" name="imgInfo">
<imgInfo :imgInfoData="dataStore.imgInfoData" ref="imgInfoRef" :attrList="dataStore.attrList" />
</el-tab-pane>
<el-tab-pane label="详细内容" name="third">
<div style="width: 1280px; margin: 0 auto">
<WangEditor v-model:value="dataStore.detail" />
</div>
</el-tab-pane>
<el-tab-pane label="相关信息及下载" name="related">
<!-- <related ref="relatedRef" :data="dataStore.related" /> -->
<div style="margin-bottom: 16px">
<el-button type="primary" size="small" @click="handleRelatedAdd()">添加行</el-button>
</div>
<FormTable
:columns="dataStore.relatedColumns"
:tableData="dataStore.relatedTableData"
@handleRemote="handleRemote"
@handleRemoteClick="handleRemoteClick"
>
<template #operation="scope">
<el-button type="danger" size="small" @click="handleRelatedDelete(scope)">删除行</el-button>
</template>
</FormTable>
</el-tab-pane>
</el-tabs>
</div>
</div>
</template>
<script setup lang="ts" name="productEditIndex">
import { ref, reactive } from "vue";
//getProductAttrsApi
import { getProductDetailsApi, getProductListApi, getProductAttrsListApi } from "@/api/modules/productList";
import { getProductCategoryListApi } from "@/api/modules/productClass";
import { RELATED_INFO_COLUMNS } from "./constant/related";
import { cloneDeep, debounce } from "lodash-es";
import { messageBox } from "@/utils/messageBox";
import { useMsg } from "@/hooks/useMsg";
import { handleSubmit, handleReset, initDetailParams } from "./utils/edit/index";
import { addLabelValue } from "./utils/common/addLabelValue";
//组件引入
import basicInfo from "./components/basicInfo.vue";
import imgInfo from "./components/imgInfo.vue";
import WangEditor from "@/components/WangEditor/index.vue";
import FormTable from "@/components/FormTable/index.vue";
const $route = useRoute();
//数据集合
const dataStore = reactive<any>({
relatedColumns: cloneDeep(RELATED_INFO_COLUMNS), //相关信息及下载表格配置
relatedTableData: [{ related_product_id: null, sort: 1, spu: "" }], //相关信息下载参数
imgInfoData: {
cover_image: "",
video_url: "",
video_img: "",
skus: []
}, //图片信息
detail: "", //详情
basicInfoRuleForm: {}, //基本信息表单数据
options: [], //产品分类
attrList: [] //产品属性列表
});
const infoRef = ref<any>(null);
const imgInfoRef = ref<any>(null);
const activeName = ref("basicInfo");
//详情
const getProductDetails = async () => {
let id = $route.query.id;
if (!id) {
return;
}
const result = await getProductDetailsApi(id);
if (result?.code === 0) {
const { data } = result;
//参数初始化(将参数按照不同的tab区分出来)
initDetailParams(dataStore, data);
}
};
getProductDetails();
//产品属性列表
const getProductAttrsList = async () => {
const result = await getProductAttrsListApi();
if (result?.code === 0) {
dataStore.attrList = result?.data;
}
};
getProductAttrsList();
//型号接口(后端大佬说用产品列表接口)
const getProductList = async (query: any) => {
const result: any = await getProductListApi({
spu: query,
page: 1,
size: 1000
});
if (result?.code === 0) {
let data = cloneDeep(result?.data?.data);
//参数本地化
let dataClone = data.map((item: any) => {
return {
label: item.spu,
value: item.spu,
id: item.id
};
});
dataStore.relatedColumns[0].options = dataClone;
}
};
//产品分类(后端大佬说直接掉列表接口)
const getProductCategoryList = async () => {
const result = await getProductCategoryListApi({ page: 1, size: 500 });
if (result?.code === 0) {
let dataClone: any = cloneDeep(result?.data);
dataStore.options = addLabelValue(dataClone);
}
};
getProductCategoryList();
//相关信息及行远程搜索下拉框点击
const handleRemoteClick = (params: any) => {
const { item, index } = params;
console.log(item, index);
dataStore.relatedTableData[index].related_product_id = item.id;
};
//相关信息及行删除
const handleRelatedDelete = (scope: any) => {
messageBox("确定要删除该行数据?", () => {
if (dataStore.relatedTableData.length === 1) {
useMsg("warning", "请至少保留一行数据 ! ");
return;
}
dataStore.relatedTableData.splice(scope.$index, 1);
});
};
//相关信息及下载添加行
const handleRelatedAdd = () => {
//添加行的初始化对象
let obj = {
related_product_id: null,
sort: 1,
spu: ""
};
dataStore.relatedTableData.push(obj);
};
//相关信息及下载远程搜索
const handleRemote = debounce((params: any) => {
getProductList(params.query);
}, 800);
//产品属性接口
// const getProductAttrs = async () => {
// const result = await getProductAttrsApi();
// if (result?.code === 0) {
// console.log(result.data);
// }
// };
// getProductAttrs();
</script>
<style scoped></style>

View File

@@ -0,0 +1,155 @@
<!-- 产品列表 -->
<template>
<div class="table-box">
<div style="padding-bottom: 16px">
<el-button type="primary" @click="handleExport"> 导出 </el-button>
</div>
<ProTable
ref="proTableRef"
:formData="dataStore.formData"
:columns="dataStore.columns"
:request-api="getProductListApi"
:init-param="dataStore.initParam"
>
<template #cover_image="scope">
<el-image :src="h + scope.row.cover_image" style="width: 60px; height: 60px" />
</template>
<template #status="scope">
<el-tag effect="dark" :type="scope.row.status === 1 ? 'success' : 'danger'">{{
scope.row.status === 1 ? "启用" : "禁用"
}}</el-tag>
</template>
<template #is_show="scope">
<el-tag :type="scope.row.is_show === 1 ? 'success' : 'danger'" effect="dark">{{
scope.row.is_show === 1 ? "已上架" : "未上架"
}}</el-tag>
</template>
<!-- sort -->
<template #sort="scope">
<el-input v-model="scope.row.sort" @blur="handleBlur(scope.row)" @input="handleInput(scope.row)"></el-input>
</template>
<template #operation="scope">
<el-button
size="small"
:type="scope.row.is_show === 1 ? 'danger' : 'primary'"
@click="handleBtnClick('上下架', scope.row)"
>{{ scope.row.is_show === 1 ? "下架" : "上架" }}</el-button
>
<el-button size="small" type="info" style="cursor: not-allowed" @click="handleBtnClick('添加SKU', scope.row)"
>添加SKU</el-button
>
<el-button size="small" type="primary" @click="handleBtnClick('编辑', scope.row)">编辑</el-button>
<el-button size="small" type="info" style="cursor: not-allowed" @click="handleBtnClick('删除', scope.row)"
>删除</el-button
>
</template>
</ProTable>
</div>
</template>
<script setup lang="ts" name="productListIndex">
import ProTable from "@/components/ProTable/index.vue";
import { integerRexg } from "@/utils/regexp/index";
import { useMsg } from "@/hooks/useMsg";
import { useExport } from "@/hooks/useExport";
const h = import.meta.env.VITE_APP_API_BASE_UPLOAD_URL;
//列表接口
//getProductDelApi
import {
getProductListApi,
getProductUpOrShelvesApi,
getProductListSortApi,
getProductListExportApi
} from "@/api/modules/productList";
import { getProductCategoryListApi } from "@/api/modules/productClass";
import { addLabelValue } from "./utils/common/addLabelValue";
//深拷贝方法
import { cloneDeep } from "lodash-es";
//表格和搜索條件
import { RULE_FORM, FORM_DATA, COLUMNS } from "./constant/index";
// 获取 ProTable 元素,调用其获取刷新数据方法(还能获取到当前查询参数,方便导出携带参数)
const proTableRef = ref<any>(null);
const $router = useRouter();
// 数据源
const dataStore = reactive<any>({
columns: COLUMNS, //列表配置项
initParam: cloneDeep(RULE_FORM), // 初始化搜索条件|重置搜索条件
ruleForm: cloneDeep(RULE_FORM), // 搜索參數
formData: FORM_DATA //搜索配置项
});
//导出
const handleExport = () => {
getProductListExport();
};
//导出接口
const getProductListExport = async () => {
const result = await getProductListExportApi(dataStore.ruleForm);
await useExport(result);
};
//上下架
const getProductUpOrShelves = async (id: any) => {
const result = await getProductUpOrShelvesApi(id);
if (result?.code === 0) {
useMsg("success", result?.msg);
proTableRef?.value?.getTableList();
}
};
//排序
const getProductListSort = async (row: any) => {
const result = await getProductListSortApi({ id: row.id, sort: row.sort });
if (result?.code === 0) {
useMsg("success", result?.msg);
proTableRef?.value?.getTableList();
}
};
//产品分类(后端大佬说直接掉列表接口)
const getProductCategoryList = async () => {
const result = await getProductCategoryListApi({ page: 1, size: 500 });
if (result?.code === 0) {
let dataClone: any = cloneDeep(result?.data);
dataStore.formData[2].options = addLabelValue(dataClone);
console.log(result?.data);
}
};
getProductCategoryList();
//排序input框失焦
const handleBlur = (row: any) => {
getProductListSort(row);
};
//
const handleInput = (row: any) => {
row.sort = integerRexg(row.sort);
};
//按钮点击事件
const handleBtnClick = (type: any, row: any) => {
// if (type === "删除") {
// // getProductDel(row.id);
// }
//添加SUK和删除暂时无法操作
if (type == "添加SUK" || type === "删除") {
return false;
}
if (type === "上下架") {
getProductUpOrShelves(row.id);
}
if (type === "编辑") {
$router.push({
path: "/admin/productManagement/list/edit",
query: {
id: row.id
}
});
return;
}
};
</script>
<style scoped></style>
./utils/common/addLabelValue

View File

@@ -0,0 +1,14 @@
export const addLabelValue = (arr: any) => {
return arr.map((item: any) => {
// 为当前对象添加 label 和 value 属性
const newItem = { ...item };
newItem.label = newItem.name;
newItem.value = newItem.id;
// 如果有子对象,递归调用 addLabelValue 处理子对象
if (newItem.children && Array.isArray(newItem.children)) {
newItem.children = addLabelValue(newItem.children);
}
return newItem;
});
};

View File

@@ -0,0 +1,4 @@
import { handleSubmit } from "./submit";
import { handleReset } from "./reset";
import { initDetailParams } from "./initDetailParams";
export { handleSubmit, handleReset, initDetailParams };

View File

@@ -0,0 +1,35 @@
import { cloneDeep } from "lodash-es";
//将参数分离
export const initDetailParams = (dataStore: any, data: any) => {
//基本信息
dataStore.basicInfoRuleForm = cloneDeep({
name: data.name,
short_name: data.short_name,
spu: data.spu,
category_id: data.category_id,
params: data.category_id,
sort: data.sort,
is_show: data.is_show,
is_new: data.is_new,
is_hot: data.is_hot,
is_sale: data.is_sale,
status: data.status,
seo_title: data.seo_title,
seo_keywords: data.seo_keywords,
seo_desc: data.seo_desc,
stock_qty: data.stock_qty,
id: data.id
});
//详情
dataStore.detail = cloneDeep(data.detail);
//图片
dataStore.imgInfoData.cover_image = data.cover_image;
dataStore.imgInfoData.video_url = data.video_url;
dataStore.imgInfoData.skus = data.skus;
dataStore.imgInfoData.video_img = data.video_img;
//相关信息及下载
if (data.related) {
dataStore.relatedTableData = cloneDeep(data.related);
}
};

View File

@@ -0,0 +1,22 @@
import { getProductDetailsApi } from "@/api/modules/productList";
import { useMsg } from "@/hooks/useMsg";
import { messageBox } from "@/utils/messageBox";
// import { cloneDeep } from "lodash-es";
import { initDetailParams } from "./initDetailParams";
//详情(重置,重新获取一下详情)
const getProductDetails = async (dataStore: any) => {
const { id } = dataStore.basicInfoRuleForm;
const result = await getProductDetailsApi(id);
if (result?.code === 0) {
const { data } = result;
initDetailParams(dataStore, data);
useMsg("success", "重置成功 !");
}
};
export const handleReset = (dataStore: any) => {
messageBox("该操作会将数据重置为初始状态", () => {
getProductDetails(dataStore);
});
};

View File

@@ -0,0 +1,78 @@
import { useMsg } from "@/hooks/useMsg";
import { getProductEditUpApi } from "@/api/modules/productList";
import { cloneDeep } from "lodash-es";
const WARN: any = {
name: "产品名称不能为空 !",
spu: "型号不能为空 !",
category_id: "产品分类不能为空 !",
sort: "产品排序不能为空 !"
};
//警告
const warnFunction = (data: any) => {
if (!data.name) {
useMsg("warning", WARN["name"]);
return false;
}
if (!data.spu) {
useMsg("warning", WARN["spu"]);
return false;
}
if (!data.category_id) {
useMsg("warning", WARN["category_id"]);
return false;
}
if (!data.name) {
useMsg("warning", WARN["sort"]);
return false;
}
return true;
};
//更新
const getProductEditUp = async (params: any) => {
const result: any = await getProductEditUpApi(params);
if (result?.code === 0) {
useMsg("success", result?.msg);
// montageImg(imgInfoRef);
}
};
export const handleSubmit = async (infoRef: any, imgInfoRef: any, dataStore: any) => {
let is = await warnFunction(infoRef.ruleForm);
if (!is) {
return false;
}
//相关信息及下载(过滤掉没有id的对象)
let relatedData = dataStore.relatedTableData.filter((item: any) => {
return item.related_product_id;
});
const { video_img, video_url, cover_image, skus } = imgInfoRef.data;
//不要直接去修改skus的数据类型,这里的skus是和表格绑定的
let skusClone = cloneDeep(skus);
skusClone.forEach((item: any) => {
let arr: any = [];
item.photo_album_clone.forEach((it: any) => {
arr.push(it.url);
});
item.photo_album = arr;
item.attrs = item.attrs;
// delete item.photo_albumClone;
});
console.log(skusClone, "=skusClone=");
let skusCloneStr = JSON.stringify(skusClone);
console.log(typeof skusCloneStr);
const params = {
...infoRef.ruleForm,
cover_image,
video_url,
video_img,
skus: skusCloneStr,
detail: dataStore.details,
related: JSON.stringify(relatedData) || []
};
console.log(params, "===========params=============");
getProductEditUp(params);
};