这篇写的是美食杰---发布菜谱页面,这是美食杰最后一篇文章了。
给大家整理一下其余几篇美食杰项目的页面:
美食杰---首页:
美食杰项目---首页_m0_62201566的博客-优快云博客
美食杰---登录页:美食杰项目----登录页面,看了不会来找我!!!_m0_62201566的博客-优快云博客
美食杰---编辑个人资料:
用vue实现美食杰项目---编辑个人资料,傻瓜式教程,看了就会!!!_m0_62201566的博客-优快云博客
美食杰---个人空间:
美食杰项目---个人空间_m0_62201566的博客-优快云博客
美食杰---菜谱大全:
美食杰----菜谱大全_m0_62201566的博客-优快云博客
美食杰----菜谱大全(二)_m0_62201566的博客-优快云博客
美食杰---详情页:
美食杰----详情页(续写)_m0_62201566的博客-优快云博客
美食杰---发布菜谱:
也就是本篇文章了!
下面来介绍一下本篇文章:
这个页面分为三个部分:
介绍作品,记录原材料,写步骤,最后提交菜谱。
思路:
1.先用Element Ui 来进行页面的布局。
2.向后端请求数据,再把数据渲染到页面上。
3.下拉选项框和输入框里绑定v-model事件,实现数据双向绑定事件。
4.在删除和增加按钮上绑定点击事件。
5.最后点击提交时向后端请求数据。
效果展示:
代码展示:
create.vue:
<template>
<div class="create">
<h2>欢迎发布新菜谱,先介绍一下你的大作!</h2>
<section class="create-introduce">
<h5>标题</h5>
{{ backData.title }}
<el-input
class="create-input"
placeholder="请输入内容"
v-model="backData.title"
></el-input>
<h5>属性</h5>
<div>
<el-select
v-for="item in properties"
: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 classifies"
:key="group.parent_type"
:label="group.parent_name"
>
<el-option
v-for="item in group.list"
:key="item.type"
:label="item.name"
:value="item.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>
<h2>记录所有原材料</h2>
<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>
<h2>开始写步骤了!能否简单易学就看你怎么写了,加油!</h2>
<section class="create-introduce">
<Upload
v-for="(item, index) in backData.steps"
:key="item.customeId"
:n="index + 1"
v-model="backData.steps[index]"
@remove="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>
<el-button
class="send"
type="primary"
size="medium"
:icon="icon"
@click="send"
>搞定,提交审核</el-button
>
</div>
</template>
<script>
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_struct = {
name: "",
specs: "",
};
// 步骤中, 每一步的数据结构
const steps_struct = { 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 {
properties: [], // 页面展示的数据
classifies: [],
icon: "",
backData: {
title: "",
property: {
// craft: '',
// flavor: ''
},
classify: "",
product_pic_url:
"https://s1.c.meishij.net/n/images/upload_big_img.png?_=1561906961",
product_story: "",
raw_material: {
// 料
main_material: Array(3)
.fill(1)
.map(() => ({ ...raw_material_struct })), // 主料 //
accessories_material: Array(3)
.fill(1)
.map(() => ({ ...raw_material_struct })), // 辅料
},
steps: Array(3)
.fill(1)
.map(() => ({ ...steps_struct, customeId: this.uuid() })),
skill: "",
},
};
},
mounted() {
getProperty().then(({ data }) => {
this.properties = data;
this.backData.property = data.reduce((o, item) => {
o[item.title] = "";
return o;
}, {});
});
getClassify().then(({ data }) => {
console.log(data);
this.classifies = data;
});
},
methods: {
uuid() {
n++;
return Date.now() + n;
},
addStep() {
this.backData.steps.push({
...steps_struct,
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 = mockData;
publish(param).then((data) => {
console.log(data);
this.$router.push({
name: "space",
});
});
},
},
};
</script>
<style lang="stylus">
.create-introduce
background-color #fff
padding 20px
.add-step-button
margin-left 100px
.create
width 100%
h2
text-align center
margin 20px 0
.send
// ff3232()
height: 70px;
width: 220px;
background #ff3232
color #fff
border none
margin 20px auto
display block
h5
margin 20px 0
.create-input input
width 446px
line-height 22px
.upload-img-box
.upload-img
float left
.introduce-text
float left
.el-textarea
width 60%
margin-left 10px
</style>
stuff.vue:
<template>
<div class="stuff">
<div class="clearfix">
<div class="raw-item" v-for="(item, index) in value" :key="index">
<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(index)"
></i>
</div>
</div>
<el-button
class="eaeaea"
type="primary"
size="medium"
icon="el-icon-plus"
@click="add"
>增加一项</el-button
>
</div>
</template>
<script>
// 组件通信使用v-model会监听 在组件上面双向绑定 value 发布事件input
export default {
props: {
value: {
type: Array,
default: () => [],
},
},
methods: {
add() {
this.$emit("input", [...this.value, { name: "", specs: "" }]);
},
remove(index) {
// this.value.splice(index,1);
const newValue = this.value.filter((item, i) => {
return i !== index;
});
this.$emit("input", newValue);
},
},
};
</script>
<style lang="stylus">
.delete-icon
background-color #ccc
border-radius 50%
color #fff
:hover
background: #ff3232;
color: #fff;
.raw-item
float left
margin-right 65px
margin-bottom 20px
.el-input
margin-right 5px
</style>
step-upload.vue:
<template>
<div 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"></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: () => ({}),
},
},
methods: {
changeUrl(data) {
this.$emit("input", {
...this.value,
img_url: data.resImgUrl,
});
},
remove() {
this.$emit("remove", this.n);
},
},
};
</script>
<style lang="stylus">
.step
margin-bottom 20px
> div
float left
.step-number
height 180px
width 100px
font-size 60px
color #aaa
font-family Arial, Helvetica, sans-serif
font-weight bold
vertical-align top
line-height 180px
.introduce-text
width 60%
margin-left 40px
.upload-box
img
vertical-align top
</style>
到这里,美食杰---发布菜谱页面就完成了,欢迎大家观看!!!