Commit e603d9c2 authored by June's avatar June

feat:首页,详情页,地址、分类、商品、搜索等接口对接

parent 5861d4d9
......@@ -10,29 +10,28 @@ export default {
this.setMenuButtonInfo() // 获取胶囊信息
this.checkUpdate() // 检查更新
const extConfig = wx.getExtConfigSync ? wx.getExtConfigSync(): {}
this.$store.state.user.appid = extConfig.appid
const token = this.$getStorage('token')
this.$store.state.user.token = token
this.getStoreInfo()
// wx.login
login_wx(async () => {
const token = await this.$getStorage('token')
if(!token) {
uni.navigateTo({
url: '/pages/login/index'
})
} else {
this.setToken(token)
}
})
uni.getExtConfig({
success: res => {
console.log(res)
}
})
login_wx()
// uni.getExtConfig({
// success: res => {
// console.log(res)
// }
// })
},
methods: {
...mapActions('systemInfo', ['setSystemInfo', 'setMenuButtonInfo']),
...mapActions('user', ['setLocation', 'setToken', 'setLoginStatus']),
...mapActions('user', ['setLocation', 'setToken', 'setLoginStatus', 'getStoreInfo']),
...mapActions('cart', ['setCount']),
// 检查更新
checkUpdate() {
if (wx.canIUse('getUpdateManager')) {
......
......@@ -3,46 +3,53 @@ import { request } from "@/lib/service"
import { baseUrl } from '@/config'
/**
* @desc 获取地址
* @desc 收货地址列表接口
* @param { Number } * 当前页码
* @param { String } 关键字:联系人/联系方式搜索
* @param { pagenum } pagenum 每页条数,默认10条/页
*/
export function addressList() {
export function addressList(params) {
return request({
url: `${baseUrl}/a`
url: 'zsxcx/addressXcx.htm',
method: 'post',
data: params
})
}
/**
* @desc 地址详情
* @desc 默认收货地址接口
*/
export function addressList() {
export function defaultAddress() {
return request({
url: `${baseUrl}/a`
url: `zsxcx/getDefaultAddressXcx.htm`,
method: 'post'
})
}
/**
* @desc 编辑地址
* @desc 新增/编辑地址
* @params { } look docs
*/
export function editAddress() {
export function editAddress(params) {
return request({
url: `${baseUrl}/a`
url: 'zsxcx/saveAddressXcx.htm',
method: 'post',
data: params,
needMask: true
})
}
/**
* @desc 删除地址
* @params { Number } address_id
*/
export function delAddress() {
export function delAddress(address_id) {
return request({
url: `${baseUrl}/a`
url: 'zsxcx/delAddressXcx.htm',
method: 'post',
data: {
address_id
},
needMask: true
})
}
/**
* @desc 添加地址
*/
export function addAddress() {
return request({
url: `${baseUrl}/a`
})
}
\ No newline at end of file
......@@ -3,17 +3,26 @@ import { baseUrl } from '@/config'
/**
* @desc 获取分类列表
* @param { Number } search_type
* @param { Number } search_type_id
*
*/
export function getCatetories(search_type, search_type_id) {
export function getCatetories() {
return request({
url: `${baseUrl}/wxxcx/searchCategory.htm`,
url: 'zsxcx/shopGoodsCategory.htm',
method: 'post'
})
}
/**
* @desc 店铺商品列表接口
* @param { String } keywords 关键字:商品名称
* @param { Number } cat_id * 分类ID
*/
export function cateGoodsList(cat_id, keywords = '') {
return request({
url: 'zsxcx/shopGoods.htm',
method: 'post',
data: {
search_type,
search_type_id
cat_id,
keywords
}
})
}
......@@ -2,15 +2,58 @@ import { request } from "@/lib/service"
import { baseUrl } from '@/config'
/**
* @desc 城市三级联动
* @desc 四级联动-省市区街道
* @param { Number } code // city code => default 1
*/
export function queryCity(code = '1') {
return request({
url: `${baseUrl}/wxxcx/areaLinkXcx.htm`,
url: 'zsxcx/areaLinkXcx.htm',
method: 'post',
data: {
code
}
})
}
/**
* @desc 浏览历史接口
* @param { String } last_time 上一次查询时间(yyyy-MM-dd)
*/
export function historyList() {
return request({
url: 'zsxcx/browse.htm',
method: 'post',
data: {
last_time
},
needMask: true
})
}
/**
* @desc 删除一条浏览历史接口
* @param { Number } bro_id 浏览历史ID
* @param { Number } goods_id 商品ID
*/
export function delHistory() {
return request({
url: 'zsxcx/delBrowse.htm',
method: 'post',
data: {
bro_id,
goods_id
},
needMask: true
})
}
/**
* @desc 获取店铺信息
*/
export function getStoreInfo() {
return request({
url: 'zsxcx/shopDetail.htm',
method: 'post'
})
}
\ No newline at end of file
......@@ -8,18 +8,16 @@ import { baseUrl } from '@/config'
* {
page 当前页码
pagenum 每页条数
sort 排序:1综合优先 2销量优先 3价格升序 4价格降序 5推荐 6离我最近
city 城市ID
sort 排序:1综合优先 2销量优先 3价格升序 4价格降序
keywords 关键字
cat_id 分类ID 0默认全部
...
}
*/
export function searchGoods(params) {
return request({
url: `${baseUrl}/wxxcx/searchGoodsXcx.htm`,
url: 'zsxcx/searchGoodsXcx.htm',
method: 'post',
data: params
data: params,
needMask: true
})
}
......@@ -27,29 +25,69 @@ export function searchGoods(params) {
/**
* @desc 商品详情
* @param { goods_id } 商品ID
* @param { shop_id } 当前店铺ID(分销功能使用)
* @param { agent_id } 代理商ID(分销功能使用)
*/
export function goodDetail(params) {
export function goodDetail(goods_id) {
return request({
url: `${baseUrl}/wxxcx/goodsDetail.htm`,
url: 'zsxcx/goodsDetail.htm',
method: 'post',
data: params
data: {
goods_id
}
})
}
/**
* @desc 商品规格选择接口
* goods_id
* shop_id
* sale_type
*/
export function goodsSku(goods_id, shop_id, sale_type = 0) {
export function goodsSku(goods_id) {
return request({
url: `${baseUrl}/wxxcx/goodsSku.htm`,
url: 'zsxcx/goodsSku.htm',
method: 'post',
data: {
goods_id, shop_id, sale_type
goods_id
}
})
}
/**
* @desc 商品详情页tab动态显示判断接口
* goods_id
*/
// export function goodsDetailTab(goods_id) {
// return request({
// url: 'zsxcx/getTabShow.htm',
// method: 'post',
// data: {
// goods_id
// }
// })
// }
/**
* @desc 商品收藏列表
* @param { Number } page 当前页码
* @param { Number } pagenum 条数
*/
export function collectGoodsList(params) {
return request({
url: 'zsxcx/collection.htm',
method: 'post',
needMask: true,
data: params
})
}
/**
* @desc 收藏/取消收藏
*/
export function collectGoods(goods_id) {
return request({
url: 'zsxcx/collectGoods.htm',
method: 'post',
data: {
goods_id
},
needMask: true
})
}
// 用户相关
import { request } from "@/lib/service"
import { request, upload } from "@/lib/service"
import md5 from 'md5'
/**
* @desc 检查登录态
*/
export function checkLoginStatus() {
return request({
url: `zsxcx/isLogin.htm`,
method: 'post',
needMask: true
})
}
/**
* 检查服务器时间
*/
export function checkLoginTime() {
return request({
url: `zsxcx/getFwqTime.htm`,
method: 'post'
})
}
/**
* @desc wx.login 获取openid unionid等信息
* @param { String } code
*/
export function wxLogin(code) {
return request({
url: `zsxcx/getInfo.htm`,
url: 'zsxcx/getInfo.htm',
data: {
js_code: code
}
......@@ -41,7 +20,7 @@ export function wxLogin(code) {
*/
export function logout() {
return request({
url: `zsxcx/loginOut.htm`,
url: 'zsxcx/loginOut.htm',
method: 'post',
needMask: true
})
......@@ -61,7 +40,7 @@ export function logout() {
*/
export function login(params) {
return request({
url: `zsxcx/login.htm`,
url: 'zsxcx/login.htm',
method: 'post',
data: params
})
......@@ -75,7 +54,7 @@ export function login(params) {
*/
export function forgetPwd(params) {
return request({
url: `zsxcx/forgetPwd.htm`,
url: 'zsxcx/forgetPwd.htm',
method: 'post',
data: params
})
......@@ -87,7 +66,7 @@ export function forgetPwd(params) {
*/
export function loginSms(phone) {
return request({
url: `zsxcx/send_lgmessage.htm`,
url: 'zsxcx/send_lgmessage.htm',
method: 'post',
data: {
phone,
......@@ -95,17 +74,53 @@ export function loginSms(phone) {
}
})
}
/**
* @desc 发送忘记密码验证码接口
*/
export function forgetSms(phone) {
return request({
url: `zsxcx/send_fgmessage.htm`,
url: 'zsxcx/send_fgmessage.htm',
method: 'post',
data: {
phone,
sign: md5('glsms' + phone)
}
})
}
/**
* @desc 获取用户头像昵称手机号码
* @return { }
*/
export function getUserInfo() {
return request({
url: 'zsxcx/avatarXcx.htm',
method: 'post'
})
}
/**
* @desc 修改头像
* @param { String } tempFile 文件地址
* @return { }
*/
export function editAvatar(tempFile) {
return upload({
url: 'zsxcx/editAvatarXcx.htm',
tempFile
})
}
/**
* @desc 修改昵称
* @param { String } nickname
*/
export function editNickname(nickname) {
return request({
url: 'zsxcx/editNicknameXcx.htm',
method: 'post',
data: {
nickname
}
})
}
\ No newline at end of file
<template>
<view class="empty flex flex-column j-center a-center">
<image
style="width: 360rpx;"
style="width: 400rpx;"
class="mb-4"
v-if="iconSrc"
:src="iconSrc"
mode="widthFix"
/>
<text class="font-28">{{text}}</text>
<text class="font-28 font-bold">{{text}}</text>
</view>
</template>
......@@ -16,7 +16,7 @@ export default {
props: {
iconSrc: {
type: String,
default: ""
default: "/static/images/common/noGoods.png"
},
text: {
type: String,
......
<template>
<uni-popup ref="goodsPopup" type="bottom">
<view class="wrapper p-2 w-100">
<view v-if="!showSku">
<image style="width: 180rpx;height: 180rpx;" src="../../static/images/common/loading.gif" mode="widthFix"></image>
<view class="wrap w-100">
<view
class="flex j-start a-center"
v-if="goods"
>
<image
class="goods-cover mr-2"
:src="goods.cover"
mode="aspectFit"
/>
<view>{{goods.title}}</view>
</view>
<view v-if="skuDta">
<view
v-for="item in skuDta"
:key="item.sku_id"
>
<view>{{item.spec_str}}</view>
<view>{{item.price}}</view>
</view>
</view>
<!-- btn -->
<view class="w-100 btn-wrap text-center font-32">
<viwe class="btn-cart">加入购物车</viwe>
<view class="btn-pay">立即购买</view>
</view>
<view v-else>1111</view>
</view>
</uni-popup>
</template>
......@@ -15,30 +39,32 @@ import { goodsSku } from '@/apis/goods.js'
export default {
name: 'goods-popup',
props: {
goods_id: {
type: Number | String,
default: 0
}
},
data() {
return {
showSku: false,
detail: null
goods: null,
skuDta: null
}
},
components: {
uniPopup
},
methods: {
async show(e) {
/**
* @param {Object} params
* goodsInfo: {
cover 封面
title
}
goodsSku sku
*/
async show(params) {
try{
console.log(e)
const res = await goodsSku(e.goods_id, e.shop_id)
console.log(res)
setTimeout(() => {
this.showSku = true
}, 2000)
console.log(params)
this.goods = params.goodsInfo
this.skuDta = params.skuData
this.$refs.goodsPopup.open()
}catch(e){
console.log(e)
......@@ -56,7 +82,38 @@ export default {
</script>
<style lang="scss" scoped>
.wrapper {
.wrap {
position: relative;
@include borderBox(20rpx, 20rpx);
padding-bottom: 118rpx;// 20 + 98
background-color: #fff;
.goods-cover {
width: 120rpx;
height: 120rpx;
}
.btn-wrap {
position: absolute;
left: 0;
right: 0;
bottom: 0;
z-index: 2;
height: 98rpx;
.btn-cart, .btn-pay {
display: inline-block;
width: 50%;
color: #fff;
font-weight: bold;
line-height: 98rpx;
}
.btn-cart {
background-color: red;
}
.btn-pay {
background-color: $primary;
}
}
}
</style>
......@@ -5,14 +5,16 @@
<scroll-view
class="scroll-wrapper"
scroll-y
enable-back-to-top
scroll-with-animation
:refresher-enabled="refresherEnabled"
:refresher-threshold="threshold"
:refresher-default-style="refresherStyle"
:refresher-background="refresherBg"
:refresher-triggered="refresherStatus"
:scroll-top="scrollTop"
@scrolltolower="scrolltolower"
@refresherrefresh="refresherrefresh"
@refresherrestore="refresherrestore"
@refresherabort="refresherabort"
>
......@@ -33,15 +35,16 @@ export default {
},
refresherStyle: { // black,white,none,none 表示不使用默认样式
type: String,
default: 'back'
default: 'black'
},
refresherBg: { // 背景色
type: String,
default: "#000"
default: "#fff"
}
},
data() {
return {
scrollTop: 0,
refresherStatus: false // 设置当前下拉刷新状态,true 表示下拉刷新已经被触发,false 表示下拉刷新未被触发
}
},
......@@ -70,6 +73,10 @@ export default {
console.log("下拉刷新被复位")
},
scroll2Top() {
this.scrollTop = Math.random()
}
// 下拉刷新被中止(不常用)
// refresherabort() {
// console.log("下拉刷新被中止")
......
<template>
<view class="w-100 search-bar flex j-center a-center" @click="nav">
<view class="search text-center">
<text class="mr-2">icon</text>
<text class="font-28">搜索商品</text>
<view class="w-100 search-bar flex j-center a-center" :style="{background: bg}" @click="nav">
<view class="my-2 search text-center flex j-center a-center" :style="{background: innerBg}">
<image
class="search-icon mr-2"
src="../../static/images/common/icon-search.png"
mode="aspectFit"
/>
<text class="font-24">搜索商品</text>
</view>
</view>
</template>
<script>
export default {
props: {
bg: {
type: String,
default: "#fff"
},
innerBg: {
type: String,
default: "#f8f8f8"
}
},
methods: {
nav() {
uni.navigateTo({
......@@ -21,14 +35,17 @@ export default {
<style lang="scss" scoped>
.search-bar {
height: 98rpx;
@include borderBox(20rpx, 20rpx);
background-color: yellow;
background-color: $white;
.search {
width: 710rpx;
height: 72rpx;
line-height: 72rpx;
background-color: #fff;
width: 690rpx;
height: 88rpx;
line-height: 88rpx;
color: #7F7F7F;
background-color: #F8F8F8;
.search-icon {
width: 34rpx;
height:34rpx;
}
}
}
</style>
......@@ -10,6 +10,3 @@ export const baseUrl = env[__wxConfig.envVersion]
// // #ifndef MP-WEIXIN
// export const baseUrl = process.env.NODE_ENV === 'development' ? env.develop : env.release
// // #endif
// 商品图片的域名地址
export const imgUrl = process.env.NODE_ENV === 'development' ? 'https://d.gelifood.com/' : 'https://www.gelifood.com/'
{
"extEnable": true,
"extAppid": "wxf19f73c0309593ab",
"extAppid": "wxd170058f4ad8fecd",
"ext": {
"appId": "wxf19f73c0309593ab",
"curversion": "city0319"
"appid": "wxd170058f4ad8fecd"
}
}
\ No newline at end of file
......@@ -19,6 +19,70 @@ function validateOps(options = {}) {
validator.add(Object.keys(options), [{strategy: 'minLength:1', errorMsg: '参数格式不正确'}])
validator.add(options.url, [{strategy: 'notEmpty', errorMsg: '请求地址不能为空'}])
return validator.validate()
}
// 没有考虑到上传,先前没有统一响应处理
async function successRes(res, options, resolve, reject) {
try{
switch(res.statusCode) {
case 200:
options.successCb && typeof options.successCb === 'function' && options.successCb()
// 处理某些接口返回String类型的数据
const data = (res.data && typeof res.data === 'string') ? JSON.parse(res.data) : res.data
const status = data.code
switch(status) {
case 10:
resolve({
status: true,
data: data.data,
msg: data.rep_msg
})
break;
case -1:
Toast({title: data.rep_msg})
reject({
status: false,
data: data.data,
msg: data.rep_msg
})
break;
case -2:
return Toast({title: data.rep_msg})
break;
case -3:
Toast({
title: '登录已失效,请重新登录',
cb: () => uni.navigateTo({
url: '/pages/login/index'
})
})
break;
default:
Toast({title: data.rep_msg})
reject({
status: false,
code: status, // 正常不返回为了好找 后端调试 -1 => 业务错误(一般失败情况返回此项);-2 => 系统异常; -3 => 请先登录; -4 => 请求失败; -10 => 签名失败
data: data.data,
msg: data.rep_msg
})
break;
}
break;
case 404:
return Toast({title: '请求路径不存在'})
break;
case 500:
return Toast({title: '服务器错误'})
break; // 这里的break可以不写,不会执行到。return直接退出函数了
default:
Toast({title: '网络请求失败'})
return
break;
}
}catch(e){
console.log(e)
Toast({title: e.msg || '程序请求错误'})
}
}
// 请求
......@@ -31,74 +95,19 @@ export function request (options) {
uni.showLoading({title: '加载中...', mask: true})
}
return new Promise((resolve, reject) => {
const token = store.state.user.token
const token = store.state.user.token || ''
const appid = store.state.user.appid || ''
uni.request({
...options,
header: {
'content-type': 'application/x-www-form-urlencoded',
'token': token
'token': token,
'appid': appid
},
success: async res => {
try{
switch(res.statusCode) {
case 200:
options.successCb && typeof options.successCb === 'function' && options.successCb()
// 处理某些接口返回String类型的数据
const data = (res.data && typeof res.data === 'string') ? JSON.parse(res.data) : res.data
const status = data.code
switch(status) {
case 10:
resolve({
status: true,
data: data.data,
msg: data.rep_msg
})
break;
case -1:
Toast({title: data.rep_msg})
resolve({
status: false,
data: data.data,
msg: data.rep_msg
})
break;
case -3:
Toast({title: '登录已失效,请重新登录', cb: () => uni.navigateTo({
url: '/pages/login/index'
})})
break;
default:
Toast({title: data.rep_msg})
resolve({
status: false,
code: status, // 正常不返回为了好找 后端调试 -1 => 业务错误(一般失败情况返回此项);-2 => 系统异常; -3 => 请先登录; -4 => 请求失败; -10 => 签名失败
data: data.data,
msg: data.rep_msg
})
break;
}
break;
case 404:
return Toast({title: '请求路径不存在'})
break;
case 500:
return Toast({title: '服务器错误'})
break; // 这里的break可以不写,不会执行到。return直接退出函数了
default:
Toast({title: '网络请求失败'})
return
break;
}
}catch(e){
console.log(e)
Toast({title: e.msg || '程序请求错误'})
}
success: res => {
successRes(res, options, resolve, reject)
},
fail: err => {
// if(err.errMsg.includes('request:fail ')) {
// Toast({title: err.errMsg})
// }
Toast({title: '网络开小差了,请检查网络!'})
options.errCb && typeof options.errCb === 'function' && options.errCb();
reject({
......@@ -116,10 +125,37 @@ export function request (options) {
}
// 上传文件
// export function upload() {
// return new Promise((resolve, reject) => {
// uni.uploadFile({
// })
// })
// }
export function upload(options) {
const valite_err = validateOps(options)
if(valite_err) return Toast({title: valite_err})
options.url = `${baseUrl}/${options.url}`
uni.showLoading({title: '上传中...', mask: true})
return new Promise((resolve, reject) => {
const token = store.state.user.token || ''
uni.uploadFile({
url: options.url,
filePath: options.tempFile,
header: {
// "Content-Type": "multipart/form-data",
token: token
},
name: 'file',
success: res =>{
successRes(res, options, resolve, reject)
},
fail: err => {
Toast({title: '网络开小差了,请检查网络!'})
options.errCb && typeof options.errCb === 'function' && options.errCb();
reject({
status: false,
code: 400,
data: err
})
},
complete: () => {
options.completeCb && typeof options.completeCb === 'function' && options.completeCb();
uni.hideLoading()
}
})
})
}
......@@ -50,11 +50,11 @@
"quickapp" : {},
/* 小程序特有相关 */
"mp-weixin" : {
"appid" : "wxe4c065e2234c470b",
"appid" : "wxd170058f4ad8fecd",
"setting" : {
"urlCheck" : false,
"es6" : true,
"postcss" : true,
"postcss" : false,
"minified" : true
},
"usingComponents" : true,
......
/**
* unipopup 的ref必须为goods_popup
*/
export default {
data() {
return {
// 弹窗商品的信息
popGoodInfo: {1: 11}
}
},
mounted() {
console.log(this.module1)
},
methods: {
// 打开弹出
openPopup() {
this.$refs.goods_popup.open()
},
closePopup() {
this.$refs.goods_popup.close()
this.popGoodInfo = {} // 清空弹窗商品的信息
}
}
}
\ No newline at end of file
......@@ -3,13 +3,15 @@
{
"path": "pages/home/index",
"style": {
"navigationBarTitleText": " "
"navigationBarTitleText": " ",
"enablePullDownRefresh": true,
"onReachBottomDistance": 100
}
},
{
"path": "pages/login/index",
"style": {
"navigationBarTitleText": "登录"
"navigationBarTitleText": " "
}
},
{
......@@ -76,6 +78,32 @@
"style": {
"navigationBarTitleText": "账号管理"
}
},
{
"path": "collection/index",
"style": {
"navigationBarTitleText": "账号管理"
}
},
{
"path": "address/index",
"style": {
"navigationBarTitleText": "地址列表",
"enablePullDownRefresh": true,
"onReachBottomDistance": 50
}
},
{
"path": "address/edit",
"style": {
"navigationBarTitleText": "添加地址"
}
},
{
"path": "web-view/index",
"style": {
"navigationBarTitleText": " "
}
}
]
}],
......@@ -88,36 +116,36 @@
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": " ",
"navigationBarBackgroundColor": "#F8F8F8",
"backgroundColor": "#F8F8F8"
"navigationBarBackgroundColor": "#fff",
"backgroundColor": "#fff"
},
"tabBar":{
"color": "#707070",
"selectedColor": "#8745FF",
"color": "#333333",
"selectedColor": "#FF661A",
"backgroundColor": "#FFFFFF",
"borderStyle": "black",
"list":[
{
"iconPath": "static/images/tabbar/find.png",
"selectedIconPath": "static/images/tabbar/find-selected.png",
"iconPath": "static/images/tabbar/home.png",
"selectedIconPath": "static/images/tabbar/home-active.png",
"pagePath": "pages/home/index",
"text":"首页"
},
{
"iconPath": "static/images/tabbar/find.png",
"selectedIconPath": "static/images/tabbar/find-selected.png",
"iconPath": "static/images/tabbar/cate.png",
"selectedIconPath": "static/images/tabbar/cate-active.png",
"pagePath": "pages/category/index",
"text":"商品分类"
},
{
"iconPath": "static/images/tabbar/find.png",
"selectedIconPath": "static/images/tabbar/find-selected.png",
"iconPath": "static/images/tabbar/cart.png",
"selectedIconPath": "static/images/tabbar/cart-active.png",
"pagePath": "pages/carts/index",
"text":"购物车"
},
{
"iconPath": "static/images/tabbar/my.png",
"selectedIconPath": "static/images/tabbar/my-selected.png",
"iconPath": "static/images/tabbar/mine.png",
"selectedIconPath": "static/images/tabbar/mine-active.png",
"text": "我的",
"pagePath": "pages/mine/index"
}
......
<template>
<view>addresslist</view>
</template>
<script>
</script>
<style>
</style>
<template>
<view class="cart-wrap">
<view v-if="!isLogin">
<view v-if="!token">
<button type="default" @click="handleLogin">登录</button>
</view>
<view v-else>
......@@ -78,7 +78,7 @@ export default {
computed: {
...mapState({
isLogin: state => state.user.isLogin
token: state => state.user.token
}),
// 总价
totalPrice() {
......
import { isArray } from "@/utils/types.js"
// 递归添加一个选中状态
function dealCate (list) {
if(list && !isArray(list)) return list
for(let i = 0; i < list.length; i ++) {
list[i].checked = false
if(isArray(list[i].list)) {
dealCate(list[i].list)
}
}
return list
}
export default {
dealCate
}
\ No newline at end of file
......@@ -6,31 +6,31 @@
<!-- category 未优化,可以递归组件 -->
<view class="category flex j-between w-100 font-24">
<scroll-view
class="scroll-left text-left"
class="scroll-left"
scroll-y
:scroll-into-view="leftScrollName"
>
<view
class="flex j-center flex-column a-center p-2"
v-for="cate_1 in cate_list"
v-for="(cate_1, cate_idx_1) in cate_list"
:key="cate_1.cat_id"
>
<view>
<view @click.stop="cateChange(cate_1.cat_id)">{{cate_1.cat_name}}</view>
<view v-show="cate_1.checked && cate_1.list.length > 0">
<view
@click.stop="cateChange(1, cate_1.cat_id)"
:class="cate1_current === cate_1.cat_id ? 'cate-active' : null"
>{{cate_1.cat_name}}</view>
<view v-show="cate1_current === cate_1.cat_id && cate_1.list.length > 0">
<view
class="ml-2"
v-for="cate_2 in cate_1.list"
v-for="(cate_2, cate_idx_2) in cate_1.list"
:key="cate_2.cat_id"
>
<view @click.stop="cateChange(cate_2.cat_id)">{{cate_2.cat_name}}</view>
<!-- 三级 -->
<!-- <view v-show="cate_2.checked && cate_2.list.length > 0">
<view
class="ml-4"
v-for="cate_3 in cate_2.list"
:key="cate_3.cat_id"
>{{cate_3.cat_name}}</view>
</view> -->
<view
:class="cate2_current === cate_2.cat_id ? 'cate-active' : null"
@click.stop="cateChange(2, cate_2.cat_id)"
>{{cate_2.cat_name}}</view>
</view>
</view>
......@@ -40,30 +40,26 @@
</scroll-view>
<view class="scroll-right flex-1">
<pull-list
<pull-list
ref="pullList"
:refresherEnabled="false"
@tolower="handleTolower"
@refresh="handleRefresh"
@tolower='tolower'
>
<view
v-for="(item, index) in goods_list"
:key="item.goods_id"
v-for="good in goods_list"
:key="good.goods_id"
@click="navDetail(good)"
>
<view
v-for="good in goods_list[index]"
:key="good.goods_id"
@click="navDetail(good)"
>
<image
style="width: 300rpx;height: 300rpx;"
lazy-load
:src="imgUrl + good.goods_thumb"
mode="aspectFit"
/>
<view>{{good.goods_name}}</view>
</view>
<image
style="width: 300rpx;height: 300rpx;"
lazy-load
:src="baseUrl + '/' + good.goods_thumb"
mode="aspectFit"
/>
<view>{{good.goods_name}}</view>
</view>
<view v-show="showNoMoreMsg">没有更多了+++++++++++++</view>
<view>到底了,看看别的分类吧~</view>
</pull-list>
</view>
</view>
......@@ -72,27 +68,25 @@
<script>
import pullList from '@/components/pull-list/index.vue'
import { getCatetories } from '@/apis/category.js'
import { getCatetories, cateGoodsList } from '@/apis/category.js'
import { searchGoods } from '@/apis/goods.js'
import { imgUrl } from '@/config/index.js'
import LoadMore from '@/utils/load-more.js'
import cate_fn from './cate.js'
import { baseUrl } from '@/config/index.js'
let cat_id = 0
const params = {
sort: 1,
city: 0,
keywords: '',
cat_id: 0
}
export default {
data() {
return {
imgUrl,
showNoMoreMsg: false,
cate_list: [],
cate_idex: [0, 0, 0], // 三级分类的下标,用于获取各个分类的name用于展示
goods_list: [] // 这里存二维数组,每10个(请求的条数)为一维 在小程序端更新数组不用setData整个数组
baseUrl,
cate1_current: '1',
cate2_current: '0',
cate_list: [], // 分类列表
goods_list: [], // 分类商品列表
showNoMoreMsg: false
}
},
components: {
......@@ -100,48 +94,46 @@ export default {
},
async created() {
this.getCate()
this.loadMore = new LoadMore(params)
},
methods: {
...cate_fn,
async getCate() {
try{
const { status, data } = await getCatetories(1, 0)
const { status, data } = await getCatetories()
if(status) {
let cate_list = this.cate_list
cate_list = this.dealCate(data[1].list)
cate_list[0].checked = true
cate_list[0].list[0].checked = true
// cate_list[0].list[0].list[0].checked = true
cate_list = data
cat_id = cate_list[0].cat_id
console.log(cat_id)
this.cate_list = cate_list
this.getCateGoods()
}
}catch(e){
this.$toast({title: e.msg || '程序错误'})
console.log(e)
this.$toast({title: e.msg || '获取分类失败'})
}
},
// 触底加载更多
handleTolower() {
this.getCateGoods()
},
cateChange(id) {
params.cat_id = id
cat_id = id
this.loadMore.resetParams()
this.goods_list = []
// 切换分类 type === 1 分类1 type === 2 分类2
cateChange(type ,cate_id) {
if(type === 1) {
this.cate1_current = cate_id
} else if(type === 2) {
this.cate2_current = cate_id
}
params.cat_id = cate_id
cat_id = cate_id
this.$refs.pullList.scroll2Top()
this.getCateGoods()
},
// 获取分类商品
async getCateGoods() {
try{
const res = await this.loadMore.getList(params, searchGoods)
if(!res.status) return this.showNoMoreMsg = true
this.$set(this.goods_list, this.goods_list.length, res.data)
const { status, data } = await cateGoodsList(cat_id)
if(status) {
this.goods_list = data
} else {
this.goods_list = []
}
}catch(e){
console.log(e)
this.$toast({title: e.msg || '程序错误'})
......@@ -153,9 +145,14 @@ export default {
uni.navigateTo({
url: `/pages/goods/detail?goods_id=${good.goods_id}&shop_id=${good.shop_id}&agent_id=${good.agent_id}`
})
},
tolower() {
console.log('触底了')
}
}
}
// disableScroll: true
</script>
<style lang="scss" scoped>
......@@ -170,6 +167,9 @@ export default {
overflow: hidden;
.scroll-left {
flex: 0 0 240rpx;
.cate-active {
color: red;
}
}
}
}
......
<template>
<view class="back-wrap flex j-center a-center" :style="menuSty" @click="handleBack">
返回
</view>
</template>
<script>
import { mapState } from 'vuex'
export default {
computed: {
...mapState({
menuButtonInfo: state => state.systemInfo.menuButtonInfo,
systemInfo: state => state.systemInfo.systemInfo
}),
menuSty() {
const menuButtonInfo = this.menuButtonInfo
const systemInfo = this.systemInfo
const left = systemInfo.windowWidth - menuButtonInfo.right
const top = menuButtonInfo.bottom - menuButtonInfo.height
return `width: ${menuButtonInfo.height}px;height: ${menuButtonInfo.height}px;left: ${left}px;top: ${top}px;`
}
},
methods: {
handleBack() {
uni.navigateBack()
}
}
}
</script>
<style scoped>
.back-wrap {
position: fixed;
z-index: 999;
border-radius: 50%;
background-color: red;
}
</style>
This diff is collapsed.
<template>
<view class="w-100">
<view class="font-bold font-36 p-2">{{title}}</view>
<view class="w-100 flex j-start a-center flex-wrap">
<view class="font-bold font-36 px-3 py-2">{{title}}</view>
<view class="list-wrap w-100 flex j-start a-center flex-wrap">
<view
class="item ml-2 mb-2"
class="list-item mb-3 ml-3"
v-for="(item, index) in list"
:key="index"
@click="nav"
>
<image class="goods_cover w-100" :src="arr[item]" mode="aspectFit"></image>
<view>
<view @click.stop="purchase(item)">购物ICON</view>
<image
class="goods_cover w-100"
:src="arr[item]"
mode="aspectFit"
/>
<view class="goods-info flex flex-column j-between">
<view class="title font-28 my-1">
标题标题标题标题标题标题标题标题标题标题标题标题标题标题标题标题标题标题标题标题标题标题标题标题标题标题标题标题标题标题标题标题标题
</view>
<view class="flex j-between a-center mt-1 mb-2">
<view class="price font-28">价格</view>
<image
class="cart-btn"
src="../../../static/images/common/icon-cart.png"
mode="aspectFit"
/>
</view>
</view>
</view>
</view>
......@@ -54,13 +68,31 @@ export default {
</script>
<style lang="scss" scoped>
.item {
box-sizing: border-box;
width: 345rpx;
height: 375rpx;
border: 1rpx solid #ccc;
.goods_cover {
height: 300rpx;
.list-wrap {
.list-item {
box-sizing: border-box;
width: 330rpx;
height: 480rpx;
background-color: $white;
border-radius: 8rpx;
.goods_cover {
height: 330rpx;
}
.goods-info {
@include borderBox(0, 20rpx);
height: 150rpx;
.title {
@include text-ellipsis(2)
}
.price {
color: $primary;
}
.cart-btn {
width: 34rpx;
height: 32rpx;
}
}
}
}
</style>
import { searchGoods } from '@/apis/goods.js'
const searchParams = {
sort: 1, // 1综合优先 2销量优先 3价格升序 4价格降序
keywords: ''
}
/**
* @desc 除几个推荐商品外,其他商品使用搜索接口综合(sort = 1, keywords='')查询
* searchList 是一个二维数组,插入下一页的时候不合并数组,只会插入数组
* 进入页面不会请求这个函数,只会在第一次触底的时候发送第一次请求
*/
async function searchData () {
try{
const searchRes = await this.loadmore.getList(searchParams, searchGoods)
console.log(searchRes)
if(searchRes.status) {
this.$set(this.searchList, this.searchList.length, searchRes.data)
}
}catch(e){
console.log(e)
}
}
export default {
searchData
}
\ No newline at end of file
This diff is collapsed.
<template>
<view>
<view>
<view class="wrapper">
<view class="title font-36 font-bold ml-4">欢迎来到{{storeInfo.shop_name || '格利专属小程序'}}</view>
<view class="input-wrap">
<view class="item">
<input
class="font-28 py-2"
:type="form.lgtype === 3 ? 'text' : 'number'"
:placeholder="userPlachelder"
v-model="form.user"
/>
</view>
<view class="item" v-show="form.lgtype === 2">
<input
class="font-28 py-2"
type="text"
placeholder="请输入验证码"
v-model="form.yzm"
/>
<view
class="sms-btn font-28 py-2"
@click="handleSms"
:class="smsDisabled ? 'disabledSmsBtn' : null"
>{{smsMsg}}</view>
</view>
<view class="item" v-show="form.lgtype === 3">
<input
class="font-28 py-2"
type="password"
placeholder="请输入密码"
v-model="form.pwd"
/>
</view>
<view class="clearfix">
<view class="forget font-24 py-1" @click="handleForget">忘记密码</view>
</view>
<view class="login-btn login-btn_active">短信验证码注册/登录</view>
<view class="login-btn">密码登录</view>
</view>
<view class="agreement-wrap text-center">
<view class="wx-login_title mb-2">微信快捷登录</view>
<label for="getPhone">
<image class="wx-login_icon mb-4" src="/static/images/common/icon_wx.png" mode="aspectFit" />
</label>
<view class="agreement flex j-start a-center">
<text class="mr-1">同意</text>
<navigator
class="agr_text"
hover-class="none"
url="/subPages/web-view/index?url=wxxcx/agreement.htm"
>《格利食品网平台服务协议》</navigator>
</view>
</view>
<button
style="display: absolute; z-index: -1;opacity: 0;"
id="getPhone"
open-type="getPhoneNumber"
@getphonenumber="getPhoneNumber"
></button>
<!-- <view>
<input
:type="form.lgtype === 3 ? 'text' : 'number'"
:placeholder="userPlachelder"
......@@ -32,8 +98,8 @@
</view>
<button type="default" @click="handleForget">忘记密码</button>
<button type="default" >忘记密码</button> -->
</view>
</template>
......@@ -64,7 +130,8 @@ export default {
computed: {
...mapState({
session_key: state => state.user.session_key,
openid: state => state.user.openid
openid: state => state.user.openid,
storeInfo: state => state.user.storeInfo
}),
userPlachelder() {
return userPlachelderStr[this.form.lgtype]
......@@ -75,9 +142,7 @@ export default {
...mapActions('user', ['login']),
async handleLogin(type) {
if(type !== this.form.lgtype) {
this.$set(this.form, 'lgtype', type)
}
if(type !== this.form.lgtype) return this.$set(this.form, 'lgtype', type)
switch (type) {
case 2:
const smsValidaErr = this.smsValidate()
......@@ -138,7 +203,78 @@ export default {
}
</script>
<style scoped>
<style lang="scss" scoped>
.wrapper {
background-color: #fff;
border-top: 1rpx solid $line;
.title {
padding-top: 80rpx;
margin-bottom: 190rpx;
}
.input-wrap {
width: 550rpx;
margin: 0 auto;
.item {
position: relative;
border-bottom: 1rpx solid $line;
.sms-btn {
position: absolute;
right: 0;
top: 50%;
z-index: 20;
transform: translateY(-50%);
background-color: #fff;
color: $primary;
}
.disabledSmsBtn {
color: $desc;
}
}
.forget {
display: inline-block;
float: right;
color: $primary;
}
.login-btn {
width: 360rpx;
height: 88rpx;
line-height: 88rpx;
text-align: center;
font-size: 30rpx;
font-weight: bold;
margin: 40rpx auto 40rpx;
border-radius: 8rpx;
background-color: $mainBg;
}
.login-btn_active {
color: #fff;
background-color: $primary;
}
}
.agreement-wrap {
position: fixed;
left: 50%;
bottom: 48rpx;
transform: translateX(-50%);
font-size: 20rpx;
.wx-login_icon {
display: inline-block;
width: 88rpx;
height: 88rpx;
border-radius: 8rpx;
background-color: #fff;
}
.agr_text {
color: $primary;
}
}
}
.btn_active {
background-color: yellow;
}
......
......@@ -2,9 +2,16 @@
<view class="w-100">
<!-- userInfo -->
<view class="user-wrap flex j-start a-center w-100" @click="handleAvatar">
<template v-if="isLogin">
<open-data class="avatar mr-2" type="userAvatarUrl"></open-data>
<open-data type="userNickName" lang="zh_CN"></open-data>
<template v-if="token">
<image
v-if="userInfo.avatar"
class="avatar mr-2"
:src="baseUrl + '/' + userInfo.avatar"
/>
<open-data v-else class="avatar mr-2" type="userAvatarUrl"></open-data>
<view v-if="userInfo.nickname">{{userInfo.nickname}}</view>
<open-data v-else type="userNickName" lang="zh_CN"></open-data>
</template>
<template v-else>
<view>登录/注册</view>
......@@ -67,19 +74,30 @@
</template>
<script>
import { mapState } from 'vuex'
import { checkLogin } from '@/utils/common.js'
import { mapState, mapActions } from 'vuex'
import { checkLogin } from '@/utils/modules/login.js'
import { baseUrl } from '@/config/index.js'
export default {
data() {
return {
baseUrl
}
},
computed: {
...mapState({
isLogin: state => state.user.isLogin
})
token: state => state.user.token,
userInfo: state => state.user.userInfo
}),
},
onShow() { // 获取更新用户信息
if(this.token) {
this.setUserInfo()
}
},
methods: {
...mapActions('user', ['setUserInfo']),
nav(type, arg) {
checkLogin(() => {
switch (type) {
......@@ -89,7 +107,9 @@ export default {
})
break;
case 'address':
console.log('地址列表')
uni.navigateTo({
url: '/pages/address/index'
})
break;
case 'store':
break;
......@@ -100,7 +120,7 @@ export default {
},
handleAvatar() {
if(this.isLogin) {
if(this.token) {
uni.navigateTo({
url: '/subPages/userManage/index'
})
......@@ -114,12 +134,13 @@ export default {
// 拨打电话
phoneCall() {
checkLogin(() => {
const phone = '100'
uni.showModal({
content: `是否拨打110`,
content: `是否拨打${phone}`,
success: res => {
if(res.confirm) {
uni.makePhoneCall({
phoneNumber: '100'
phoneNumber: phone
})
}
}
......
......@@ -13,31 +13,72 @@
<view @click="nav">icon</view>
</viwe>
<!-- list -->
<view class="w-100">
<view class="filter-bar w-100">
<view class="list w-100">
<!-- <view class="filter-bar w-100">
<view>综合</view>
<view>销量</view>
<view>价格</view>
</view>
</view> -->
<pull-list
@tolower="tolower"
@refresh="refresh"
>
<view
v-for="(item, index) in searchList"
:key="index"
>
<view
v-for="good in item"
:key="good.goods_id"
>
<image
:src="baseUrl + '/' + good.goods_thumb"
style="width: 300rpx;"
mode="widthFix"
/>
</view>
</view>
</pull-list>
<view class="list w-100">
</view>
</view>
</view>
</template>
<script>
import pullList from '@/components/pull-list/index.vue'
import { searchGoods } from '@/apis/goods.js'
import LoadMore from '@/utils/load-more.js'
import { baseUrl } from '@/config/index.js'
const searchParams = {
sort: 1, // 1综合优先 2销量优先 3价格升序 4价格降序
keywords: ''
}
export default {
data() {
return {
baseUrl,
searchList: []
}
},
components: {
pullList
},
onLoad(options) {
this.loadmore = new LoadMore()
searchParams.keywords = options.val || ''
uni.setNavigationBarTitle({
title: options.val
})
this.getData()
},
methods: {
nav() {
uni.switchTab({
......@@ -45,7 +86,28 @@ export default {
})
},
async getData() {
try{
const listRes = await this.loadmore.getList(searchParams, searchGoods)
if(listRes.status) {
this.$set(this.searchList, this.searchList.length, listRes.data)
}
}catch(e){
this.$toast({title: e.msg || '程序错误'})
}
},
// 加载更多
tolower() {
this.getData()
},
// 下拉刷新
refresh() {
this.searchList = []
this.loadmore.resetParams()
this.getData()
}
}
}
</script>
......@@ -68,4 +130,8 @@ export default {
transform: translateY(-50%);
}
}
.list {
height: calc(100vh - 98rpx);
}
</style>
import { setStorageAsync, removeStorage } from '@/lib/storage'
// import store from '@/store/index.js'
import { login, logout } from '@/apis/user.js'
import { login, logout, getUserInfo } from '@/apis/user.js'
import { getStoreInfo } from '@/apis/common.js'
import Toast from '@/lib/toast/index.js'
const state = {
token: null,
openid: null,
unionid: null,
session_key: '',
userInfo: {},
userInfo: {},// 昵称,头像,手机号码
location: {},
appid: '',
storeInfo: {},
isOverdue: false, // 服务是否过期
isLogin: true,
}
const mutations = {
......@@ -26,9 +26,11 @@ const mutations = {
SETUSERINFO(state, userInfo){
state.userInfo = userInfo
},
LOGIN(state, params) {
state.token = params.token
state.userInfo.user = params.user
SETSTOREINFO(state, storeInfo) {
state.storeInfo = storeInfo
},
LOGIN(state, token) {
state.token = token
},
LOGOUT(state){
state.token = ''
......@@ -53,17 +55,28 @@ const actions = {
}
})
},
setUserInfo: ({commit}, userInfo) => {
commit('SETUSERINFO', userInfo)
setStorage('userInfo',userInfo)
setUserInfo: async ({commit}, userInfo) => {
try{
const { status, data } = await getUserInfo()
if(status) {
commit('SETUSERINFO', data)
}
}catch(e){
Toast({title: e.msg || '登录失败'})
}
},
login: async ({commit}, form) => {
try{
const { status, data } = await login(form)
if(status) {
setStorageAsync('token', data.token)
commit('LOGIN', data)
uni.navigateBack()
// 这里返回了用户名,但后面会有个接口单独返回,所以这里不操作useInfo
commit('LOGIN', data.token)
Toast({
title: '登录成功',
cb: () => uni.navigateBack()
})
}
}catch(e){
......@@ -76,10 +89,26 @@ const actions = {
if(status) {
commit('LOGOUT')
removeStorage('token')
Toast({
title: '退出成功',
cb: () => uni.navigateBack()
})
}
}catch(e){
Toast({title: e.msg || '退出登录失败'})
}
},
getStoreInfo: async ({commit}) => {
try{
const { status, data } = await getStoreInfo()
if(status) {
commit('SETSTOREINFO', data)
}
}catch(e){
console.log(e)
//TODO handle the exception
}
}
}
......
$test-primary: #007aff;
\ No newline at end of file
$primary: #FF661A; // 主色
$text: #333; // 正文色
$desc: #7f7f7f; // 描述色
$warning: #FFBB33; // 黄色 警告
$disabled: #cccccc;
$daner: #FC401E; // 红色 错误
$line: #d9d9d9; // 线条色
$mainBg: #F8F8F8; // 部分按钮、背景色
$white: #fff; // 白色
.clearfix:after {
visibility: hidden;
display: block;
font-size: 0;
content: " ";
clear: both;
height: 0;
}
\ No newline at end of file
page {
color: #333;
background-color: #F8F8F8;
}
/* 去除image的间距 */
image {
display: block;
}
.wrapper {
width: 100%;
min-height: 100vh;
}
.w-100 { width: 100%; }
.wh-100 { width: 100%; height: 100%; }
/* flex 布局 */
.flex { display:flex!important; }
......@@ -17,6 +30,7 @@
.a-stretch{ align-items: stretch; }
.a-start{ align-items: flex-start; }
.a-end{ align-items: flex-end; }
.as-end{ align-self: flex-end; }
.flex-1{ flex: 1; }
.flex-2{ flex: 2; }
......
<template>
<view>
<view class="uni-list-cell-db">
<picker mode="multiSelector" @columnchange="cityChange" @change="confirm" :value="cityIndex" :range="cities">
<view class="uni-input">{{cities[0][cityIndex[0]] || ''}}-{{cities[1][cityIndex[1]] || ''}}-{{cities[2][cityIndex[2]] || ''}}</view>
</picker>
<picker
mode="multiSelector"
@columnchange="cityChange"
@change="confirm"
:value="cityIndex"
:range="cities"
>
<view v-if="!result">
请选择地区
</view>
<view v-else>
<!-- 可能没有第四级 -->
{{cities[0][cityIndex[0]] || ''}} {{cities[1][cityIndex[1]] || ''}} {{cities[2][cityIndex[2]] || ''}} {{ cities[3][cityIndex[3]] || ''}}
</view>
</view>
</picker>
</template>
<script>
import { throttle } from '@/utils/common.js'
import { queryCity } from '@/apis/common.js'
const cacheList = []
const cacheList = [null, null, null, null] // 分别为四级区域,其中第一级是不会变得
const cityCode = { // 四级联动的城市的code,第一级默认是1
"0": 1,
"1": 0,
"1": 1,
"2": 0,
"3": 0
"3": 0,
"4": 0
}
let cityAreaIdIdx = {} // area_id 实际拿到,但是请求使用code
export default {
name: 'city-picker',
props: {
cityForm: {
type: Object,
default: null
}
},
data() {
return {
result: null,
code : 1,
cityIndex: [0, 0, 0, 0],
cities: [] ,// 这里是一个二维数组
cities: [] ,// 这里是一个二维数组,这个和cacheList结构一样,但是这个只有城市名称
}
},
created() {
mounted() {
const form = this.cityForm
cityAreaIdIdx = {
"1": form.province || '',
"2": form.city || '',
"3": form.district || '',
"4": form.street || ''
}
this.getDefault()
},
methods: {
async getDefault() {
try{
await this.get_city(1)
await this.get_city(2)
await this.get_city(3)
await this.get_city(4)
uni.showLoading({title: '加载中...', mask: true})
const cityForm = this.cityForm
let i = 1
const cities = []
/*
* 添加地址时,拿父级code去请求(第四级可能会没有数据);
* 修改地址时,后端返回的数据对应是四级联动数据的area_id,因此需要遍历找到对应的areaid的code去请求
* Both code and me can run. I am tired
*/
if(cityForm && !cityForm.address_id) {
while(i <= 4) { // 添加地址时
const cityData = await this.getCityData(cityCode[i])
cacheList[i - 1] = cityData
cities[i - 1] = cityData ? cityData.map(item => item.area_name) : []
i ++
cityCode[i] = cityData[0].code
}
} else { // 编辑地址时
while(i <= 4) {
const cityData = await this.getCityData(cityCode[i])
cacheList[i - 1] = cityData
this.cityIndex[i - 1] = cityData ? cityData.findIndex(item => item.area_id === cityAreaIdIdx[i]) : 0 // 当前列的当前地区下标 第四级可能会为空
cities[i - 1] = cityData ? cityData.map(item => item.area_name) : []
if(cityData) {
cityCode[i + 1] = cityData[this.cityIndex[i - 1]] ? cityData[this.cityIndex[i - 1]].code : ''
}
i ++
}
this.result = true // 有地址时,随便改变result 不为false即可
}
this.cities = cities
uni.hideLoading()
this.$forceUpdate() // 慎用 重新渲染
}catch(e){
console.log(e)
this.$toast({title: e.msg || '程序错误'})
}
},
async getCityData(code) {
try{
const { status, data } = await queryCity(code)
if(!status) return null
return data
}catch(e){
console.log(e)
this.$toast({title: e.msg || '程序错误'})
}
},
// idx 获取第几列的数据
async get_city(idx) {
try{
const { status, data } = await queryCity(cityCode[idx - 1])
if(status) {
cityCode[idx] = data[0].code
this.cities[idx - 1] = data.map(item => item.area_name)
cacheList[idx - 1] = data
if(data === null) {
cityCode[idx] = 0
this.cities[idx - 1] = []
cacheList[idx - 1] = []
} else {
cityCode[idx] = data[0].code || 0
this.cities[idx - 1] = data.map(item => item.area_name)
cacheList[idx - 1] = data
}
}
}catch(e){
console.log(e)
this.$toast({title: e.msg || '程序错误'})
}
},
......@@ -65,6 +139,7 @@ export default {
this.cityIndex[column] = value
switch (column) {
case 0: //拖动第1列
uni.showLoading({title: '加载中...', mask: true})
cityCode[1] = cacheList[0][value].code
await this.get_city(2)
await this.get_city(3)
......@@ -74,20 +149,24 @@ export default {
this.cityIndex.splice(1, 1, 0)
this.cityIndex.splice(2, 1, 0)
this.cityIndex.splice(3, 1, 0)
uni.hideLoading()
break;
case 1: //拖动第2列
uni.showLoading({title: '加载中...', mask: true})
cityCode[2] = cacheList[1][value].code
await this.get_city(3)
await this.get_city(4)
this.cityIndex.splice(2, 1, 0)
this.cityIndex.splice(3, 1, 0)
uni.hideLoading()
break;
case 2:
uni.showLoading({title: '加载中...', mask: true})
cityCode[3] = cacheList[2][value].code
await this.get_city(4)
this.cityIndex.splice(3, 1, 0)
uni.hideLoading()
break;
}
......@@ -96,15 +175,16 @@ export default {
confirm(e) {
const value = e.detail.value
const result = {
province_code: cacheList[0][value[0]].code,
province_code: cacheList[0][value[0]].area_id,
province_name: cacheList[0][value[0]].area_name,
city_code: cacheList[1][value[1]].code,
city_code: cacheList[1][value[1]].area_id,
city_name: cacheList[1][value[1]].area_name,
area_code: cacheList[2][value[2]].code,
area_code: cacheList[2][value[2]].area_id,
area_name: cacheList[2][value[2]].area_name,
detail_code: cacheList[3][value[3]].code,
detail_name: cacheList[3][value[3]].area_name
detail_code: cacheList[3][value[3]] ? cacheList[3][value[3]].area_id : 0,
detail_name: cacheList[3][value[3]] ? cacheList[3][value[3]].area_name : ''
}
this.result = result
this.$emit('confirmCity', result)
}
......
<template>
<view class="wrapper">
<view>
<input
type="text"
placeholder="请输入联系人名字"
v-model="form.consignee"
/>
</view>
<view>
<input
type="number"
maxlength="11"
placeholder="请输入电话号码"
v-model="form.mobile"
/>
</view>
<view class="p-2" style="background-color: red;">
<city-picker :cityForm="form" @confirmCity="confirmCity"></city-picker>
</view>
<view>
<input
type="text"
placeholder="请输入详细地址"
v-model="form.address"
/>
</view>
<view>
设置为默认收货地址
<switch
:checked="form.is_default"
color="#04BE02"
@change="handleDefault('is_default', form.is_default)"
/>
</view>
<view>
设置为默认发货地址
<switch
:checked="!!form.is_send_default"
color="#04BE02"
@change="handleDefault('is_send_default', form.is_send_default)"
/>
</view>
<view class="btn w-100" @click="handleAdd">添加</view>
</view>
</template>
<script>
import cityPicker from './components/city-picker/index.vue'
import { editAddress } from '@/apis/address.js'
import editModules from './editModuls.js'
export default {
data() {
return {
form: {
address_id: '', // 收货地址唯一ID(填写此项则判定为编辑,否则为新增)
consignee: '', //* 收货人名称
province: '', // *
city: '', // *
district: '', // *
street: '', // *
address: '', //
mobile: '', //
is_default: 0, // 是否为默认收货地址:1默认 0非默认
is_send_default: 0, // * 是否默认为发货地址:1默认 0非默认
corporate_name: '' // 公司名称
}
}
},
components: {
cityPicker
},
onLoad(ops) {
const params = ops.params ? JSON.parse(decodeURIComponent(ops.params)) : ''
if(params && params.address_id) {
uni.setNavigationBarTitle({
title: '编辑地址'
})
this.form = Object.assign(this.form, params)
this.address_id = params.address_id
}
},
methods: {
...editModules,
// 添加地址
async handleAdd() {
try{
const form = this.form
const valiteErr = this.validateEdit(form)
if(valiteErr) return this.$toast({title: valiteErr})
const { status } = await editAddress(this.form)
if(status) {
this.$toast({title: '添加成功', cb: () => uni.navigateBack()})
}
}catch(e){
//TODO handle the exception
this.$toast({title: e.msg || '添加地址失败'})
}
},
// 设置默认
handleDefault(key, val) {
this.$set(this.form, key, +!val)
},
confirmCity(e) {
console.log(e)
const form = this.form
form.province = e.province_code
form.city = e.city_code
form.district = e.area_code
form.street = e.detail_code
this.form = form
}
}
}
</script>
<style lang="scss" scoped>
.wrapper {
position: relative;
margin-bottom: 98rpx;
.btn {
position: absolute;
left: 0;
right: 0;
bottom: 0;
height: 98rpx;
z-index: 1;
background-color: red;
}
}
</style>
import Validator from '@/utils/validate.js'
function validateEdit(form) {
const validator = new Validator()
validator.add(form.consignee, [{strategy: 'notEmpty', errorMsg: '联系人不能为空'}])
validator.add(form.mobile, [{strategy: 'notEmpty', errorMsg: '手机号码不能为空'}, {strategy: 'isMobile', errorMsg: '手机号码格式不正确'}])
validator.add(form.province, [{strategy: 'notEmpty', errorMsg: '省份不能为空'}])
validator.add(form.city, [{strategy: 'notEmpty', errorMsg: '城市不能为空'}])
validator.add(form.district, [{strategy: 'notEmpty', errorMsg: '地区不能为空'}])
// validator.add(form.street, [{strategy: 'notEmpty', errorMsg: '街道不能为空'}])
validator.add(form.address, [{strategy: 'notEmpty', errorMsg: '详细地址不能为空'}])
return validator.validate()
}
export default {
validateEdit
}
\ No newline at end of file
<template>
<view>
<block
v-for="(item, index) in list"
:key="index"
>
<view
v-for="(address, inneridx) in item"
:key="address.address_id"
@click="handleDetail(address)"
style="width: 100%;border-bottom: 1px solid red;"
>
<view>{{address.consignee}}</view>
<view>{{address.mobile}}</view>
<view>{{address.p_cn + address.d_cn + address.c_cn + address.st_cn + address.address}}</view>
<button type="default" @click="handleDel(address.address_id, index, inneridx)">删除地址</button>
</view>
</block>
<navigator url="/subPages/address/edit">
<button type="default">添加地址</button>
</navigator>
</view>
</template>
<script>
import { addressList, defaultAddress, delAddress } from '@/apis/address.js'
import LoadMore from '@/utils/load-more.js'
export default {
data() {
return {
list: []
}
},
created() {
this.loadmore = new LoadMore()
},
onShow() {
this.init()
},
// 下拉刷新
onPullDownRefresh() {
uni.showLoading({
title: '加载中...'
})
this.init()
let timer = setTimeout(() => {
clearTimeout(timer)
timer = null
uni.hideLoading()
uni.stopPullDownRefresh()
}, 800)
},
// 上拉加载更多
onReachBottom() {
console.log('触底')
},
methods: {
init() {
// 初始化
this.list = []
this.loadmore.resetParams()
uni.showLoading({title: '加载中...'})
Promise.all([defaultAddress(), this.loadmore.getList({keywords: ''}, addressList)])
.then(res => {
console.log(res)
this.$set(this.list, this.list.length, res[1].data)
})
.catch(err => {
console.log(err)
this.$toast({title: '获取数据失败'})
})
.finally(() => {
uni.hideLoading()
})
},
// 获取默认地址
async getDefault() {
try{
const { status, data } = await defaultAddress()
}catch(e){
this.$toast({title: e.msg || '程序错误'})
}
},
// 获取地址列表
async getAddressList() {
try{
const { status, data } = await addressList({keywords: ''})
}catch(e){
this.$toast({title: e.msg || '程序错误'})
}
},
// 删除地址
async handleDel(id, wrapIdx, innerIdx) {
try{
const { status, data } = await delAddress(id)
if(status) {
// 这里不会重新请求刷新数据
this.list[wrapIdx].splice(innerIdx, 1)
this.$toast({title: '删除成功'})
}
}catch(e){
this.$toast({title: e.msg || '程序错误'})
//TODO handle the exception
}
},
handleDetail(address) {
address.is_default = Number(address.is_default)
address.is_send_default =Number(address.is_send_default)
const params = encodeURIComponent(JSON.stringify(address))
uni.navigateTo({
url: `/subPages/address/edit?params=${params}`
})
},
confirmCity(e) {
console.log(e)
}
}
}
</script>
<style>
</style>
<template>
<view class="wrapper w-100">
<!-- tab -->
<view class="tab-wrap w-100 flex j-between a-center text-center">
<view
class="flex-1 tab-item"
:class="tab_current === 0 ? 'tab-active' : null"
@click="handleTab(0)"
>收藏</view>
<view
class="flex-1"
:class="tab_current === 1 ? 'tab-active' : null"
@click="handleTab(1)"
>浏览历史</view>
</view>
<!-- list -->
<swiper class="list-wrap" :current="tab_current">
<swiper-item>
<pull-list
@tolower="tolowerC"
@refresh="refreshC"
>
<view v-for="item in list" :key="item.goods_id">
<image :src="baseUrl + '/' + item.goods_thumb" style="width: 300rpx;" mode="widthFix"></image>
</view>
</pull-list>
</swiper-item>
<swiper-item>
<pull-list
@tolower="tolowerH"
@refresh="refreshH"
>
<view>1</view>
</pull-list>
</swiper-item>
</swiper>
</view>
</template>
<script>
import pullList from '@/components/pull-list/index.vue'
import { collectGoodsList } from '@/apis/goods.js'
import { historyList, test } from '@/apis/common.js'
import LoadMore from '@/utils/load-more.js'
import { baseUrl } from '@/config/index.js'
export default {
data() {
return {
baseUrl,
tab_current: 0,
list: []
}
},
components: {
pullList
},
created() {
this.collectList = new LoadMore()
},
mounted() {
this.getCollect()
},
methods: {
// tab 切换
handleTab(idx) {
if(idx === this.tab_current) return
this.tab_current = idx
},
// 封装得不够透彻
tolowerC() {
this.getCollect()
console.log('收藏下拉')
},
refreshC() {
console.log('收藏下拉')
},
tolowerH() {
console.log('历史下拉')
},
refreshH() {
console.log('历史上拉')
},
async getCollect() {
const list = await this.collectList.getList({ keywords: '大成'}, test)
console.log(list)
}
}
}
</script>
<style lang="scss" scoped>
.wrapper {
min-height: 100vh;
.tab-wrap {
box-sizing: border-box;
height: 98rpx;
border-bottom: 1px solid red;
.tab-active {
color: red;
}
}
.list-wrap {
width: 100%;
height: calc(100vh - 98rpx);
}
}
</style>
......@@ -68,7 +68,7 @@ export default {
const valiErr = this.phoneValidate()
if(valiErr) return this.$toast({title: valiErr})
const res = await forgetSms(this.form.phone)
console.log(res)
if(!res.status) return
timerFn(60, num => {
if(num <= 0) {
this.smsDisabled = false
......@@ -91,6 +91,7 @@ export default {
console.log(validateErr)
if(validateErr) return this.$toast({title: validateErr})
const { status, msg } = await forgetPwd(this.form)
console.log(status, msg)
if(status) {
this.$toast({title: msg, cb: () => {uni.navigateBack()}})
}
......
<template>
<view class="w-100">
<view class="wrapper w-100">
<view class="w-100 flex j-between a-center">
<view>头像</view>
<image
class="avatar"
src="../../static/images/common/icon_nothing.png"
mode="aspectFit"
@click="changeAvatar"
/>
<view
class="avatar"
@click="handleAvatar"
>
<template v-if="userInfo && userInfo.avatar">
<image
class="avatar-img"
:src="baseUrl + '/' + userInfo.avatar"
mode="aspectFit"
/>
</template>
<template v-else>
<open-data class="avatar-img" type="userAvatarUrl"></open-data>
</template>
</view>
</view>
<view @click="handleLogout">退出登录</view>
<view class="w-100 flex j-between a-center">
<view>昵称</view>
<view class="nickname">
<template v-if="userInfo && userInfo.nickname">
<input
type="text"
:value="userInfo.nickname"
confirm-type="done"
@confirm="changeNickname"
/>
</template>
<template v-else>
<open-data type="userNickName" lang="zh_CN"></open-data>
</template>
</view>
</view>
<view class="logout-btn w-100 text-center" @click="logout">退出登录</view>
</view>
</template>
<script>
import { mapState, mapActions } from 'vuex'
import { editAvatar, editNickname } from '@/apis/user.js'
import { baseUrl } from '@/config/index.js'
export default {
data() {
return {
baseUrl
}
},
computed: {
...mapState({
userInfo: state => state.user.userInfo
}),
},
mounted() {
console.log(this.userInfo)
},
methods: {
changeAvatar(e) {
...mapActions('user', ['logout', 'setUserInfo']),
handleAvatar(e) {
uni.chooseImage({
count: 1,
success: res => console.log(res)
success: async res => {
try{
const fileInfo = res.tempFiles[0]
if(fileInfo.size > 2097152) return this.$toast({title: '图片不能大于2M'})
const { status, data } = await editAvatar(fileInfo.path)
if(status) {
this.setUserInfo()
}
}catch(e){
this.$toast({title: e.msg || '上传失败'})
}
}
})
},
async changeNickname(e) {
try{
const { status, data } = await editNickname(e.detail.value)
if(status) {
this.setUserInfo()
}
}catch(e){
this.$toast({title: e.msg || '修改失败'})
}
}
}
}
</script>
<style lang="scss" scoped>
.avatar {
width: 180rpx;
height: 180rpx;
.wrapper {
margin-bottom: 98rpx;
.avatar {
width: 180rpx;
height: 180rpx;
.avatar-img {
width: 100%;
height: 100%;
}
}
.logout-btn {
position: fixed;
left: 0;
right: 0;
bottom: 0;
height: 98rpx;
line-height: 98rpx;
z-index: 1;
color: #fff;
background-color: red;
}
}
</style>
<template>
<web-view :src="webUrl" @load="webViewLoad" @error="webViewLoadErr"></web-view>
</template>
<script>
import { baseUrl } from '@/config/index.js'
export default {
data() {
return {
webUrl: ''
}
},
onLoad(ops) {
const { url } = ops
if(!url) return this.$toast({title: '缺少地址', cb: () => {
uni.navigateBack()
}})
this.webUrl = `${baseUrl}/{url}`
},
methods: {
webViewLoadErr() {
this.$toast({title: '网页加载失败', cb: () => {
uni.navigateBack()
}})
}
}
}
</script>
......@@ -19,14 +19,45 @@ export function timerFn(num = 60, cb) {
}
/**
* @desc 检查登录
* @param { Function } cb
* @desc 节流函数 cv的
* @param {Function} callback 回调函数
* @param {Number} wait 间隔时间
* @return {Function} 节流函数
*/
export function checkLogin(cb) {
if(!store.state.user.token) return Toast({title: '您尚未登录,请先登录', cb: () => {
uni.navigateTo({
url: '/pages/login/index'
})
}})
cb && isFunction(cb) && cb()
export function throttle(callback, wait = 3000) {
let timer = null;
let startTime;
return function () {
const ctx = this;
const args = arguments;
const now = +new Date();
if (startTime && now < startTime + wait) {
console.log('000==')
clearTimeout(timer);
timer = setTimeout(function () {
startTime = now;
callback.apply(ctx, args);
}, wait);
} else {
console.logout("33")
startTime = now;
callback.apply(ctx, args);
}
}
}
/**
* @微信支付
* @param { Functiom } cb 回调函数
*/
export function wxPay(cb) {
uni.requestPayment({
timeStamp: '',
nonceStr: '',
package: '',
signType: 'MD5',
paySign: '',
success (res) { },
fail (res) { }
})
}
\ No newline at end of file
import { isFunction } from '@/utils/types.js'
import Toast from '@/lib/toast/index.js'
/**
* 多页加载
*/
......@@ -12,25 +13,25 @@ class LoadMore {
try {
if(!isFunction(cb)) return { status: false, data: null, msg: '参数类型不正确' }
if(this.flat) return { status: false, data: null, msg: '请求中,请勿重复操作' }
if(!!this.totalPage && (this.page === this.totalPage)) return { status: false, data: null, msg: '没有更多页面了' }
// !!this.totalpage 用于判断是否是初始值0
if(this.totalPage && (this.page > this.totalPage)) return { status: false, data: null, msg: '没有更多页面了' }
const requestParams = Object.assign(params, {
page: this.page,
pagenum: this.pagenum
})
const { status, data, res } = await cb(requestParams)
if(!status) return { status: false, data: null, msg: '没有更多页面了' }
const count = res.pageinfo.count // 总数
const pages = Math.ceil(count / this.pagenum)
if(this.page < pages) {
this.page ++
}
const { status, data } = await cb(requestParams)
if(!status) return { status: false, data: null, msg: '请求失败' }
const count = data.pageinfo.count // 总数
const pages = data.pageinfo.pages // 总页数
// const pages = Math.ceil(count / this.pagenum)
this.page ++
this.total = count
this.totalPage = pages
this.flat = false
return { status: true, data, msg: '成功' }
return { status: true, data: data.list, msg: '成功' }
} catch (e) {
// 如果无一页,接口code会返回-1 在封装走reject 在这里会直接抛出
return { status: false, data: null, msg: '获取列表失败' }
console.log(e)
return Toast({title: '获取列表失败'})
}
}
......
import { wxLogin } from '@/apis/user.js'
import store from '@/store/index.js'
import { isFunction } from '@/utils/types.js'
import Toast from '@/lib/toast/index.js'
// wx.login
export function login_wx(cb) {
uni.login({
success: async res => {
const { status, data } = await wxLogin(res.code)
cb && isFunction(cb) && cb()
if(status) {
store.state.user.openid = data.openid
store.state.user.unionid = data.unionid
store.state.user.session_key = data.session_key
}
}
})
}
\ No newline at end of file
try{
uni.login({
success: async res => {
const { status, data } = await wxLogin(res.code)
if(status) {
cb && isFunction(cb) && cb()
store.state.user.openid = data.openid
store.state.user.unionid = data.unionid
store.state.user.session_key = data.session_key
}
},
fail: err => uni.showModal({
title: "提示",
content: err.errMsg,
showCancel: false
})
})
}catch(e){
console.log(e)
//TODO handle the exception
}
}
/**
* @desc 检查登录
* @param { Function } cb
*/
export function checkLogin(cb) {
if(!store.state.user.token) return Toast({title: '您尚未登录,请先登录', cb: () => {
uni.navigateTo({
url: '/pages/login/index'
})
}})
cb && isFunction(cb) && cb()
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment