原创

基于node.js和VUE的前后端分离智能客服商城毕业设计,附源码

今天给大家介绍一个前后端分离的商城项目,说到前后端分离,大家肯定以为后端是java一类的,但是今天的项目采用的是Node.js的express框架。

架构

前端:vue全家桶
后端: node:express框架
数据库:MySQL

基本功能
普通用户

注册、登录(图形验证码)
定位 (腾讯地图定位功能)、自主选择所在城市
商品
    分类
    简单展示商品
    查看商品详情
    商品评论
分页功能
购物车功能
    加入购物车
    购物车商品数量增减
    清空购物车
    商品结算
关键词模糊搜索商品 (关键词需为商品名称)
用户个人中心
查看订单
    修改用户信息 (头像、昵称、简介...)
    修改手机号
    修改密码
    联系客服

管理员

登录(固定账号密码:admin)
查看所有用户
查看数据库商品信息
商品上架(添加商品)
删除/修改商品
查看订单
分页功能
客服管理


部分代码展示

<template>
  <div id="container" v-if="goodsDetail[0]">
    <div class="pro_detail">
      <div class="pro_img">
        <div class="tb_booth">
          <img :src="goodsDetail[0].image_url" class="pro_middle_img" />
        </div>
      </div>
      <div class="pro_meg">
        <div class="pro_meg_hd">
          <h1>
            {{ goodsDetail[0].goods_name }}
          </h1>
        </div>
        <div class="pro_meg_price">
          <dl>
            <dt>促销价</dt>
            <dd>
              <div class="promo_price">
                <span class="tm-price">{{
                  (goodsDetail[0].price / 100) | moneyFormat
                }}</span>
                <b>优惠促销</b>
              </div>
            </dd>
          </dl>
          <dl>
            <dt>市场价</dt>
            <dd class="nor_price">
              {{ (goodsDetail[0].normal_price / 100) | moneyFormat }}
            </dd>
          </dl>
          <dl>
            <dt>本店优惠</dt>
            <dd>包邮</dd>
          </dl>
          <dl>
            <dt class="sale_tip">{{ goodsDetail[0].sales_tip }}</dt>
          </dl>
          <dl>
            <dt>服务承诺</dt>
            <dd>
              <span>正品保证</span>
              <span>极速退货</span>
            </dd>
          </dl>
        </div>
        <div class="pro_meg_deliver">
          <dl>
            <dt>运费</dt>
            <dd>
              福建福州&nbsp;&nbsp;至&nbsp;&nbsp;福建福州&nbsp;&nbsp;&nbsp;快递:0.00
            </dd>
          </dl>
        </div>
        <div class="pro_meg_console">
          <dl class="tb-sku">
            <dt>数量</dt>
            <dd>
              <div class="item-amout">
                <el-input-number
                  v-model="shopNum"
                  :min="1"
                  :max="goodsDetail[0].counts"
                ></el-input-number>
              </div>
              <span>库存</span><em>{{ goodsDetail[0].counts }}</em
              ><span>件</span>
            </dd>
          </dl>
          <div class="shopping_car">
            <el-button
              type="danger"
              @click.prevent="dealWithCellBtnClick(goodsDetail[0])"
              >加入购物车</el-button
            >
          </div>
        </div>
      </div>
    </div>
    <div class="pro_comment">
      <h3>商品评价</h3>
      <div v-if="goodsDetail[0].comments_count">
        <div
          class="media"
          v-for="(comment, index) in goodsComment"
          :key="index"
        >
          <div class="media-body">
            <h6 class="media-heading" v-if="comment.user_nickname">
              用户:&nbsp;{{ comment.user_nickname }}
            </h6>
            <h6 class="media-heading" v-else>
              用户:&nbsp;{{ comment.user_name | nameFormat }}
            </h6>
            <span>评论:</span>&nbsp;{{ comment.comment_detail }}
            <el-rate
              v-model="comment.comment_rating"
              disabled
              show-score
              text-color="#ff9900"
            >
            </el-rate>
          </div>
        </div>
      </div>
      <div class="media" v-else>
        <div class="media-body">本商品暂无评论</div>
      </div>
    </div>
    <div class="pro_judge" v-if="userInfo.user_name">
      <h3>评价该商品</h3>
      <span>为该商品评分</span>
      <el-rate
        v-model="rating"
        :colors="colors"
        show-score
        text-color="#ff9900"
      >
      </el-rate>
      <el-input
        type="textarea"
        autosize
        placeholder="请输入内容"
        v-model="textarea"
      >
      </el-input>
      <el-button type="primary" @click="post()"
        >发布<i class="el-icon-edit el-icon--right"></i
      ></el-button>
    </div>
    <div class="pro_judge" v-else>
      <h3>评价该商品</h3>
      <span class="judge_erro_tip">登录后才可发表评论</span>
    </div>
  </div>
