提交 bf845e1e authored 作者: lhh's avatar lhh

update

上级
File added
<script>
/**
* vuex管理登陆状态,具体可以参考官方登陆模板示例
*/
import {
mapMutations
} from 'vuex';
export default {
methods: {
...mapMutations(['login'])
},
onLaunch: function() {
let userInfo = uni.getStorageSync('userInfo') || '';
if(userInfo.id){
//更新登陆状态
uni.getStorage({
key: 'userInfo',
success: (res) => {
this.login(res.data);
}
});
}
},
onShow: function() {
console.log('App Show')
},
onHide: function() {
console.log('App Hide')
},
}
</script>
<style lang='scss'>
/*
全局公共样式和字体图标
*/
@font-face {
font-family: yticon;
font-weight: normal;
font-style: normal;
src: url('https://at.alicdn.com/t/font_1078604_w4kpxh0rafi.ttf') format('truetype');
}
.yticon {
font-family: "yticon" !important;
font-size: 16px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
.icon-yiguoqi1:before {
content: "\e700";
}
.icon-iconfontshanchu1:before {
content: "\e619";
}
.icon-iconfontweixin:before {
content: "\e611";
}
.icon-alipay:before {
content: "\e636";
}
.icon-shang:before {
content: "\e624";
}
.icon-shouye:before {
content: "\e626";
}
.icon-shanchu4:before {
content: "\e622";
}
.icon-xiaoxi:before {
content: "\e618";
}
.icon-jiantour-copy:before {
content: "\e600";
}
.icon-fenxiang2:before {
content: "\e61e";
}
.icon-pingjia:before {
content: "\e67b";
}
.icon-daifukuan:before {
content: "\e68f";
}
.icon-pinglun-copy:before {
content: "\e612";
}
.icon-dianhua-copy:before {
content: "\e621";
}
.icon-shoucang:before {
content: "\e645";
}
.icon-xuanzhong2:before {
content: "\e62f";
}
.icon-gouwuche_:before {
content: "\e630";
}
.icon-icon-test:before {
content: "\e60c";
}
.icon-icon-test1:before {
content: "\e632";
}
.icon-bianji:before {
content: "\e646";
}
.icon-jiazailoading-A:before {
content: "\e8fc";
}
.icon-zuoshang:before {
content: "\e613";
}
.icon-jia2:before {
content: "\e60a";
}
.icon-huifu:before {
content: "\e68b";
}
.icon-sousuo:before {
content: "\e7ce";
}
.icon-arrow-fine-up:before {
content: "\e601";
}
.icon-hot:before {
content: "\e60e";
}
.icon-lishijilu:before {
content: "\e6b9";
}
.icon-zhengxinchaxun-zhifubaoceping-:before {
content: "\e616";
}
.icon-naozhong:before {
content: "\e64a";
}
.icon-xiatubiao--copy:before {
content: "\e608";
}
.icon-shoucang_xuanzhongzhuangtai:before {
content: "\e6a9";
}
.icon-jia1:before {
content: "\e61c";
}
.icon-bangzhu1:before {
content: "\e63d";
}
.icon-arrow-left-bottom:before {
content: "\e602";
}
.icon-arrow-right-bottom:before {
content: "\e603";
}
.icon-arrow-left-top:before {
content: "\e604";
}
.icon-icon--:before {
content: "\e744";
}
.icon-zuojiantou-up:before {
content: "\e605";
}
.icon-xia:before {
content: "\e62d";
}
.icon--jianhao:before {
content: "\e60b";
}
.icon-weixinzhifu:before {
content: "\e61a";
}
.icon-comment:before {
content: "\e64f";
}
.icon-weixin:before {
content: "\e61f";
}
.icon-fenlei1:before {
content: "\e620";
}
.icon-erjiye-yucunkuan:before {
content: "\e623";
}
.icon-Group-:before {
content: "\e688";
}
.icon-you:before {
content: "\e606";
}
.icon-forward:before {
content: "\e607";
}
.icon-tuijian:before {
content: "\e610";
}
.icon-bangzhu:before {
content: "\e679";
}
.icon-share:before {
content: "\e656";
}
.icon-yiguoqi:before {
content: "\e997";
}
.icon-shezhi1:before {
content: "\e61d";
}
.icon-fork:before {
content: "\e61b";
}
.icon-kafei:before {
content: "\e66a";
}
.icon-iLinkapp-:before {
content: "\e654";
}
.icon-saomiao:before {
content: "\e60d";
}
.icon-shezhi:before {
content: "\e60f";
}
.icon-shouhoutuikuan:before {
content: "\e631";
}
.icon-gouwuche:before {
content: "\e609";
}
.icon-dizhi:before {
content: "\e614";
}
.icon-fenlei:before {
content: "\e706";
}
.icon-xingxing:before {
content: "\e70b";
}
.icon-tuandui:before {
content: "\e633";
}
.icon-zuanshi:before {
content: "\e615";
}
.icon-zuo:before {
content: "\e63c";
}
.icon-shoucang2:before {
content: "\e62e";
}
.icon-shouhuodizhi:before {
content: "\e712";
}
.icon-yishouhuo:before {
content: "\e71a";
}
.icon-dianzan-ash:before {
content: "\e617";
}
view,
scroll-view,
swiper,
swiper-item,
cover-view,
cover-image,
icon,
text,
rich-text,
progress,
button,
checkbox,
form,
input,
label,
radio,
slider,
switch,
textarea,
navigator,
audio,
camera,
image,
video {
box-sizing: border-box;
}
/* 骨架屏替代方案 */
.Skeleton {
background: #f3f3f3;
padding: 20upx 0;
border-radius: 8upx;
}
/* 图片载入替代方案 */
.image-wrapper {
font-size: 0;
background: #f3f3f3;
border-radius: 4px;
image {
width: 100%;
height: 100%;
transition: .6s;
opacity: 0;
&.loaded {
opacity: 1;
}
}
}
.clamp {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
display: block;
}
.common-hover {
background: #f5f5f5;
}
/*边框*/
.b-b:after,
.b-t:after {
position: absolute;
z-index: 3;
left: 0;
right: 0;
height: 0;
content: '';
transform: scaleY(.5);
border-bottom: 1px solid $border-color-base;
}
.b-b:after {
bottom: 0;
}
.b-t:after {
top: 0;
}
/* button样式改写 */
uni-button,
button {
height: 80upx;
line-height: 80upx;
font-size: $font-lg + 2upx;
font-weight: normal;
&.no-border:before,
&.no-border:after {
border: 0;
}
}
uni-button[type=default],
button[type=default] {
color: $font-color-dark;
}
/* input 样式 */
.input-placeholder {
color: #999999;
}
.placeholder {
color: #999999;
}
</style>
差异被折叠。
# mall-app-web
<p>
<a href="#公众号"><img src="http://macro-oss.oss-cn-shenzhen.aliyuncs.com/mall/badge/%E5%85%AC%E4%BC%97%E5%8F%B7-macrozheng-blue.svg" alt="公众号"></a>
<a href="#公众号"><img src="http://macro-oss.oss-cn-shenzhen.aliyuncs.com/mall/badge/%E4%BA%A4%E6%B5%81-%E5%BE%AE%E4%BF%A1%E7%BE%A4-2BA245.svg" alt="交流"></a>
<a href="https://github.com/macrozheng/mall"><img src="http://macro-oss.oss-cn-shenzhen.aliyuncs.com/mall/badge/%E5%90%8E%E5%8F%B0%E9%A1%B9%E7%9B%AE-mall-blue.svg" alt="后台项目"></a>
<a href="https://github.com/macrozheng/mall-admin-web"><img src="http://macro-oss.oss-cn-shenzhen.aliyuncs.com/mall/badge/%E5%89%8D%E7%AB%AF%E9%A1%B9%E7%9B%AE-mall--admin--web-green.svg" alt="前端项目"></a>
<a href="https://gitee.com/macrozheng/mall-app-web"><img src="http://macro-oss.oss-cn-shenzhen.aliyuncs.com/mall/badge/%E7%A0%81%E4%BA%91-%E9%A1%B9%E7%9B%AE%E5%9C%B0%E5%9D%80-orange.svg" alt="码云"></a>
</p>
## 前言
该项目为前后端分离项目的前端部分,后端项目`mall`地址:[传送门](https://github.com/macrozheng/mall)
## 项目介绍
`mall-app-web`是一个电商系统的移动端项目,基于`uni-app`实现。主要包括首页门户、商品推荐、商品搜索、商品展示、购物车、订单流程、会员中心、客户服务、帮助中心等功能。
### 项目演示
项目在线演示地址:[https://www.macrozheng.com/app/](https://www.macrozheng.com/app/)
### 效果展示
![](http://img.macrozheng.com/mall/project/mall_app_web_preview_01.png)![](http://img.macrozheng.com/mall/project/mall_app_web_preview_02.png)
![](http://img.macrozheng.com/mall/project/mall_app_web_preview_03.png)![](http://img.macrozheng.com/mall/project/mall_app_web_preview_04.png)
![](http://img.macrozheng.com/mall/project/mall_app_web_preview_05.png)![](http://img.macrozheng.com/mall/project/mall_app_web_preview_06.png)
![](http://img.macrozheng.com/mall/project/mall_app_web_preview_07.png)![](http://img.macrozheng.com/mall/project/mall_app_web_preview_08.png)
![](http://img.macrozheng.com/mall/project/mall_app_web_preview_09.png)![](http://img.macrozheng.com/mall/project/mall_app_web_preview_10.png)
### 技术选型
| 技术 | 说明 | 官网 |
| ------------ | ---------------- | --------------------------------------- |
| Vue | 核心前端框架 | https://vuejs.org |
| Vuex | 全局状态管理框架 | https://vuex.vuejs.org |
| uni-app | 移动端前端框架 | https://uniapp.dcloud.io |
| mix-mall | 电商项目模板 | https://ext.dcloud.net.cn/plugin?id=200 |
| luch-request | HTTP请求框架 | https://github.com/lei-mu/luch-request |
### 项目结构
``` lua
src -- 源码目录
├── api -- luch-request网络请求定义
├── components -- 通用组件封装
├── js_sdk -- 第三方sdk源码
├── static -- 图片等静态资源
├── store -- vuex的状态管理
├── utils -- 工具类
└── pages -- 前端页面
├── address -- 地址管理页
├── brand -- 商品品牌页
├── cart -- 购物车页
├── category -- 商品分类页
├── coupon -- 优惠券页
├── index -- 首页
├── money -- 支付页
├── notice -- 通知页
├── order -- 订单页
├── product -- 商品页
├── public -- 登录页
├── set -- 设置页
├── user -- 会员页
└── userinfo -- 会员信息页
```
## 搭建步骤
- 本项目使用了`uni-app`专用开发工具`HBuilder X`(App开发版)开发,下载地址:https://www.dcloud.io/hbuilderx.html
- 该项目为前后端分离项目,访问本地访问接口需搭建后台环境,搭建请参考后端项目[传送门](https://github.com/macrozheng/mall)
- 注意由于`mall-app-web`中的接口都在`mall-portal`模块中,所以一定要启动该模块;
- 访问在线接口无需搭建后台环境,只需将`utils/requestUtil.js`文件中的`config.baseUrl`改为线上地址即可:https://portal-api.macrozheng.com
- 克隆源代码到本地,使用`HBuilder X`打开;
-`HBuilder X`中使用`运行->运行到浏览器->Chrome`运行项目,运行成功后会自动打开下面地址(将浏览器改为手机模式):http://localhost:8080
- 如果浏览器没有启动的话,可以直接访问如下地址访问:http://localhost:8080
## 公众号
学习不走弯路,关注公众号「**macrozheng**」,回复「**学习路线**」,获取mall项目专属学习路线!
加微信群交流,公众号后台回复「**加群**」即可。
![公众号图片](http://macro-oss.oss-cn-shenzhen.aliyuncs.com/mall/banner/qrcode_for_macrozheng_258.jpg)
## 许可证
[Apache License 2.0](https://github.com/macrozheng/mall-app-web/blob/master/LICENSE)
Copyright (c) 2020-2024 macrozheng
\ No newline at end of file
import request from '@/utils/requestUtil'
export function fetchAddressList() {
return request({
method: 'GET',
url: '/member/address/list'
})
}
export function fetchAddressDetail(id) {
return request({
method: 'GET',
url: `/member/address/${id}`
})
}
export function addAddress(data) {
return request({
method: 'POST',
url: '/member/address/add',
data:data
})
}
export function updateAddress(data) {
return request({
method: 'POST',
url: `/member/address/update/${data.id}`,
data:data
})
}
export function deleteAddress(id) {
return request({
method: 'POST',
url: `/member/address/delete/${id}`
})
}
import request from '@/utils/requestUtil'
export function getBrandDetail(id) {
return request({
method: 'GET',
url: `/brand/detail/${id}`,
})
}
export function fetchBrandProductList(params) {
return request({
method: 'GET',
url: '/brand/productList',
params:params
})
}
export function fetchBrandRecommendList(params) {
return request({
method: 'GET',
url: '/brand/recommendList',
params:params
})
}
\ No newline at end of file
import request from '@/utils/requestUtil'
export function addCartItem(data) {
return request({
method: 'POST',
url: '/cart/add',
data: data
})
}
export function fetchCartList() {
return request({
method: 'GET',
url: '/cart/list'
})
}
export function deletCartItem(params) {
return request({
method: 'POST',
url: '/cart/delete',
params:params
})
}
export function updateQuantity(params) {
return request({
method: 'GET',
url: '/cart/update/quantity',
params:params
})
}
export function clearCartList() {
return request({
method: 'POST',
url: '/cart/clear'
})
}
\ No newline at end of file
import request from '@/utils/requestUtil'
export function fetchProductCouponList(productId) {
return request({
method: 'GET',
url: `/member/coupon/listByProduct/${productId}`,
})
}
export function addMemberCoupon(couponId) {
return request({
method: 'POST',
url: `/member/coupon/add/${couponId}`,
})
}
export function fetchMemberCouponList(useStatus) {
return request({
method: 'GET',
url: '/member/coupon/list',
params:{useStatus:useStatus}
})
}
\ No newline at end of file
import request from '@/utils/requestUtil'
export function fetchContent() {
return request({
method: 'GET',
url: '/home/content'
})
}
export function fetchRecommendProductList(params) {
return request({
method: 'GET',
url: '/home/recommendProductList',
params:params
})
}
export function fetchProductCateList(parentId) {
return request({
method: 'GET',
url: '/home/productCateList/'+parentId,
})
}
export function fetchNewProductList(params) {
return request({
method: 'GET',
url: '/home/newProductList',
params:params
})
}
export function fetchHotProductList(params) {
return request({
method: 'GET',
url: '/home/hotProductList',
params:params
})
}
import request from '@/utils/requestUtil'
export function memberLogin(data) {
return request({
method: 'POST',
url: '/sso/login',
header: {
'content-type': 'application/x-www-form-urlencoded;charset=utf-8'
},
data: data
})
}
export function memberInfo() {
return request({
method: 'GET',
url: '/sso/info'
})
}
import request from '@/utils/requestUtil'
export function createBrandAttention(data) {
return request({
method: 'POST',
url: '/member/attention/add',
data: data
})
}
export function deleteBrandAttention(params) {
return request({
method: 'POST',
url: '/member/attention/delete',
params: params
})
}
export function fetchBrandAttentionList(params) {
return request({
method: 'GET',
url: '/member/attention/list',
params:params
})
}
export function brandAttentionDetail(params) {
return request({
method: 'GET',
url: '/member/attention/detail',
params: params
})
}
export function clearBrandAttention() {
return request({
method: 'POST',
url: '/member/attention/clear'
})
}
\ No newline at end of file
import request from '@/utils/requestUtil'
export function createProductCollection(data) {
return request({
method: 'POST',
url: '/member/productCollection/add',
data: data
})
}
export function deleteProductCollection(params) {
return request({
method: 'POST',
url: '/member/productCollection/delete',
params: params
})
}
export function fetchProductCollectionList(params) {
return request({
method: 'GET',
url: '/member/productCollection/list',
params:params
})
}
export function productCollectionDetail(params) {
return request({
method: 'GET',
url: '/member/productCollection/detail',
params: params
})
}
export function clearProductCollection() {
return request({
method: 'POST',
url: '/member/productCollection/clear'
})
}
\ No newline at end of file
import request from '@/utils/requestUtil'
export function createReadHistory(data) {
return request({
method: 'POST',
url: '/member/readHistory/create',
data: data
})
}
export function fetchReadHistoryList(params) {
return request({
method: 'GET',
url: '/member/readHistory/list',
params: params
})
}
export function clearReadHistory() {
return request({
method: 'POST',
url: '/member/readHistory/clear'
})
}
\ No newline at end of file
import request from '@/utils/requestUtil'
export function generateConfirmOrder(data) {
return request({
method: 'POST',
url: '/order/generateConfirmOrder',
data: data
})
}
export function generateOrder(data) {
return request({
method: 'POST',
url: '/order/generateOrder',
data: data
})
}
export function fetchOrderList(params) {
return request({
method: 'GET',
url: '/order/list',
params: params
})
}
export function payOrderSuccess(data) {
return request({
method: 'POST',
url: '/order/paySuccess',
header: {
'content-type': 'application/x-www-form-urlencoded;charset=utf-8'
},
data: data
})
}
export function fetchOrderDetail(orderId) {
return request({
method: 'GET',
url: `/order/detail/${orderId}`
})
}
export function cancelUserOrder(data) {
return request({
method: 'POST',
url: '/order/cancelUserOrder',
header: {
'content-type': 'application/x-www-form-urlencoded;charset=utf-8'
},
data: data
})
}
export function confirmReceiveOrder(data) {
return request({
method: 'POST',
url: '/order/confirmReceiveOrder',
header: {
'content-type': 'application/x-www-form-urlencoded;charset=utf-8'
},
data: data
})
}
export function deleteUserOrder(data) {
return request({
method: 'POST',
url: '/order/deleteOrder',
header: {
'content-type': 'application/x-www-form-urlencoded;charset=utf-8'
},
data: data
})
}
export function fetchAliapyStatus(params) {
return request({
method: 'GET',
url: '/alipay/query',
params: params
})
}
\ No newline at end of file
import request from '@/utils/requestUtil'
export function searchProductList(params) {
return request({
method: 'GET',
url: '/product/search',
params: params
})
}
export function fetchCategoryTreeList() {
return request({
method: 'GET',
url: '/product/categoryTreeList'
})
}
export function fetchProductDetail(id) {
return request({
method: 'GET',
url: '/product/detail/'+id
})
}
差异被折叠。
<template>
<view class="content">
<view class="mix-list-cell" :class="border" @click="eventClick" hover-class="cell-hover" :hover-stay-time="50">
<text
v-if="icon"
class="cell-icon yticon"
:style="[{
color: iconColor,
}]"
:class="icon"
></text>
<text class="cell-tit clamp">{{title}}</text>
<text v-if="tips" class="cell-tip">{{tips}}</text>
<text class="cell-more yticon"
:class="typeList[navigateType]"
></text>
</view>
</view>
</template>
<script>
/**
* 简单封装了下, 应用范围比较狭窄,可以在此基础上进行扩展使用
* 比如加入image, iconSize可控等
*/
export default {
data() {
return {
typeList: {
left: 'icon-zuo',
right: 'icon-you',
up: 'icon-shang',
down: 'icon-xia'
},
}
},
props: {
icon: {
type: String,
default: ''
},
title: {
type: String,
default: '标题'
},
tips: {
type: String,
default: ''
},
navigateType: {
type: String,
default: 'right'
},
border: {
type: String,
default: 'b-b'
},
hoverClass: {
type: String,
default: 'cell-hover'
},
iconColor: {
type: String,
default: '#333'
}
},
methods: {
eventClick(){
this.$emit('eventClick');
}
},
}
</script>
<style lang='scss'>
.icon .mix-list-cell.b-b:after{
left: 90upx;
}
.mix-list-cell{
display:flex;
align-items:baseline;
padding: 20upx $page-row-spacing;
line-height:60upx;
position:relative;
&.cell-hover{
background:#fafafa;
}
&.b-b:after{
left: 30upx;
}
.cell-icon{
align-self:center;
width:56upx;
max-height:60upx;
font-size:38upx;
}
.cell-more{
align-self: center;
font-size:30upx;
color:$font-color-base;
margin-left:$uni-spacing-row-sm;
}
.cell-tit{
flex: 1;
font-size: $font-base;
color: $font-color-dark;
margin-right:10upx;
}
.cell-tip{
font-size: $font-sm+2upx;
color: $font-color-light;
}
}
</style>
<template>
<!-- loading 加载 -->
<view class="mix-loading-content">
<view class="mix-loading-wrapper">
<image
class="mix-loading-icon"
src="data:image/png;base64,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">
</image>
</view>
</view>
</template>
<script>
export default {
props: {
top: {
//距离顶部距离,单位upx
type: Number,
default: 0
},
},
data() {
return {
};
},
methods: {
}
}
</script>
<style>
.mix-loading-content{
display:flex;
justify-content: center;
align-items: center;
position: absolute;
left: 0;
top: 0;
width: 100%;
height: 100%;
background-color: transparent;
}
.mix-loading-wrapper{
display: flex;
justify-content: center;
align-items: center;
animation: loading .5s ease-in infinite both alternate;
}
.mix-loading-icon{
width: 80upx;
height: 80upx;
transition: .3s;
}
@keyframes loading {
0% {
transform: translateY(-20upx) scaleX(1);
}
100% {
transform: translateY(4upx) scaleX(1.3);
}
}
</style>
<template>
<view v-if="show" class="mask" @click="toggleMask" @touchmove.stop.prevent="stopPrevent"
:style="{backgroundColor: backgroundColor}"
>
<view
class="mask-content"
@click.stop.prevent="stopPrevent"
:style="[{
height: config.height,
transform: transform
}]"
>
<scroll-view class="view-content" scroll-y>
<view class="share-header">
分享到
</view>
<view class="share-list">
<view
v-for="(item, index) in shareList" :key="index"
class="share-item"
@click="shareToFriend(item.text)"
>
<image :src="item.icon" mode=""></image>
<text>{{item.text}}</text>
</view>
</view>
</scroll-view>
<view class="bottom b-t" @click="toggleMask">取消</view>
</view>
</view>
</template>
<script>
export default {
data() {
return {
transform: 'translateY(50vh)',
timer: 0,
backgroundColor: 'rgba(0,0,0,0)',
show: false,
config: {},
};
},
props:{
contentHeight:{
type: Number,
default: 0
},
//是否是tabbar页面
hasTabbar:{
type: Boolean,
default: false
},
shareList:{
type: Array,
default: function(){
return [];
}
}
},
created() {
const height = uni.upx2px(this.contentHeight) + 'px';
this.config = {
height: height,
transform: `translateY(${height})`,
backgroundColor: 'rgba(0,0,0,.4)',
}
this.transform = this.config.transform;
},
methods:{
toggleMask(){
//防止高频点击
if(this.timer == 1){
return;
}
this.timer = 1;
setTimeout(()=>{
this.timer = 0;
}, 500)
if(this.show){
this.transform = this.config.transform;
this.backgroundColor = 'rgba(0,0,0,0)';
setTimeout(()=>{
this.show = false;
this.hasTabbar && uni.showTabBar();
}, 200)
return;
}
this.show = true;
//等待mask重绘完成执行
if(this.hasTabbar){
uni.hideTabBar({
success: () => {
setTimeout(()=>{
this.backgroundColor = this.config.backgroundColor;
this.transform = 'translateY(0px)';
}, 10)
}
});
}else{
setTimeout(()=>{
this.backgroundColor = this.config.backgroundColor;
this.transform = 'translateY(0px)';
}, 10)
}
},
//防止冒泡和滚动穿透
stopPrevent(){},
//分享操作
shareToFriend(type){
this.$api.msg(`分享给${type}`);
this.toggleMask();
},
}
}
</script>
<style lang='scss'>
.mask{
position:fixed;
left: 0;
top: 0;
right: 0;
bottom: 0;
display:flex;
justify-content: center;
align-items: flex-end;
z-index: 998;
transition: .3s;
.bottom{
position:absolute;
left: 0;
bottom: 0;
display:flex;
justify-content: center;
align-items: center;
width: 100%;
height: 90upx;
background: #fff;
z-index: 9;
font-size: $font-base + 2upx;
color: $font-color-dark;
}
}
.mask-content{
width: 100%;
height: 580upx;
transition: .3s;
background: #fff;
&.has-bottom{
padding-bottom: 90upx;
}
.view-content{
height: 100%;
}
}
.share-header{
height: 110upx;
font-size: $font-base+2upx;
color: font-color-dark;
display:flex;
align-items:center;
justify-content: center;
padding-top: 10upx;
&:before, &:after{
content: '';
width: 240upx;
heighg: 0;
border-top: 1px solid $border-color-base;
transform: scaleY(.5);
margin-right: 30upx;
}
&:after{
margin-left: 30upx;
margin-right: 0;
}
}
.share-list{
display:flex;
flex-wrap: wrap;
}
.share-item{
min-width: 33.33%;
display:flex;
flex-direction: column;
justify-content: center;
align-items: center;
height: 180upx;
image{
width: 80upx;
height: 80upx;
margin-bottom: 16upx;
}
text{
font-size: $font-base;
color: $font-color-base;
}
}
</style>
<template>
<view class="uni-load-more">
<view class="uni-load-more__img" v-show="status === 'loading' && showIcon">
<view class="load1">
<view :style="{background:color}"></view>
<view :style="{background:color}"></view>
<view :style="{background:color}"></view>
<view :style="{background:color}"></view>
</view>
<view class="load2">
<view :style="{background:color}"></view>
<view :style="{background:color}"></view>
<view :style="{background:color}"></view>
<view :style="{background:color}"></view>
</view>
<view class="load3">
<view :style="{background:color}"></view>
<view :style="{background:color}"></view>
<view :style="{background:color}"></view>
<view :style="{background:color}"></view>
</view>
</view>
<text class="uni-load-more__text" :style="{color:color}">{{status === 'more' ? contentText.contentdown : (status === 'loading' ? contentText.contentrefresh : contentText.contentnomore)}}</text>
</view>
</template>
<script>
export default {
name: "uni-load-more",
props: {
status: {
//上拉的状态:more-loading前;loading-loading中;noMore-没有更多了
type: String,
default: 'more'
},
showIcon: {
type: Boolean,
default: true
},
color: {
type: String,
default: "#777777"
},
contentText: {
type: Object,
default () {
return {
contentdown: "上拉显示更多",
contentrefresh: "正在加载...",
contentnomore: "没有更多数据了"
};
}
}
},
data() {
return {}
}
}
</script>
<style>
@charset "UTF-8";
.uni-load-more {
display: flex;
flex-direction: row;
height: 80upx;
align-items: center;
justify-content: center
}
.uni-load-more__text {
font-size: 28upx;
color: #999
}
.uni-load-more__img {
height: 24px;
width: 24px;
margin-right: 10px
}
.uni-load-more__img>view {
position: absolute
}
.uni-load-more__img>view view {
width: 6px;
height: 2px;
border-top-left-radius: 1px;
border-bottom-left-radius: 1px;
background: #999;
position: absolute;
opacity: .2;
transform-origin: 50%;
animation: load 1.56s ease infinite
}
.uni-load-more__img>view view:nth-child(1) {
transform: rotate(90deg);
top: 2px;
left: 9px
}
.uni-load-more__img>view view:nth-child(2) {
transform: rotate(180deg);
top: 11px;
right: 0
}
.uni-load-more__img>view view:nth-child(3) {
transform: rotate(270deg);
bottom: 2px;
left: 9px
}
.uni-load-more__img>view view:nth-child(4) {
top: 11px;
left: 0
}
.load1,
.load2,
.load3 {
height: 24px;
width: 24px
}
.load2 {
transform: rotate(30deg)
}
.load3 {
transform: rotate(60deg)
}
.load1 view:nth-child(1) {
animation-delay: 0s
}
.load2 view:nth-child(1) {
animation-delay: .13s
}
.load3 view:nth-child(1) {
animation-delay: .26s
}
.load1 view:nth-child(2) {
animation-delay: .39s
}
.load2 view:nth-child(2) {
animation-delay: .52s
}
.load3 view:nth-child(2) {
animation-delay: .65s
}
.load1 view:nth-child(3) {
animation-delay: .78s
}
.load2 view:nth-child(3) {
animation-delay: .91s
}
.load3 view:nth-child(3) {
animation-delay: 1.04s
}
.load1 view:nth-child(4) {
animation-delay: 1.17s
}
.load2 view:nth-child(4) {
animation-delay: 1.3s
}
.load3 view:nth-child(4) {
animation-delay: 1.43s
}
@-webkit-keyframes load {
0% {
opacity: 1
}
100% {
opacity: .2
}
}
</style>
\ No newline at end of file
<template>
<view class="uni-numbox">
<view class="uni-numbox-minus"
@click="_calcValue('subtract')"
>
<text class="yticon icon--jianhao" :class="minDisabled?'uni-numbox-disabled': ''" ></text>
</view>
<input
class="uni-numbox-value"
type="number"
:disabled="disabled"
:value="inputValue"
@blur="_onBlur"
>
<view
class="uni-numbox-plus"
@click="_calcValue('add')"
>
<text class="yticon icon-jia2" :class="maxDisabled?'uni-numbox-disabled': ''" ></text>
</view>
</view>
</template>
<script>
export default {
name: 'uni-number-box',
props: {
isMax: {
type: Boolean,
default: false
},
isMin: {
type: Boolean,
default: false
},
index: {
type: Number,
default: 0
},
value: {
type: Number,
default: 0
},
min: {
type: Number,
default: -Infinity
},
max: {
type: Number,
default: Infinity
},
step: {
type: Number,
default: 1
},
disabled: {
type: Boolean,
default: false
}
},
data() {
return {
inputValue: this.value,
minDisabled: false,
maxDisabled: false
}
},
created(){
this.maxDisabled = this.isMax;
this.minDisabled = this.isMin;
},
computed: {
},
watch: {
inputValue(number) {
const data = {
number: number,
index: this.index
}
this.$emit('eventChange', data);
}
},
methods: {
_calcValue(type) {
const scale = this._getDecimalScale();
let value = this.inputValue * scale;
let newValue = 0;
let step = this.step * scale;
if(type === 'subtract'){
newValue = value - step;
if (newValue <= this.min){
this.minDisabled = true;
}
if(newValue < this.min){
newValue = this.min
}
if(newValue < this.max && this.maxDisabled === true){
this.maxDisabled = false;
}
}else if(type === 'add'){
newValue = value + step;
if (newValue >= this.max){
this.maxDisabled = true;
}
if(newValue > this.max){
newValue = this.max
}
if(newValue > this.min && this.minDisabled === true){
this.minDisabled = false;
}
}
if(newValue === value){
return;
}
this.inputValue = newValue / scale;
},
_getDecimalScale() {
let scale = 1;
// 浮点型
if (~~this.step !== this.step) {
scale = Math.pow(10, (this.step + '').split('.')[1].length);
}
return scale;
},
_onBlur(event) {
let value = event.detail.value;
if (!value) {
this.inputValue = 0;
return
}
value = +value;
if (value > this.max) {
value = this.max;
} else if (value < this.min) {
value = this.min
}
this.inputValue = value
}
}
}
</script>
<style>
.uni-numbox {
position:absolute;
left: 30upx;
bottom: 0;
display: flex;
justify-content: flex-start;
align-items: center;
width:230upx;
height: 70upx;
background:#f5f5f5;
}
.uni-numbox-minus,
.uni-numbox-plus {
margin: 0;
background-color: #f5f5f5;
width: 70upx;
height: 100%;
line-height: 70upx;
text-align: center;
position: relative;
}
.uni-numbox-minus .yticon,
.uni-numbox-plus .yticon{
font-size: 36upx;
color: #555;
}
.uni-numbox-minus {
border-right: none;
border-top-left-radius: 6upx;
border-bottom-left-radius: 6upx;
}
.uni-numbox-plus {
border-left: none;
border-top-right-radius: 6upx;
border-bottom-right-radius: 6upx;
}
.uni-numbox-value {
position: relative;
background-color: #f5f5f5;
width: 90upx;
height: 50upx;
text-align: center;
padding: 0;
font-size: 30upx;
}
.uni-numbox-disabled.yticon {
color: #d6d6d6;
}
</style>
<template>
<view class="upload-content">
<block v-for="(item, index) in imageList" :key="index">
<view class="upload-item">
<image class="upload-img" :src="item.filePath" mode="aspectFill" @click="previewImage(index)"></image>
<image class="upload-del-btn"
@click="delImage(index)"
src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAACYAAAAmCAYAAACoPemuAAAAGXRFWHRTb2Z0d2FyZQBBZG9iZSBJbWFnZVJlYWR5ccllPAAAAyJpVFh0WE1MOmNvbS5hZG9iZS54bXAAAAAAADw/eHBhY2tldCBiZWdpbj0i77u/IiBpZD0iVzVNME1wQ2VoaUh6cmVTek5UY3prYzlkIj8+IDx4OnhtcG1ldGEgeG1sbnM6eD0iYWRvYmU6bnM6bWV0YS8iIHg6eG1wdGs9IkFkb2JlIFhNUCBDb3JlIDUuMy1jMDExIDY2LjE0NTY2MSwgMjAxMi8wMi8wNi0xNDo1NjoyNyAgICAgICAgIj4gPHJkZjpSREYgeG1sbnM6cmRmPSJodHRwOi8vd3d3LnczLm9yZy8xOTk5LzAyLzIyLXJkZi1zeW50YXgtbnMjIj4gPHJkZjpEZXNjcmlwdGlvbiByZGY6YWJvdXQ9IiIgeG1sbnM6eG1wPSJodHRwOi8vbnMuYWRvYmUuY29tL3hhcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RSZWY9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZVJlZiMiIHhtcDpDcmVhdG9yVG9vbD0iQWRvYmUgUGhvdG9zaG9wIENTNiAoV2luZG93cykiIHhtcE1NOkluc3RhbmNlSUQ9InhtcC5paWQ6RjNBODgzQjUwNDM5MTFFOUJDMjlGN0UwRTJGMjVCNjQiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6RjNBODgzQjYwNDM5MTFFOUJDMjlGN0UwRTJGMjVCNjQiPiA8eG1wTU06RGVyaXZlZEZyb20gc3RSZWY6aW5zdGFuY2VJRD0ieG1wLmlpZDpGM0E4ODNCMzA0MzkxMUU5QkMyOUY3RTBFMkYyNUI2NCIgc3RSZWY6ZG9jdW1lbnRJRD0ieG1wLmRpZDpGM0E4ODNCNDA0MzkxMUU5QkMyOUY3RTBFMkYyNUI2NCIvPiA8L3JkZjpEZXNjcmlwdGlvbj4gPC9yZGY6UkRGPiA8L3g6eG1wbWV0YT4gPD94cGFja2V0IGVuZD0iciI/PuYQTIAAAAHNSURBVHjazJgxasMwFIbjnsD4BDmCLxDQDZrNQ5bcILlBvXvpDRLIWmiHFkyH2t0LyVbwEkPpbE/eYlUCGYQax096UuIffkIgij7ryU/vyZuYy2e+Z56K70R8lsJcOfPn5AriMCvmZ2YKdMW8EQ/hRA9iEorwXlpZtObMRySQ6kysvrFWloHUEIcmUBuHUDLccmxQskErt7wyVLdy00tQ4Q2g5De2V9kNwWjffiNDA33f/42i6E13Qj5uNptlwJD+SyODuaqqqi/KVBTFOxSKA7VtW/NxSZI8AcbE2nuraZpvKgSBk6G4drsdBOwogz1CwAghH/JEl+BUKJ1VltPHHjoIAoeE4l53YFqb+RKcBSgqyqXhtxEKZwkKB3YOThYCqjPuCDoHZwGK3mGLtdPp5LmqTI1Dqe4p26EkNqA4iE6eg2z+iQ0okyQMATvYgLIIt9Y6knTyFBIudHqIq3BpmkL60VJ9O0sXZY8MBwSLtdNGEAQ/i8XiVXcz83EcEPDbuq/fzMdYWt+6GTmMsX2rh9q3TtsxNrzXhKt1rwg6rR1Dhdjqo7QMlWOvoWTF4imxQMRF7eaL8L5ohmwrLgDB8pCQc8DlcG7y538CDABJNGPqfaJgfgAAAABJRU5ErkJggg=="
mode="scaleToFill">
</image>
<view class="upload-progress" v-if="item.progress < 100">{{item.progress}}%</view>
</view>
</block>
<view class="upload-add-btn" v-if="rduLength > 0" @click="chooseImage"></view>
</view>
</template>
<script>
export default {
data() {
return {
imageList: []
};
},
props: {
url: {
type: String,
value: '' //上传接口地址
},
count: {
type: Number,
value: 4 //单次可选择的图片数量
},
length: {
type: Number,
value: 50 //可上传总数量
}
},
computed: {
rduLength(){
return this.length - this.imageList.length;
}
},
methods: {
//选择图片
chooseImage: function(){
uni.chooseImage({
count: this.rduLength < this.count ? this.rduLength : this.count, //最多可以选择的图片张数,默认9
sizeType: ['original', 'compressed'], //original 原图,compressed 压缩图,默认二者都有
sourceType: ['album'], //album 从相册选图,camera 使用相机,默认二者都有
success: (res)=> {
const images = res.tempFilePaths;
this.uploadFiles(images);
}
});
},
//上传图片
async uploadFiles(images){
this.imageList.push({
filePath: images[0],
progress: 0
});
uni.showLoading({
title: '请稍后..',
mask: true,
})
try{
const uploadUrl = await this.uploadImage(images[0]);
}catch(err){
console.log(err);
return;
}
if(uploadUrl !== false){
images.splice(0, 1);
this.imageList[this.imageList.length - 1].src = uploadUrl;
//判断是否需要继续上传
if(images.length > 0 && this.rduLength > 0){
this.uploadFiles(images);
}else{
uni.hideLoading();
}
}else{
//上传失败处理
this.imageList.pop();
uni.hideLoading();
uni.showToast({
title: '上传中出现问题,已终止上传',
icon: 'none',
mask: true,
duration: 2000
});
}
},
uploadImage: function(file){
return new Promise((resolve, reject)=> {
//发送给后端的附加参数
const formData = {
thumb_mode: 1,
};
this.uploadTask = uni.uploadFile({
url: this.url,
filePath: file,
name: 'file',
formData: formData,
success(uploadFileResult){
const uploadFileRes = JSON.parse(uploadFileResult.data) || {};
if(uploadFileRes.status === 1 && uploadFileRes.data){
resolve(uploadFileRes.data);
}else{
reject('接口返回错误');
}
},
fail(){
reject('网络链接错误');
}
});
//上传进度
this.uploadTask.onProgressUpdate((progressRes)=> {
this.imageList[this.imageList.length - 1].progress = progressRes.progress;
});
});
},
//删除图片
delImage: function(index){
uni.showModal({
content: '确定要放弃这张图片么?',
success: (confirmRes)=> {
if (confirmRes.confirm) {
this.imageList.splice(index, 1);
}
}
});
},
//预览图片
previewImage: function(index){
const urls = [];
this.imageList.forEach((item)=> {
urls.push(item.filePath);
})
uni.previewImage({
current: urls[index],
urls: urls,
indicator: "number"
})
}
}
}
</script>
<style lang="scss">
.upload-content{
padding:24upx 0 0 28upx;
background-color: #fff;
overflow:hidden;
}
.upload-item{
position: relative;
float:left;
width:150upx;
height:150upx;
margin-right:30upx;
margin-bottom:30upx;
&:nth-child(4n){
margin-right:0;
}
.upload-img{
width:100%;
height:100%;
border-radius:8upx;
}
.upload-del-btn{
position: absolute;
right:-16upx;
top:-14upx;
width:36upx;
height:36upx;
border: 4upx solid #fff;
border-radius: 100px;
}
.upload-progress{
position: absolute;
left:0;
top:0;
display:flex;
align-items:center;
justify-content: center;
width:100%;
height:100%;
background-color: rgba(0,0,0,.4);
color:#fff;
font-size:24upx;
border-radius:8upx;
}
}
.upload-add-btn {
position: relative;
float:left;
width: 150upx;
height: 150upx;
z-index: 99;
border-radius:8upx;
background:#f9f9f9;
&:before,
&:after {
content: " ";
position: absolute;
top: 50%;
left: 50%;
-webkit-transform: translate(-50%, -50%);
transform: translate(-50%, -50%);
width: 4upx;
height: 60upx;
background-color: #d6d6d6;
}
&:after {
width: 60upx;
height: 4upx;
}
&:active {
background-color: #f7f7f7;
}
}
</style>
差异被折叠。
/**
* Request 1.0.5
* @Class Request
* @description luch-request 1.0.4 http请求插件
* @Author lu-ch
* @Date 2019-12-12
* @Email webwork.s@qq.com
* http://ext.dcloud.net.cn/plugin?id=392
*/
export default class Request {
config = {
baseUrl: '',
header: {
'content-type': 'application/json;charset=UTF-8'
},
method: 'GET',
dataType: 'json',
// #ifndef MP-ALIPAY || APP-PLUS
responseType: 'text',
// #endif
custom: {},
// #ifdef MP-ALIPAY
timeout: 30000,
// #endif
// #ifdef APP-PLUS
sslVerify: true
// #endif
}
static posUrl (url) { /* 判断url是否为绝对路径 */
return /(http|https):\/\/([\w.]+\/?)\S*/.test(url)
}
static addQueryString (params) {
let paramsData = ''
Object.keys(params).forEach(function (key) {
paramsData += key + '=' + encodeURIComponent(params[key]) + '&'
})
return paramsData.substring(0, paramsData.length - 1)
}
/**
* @property {Function} request 请求拦截器
* @property {Function} response 响应拦截器
* @type {{request: Request.interceptor.request, response: Request.interceptor.response}}
*/
interceptor = {
/**
* @param {Request~requestCallback} cb - 请求之前拦截,接收一个函数(config, cancel)=> {return config}。第一个参数为全局config,第二个参数为函数,调用则取消本次请求。
*/
request: (cb) => {
if (cb) {
this.requestBeforeFun = cb
}
},
/**
* @param {Request~responseCallback} cb 响应拦截器,对响应数据做点什么
* @param {Request~responseErrCallback} ecb 响应拦截器,对响应错误做点什么
*/
response: (cb, ecb) => {
if (cb && ecb) {
this.requestComFun = cb
this.requestComFail = ecb
}
}
}
requestBeforeFun (config) {
return config
}
requestComFun (response) {
return response
}
requestComFail (response) {
return response
}
/**
* 自定义验证器,如果返回true 则进入响应拦截器的响应成功函数(resolve),否则进入响应拦截器的响应错误函数(reject)
* @param { Number } statusCode - 请求响应体statusCode(只读)
* @return { Boolean } 如果为true,则 resolve, 否则 reject
*/
validateStatus (statusCode) {
return statusCode === 200
}
/**
* @Function
* @param {Request~setConfigCallback} f - 设置全局默认配置
*/
setConfig (f) {
this.config = f(this.config)
}
/**
* @Function
* @param {Object} options - 请求配置项
* @prop {String} options.url - 请求路径
* @prop {Object} options.data - 请求参数
* @prop {Object} [options.responseType = config.responseType] [text|arraybuffer] - 响应的数据类型
* @prop {Object} [options.dataType = config.dataType] - 如果设为 json,会尝试对返回的数据做一次 JSON.parse
* @prop {Object} [options.header = config.header] - 请求header
* @prop {Object} [options.method = config.method] - 请求方法
* @returns {Promise<unknown>}
*/
async request (options = {}) {
options.baseUrl = this.config.baseUrl
options.dataType = options.dataType || this.config.dataType
// #ifndef MP-ALIPAY || APP-PLUS
options.responseType = options.responseType || this.config.responseType
// #endif
// #ifdef MP-ALIPAY
options.timeout = options.timeout || this.config.timeout
// #endif
options.url = options.url || ''
options.data = options.data || {}
options.params = options.params || {}
options.header = options.header || this.config.header
options.method = options.method || this.config.method
options.custom = { ...this.config.custom, ...(options.custom || {}) }
// #ifdef APP-PLUS
options.sslVerify = options.sslVerify === undefined ? this.config.sslVerify : options.sslVerify
// #endif
return new Promise((resolve, reject) => {
let next = true
let handleRe = {}
options.complete = (response) => {
response.config = handleRe
if (this.validateStatus(response.statusCode)) { // 成功
response = this.requestComFun(response)
resolve(response)
} else {
response = this.requestComFail(response)
reject(response)
}
}
const cancel = (t = 'handle cancel', config = options) => {
const err = {
errMsg: t,
config: config
}
reject(err)
next = false
}
handleRe = { ...this.requestBeforeFun(options, cancel) }
const _config = { ...handleRe }
if (!next) return
delete _config.custom
let mergeUrl = Request.posUrl(_config.url) ? _config.url : (_config.baseUrl + _config.url)
if (JSON.stringify(_config.params) !== '{}') {
const paramsH = Request.addQueryString(_config.params)
mergeUrl += mergeUrl.indexOf('?') === -1 ? `?${paramsH}` : `&${paramsH}`
}
_config.url = mergeUrl
uni.request(_config)
})
}
get (url, options = {}) {
return this.request({
url,
method: 'GET',
...options
})
}
post (url, data, options = {}) {
return this.request({
url,
data,
method: 'POST',
...options
})
}
// #ifndef MP-ALIPAY
put (url, data, options = {}) {
return this.request({
url,
data,
method: 'PUT',
...options
})
}
// #endif
// #ifdef APP-PLUS || H5 || MP-WEIXIN || MP-BAIDU
delete (url, data, options = {}) {
return this.request({
url,
data,
method: 'DELETE',
...options
})
}
// #endif
// #ifdef APP-PLUS || H5 || MP-WEIXIN
connect (url, data, options = {}) {
return this.request({
url,
data,
method: 'CONNECT',
...options
})
}
// #endif
// #ifdef APP-PLUS || H5 || MP-WEIXIN || MP-BAIDU
head (url, data, options = {}) {
return this.request({
url,
data,
method: 'HEAD',
...options
})
}
// #endif
// #ifdef APP-PLUS || H5 || MP-WEIXIN || MP-BAIDU
options (url, data, options = {}) {
return this.request({
url,
data,
method: 'OPTIONS',
...options
})
}
// #endif
// #ifdef APP-PLUS || H5 || MP-WEIXIN
trace (url, data, options = {}) {
return this.request({
url,
data,
method: 'TRACE',
...options
})
}
// #endif
upload (url, {
// #ifdef APP-PLUS
files,
// #endif
// #ifdef MP-ALIPAY
fileType,
// #endif
filePath,
name,
header,
formData,
custom
}) {
return new Promise((resolve, reject) => {
let next = true
let handleRe = {}
const globalHeader = { ...this.config.header }
delete globalHeader['content-type']
const pubConfig = {
baseUrl: this.config.baseUrl,
url,
// #ifdef APP-PLUS
files,
// #endif
// #ifdef MP-ALIPAY
fileType,
// #endif
filePath,
method: 'UPLOAD',
name,
header: header || globalHeader,
formData,
custom: { ...this.config.custom, ...(custom || {}) },
complete: (response) => {
response.config = handleRe
if (response.statusCode === 200) { // 成功
response = this.requestComFun(response)
resolve(response)
} else {
response = this.requestComFail(response)
reject(response)
}
}
}
const cancel = (t = 'handle cancel', config = pubConfig) => {
const err = {
errMsg: t,
config: config
}
reject(err)
next = false
}
handleRe = { ...this.requestBeforeFun(pubConfig, cancel) }
const _config = { ...handleRe }
if (!next) return
delete _config.custom
_config.url = Request.posUrl(_config.url) ? _config.url : (_config.baseUrl + _config.url)
uni.uploadFile(_config)
})
}
}
/**
* setConfig回调
* @return {Object} - 返回操作后的config
* @callback Request~setConfigCallback
* @param {Object} config - 全局默认config
*/
/**
* 请求拦截器回调
* @return {Object} - 返回操作后的config
* @callback Request~requestCallback
* @param {Object} config - 全局config
* @param {Function} [cancel] - 取消请求钩子,调用会取消本次请求
*/
/**
* 响应拦截器回调
* @return {Object} - 返回操作后的response
* @callback Request~responseCallback
* @param {Object} response - 请求结果 response
*/
/**
* 响应错误拦截器回调
* @return {Object} - 返回操作后的response
* @callback Request~responseErrCallback
* @param {Object} response - 请求结果 response
*/
import Vue from 'vue'
import store from './store'
import App from './App'
const msg = (title, duration=1500, mask=false, icon='none')=>{
//统一提示方便全局修改
if(Boolean(title) === false){
return;
}
uni.showToast({
title,
duration,
mask,
icon
});
}
const prePage = ()=>{
let pages = getCurrentPages();
let prePage = pages[pages.length - 2];
// #ifdef H5
return prePage;
// #endif
return prePage.$vm;
}
Vue.config.productionTip = false
Vue.prototype.$fire = new Vue();
Vue.prototype.$store = store;
Vue.prototype.$api = {msg, prePage};
App.mpType = 'app'
const app = new Vue({
...App
})
app.$mount()
\ No newline at end of file
{
"name" : "mall-app-web",
"appid" : "",
"description" : "",
"versionName" : "1.0.0",
"versionCode" : "100",
"transformPx" : false,
"app-plus" : {
/* 5+App特有相关 */
"usingComponents" : true,
"splashscreen" : {
"alwaysShowBeforeRender" : true,
"waiting" : true,
"autoclose" : true,
"delay" : 0
},
"modules" : {},
/* 模块配置 */
"distribute" : {
/* 应用发布信息 */
"android" : {
/* android打包配置 */
"permissions" : [
"<uses-permission android:name=\"android.permission.CHANGE_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.MOUNT_UNMOUNT_FILESYSTEMS\"/>",
"<uses-permission android:name=\"android.permission.READ_CONTACTS\"/>",
"<uses-permission android:name=\"android.permission.VIBRATE\"/>",
"<uses-permission android:name=\"android.permission.READ_LOGS\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_WIFI_STATE\"/>",
"<uses-feature android:name=\"android.hardware.camera.autofocus\"/>",
"<uses-permission android:name=\"android.permission.WRITE_CONTACTS\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_NETWORK_STATE\"/>",
"<uses-permission android:name=\"android.permission.CAMERA\"/>",
"<uses-permission android:name=\"android.permission.RECORD_AUDIO\"/>",
"<uses-permission android:name=\"android.permission.GET_ACCOUNTS\"/>",
"<uses-permission android:name=\"android.permission.MODIFY_AUDIO_SETTINGS\"/>",
"<uses-permission android:name=\"android.permission.READ_PHONE_STATE\"/>",
"<uses-permission android:name=\"android.permission.CHANGE_WIFI_STATE\"/>",
"<uses-permission android:name=\"android.permission.WAKE_LOCK\"/>",
"<uses-permission android:name=\"android.permission.CALL_PHONE\"/>",
"<uses-permission android:name=\"android.permission.FLASHLIGHT\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_COARSE_LOCATION\"/>",
"<uses-feature android:name=\"android.hardware.camera\"/>",
"<uses-permission android:name=\"android.permission.ACCESS_FINE_LOCATION\"/>",
"<uses-permission android:name=\"android.permission.WRITE_SETTINGS\"/>"
]
},
"ios" : {},
/* ios打包配置 */
"sdkConfigs" : {}
}
},
/* SDK配置 */
"quickapp" : {},
/* 快应用特有相关 */
"mp-weixin" : {
/* 小程序特有相关 */
"usingComponents" : true,
"appid" : "",
"setting" : {
"urlCheck" : true
}
},
"h5" : {
"devServer" : {
"https" : false,
"port" : 8060
},
"domain" : "localhost",
"router" : {
"base" : ""
}
}
}
{
"pages": [
{
"path": "pages/index/index",
"style": {
// #ifdef MP
"navigationBarTitleText": "Mall商城",
//"navigationStyle": "custom",
// #endif
"enablePullDownRefresh": true,
"app-plus": {
"titleNView": {
"type": "transparent",
"searchInput": {
"backgroundColor": "rgba(231, 231, 231,.7)",
"borderRadius": "16px",
"placeholder": "请输入商品 如:手机",
"disabled": true,
"placeholderColor": "#606266"
},
"buttons": [{
"fontSrc": "/static/yticon.ttf",
"text": "\ue60d",
"fontSize": "26",
"color": "#303133",
"float": "left",
"background": "rgba(0,0,0,0)"
},
{
"fontSrc": "/static/yticon.ttf",
"text": "\ue744",
"fontSize": "27",
"color": "#303133",
"background": "rgba(0,0,0,0)",
"redDot": true
}
]
}
}
}
},
{
"path": "pages/product/product",
"style": {
"navigationBarTitleText": "详情展示",
"app-plus": {
"titleNView": {
"type": "transparent"
}
}
}
}, {
"path": "pages/set/set",
"style": {
"navigationBarTitleText": "设置"
}
},
{
"path": "pages/userinfo/userinfo",
"style": {
"navigationBarTitleText": "修改资料"
}
}, {
"path": "pages/cart/cart",
"style": {
"navigationBarTitleText": "购物车"
}
}, {
"path": "pages/public/login",
"style": {
"navigationBarTitleText": "",
"navigationStyle": "custom",
"app-plus": {
"titleNView": false,
"animationType": "slide-in-bottom"
}
}
}, {
"path": "pages/public/register",
"style": {
"navigationBarTitleText": "",
"navigationStyle": "custom",
"app-plus": {
"titleNView": false,
"animationType": "slide-in-bottom"
}
}
}, {
"path": "pages/user/user",
"style": {
"navigationBarTitleText": "我的",
// #ifdef MP
"navigationStyle": "custom",
// #endif
"app-plus": {
"bounce": "none",
"titleNView": {
"type": "transparent",
"buttons": [{
"fontSrc": "/static/yticon.ttf",
"text": "\ue60f",
"fontSize": "24",
"color": "#303133",
"width": "46px",
"background": "rgba(0,0,0,0)"
},
{
"fontSrc": "/static/yticon.ttf",
"text": "\ue744",
"fontSize": "28",
"color": "#303133",
"background": "rgba(0,0,0,0)",
"redDot": true
}
]
}
}
}
}, {
"path": "pages/order/order",
"style": {
"navigationBarTitleText": "我的订单",
"app-plus": {
"bounce": "none"
}
}
}, {
"path": "pages/money/money",
"style": {}
}, {
"path": "pages/order/createOrder",
"style": {
"navigationBarTitleText": "创建订单"
}
}, {
"path": "pages/order/orderDetail",
"style": {
"navigationBarTitleText": "订单详情"
}
}, {
"path": "pages/address/address",
"style": {
"navigationBarTitleText": "收货地址"
}
}, {
"path": "pages/address/addressManage",
"style": {
"navigationBarTitleText": ""
}
}, {
"path": "pages/money/pay",
"style": {
"navigationBarTitleText": "支付"
}
},
{
"path": "pages/money/paySuccess",
"style": {
"navigationBarTitleText": "支付成功"
}
}, {
"path": "pages/notice/notice",
"style": {
"navigationBarTitleText": "通知"
}
}, {
"path": "pages/category/category",
"style": {
"navigationBarTitleText": "分类",
"app-plus": {
"bounce": "none"
}
}
}, {
"path": "pages/product/list",
"style": {
"enablePullDownRefresh": true,
"navigationBarTitleText": "商品列表"
}
}, {
"path": "pages/coupon/couponList",
"style": {
"enablePullDownRefresh": true,
"navigationBarTitleText": "优惠券列表"
}
}, {
"path": "pages/brand/brandDetail",
"style": {
"enablePullDownRefresh": true,
"navigationBarTitleText": "品牌详情"
}
}, {
"path": "pages/brand/list",
"style": {
"enablePullDownRefresh": true,
"navigationBarTitleText": "推荐品牌列表"
}
}, {
"path": "pages/product/newProductList",
"style": {
"enablePullDownRefresh": true,
"navigationBarTitleText": "新鲜好物"
}
}, {
"path": "pages/product/hotProductList",
"style": {
"enablePullDownRefresh": true,
"navigationBarTitleText": "人气推荐"
}
}, {
"path": "pages/user/readHistory",
"style": {
"enablePullDownRefresh": true,
"navigationBarTitleText": "我的足迹",
"app-plus": {
"titleNView": {
"buttons": [{
"text": "清空",
"fontSize": "16",
"color": "#303133",
"width": "46px",
"background": "rgba(0,0,0,0)"
}]
}
}
}
},{
"path": "pages/user/productCollection",
"style": {
"enablePullDownRefresh": true,
"navigationBarTitleText": "我的收藏",
"app-plus": {
"titleNView": {
"buttons": [{
"text": "清空",
"fontSize": "16",
"color": "#303133",
"width": "46px",
"background": "rgba(0,0,0,0)"
}]
}
}
}
},{
"path": "pages/user/brandAttention",
"style": {
"enablePullDownRefresh": true,
"navigationBarTitleText": "我的关注",
"app-plus": {
"titleNView": {
"buttons": [{
"text": "清空",
"fontSize": "16",
"color": "#303133",
"width": "46px",
"background": "rgba(0,0,0,0)"
}]
}
}
}
}
],
"globalStyle": {
"navigationBarTextStyle": "black",
"navigationBarTitleText": "mall-app-web",
"navigationBarBackgroundColor": "#FFFFFF",
"backgroundColor": "#f8f8f8"
},
"tabBar": {
"color": "#C0C4CC",
"selectedColor": "#fa436a",
"borderStyle": "black",
"backgroundColor": "#ffffff",
"list": [{
"pagePath": "pages/index/index",
"iconPath": "static/tab-home.png",
"selectedIconPath": "static/tab-home-current.png",
"text": "首页"
},
{
"pagePath": "pages/category/category",
"iconPath": "static/tab-cate.png",
"selectedIconPath": "static/tab-cate-current.png",
"text": "分类"
},
{
"pagePath": "pages/cart/cart",
"iconPath": "static/tab-cart.png",
"selectedIconPath": "static/tab-cart-current.png",
"text": "购物车"
},
{
"pagePath": "pages/user/user",
"iconPath": "static/tab-my.png",
"selectedIconPath": "static/tab-my-current.png",
"text": "我的"
}
]
}
}
<template>
<view class="content b-t">
<view class="list b-b" v-for="(item, index) in addressList" :key="index" @click="checkAddress(item)">
<view class="wrapper">
<view class="address-box">
<text v-if="item.defaultStatus==1" class="tag">默认</text>
<text class="address">{{item.province}} {{item.city}} {{item.region}} {{item.detailAddress}}</text>
</view>
<view class="u-box">
<text class="name">{{item.name}}</text>
<text class="mobile">{{item.phoneNumber}}</text>
</view>
</view>
<text class="yticon icon-bianji" @click.stop="addAddress('edit', item)"></text>
<text class="yticon icon-iconfontshanchu1" @click.stop="handleDeleteAddress(item.id)"></text>
</view>
<button class="add-btn" @click="addAddress('add')">新增地址</button>
</view>
</template>
<script>
import {
fetchAddressList,
deleteAddress
} from '@/api/address.js';
export default {
data() {
return {
source: 0,
addressList: []
}
},
onLoad(option) {
console.log(option.source);
this.source = option.source;
this.loadData();
},
methods: {
async loadData() {
fetchAddressList().then(response => {
this.addressList = response.data;
});
},
//选择地址
checkAddress(item) {
if (this.source == 1) {
//this.$api.prePage()获取上一页实例,在App.vue定义
this.$api.prePage().currentAddress = item;
uni.navigateBack()
}
},
addAddress(type, item) {
if (type == 'edit') {
uni.navigateTo({
url: `/pages/address/addressManage?type=${type}&id=${item.id}`
})
} else {
uni.navigateTo({
url: `/pages/address/addressManage?type=${type}`
})
}
},
//处理删除地址
handleDeleteAddress(id){
let superThis = this;
uni.showModal({
title: '提示',
content: '是否要删除该地址',
success: function (res) {
if (res.confirm) {
deleteAddress(id).then(response=>{
superThis.loadData();
});
} else if (res.cancel) {
console.log('用户点击取消');
}
}
});
},
//添加或修改成功之后回调
refreshList(data, type) {
//添加或修改后事件,这里直接在最前面添加了一条数据,实际应用中直接刷新地址列表即可
// this.addressList.unshift(data);
this.loadData();
console.log(data, type);
}
}
}
</script>
<style lang='scss'>
page {
padding-bottom: 120upx;
}
.content {
position: relative;
}
.list {
display: flex;
align-items: center;
padding: 20upx 30upx;
;
background: #fff;
position: relative;
}
.wrapper {
display: flex;
flex-direction: column;
flex: 1;
}
.address-box {
display: flex;
align-items: center;
.tag {
font-size: 24upx;
color: $base-color;
margin-right: 10upx;
background: #fffafb;
border: 1px solid #ffb4c7;
border-radius: 4upx;
padding: 4upx 10upx;
line-height: 1;
}
.address {
font-size: 30upx;
color: $font-color-dark;
}
}
.u-box {
font-size: 28upx;
color: $font-color-light;
margin-top: 16upx;
.name {
margin-right: 30upx;
}
}
.icon-bianji {
display: flex;
align-items: center;
height: 80upx;
font-size: 40upx;
color: $font-color-light;
padding-left: 30upx;
}
.icon-iconfontshanchu1 {
display: flex;
align-items: center;
height: 80upx;
font-size: 40upx;
color: $font-color-light;
padding-left: 30upx;
}
.add-btn {
position: fixed;
left: 30upx;
right: 30upx;
bottom: 16upx;
z-index: 95;
display: flex;
align-items: center;
justify-content: center;
width: 690upx;
height: 80upx;
font-size: 32upx;
color: #fff;
background-color: $base-color;
border-radius: 10upx;
box-shadow: 1px 2px 5px rgba(219, 63, 96, 0.4);
}
</style>
<template>
<view class="content">
<view class="row b-b">
<text class="tit">姓名</text>
<input class="input" type="text" v-model="addressData.name" placeholder="收货人姓名" placeholder-class="placeholder" />
</view>
<view class="row b-b">
<text class="tit">手机号码</text>
<input class="input" type="number" v-model="addressData.phoneNumber" placeholder="收货人手机号码" placeholder-class="placeholder" />
</view>
<view class="row b-b">
<text class="tit">邮政编码</text>
<input class="input" type="number" v-model="addressData.postCode" placeholder="收货人邮政编码" placeholder-class="placeholder" />
</view>
<!-- <view class="row b-b">
<text class="tit">所在区域</text>
<text @click="chooseLocation" class="input">
{{addressData.province}} {{addressData.city}} {{addressData.region}}
</text>
<text class="yticon icon-shouhuodizhi" @click="chooseLocation"></text>
</view> -->
<view class="row b-b">
<text class="tit">所在区域</text>
<input class="input" type="text" v-model="addressData.prefixAddress" placeholder="所在区域" placeholder-class="placeholder" />
</view>
<view class="row b-b">
<text class="tit">详细地址</text>
<input class="input" type="text" v-model="addressData.detailAddress" placeholder="详细地址" placeholder-class="placeholder" />
</view>
<view class="row default-row">
<text class="tit">设为默认</text>
<switch :checked="addressData.defaultStatus==1" color="#fa436a" @change="switchChange" />
</view>
<button class="add-btn" @click="confirm">提交</button>
</view>
</template>
<script>
import {
addAddress,
updateAddress,
fetchAddressDetail
} from '@/api/address.js';
export default {
data() {
return {
addressData: {
name: '',
phoneNumber: '',
postCode: '',
detailAddress: '',
default: false,
province: '',
city: '',
region: '',
prefixAddress: ''
}
}
},
onLoad(option) {
let title = '新增收货地址';
if (option.type === 'edit') {
title = '编辑收货地址'
fetchAddressDetail(option.id).then(response=>{
this.addressData = response.data;
this.addressData.prefixAddress = this.addressData.province+this.addressData.city+this.addressData.region;
});
}
this.manageType = option.type;
uni.setNavigationBarTitle({
title
})
},
methods: {
switchChange(e) {
this.addressData.defaultStatus = e.detail.value ? 1 : 0;
},
//地图选择地址
chooseLocation() {
uni.chooseLocation({
success: (data) => {
this.covertAdderss(data.address);
this.addressData.detailAddress = data.name;
}
})
},
//将地址转化为省市区
covertAdderss(address) {
console.log("covertAdderss", address);
if (address.indexOf("省") != -1) {
this.addressData.province = address.substr(0, address.indexOf("省") + 1);
address = address.replace(this.addressData.province, "");
this.addressData.city = address.substr(0, address.indexOf("市") + 1);
address = address.replace(this.addressData.city, "");
this.addressData.region = address.substr(0, address.indexOf("区") + 1);
} else {
this.addressData.province = address.substr(0, address.indexOf("市") + 1);
address = address.replace(this.addressData.province, "");
this.addressData.city = "";
this.addressData.region = address.substr(0, address.indexOf("区") + 1);
}
},
//提交
confirm() {
let data = this.addressData;
if (!data.name) {
this.$api.msg('请填写收货人姓名');
return;
}
if (!/(^1[3|4|5|7|8][0-9]{9}$)/.test(data.phoneNumber)) {
this.$api.msg('请输入正确的手机号码');
return;
}
if (!data.prefixAddress) {
this.$api.msg('请输入区域');
return;
}
this.covertAdderss(data.prefixAddress);
if (!data.province) {
this.$api.msg('请输入正确的省份');
return;
}
if (!data.detailAddress) {
this.$api.msg('请填写详细地址信息');
return;
}
if(this.manageType=='edit'){
updateAddress(this.addressData).then(response=>{
//this.$api.prePage()获取上一页实例,可直接调用上页所有数据和方法,在App.vue定义
this.$api.prePage().refreshList(data, this.manageType);
this.$api.msg("地址修改成功!");
setTimeout(() => {
uni.navigateBack()
}, 800)
});
}else{
addAddress(this.addressData).then(response=>{
//this.$api.prePage()获取上一页实例,可直接调用上页所有数据和方法,在App.vue定义
this.$api.prePage().refreshList(data, this.manageType);
this.$api.msg("地址添加成功!");
setTimeout(() => {
uni.navigateBack()
}, 800)
});
}
},
}
}
</script>
<style lang="scss">
page {
background: $page-color-base;
padding-top: 16upx;
}
.row {
display: flex;
align-items: center;
position: relative;
padding: 0 30upx;
height: 110upx;
background: #fff;
.tit {
flex-shrink: 0;
width: 150upx;
font-size: 30upx;
color: $font-color-dark;
}
.input {
flex: 1;
font-size: 30upx;
color: $font-color-dark;
}
.icon-shouhuodizhi {
font-size: 36upx;
color: $font-color-light;
}
}
.default-row {
margin-top: 16upx;
.tit {
flex: 1;
}
switch {
transform: translateX(16upx) scale(.9);
}
}
.add-btn {
display: flex;
align-items: center;
justify-content: center;
width: 690upx;
height: 80upx;
margin: 60upx auto;
font-size: $font-lg;
color: #fff;
background-color: $base-color;
border-radius: 10upx;
box-shadow: 1px 2px 5px rgba(219, 63, 96, 0.4);
}
</style>
<template>
<view>
<!-- 顶部大图 -->
<view class="top-image">
<view class="image-wrapper">
<image :src="brand.bigPic" class="loaded" mode="aspectFill"></image>
</view>
</view>
<!-- 品牌信息 -->
<view class="info">
<view class="image-wrapper">
<image :src="brand.logo" class="loaded" mode="aspectFit"></image>
</view>
<view class="title">
<text :class="{Skeleton:!loaded}">{{brand.name}}</text>
<text :class="{Skeleton:!loaded}">品牌首字母:{{brand.firstLetter}}</text>
</view>
<view>
<text class="yticon icon-shoucang" :class="{active: favoriteStatus}" @click="favorite()"></text>
</view>
</view>
<!-- 品牌故事 -->
<view class="section-tit">品牌故事</view>
<view class="brand-story">
<text class="text">{{brand.brandStory}}</text>
</view>
<!-- 相关商品 -->
<view class="section-tit">相关商品</view>
<view class="goods-list">
<view v-for="(item, index) in productList" :key="index" class="goods-item" @click="navToDetailPage(item)">
<view class="image-wrapper">
<image :src="item.pic" mode="aspectFill"></image>
</view>
<text class="title clamp">{{item.name}}</text>
<text class="title2">{{item.subTitle}}</text>
<view class="price-box">
<text class="price">{{item.price}}</text>
<text>已售 {{item.sale}}</text>
</view>
</view>
</view>
<uni-load-more :status="loadingType"></uni-load-more>
</view>
</template>
<script>
import share from '@/components/share';
import {
getBrandDetail,
fetchBrandProductList
} from '@/api/brand.js';
import {
createBrandAttention,
deleteBrandAttention,
brandAttentionDetail
} from '@/api/memberBrandAttention.js';
import uniLoadMore from '@/components/uni-load-more/uni-load-more.vue';
import {
mapState
} from 'vuex';
export default {
components: {
share
},
data() {
return {
loaded: false,
brand: {},
productList:[],
loadingType: 'more',
favoriteStatus:false,
queryParam: {
brandId: null,
pageNum: 1,
pageSize: 4
}
};
},
onLoad(options) {
this.loaded = true;
let id = options.id;
getBrandDetail(id).then(response => {
this.brand = response.data;
this.initBrandAttention();
});
this.queryParam.brandId = id;
this.loadData('refresh');
},
computed: {
...mapState(['hasLogin'])
},
methods: {
imageOnLoad(key, index) {
this.$set(this.data[key][index], 'loaded', 'loaded');
},
//收藏
favorite() {
if (!this.checkForLogin()) {
return;
}
if (this.favoriteStatus) {
//取消收藏
deleteBrandAttention({
brandId: this.brand.id
}).then(response => {
uni.showToast({
title: "取消收藏成功!",
icon: 'none'
});
this.favoriteStatus = !this.favoriteStatus;
});
} else {
//收藏
let brandAttention = {
brandId : this.brand.id,
brandName : this.brand.name,
brandLogo : this.brand.logo,
brandCity : ""
}
createBrandAttention(brandAttention).then(response=>{
uni.showToast({
title: "收藏成功!",
icon: 'none'
});
this.favoriteStatus = !this.favoriteStatus;
});
}
},
//详情
navToDetailPage(item) {
let id = item.id;
uni.navigateTo({
url: `/pages/product/product?id=${id}`
})
},
//加载商品 ,带下拉刷新和上滑加载
async loadData(type = 'add', loading) {
//没有更多直接返回
if (type === 'add') {
if (this.loadingType === 'nomore') {
return;
}
this.loadingType = 'loading';
} else {
this.loadingType = 'more'
}
if (type === 'refresh') {
this.queryParam.pageNum=1;
this.productList = [];
}
fetchBrandProductList(this.queryParam).then(response => {
let productList = response.data.list;
if (response.data.list.length === 0) {
//没有更多了
this.loadingType = 'nomore';
this.queryParam.pageNum--;
} else {
if (response.data.list.length < this.queryParam.pageSize) {
this.loadingType = 'nomore';
this.queryParam.pageNum--;
} else {
this.loadingType = 'more';
}
this.productList = this.productList.concat(productList);
}
if (type === 'refresh') {
if (loading == 1) {
uni.hideLoading()
} else {
uni.stopPullDownRefresh();
}
}
});
},
//下拉刷新
onPullDownRefresh() {
this.loadData('refresh');
},
//加载更多
onReachBottom() {
this.queryParam.pageNum++;
this.loadData();
},
//初始化收藏状态
initBrandAttention(){
if(this.hasLogin){
brandAttentionDetail({brandId:this.brand.id}).then(response=>{
this.favoriteStatus = response.data!=null;
});
}
},
//检查登录状态并弹出登录框
checkForLogin() {
if (!this.hasLogin) {
uni.showModal({
title: '提示',
content: '你还没登录,是否要登录?',
confirmText: '去登录',
cancelText: '取消',
success: function(res) {
if (res.confirm) {
uni.navigateTo({
url: '/pages/public/login'
})
} else if (res.cancel) {
console.log('用户点击取消');
}
}
});
return false;
} else {
return true;
}
},
}
}
</script>
<style lang="scss">
page {
background: $page-color-base;
}
.top-image {
height: 200px;
.image-wrapper {
display: flex;
justify-content: center;
align-content: center;
width: 100%;
height: 100%;
overflow: hidden;
image {
width: 100%;
height: 100%;
}
}
}
.info {
display: flex;
align-items: center;
padding: 30upx 50upx;
background: #fff;
margin-top: 16upx;
.image-wrapper {
width: 210upx;
height: 70upx;
background: #fff;
image{
width:100%;
height: 100%;
}
}
.title {
flex: 1;
display: flex;
flex-direction: column;
font-size: $font-lg+4upx;
margin-left: 30upx;
color: $font-color-dark;
text:last-child {
font-size: $font-sm;
color: $font-color-light;
margin-top: 8upx;
&.Skeleton{
width:220upx;
}
}
}
.yticon {
font-size: 80upx;
color: $font-color-base;
margin: 0 10upx 0 30upx;
&.active {
color: #ff4443;
}
}
}
.brand-story {
display: flex;
padding: 30upx;
background: #fff;
.text {
font-size: $font-sm;
color: $font-color-light;
}
}
.actions {
padding: 10upx 28upx;
background: #fff;
.yticon {
font-size: 46upx;
color: $font-color-base;
padding: 10upx 12upx;
&.active {
color: #ff4443;
}
&:nth-child(2) {
font-size: 50upx;
}
}
}
.section-tit {
font-size: $font-base+2upx;
color: $font-color-dark;
background: #fff;
margin-top: 16upx;
text-align: center;
padding-top: 20upx;
padding-bottom: 20upx;
}
/* 商品列表 */
.goods-list {
display: flex;
flex-wrap: wrap;
padding: 0 30upx;
background: #fff;
.goods-item {
display: flex;
flex-direction: column;
width: 48%;
padding-bottom: 40upx;
&:nth-child(2n+1) {
margin-right: 4%;
}
}
.image-wrapper {
width: 100%;
height: 330upx;
border-radius: 3px;
overflow: hidden;
image {
width: 100%;
height: 100%;
opacity: 1;
}
}
.title {
font-size: $font-lg;
color: $font-color-dark;
line-height: 80upx;
}
.title2 {
font-size: $font-sm;
color: $font-color-light;
line-height: 40upx;
height: 80upx;
overflow: hidden;
text-overflow: ellipsis;
display: block;
}
.price-box {
display: flex;
align-items: center;
justify-content: space-between;
padding-right: 10upx;
font-size: 24upx;
color: $font-color-light;
}
.price {
font-size: $font-lg;
color: $uni-color-primary;
line-height: 1;
&:before {
content: '¥';
font-size: 26upx;
}
}
}
</style>
<template>
<view class="content">
<image src="/static/recommend_brand_banner.png" class="banner-image"></image>
<view class="section-tit">相关品牌</view>
<view class="goods-list">
<view v-for="(item, index) in brandList" :key="index" class="goods-item" @click="navToDetailPage(item)">
<view class="image-wrapper">
<image :src="item.logo" mode="aspectFit"></image>
</view>
<text class="title clamp">{{item.name}}</text>
<text class="title2">商品数量:{{item.productCount}}</text>
</view>
</view>
<uni-load-more :status="loadingType"></uni-load-more>
</view>
</template>
<script>
import {
fetchBrandRecommendList
} from '@/api/brand.js';
import uniLoadMore from '@/components/uni-load-more/uni-load-more.vue';
export default {
components: {
uniLoadMore
},
data() {
return {
loadingType: 'more', //加载更多状态
brandList: [],
searchParam: {
pageNum: 1,
pageSize: 6
}
};
},
onLoad(options) {
this.loadData();
},
//下拉刷新
onPullDownRefresh() {
this.loadData('refresh');
},
//加载更多
onReachBottom() {
this.searchParam.pageNum++;
this.loadData();
},
methods: {
//加载商品 ,带下拉刷新和上滑加载
async loadData(type = 'add', loading) {
//没有更多直接返回
if (type === 'add') {
if (this.loadingType === 'nomore') {
return;
}
this.loadingType = 'loading';
} else {
this.loadingType = 'more'
}
if (type === 'refresh') {
this.searchParam.pageNum=1;
this.brandList = [];
}
fetchBrandRecommendList(this.searchParam).then(response => {
let prandList = response.data;
if (response.data.length === 0) {
//没有更多了
this.loadingType = 'nomore';
this.searchParam.pageNum--;
} else {
if (response.data.length < this.searchParam.pageSize) {
this.loadingType = 'nomore';
this.searchParam.pageNum--;
} else {
this.loadingType = 'more';
}
this.brandList = this.brandList.concat(prandList);
}
if (type === 'refresh') {
if (loading == 1) {
uni.hideLoading()
} else {
uni.stopPullDownRefresh();
}
}
});
},
//详情
navToDetailPage(item) {
let id = item.id;
uni.navigateTo({
url: `/pages/brand/brandDetail?id=${id}`
})
},
stopPrevent() {}
},
}
</script>
<style lang="scss">
page,
.content {
background: $page-color-base;
}
.banner-image{
width: 100%;
}
.section-tit {
font-size: $font-base+2upx;
color: $font-color-dark;
background: #fff;
margin-top: 16upx;
text-align: center;
padding-top: 20upx;
padding-bottom: 20upx;
}
/* 商品列表 */
.goods-list {
display: flex;
flex-wrap: wrap;
padding: 0 30upx;
background: #fff;
.goods-item {
display: flex;
flex-direction: column;
width: 48%;
padding-bottom: 40upx;
&:nth-child(2n+1) {
margin-right: 4%;
}
}
.image-wrapper {
width: 100%;
height: 150upx;
border-radius: 3px;
overflow: hidden;
background-color: #fff;
image {
width: 100%;
height: 100%;
opacity: 1;
}
}
.title {
font-size: $font-lg;
color: $font-color-dark;
line-height: 80upx;
}
.title2 {
font-size: $font-sm;
color: $font-color-light;
line-height: 40upx;
height: 80upx;
overflow: hidden;
text-overflow: ellipsis;
display: block;
}
.price-box {
display: flex;
align-items: center;
justify-content: space-between;
padding-right: 10upx;
font-size: 24upx;
color: $font-color-light;
}
.price {
font-size: $font-lg;
color: $uni-color-primary;
line-height: 1;
}
}
</style>
<template>
<view class="container">
<!-- 空白页 -->
<view v-if="!hasLogin || empty===true" class="empty">
<image src="/static/emptyCart.jpg" mode="aspectFit"></image>
<view v-if="hasLogin" class="empty-tips">
空空如也
<navigator class="navigator" v-if="hasLogin" url="../index/index" open-type="switchTab">随便逛逛></navigator>
</view>
<view v-else class="empty-tips">
空空如也
<view class="navigator" @click="navToLogin">去登陆></view>
</view>
</view>
<view v-else>
<!-- 列表 -->
<view class="cart-list">
<block v-for="(item, index) in cartList" :key="item.id">
<view class="cart-item" :class="{'b-b': index!==cartList.length-1}">
<view class="image-wrapper">
<image :src="item.productPic" :class="[item.loaded]" mode="aspectFill" lazy-load @load="onImageLoad('cartList', index)"
@error="onImageError('cartList', index)"></image>
<view class="yticon icon-xuanzhong2 checkbox" :class="{checked: item.checked}" @click="check('item', index)"></view>
</view>
<view class="item-right">
<text class="clamp title">{{item.productName}}</text>
<text class="attr">{{item.spDataStr}}</text>
<text class="price">¥{{item.price}}</text>
<uni-number-box class="step" :min="1" :max="100" :value="item.quantity" :index="index" @eventChange="numberChange"></uni-number-box>
</view>
<text class="del-btn yticon icon-fork" @click="handleDeleteCartItem(index)"></text>
</view>
</block>
</view>
<!-- 底部菜单栏 -->
<view class="action-section">
<view class="checkbox">
<image :src="allChecked?'/static/selected.png':'/static/select.png'" mode="aspectFit" @click="check('all')"></image>
<view class="clear-btn" :class="{show: allChecked}" @click="clearCart">
清空
</view>
</view>
<view class="total-box">
<text class="price">¥{{total}}</text>
</view>
<button type="primary" class="no-border confirm-btn" @click="createOrder">去结算</button>
</view>
</view>
</view>
</template>
<script>
import {
mapState
} from 'vuex';
import uniNumberBox from '@/components/uni-number-box.vue';
import {
fetchCartList,
deletCartItem,
updateQuantity,
clearCartList
} from '@/api/cart.js';
export default {
components: {
uniNumberBox
},
data() {
return {
total: 0, //总价格
allChecked: false, //全选状态 true|false
empty: false, //空白页现实 true|false
cartList: [],
};
},
onLoad() {
// this.loadData();
},
onShow(){
//页面显示时重新加载购物车
this.loadData();
},
watch: {
//显示空白页
cartList(e) {
let empty = e.length === 0 ? true : false;
if (this.empty !== empty) {
this.empty = empty;
}
}
},
computed: {
...mapState(['hasLogin'])
},
methods: {
//请求数据
async loadData() {
if(!this.hasLogin){
return;
}
fetchCartList().then(response => {
let list = response.data;
let cartList = list.map(item => {
item.checked = true;
item.loaded = "loaded";
let spDataArr = JSON.parse(item.productAttr);
let spDataStr = '';
for (let attr of spDataArr) {
spDataStr += attr.key;
spDataStr += ":";
spDataStr += attr.value;
spDataStr += ";";
}
item.spDataStr = spDataStr;
return item;
});
this.cartList = cartList;
this.calcTotal(); //计算总价
});
},
//监听image加载完成
onImageLoad(key, index) {
this.$set(this[key][index], 'loaded', 'loaded');
},
//监听image加载失败
onImageError(key, index) {
this[key][index].productPic = '/static/errorImage.jpg';
},
navToLogin() {
uni.navigateTo({
url: '/pages/public/login'
})
},
//选中状态处理
check(type, index) {
if (type === 'item') {
this.cartList[index].checked = !this.cartList[index].checked;
} else {
const checked = !this.allChecked
const list = this.cartList;
list.forEach(item => {
item.checked = checked;
})
this.allChecked = checked;
}
this.calcTotal(type);
},
//数量
numberChange(data) {
let cartItem = this.cartList[data.index];
updateQuantity({id:cartItem.id,quantity:data.number}).then(response=>{
cartItem.quantity = data.number;
this.calcTotal();
});
},
//删除
handleDeleteCartItem(index) {
let list = this.cartList;
let row = list[index];
let id = row.id;
deletCartItem({ids:id}).then(response=>{
this.cartList.splice(index, 1);
this.calcTotal();
uni.hideLoading();
});
},
//清空
clearCart() {
clearCartList().then(response=>{
uni.showModal({
content: '清空购物车?',
success: (e) => {
if (e.confirm) {
this.cartList = [];
}
}
})
});
},
//计算总价
calcTotal() {
let list = this.cartList;
if (list.length === 0) {
this.empty = true;
return;
}
let total = 0;
let checked = true;
list.forEach(item => {
if (item.checked === true) {
total += item.price * item.quantity;
} else if (checked === true) {
checked = false;
}
})
this.allChecked = checked;
this.total = Number(total.toFixed(2));
},
//创建订单
createOrder() {
let list = this.cartList;
let cartIds = [];
list.forEach(item => {
if (item.checked) {
cartIds.push(item.id);
}
})
if(cartIds.length==0){
uni.showToast({
title:'您还未选择要下单的商品!',
duration:1000
})
return;
}
uni.navigateTo({
url: `/pages/order/createOrder?cartIds=${JSON.stringify(cartIds)}`
})
}
}
}
</script>
<style lang='scss'>
.container {
padding-bottom: 134upx;
/* 空白页 */
.empty {
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100vh;
padding-bottom: 100upx;
display: flex;
justify-content: center;
flex-direction: column;
align-items: center;
background: #fff;
image {
width: 240upx;
height: 160upx;
margin-bottom: 30upx;
}
.empty-tips {
display: flex;
font-size: $font-sm+2upx;
color: $font-color-disabled;
.navigator {
color: $uni-color-primary;
margin-left: 16upx;
}
}
}
}
/* 购物车列表项 */
.cart-item {
display: flex;
position: relative;
padding: 30upx 40upx;
.image-wrapper {
width: 230upx;
height: 230upx;
flex-shrink: 0;
position: relative;
image {
border-radius: 8upx;
}
}
.checkbox {
position: absolute;
left: -16upx;
top: -16upx;
z-index: 8;
font-size: 44upx;
line-height: 1;
padding: 4upx;
color: $font-color-disabled;
background: #fff;
border-radius: 50px;
}
.item-right {
display: flex;
flex-direction: column;
flex: 1;
overflow: hidden;
position: relative;
padding-left: 30upx;
.title,
.price {
font-size: $font-base + 2upx;
color: $font-color-dark;
height: 40upx;
line-height: 40upx;
}
.attr {
font-size: $font-sm + 2upx;
color: $font-color-light;
height: 50upx;
line-height: 50upx;
}
.price {
height: 50upx;
line-height: 50upx;
}
}
.del-btn {
padding: 4upx 10upx;
font-size: 34upx;
height: 50upx;
color: $font-color-light;
}
}
/* 底部栏 */
.action-section {
/* #ifdef H5 */
margin-bottom: 100upx;
/* #endif */
position: fixed;
left: 30upx;
bottom: 30upx;
z-index: 95;
display: flex;
align-items: center;
width: 690upx;
height: 100upx;
padding: 0 30upx;
background: rgba(255, 255, 255, .9);
box-shadow: 0 0 20upx 0 rgba(0, 0, 0, .5);
border-radius: 16upx;
.checkbox {
height: 52upx;
position: relative;
image {
width: 52upx;
height: 100%;
position: relative;
z-index: 5;
}
}
.clear-btn {
position: absolute;
left: 26upx;
top: 0;
z-index: 4;
width: 0;
height: 52upx;
line-height: 52upx;
padding-left: 38upx;
font-size: $font-base;
color: #fff;
background: $font-color-disabled;
border-radius: 0 50px 50px 0;
opacity: 0;
transition: .2s;
&.show {
opacity: 1;
width: 120upx;
}
}
.total-box {
flex: 1;
display: flex;
flex-direction: column;
text-align: right;
padding-right: 40upx;
.price {
font-size: $font-lg;
color: $font-color-dark;
}
.coupon {
font-size: $font-sm;
color: $font-color-light;
text {
color: $font-color-dark;
}
}
}
.confirm-btn {
padding: 0 38upx;
margin: 0;
border-radius: 100px;
height: 76upx;
line-height: 76upx;
font-size: $font-base + 2upx;
background: $uni-color-primary;
box-shadow: 1px 2px 5px rgba(217, 60, 93, 0.72)
}
}
/* 复选框选中状态 */
.action-section .checkbox.checked,
.cart-item .checkbox.checked {
color: $uni-color-primary;
}
</style>
<template>
<view class="content">
<scroll-view scroll-y class="left-aside">
<view v-for="item in flist" :key="item.id" class="f-item b-b" :class="{active: item.id === currentId}" @click="tabtap(item)">
{{item.name}}
</view>
</scroll-view>
<scroll-view scroll-with-animation scroll-y class="right-aside">
<view class="s-list">
<view @click="navToList(item.id)" class="s-item" v-for="item in slist" :key="item.id">
<image :src="item.icon||'http://macro-oss.oss-cn-shenzhen.aliyuncs.com/mall/images/20190519/default.png'"></image>
<text>{{item.name}}</text>
</view>
</view>
</scroll-view>
</view>
</template>
<script>
import {
fetchProductCateList
} from '@/api/home.js';
export default {
data() {
return {
currentId: 0,
flist: [],
slist: []
}
},
onLoad() {
this.loadData();
},
methods: {
async loadData() {
fetchProductCateList(0).then(response => {
this.flist = response.data;
if (this.flist.length > 0) {
this.currentId = this.flist[0].id;
fetchProductCateList(this.currentId).then(response => {
this.slist = response.data;
});
}
})
},
//一级分类点击
tabtap(item) {
this.currentId = item.id;
fetchProductCateList(this.currentId).then(response => {
this.slist = response.data;
});
},
navToList(sid) {
uni.navigateTo({
url: `/pages/product/list?fid=${this.currentId}&sid=${sid}`
})
}
}
}
</script>
<style lang='scss'>
page,
.content {
height: 100%;
background-color: #f8f8f8;
}
.content {
display: flex;
}
.left-aside {
flex-shrink: 0;
width: 200upx;
height: 100%;
background-color: #fff;
}
.f-item {
display: flex;
align-items: center;
justify-content: center;
width: 100%;
height: 100upx;
font-size: 28upx;
color: $font-color-base;
position: relative;
&.active {
color: $base-color;
background: #f8f8f8;
&:before {
content: '';
position: absolute;
left: 0;
top: 50%;
transform: translateY(-50%);
height: 36upx;
width: 8upx;
background-color: $base-color;
border-radius: 0 4px 4px 0;
opacity: .8;
}
}
}
.right-aside {
flex: 1;
overflow: hidden;
padding-left: 20upx;
}
.s-list {
margin-top: 20upx;
display: flex;
flex-wrap: wrap;
width: 100%;
background: #fff;
padding-top: 12upx;
&:after {
content: '';
flex: 99;
height: 0;
}
}
.s-item {
flex-shrink: 0;
display: flex;
justify-content: center;
align-items: center;
flex-direction: column;
width: 176upx;
font-size: 26upx;
color: #666;
padding-bottom: 20upx;
image {
width: 140upx;
height: 140upx;
}
}
</style>
<template>
<view class="content">
<view class="navbar">
<view v-for="(item, index) in navList" :key="index" class="nav-item" :class="{current: tabCurrentIndex === index}"
@click="tabClick(index)">
{{item.text}}
</view>
</view>
<!-- 优惠券页面,仿mt -->
<view class="coupon-item" v-for="(item,index) in couponList" :key="index">
<view class="con">
<view class="left">
<text class="title">{{item.name}}</text>
<text class="time">有效期至{{item.endTime | formatDateTime}}</text>
</view>
<view class="right">
<text class="price">{{item.amount}}</text>
<text>{{item.minPoint}}可用</text>
</view>
<view class="circle l"></view>
<view class="circle r"></view>
</view>
<text class="tips">{{item.useType | formatCouponUseType}}</text>
</view>
</view>
</template>
<script>
import {
fetchMemberCouponList
} from '@/api/coupon.js';
import {
formatDate
} from '@/utils/date';
export default {
data() {
return {
couponList: [],
tabCurrentIndex:0,
useStatus:0,
navList: [
{
useStatus: 0,
text: '未使用'
},
{
useStatus: 1,
text: '已使用'
},
{
useStatus: 2,
text: '已过期'
}
],
};
},
onLoad() {
this.loadData();
},
filters:{
formatDateTime(time) {
if (time == null || time === '') {
return 'N/A';
}
let date = new Date(time);
return formatDate(date, 'yyyy-MM-dd hh:mm:ss')
},
formatCouponUseType(useType) {
if (useType == 0) {
return "全场通用";
} else if (useType == 1) {
return "指定分类商品可用";
} else if (useType == 2) {
return "指定商品可用";
}
return null;
},
},
methods: {
loadData(){
fetchMemberCouponList(this.useStatus).then(response=>{
this.couponList = response.data;
});
},
tabClick(index){
this.tabCurrentIndex = index;
this.useStatus = this.navList[index].useStatus;
this.loadData();
},
}
}
</script>
<style lang='scss'>
page {
background: $page-color-base;
padding-bottom: 100upx;
}
.navbar {
display: flex;
height: 40px;
padding: 0 5px;
background: #fff;
box-shadow: 0 1px 5px rgba(0, 0, 0, .06);
position: relative;
z-index: 10;
.nav-item {
flex: 1;
display: flex;
justify-content: center;
align-items: center;
height: 100%;
font-size: 15px;
color: $font-color-dark;
position: relative;
&.current {
color: $base-color;
&:after {
content: '';
position: absolute;
left: 50%;
bottom: 0;
transform: translateX(-50%);
width: 44px;
height: 0;
border-bottom: 2px solid $base-color;
}
}
}
}
/* 优惠券列表 */
.coupon-item {
display: flex;
flex-direction: column;
margin: 20upx 24upx;
background: #fff;
.con {
display: flex;
align-items: center;
position: relative;
height: 120upx;
padding: 0 30upx;
&:after {
position: absolute;
left: 0;
bottom: 0;
content: '';
width: 100%;
height: 0;
border-bottom: 1px dashed #f3f3f3;
transform: scaleY(50%);
}
}
.left {
display: flex;
flex-direction: column;
justify-content: center;
flex: 1;
overflow: hidden;
height: 100upx;
}
.title {
font-size: 32upx;
color: $font-color-dark;
margin-bottom: 10upx;
}
.time {
font-size: 24upx;
color: $font-color-light;
}
.right {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
font-size: 26upx;
color: $font-color-base;
height: 100upx;
}
.price {
font-size: 44upx;
color: $base-color;
&:before {
content: '¥';
font-size: 34upx;
}
}
.tips {
font-size: 24upx;
color: $font-color-light;
line-height: 60upx;
padding-left: 30upx;
}
.circle {
position: absolute;
left: -6upx;
bottom: -10upx;
z-index: 10;
width: 20upx;
height: 20upx;
background: #f3f3f3;
border-radius: 100px;
&.r {
left: auto;
right: -6upx;
}
}
}
</style>
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
差异被折叠。
Markdown 格式
0%
您添加了 0 到此讨论。请谨慎行事。
请先完成此评论的编辑!
注册 或者 后发表评论