概述:
- 第一部分是显示菜品的样子,菜品的属性
- 第二部分是菜品需要的材料,分为主料和辅料
- 第三部分菜品制作的步骤
- 最后就把前三部提交,提交后跳转到个人空间查看菜品信息
1. 头部:定义标题内容,定义要发布的菜品,口味,难度之类的选择,随后就是菜谱分类,有家常菜谱和中华菜谱两个一级菜单,有类型,看你要发布的菜是什么菜,然后归类选择,选择好之后,底下是上传成品图片,右侧是描述左侧成品图的由来
代码
<section class="create-introduce">
<h5>标题</h5>
<el-input v-model='backData.title' class="create-input" placeholder="请输入内容"></el-input>
<h5>属性</h5>
<div>
<el-select
v-for='item in propertys'
:key='item.parent_type'
:placeholder='item.parent_name'
v-model='backData.property[item.title]'
>
<el-option
v-for='option in item.list'
:key='option.type'
:label='option.name'
:value='option.type'
>
</el-option>
</el-select>
</div>
<h5>菜谱分类</h5>
<div>
<el-select
placeholder="请选择菜谱分类"
v-model='backData.classify'>
<el-option-group
v-for='group in getClassifyes'
:key='group.parent_type'
:label='group.parent_name'
>
<el-option
v-for='options in group.list'
:key='options.type'
:label='options.name'
:value='options.type'
>
</el-option>
</el-option-group>
</el-select>
</div>
<h5>成品图 (328*440)</h5>
<div class="upload-img-box clearfix">
<div class="upload-img">
<upload-img
action='/api/upload?type=product'
:image-url="backData.product_pic_url"
@res-url="(data) => {backData.product_pic_url = data.resImgUrl}"
></upload-img>
</div>
<el-input
class="introduce-text"
type="textarea"
:rows="10"
placeholder="请输入内容"
v-model="backData.product_story"
>
</el-input>
</div>
</section>
upload-img 组件代码
<template>
<el-upload
class="avatar-uploader"
:action="action"
:show-file-list="false"
:on-success="handleAvatarSuccess"
accept=".jpg, .jpeg, .png, .gif, .bmp"
:before-upload="beforeAvatarUpload">
<img v-if="imageUrl" :src="imageUrl" class="avatar">
<i v-else class="el-icon-plus avatar-uploader-icon"></i>
</el-upload>
</template>
<script>
export default {
props:{
action:{String,Number},
maxSize:{
type:Number,
default:2
},
imageUrl:{
type:String,
default:''
},
imgMaxWidth:{
type:[Number,String],
default:'auto'
}
},
data(){
return {
}
},
methods: {
// 图片上传成功之后
handleAvatarSuccess(res, file) {
console.log(res)
console.log(file)
if(res.code === 1){
this.$message({
message:res.mes,
type:"warning"
})
// return;
}
this.$emit('imageURL', URL.createObjectURL(file.raw))
},
// 图片上传成功之前
beforeAvatarUpload(file) {
const isLt2M = file.size / 240 / 240 < 2;
if (!isLt2M) {
this.$message.error('上传头像图片大小不能超过 2MB!');
}
return isLt2M;
}
}
}
</script>
2.菜品材料部分
菜品需要用到的材料分为主料与辅料,添加功能,如果需要的材料过多的话,可以操作进行添加,删除图标,可以对内容进行删除操作,判断条件,当内容只剩下一个的时候就不让进行删除操作了,添加可以没有上限,但是删除需要设置下限。
渲染视图代码
<section class="create-introduce">
<h5>主料</h5>
<!--[ { "name": "", "specs": "" }, { "name": "", "specs": "" }, { "name": "", "specs": "" } ]-->
<Stuff
v-model='backData.raw_material.main_material'
></Stuff>
<h5>辅料</h5>
<Stuff
v-model='backData.raw_material.accessories_material'
></Stuff>
</section>
Stuff 组件代码
<template>
<div class="stuff">
<div class="clearfix">
<div class="raw-item"
v-for='(item,i) in value'
:key='i'
>
<el-input v-model='item.name' placeholder="请输入内容" style="width: 200px" ></el-input>
<el-input v-model='item.specs' placeholder="请输入内容" style="width: 100px" ></el-input>
<i class="delete-icon el-icon-close" v-show='value.length !==1' @click='remove(i)'></i>
</div>
</div>
<el-button @click='add()' class="eaeaea" type="primary" size="medium" icon="el-icon-plus">增加一项</el-button>
</div>
</template>
<script>
// v-model 在组件上面双向绑定 value 发布事件input
export default {
props: {
value:{
type:Array,
default:()=>[]
}
},
methods:{
remove(index){
const newValue = this.value.filter((item,i)=>{
return i !== index ;
});
this.$emit('input',newValue)
},
add(){
this.$emit('input',[
...this.value,
{'name':'','specs':''}
])
}
}
}
</script>
3.菜品制作,按菜品的制作步骤一步步写就好了,和内容部分功能相似,都有一个添加和删除的效果
渲染视图代码
<section class="create-introduce">
<Upload
v-for="(item,i) in backData.steps"
:key="item.customeId"
:n="i+1"
v-model="backData.steps[i]"
@removeList='removeStep'
></Upload>
<el-button
class="eaeaea add-step-button"
type="primary"
size="medium"
icon="el-icon-plus"
@click='addStep()'
>增加一步</el-button>
<h5>烹饪小技巧</h5>
<el-input
class="introduce-text"
type="textarea"
:rows="8"
placeholder="分享下你做这道菜的过程中的心得和小技巧吧!"
v-model="backData.skill"
>
</el-input>
</section>
Upload 组件代码
<template>
<div ref="stepFix" class="step clearfix">
<div class="step-number">{{n}}.</div>
<div class="upload-box">
<upload-img
action='/api/upload?type=step'
:image-url="$options.imageUrl"
:img-max-width="184"
@res-url="changeUrl"
></upload-img>
</div>
<el-input
class="introduce-text"
type="textarea"
:rows="8"
placeholder="请输入内容"
v-model="value.describe"
>
</el-input>
<i class="delete-icon el-icon-close" v-show="value.length!==1" @click="remove()"></i>
</div>
</template>
<script>
import UploadImg from '@/components/upload-img'
export default {
components: {UploadImg},
imageUrl: 'https://s1.c.meishij.net/n/images/upload_step_img.png',
props: {
n:{
type:Number,
default:1
},
length:{
type:Number,
default:1
},
value:{
type:Object,
default:()=>({})
}
},
mounted(){
},
methods: {
changeUrl(data){
this.$emit('input', {
...this.value,
img_url: data.resImgUrl
})
},
remove(){
this.$emit('removeList',this.n)
}
}
}
</script>
4.提交按钮,提交按钮的功能就是把上面所编辑的内容什么的保存到个人空间作品里,点击提交后会跳转到个人空间,也可以直接看到提交的菜品信息
渲染视图代码
<el-button
class="send"
type="primary"
size="medium"
:icon="icon"
@click='send()'
>搞定,提交审核</el-button>
js全部代码
import Stuff from './stuff'
import Upload from './step-upload'
import UploadImg from '@/components/upload-img'
import {getProperty, getClassify, publish} from '@/service/api'
// 向后端发送的数据结构
const backData = {
title: '', // 标题
product_pic_url: '', // 成品图URL
product_story: '', // 成品图故事
property: {
craft: 0, // 工艺 enum: [1,2,3,4],
flavor: 0, // 口味 enum: [1,2,3,4],
hard: 0, // 难度 enum: [1,2,3,4],
pepole: 0 // pepole 人数: [1,2,3,4],
}, // 属性
raw_material: { // 料
main_material: [{name: '',specs: ''}], // 主料
accessories_material: [{name: '',specs: ''}], // 辅料
},
steps: [{img_url: '',describe: '',}], // 步骤
classify: '', // 菜谱分类
skill: '',
}
// 用料的数据结构
const raw_material_start = {
name:'',specs:''
}
// 步骤中每一步的数据结构
const raw_material_steps = {
img_url: '',describe: '',
}
// 步骤中,存储序号
let n = 1 ;
//用于提交的测试数据, 避免每次测试时在页面逐个选中
const mockData = {
"title": "测试数据123",
"property": {
"craft": "1-2",
"flavor": "2-1",
"hard": "3-1",
"people": "4-1"
},
"classify": "1-1",
"product_pic_url": "http://127.0.0.1:7001/static/upload/product/328X4401565628820747.jpg",
"product_story": "1",
"raw_material": {
"main_material": [
{
"name": "1",
"specs": "1"
},
{
"name": "2",
"specs": "2"
},
{
"name": "3",
"specs": "3"
}
],
"accessories_material": [
{
"name": "1",
"specs": "3"
},
{
"name": "2",
"specs": "4"
},
{
"name": "4",
"specs": "4"
}
]
},
"steps": [
{
"img_url": "http://127.0.0.1:7001/static/upload/step/210X210X21565628835530.jpg",
"describe": "1"
},
{
"img_url": "http://127.0.0.1:7001/static/upload/step/210X210X21565628839458.jpg",
"describe": ""
},
{
"img_url": "http://127.0.0.1:7001/static/upload/step/210X2101565628842198.jpg",
"describe": "3"
}
],
"skill": "心得"
}
export default {
name: 'create',
components: {Stuff,Upload,UploadImg},
data(){
return {
propertys:[], // 页面展示的数据
getClassifyes:[],
icon:'',
backData:{
title:'',
property: {},
classify:'',
product_pic_url: 'https://s1.c.meishij.net/n/images/upload_big_img.png?_=1561906961', // 成品图URL
product_story: '', // 成品图故事
raw_material: { // 料
main_material: Array(3).fill(1).map(()=>({...raw_material_start})), // 主料
accessories_material: Array(3).fill(1).map(()=>({...raw_material_start})), // 辅料
// main_material:[{'name':'','specs':''},{'name':'','specs':''},{'name':'','specs':''}],
// accessories_material:[{'name':'','specs':''},{'name':'','specs':''},{'name':'','specs':''}],
},
steps:Array(3).fill(1).map(()=>({...raw_material_steps,customeId:this.uuid()})),
skill:''
},
}
},
mounted(){
getProperty().then(({data})=>{
// console.log(data)
this.propertys = data ;
// console.log(this.propertys)
this.backData.property = this.propertys.reduce((totel,item)=>{
totel[item.title] ='' ;
return totel ;
// console.log(this.backData.property)
},{})
})
getClassify().then(({data})=>{
// console.log(data)
this.getClassifyes = data ;
})
},
methods:{
uuid(){
n++;
return Date.now() + n ;
},
addStep(){
this.backData.steps.push({
...raw_material_steps,
customeId:this.uuid()
})
},
removeStep(index){
this.backData.steps.splice(index-1,1)
},
send(){
this.icon = 'el-icon-loading' ;
let param = this.backData ;
// 验证
// 删除字段 删除字段后,当前页面需要用到这个字段的地方可能会有问题
// 提取出需要的字段
param.steps = param.steps.map((item)=>{
return {
img_url:item.img_url,
describe:item.describe
}
})
// 1.测试过程中不跳转,手动去打开指定的跳转的页面去看数据对不对
// 2.mock数据,模拟一套数据,预先准备一套
//console.log(JSON.stringify(param, null, 2)); 这个2 看懵了吧? 这是转字符串输出, 2个间距,有空多看看基础吧
param = this.backData; // 测试数据 mockData
publish(param).then((data)=>{
console.log(data)
this.$router.push({
name:'space'
})
})
}
},
}