</template>

<script>
import { postComment, addGoodsToCart } from "./../../api/index";
import { MessageBox } from "element-ui";
import { mapActions } from "vuex";
import { mapState } from "vuex";

export default {
  data() {
    return {
      textarea: "",
      rating: 0,
      colors: ["#99A9BF", "#F7BA2A", "#FF9900"],
      currentGoodsId: 0,
      shopNum: 1,
    };
  },
  computed: {
    ...mapState(["goodsDetail", "userInfo", "goodsComment"]),
  },
  created() {
    this.currentGoodsId = Number(this.$route.params.id);
    this.$store.dispatch("reqGoodsDetail", {
      goodsNo: this.currentGoodsId,
    });
    this.$store.dispatch("reqGoodsComment", {
      goodsId: this.currentGoodsId,
    });
  },
  watch: {
    $route() {
      this.currentGoodsId = Number(this.$route.params.id);
      this.$store.dispatch("reqGoodsDetail", {
        goodsNo: this.currentGoodsId,
      });
      this.$store.dispatch("reqGoodsComment", {
        goodsId: this.currentGoodsId,
      });
      // 请求当前用户信息
      this.$store.dispatch("getUserInfo");
    },
  },
  methods: {
    async post() {
      if (!this.textarea) {
        MessageBox({
          type: "info",
          message: "评论不得为空",
          showClose: true,
        });
        return;
      }
      let result = await postComment(
        this.goodsDetail[0].goods_id,
        this.textarea,
        this.rating,
        this.userInfo.id
      );
      if (result.success_code === 200) {
        MessageBox({
          type: "success",
          message: "发布成功",
          showClose: true,
        });
        this.textarea = "";
        this.$store.dispatch("reqGoodsComment", {
          goodsId: this.currentGoodsId,
        });
      } else {
        MessageBox({
          type: "info",
          message: "发布失败",
          showClose: true,
        });
      }
    },
    // 监听商品点击
    async dealWithCellBtnClick(goods) {
      // 1. 发送请求
      // user_id, goods_id, goods_name, thumb_url, price, buy_count, counts
      if (this.userInfo.user_name) {
        let result = await addGoodsToCart(
          this.userInfo.id,
          goods.goods_id,
          goods.short_name,
          goods.thumb_url,
          goods.price,
          this.shopNum,
          goods.counts
        );
        if (result.success_code === 200) {
          MessageBox({
            type: "success",
            message: result.message,
            showClose: true,
          });
          let user_id = this.userInfo.id;
          // 请求商品数据
          this.$store.dispatch("reqCartsGoods", { user_id });
          this.shopNum = 1;
        }
      }
    },
  },
};
</script>

