var userInfo = {"openId":"oQmbb4sNZdxaUQZ0sfYgvtOP2S7c","nickName":"何玉硕","gender":1,"language":"zh_CN","city":"Changping","province":"Beijing","country":"China","avatarUrl":"https://wx.qlogo.cn/mmopen/vi_32/Q0j4TwGTfTIbWFEIJj8IpGeHM7dGic1aTFZALjWcMm9ltWfFiaQfVRYticWBfgGfzXWMt2EkJWiaicPtftHAlWxUibxQ/132","watermark":{"timestamp":1535513485,"appid":"wx601ce71bde7b9add"}};
var openId = userInfo.openId;
wx.setStorageSync("userInfo", userInfo);
wx.setStorageSync("openId", openId);
一直打算自己写接口,写一个上线的小程序,数据方面总是无从下手,无意中发现一个网友爬取的网易严选商城的一些数据大概一共有 20 张左右的表,算是相当详细了(对其中部分表的字段和部分数据进行了修改,)平时写项目大部分用的 vue,所以直接选择了 mpvue 开发,后端一开始打算用 php 但是学了半个月感觉需要学的东西太多,短时间里写不出这个线上小程序,最后决定用 node 来开发提供接口。
请求封装
const host = 'https://www.heyuhsuo.xyz/heyushuo';
export {
host
}
//请求封装
function request(url, method, data, header = {}) {
wx.showLoading({
title: '加载中' //数据请求前loading
})
return new Promise((resolve, reject) => {
wx.request({
url: host + url, //仅为示例,并非真实的接口地址
method: method,
data: data,
header: {
'content-type': 'application/json' // 默认值
},
success: function (res) {
wx.hideLoading();
resolve(res.data)
},
fail: function (error) {
wx.hideLoading();
reject(false)
},
complete: function () {
wx.hideLoading();
}
})
})
}
export function get(url, data) {
return request(url, 'GET', data)
}
export function post(url, data) {
return request(url, 'POST', data)
}
用户是否登录 未登录跳转到登录页面
export function toLogin() {
const userInfo = wx.getStorageSync('userInfo');
if (!userInfo) {
wx.navigateTo({
url: "/pages/login/main"
});
} else {
return true;
}
}
export function login() {
const userInfo = wx.getStorageSync('userInfo');
if (userInfo) {
return userInfo;
}
}
export function getStorageOpenid() {
const openId = wx.getStorageSync("openId");
if (openId) {
return openId;
} else {
return ''
}
}
首页code
<template>
<div class="index">
<div class="search">
<div @click="toMappage">{{cityName}}div>
<div @click="toSearch">
<input type="text" placeholder="搜索商品">
<span class="icon">span>
div>
div>
<div class="swiper">
<swiper class="swiper-container" indicator-dots="true" autoplay="true" interval="3000" circular="true" duration="500">
<block v-for="(item, index) in banner " :key="index">
<swiper-item class="swiper-item">
<image :src="item.image_url" class="slide-image" />
swiper-item>
block>
swiper>
div>
<div class="channel">
<div @click="categoryList(item.id)" v-for="(item, index) in channel" :key="index">
<img :src="item.icon_url" alt="">
<p>{{item.name}}p>
div>
div>
<div class="brand">
<div @click="tobrandList" class="head">
品牌制造商直供
div>
<div class="content">
<div @click="branddetail(item.id)" v-for="(item, index) in brandList" :key="index">
<div>
<p>{{item.name}}p>
<p>{{item.floor_price}}元起p>
div>
<img :src="item.new_pic_url" alt="">
div>
div>
div>
<div class="newgoods">
<div @click="goodsList('new')" class="newgoods-top">
<div class="top">
<p>新品首发p>
<p>查看全部p>
div>
div>
<div class="list">
<ul>
<scroll-view class="scroll-view" :scroll-x="true">
<li @click="goodsDetail(item.id)" v-for="(item, index) in newGoods" :key="index">
<img :src="item.list_pic_url" alt="">
<p>{{item.name}}p>
<p>{{item.goods_brief}}p>
<p>¥{{item.retail_price}}p>
li>
scroll-view>
ul>
div>
div>
<div class="newgoods hotgoods">
<div @click="goodsList('hot')" class="newgoods-top">
<div class="top">
<p>人气推荐
<span>span> 好物精选p>
<p>查看全部p>
div>
div>
<div class="list">
<ul>
<scroll-view class="scroll-view" :scroll-x="true">
<li @click="goodsDetail(item.id)" v-for="(item, index) in hotGoods" :key="index">
<img :src="item.list_pic_url" alt="">
<p>{{item.name}}p>
<p>{{item.goods_brief}}p>
<p>¥{{item.retail_price}}p>
li>
scroll-view>
ul>
div>
div>
<div class="topicList">
<div @click="totopic" class="topicList-top">
专题精选
<span class="icon">span>
div>
<div class="list">
<ul>
<scroll-view class="scroll-view" :scroll-x="true">
<li @click="topicdetail(item.id)" v-for="(item, index) in topicList" :key="index">
<img :src="item.item_pic_url" alt="">
<div class="btom">
<div>
<p>{{item.title}}p>
<p>{{item.subtitle}}p>
div>
<div>
{{item.price_info}}元起
div>
div>
li>
scroll-view>
ul>
div>
div>
<div class="newcategory">
<div class="list" v-for="(item, index) in newCategoryList" :key="index">
<div class="head">{{item.name}}好物div>
<div class="sublist">
<div @click="goodsDetail(subitem.id)" v-for="(subitem, subindex) in item.goodsList" :key="subindex">
<img :src="subitem.list_pic_url" alt="">
<p>{{subitem.name}}p>
<p>¥{{subitem.retail_price}}p>
div>
<div @click="categoryList(item.id)">
<div class="last">
<p>{{item.name}}好物p>
<span class="icon">span>
div>
div>
div>
div>
div>
div>
template>
<script>
import amapFile from "../../utils/amap-wx";
import { get } from "../../utils";
import { mapState, mapMutations } from "vuex";
export default {
onLoad() {
this.getCityName();
},
onShow() {
if (wx.getStorageSync("cityName")) {
this.cityName = wx.getStorageSync("cityName");
wx.removeStorageSync("cityName");
}
},
computed: {
...mapState(["cityName"])
},
mounted() {
this.getData();
},
data() {
return {
banner: [],
channel: [],
brandList: [],
newGoods: [],
hotGoods: [],
topicList: [],
newCategoryList: []
};
},
components: {},
methods: {
...mapMutations(["update"]),
toMappage() {
var _this = this;
// 可以通过 wx.getSetting 先查询一下用户是否授权了 "scope.record" 这个 scope
wx.getSetting({
success(res) {
//如果没有同意授权,打开设置
if (!res.authSetting["scope.userLocation"]) {
wx.openSetting({
success: res => {
_this.getCityName();
}
});
} else {
wx.navigateTo({
url: "/pages/mappage/main"
});
}
}
});
},
getCityName() {
var _this = this;
var myAmapFun = new amapFile.AMapWX({
key: "e545e7f79a643f23aef187add14e4548"
});
myAmapFun.getRegeo({
success: function(data) {
//成功回调
console.log(data);
// data[0].regeocodeData.formatted_address
_this.cityName = data[0].regeocodeData.formatted_address;
_this.update({ cityName: data[0].regeocodeData.formatted_address });
},
fail: function(info) {
//失败回调
console.log(info);
//如果用户拒绝授权
// 默认为北京
_this.cityName = "北京市";
_this.update({ cityName: "北京市" });
}
});
},
toSearch() {
wx.navigateTo({
url: "/pages/search/main"
});
},
async getData() {
const data = await get("/index/index");
this.banner = data.banner;
this.channel = data.channel;
this.brandList = data.brandList;
this.newGoods = data.newGoods;
this.hotGoods = data.hotGoods;
this.topicList = data.topicList;
this.newCategoryList = data.newCategoryList;
},
goodsDetail(id) {
wx.navigateTo({
url: "/pages/goods/main?id=" + id
});
},
categoryList(id) {
wx.navigateTo({
url: "/pages/categorylist/main?id=" + id
});
},
goodsList(info) {
if (info == "hot") {
wx.navigateTo({
url: "/pages/newgoods/main?isHot=" + 1
});
} else {
wx.navigateTo({
url: "/pages/newgoods/main?isNew=" + 1
});
}
},
topicdetail(id) {
wx.navigateTo({
url: "/pages/topicdetail/main?id=" + id
});
},
totopic() {
wx.navigateTo({
url: "/pages/topic/main"
});
},
tobrandList() {
wx.navigateTo({
url: "/pages/brandlist/main"
});
},
branddetail(id) {
wx.navigateTo({
url: "/pages/branddetail/main?id=" + id
});
}
},
created() {}
};
script>
<style lang='scss' scoped>
@import "./style.scss";
style>
搜索功能页
<template>
<div class="search">
<div class="head">
<div>
<img src="http://nos.netease.com/mailpub/hxm/yanxuan-wap/p/20150730/style/img/icon-normal/search2-2fb94833aa.png" alt="">
<input type="text" confirm-type="search" focus="true" v-model="words" @focus="inputFocus" @input="tipsearch" @confirm="searchWords"
placeholder="商品搜索">
<img @click="clearInput" class="del" src="http://nos.netease.com/mailpub/hxm/yanxuan-wap/p/20150730/style/img/icon-normal/clearIpt-f71b83e3c2.png"
alt="">
div>
<div @click="cancel">取消div>
div>
<div class="searchtips" v-if="words">
<div @click="searchWords" v-if="tipsData.length!=0" :data-value="item.name" v-for="(item,index) in tipsData" :key="index">
{{ item.name }}
div>
<div v-if="tipsData.length==0" class="nogoods">
数据库暂无此类商品...
div>
div>
<div class="history" v-if="historyData.length!=0">
<div class="t">
<div>历史记录div>
<div @click="clearHistory">
div>
div>
<div class="cont">
<div @click="searchWords" :data-value="item.keyword" v-for="(item,index) in historyData" :key="index">
{{item.keyword}}
div>
div>
div>
<div class="history hotsearch">
<div class="t">
<div>热门搜索div>
div>
<div class="cont">
<div @click="searchWords" v-for="(item,index) in hotData" :data-value="item.keyword" :class="{active:0==index}" :key="index">
{{item.keyword}}
div>
div>
div>
<div v-if="listData.length!=0" class="goodsList">
<div class="sortnav">
<div @click="changeTab(0)" :class="[0==nowIndex ?'active':'']">综合div>
<div @click="changeTab(1)" class="price" :class="[1==nowIndex ?'active':'', order =='desc'? 'desc':'asc']">价格div>
<div @click="changeTab(2)" :class="[2==nowIndex ?'active':'']">分类div>
div>
<div class="sortlist">
<div @click="goodsDetail(item.id)" v-for="(item, index) in listData" :key="index" :class="[(listData.length)%2==0?'active':'none']"
class="item">
<img :src="item.list_pic_url" alt="">
<p class="name">{{item.name}}p>
<p class="price">¥{{item.retail_price}}p>
div>
div>
div>
div>
template>
<script>
import {
post,
get
} from "../../utils";
export default {
onLoad() {
this.initData();
},
created() {},
mounted() {
this.openid = wx.getStorageSync("openid") || "";
this.getHotData();
},
data() {
return {
nowIndex: 0,
words: "",
historyData: [],
hotData: [],
tipsData: [],
listData: [],
openid: "",
order: "",
isHot: "",
isNew: ""
};
},
components: {},
methods: {
initData() {
this.nowIndex = 0
this.words = ""
this.historyData = []
this.hotData = []
this.tipsData = []
this.listData = []
this.openid = ""
this.order = ""
this.isHot = ""
this.isNew = ""
},
goodsDetail(id) {
wx.navigateTo({
url: "/pages/goods/main?id=" + id
});
},
cancel() {
wx.navigateBack({
delta: 1 //返回的页面数,如果 delta 大于现有页面数,则返回到首页,
});
},
clearInput() {
this.words = "";
this.listData = [];
this.tipsData = [];
},
inputFocus() {
//商品清空
this.listData = [];
//展示搜索提示信息
this.tipsearch();
},
async getlistData() {
//获取商品列表
const data = await get("/search/helperaction", {
keyword: this.words,
order: this.order
});
this.listData = data.keywords;
this.tipsData = [];
},
changeTab(index) {
this.nowIndex = index;
if (index == 1) {
this.order = this.order == "asc" ? "desc" : "asc";
} else {
this.order = "";
}
this.getlistData();
},
async clearHistory() {
const data = await post("/search/clearhistoryAction", {
openId: this.openid
});
console.log(data);
if (data) {
this.historyData = [];
}
},
async searchWords(e) {
var vaule = e.currentTarget.dataset.value;
this.words = vaule || this.words;
const data = await post("/search/addhistoryaction", {
openId: this.openid,
keyword: vaule || this.words
});
console.log(data);
//获取历史数据
this.getHotData();
//获取商品列表
this.getlistData();
},
async getHotData(first) {
const data = await get("/search/indexaction?openId=" + this.openid);
this.hotData = data.hotKeywordList;
this.historyData = data.historyData;
},
async tipsearch(e) {
const data = await get("/search/helperaction", {
keyword: this.words
});
this.tipsData = data.keywords;
},
topicDetail(id) {
wx.navigateTo({
url: "/pages/topicdetail/main?id=" + id
});
}
},
computed: {}
};
script>
<style lang='scss' scoped>
@import "./style";
style>
分类页面
<template>
<div class="category">
<div class="search" @click="tosearch">
<div class="ser">
<span class="icon">span>
<span>商品搜索,共239款好物span>
div>
div>
<div class="content">
<scroll-view class="left" scroll-y="true">
<div class="iconText" @click="selectitem(item.id,index)" v-for="(item, index) in listData" :class="[index==nowIndex?'active':'']" :key="index">
{{item.name}}
div>
scroll-view>
<scroll-view class="right" scroll-y="true">
<div class="banner">
<img :src="detailData.banner_url" alt="">
div>
<div class="title">
<span>—span>
<span>{{detailData.name}}分类span>
<span>—span>
div>
<div class="bottom">
<div @click="categoryList(item.id)" v-for="(item,index) in detailData.subList" :key="index" class="item">
<img :src="item.wap_banner_url" alt="">
<span>{{item.name}}span>
div>
div>
scroll-view>
div>
div>
template>
<script>
import { get } from "../../utils";
export default {
created() {},
mounted() {
//获取列表数据
this.getListData();
//获取默认右侧数据
this.selectitem(this.id, this.nowIndex);
},
data() {
return {
id: "1005000",
nowIndex: 0,
listData: [],
detailData: {}
};
},
components: {},
methods: {
tosearch() {
wx.navigateTo({ url: "/pages/search/main" });
},
async selectitem(id, index) {
this.nowIndex = index;
const data = await get("/category/currentaction", {
id: id
});
this.detailData = data.data.currentOne;
},
async getListData() {
const data = await get("/category/indexaction");
this.listData = data.categoryList;
},
categoryList(id) {
console.log("tiaozhuan");
wx.navigateTo({
url: "../categorylist/main?id=" + id
});
}
},
computed: {}
};
script>
<style lang='scss' scoped>
@import "./style";
style>
购物车页面
<template>
<div class="cart">
<div class="top">
<div>30天无忧退货div>
<div>48小时快速退款div>
<div>满88元免邮费div>
div>
<div class="cartlist">
<div class="item" @touchstart="startMove" @touchmove="deleteGoods" @touchend="endMove" :data-index="index" v-for="(item,index) in listData"
:key="index">
<div class="con" :style="item.textStyle">
<div class="left">
<div class="icon" @click="changeColor(index,item.goods_id)" :class="[ Listids[index] ? 'active' : '',{active:allcheck}]">div>
<div class="img">
<img :src="item.list_pic_url" alt="">
div>
<div class="info">
<p>{{item.goods_name}}p>
<p>¥{{item.retail_price}}p>
div>
div>
<div class="right">
<div class="num">
x{{item.number}}
div>
div>
div>
<div @click="delGoods(item.id,index)" class="delete" :style="item.textStyle1">
<div>
删除
div>
div>
div>
div>
<div v-if="false" class="nogoods">
<img src="http://nos.netease.com/mailpub/hxm/yanxuan-wap/p/20150730/style/img/icon-normal/noCart-a8fe3f12e5.png" alt="">
div>
<div class="fixed">
<div @click="allCheck" :class="{active:allcheck}" class="left">
全选({{isCheckedNumber}})
div>
<div class="right">
<div>
¥{{allPrise}}
div>
<div @click="orderDown">下单div>
div>
div>
div>
template>
<script>
import {
get,
post,
login,
getStorageOpenid
} from "../../utils";
export default {
onShow() {
this.openId = getStorageOpenid();
this.getListData();
},
created() {},
data() {
return {
openId: "",
allcheck: false,
listData: [],
Listids: [],
userInfo: {},
tranX: 0,
tranX1: 0,
startX: "",
startY: "",
moveX: "",
moveY: "",
moveEndX: "",
moveEndY: "",
X: 0,
Y: ""
};
},
components: {},
methods: {
initTextStyle() {
//滑动之前先初始化数据
for (var i = 0; i < this.listData.length; i++) {
this.listData[i].textStyle = "";
this.listData[i].textStyle1 = "";
}
},
startMove(e) {
this.initTextStyle();
this.startX = e.touches[0].pageX;
this.startY = e.touches[0].pageY;
},
deleteGoods(e) {
//滑动之前先初始化样式数据
this.initTextStyle();
var index = e.currentTarget.dataset.index;
console.log(index);
if (this.X <= -100) {
this.flag = true;
}
if (!this.flag) {
this.moveX = e.touches[0].pageX;
this.moveY = e.touches[0].pageY;
this.X = this.moveX - this.startX;
this.Y = this.moveX - this.startY;
this.listData[index].textStyle = `transform:translateX(${this.tranX}rpx);`;
if (this.X >= 100) {
this.X = 0;
}
this.tranX = this.X;
if (this.X <= -100) {
this.X = -100;
}
this.tranX1 = this.X;
this.listData[index].textStyle1 = `transform:translateX(${this.tranX1}rpx);`;
} else {
this.moveX = e.touches[0].pageX;
this.moveY = e.touches[0].pageY;
this.X = this.moveX - this.startX;
this.Y = this.moveX - this.startY;
this.tranX = this.X - 100;
this.listData[index].textStyle = `transform:translateX(${this.tranX}rpx);`;
// transform:'translateX(' + tranX + 'rpx)'
console.log("heyushuo");
console.log(this.listData[index].textStyle);
if (this.X + -100 > -100) {
this.flag = false;
}
this.tranX1 = -100;
this.listData[index].textStyle1 = `transform:translateX(-100rpx);`;
console.log(this.listData[index].textStyle1);
// this.listData = this.listData;
}
// if (Math.abs(this.X) > Math.abs(this.Y) && this.X > 20) {
// this.scrollflag = false;
// } else if (Math.abs(this.X) > Math.abs(this.Y) && this.X < 20) {
// console.log("right 2 left");
// }
},
endMove(e) {
var index = e.currentTarget.dataset.index;
if (this.X > -50) {
this.tranX1 = 0;
this.tranX = 0;
this.listData[index].textStyle = `transform:translateX(${this.tranX}rpx);`;
this.listData[index].textStyle1 = `transform:translateX(${this.tranX1}rpx);`;
} else if (this.X <= -50) {
this.tranX1 = -100;
this.tranX = -100;
this.listData[index].textStyle = `transform:translateX(${this.tranX}rpx);`;
this.listData[index].textStyle1 = `transform:translateX(${this.tranX1}rpx);`;
}
},
async orderDown() {
if (this.Listids.length == 0) {
wx.showToast({
title: "请选择商品",
icon: "none",
duration: 1500
});
return false;
}
// 去掉数组中空的false的
var newgoodsid = [];
for (let i = 0; i < this.Listids.length; i++) {
const element = this.Listids[i];
if (element) {
newgoodsid.push(element);
}
}
var goodsId = newgoodsid.join(",");
const data = await post("/order/submitAction", {
goodsId: goodsId,
openId: this.openId,
allPrise: this.allPrise
});
if (data) {
wx.navigateTo({
url: "/pages/order/main"
});
}
},
async delGoods(id, index) {
var _this = this;
wx.showModal({
title: "",
content: "是否要删除该商品",
success: function (res) {
if (res.confirm) {
_this.Listids.splice(index, 1);
const data = get("/cart/deleteAction", {
id: id
}).then(() => {
_this.getListData();
});
} else if (res.cancel) {
console.log("用户点击取消");
//滑动之前先初始化样式数据
_this.initTextStyle();
}
}
});
},
async getListData() {
const data = await get("/cart/cartList", {
openId: this.openId
});
for (var i = 0; i < data.data.length; i++) {
data.data[i].textStyle = "";
data.data[i].textStyle1 = "";
}
this.listData = data.data;
},
allCheck() {
//先清空
this.Listids = [];
if (this.allcheck) {
this.allcheck = false;
} else {
console.log("选择全部");
this.allcheck = true;
//循环遍历所有的商品id
for (let i = 0; i < this.listData.length; i++) {
const element = this.listData[i];
this.Listids.push(element.goods_id);
}
}
},
change(e) {},
changeColor(index, id) {
if (this.Listids[index]) {
this.$set(this.Listids, index, false);
} else {
this.$set(this.Listids, index, id);
}
}
},
computed: {
isCheckedNumber() {
let number = 0;
for (let i = 0; i < this.Listids.length; i++) {
if (this.Listids[i]) {
number++;
}
}
if (number == this.listData.length && number != 0) {
this.allcheck = true;
} else {
this.allcheck = false;
}
return number;
},
allPrise() {
var Prise = 0;
for (let i = 0; i < this.Listids.length; i++) {
if (this.Listids[i]) {
Prise = Prise + this.listData[i].retail_price * this.listData[i].number;
}
}
return Prise;
}
}
};
script>
<style lang='scss' scoped>
@import "./style";
style>
前端:小程序、mpvue、async、await
后端:Node、koa2、mysql、knex.js 操作数据库,可视化工具使用的 Navicat
npm install 下载依赖
npm run dev 运行项目
这里部分项目的接口都可以访问了,但是登录接口不可以,只有本地搭建一套才可以使用登录接口
因为你用的自己的Appi打开微信开发者工具,无法调用我这边的登录,我后台默认是自己的Appid
IT之家小程序版客户端(使用 Mpvue 开发,兼容 Web)ithome-lite-master.zip
mpvue 仿网易严选mpvue-shop-master.zip
mpvue-音乐播放器mpvue-music-master.zip
mpvue性能测试与体验miniweibo-master.zip
mpvue改造的日历.zip
mpvue框架仿滴滴出行didi-master.zip
mpVue高仿美团小程序教程mpvue-meituan-master.zip
uni APP自动更新并安装.vue
uni-app nvue沉浸式状态栏(线性渐变色).vue
uni-app 二维码生成器分享wxqrcode.zip
uni-app 侧边导航分类,适合商品分类页面uni-app-left-navigation-master.zip
uni-app 自定义底部导航栏uni-app-bottom-navigation-master.zip
uni-app全局变量的几种实现方式.zip
uni-app的markdown富文本编辑器插件uniapp-markdown-master.zip
uni-app自定义导航栏title-custom.zip
uniapp聊天实例,支持图片,语音,表情.zip
uniapp选择器,包含一级,二级级联,三级级联uniapp-picker-master.zip
vue-mpvue-ChatRobot聊天机器人vue-mpvue-ChatRobot-master.zip
【小程序】CNode社区mpvue-cnode-master.zip
【插件、图表】7种图表漂亮丰富uniCharts.zip
一款播课类小程序, 基于 mpvue 构建mp-podcast-mpvue-master.zip
云档新版小程序端,基于mpvue开发cloud-doc-v2-master.zip
仿uc浏览器列表.vue
仿扎克新闻mpZAKER-master.zip
仿网易云UImusic播放器mpvue-music-master.zip
仿追书神器的小说阅读器小程序wx-book-master.zip
参照米家APP布局和样式,编写的一款智能家居小程序smart-home-master.zip
商城实例mpvue-xbyjShop-master.zip
基于 mpvue 实现豆瓣电影微信小程序mpvue-douban-master.zip
基于mpvue的优酷mpvue-youku-master.zip
校园助手示例SHUhelper-master.zip
类似mui中的chat(聊天窗口)实现uniapp-chat-master.zip
美团外卖(第三方)开源程序mpvue-master.zip
美食搜索mpvue-FG-master.zip
豆瓣平分mpvue-douban-pingfen-master.zip
顶部tabbar.vue
如果本项目对您有帮助,欢迎 “点赞,关注” 支持一下 谢谢~