Egg.js+Vant前后端实例

本文涉及的产品
RDS MySQL Serverless 基础系列,0.5-2RCU 50GB
RDS MySQL Serverless 高可用系列,价值2615元额度,1个月
RDS MySQL DuckDB 分析主实例,集群系列 8核16GB
简介: Egg.js+Vant前后端实例

前后端环境

$ node -v
v10.16.0

前端部分

1、项目环境

# 创建项目
cnpm install -g @vue/cli
vue create client && cd client
# 安装依赖
cnpm i vant -S  # vant
cnpm i babel-plugin-import -D
cnpm i vue-router --save  # 路由
cnpm i moment --save   # 时间处理
# 启动服务
npm run serve

2、项目目录

├── .babelrc   // 新建
├── src
│   ├── App.vue
│   ├── main.js
│   ├── router    // 新建
│   │   └── index.js
│   └── view      // 新建
│       ├── Add.vue
│       ├── Detail.vue
│       └── Home.vue
└── vue.config.js  // 新建

3、文件内容

.babelrc

{
  "plugins": [
    [
      "import",
      {
        "libraryName": "vant",
        "libraryDirectory": "es",
        "style": true
      }
    ]
  ]
}

vue.config.js

module.exports = {
    // 处理跨域请求
    devServer: {
        proxy: {
            '/article': {
                target: "http://127.0.0.1:7001/",
                ws: true, // 允许websockt服务
                changeOrigin: true // 开启虚拟服务器,请求代理服务器
            }
        }
    }
}

/src/router/index.js

import Vue from 'vue'
import VueRouter from 'vue-router'
Vue.use(VueRouter);
const router = new VueRouter({
    mode: 'hash',
    routes: [
        { path: '/', component: () => import('../view/Home.vue') },
        { path: '/detail', component: () => import('../view/Detail.vue') },
        { path: '/add', component: () => import('../view/Add.vue') }
    ]
})
export default router

/src/main.js

import Vue from 'vue'
import App from './App.vue'
import router from './router'
Vue.config.productionTip = false
new Vue({
  router,
  render: h => h(App),
}).$mount('#app')

/src/App.vue

<template>
  <div id="app">
    <router-view></router-view>
    <van-tabbar route>
      <van-tabbar-item replace to="/" icon="home-o">主页</van-tabbar-item>
      <van-tabbar-item replace to="/add" icon="plus">发布</van-tabbar-item>
    </van-tabbar>
  </div>
</template>
<script>
import { Tabbar, TabbarItem } from "vant";
export default {
  name: "app",
  components: {
    [Tabbar.name]: Tabbar,
    [TabbarItem.name]: TabbarItem,
  }
};
</script>
<style>
</style>

/src/view/Home.vue

<template>
  <div>
    <van-list v-model="loading" 
    :finished="finished" 
    finished-text="没有更多了" 
    @load="onLoad">
      <van-cell v-for="item in list" 
      :key="item.id" 
      @click="handleItemClick(item.id)">
        <div class="item">
          <div class="left">
            <img :src="item.image" />
          </div>
          <div class="right">
            <div class="title">{{item.title}}</div>
            <div class="create_time">{{item.create_time}}</div>
          </div>
        </div>
      </van-cell>
    </van-list>
  </div>
</template>
<script>
import { List, Cell, Toast } from "vant";
import moment from "moment";
export default {
  components: {
    [List.name]: List,
    [Cell.name]: Cell
  },
  data() {
    return {
      list: [],
      loading: false,
      finished: false
    };
  },
  methods: {
    handleItemClick(uid) {
      console.log(uid);
      this.$router.push({
        path: "/detail",
        query: {
          id: uid
        }
      });
    },
    onLoad() {
      fetch("/article/list")
        .then(res => res.json())
        .then(res => {
          if (res.status === 200) {
            this.loading = false;
            this.finished = true;
            // 处理返回的时间格式
            this.list = res.data.map(item => {
              if (item.create_time) {
                item.create_time = moment(item.create_time).format(
                  "YYYY-MM-DD HH:mm:ss"
                );
              }
              return item;
            });
          } else {
            Toast.fails(res.msg);
          }
        });
    }
  }
};
</script>
<style scoped>
.item {
  display: flex;
  flex-direction: row;
}
.item .left, .item img {
  height: 100px;
  width: 150px;
  border-radius: 10px;
}
.item .right {
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  margin-left: 15px;
}
.item .right .title {
  font-size: 18px;
}
.item .right .create_time {
  font-size: 12px;
  color: #9e9e9e;
}
</style>

