【Food Corner】是我在学习Vue、Node.js等相关技术后独立开发完成的一个菜谱webapp,包含一个后台管理系统,参考了现在主流菜谱应用的功能和界面设计。
从3月初开始选择一个自己感兴趣的内容到最后基本上完成想要实现的功能,总共花费了大半个月的时间,虽然说是完全自己开发的,但是还是在之前练手的王者荣耀移动端基础上进行开发,也参考了一些开源的项目。整体功能设计还是比较简单的,在移动端的设计上与纯静态展示界面相比还是增加了一些新的功能。
作为一个小白,明白还要学习的内容还有很多,完成这个人小项目就当做是巩固之前学过的内容吧,毕竟熟能生巧,跟着视频学总觉得没什么难点,一旦完全自己做,还是有很多意想不到的bug,在解决这些问题的同时也觉得能力有了一定的提升。
项目说明
开发环境 Win10 node.js 10.16.0 Chrome
Github地址:github.com/Zhouqiaoqia…
技术栈
vue + node.js + express + mongodb + elementui
项目运行
运行前你需要安装好:
node.js 10+、mongodb、nodemon、vue cli3
项目运行:
clone项目到本地
npm install
cd web(移动端)/admin(后台管理系统)
npm run serve
访问地址
目标功能
移动端:
- 注册新用户
- 登录、退出登录
- 轮播广告位
- 菜谱列表
- 菜谱详情
- 搜索菜谱
- 活动文章/社区文章列表
- 文章详情
- 查看菜谱分类
- 查看某个类别下的菜谱
- 添加菜谱(需登录)
- 个人中心查看(需登录)
后台管理系统:
- 登录管理员账号
- 登录校验
- 数据统计展示(菜谱数量、菜谱种类、用户数)
- 菜谱类别列表
- 新建、编辑菜谱类别
- 菜谱列表
- 菜谱详情(基本信息、食材、步骤)
- 新建、编辑菜谱
- 文章列表
- 新建、编辑文章
- 广告位管理
- 用户列表
- 管理员用户管理
个人全栈项目Food Corner,基于node-express-vue-mongodb的菜谱应用,含后台管理系统移动端网页
项目截图
项目总结
与上一个练手的王者荣耀移动端项目相比,在这个项目中我结合实际情况添加了新的功能,比如用户登录注册、登录后个人中心显示、用户发布菜谱、搜索菜谱等,不再是单纯的静态页面展示。
在页面设计上也引入一些未使用过的组件,如底部导航栏、横向滚动条、分类导航等,当然也遇到了非常多的问题。
组件:
-
横向滚动条
横向滚动条和垂直滚动条类似,换了一下滚动的方向,需要设置高度和计算得到所有元素的总宽度
<!--横向活动菜单--> <p class="fs-llg my-2 px-2"><i class="iconfont icon-icon-test1 mr-2"></i><strong>热门活动</strong></p> <div class="father_scroll d-flex" ref="father_scroll"> <div class="child_scroll" ref="child_scroll"> <router-link tag="div" v-for="item in articles" :key="item._id" class="mx-1" :to="`/article/detail/${item._id}`"> <div class="bg-grey-1" style="height:80px;border-radius:10px"> <img :src="item.banner" class="h-100" style="border-radius:10px"> </div> </router-link> </div> </div>created(){ //横向滚动条 this.$nextTick(() => {//回调横向滚动方法 this.personScroll(); });}, //计算出横向滚动组件需要的总宽度 GetNum(){ this.width=80*3; return this.width; }, personScroll() { this.$refs.child_scroll.style.width = this.GetNum() + "px"; this.$nextTick(() => { if (!this.scroll) { this.scroll = new BScroll(this.$refs.father_srcoll, { startX: 0, click: true, scrollX: true, //x方向滚动 scrollY: false, eventPassthrough: "vertical" }); } else { this.scroll.refresh(); } }); }, -
搜索框
点击右边的搜索图标进行查找
<!--搜索框--> <div class="d-flex jc-center my-1 pb-1"> <input class="search w-90 p-2 bg-grey-1 text-grey-2" placeholder=" 搜索食谱" v-model="searchMenu"/> <i class="iconfont icon-sousuo0101" style="margin-top:10px" @click="searchBtn"></i> </div> -
底部导航
底部导航的写法可以写成组件引入,也可以直接在Main.vue中写入,采用for循环出底部导航的每个item,点击时加上active效果
<div class="nav-item"
v-for="(item, index) of list"
:key="index"
:class="{active:index == num}"
@click="addClassName(index)">
<router-link tag="div" class="nav-link" :to="item.path">
<i v-show="num!=index" :class="item.icon" class="fs-ic"></i>
<i v-show="num==index" :class="item.active" class="fs-ic"></i>
<p>{{ item.name }}</p>
</router-link>
</div>
</div>
<router-view/> //内容展示
定义一个数组存放导航item的名称、图标、路由地址等
data() {
return {
num: 0,
list: [
{
icon: "iconfont icon-shouyeweixuanzhong", //原始显示的图标
active: "iconfont icon-shouyexuanzhong", //点击之后要显示的图标
name: "首 页",
path: "/home"
},
{
icon: "iconfont icon-guanzhuweixuanzhong",
active: "iconfont icon-guanzhuxuanzhong",
name: "社 区",
path: "/community"
},
{
icon: "iconfont icon-xinzeng",
active: "iconfont icon-xinzeng1",
name: "发 布",
path: "/add"
},
{
icon: "iconfont icon-fenlei",
active: "iconfont icon-fenlei1",
name: "分 类",
path: "/category"
},
{
icon: "iconfont icon-zhanghaoweixuanzhong",
active: "iconfont icon-zhanghaoxuanzhong",
name: "我 的",
path: "/personalcenter"
},
],
tabName: ""
};
},
-
分类导航
分类导航是一个单独的页面,左侧一级分类用ul li实现,右侧内容根据点击的一级菜单选择呈现的内容
效果图:
/*左侧一级菜单*/
<div class="left-nav">
<ul class="nav-title fs-lg">
<li v-for="(item,index) in tabParent"
:key="item._id" @click="get(index)"
:class="{active:cur==index}">
<h4>{{item.name}}</h4>
</li>
</ul>
</div>
/*右侧二级菜单*/
<div class="nav-content d-flex jc-start flex-wrap mt-5 ml-3" style="width:300px;height:300px;">
<div style="width:80px;text-align:center;" class="mt-3"
v-for="(item,index) in tabChildren"
:key="index">
//根据点击分类跳转显示对应类别的所有菜谱
<router-link tag="div" :to="`/menu/list/${item._id}`">
<img :src="item.icon" class=" " style="width:50px;height:50px">
<p class="fs-md">{{item.name}}</p>
</router-link>
</div>
</div>
功能:
- 登录注册、登陆跳转
<form @submit.prevent="login" style="text-align:center">
<input type="text" v-model="model.username" class="p-2" placeholder=" 请输入用户名">
<input type="password" v-model="model.password" class="p-2"
placeholder=" 请输入密码">
<button class="bg-primary m-3 py-2 fs-lg" type="submit">登 录</button>
</form>
触发时间后我们要获取登录名并显示在个人中心就需要使用localStorage
async login(){
const res=await this.$http.post('/login',this.model);
localStorage.token=res.data.token
//用户名存入localStorage,localStorage.getItem('username')获取用户名
localStorage.setItem('username', this.model.username);
this.$router.push(`/personalcenter`)
this.$message({
type:'success',
message:'登录成功'
})
}
}
-
搜索菜谱
searchBtn(){ var searchMenu = this.searchMenu; if (searchMenu) { this.searchData = this.menus.filter( value=>value.name.indexOf(this.searchMenu)!==-1 //查找菜谱 ) } this.$router.push({path: '/search/list', query: {name: this.searchData}}); }查询到的结果在SearchList.vue中以列表形式展示
-
发布菜谱
在已有用户登录情况下可以发布菜谱,否则会弹出登录页面;这里上传内容的方式和后台管理系统的类似,只不过很多用了原生的组件和方法,这里就不再多作解释,比较简单,可以看一下源码Add.vue
效果图:
令人抓狂的bug们:
-
引入cors写法
app.use(require('cors')()) -
查询parent分类
好几次在查询上级分类的时候发现只能请求到上级分类的id,在这个上面花了很多时间,发现忘了加上下面这个查询
if(req.Model.modelName==='Xxx'){//xxx是包含父类对象的模型名称 queryOptions.populate='parent' } -
使用静态资源(忘记加/),又是比较粗心的错误
app.use('/uploads',express.static(__dirname + '/uploads'))