提交 61521098 authored 作者: 刘旭's avatar 刘旭

商品编辑页面

上级 3af02f39
.router{
margin: 10px 5px;
.router {
margin: 10px 5px;
}
.app-container{
padding: 20px;
.app-container {
padding: 20px;
}
.filter-container {
padding-bottom: 10px;
padding-bottom: 10px;
.filter-item {
display: inline-block;
vertical-align: middle;
margin-bottom: 10px;
margin-right: 10px;
}
.filter-item {
display: inline-block;
vertical-align: middle;
margin-bottom: 10px;
margin-right: 10px;
}
}
.avatar-uploader .avatar {
width: 178px;
height: 178px;
display: block;
}
.avatar-uploader .el-upload {
border: 1px dashed #dcdfe6;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
transition: 0.2s;
}
.avatar-uploader .el-upload:hover {
/* // border-color: var(--el-color-primary); */
}
.el-icon.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 178px;
height: 178px;
text-align: center;
}
......@@ -19,6 +19,7 @@ const props = defineProps({
}
})
const mode = ref('mode')
const emits = defineEmits(['htmlChange'])
// 编辑器实例,必须用 shallowRef
......
......@@ -18,6 +18,21 @@ const routes: Array<RouteRecordRaw> = [
],
},
{
path: "/commodity",
component: Layout,
children: [
{
path: "/commodity/edit",
component: () => import("@/views/commodityManage/edit.vue"),
name: "edit",
meta: {
title: "商品编辑",
icon: "",
},
},
],
},
{
path: "/login",
name: "login",
component: () => import("@/views/login/login.vue"),
......
<template>
<div class="app-container">
<el-card class="box-card">
<h3>商品介绍</h3>
<el-form ref="goodsRef" :rules="rules" :model="goods" label-width="150px">
<el-form-item label="商品编号" prop="goodsSn">
<el-input v-model="goods.goodsSn" />
</el-form-item>
<el-form-item label="商品名称" prop="name">
<el-input v-model="goods.name" />
</el-form-item>
<el-form-item label="市场售价" prop="counterPrice">
<el-input v-model="goods.counterPrice" placeholder="0.00">
<template slot="append"></template>
</el-input>
</el-form-item>
<el-form-item label="是否新品">
<el-radio-group v-model="goods.isNew">
<el-radio :label="true">新品</el-radio>
<el-radio :label="false">非新品</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="是否热卖">
<el-radio-group v-model="goods.isHot">
<el-radio :label="false">普通</el-radio>
<el-radio :label="true">热卖</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="是否在售">
<el-radio-group v-model="goods.isOnSale">
<el-radio :label="true">在售</el-radio>
<el-radio :label="false">未售</el-radio>
</el-radio-group>
</el-form-item>
<el-form-item label="商品图片">
<el-upload :action="uploadPath" :show-file-list="false" :headers="headers"
:on-success="uploadPicUrl" class="avatar-uploader" accept=".jpg,.jpeg,.png,.gif">
<img v-if="goods.picUrl" :src="goods.picUrl" class="avatar" />
<i v-else class="el-icon-plus avatar-uploader-icon" />
</el-upload>
</el-form-item>
<el-form-item label="宣传画廊">
<el-upload :action="uploadPath" :limit="5" :headers="headers" :on-exceed="uploadOverrun"
:on-success="handleGalleryUrl" :on-remove="handleRemove" multiple accept=".jpg,.jpeg,.png,.gif"
list-type="picture-card">
<i class="el-icon-plus" />
</el-upload>
</el-form-item>
<el-form-item label="商品单位">
<el-input v-model="goods.unit" placeholder="件 / 个 / 盒" />
</el-form-item>
<el-form-item label="关键字">
<el-tag v-for="tag in keywords" :key="tag" closable type="primary" @close="handleClose(tag)">
{{ tag }}
</el-tag>
<el-input v-if="newKeywordVisible" ref="newKeywordInput" v-model="newKeyword"
class="input-new-keyword" @keyup.enter.native="handleInputConfirm" @blur="handleInputConfirm" />
<el-button v-else class="button-new-keyword" type="primary" @click="showInput">+ 增加</el-button>
</el-form-item>
<el-form-item label="所属分类">
<el-cascader v-model="categoryIds" :options="categoryList" expand-trigger="hover" clearable
@change="handleCategoryChange" />
</el-form-item>
<el-form-item label="所属品牌商">
<el-select v-model="goods.brandId" clearable>
<el-option v-for="item in brandList" :key="item.value" :label="item.label"
:value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="商品简介">
<el-input v-model="goods.brief" />
</el-form-item>
<el-form-item label="商品详细介绍">
<!-- <editor v-model="goods.detail" :init="editorInit" /> -->
<richTextEditor :valueHtml='goods.detail' @htmlChange="htmlChange" />
</el-form-item>
</el-form>
</el-card>
<el-card class="box-card">
<h3>商品规格</h3>
<el-row :gutter="20" type="flex" align="middle" style="padding: 20px 0">
<el-col :span="10">
<el-radio-group v-model="multipleSpec" @change="specChanged">
<el-radio-button :label="false">默认标准规格</el-radio-button>
<el-radio-button :label="true">多规格支持</el-radio-button>
</el-radio-group>
</el-col>
<el-col v-if="multipleSpec" :span="10">
<el-button :plain="true" type="primary" @click="handleSpecificationShow">添加</el-button>
</el-col>
</el-row>
<el-table :data="specifications">
<el-table-column property="specification" label="规格名" />
<el-table-column property="value" label="规格值">
<template #default="scope">
<el-tag type="primary">
{{ scope.row.value }}
</el-tag>
</template>
</el-table-column>
<el-table-column property="picUrl" label="规格图片">
<template #default="scope">
<img v-if="scope.row.picUrl" :src="scope.row.picUrl" width="40" />
</template>
</el-table-column>
<el-table-column v-if="multipleSpec" align="center" label="操作" width="250"
class-name="small-padding fixed-width">
<template #default="scope">
<el-button type="danger" size="small" @click="handleSpecificationDelete(scope.row)">删除
</el-button>
</template>
</el-table-column>
</el-table>
<el-dialog v-model="specVisiable" title="设置规格">
<el-form ref="specRef" :rules="rules" :model="specForm" status-icon label-position="left"
label-width="100px" style="width: 400px; margin-left: 50px">
<el-form-item label="规格名" prop="specification">
<el-input v-model="specForm.specification" />
</el-form-item>
<el-form-item label="规格值" prop="value">
<el-input v-model="specForm.value" />
</el-form-item>
<el-form-item label="规格图片" prop="picUrl">
<el-upload :action="uploadPath" :show-file-list="false" :headers="headers"
:on-success="uploadSpecPicUrl" class="avatar-uploader" accept=".jpg,.jpeg,.png,.gif">
<img v-if="specForm.picUrl" :src="specForm.picUrl" class="avatar" />
<i v-else class="el-icon-plus avatar-uploader-icon" />
</el-upload>
</el-form-item>
</el-form>
<template #footer class="dialog-footer">
<el-button @click="specVisiable = false">取消</el-button>
<el-button type="primary" @click="handleSpecificationAdd">确定</el-button>
</template>
</el-dialog>
</el-card>
<!-- <el-card class="box-card">
<h3>商品库存</h3>
<el-table :data="products">
<el-table-column property="value" label="货品规格">
<template #default="scope">
<el-tag v-for="tag in scope.row.specifications" :key="tag">
{{ tag }}
</el-tag>
</template>
</el-table-column>
<el-table-column property="price" width="100" label="货品售价" />
<el-table-column property="number" width="100" label="货品数量" />
<el-table-column property="url" width="100" label="货品图片">
<template #default="scope">
<img v-if="scope.row.url" :src="scope.row.url" width="40" />
</template>
</el-table-column>
<el-table-column align="center" label="操作" width="100" class-name="small-padding fixed-width">
<template #default="scope">
<el-button type="primary" size="small" @click="handleProductShow(scope.row)">设置</el-button>
</template>
</el-table-column>
</el-table>
<el-dialog v-model="productVisiable" title="添加货品">
<el-form ref="productRef" :model="productForm" status-icon label-position="left" label-width="100px"
style="width: 400px; margin-left: 50px">
<el-form-item label="货品规格列" prop="specifications">
<el-tag v-for="tag in productForm.specifications" :key="tag">
{{ tag }}
</el-tag>
</el-form-item>
<el-form-item label="货品售价" prop="price">
<el-input v-model="productForm.price" />
</el-form-item>
<el-form-item label="货品数量" prop="number">
<el-input v-model="productForm.number" />
</el-form-item>
<el-form-item label="货品图片" prop="url">
<el-upload :action="uploadPath" :show-file-list="false" :headers="headers"
:on-success="uploadProductUrl" class="avatar-uploader" accept=".jpg,.jpeg,.png,.gif">
<img v-if="productForm.url" :src="productForm.url" class="avatar" />
<i v-else class="el-icon-plus avatar-uploader-icon" />
</el-upload>
</el-form-item>
</el-form>
<template #footer class="dialog-footer">
<el-button @click="productVisiable = false">取消</el-button>
<el-button type="primary" @click="handleProductEdit">确定</el-button>
</template>
</el-dialog>
</el-card> -->
<el-card class="box-card">
<h3>商品参数</h3>
<el-button type="primary" @click="handleAttributeShow">添加</el-button>
<el-table :data="attributes">
<el-table-column property="attribute" label="商品参数名称" />
<el-table-column property="value" label="商品参数值" />
<el-table-column align="center" label="操作" width="100" class-name="small-padding fixed-width">
<template #default="scope">
<el-button type="danger" size="small" @click="handleAttributeDelete(scope.row)">删除</el-button>
</template>
</el-table-column>
</el-table>
<el-dialog v-model="attributeVisiable" title="添加商品参数">
<el-form ref="attributeRef" :model="attributeForm" status-icon label-position="left" label-width="100px"
style="width: 400px; margin-left: 50px">
<el-form-item label="商品参数名称" prop="attribute">
<el-input v-model="attributeForm.attribute" />
</el-form-item>
<el-form-item label="商品参数值" prop="value">
<el-input v-model="attributeForm.value" />
</el-form-item>
</el-form>
<template #footer class="dialog-footer">
<el-button @click="attributeVisiable = false">取消</el-button>
<el-button type="primary" @click="handleAttributeAdd">确定</el-button>
</template>
</el-dialog>
</el-card>
<div class="op-container">
<el-button @click="handleCancel">取消</el-button>
<el-button type="primary" @click="handleEdit">更新商品</el-button>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, computed, nextTick } from "vue";
import { detailGoods, editGoods, listCatAndBrand } from "@/services/api/commodityManage/list";
import { createStorage, uploadPath } from "@/services/api/storage";
import { ElMessageBox, ElMessage, ElNotification } from "element-plus";
import richTextEditor from '@/components/richTextEditor.vue'
import router from '@/router'
let route: any = router
let newKeywordVisible = ref(false);
let newKeyword = ref("");
let keywords = ref([] as any);
let galleryFileList = ref([] as any);
let categoryIds = ref([] as any);
let categoryList = ref([] as any);
let brandList = ref([] as any);
let goods = ref({
goodsSn: "",
name: "",
counterPrice: "",
gallery: [] as any,
isHot: false,
isNew: true,
isOnSale: true,
picUrl: "",
unit: "",
brandId: "",
brief: "",
detail: '<p>你好啊!!</p>',
categoryId: undefined,
} as any);
let specVisiable = ref(false);
let specForm = ref({ specification: "", value: "", picUrl: "" } as any);
let multipleSpec = ref(false);
let specifications = ref([
{ specification: "规格", value: "标准", picUrl: "" },
]);
let productVisiable = ref(false);
let productForm = ref({
id: 0,
specifications: [],
price: 0.0,
number: 0,
url: "",
});
let products = ref([
{ id: 0, specifications: ["标准"], price: 0.0, number: 0, url: "" },
]);
let attributeVisiable = ref(false);
let attributeForm = ref({ attribute: "", value: "" } as any);
let attributes = ref([] as any);
let rules = ref({
goodsSn: [{ required: true, message: "商品编号不能为空", trigger: "blur" }],
name: [{ required: true, message: "商品名称不能为空", trigger: "blur" }],
});
let headers = computed(() => {
return { Authorization: localStorage.getItem("token") };
});
let init = () => {
if (route.options.history.location.split('=').length > 1) {
var goodsId = route.options.history.location.split('=')[1]
}
else return
detailGoods(goodsId).then((res) => {
goods.value = res.data.goods;
console.log(goods.value);
// 稍微调整一下前后端不一致
if (goods.value?.brandId === 0) {
goods.value!.brandId = null;
}
if (goods.value?.keywords === "") {
goods.value!.keywords = null;
}
specifications.value = res.data.specifications;
products.value = res.data.products;
attributes.value = res.data.attributes;
categoryIds.value = res.data.categoryIds;
console.log(categoryIds.value, 'categoryIds.value');
galleryFileList.value = [];
for (var i = 0; i < goods.value?.gallery.length; i++) {
galleryFileList.value.push({
url: goods.value?.gallery[i],
});
}
const keyword: any = res.data.goods.keywords;
if (keyword !== null) {
keywords.value = keyword.split(",");
}
});
listCatAndBrand().then((res: any) => {
categoryList.value = res.data.categoryList;
brandList.value = res.data.brandList;
});
}
init()
let htmlChange = (value: any) => {
// console.log('富文本value', value);
}
let handleCategoryChange = (value: any) => {
goods.value.categoryId = value[value.length - 1];
}
let handleCancel = () => {
// $store.dispatch("tagsView/delView", $route);
// $router.push({ path: "/goods/list" });
}
let handleEdit = () => {
const finalGoods = {
goods: goods.value,
specifications: specifications.value,
products: products.value,
attributes: attributes.value,
};
editGoods(finalGoods)
.then((res: any) => {
ElNotification({
type: 'success',
title: "成功",
message: "编辑成功",
});
// $store.dispatch("tagsView/delView", $route);
router.push({ path: "/goods/list" });
})
.catch((res: any) => {
ElMessageBox.alert("业务错误:" + res.data.errmsg, "警告", {
confirmButtonText: "确定",
type: "error",
});
});
}
let handleClose = (tag: any) => {
keywords.value.splice(keywords.value.indexOf(tag), 1);
goods.value.keywords = keywords.value.toString();
}
let showInput = () => {
newKeywordVisible.value = true;
nextTick(() => {
// $refs.newKeywordInput.$refs.input.focus();
});
}
let handleInputConfirm = () => {
const newKeywords: string = newKeyword.value;
if (newKeywords) {
keywords.value.push(newKeywords);
goods.value.keywords = keywords.value.toString();
}
newKeywordVisible.value = false;
newKeyword.value = "";
}
let uploadPicUrl = (res: any) => {
goods.value.picUrl = res.data.url;
}
let uploadOverrun = () => {
ElMessage({
type: "error",
message: "上传文件个数超出限制!最多上传5张图片!",
});
}
let handleGalleryUrl = (res: any, file: any, fileList: any) => {
if (res.errno === 0) {
goods.value.gallery.push(res.data.url);
}
}
let handleRemove = (file: any, fileList: any) => {
for (let i = 0; i < goods.value.gallery.length; i++) {
// 这里存在两种情况
// 1. 如果所删除图片是刚刚上传的图片,那么图片地址是file.res.data.url
// 此时的file.url虽然存在,但是是本机地址,而不是远程地址。
// 2. 如果所删除图片是后台返回的已有图片,那么图片地址是file.url
let url;
if (file.res === undefined) {
url = file.url;
} else {
url = file.res.data.url;
}
if (goods.value.gallery[i] === url) {
goods.value.gallery.splice(i, 1);
}
}
}
let specChanged = (label: any) => {
if (label === false) {
specifications.value = [
{ specification: "规格", value: "标准", picUrl: "" },
];
products.value = [
{ id: 0, specifications: ["标准"], price: 0.0, number: 0, url: "" },
];
} else {
specifications.value = [];
products.value = [];
}
}
let uploadSpecPicUrl = (res: any) => {
specForm.value.picUrl = res.data.url;
}
let handleSpecificationShow = () => {
specForm = { specification: "", value: "", picUrl: "" };
specVisiable.value = true;
}
let handleSpecificationAdd = () => {
let index = specifications.value.length - 1;
for (let i = 0; i < specifications.value.length; i++) {
const v = specifications.value[i];
if (v.specification === specForm.value.specification) {
if (v.value === specForm.value) {
ElMessage({
type: "warning",
message: "已经存在规格值:" + v.value,
});
specForm.value = {};
specVisiable.value = false;
return;
} else {
index = i;
}
}
}
specifications.value.splice(index + 1, 0, specForm);
specVisiable.value = false;
specToProduct();
}
let handleSpecificationDelete = (row: any) => {
const index = specifications.value.indexOf(row);
specifications.value.splice(index, 1);
specToProduct();
}
let specToProduct = () => {
if (specifications.value.length === 0) {
return;
}
// 根据specifications创建临时规格列表
let specValues = [];
let spec = specifications.value[0].specification;
let values = [];
values.push(0);
for (let i = 1; i < specifications.value.length; i++) {
const aspec = specifications.value[i].specification;
if (aspec === spec) {
values.push(i);
} else {
specValues.push(values);
spec = aspec;
values = [];
values.push(i);
}
}
specValues.push(values);
// 根据临时规格列表生产货品规格
// 算法基于 https://blog.csdn.net/tyhj_sf/article/details/53893125
let productsIndex = 0;
let products = [];
let combination = [];
let n = specValues.length;
for (let s = 0; s < n; s++) {
combination[s] = 0;
}
let index = 0;
let isContinue = false;
do {
let specifications = [] as any;
for (let x = 0; x < n; x++) {
let z = specValues[x][combination[x]];
specifications.push(specifications[z]);
}
products[productsIndex] = {
id: productsIndex,
specifications: specifications,
price: 0.0,
number: 0,
url: "",
};
productsIndex++;
index++;
combination[n - 1] = index;
for (let j = n - 1; j >= 0; j--) {
if (combination[j] >= specValues[j].length) {
combination[j] = 0;
index = 0;
if (j - 1 >= 0) {
combination[j - 1] = combination[j - 1] + 1;
}
}
}
isContinue = false;
for (let p = 0; p < n; p++) {
if (combination[p] !== 0) {
isContinue = true;
}
}
} while (isContinue);
products = products;
}
let handleProductShow = (row: any) => {
productForm = Object.assign({}, row);
productVisiable.value = true;
}
let uploadProductUrl = (res: any) => {
productForm.value.url = res.data.url;
}
let handleProductEdit = () => {
for (let i = 0; i < products.value.length; i++) {
const v = products.value[i];
if (v.id === productForm.value.id) {
products.value.splice(i, 1, productForm.value);
break;
}
}
productVisiable.value = false;
}
let handleAttributeShow = () => {
attributeForm.value = {};
attributeVisiable.value = true;
}
let handleAttributeAdd = () => {
attributes.value.unshift(attributeForm.value);
attributeVisiable.value = false;
}
let handleAttributeDelete = (row: any) => {
const index = attributes.value.indexOf(row);
attributes.value.splice(index, 1);
}
</script>
<style>
.el-card {
margin-bottom: 10px;
}
.el-tag+.el-tag {
margin-left: 10px;
}
.input-new-keyword {
width: 90px;
margin-left: 10px;
vertical-align: bottom;
}
.avatar-uploader .el-upload {
width: 145px;
height: 145px;
border: 1px dashed #d9d9d9;
border-radius: 6px;
cursor: pointer;
position: relative;
overflow: hidden;
}
.avatar-uploader .el-upload:hover {
border-color: #20a0ff;
}
.avatar-uploader-icon {
font-size: 28px;
color: #8c939d;
width: 120px;
height: 120px;
line-height: 120px;
text-align: center;
}
.avatar {
width: 145px;
height: 145px;
display: block;
}
.op-container {
display: flex;
justify-content: center;
}
</style>
\ No newline at end of file
......@@ -161,7 +161,10 @@ let handleCreate = () => {
router.push({ path: "/commodity/shelves" });
};
let handleUpdate = (row: any) => {
// router.push({ path: "/goods/edit", query: { id: row.id } });
console.log(row.id);
let commodityId = row.id
localStorage.setItem('commodityId', commodityId)
router.push({ path: "/commodity/edit", query: { id: row.id } });
};
let showDetail = (detail: any) => {
goodsDetail = detail;
......
......@@ -67,17 +67,21 @@
</el-select>
</el-form-item>
<el-form-item label="类目图标" prop="iconUrl">
<el-upload :headers="headers" :action="uploadPath" :show-file-list="false"
<el-upload :headers="headers" :action="uploadPath" :show-file-list="false" list-type='picture'
:on-success="uploadIconUrl" class="avatar-uploader" accept=".jpg,.jpeg,.png,.gif">
<img v-if="dataForm.iconUrl" :src="dataForm.iconUrl" class="avatar" />
<i v-else class="el-icon-plus avatar-uploader-icon" />
<el-icon v-else class="avatar-uploader-icon">
<Plus />
</el-icon>
</el-upload>
</el-form-item>
<el-form-item label="类目图片" prop="picUrl">
<el-upload :headers="headers" :action="uploadPath" :show-file-list="false"
:on-success="uploadPicUrl" class="avatar-uploader" accept=".jpg,.jpeg,.png,.gif">
<img v-if="dataForm.picUrl" :src="dataForm.picUrl" class="avatar" />
<i v-else class="el-icon-plus avatar-uploader-icon" />
<img v-if="dataForm.iconUrl" :src="dataForm.iconUrl" class="avatar" />
<el-icon v-else class="avatar-uploader-icon">
<Plus />
</el-icon>
</el-upload>
</el-form-item>
<el-form-item label="类目简介" prop="desc">
......@@ -266,7 +270,3 @@ let handleDelete = (row: any) => {
});
};
</script>
<style lang = "scss" scoped>
</style>
\ No newline at end of file
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论