/src/view/Detail.vue

<template>
  <div class="detail">
    <div class="title">{{detail.title}}</div>
    <div class="create-time">{{detail.create_time}}</div>
    <div class="summary">{{detail.summary}}</div>
    <div class="content">{{detail.content}}</div>
  </div>
</template>
<script>
import moment from "moment";
import { Toast } from 'vant';
export default {
  data() {
    return {
      detail: {}
    };
  },
  created() {
    fetch("/article/detail/" + this.$route.query.id)
      .then(res => res.json())
      .then(res => {
        if (res.status === 200) {
          this.detail = res.data;
          this.detail.create_time = this.detail.create_time
            ? moment(this.detail.create_time).format("YYYY-MM-DD HH:mm:ss")
            : undefined;
        } else {
          Toast.fail(res.msg);
        }
      });
  }
};
</script>
<style scoped>
.detail {
  padding: 20px;
  text-align: left;
}
.detail .title {
  font-size: 25px;
  margin-bottom: 20px;
}
.detail .create-time {
  text-align: right;
  color: #9e9e9e;
  margin-bottom: 20px;
}
.detail .summary {
  padding: 20px;
  background: #dcdcdc;
  margin-bottom: 20px;
}
.detail .content {
  text-indent: 2em;
  line-height: 200%;
}
</style>

/src/view/Add.vue

<template>
  <div>
    <van-uploader v-model="fileList" :after-read="afterRead" :max-count="1" />
    <van-cell-group>
      <van-field v-model="title" label="文章标题" placeholder="文章标题" />
      <van-field v-model="summary" label="文章摘要" placeholder="文章摘要" />
      <van-field v-model="content" label="文章内容" type='textarea' autosize placeholder="文章内容" />
    </van-cell-group>
    <van-button type="primary" @click="handleAdd" class="add-button">提交</van-button>
  </div>
</template>
<script>
import { Field, Button, CellGroup, Uploader, Toast } from 'vant';
export default {
  components: {
    [Field.name]: Field,
    [Button.name]: Button,
    [CellGroup.name]: CellGroup,
    [Uploader.name]: Uploader,
  },
  data() {
    return {
      fileList: [],
      title: "",
      summary: "",
      content: "",
      image: ""
    };
  },
  methods: {
    afterRead(file) {
      console.log(file);
      // this.image = file.content
    },
    handleAdd(){
      const data = {
        title: this.title,
        summary: this.summary,
        content: this.content,
        image: this.image,
      }
      fetch('/article/create', {
        method: 'post',
        headers: {
          'Content-type': 'application/json'
        },
        body: JSON.stringify(data)
      })
      .then(res=>res.json())
      .then(res=>{
        if(res.status===200){
          Toast.success("文章发布成功");
          this.$router.push('/');
        }else{
          Toast.fail(res.msg);
        }
      })
      console.log(data);
    }
  }
};
</script>
<style scoped>
.add-button{
  position: absolute;
  left: 0;
  bottom: 80px;
  width: 100%
}
</style>

后端部分

1、项目环境

# 项目环境
mkdir server && cd server
cnpm init egg --type=simple
cnpm i
cnpm i egg-view-ejs  # 模板
cnpm i --save egg-mysql  # 数据库
cnpm i moment --save   # 时间处理
# 启动服务
cnpm run dev

2、项目目录

.
├── app
│   ├── router.js
│   ├── controller
│   │   ├── article.js
│   ├── service
│   │   ├── article.js
├── config
│   ├── config.default.js
│   └── plugin.js

3、文件内容

config/plugin.js

'use strict';
// 模板引擎
exports.ejs = {
  enable: true,
  package: 'egg-view-ejs',
};
// mysql
exports.mysql = {
  enable: true,
  package: 'egg-mysql',
};

config/config.default.js

/* eslint valid-jsdoc: "off" */
'use strict';
/**
 * @param {Egg.EggAppInfo} appInfo app info
 */