<style scoped>
#container > .pro_detail {
  width: 990px;
  position: relative;
  z-index: 100;
  margin: 20px auto;
}
#container > .pro_comment {
  width: 73%;
  position: relative;
  margin: 40px auto 0;
  padding: 20px;
  border: 1px solid #ccc;
  border-bottom: none;
}
#container > .pro_judge {
  width: 73%;
  position: relative;
  margin: 0 auto 60px;
  padding: 20px;
  border-top: none;
  border: 1px solid #ccc;
}
.pro_judge > span {
  font-size: 12px;
  color: #ccc;
}
.pro_judge > .el-rate {
  display: inline-block;
}
.pro_judge .el-textarea {
  width: 50%;
  display: block;
  margin: 20px 0;
}
.pro_comment > h3 {
  font-weight: bold;
  margin-bottom: 20px;
}
.pro_comment .media {
  border-top: 1px dashed #ccc;
  padding: 10px 0;
}
.pro_comment .media .media-heading {
  margin-bottom: 10px;
  font-weight: bolder;
}
.pro_comment .media .media-body {
  font-size: 14px;
}
.pro_comment .media .media-body span {
  font-weight: bolder;
}
.pro_comment .el-rate {
  display: inline-block;
  margin-left: 20px;
}
.pro_judge > h3 {
  font-weight: bold;
  margin-bottom: 20px;
}
.pro_judge .judge_erro_tip {
  font-size: 15px;
  font-weight: bolder;
  color: #000;
}
.pro_detail > .pro_img {
  float: left;
  position: relative;
  padding: 100px 0;
  width: 480px;
  border: 1px solid #ccc;
}
.pro_img > .tb_booth {
  position: relative;
  z-index: 1;
}
.tb_booth > .pro_middle_img {
  width: auto;
  height: auto;
  max-width: 100%;
  max-height: 100%;
}
.pro_detail > .pro_meg {
  margin: 0 0 0 520px;
  color: #666;
  padding: 0 0 3px;
}
.pro_meg > .pro_meg_hd {
  padding: 0 10px 12px;
  color: #000;
}
.pro_meg_hd > h1 {
  font-size: 16px;
  font-weight: 700;
  color: #000;
}
.pro_meg > .pro_meg_price {
  padding: 5px 20px;
  height: 200px;
  background-color: rgba(247, 244, 244, 0.6);

  display: flex;
  flex-direction: column;
  justify-content: space-around;
}
.pro_meg_price dl {
  display: flex;
  align-items: center;

  margin-bottom: 0 !important;
  cursor: pointer;
}
.pro_meg_price dl dt {
  width: 70px;
  color: #999;
  font-size: 12px;
}
.pro_meg_price dl dd {
  margin-bottom: 0 !important;
  font-family: Arial;
}
.pro_meg_price dl dd div {
  display: flex;
  align-items: center;
}
.pro_meg_price dl:last-child dd {
  color: #ff0036;
  font-weight: bold;
  font-size: 12px;
}
.promo_price {
  line-height: 24px;
  vertical-align: middle;
  color: #ff0036;
  font-size: 18px;
  font-family: Arial;
  -webkit-font-smoothing: antialiased;
}
.promo_price b {
  display: inline-block;
  font-weight: normal;
}
.promo_price b:last-child {
  font-size: 12px;
  background: #f47a86;
  color: white;
  padding: 0 6px;
}
.promo_price > .tm-price {
  font-size: 20px;
  display: inline-block;
  margin-right: 12px;
  font-weight: bold;
}
.nor_price {
  text-decoration: line-through;
}
.sale_tip {
  color: #ff0036 !important;
  font-weight: bolder;
  width: 80px !important;
}
.pro_meg_deliver {
  margin: 5px 20px -15px 0;
  padding: 5px;
}
.pro_meg_deliver dl {
  padding: 5px;
  font-size: 14px;
  color: black;
  cursor: pointer;
}
.pro_meg_deliver dl dt {
  color: #999;
  font-size: 14px;
  text-align: left;
  width: 69px;
  margin: 0 0 0 8px;
  float: left;
}
.pro_meg_deliver dl dd {
  font-size: 13px;
}
.pro_meg_console {
  margin: 5px 20px 5px 0;
  padding: 5px;
}
.tb-sku {
  padding: 5px;
  font-size: 14px;
  color: black;
  cursor: pointer;
}
.tb-sku dt {
  color: #999;
  font-size: 14px;
  text-align: left;
  width: 69px;
  margin: 0 0 0 8px;
  float: left;
}
.tb-sku dd {
  font-size: 13px;
}
.tb-sku dd div {
  display: inline-block;
  margin-right: 20px;
}
.item-amout {
  height: 25px;
}
.item-amout a {
  display: inline-block;
  height: 23px;
  width: 17px;
  border: 1px solid #e5e5e5;
  background: #f0f0f0;
  text-align: center;
  line-height: 23px;
  color: #444;
  cursor: pointer;
}
.item-amout a:hover {
  color: #f50;
  border-color: #f60;
}
.item-amout > .text_amount {
  width: 40px;
  height: 20px;
  text-align: center;
  display: inline-block;
}
.shopping_car {
  margin: 20px auto 0;

  display: flex;
  justify-content: center;
}
.shopping_car button {
  outline: none;
}
</style>

演示视频链接

源码链接

正文到此结束
本文目录