module.exports = appInfo => {
  /**
   * built-in config
   * @type {Egg.EggAppConfig}
   **/
  const config = exports = {};
  // 关闭scrf
  config.security = {
    csrf: {
      enable: false,
    },
  };
  // 配置模板引擎
  config.view = {
    mapping: {
      '.html': 'ejs',
    },
  };
  // 配置数据库
  config.mysql = {
    // 单数据库信息配置
    client: {
      // host
      host: '127.0.0.1',
      // 端口号
      port: '3306',
      // 用户名
      user: 'root',
      // 密码
      password: 'aBc@123456',
      // 数据库名
      database: 'data',
    },
    // 是否加载到 app 上,默认开启
    app: true,
    // 是否加载到 agent 上,默认关闭
    agent: false,
  };
  // use for cookie sign key, should change to your own and keep security
  config.keys = appInfo.name + '_1570720226826_6549';
  // add your middleware config here
  config.middleware = [];
  // add your user config here
  const userConfig = {
    // myAppName: 'egg',
  };
  return {
    ...config,
    ...userConfig,
  };
};

app/router.js

'use strict';
/**
 * @param {Egg.Application} app - egg application
 */
module.exports = app => {
  const { router, controller } = app;
  router.post('/article/create', controller.article.create);
  router.get('/article/list', controller.article.list);
  router.get('/article/detail/:id', controller.article.detail);
};

app/service/article.js

'use strict';
const Service = require('egg').Service;
class ArticleService extends Service {
  async create(params) {
    const { app } = this;
    try {
      const result = await app.mysql.insert('article', params);
      return result;
    }
    catch (err) {
      console.log(err);
      return null;
    }
  }
  async list() {
    const { app } = this;
    try {
      // 查询所有数据
      const result = await app.mysql.select('article');
      return result;
    }
    catch (err) {
      console.log(err);
      return null;
    }
  }
  async detail(id) {
    const { app } = this;
    if(!id){
      console.log("id不能为空");
      return null;
    }
    try {
      // 查询数据
      const result = await app.mysql.get('article', {id});
      return result;
    }
    catch (err) {
      console.log(err);
      return null;
    }
  }
}
module.exports = ArticleService;

app/controller/article.js

'use strict';
const Controller = require('egg').Controller;
const moment = require('moment');
class ArticleController extends Controller {
  async create() {
    const { ctx } = this;
    const data = {
      ...ctx.request.body,
      create_time: moment().format('YYYY-MM-DD HH:mm:ss')
    }
    const res = await ctx.service.article.create(data);
    if (res) {
      ctx.body = {
        status: 200,
        data: res,
      }
    } else {
      ctx.body = {
        status: 500,
        data: null,
        msg: '发布失败'
      }
    }
  }
  async list() {
    const { ctx } = this;
    const res = await ctx.service.article.list();
    if (res) {
      ctx.body = {
        status: 200,
        data: res,
      }
    } else {
      ctx.body = {
        status: 500,
        data: null,
        msg: '文章列表获取失败'
      }
    }
  }
  async detail() {
    const { ctx } = this;
    const res = await ctx.service.article.detail(ctx.params.id);
    if (res) {
      ctx.body = {
        status: 200,
        data: res,
      }
    } else {
      ctx.body = {
        status: 500,
        data: null,
        msg: '文章列表获取失败'
      }
    }
  }
}
module.exports = ArticleController;

4、mysql建表语句

create table article(
    id int(11) primary key not null auto_increment,
    title varchar(255) not null default '' comment '文章标题',
    create_time timestamp default null comment '发布时间',
    summary varchar(255) not null default '' comment '文章简介',
    content text default null comment '文章内容',
    image text default null comment '文章图片'
) engine=InnoDB comment "文章表"


相关实践学习
每个IT人都想学的“Web应用上云经典架构”实战
本实验从Web应用上云这个最基本的、最普遍的需求出发,帮助IT从业者们通过“阿里云Web应用上云解决方案”,了解一个企业级Web应用上云的常见架构,了解如何构建一个高可用、可扩展的企业级应用架构。
MySQL数据库入门学习
本课程通过最流行的开源数据库MySQL带你了解数据库的世界。 &nbsp; 相关的阿里云产品:云数据库RDS MySQL 版 阿里云关系型数据库RDS(Relational Database Service)是一种稳定可靠、可弹性伸缩的在线数据库服务,提供容灾、备份、恢复、迁移等方面的全套解决方案,彻底解决数据库运维的烦恼。 了解产品详情:&nbsp;https://www.aliyun.com/product/rds/mysql&nbsp;
相关文章
|
6月前
|
缓存 监控 搜索推荐
电商生态协同的关键:API接口在电商数据对接中的应用与实践
电商数据对接API接口是连接电商平台与外部系统的智慧桥梁,通过标准化协议实现商品管理、订单处理、支付结算、物流追踪及数据分析等全链路支持。本文从核心功能、对接流程、应用场景和优化策略四个方面解析其技术逻辑与实践路径。API接口助力店铺管理自动化、精准营销与跨境电商全链路管理,同时通过安全防护、性能调优与合规管理提升效能,推动电商行业向智能化、高效化发展。
|
网络安全 Nacos 数据安全/隐私保护
nacos常见问题之使用默认用户名密码提示错误如何解决
Nacos是阿里云开源的服务发现和配置管理平台,用于构建动态微服务应用架构;本汇总针对Nacos在实际应用中用户常遇到的问题进行了归纳和解答,旨在帮助开发者和运维人员高效解决使用Nacos时的各类疑难杂症。
SAP MM 移动类型107和109之研究
SAP MM 移动类型107和109 之研究 采购订单收货的移动类型,最常见的当属101,102,122以及161。  103和105是一对,用来先收到供应商冻结库存(无价值),然后再用105收到自己的库存里。
4186 0
|
NoSQL Java Redis
Spring boot整合Redis实现发布订阅(超详细)
Redis发布订阅(pub/sub)是一种消息通信模式:发送者(pub)发送消息,订阅者(sub)接收信息。微信,微博,关注系统 Redis客户端可以订阅任意数量的频道
8720 0
Spring boot整合Redis实现发布订阅(超详细)
|
5月前
|
JSON 监控 API
亚马逊Amazon商品详情API接口解析,josn数据参考
亚马逊商品详情API接口助力开发者高效获取商品信息,返回结构清晰的JSON数据,涵盖价格、描述、图片等关键字段。本文详解API调用方法与JSON格式,助您快速掌握商品数据抓取技巧,提升开发效率,适用于电商、数据分析等领域。
|
11月前
|
网络协议 应用服务中间件 网络安全
Nginx,正向代理
本文介绍了Nginx作为HTTPS正向代理的两种方案:HTTP CONNECT隧道(7层)和NGINX stream(4层)。HTTP CONNECT隧道需要客户端手动配置代理,通过CONNECT请求建立隧道;而NGINX stream则更适合透明代理,利用SNI字段实现流量转发。文章详细讲解了两者的原理、环境搭建、使用场景及常见问题,并提供了配置示例和最佳实践建议。内容转载自阿里云开发者社区@怀知的文章,推荐读者参阅原文获取更多信息。感谢您的阅读!
1450 80
Nginx,正向代理
|
数据采集 监控 数据挖掘
CSV文件自动化生成:用Pandas与Datetime高效处理京东商品信息
在电商竞争激烈的背景下,实时掌握商品价格和库存信息至关重要。本文介绍如何使用Python的`pandas`和`datetime`库从京东抓取商品名称、价格等信息,并生成CSV文件。结合代理IP技术,提升爬取效率和稳定性。通过设置请求头、使用代理IP和多线程技术,确保数据抓取的连续性和成功率。最终,数据将以带时间戳的CSV文件形式保存,方便后续分析。
444 2
|
Python
Python实用记录(十六):PyQt/PySide6联动VSCode便捷操作指南
本文提供了一份详细的PySide6与VSCode联动的操作指南,包括安装配置VSCode、安装必要的扩展、配置扩展以及编辑和运行PySide6项目。文中还提到了相关工具如uic.exe、rcc.exe和designer.exe的用途,并提供了进一步学习的资源。
1917 1
Python实用记录(十六):PyQt/PySide6联动VSCode便捷操作指南
|
9月前
|
设计模式 人工智能 JSON
一文掌握大模型提示词技巧:从战略到战术(一)
一文掌握大模型提示词技巧:从战略到战术
740 5
|
网络协议 算法 网络架构
PPP协议
PPP协议
1040 1
PPP协议

热门文章

最新文章