This commit is contained in:
Junling Bu
2019-04-19 18:33:18 +08:00
153 changed files with 17147 additions and 18 deletions

View File

@@ -2,7 +2,7 @@
又一个小商场系统。
litemall = Spring Boot后端 + Vue管理员前端 + 微信小程序用户前端
litemall = Spring Boot后端 + Vue管理员前端 + 微信小程序用户前端 + Vue用户移动端
* [文档](https://linlinjava.gitbook.io/litemall)
* [贡献](https://linlinjava.gitbook.io/litemall/contribute)
@@ -20,6 +20,12 @@ litemall = Spring Boot后端 + Vue管理员前端 + 微信小程序用户前端
![](./doc/pic/litemall_wx_demo.png)
> 注意:此实例是测试小商场,开发者请不要尝试购买商品、付款、退款操作。
### 轻商场实例
目前未部署
![](./doc/pic/5.gif)
### 管理后台实例
![](./doc/pic/4.png)
@@ -122,6 +128,20 @@ litemall = Spring Boot后端 + Vue管理员前端 + 微信小程序用户前端
> 这里只是最简启动方式,而小商场的微信登录、微信支付等功能需开发者设置才能运行,
> 更详细方案请参考[文档](https://linlinjava.gitbook.io/litemall/project)。
6. 启动轻商城前端
打开命令行,输入以下命令
```bash
npm install -g cnpm --registry=https://registry.npm.taobao.org
cd litemall/litemall-vue
cnpm install
cnpm run dev
```
此时浏览器建议采用chrome 手机模式)打开,输入网址`http://localhost:6255`, 此时进入轻商场。
注意:
> 现在功能很不稳定,处在开发阶段。
## 开发计划
当前版本[v1.3.0](https://linlinjava.gitbook.io/litemall/changelog)
@@ -139,6 +159,7 @@ V 2.0.0 完成以下目标:
1. 小商城和管理后台完成所有基本业务;
2. 管理后台实现统计功能、日志功能、权限功能;
3. 业务代码和细节代码进行调整优化;
4. 轻商城的开发;
V 3.0.0 完成以下目标:
@@ -169,20 +190,26 @@ V 3.0.0 完成以下目标:
项目介绍: 一个基于Vue和Element的后台集成方案
项目参考litemall项目的litemall-admin模块的前端框架基于项目修改扩展。
项目参考litemall项目的litemall-admin模块的前端框架基于vue-element-admin项目修改扩展。
3. [mall-admin-web](https://github.com/macrozheng/mall-admin-web)
项目介绍mall-admin-web是一个电商后台管理系统的前端项目基于Vue+Element实现。
项目参考litemall项目的litemall-admin模块的一些页面布局样式参考了项目。
项目参考litemall项目的litemall-admin模块的一些页面布局样式参考了mall-admin-web项目。
4. [biu](https://github.com/CaiBaoHong/biu)
项目介绍管理后台项目开发脚手架基于vue-element-admin和springboot搭建前后端分离方式开发和部署。
项目参考litemall项目的权限管理功能参考了项目。
项目参考litemall项目的权限管理功能参考了biu项目。
5. [vant--mobile-mall](https://github.com/qianzhaoy/vant--mobile-mall)
项目介绍:基于有赞 vant 组件库的移动商城。
项目参考litemall项目的litemall-vue模块基于vant--mobile-mall项目开发。
## 问题
![](doc/pic/qq.png)

View File

@@ -7,4 +7,5 @@
* [1. 系统架构](./project.md)
* [2. 基础系统](./platform.md)
* [3. 小商场](./wxmall.md)
* [4. 管理后台](./admin.md)
* [4. 管理后台](./admin.md)
* [5. 轻商城](./mobmall.md)

30
doc/mobmall.md Normal file
View File

@@ -0,0 +1,30 @@
# 5 litemall轻商城
litemall轻商城是商城移动版本。
技术:
* 轻商城前端即litemall-vue模块
* power by vue-cli3
* Vue + Vue-router + Vant + Sass
* axios
* vee-validate
* fastclick
* babel-polyfill
* @xkeshi/vue-countdown
* Vant
* 轻商城前端即litemall-wx-api模块也就是和小商城后端是一样的。
* Spring Boot 2.x
* Spring MVC
* [weixin-java-tools](https://gitee.com/binary/weixin-java-tools)
## 5.1 litemall-wx-api
可以阅读3.1
## 3.2 litemall-vue
这里的代码基于[vant--mobile-mall](https://github.com/qianzhaoy/vant--mobile-mall)
文档未完成。

BIN
doc/pic/5.gif Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 189 KiB

View File

@@ -2,30 +2,35 @@
## 1.1 简介
litemall是一个简单的商场系统基于现有的开源项目重新实现一个完整的前后端项目包含小程序客户端和网页管理端。
litemall是一个简单的商场系统基于现有的开源项目重新实现一个完整的前后端项目包含小程序客户端、移动客户端和网页管理端。
![](./pic1/1-1.png)
项目的架构是个系统和个模块:
项目的架构是个系统和个模块:
* 基础系统子系统(platform)
由数据库、litemall-core模块、litemall-db模块和litemall-all模块组成;
* 小商场子系统(wxmall)
* 小商场子系统(wxmall即weixin mall)
由litemall-wx-api模块、litemall-wx模块和renard-wx模块组成
* 轻商城子系统(mobmall即mobile mall)
由litemall-wx-api模块和litemall-vue模块组成。
注意,目前这里移动商城子系统的后端和小商场子系统是一样的。
* 简商城子系统(webmall)
这里仅列出,目前没有开发计划。
* 管理后台子系统(admin)
由litemall-admin-api模块和litemall-admin模块组成。
* 简单商城系统(mall)
这里仅列出,目前没有开发计划。
而六个模块的开发设计到三种技术栈:
而九个模块的开发设计到三种技术栈:
* Spring Boot技术栈
@@ -38,7 +43,7 @@ litemall是一个简单的商场系统基于现有的开源项目重新实
* Vue技术栈
采用VSC开发工具开发litemall-admin模块。
采用VSC开发工具开发litemall-admin模块和litemall-vue模块
## 1.2 系统功能
@@ -51,7 +56,7 @@ litemall是一个简单的商场系统基于现有的开源项目重新实
* 系统业务模块
* 配置业务模块
### 1.2.1 小程序端功能
### 1.2.1 小商城功能
* 首页
* 专题列表、专题详情
@@ -69,7 +74,29 @@ litemall是一个简单的商场系统基于现有的开源项目重新实
* 地址列表、地址添加、地址删除
* 收藏、足迹、关于
### 1.2.2 管理平台功能
### 1.2.1 轻商城功能
**目前还在开发中,不稳定**
以下是准备完成的功能:
* 首页
* 专题列表、专题详情
* 分类列表、分类详情
* 品牌列表、品牌详情
* 新品首发、人气推荐
* 团购
* 搜索
* 商品详情
* 商品评价列表、商品评价
* 购物车
* 下单
* 个人
* 订单列表、订单详情
* 地址列表、地址添加、地址删除
* 收藏、足迹、关于
### 1.2.3 管理平台功能
* 会员管理
* 会员管理
@@ -101,9 +128,13 @@ litemall是一个简单的商场系统基于现有的开源项目重新实
* 对象存储
* 权限管理
* 定时任务(待定)
* 参数管理
* 操作日志
* 统计管理
* 配置管理
* 商场配置
* 小程序配置
* 运费配置
* 订单配置
* 统计报表
* 用户统计
* 订单统计
* 商品统计

17
litemall-vue/.eslintrc.js Normal file
View File

@@ -0,0 +1,17 @@
module.exports = {
root: true,
env: {
node: true
},
// extends: ['plugin:vue/essential', '@vue/prettier'],
// rules: {
// camelcase: 'off',
// quotes: ['error', 'single'],
// indent: ['error', 2, { SwitchCase: 1 }],
// 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
// 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
// },
parserOptions: {
parser: 'babel-eslint'
}
};

21
litemall-vue/.gitignore vendored Normal file
View File

@@ -0,0 +1,21 @@
.DS_Store
node_modules
/dist
# local env files
.env.local
.env.*.local
# Log files
npm-debug.log*
yarn-debug.log*
yarn-error.log*
# Editor directories and files
.idea
.vscode
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw*

View File

@@ -0,0 +1,5 @@
module.exports = {
plugins: {
autoprefixer: {}
}
};

View File

@@ -0,0 +1,4 @@
module.exports = {
tabWidth: 2,
singleQuote: true
};

View File

@@ -0,0 +1,15 @@
module.exports = {
presets: ['@vue/app'],
plugins: [
'lodash',
[
'import',
{
libraryName: 'vant',
libraryDirectory: 'es',
style: true
},
'vant'
]
]
};

45
litemall-vue/package.json Normal file
View File

@@ -0,0 +1,45 @@
{
"name": "litemall-vue",
"version": "0.1.0",
"description": "litemall-vue basing on vant--mobile-mall 0.1.0",
"author": "litemall <linlinjava@163.com>",
"license": "MIT",
"private": true,
"scripts": {
"dev": "vue-cli-service serve",
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"build:dep": "vue-cli-service build",
"build:prod": "vue-cli-service build",
"lint": "vue-cli-service lint"
},
"dependencies": {
"@xkeshi/vue-countdown": "^1.0.1",
"axios": "^0.18.0",
"dayjs": "^1.7.7",
"js-md5": "^0.7.3",
"lodash": "^4.17.11",
"vant": "^1.4.4",
"vee-validate": "^2.1.4",
"vue": "^2.5.17",
"vue-router": "^3.0.1",
"vuelidation": "^1.1.0"
},
"devDependencies": {
"@vue/cli-plugin-babel": "^3.0.5",
"@vue/cli-plugin-eslint": "^3.0.5",
"@vue/cli-service": "^3.0.5",
"@vue/eslint-config-prettier": "^3.0.5",
"babel-plugin-import": "^1.9.1",
"babel-plugin-lodash": "^3.3.4",
"node-sass": "^4.9.3",
"sass-loader": "^7.1.0",
"vue-template-compiler": "^2.5.17"
},
"browserslist": [
"> 1%",
"last 2 versions",
"not ie <= 8"
]
}

View File

Before

Width:  |  Height:  |  Size: 66 KiB

After

Width:  |  Height:  |  Size: 66 KiB

View File

@@ -0,0 +1,25 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta content="black" name="apple-mobile-web-app-status-bar-style">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<link rel="icon" href="./favicon.ico" type="image/x-icon" />
<title>litemall-vue</title>
</head>
<body>
<noscript>
<strong>Please enable it to continue.</strong>
</noscript>
<div id="app">
<div class="lds-ball">
<div></div>
</div>
</div>
<!-- built files will be auto injected -->
</body>
</html>

11
litemall-vue/src/App.vue Normal file
View File

@@ -0,0 +1,11 @@
<template>
<div id="app">
<keep-alive>
<router-view class="view-router" v-if="$route.meta.keepAlive"></router-view>
</keep-alive>
<router-view class="view-router" v-if="!$route.meta.keepAlive"></router-view>
<router-view name="tabbar"></router-view>
</div>
</template>
<style lang="scss" src="./assets/scss/global.scss" />

7
litemall-vue/src/api/goods.js Executable file
View File

@@ -0,0 +1,7 @@
import request from '@/core/utils/request'
// export const GOODS_CATEGORY = '/category';
export const GOODS_CATEGORY = '/wx/catalog/index';
export const GOODS_CHANNGE_CATEGORY = '/wx/catalog/current?id=';
export const GOODS_SEARCH = '/moreGoods';
export const GOODS_DETAIL = '/details';

5
litemall-vue/src/api/order.js Executable file
View File

@@ -0,0 +1,5 @@
export const ORDER_LIST = '/order-list';
export const ELE_COUPON_LIST = '/electronic-list';
export const REFUND_LIST = '/refund-list';

View File

7
litemall-vue/src/api/shop.js Executable file
View File

@@ -0,0 +1,7 @@
export const HOME_module = '/home';
export const ALL_GOODS = '/moreGoods';
export const SHOPINFO = '/shop-info';
// 运费模板
export const POST_FEE = '';

18
litemall-vue/src/api/user.js Executable file
View File

@@ -0,0 +1,18 @@
// 登录
export const USER_LOGIN = '/wx/auth/login';
export const USER_LOGOUT = '';
// 用户信息
export const USER_PROFILE = '/user-profile';
export const USER_MODIFY_PASSWORD = '';
export const USER_CHANGE_MOBILE = '';
// 验证码
export const USER_SENDCODE = '';
// 地址
export const ADDRESS = '/address';
export const ADDRESS_DEFAULT = '/address-default';
// 收藏
export const GOODS_COLLECT_LIST = '/moreGoods';

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 42 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 15 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 8.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -0,0 +1 @@
@import "./mixin/one-border";

View File

@@ -0,0 +1,127 @@
//loading
.van-loading--gradient-circle, .van-loading--spinner{
margin: 0 auto;
}
//tabber
div.van-tabbar-item--active {
color: $red;
}
.van-tabbar-item__icon .van-icon{
font-size: 18px;
}
//按钮组
a.van-goods-action__mini-btn i {
font-size: 22px;
}
.van-goods-action .van-button--bottom-action {
font-size: $font-size-normal;
}
.van-button--bottom-action.van-button--primary {
background-color: $red;
}
.van-button--danger {
background-color: $red;
}
.van-address-edit__buttons .van-button--primary{
background-color: $red;
border-color: $red;
}
// 多选
.van-checkbox__control:checked + i.van-icon-success {
background-color: $red;
border-color: $red;
}
//单选
.van-radio i.van-icon-checked {
color: $red;
}
.payment .van-cell-group .van-radio__input{
position: absolute;
right: 10px;
top: 50%;
transform: translate3d(0, -50%, 0);
}
.van-radio .van-radio__input{
height: 16px;
}
.van-radio i.van-icon{
font-size: 16px;
}
span.van-radio__input, span.van-radio__label{
vertical-align: unset;
line-height: 16px;
}
.van-cell-group .van-radio__label{
margin-left: 0;
}
//弹窗
.van-dialog__confirm,
.van-dialog__confirm:active {
color: $red;
}
//商品卡片
.van-card__footer {
width: 100%;
padding-left: 130px;
box-sizing: border-box;
}
div.van-card {
font-size: $font-size-normal;
background-color: #fff;
}
div.van-card__footer {
font-size: 12px;
color: $font-color-gray;
}
//cell
.van-cell__title{
vertical-align: middle;
}
//商品详情
.item_detail .van-picker__cancel {
color: #000;
}
.item_detail .van-picker__confirm {
color: $red;
}
//购物车
.tab-cart > .card-goods .van-checkbox {
padding-left: 10px;
box-sizing: border-box;
}
.tab-cart > .card-goods .van-card {
flex: 1;
}
.tab-cart .van-card:not(:first-child){
margin-top: 0;
padding-top: 10px;
padding-bottom: 10px;
}
//设置昵称
.set_nickname .van-field__control{
text-align: right
}
//商品列表
.item_list .van-tab--disabled{
color: #000;
}

View File

@@ -0,0 +1,16 @@
$red: #db3d3c;
$gray-deep: #999;
$gray: #bfbfbf;
$gray-shallow: #e5e5e5;
$gray-shallow-more: #f2f2f2;
$icon-bg: #f9f3e8;
//字体变量
$font-color-gray: $gray-deep;
$font-size-small: 12px;
$font-size-normal: 14px;
$font-size-big: 16px;
$border-color: $gray-shallow;
$bg-color: $gray-shallow-more;

View File

@@ -0,0 +1,59 @@
.red{
color: $red;
}
.text-desc{
font-size:$font-size-small;
color: $font-color-gray;
}
.float-r {
float: right;
}
.float-l {
float: left;
}
.clearfix {
&:before,
&:after {
content: " "; // 1
display: table; // 2
}
&:after {
clear: both;
}
}
.one_border {
@include one-border;
}
.one_border:last-child::after {
border-bottom-width: 0px;
}
.van-hairline--top-bottom::after {
border-width: 1px 0;
border-top-width: 0px;
border-right-width: 0px;
border-bottom-width: 0px;
border-left-width: 0px;
}
.text-center{
text-align: center;
}
.over-hide {
overflow: hidden !important;
}
.no-pad-bottom {
padding-bottom: 0 !important;
}
.height-fix42 {
padding-bottom: 42px;
}

View File

@@ -0,0 +1,45 @@
@import "./var";
@import "mixin";
@import "./common";
@import "./vant-theme";
@import "./spinner";
* {
box-sizing: border-box;
}
a {
color: #000
}
input:-webkit-autofill {
-webkit-box-shadow: 0 0 0 1000px white inset;
}
html {
-webkit-overflow-scrolling: touch;
}
body {
overflow-x: hidden;
max-width: 700px;
margin: 0 auto;
font-size: 14px;
}
.view-router {
background-color: $bg-color;
height: 100vh;
overflow-y: auto;
padding-bottom: 50px;
box-sizing: border-box;
}
.full-page {
height: 100vh;
}
.scroll-wrap {
overflow-y: scroll;
}

View File

@@ -0,0 +1,318 @@
@font-face {
font-family: "iconfont";
src: url('./iconfont.ttf') format('truetype');
}
@font-face {
font-family: "vanIcon";
src: url(https://b.yzcdn.cn/zanui/icon/vant-icon-4c3245.ttf) format('truetype');
}
.van-icon {
position: relative;
font-family: "iconfont", "vanIcon" !important;
font-size: 14px;
font-style: normal;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
i.van-icon-huida:before {
content: "\e678";
}
i.van-icon-wen:before {
content: "\e715";
}
i.van-icon-wuliu:before {
content: "\e640";
}
i.van-icon-hint:before {
content: "\e62a";
}
i.van-icon-add:before {
content: "\e64f";
}
i.van-icon-miaosha:before {
content: "\e68f";
}
i.van-icon-lock:before {
content: "\e60c";
}
i.van-icon-wode:before {
content: "\e604";
}
i.van-icon-checked:before {
content: "\e607";
}
i.van-icon-check:before {
content: "\e628";
}
i.van-icon-leimu:before {
content: "\e703";
}
i.van-icon-camera_full:before {
content: "\e618";
}
i.van-icon-cart-full:before {
content: "\e73c";
}
i.van-icon-cart:before {
content: "\e73d";
}
i.van-icon-miaosha-copy:before {
content: "\e601";
}
i.van-icon-id-card:before {
content: "\e61e";
}
i.van-icon-compass-full:before {
content: "\e7ac";
}
i.van-icon-fail:before {
content: "\e609";
}
i.van-icon-success:before {
content: "\e626";
}
i.van-icon-wangwang-full:before {
content: "\e605";
}
i.van-icon-daifahuo:before {
content: "\e642";
}
i.van-icon-arrowupcircle:before {
content: "\e6cf";
}
i.van-icon-class-full:before {
content: "\e7f8";
}
i.van-icon-fenxiang:before {
content: "\e610";
}
i.van-icon-gold-bean:before {
content: "\e629";
}
i.van-icon-coupon-due:before {
content: "\e6dd";
}
i.van-icon-coupon-used:before {
content: "\e6df";
}
i.van-icon-team:before {
content: "\e65d";
}
i.van-icon-dingwei:before {
content: "\e622";
}
i.van-icon-editor:before {
content: "\e685";
}
i.van-icon-coupon:before {
content: "\e60e";
}
i.van-icon-arrow-down:before {
content: "\e61b";
}
i.van-icon-clear:before {
content: "\e61f";
}
i.van-icon-laba:before {
content: "\e73b";
}
i.van-icon-kefu:before {
content: "\e616";
}
i.van-icon-tubiao215:before {
content: "\e619";
}
i.van-icon-jijiangkaishi:before {
content: "\e681";
}
i.van-icon-arrow:before {
content: "\e66e";
}
i.van-icon-mobile:before {
content: "\e72c";
}
i.van-icon-username:before {
content: "\e84d";
}
i.van-icon-icon104:before {
content: "\e665";
}
i.van-icon-list:before {
content: "\e68e";
}
i.van-icon-set:before {
content: "\e690";
}
i.van-icon-good:before {
content: "\e699";
}
i.van-icon-search:before {
content: "\e6a4";
}
i.van-icon-compass:before {
content: "\e6a6";
}
i.van-icon-success-radius:before {
content: "\e6b4";
}
i.van-icon-browse:before {
content: "\e6c0";
}
i.van-icon-phone:before {
content: "\e6c2";
}
i.van-icon-naozhong:before {
content: "\e661";
}
i.van-icon-shiliangzhinengduixiang42:before {
content: "\e675";
}
i.van-icon-dengdai:before {
content: "\e60b";
}
i.van-icon-cancel:before {
content: "\e61c";
}
i.van-icon-shouhouguanli:before {
content: "\e608";
}
i.van-icon-invitation:before {
content: "\e932";
}
i.van-icon-arrow-left:before {
content: "\e625";
}
i.van-icon-clear-full:before {
content: "\e658";
}
i.van-icon-wode1:before {
content: "\e602";
}
i.van-icon-baoguo-shixin:before {
content: "\e8fb";
}
i.van-icon-lajitong:before {
content: "\e62f";
}
i.van-icon-daifukuan:before {
content: "\e60a";
}
i.van-icon-eye-close:before {
content: "\e60d";
}
i.van-icon-eye-open:before {
content: "\e623";
}
i.van-icon-baoguo-kongxin:before {
content: "\e61a";
}
i.van-icon-dingwei1:before {
content: "\e7db";
}
i.van-icon-n4:before {
content: "\e79a";
}
i.van-icon-jiaoyiwancheng:before {
content: "\e686";
}
i.van-icon-jiaoyiguanbi:before {
content: "\e687";
}
i.van-icon-qianshoutixing:before {
content: "\e679";
}
i.van-icon-chulizhong:before {
content: "\e68b";
}
i.van-icon-tuandui1:before {
content: "\e63d";
}
i.van-icon-icon:before {
content: "\e603";
}
i.van-icon-wangwang:before {
content: "\e933";
}
i.van-icon-shoucang:before {
content: "\e620";
}
i.van-icon-shoucang-full:before {
content: "\e61d";
}

Binary file not shown.

View File

@@ -0,0 +1,17 @@
@mixin one-border($direction: bottom){
position: relative;
&::after {
content: "";
position: absolute;
top: 0;
left: 0;
width: 200%;
height: 200%;
transform: scale(.5);
transform-origin: 0 0;
pointer-events: none;
box-sizing: border-box;
border-#{$direction}: 1px solid $border-color;
}
}

View File

@@ -0,0 +1,53 @@
@keyframes lds-ball {
0%, 100% {
animation-timing-function: cubic-bezier(0.45, 0, 0.9, 0.55);
}
0% {
-webkit-transform: translate(0, 0);
transform: translate(0, 0);
}
50% {
-webkit-transform: translate(0, 108px);
transform: translate(0, 108px);
animation-timing-function: cubic-bezier(0, 0.45, 0.55, 0.9);
}
100% {
-webkit-transform: translate(0, 0);
transform: translate(0, 0);
}
}
@-webkit-keyframes lds-ball {
0%, 100% {
animation-timing-function: cubic-bezier(0.45, 0, 0.9, 0.55);
}
0% {
-webkit-transform: translate(0, 0);
transform: translate(0, 0);
}
50% {
-webkit-transform: translate(0, 108px);
transform: translate(0, 108px);
animation-timing-function: cubic-bezier(0, 0.45, 0.55, 0.9);
}
100% {
-webkit-transform: translate(0, 0);
transform: translate(0, 0);
}
}
.lds-ball {
position: relative;
}
.lds-ball div {
width: 52px;
height: 52px;
border-radius: 50%;
background: #e15b64;
-webkit-animation: lds-ball 1s linear infinite;
animation: lds-ball 1s linear infinite;
}
.lds-ball {
position: fixed;
left: 50%;
top: 30%;
transform: translateX(-50%);
}

View File

@@ -0,0 +1,32 @@
// 使用这个会导致组件内部的 router 导航守卫无法使用, 慎用
/**
* @param { string } chunkPath views 文件夹下的页面路径
* @return { function } 返回 promise<component> 的匿名函数
*/
import spinner from '@/vue/components/spinner';
export default chunkPath => {
const AsyncHandler = () => ({
component: new Promise(resolve => {
setTimeout(() => {
resolve(
import(/* webpackChunkName: "[request]" */ `@/views/${chunkPath}`)
);
}, 1000);
}),
loading: spinner,
error: {
render(h) {
return h('div', {}, ['异步组件加载失败']);
}
},
timeout: 10000
});
return () =>
Promise.resolve({
functional: true,
render(h, { data, children }) {
return h(AsyncHandler, data, children);
}
});
};

View File

@@ -0,0 +1,12 @@
export const idCard = /^[1-9]{1}[0-9]{14}$|^[1-9]{1}[0-9]{16}([0-9]|[xX])$/;
export const mobileReg = /^1[0-9]{10}$/;
export const address = val => {
const value = val.trim();
return value.length >= 5 && value.length <= 100;
};
export const userName = /^[a-zA-Z0-9_\u4e00-\u9fa5]{3,20}$/;
export const emailReg = /^(\w)+(\.\w+)*@(\w)+((\.\w+)+)$/;

View File

@@ -0,0 +1,20 @@
export const getLocalStorage = (...args) => {
const storage = {};
args.forEach(arg => {
storage[arg] = window.localStorage.getItem(arg) || null;
});
return storage;
};
export const setLocalStorage = data => {
Object.keys(data).forEach(prop => {
const el = data[prop];
window.localStorage.setItem(prop, el);
});
};
export const removeLocalStorage = (...args) => {
args.forEach(arg => {
window.localStorage.removeItem(arg);
});
};

View File

@@ -0,0 +1,6 @@
export default name => {
const reg = new RegExp(`(^|&)${name}=([^&]*)(&|$)`);
const r = window.location.search.substr(1).match(reg);
if (r != null) return decodeURIComponent(r[2]);
return '';
};

View File

@@ -0,0 +1,90 @@
// import axios from 'axios'
// import { Message, MessageBox } from 'element-ui'
// import store from '@/store'
// import { getToken } from '@/utils/auth'
// // create an axios instance
// const service = axios.create({
// baseURL: process.env.BASE_API, // api 的 base_url
// timeout: 5000 // request timeout
// })
// // request interceptor
// service.interceptors.request.use(
// config => {
// // Do something before request is sent
// if (store.getters.token) {
// // 让每个请求携带token-- ['X-Litemall-Admin-Token']为自定义key 请根据实际情况自行修改
// config.headers['X-Litemall-Admin-Token'] = getToken()
// }
// return config
// },
// error => {
// // Do something with request error
// console.log(error) // for debug
// Promise.reject(error)
// }
// )
// // response interceptor
// service.interceptors.response.use(
// response => {
// const res = response.data
// if (res.errno === 501) {
// MessageBox.alert('系统未登录,请重新登录', '错误', {
// confirmButtonText: '确定',
// type: 'error'
// }).then(() => {
// store.dispatch('FedLogOut').then(() => {
// location.reload()
// })
// })
// return Promise.reject('error')
// } else if (res.errno === 502) {
// MessageBox.alert('系统内部错误,请联系管理员维护', '错误', {
// confirmButtonText: '确定',
// type: 'error'
// })
// return Promise.reject('error')
// } else if (res.errno === 503) {
// MessageBox.alert('请求业务目前未支持', '警告', {
// confirmButtonText: '确定',
// type: 'error'
// })
// return Promise.reject('error')
// } else if (res.errno === 504) {
// MessageBox.alert('更新数据已经失效,请刷新页面重新操作', '警告', {
// confirmButtonText: '确定',
// type: 'error'
// })
// return Promise.reject('error')
// } else if (res.errno === 505) {
// MessageBox.alert('更新失败,请再尝试一次', '警告', {
// confirmButtonText: '确定',
// type: 'error'
// })
// return Promise.reject('error')
// } else if (res.errno === 506) {
// MessageBox.alert('没有操作权限,请联系管理员授权', '错误', {
// confirmButtonText: '确定',
// type: 'error'
// })
// return Promise.reject('error')
// } else if (res.errno !== 0) {
// // 非5xx的错误属于业务错误留给具体页面处理
// return Promise.reject(response)
// } else {
// return response
// }
// }, error => {
// console.log('err' + error)// for debug
// Message({
// message: '登录连接超时(后台不能连接,请联系系统管理员)',
// type: 'error',
// duration: 5 * 1000
// })
// return Promise.reject(error)
// })
// export default service

View File

@@ -0,0 +1,31 @@
export default {
isAttached(element) {
let currentNode = element.parentNode;
while (currentNode) {
if (currentNode.tagName === 'HTML') {
return true;
}
if (currentNode.nodeType === 11) {
return false;
}
currentNode = currentNode.parentNode;
}
return false;
},
getScrollLeft(element) {
return 'scrollLeft' in element ? element.scrollLeft : element.pageXOffset;
},
getVisibleHeight(element) {
return element === window
? element.innerHeight
: element.getBoundingClientRect().height;
},
getVisibleWidth(element) {
return element === window
? element.innerWidth
: element.getBoundingClientRect().width;
}
};

45
litemall-vue/src/main.js Normal file
View File

@@ -0,0 +1,45 @@
import Vue from 'vue';
import App from './App.vue';
import router from './vue/router';
import './assets/scss/global.scss';
import '@/assets/scss/iconfont/iconfont.css';
import VeeValidate, { Validator } from 'vee-validate';
import VueCountdown from '@/vue/plugins/vue-countdown';
import zhCN from 'vee-validate/dist/locale/zh_CN';
import axios from '@/vue/plugins/axios';
import filters from '@/vue/filter';
Vue.use(VueCountdown);
Vue.use(axios);
Vue.use(filters);
Validator.localize('zh-CN', zhCN);
Vue.use(VeeValidate, {
locale: 'zh-CN'
});
import { Lazyload, Icon, Cell, CellGroup, loading, Button, Toast } from 'vant';
Vue.use(Icon);
Vue.use(Cell);
Vue.use(CellGroup);
Vue.use(loading);
Vue.use(Button);
Vue.use(Toast);
Vue.use(Lazyload, {
preLoad: 1.3,
error: require('@/assets/images/goods_default.png'),
loading: require('@/assets/images/goods_default.png'),
attempt: 1,
listenEvents: ['scroll'],
lazyComponent: true
});
Vue.config.productionTip = false;
new Vue({
router,
render: h => h(App)
}).$mount('#app');

View File

@@ -0,0 +1,67 @@
<template>
<van-cell-group>
<van-cell>
<van-notice-bar
:text="notice"
background="white"
:leftIcon="trumpet"
style="padding-left: 0"
/>
</van-cell>
<van-cell :title="address" icon="dingwei" isLink :url="mapSrc"></van-cell>
<van-cell icon="phone" isLink>
<template slot="title">
<a :href="'tel:' + mobile" class="store_mobile">{{mobile}}</a>
</template>
</van-cell>
</van-cell-group>
</template>
<script>
import { NoticeBar } from 'vant';
import trumpet from '@/assets/images/trumpet.png';
export default {
name: 'shop-info-group',
props: {
address: {
type: String,
required: true
},
mobile: {
type: String,
required: true
},
notice: {
type: String,
default: ''
},
location: Object
},
data() {
const { location } = this;
const MAP_PATH = `http://m.amap.com/navi/?dest=${location.lat},${
location.lng
}&key=ab67b14d58d47912a9feb63ba862450c&destName=${location.name}`;
return {
trumpet,
mapSrc: location ? MAP_PATH : '#'
};
},
created() {},
components: {
[NoticeBar.name]: NoticeBar
}
};
</script>
<style lang="scss" scoped>
.store_mobile {
color: #0000a0;
text-decoration: underline;
}
</style>

View File

@@ -0,0 +1,69 @@
<template>
<div class="signboard">
<img :src="boardUrl" :height="signboardHeight" width="100%">
<div class="store_opacity clearfix">
<div class="float-l">{{storeName}}</div>
<div class="float-r store_collect isCollect" @click="showCollect = true">
<van-icon name="shoucang-full" />
<span>收藏</span>
</div>
</div>
<van-popup v-model="showCollect" position="top" style="background-color: transparent">
<img :src="showCollect && collectImg" @click="showCollect = false" width="100%" alt="右上角收藏">
</van-popup>
</div>
</template>
<script>
import { Popup } from 'vant';
import collectImg from '@/assets/images/index_collect.png';
export default {
name: 'sign-board',
props: {
boardUrl: {
type: String,
required: true
},
storeName: {
type: String,
required: true
}
},
data() {
const clientW =
document.body.clientWidth || document.documentElement.clientWidth;
const signboardHeight = clientW ? (clientW * 2) / 3 : 250;
return {
signboardHeight,
showCollect: false,
collectImg
};
},
components: {
[Popup.name]: Popup
}
};
</script>
<style lang="scss" scoped>
.signboard {
position: relative;
min-height: 250px;
}
.store_opacity {
position: absolute;
bottom: 0;
color: #fff;
width: 100%;
background-image: linear-gradient(rgba(0, 0, 0, 0), rgba(0, 0, 0, 1));
padding: 15px 10px;
box-sizing: border-box;
}
.isCollect i {
color: $red;
}
</style>

View File

@@ -0,0 +1,510 @@
<template>
<div class="tab_home">
<div class="tal_class_searchBox">
<van-search placeholder="点击前往搜索" @click="$router.push({ name: 'search' })"/>
<div class="tal_class_searchMask"></div>
</div>
<van-swipe :autoplay="3000" indicator-color="white">
<van-swipe-item v-for="(image, index) in brandList" :key="index">
<img :src="image" style="height:230px">
</van-swipe-item>
</van-swipe>
<van-tabbar active-color="#7d7e80" v-model="active" class="goods-channel">
<van-tabbar-item
@click="changeTabbar(iconJson)"
style="font-size:14px"
v-if="index < 5"
v-for="(iconJson, index) in shopInfos.channel"
:key="index"
:icon="iconJson.iconUrl"
>{{iconJson.name}}</van-tabbar-item>
</van-tabbar>
<van-tabbar active-color="#7d7e80" v-model="active" class="goods-channel">
<van-tabbar-item
@click="changeTabbar(iconJson)"
style="font-size:14px"
v-if="index >= 5"
v-for="(iconJson, index) in shopInfos.channel"
:key="index"
:icon="iconJson.iconUrl"
>{{iconJson.name}}</van-tabbar-item>
<van-tabbar-item></van-tabbar-item>
</van-tabbar>
<!-- <van-panel title="优惠券" style=" padding-bottom: 10px;">
<div
class="van-coupon-item"
v-for="(coupon,index) in shopInfos.couponList"
:key="index"
@click="getCoupon(coupon.id)"
>
<div class="van-coupon-item__content">
<div class="van-coupon-item__head">
<h2>
<span>¥</span>
{{coupon.discount}}
</h2>
<p>{{coupon.desc }} - {{coupon.tag}}</p>
</div>
<div class="van-coupon-item__body">
<h2>{{coupon.name}}</h2>
<p>有效期{{coupon.days}} </p>
</div>
</div>
</div>
</van-panel>-->
<van-panel title="团购专区">
<!-- {{shopInfos.grouponList}} -->
<van-card
:thumb-link="goDetail(groupGood.goods.id)"
v-for="(groupGood ,index) in shopInfos.grouponList"
:key="index"
:title="groupGood.goods.name"
:desc="groupGood.goods.brief"
:num="groupGood.groupon_member"
:origin-price="groupGood.goods.counterPrice"
:price="groupGood.goods.retailPrice +'.00'"
:thumb="groupGood.goods.picUrl"
@native-click="goDetail(groupGood.goods.id)"
>
<!-- <div slot="footer">添加日期 {{item.addTime}}</div> -->
</van-card>
</van-panel>
<van-panel title="新品首发">
<!-- {{shopInfos.grouponList}} -->
<van-row gutter>
<van-col span="12" v-for="(newGood ,index) in shopInfos.newGoodsList" :key="index">
<router-link :to="{ path: `/items/detail/${newGood.id}`}">
<img :src="newGood.picUrl" style="width:180px;height:180px;">
</router-link>
<span
style="padding-left: 20px;position: relative;bottom: 10px; color: rgb(123, 116, 116);white-space: nowrap;"
>{{newGood.name}}</span>
<span
style="padding-left: 80px;position: relative;bottom: 10px; color:#ab956d"
> {{newGood.retailPrice}}</span>
</van-col>
</van-row>
</van-panel>
<van-panel title="人气推荐">
<!-- {{shopInfos.grouponList}} -->
<van-card
:thumb-link="goDetail(groupGood.id)"
v-for="(groupGood ,index) in shopInfos.hotGoodsList"
:key="index"
:title="groupGood.name"
:desc="groupGood.brief"
:origin-price="groupGood.counterPrice"
:price="groupGood.retailPrice +'.00'"
:thumb="groupGood.picUrl"
@native-click="goDetail(groupGood.id)"
>
<!-- <div slot="footer">添加日期 {{item.addTime}}</div> -->
</van-card>
</van-panel>
<!-- <van-list
v-model="loading"
class="scroll-load"
:finished="finished"
:immediate-check="false"
:offset="100"
@load="loadMore"
>
<item-group
v-for="( group, key ) in itemGroup"
v-if="group"
:key="key"
class="interval_bot"
:setting="group.setting"
>
<component
v-for="item in group.items"
:goods="item"
:key="item.id"
:is="getStyle(group.setting.style)"
@click="toGoods(item)"
>
<div slot="mask" v-if="lootAll(item)">
<img src="../../assets/images/not_enough.png" alt="已抢光">
</div>
<div slot="leftTopIcon" v-if="item.as_status < 2">
<img :src="mxStatus(item.as_status)" alt="秒杀">
</div>
</component>
</item-group>
</van-list>-->
</div>
</template>
<script>
import { HOME_module, ALL_GOODS } from '@/api/shop';
import getLocationParam from 'core/utils/location-param';
import mx_be_to from '@/assets/images/mx_be_to.png';
import mx_start from '@/assets/images/mx_start.png';
import SignBoard from './tabbar-home-sign-board';
import ShopInfoGroup from './tabbar-home-shop-info';
import ItemGroup from '@/vue/components/item-group/';
import ItemCardVert from '@/vue/components/item-card-vert/';
import ItemCardHori from '@/vue/components/item-card-hori/';
import loadMore from '@/vue/mixin/list-load-more';
import scrollFixed from '@/vue/mixin/scroll-fixed';
import _ from 'lodash';
const coupon = {
available: 1,
discount: 0,
denominations: 150,
originCondition: 0,
reason: '',
value: 150,
name: '优惠券名称',
startAt: 1489104000,
endAt: 1514592000
};
import {
List,
Swipe,
SwipeItem,
Tabbar,
TabbarItem,
Search,
Panel,
CouponCell,
CouponList,
Toast,
Card,
Row,
Col
} from 'vant';
export default {
mixins: [loadMore, scrollFixed],
data() {
const shop_id = getLocationParam('shop_id');
return {
shop_id,
brandList: [],
shopInfos: [],
shopInfo: null,
coupons: [coupon],
itemGroup: {
mx_goods: null,
activity_seckill: null,
shop_recommend: null,
goods: null
},
mx_be_to,
mx_start,
isLoading: false
};
},
computed: {
location() {
const shopInfo = this.shopInfo;
const local = {
name: shopInfo.shop_name,
lat: shopInfo.lat,
lng: shopInfo.lng
};
return local.lat && local.lng ? local : null;
}
},
created() {
// this.initViews();
this.initNewViews();
},
methods: {
goDetail(id) {
return `#/items/detail/${id}`;
},
async getCoupon(id) {
let errmsg = await this.$reqPost('/wx/coupon/receive', {
couponId: id
});
Toast.success('领取成功');
},
async changeTabbar(o) {
let { data } = await this.$reqGet(
`/wx/goods/category?id=${o.id}`
);
let categoryId = data.data.currentCategory.id;
this.$router.push({
path: `items/list?keyword=&itemClass=${categoryId}`
});
},
initViews() {
this.$reqGet(HOME_module, {
shop_id: this.shop_id,
'per-page': this.pages.perPage,
page: 1
}).then(res => {
const { shop_info, page } = res.data.data;
const {
mx_goods,
shop_recommend,
activity_seckill,
goods
} = this.decorate(res.data.data);
this.shopInfo = shop_info;
this.itemGroup.mx_goods = mx_goods;
this.itemGroup.shop_recommend = shop_recommend;
this.itemGroup.activity_seckill = activity_seckill;
this.itemGroup.goods = goods;
this.setPages(page);
});
},
initNewViews() {
this.$reqGet('/wx/home/index').then(res => {
this.shopInfos = res.data.data;
this.brandList = [];
_.each(res.data.data.brandList, v => {
this.brandList.push(v.picUrl);
});
});
},
initData() {
// return this.$reqGet(ALL_GOODS, {
// shop_id: this.shop_id,
// 'per-page': this.pages.perPage,
// page: this.pages.currPage
// }).then(res => {
// const { items, page } = res.data.data;
// this.itemGroup.goods && this.itemGroup.goods.items.push(...items);
// return page;
// });
},
toGoods(item) {
// 如果是秒杀商品, 并且已经抢光
if (this.lootAll(item)) {
this.$dialog.alert({ message: '该秒杀商品已抢光,看看别的吧!' });
return;
}
this.$router.push({ path: `/items/detail/${item.id}` });
},
groupIcon(key) {
const iconGroup = {
activity_seckill: 'naozhong',
goods: 'list',
mx_goods: 'n4',
shop_recommend: 'good'
};
return iconGroup[key] || '';
},
getStyle(style) {
return style ? 'item-card-vert' : 'item-card-hori';
},
decorate({ mx_goods, shop_recommend, activity_seckill, goods }) {
if (mx_goods) {
mx_goods.setting.icon = 'n4';
mx_goods.setting.title_desc = '分享得金豆';
mx_goods.setting.title_color = '#db3d3c';
mx_goods.setting.item_len = mx_goods.items.length;
}
if (shop_recommend) {
shop_recommend.setting.icon = 'good';
shop_recommend.setting.item_len = shop_recommend.items.length;
}
if (activity_seckill) {
activity_seckill.setting.icon = 'naozhong';
activity_seckill.setting.title_color = '#db3d3c';
activity_seckill.setting.item_len = activity_seckill.items.length;
}
if (goods) {
goods.setting.icon = 'list';
goods.setting.item_len = goods.items.length;
}
return {
mx_goods,
shop_recommend,
activity_seckill,
goods
};
},
lootAll(item) {
return (
typeof item.as_status !== 'undefined' && item.sold_num == item.total
);
},
mxStatus(as_status) {
return as_status ? this.mx_start : this.mx_be_to;
}
},
components: {
// Vue.use(Tabbar).use(TabbarItem);,
[Row.name]: Row,
[Col.name]: Col,
[Card.name]: Card,
[Toast.name]: Toast,
[CouponCell.name]: CouponCell,
[CouponList.name]: CouponList,
[Search.name]: Search,
[Panel.name]: Panel,
[List.name]: List,
[Swipe.name]: Swipe,
[SwipeItem.name]: SwipeItem,
[Tabbar.name]: Tabbar,
[TabbarItem.name]: TabbarItem,
[SignBoard.name]: SignBoard,
[ShopInfoGroup.name]: ShopInfoGroup,
[ItemGroup.name]: ItemGroup,
[ItemCardVert.name]: ItemCardVert,
[ItemCardHori.name]: ItemCardHori
}
};
</script>
<style lang="scss" scoped>
.interval_bot {
margin-bottom: 10px;
}
.goods-channel {
position: sticky;
border-bottom-width: 0px;
}
.van-coupon-cell--selected {
color: #323233;
}
.van-coupon-list {
height: 100%;
position: relative;
background-color: #f8f8f8;
}
.van-coupon-list__field {
padding: 7px 15px;
}
.van-coupon-list__exchange {
height: 32px;
line-height: 30px;
}
.van-coupon-list__list {
overflow-y: auto;
padding: 15px 0;
-webkit-box-sizing: border-box;
box-sizing: border-box;
-webkit-overflow-scrolling: touch;
}
.van-coupon-list__close {
left: 0;
bottom: 0;
position: absolute;
font-weight: 500;
}
.van-coupon-list__empty {
padding-top: 100px;
text-align: center;
}
.van-coupon-list__empty p {
color: #969799;
margin: 15px 0;
font-size: 14px;
line-height: 20px;
}
.van-coupon-list__empty img {
width: 80px;
height: 84px;
}
.van-coupon-item {
overflow: hidden;
border-radius: 4px;
margin: 0 15px 15px;
background-color: #fff;
-webkit-box-shadow: 0 0 4px rgba(0, 0, 0, 0.1);
box-shadow: 0 0 4px rgba(0, 0, 0, 0.1);
}
.van-coupon-item:active {
background-color: #e8e8e8;
}
.van-coupon-item__content {
display: -webkit-box;
display: -ms-flexbox;
display: flex;
height: 100px;
padding: 24px 0 0 15px;
-webkit-box-sizing: border-box;
box-sizing: border-box;
}
.van-coupon-item h2,
.van-coupon-item p {
margin: 0;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
.van-coupon-item h2 {
height: 34px;
font-weight: 500;
line-height: 34px;
}
.van-coupon-item p {
font-size: 12px;
line-height: 16px;
color: #969799;
}
.van-coupon-item__head {
min-width: 90px;
}
.van-coupon-item__head h2 {
color: #f44;
font-size: 24px;
}
.van-coupon-item__head h2 span {
font-size: 50%;
}
.van-coupon-item__body {
-webkit-box-flex: 1;
-ms-flex: 1;
flex: 1;
position: relative;
border-radius: 0 4px 4px 0;
}
.van-coupon-item__body h2 {
font-size: 16px;
}
.van-coupon-item__corner {
top: 16px;
right: 15px;
position: absolute;
}
.van-coupon-item__corner .van-icon {
border-color: #f44;
background-color: #f44;
}
.van-coupon-item__reason {
padding: 7px 15px;
border-top: 1px dashed #ebedf0;
background-color: #fafafa;
}
.van-coupon-item--disabled:active {
background-color: #fff;
}
.van-coupon-item--disabled .van-coupon-item__content {
height: 90px;
}
.van-coupon-item--disabled h2,
.van-coupon-item--disabled p,
.van-coupon-item--disabled span {
color: #969799;
}
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,513 @@
<template>
<div class="item_cell_group">
<van-cell-group>
<van-cell
title="选择规格"
isLink
:value="selectSku.selectedSkuComb.sku_str"
@click.native="skuClick"
/>
<van-cell title="商品属性" isLink @click.native="propsPopup = true"/>
<!-- <van-cell
title="配送至"
isLink
:value="addressVal.area_name"
@click.native="addressPopup = true"
/>-->
<!-- <van-cell title="运费" :value="postFee | yuan"/> -->
</van-cell-group>
<van-sku
v-model="showSku"
:showAddCartBtn="showAddCartBtn"
:buyText="buyText"
:sku="skus.sku"
:goods="skus.goods_info"
:goodsId="goodsInfo.id"
:disableStepperInput="true"
@buy-clicked="buyGoods"
@add-cart="onAddCartClicked"
/>
<van-popup v-model="propsPopup" position="bottom">
<popup-props :propsStr="props_str"></popup-props>
</van-popup>
<van-popup v-model="areaPopup" position="bottom">
<popup-area v-if="areaPopup" @confirm="emitAddressVal" @cancel="areaPopup = false"/>
</van-popup>
<van-popup v-model="addressPopup" position="bottom">
<popup-address
:is-show="addressPopup"
:addressVal="addressVal"
:default-id="defaultId"
@confirm="emitAddressVal"
@area-click="areaClick"
/>
</van-popup>
</div>
</template>
<script>
const popupArea = () =>
import(/* webpackChunkName: "popup-area" */ './popup-area');
import popupAddress from './popup-address';
import popupProps from './popup-props';
import actionMixin from '../mix';
import { ADDRESS_DEFAULT } from '@/api/user';
import _ from 'lodash';
import { debug } from 'util';
// import { POST_FEE } from '@/api/shop';
function getGoodInfoFromSpecification(productLists, compareS1, compareS2) {
if (productLists.length === 0) {
return productLists;
}
_.each(productLists, v => {
if (_.without(v, compareS1, compareS2).length > 0) {
return v;
}
});
console.error(
compareS1 +
',' +
compareS2 +
' getGoodInfoFromSpecification no match '
);
return {};
}
export default {
name: 'entity-group',
props: {
goodsInfo: {
type: Object,
default: () => ({})
},
specification_list: {
type: Array,
default: () => []
},
selectSku: {
type: Object,
default: () => ({})
},
addressVal: {
type: Object,
default: () => ({})
}
},
mixins: [actionMixin],
data() {
const sku = this.skuAdapter(this.goodsInfo.skus, this.goodsInfo.prop_imgs);
const goods_info = this.setSkuGoodsInfo(this.goodsInfo);
const postFee = this.goodsInfo.is_fenxiao ? '免邮费' : '';
return {
postFee,
cartOrBuy: '',
propsPopup: false,
addressPopup: false,
areaPopup: false,
skus: {
sku,
goods_info
},
defaultId: -1
};
},
computed: {
props_str() {
// this.goodsInfo.props_str = '品牌:GOON大王天使;纸尿裤尺码:S58;';
// if (this.goodsInfo.props_str) {
// return this.goodsInfo.props_str
// .split(';')
// .filter(str => str != '')
// .map(str => str.split(':'));
// }
let props_arr = [];
_.each(this.goodsInfo.attribute, json => {
props_arr.push([json['attribute'], json['value']]);
});
return props_arr || [];
},
weight() {
return parseFloat(this.goodsInfo.weight) * this.selectSku.selectedNum;
}
},
created() {
// this.getAddressDefault();
},
methods: {
skuClick(status) {
this.cartOrBuy = status;
this.showSku = true;
},
buyGoods(data) {
switch (this.cartOrBuy) {
case 'cart':
this.addCart(data);
break;
case 'buy':
this.bug(data);
break;
default:
break;
}
// this.$toast(JSON.stringify(data));
this.showSku = false;
},
getProductId(s1, s2) {
var productId;
let s1_name = _.find(this.specification_list, v => {
return v.id === s1;
}).value;
let s2_name = _.find(this.specification_list, v => {
return v.id === s2;
}).value;
_.each(this.goodsInfo.productList, v => {
let result = _.without(v.specifications, s1_name, s2_name);
if (result.length === 0) {
productId = v.id;
}
});
return productId;
},
getProductIdByOne(s1) {
var productId;
let s1_name = _.find(this.specification_list, v => {
return v.id === s1;
}).value;
_.each(this.goodsInfo.productList, v => {
let result = _.without(v.specifications, s1_name);
if (result.length === 0) {
productId = v.id;
}
});
return productId;
},
async bug(data) {
console.log(data);
// debugger;
let params = {
goodsId: data.goodsId,
number: data.selectedNum,
productId: this.getProductIdByOne(data.selectedSkuComb.s1)
};
// 如果包含s2说明多种规格,目前支持两个规格
if (_.has(data.selectedSkuComb, 's2')) {
params.productId = this.getProductId(
data.selectedSkuComb.s1,
data.selectedSkuComb.s2
);
}
this.$reqPost('/wx/cart/fastadd', params).then(req => {
let cartId = req.data.data;
// this.$reqGet(
// `/wx/cart/checkout?cartId=${cartId}&addressId=0&couponId=0&grouponRulesId=0`
// ).then(() => {cartId=${cartId}
this.$router.push({
name: `placeOrderEntity`,
params: { cartId: cartId }
});
// });
});
},
addCart(data) {
console.log(data);
// debugger;
let params = {
goodsId: data.goodsId,
number: data.selectedNum,
productId: this.getProductIdByOne(data.selectedSkuComb.s1)
};
// 如果包含s2说明多种规格,目前支持两个规格
if (_.has(data.selectedSkuComb, 's2')) {
params.productId = this.getProductId(
data.selectedSkuComb.s1,
data.selectedSkuComb.s2
);
}
this.$reqPost('/wx/cart/add', params).then(() => {
this.$emit('cart-count', data.selectedNum);
this.$toast({
message: '已添加至购物车',
duration: 1500
});
// this.cartInfo = String(parseInt(this.cartInfo) + 1);
});
},
onAddCartClicked(data) {
this.$toast(JSON.stringify(data));
},
areaClick() {
this.areaPopup = true;
this.addressPopup = false;
},
emitAddressVal(data) {
this.$emit('update:addressVal', data);
},
setSkuGoodsInfo({ name, pic_url, sales_price }) {
return {
title: name,
picture: pic_url,
price: sales_price
};
},
getAddressDefault() {
localStorage.getItem('Authorization') &&
this.$reqGet(ADDRESS_DEFAULT).then(res => {
const data = res.data.data;
this.defaultId = data.id;
this.emitAddressVal(data);
});
},
skuAdapter(skus = [], prop_imgs = []) {
// debugger;
const tree = this.setSkuTree(skus, prop_imgs);
const list = this.setSkuList(skus);
const skuInfo = {
price: parseInt(this.goodsInfo.retailPrice), // 未选择规格时的价格
stock_num: this.goodsInfo.productList[0].number || 1024, // 总库存
collection_id: '', // 无规格商品skuId取collection_id否则取所选sku组合对应的id
none_sku: false, // 是否无规格商品
hide_stock: false
};
return {
tree,
list,
...skuInfo
};
},
setSkuList(skus) {
// debugger;
// return [
// // {
// // id: 2259,
// // price: this.goodsInfo.retailPrice * 100,
// // discount: 100,
// // code: '',
// // s1: this.goodsInfo.specificationList[0].valueList[0].id,
// // // s1: this.goodsInfo.productList[0].id,
// // kdt_id: 55,
// // discount_price: 0,
// // stock_num: this.goodsInfo.productList[0].number || 1024,
// // stock_mode: 0,
// // is_sell: null,
// // combin_sku: false,
// // goods_id: 946755
// // },
// // {
// // id: 2259,
// // price: this.goodsInfo.retailPrice * 100,
// // discount: 100,
// // code: '',
// // s1: this.goodsInfo.specificationList[0].valueList[1].id,
// // // s1: this.goodsInfo.productList[0].id,
// // kdt_id: 55,
// // discount_price: 0,
// // stock_num: this.goodsInfo.productList[0].number || 1024,
// // stock_mode: 0,
// // is_sell: null,
// // combin_sku: false,
// // goods_id: 946755
// // },
// // {
// // id: 2259,
// // price: this.goodsInfo.retailPrice * 100,
// // discount: 100,
// // code: '',
// // s2: this.goodsInfo.specificationList[1].valueList[0].id,
// // // s1: this.goodsInfo.productList[0].id,
// // kdt_id: 55,
// // discount_price: 0,
// // stock_num: this.goodsInfo.productList[0].number || 1024,
// // stock_mode: 0,
// // is_sell: null,
// // combin_sku: false,
// // goods_id: 946755
// // },
// // {
// // id: 2259,
// // price: this.goodsInfo.retailPrice * 100,
// // discount: 100,
// // code: '',
// // s2: this.goodsInfo.specificationList[1].valueList[1].id,
// // // s1: this.goodsInfo.productList[0].id,
// // kdt_id: 55,
// // discount_price: 0,
// // stock_num: this.goodsInfo.productList[0].number || 1024,
// // stock_mode: 0,
// // is_sell: null,
// // combin_sku: false,
// // goods_id: 946755
// // },
// {
// id: 2259, // skuId下单时后端需要
// price: 30, // 价格(单位分)
// s1: 270, // 规格类目 k_s 为 s1 的对应规格值 id
// s2: 272, // 规格类目 k_s 为 s2 的对应规格值 id
// s3: '0', // 最多包含3个规格值为0表示不存在该规格
// stock_num: 30 // 当前 sku 组合对应的库存
// }
// ];
var sku_list = [];
// 如果是多种规格则需要特殊处理
if (this.goodsInfo.specificationList.length > 1) {
_.each(this.goodsInfo.productList, v => {
if (v.specifications.length > 1) {
var sku_list_obj = {};
_.each(v.specifications, (specificationName, index) => {
sku_list_obj[
's' + (~~index + 1)
] = this.findSpecificationInfoByName(specificationName).id;
});
}
sku_list_obj.price = v.price * 100;
sku_list_obj.stock_num = v.number;
sku_list.push(sku_list_obj);
});
// debugger;
} else {
sku_list = [
{
id: 2259,
price: this.goodsInfo.retailPrice * 100,
discount: 100,
code: '',
s1: this.goodsInfo.specificationList[0].valueList[0].id,
// s1: this.goodsInfo.productList[0].id,
kdt_id: 55,
discount_price: 0,
stock_num: this.goodsInfo.productList[0].number || 1024,
stock_mode: 0,
is_sell: null,
combin_sku: false,
goods_id: 946755
}
];
}
return sku_list;
},
findSpecificationInfoByName(name) {
let info = {};
_.each(this.specification_list, specification => {
if (specification.value === name) {
info = specification;
}
});
return info;
},
setSkuTree(skus, prop_imgs) {
// const skulist = [];
// skus.forEach(el => {
// const propImg = prop_imgs.find(img => img.props == el.props);
// el.props_str_arr = el.props_str.split(';').filter(str => str != '');
// el.props_arr = el.props.split(';').filter(str => str != '');
// el.imgUrl = propImg ? propImg.url : '';
// });
// debugger;
// skus.forEach(el => {
// el.props_str_arr.forEach((sku, i) => {
// const prop = el.props_arr[i];
// // 大规格
// const pName = sku.substr(0, sku.indexOf(':'));
// const k_id = prop.substr(0, prop.indexOf(':'));
// // 规格值 prop_values
// const vName = sku.substr(sku.indexOf(':') + 1);
// const vid = prop.substr(prop.indexOf(':') + 1);
// debugger;
// if (!skulist[i]) {
// skulist[i] = {
// k_id,
// k: pName,
// v: [
// {
// id: vid,
// name: vName,
// imgUrl: el.imgUrl
// }
// ],
// k_s: `s${i + 1}`
// };
// } else {
// const isPass = skulist[i].v.some(val => val.id == vid);
// !isPass &&
// skulist[i].v.push({
// id: vid,
// name: vName,
// imgUrl: el.imgUrl
// });
// }
// });
// });
// return skulist;
let that = this;
let specifications = [];
_.each(this.goodsInfo.specificationList, (v, k) => {
_.each(v.valueList, vv => {
vv.name = vv.value;
_.each(this.goodsInfo.productList, p => {
if (p.id === vv.id) {
vv.imgUrl = p.url;
vv.number = p.number;
// vv.id = getGoodInfoFromSpecification(
// this.goodsInfo.productList,
// v.name,
// vv.value
// ).id;
//todo id
}
});
_.isEmpty(vv.imgUrl)
? (vv.imgUrl = this.goodsInfo.productList[0].url)
: vv.imgUrl;
});
_.each(v.valueList, v => {
that.specification_list.push(v);
});
specifications.push({
k: v.name,
v: v.valueList,
k_s: 's' + (~~k + 1)
});
});
// _.each(this.goodsInfo.productList, v => {
// v.imgUrl = v.url;
// v.specification = v.specifications[0];
// });
// specifications.push({
// k: '规格',
// v: this.goodsInfo.productList,
// k_s: 's1'
// });
// debugger;
return specifications;
}
},
components: {
popupArea,
[popupAddress.name]: popupAddress,
[popupProps.name]: popupProps
}
};
</script>
<style lang="scss" scoped>
</style>

View File

@@ -0,0 +1,168 @@
<template>
<div class="popup_wrap address_wrap">
<van-icon name="clear" class="cancel_popup" @click.native="hide"></van-icon>
<!-- <div class="popup_header">配送至</div> -->
<div class="popup_content">
<van-loading v-if="!addressReady" class="address_popup_load" type="circle" color="black"/>
<div v-for="(li, i) in address_list" :key="i" @click="listChoose(li)">
<van-tag plain type="danger" style="margin-right: 5px;" v-if="li.isDefault">默认</van-tag>
{{li.area_name + li.address}}
<van-icon name="success" class="address_active" v-show="addressVal.id == li.id"></van-icon>
</div>
</div>
<div class="popup_footer">
<van-cell-group>
<van-cell is-link title="其他区域" @click.native="areaChoose"></van-cell>
</van-cell-group>
</div>
</div>
</template>
<script>
import { ADDRESS } from '@/api/user';
import { Tag } from 'vant';
export default {
name: 'popup-address',
props: {
isShow: Boolean,
defaultId: [Number, String],
addressVal: {
type: Object,
default: () => ({})
}
},
data() {
return {
addressReady: false,
address_list: [],
address_default: {}
};
},
watch: {
isShow(val) {
val && !this.address_list.length && this.getAddress();
}
},
created() {
!this.address_list.length && this.getAddress();
},
methods: {
hide() {
this.$parent.$emit('input', false);
},
getAddress() {
if (localStorage.getItem('Authorization')) {
this.$reqGet(ADDRESS).then(res => {
const data = res.data.data.map(data => {
data.isDefault = data.id == this.defaultId;
return data;
});
this.address_list = data;
this.addressReady = true;
});
} else {
this.address_list = [];
this.addressReady = true;
}
},
listChoose(li) {
this.$emit('confirm', li);
this.hide();
},
areaChoose() {
this.$emit('area-click', true);
}
},
components: {
[Tag.name]: Tag
}
};
</script>
<style lang="scss" scoped>
@import '../../../../assets/scss/var';
@import '../../../../assets/scss/mixin';
.popup_wrap {
position: relative;
padding-bottom: 30px;
box-sizing: border-box;
.popup_header {
padding: 15px 0 30px 0;
text-align: center;
}
.popup_content {
min-height: 150px;
max-height: 400px;
box-sizing: border-box;
overflow-x: hidden;
overflow-y: scroll;
padding: 0 10px;
line-height: 30px;
&::-webkit-scrollbar {
background-color: #fff;
width: 5px;
}
&::-webkit-scrollbar-thumb {
border-radius: 3px;
background-color: #bebebe;
}
ol {
padding-left: 15px;
list-style: decimal;
}
}
.cancel_popup {
position: absolute;
right: 15px;
top: 15px;
z-index: 9;
font-size: 18px;
}
}
.address_wrap {
.popup_header {
@include one-border;
text-align: left;
padding-bottom: 15px;
padding-left: 10px;
margin-bottom: 15px;
}
.popup_content {
@include one-border;
line-height: 22px;
max-height: 300px;
overflow-x: hidden;
overflow-y: auto;
> div {
position: relative;
margin-bottom: 10px;
padding-right: 30px;
}
> div.address_popup_load {
margin: 0 auto;
padding: 0;
}
> div i {
position: absolute;
right: 0;
top: 50%;
transform: translate(0, -50%);
color: #fff;
&.address_active {
color: $red;
}
}
}
}
</style>

View File

@@ -0,0 +1,50 @@
<template>
<van-area v-once :areaList="areaList" @confirm="areaConfirm" @cancel="areaCanccel" />
</template>
<script>
import areaList from './area.json';
import { Area } from 'vant';
export default {
name: 'popup-area',
data() {
return {
areaList
};
},
methods: {
areaCanccel() {
this.$emit('cancel');
},
areaConfirm(areaData) {
if (areaData.every(area => area.code != -1)) {
this.$emit('confirm', this.analyArea(areaData));
this.$emit('cancel');
} else {
this.$toast('请选择完整地区');
}
},
analyArea(areaData) {
const province = areaData[0] || {};
const city = areaData[1] || {};
const district = areaData[2] || {};
return {
id: null,
area_name: `${province.name} ${city.name} ${district.name} `,
district: district.code,
city: city.code,
province: province.code
};
}
},
components: {
[Area.name]: Area
}
};
</script>

View File

@@ -0,0 +1,79 @@
<template>
<div class="popup_wrap">
<van-icon name="clear" class="cancel_popup" @click.native="$parent.value = false"></van-icon>
<div class="popup_header">商品属性</div>
<div class="popup_content">
<van-cell-group>
<van-cell v-for="(str, i) in propsStr" :key="i">
<van-row>
<van-col span="8">{{str[0]}}</van-col>
<van-col span="16">{{str[1]}}</van-col>
</van-row>
</van-cell>
</van-cell-group>
</div>
</div>
</template>
<script>
import { Row, Col } from 'vant';
export default {
name: 'popup-props',
props: {
propsStr: {
type: Array,
default: () => []
}
},
components: {
[Col.name]: Col,
[Row.name]: Row
}
};
</script>
<style lang="scss" scoped>
.popup_wrap {
position: relative;
padding-bottom: 30px;
box-sizing: border-box;
.popup_header {
padding: 15px 0 30px 0;
text-align: center;
}
.popup_content {
min-height: 150px;
max-height: 400px;
box-sizing: border-box;
overflow-x: hidden;
overflow-y: scroll;
padding: 0 10px;
line-height: 30px;
&::-webkit-scrollbar {
background-color: #fff;
width: 5px;
}
&::-webkit-scrollbar-thumb {
border-radius: 3px;
background-color: #bebebe;
}
ol {
padding-left: 15px;
list-style: decimal;
}
}
.cancel_popup {
position: absolute;
right: 15px;
top: 15px;
z-index: 9;
font-size: 18px;
}
}
</style>

View File

@@ -0,0 +1,121 @@
<template>
<div class="item_cell_group">
<van-cell-group>
<van-cell title="有效期" value="2017-8-18~2018-8-19"/>
<van-cell title="选择规格" isLink :value="skuComb.sku_str" @click.native="skuClick" />
<van-cell title="文一西路花蒋路交叉口" isLink url="http://m.amap.com/navi/?dest=120.145409,30.238695&key=ab67b14d58d47912a9feb63ba862450c&destName=三潭印月"/>
<van-cell isLink v-if="mobile">
<template slot="title">
<a :href="'tel:' + mobile" class="store_mobile">{{mobile}}</a>
</template>
</van-cell>
<van-cell title="注意事项" @click.native="showPopup = true" isLink />
</van-cell-group>
<van-sku
v-model="showSku"
:showAddCartBtn="showAddCartBtn"
:buyText="buyText"
:sku="sku.sku"
:goods="sku.goods_info"
:goodsId="sku.goods_id"
:disableStepperInput="true"
@buy-clicked="buyGoods"
/>
<van-popup v-model="showPopup" position="bottom" lockOnScroll>
<div class="popup_wrap">
<van-icon name="clear" class="cancel_popup" @click.native="showPopup = false"></van-icon>
<div class="popup_header">注意事项</div>
<div class="popup_content">
<div>这里是注意事项的内容:</div>
<ol>
<li>这里是注意意事项的内容事项的内容</li>
<li>这里是注意事意事项的内容项的内容</li>
<li>这里是注意意事项的内容事项的内容</li>
<li>这里是注意意事项的内容事项的内容</li>
<li>这里是注意意事项的内容事项的内容</li>
<li>这里是注意事项的内容</li>
<li>这里是注意意事项的内容事项的内容</li>
<li>这里是注意意事项的内容事项的内容</li>
<li>这里是注意意事项的内容事项的内容</li>
</ol>
</div>
</div>
</van-popup>
</div>
</template>
<script>
// import sku from "./sku";
import actionMixin from '../mix';
export default {
name: 'virtual-group',
props: {
mobile: String,
skuComb: {
type: Object,
default: () => ({})
},
addressVal: {
type: Object,
default: () => ({})
}
},
mixins: [actionMixin],
data() {
return {
// sku,
};
},
methods: {
addressClick() {}
}
};
</script>
<style lang="scss" scoped>
.popup_wrap {
position: relative;
padding-bottom: 30px;
box-sizing: border-box;
}
.popup_header {
padding: 15px 0 30px 0;
text-align: center;
}
.popup_content {
height: 150px;
box-sizing: border-box;
overflow-x: hidden;
overflow-y: scroll;
padding: 0 10px;
line-height: 30px;
&::-webkit-scrollbar {
background-color: #fff;
width: 5px;
}
&::-webkit-scrollbar-thumb {
border-radius: 3px;
background-color: #bebebe;
}
ol {
padding-left: 15px;
list-style: decimal;
}
}
.store_mobile {
display: block;
}
.cancel_popup {
position: absolute;
right: 15px;
top: 15px;
font-size: 18px;
}
</style>

View File

@@ -0,0 +1,274 @@
<template>
<div class="item_detail">
<van-swipe :autoplay="3000">
<van-swipe-item v-for="(image, index) in itemImgs" :key="index">
<!-- <img v-lazy="image" width="100%"> -->
<img :src="image" width="100%">
</van-swipe-item>
</van-swipe>
<van-cell-group class="item_cell_group" v-if="goods">
<van-cell class="item_info">
<div>
<span class="item_price">{{ goods.retailPrice*100 | yuan }}</span>
<span class="item_market_price">{{goods.counterPrice*100 | yuan}}</span>
</div>
<div class="item-title">
<!-- <van-tag plain type="danger" v-if="goods.is_haitao">海淘</van-tag> -->
{{ goods.name }}
</div>
<!-- <div class="item_intro">{{goods.sell_point}}</div> ???-->
<!-- <div class="item_dispatch">发货地: {{}}</div> -->
</van-cell>
</van-cell-group>
<component
v-if="goods"
ref="goodAction"
v-bind:is="'entity-group'"
:selectSku.sync="selectSku"
:addressVal.sync="addressVal"
:mobile="mobile"
:goods-info="goods"
@skuBuy="doBuyNow"
@cart-count="cartEvent"
/>
<div class="item_desc" v-if="goods">
<div class="item_desc_title">商品详情</div>
<div class="item_desc_wrap" v-if="goods.detail.length === 0" style="padding-left: 170px;">
<p>无详情</p>
</div>
<div class="item_desc_wrap" v-html="goods.detail"></div>
</div>
<van-goods-action>
<!-- <van-goods-action-mini-btn @click="doContact" icon="wangwang" iconClass="red afterTag"/> -->
<van-goods-action-mini-btn @click="toCart" icon="cart" :info="cartInfo"/>
<van-goods-action-mini-btn
:style="collectAdd ? 'color: #f7b444;':''"
@click="addCollect"
icon="shoucang"
/>
<van-goods-action-big-btn @click="openSku('cart')" text="加入购物车"/>
<van-goods-action-big-btn primary @click="openSku('buy')" text="立即购买"/>
</van-goods-action>
<van-popup v-model="showContact">
<md-kefu mobile="16454193338"/>
</van-popup>
</div>
</template>
<script>
import { GOODS_DETAIL } from '@/api/goods';
import {
Swipe,
SwipeItem,
GoodsAction,
GoodsActionBigBtn,
GoodsActionMiniBtn,
Popup
} from 'vant';
import md_kefu from '@/vue/components/md-kefu/';
export default {
props: {
itemId: [String, Number]
},
data() {
const isLogin = !!localStorage.getItem('Authorization');
return {
isLogin,
itemImgs: [],
collectAdd: false,
showContact: false,
cartInfo: '0',
mobile: '13454193338',
selectSku: {
selectedNum: 1,
selectedSkuComb: {}
},
addressVal: {
id: null,
area_name: '',
district: '',
city: '',
province: ''
},
goods: null,
productList: []
};
},
computed: {
// itemImgs() {
// debugger;
// return this.goods.info.gallery;
// }
},
created() {
this.initData();
},
methods: {
async initData() {
// let a = this.$route.params.itemId;
this.$reqGet(`/wx/goods/detail?id=${this.itemId}`).then(
res => {
this.goods = res.data.data.info;
this.goods.attribute = res.data.data.attribute;
this.goods.specificationList = res.data.data.specificationList;
this.goods.productList = res.data.data.productList;
this.productList = res.data.data.productList;
this.itemImgs = res.data.data.info.gallery || [];
this.collectAdd = res.data.data.userHasCollect === 1;
}
);
let { data } = await this.$reqGet('/wx/cart/goodscount');
this.cartInfo = data.data;
// this.$reqGet(GOODS_DETAIL).then(res => {
// this.goods = res.data.data;
// });
},
openSku(status) {
const goodAction = this.$refs.goodAction;
goodAction.skuClick(status);
},
cartEvent(count) {
this.cartInfo = ~~this.cartInfo + ~~count + '';
},
doBuyNow() {
// if (
// (this.goods.has_sku && this.selectSku.sku_id) ||
// !this.goods.has_sku
// ) {
// this.$router.push({ name: 'placeOrderEntity' });
// } else {
// const goodAction = this.$refs.goodAction;
// goodAction.showSku = true;
// goodAction.isSkuBuy = true;
// }
},
addCart() {
// debugger;
// if (this.goods.has_sku && this.selectSku.sku_id) {
// this.$reqPost('/wx/cart/add', {
// goodsId: this.itemId,
// number: 1
// }).then(() => {
// this.$toast({
// message: '已添加至购物车',
// duration: 1500
// });
// this.cartInfo = String(parseInt(this.cartInfo) + 1);
// });
// }
},
doContact() {
this.showContact = true;
},
toCart() {
this.$router.push({
name: 'cart'
});
},
async addCollect() {
let { data } = await this.$reqPost(
'/wx/collect/addordelete',
{
valueId: this.itemId,
type: 0
}
);
let type = data.data.type;
this.collectAdd = type === 'add' ? true : false;
this.$toast({
message: this.collectAdd ? '添加成功' : '取消成功',
duration: 1500
});
}
},
components: {
[md_kefu.name]: md_kefu,
[Popup.name]: Popup,
[Swipe.name]: Swipe,
[SwipeItem.name]: SwipeItem,
[GoodsAction.name]: GoodsAction,
[GoodsActionBigBtn.name]: GoodsActionBigBtn,
[GoodsActionMiniBtn.name]: GoodsActionMiniBtn,
'entity-group': () =>
import(/* webpackChunkName: "EntityGroup" */ './EntityGroup/index'),
'virtual-group': () =>
import(/* webpackChunkName: "VirtualGroup" */ './VirtualGroup/index.vue')
}
};
</script>
<style lang="scss" scoped>
.item_detail {
img {
max-width: 100%;
}
}
.item_cell_group {
margin-bottom: 15px;
}
.item_price {
font-size: 20px;
color: $red;
margin-right: 10px;
}
.item_market_price {
color: $font-color-gray;
text-decoration: line-through;
font-size: $font-size-small;
}
.item-title {
line-height: 1.4;
}
.item_dispatch {
font-size: $font-size-small;
color: $font-color-gray;
}
.item_intro {
line-height: 18px;
margin: 5px 0;
font-size: $font-size-small;
color: $font-color-gray;
overflow: hidden;
text-overflow: ellipsis;
display: -webkit-box;
-webkit-box-orient: vertical;
-webkit-line-clamp: 3;
}
.item_desc {
background-color: #fff;
p {
padding: 0 10px;
}
/deep/ img {
max-width: 100%;
display: block;
}
}
.item_desc_title {
@include one-border;
padding: 10px 0;
text-align: center;
}
</style>

View File

@@ -0,0 +1,42 @@
import { Sku, Popup } from 'vant';
export default {
props: {
selectSku: Object
},
data() {
return {
showSku: false,
showAddCartBtn: false,
isSkuBuy: false,
buyText: '确定'
};
},
methods: {
buyGoods(data) {
data = this.selectSkuData(data);
this.showSku = false;
this.$emit('update:selectSku', data);
this.isSkuBuy && this.$emit('skuBuy', data);
},
selectSkuData(data) {
// debugger
if (data.selectedSkuComb) {
data.selectedSkuComb.sku_str = data.selectedSkuComb.props_str_arr
.map(str => str.match(/[^:]*:([^:]*)/)[1])
.join(',');
} else {
data.selectedSkuComb = {};
}
return data;
},
skuClick() {
this.isSkuBuy = false;
this.showSku = true;
}
},
components: {
[Sku.name]: Sku,
[Popup.name]: Popup
}
};

View File

@@ -0,0 +1,251 @@
<template>
<div class="item_list over-hide">
<form action="/search">
<van-search
placeholder="请输入商品名称"
v-model="searchVal"
@click="$router.push({ name: 'search' })"
showAction
/>
</form>
<van-tabs v-model="tabActive" @disabled="toggleFilterModal(true)">
<van-tab
v-for="(tab, tabIndex) in tabsItem"
:title="tab.name"
:key="tab.type"
:disabled="tab.sort === false"
>
<InfinityScroll
:ref="'tabScrolls' + tabIndex"
class="full-page scroll-wrap fix-height"
:beforeRequest="beforeRequest"
:apiUrl="listApi"
@onLoad="onLoad(tabIndex, $event)"
>
<item-group>
<item-card-hori
v-for="(item, i) in tab.items"
:key="i"
:goods="item"
@click="itemClick(item.id)"
/>
</item-group>
</InfinityScroll>
</van-tab>
</van-tabs>
<van-popup class="filterItem" v-model="filterItemShow" position="right">
<ul>
<li
v-for="(li, i) in filterItem"
:key="i"
@click="filterMethod(i)"
:class="{filter_active: li.isActive}"
>
{{li.name}}
<van-icon name="success" v-show="li.isActive" class="float-r"/>
</li>
</ul>
</van-popup>
<!-- <transition name="fade">
<van-icon
name="arrowupcircle"
class="backTop"
@click.native="backTop"
v-show="showArrow"
/>
</transition>-->
</div>
</template>
<script>
import { GOODS_SEARCH } from '@/api/goods';
import ItemGroup from '@/vue/components/item-group';
import ItemCardHori from '@/vue/components/item-card-hori/';
import { Search, Tab, Tabs, Popup } from 'vant';
// import { throttle } from 'lodash';
import InfinityScroll from '@/vue/components/infinity-scroll';
export default {
name: 'Item-list',
props: {
keyword: {
type: String,
default: ''
},
itemClass: {
type: [String, Number],
default: ''
}
},
data() {
return {
listApi: GOODS_SEARCH,
shop_id: 1,
tabActive: 0,
tabsItem: [
{ name: '默认', sort: '', items: [] },
{ name: '销量', sort: 'sold_quantity', items: [] },
{ name: '最新', sort: 'created_at', items: [] }
// { name: '筛选', sort: false, items: [] }
],
is_haitao: 0,
filterItem: [
{
name: '全部',
filterType: 2,
isActive: true
},
{
name: '店铺商品',
filterType: 0,
isActive: false
},
{
name: '海淘商品',
filterType: 1,
isActive: false
}
],
isHaitao: 2,
searchVal: '',
filterItemShow: false
// showArrow: false
};
},
computed: {
sortVal() {
const { tabActive: i } = this;
return this.tabsItem[i].sort;
}
},
created() {
// this.scrollShowArrow = throttle(this.scrollShowArrow, 100);
},
methods: {
onLoad(i, items) {
this.tabsItem[i].items.push(...items);
},
beforeRequest() {
return {
params: {
q: this.searchVal,
shop_id: this.shop_id,
cid: this.itemClass,
sort: this.sortVal,
is_haitao: this.isHaitao
}
};
},
// 滚动容器改变, 导致滚动监听失效, 暂时先注释了
// eventListen(isBind = true) {
// if (isBind) {
// this.$el.addEventListener('scroll', this.scrollShowArrow);
// } else {
// this.$el.removeEventListener('scroll', this.scrollShowArrow);
// }
// },
// scrollShowArrow() {
// this.showArrow = this.$el.scrollTop > 120;
// },
activeFilter(i) {
this.filterItem.forEach((item, index) => {
item.isActive = i === index;
});
},
resetActiveTab() {
const { tabActive: i } = this;
this.tabsItem[i].items = [];
const targetScroll = this.$refs[`tabScrolls${i}`][0];
// debugger;
targetScroll && targetScroll.resetInit();
},
toggleFilterModal(status) {
this.filterItemShow = status;
},
filterMethod(i) {
const filterType = this.filterItem[i].filterType;
if (filterType === this.isHaitao) return;
this.isHaitao = filterType;
this.activeFilter(i);
this.toggleFilterModal(false);
this.resetActiveTab();
},
// backTop() {
// this.$el.scrollTop = 0;
// },
itemClick(id) {
this.$router.push({ name: 'detail', params: { itemId: id } });
}
},
components: {
InfinityScroll,
[ItemGroup.name]: ItemGroup,
[ItemCardHori.name]: ItemCardHori,
[Tab.name]: Tab,
[Tabs.name]: Tabs,
[Search.name]: Search,
[Popup.name]: Popup
}
};
</script>
<style lang="scss" scoped>
.fade-enter,
.fade-leave-to {
opacity: 0;
}
.fade-enter-active,
.fade-leave-active {
transition: all 0.5s;
}
.fix-height {
padding-bottom: 88px;
}
.over-hide {
overflow: hidden;
}
.item_list {
background-color: #fff;
padding-bottom: 0;
}
.fixedTop {
position: fixed;
width: 100%;
top: 0;
z-index: 999;
}
.items_loading {
margin: 0 auto;
}
.filterItem {
width: 40%;
height: 100%;
li {
padding: 10px;
&.filter_active {
color: $red;
}
}
}
.backTop {
position: fixed;
right: 20px;
bottom: 20px;
font-size: 24px;
}
</style>

View File

@@ -0,0 +1,166 @@
<template>
<div class="item_list">
<form action="/search" class="fixedTop">
<van-search placeholder="请输入商品名称" v-model="searchVal" @search="resetInit" showAction/>
</form>
<van-list
v-model="loading"
:finished="finished"
:immediate-check="false"
:offset="100"
@load="loadMore"
>
<item-group>
<item-card-hori
v-for="(item) in items"
:key="item.id"
:goods="item"
@click="itemClick(item.id)"
/>
</item-group>
</van-list>
<is-empty v-if="items.length === 0">抱歉,没有找到符合条件商品</is-empty>
<transition name="fade">
<van-icon name="arrowupcircle" class="backTop" @click.native="backTop" v-show="showArrow"></van-icon>
</transition>
</div>
</template>
<script>
import { GOODS_SEARCH } from '@/api/goods';
import ItemGroup from '@/vue/components/item-group/';
import IsEmpty from '@/vue/components/is-empty/';
import ItemCardHori from '@/vue/components/item-card-hori/';
import { Search, List } from 'vant';
import _ from 'lodash';
import loadMore from '@/vue/mixin/list-load-more';
import scrollFixed from '@/vue/mixin/scroll-fixed';
export default {
name: 'Item-list',
props: {
keyword: {
type: String,
default: ''
}
},
mixins: [loadMore, scrollFixed],
data() {
return {
isEmpty: false,
shop_id: '',
searchVal: '',
showArrow: false
};
},
activated() {
this.searchVal = this.keyword;
this.resetInit();
this.eventListen();
},
deactivated() {
document
.getElementById('app')
.removeEventListener('scroll', this.scrollShowArrow);
},
created() {
this.initData();
this.scrollShowArrow = _.throttle(this.scrollShowArrow, 100);
},
methods: {
initData() {
// debugger;
this.items = [];
return this.$reqGet(
`/wx/goods/list`,
{
keyword: this.searchVal,
page: 1,
size: 100,
sort: 'name',
order: 'desc',
categoryId: 0
},
{
hideLoading: true
}
).then(res => {
const { goodsList, filterCategoryList } = res.data.data;
_.each(goodsList, v => {
v.pic_url = v.picUrl;
});
this.items.push(...goodsList);
// return page;
});
},
eventListen() {
document
.getElementById('app')
.addEventListener('scroll', this.scrollShowArrow);
},
scrollShowArrow() {
this.showArrow = document.getElementById('app').scrollTop > 120;
},
backTop() {
document.getElementById('app').scrollTop = 0;
},
itemClick(i) {
this.$router.push({ name: 'detail', params: { itemId: i } });
}
},
components: {
[ItemGroup.name]: ItemGroup,
[ItemCardHori.name]: ItemCardHori,
[Search.name]: Search,
[List.name]: List,
[IsEmpty.name]: IsEmpty
}
};
</script>
<style lang="scss" scoped>
.fade-enter,
.fade-leave-to {
opacity: 0;
}
.fade-enter-active,
.fade-leave-active {
transition: all 0.5s;
}
.item_list {
background-color: #fff;
padding-top: 50px;
box-sizing: border-box;
}
.fixedTop {
position: fixed;
width: 100%;
top: 0;
z-index: 999;
}
.items_loading {
margin: 0 auto;
}
.backTop {
position: fixed;
right: 20px;
bottom: 20px;
font-size: 24px;
}
</style>

View File

@@ -0,0 +1,104 @@
<template>
<div class="item_search">
<form action="/search" @submit="disabledSubmit">
<van-search placeholder="请输入商品名称" v-model="keyword" @search="enterSearch" autofocus/>
</form>
<div class="item_search_content">
<div class="item_search_text clearfix">
<div class="float-l">历史搜索</div>
<div class="float-r" @click="clearHistory">
<van-icon name="lajitong" style="font-size: 12px;margin-right: 3px" />
清空历史记录
</div>
</div>
<div class="item_search_history">
<word-tag
v-for="(his, i) in wordHistory"
:key="i"
@click="toSearchResult(his)"
>{{his}}</word-tag>
</div>
</div>
</div>
</template>
<script>
import { Search } from 'vant';
import SrarchTag from './search-tag';
export default {
data() {
return {
keyword: '',
focusStatus: true,
wordHistory: []
};
},
methods: {
enterSearch() {
const keyword = this.keyword;
this.pushHistoryTolocal(keyword);
this.toSearchResult(keyword);
},
toSearchResult(word) {
this.keyword = word.trim();
this.$router.push({
name: 'search-result',
query: { keyword: word.trim() }
});
},
pushHistoryTolocal(keyword) {
const wordHistory = this.wordHistory;
const historyKeyWord = this.getKeyWordHistory();
if (!!keyword.trim() && historyKeyWord.indexOf(keyword) < 0) {
wordHistory.push(keyword);
window.localStorage.setItem('keyword', wordHistory.join('|'));
}
},
getKeyWordHistory() {
const listWord = window.localStorage.getItem('keyword');
return listWord ? listWord.split('|') : [];
},
clearHistory() {
this.$dialog
.confirm({
message: '是否清空历史记录'
})
.then(() => {
window.localStorage.setItem('keyword', '');
this.wordHistory = [];
});
},
disabledSubmit() {
return false;
}
},
activated() {
this.wordHistory = this.getKeyWordHistory();
},
components: {
[Search.name]: Search,
[SrarchTag.name]: SrarchTag
}
};
</script>
<style lang="scss" scoped>
.item_search {
background-color: #fff;
}
.item_search_content {
padding: 15px 10px 0;
}
.item_search_text {
font-size: $font-size-normal;
color: $font-color-gray;
margin-bottom: 20px;
}
.item_search_history > span {
margin-right: 10px;
margin-bottom: 10px;
}
</style>

View File

@@ -0,0 +1,28 @@
<template>
<span class="search_tag" @click="OnClick">
<slot></slot>
</span>
</template>
<script>
export default {
name: 'word-tag',
methods: {
OnClick() {
this.$emit('click');
}
}
};
</script>
<style lang="scss" scoped>
.search_tag {
display: inline-block;
font-size: 12px;
background-color: #f4f4f4;
padding: 3px 10px;
border-radius: 11px;
min-width: 20px;
text-align: center;
}
</style>

View File

@@ -0,0 +1,170 @@
<template>
<div class="class_tree clearfix">
<ul class="class_tree_nav">
<li
v-for="(item ,index) in list"
:key="item.id"
:class="{active_nav: navActive == index}"
@click="navclick(index)"
>{{item.name}}</li>
</ul>
<div class="class_tree_content">
<div class="class_tree_all">
<img style="width:250px" v-lazy="currentCategory.picUrl">
</div>
<div class="box">
<span>{{currentCategory.desc}}</span>
</div>
<div class="class_tree_items_wrap clearfix">
<div @click="classClick(item.id)" :key="i" v-for="(item, i) in goods">
<div class="class_tree_item_img">
<img :src="item.picUrl" :alt="item.name">
</div>
<div class="class_tree_item_name">{{item.name}}</div>
</div>
</div>
</div>
</div>
</template>
<script>
export default {
name: 'class-tree',
model: {
prop: 'activeIndex'
},
props: {
activeIndex: {
type: Number,
default: 0
},
list: {
type: Array,
default: () => []
}
},
data() {
const navActive =
this.activeIndex >= this.list.length ? 0 : this.activeIndex;
return {
navActive,
goods: [],
currentCategory: {}
};
},
computed: {},
methods: {
changeList(data) {
this.goods = data.currentSubCategory;
this.currentCategory = data.currentCategory;
console.log(this.goods);
},
allClick() {
this.$emit('all-click');
},
navclick(i) {
this.navActive = i;
this.$emit('nav-click', this.list[i].id);
},
classClick(id) {
this.$emit('class-click', id);
}
}
};
</script>
<style lang="scss" scoped>
@import '../../assets/scss/mixin';
.box {
width: 250px;
height: 20px;
text-align: center;
font-family: PingFangSC-Light, helvetica, 'Heiti SC';
font-size: 13px;
position: absolute;
top: 95px;
}
.box span {
line-height: 20px;
}
.class_tree {
position: relative;
background-color: #fff;
overflow-x: hidden;
text-overflow: ellipsis;
white-space: nowrap;
overflow: hidden;
height: 100%;
box-sizing: border-box;
}
.class_tree_nav {
float: left;
width: 100px;
height: 100%;
background-color: #f8f8f8;
overflow: scroll;
> li {
@include one-border;
height: 40px;
line-height: 40px;
text-align: center;
border-left: 2px solid $bg-color;
}
> li.active_nav {
background-color: #fff;
border-left: 2px solid $red;
color: $red;
}
}
.class_tree_content {
margin-left: 100px;
height: 100%;
overflow-x: hidden;
overflow-y: scroll;
.class_tree_all {
text-align: right;
padding-right: 10px;
height: 40px;
line-height: 40px;
color: $font-color-gray;
font-size: $font-size-small;
}
.van-icon-arrow {
font-size: $font-size-small;
}
.class_tree_items_wrap {
padding: 10px 20px;
margin-right: -3%;
margin-top: 70px;
text-align: center;
> div {
float: left;
padding-right: 3%;
box-sizing: border-box;
width: 33.333%;
margin-bottom: 20px;
}
img {
max-width: 100%;
}
.class_tree_item_img {
display: inline-block;
max-width: 100%;
width: 70px;
height: 70px;
}
.class_tree_item_name {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
}
}
</style>

View File

@@ -0,0 +1,112 @@
<template>
<div class="tab_class">
<div class="tal_class_searchBox">
<van-search placeholder="点击前往搜索"/>
<div class="tal_class_searchMask" @click="$router.push({ name: 'search' })"></div>
</div>
<class-tree
ref="classTree"
class="height-fix42"
@nav-click="changeCatalog"
@class-click="toItemList"
@all-click="toItemList"
:list="list"
></class-tree>
<is-empty v-if="isEmpty">抱歉,店主还未上架商品</is-empty>
</div>
</template>
<script>
import { GOODS_CATEGORY, GOODS_CHANNGE_CATEGORY } from '@/api/goods';
import getLocationParam from 'core/utils/location-param';
import { Search } from 'vant';
import classTree from './tabbar-class-tree';
import IsEmpty from '@/vue/components/is-empty';
import _ from 'lodash';
import { async } from 'q';
function getIndex(arr, keyWord) {
let index = 0;
_.each(arr, (v, k) => {
if (v.id === keyWord) {
index = k;
return false;
}
});
return index;
}
export default {
data() {
return {
list: [],
subCategory: [],
isEmpty: false
};
},
created() {
this.initData();
},
methods: {
initData() {
const shop_id = getLocationParam('shop_id');
this.$reqGet(`${GOODS_CATEGORY}`).then(res => {
this.list = res.data.data.categoryList;
this.$refs.classTree.changeList(res.data.data);
this.subCategory = res.data.data.currentSubCategory;
if (this.subCategory.length === 0) this.isEmpty = true;
});
},
removeNoChild(data) {
return data.filter(item => item.children && item.children.length);
},
changeCatalog(id) {
this.$reqGet(`${GOODS_CHANNGE_CATEGORY}${id}`).then(res => {
let index = getIndex(this.list, res.data.data.currentCategory.id);
this.$refs.classTree.changeList(res.data.data);
this.subCategory = res.data.data.currentSubCategory;
if (this.subCategory.length === 0) this.isEmpty = true;
});
},
toItemList(id) {
this.$router.push({
name: 'list',
query: { keyword: '', itemClass: id }
});
}
},
components: {
[Search.name]: Search,
[classTree.name]: classTree,
[IsEmpty.name]: IsEmpty
}
};
</script>
<style scoped>
.tab_class {
overflow: hidden;
background-color: #fff;
}
.height-fix {
padding-bottom: 42px;
}
.tal_class_searchBox {
position: relative;
}
.tal_class_searchMask {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
z-index: 9;
}
</style>

View File

@@ -0,0 +1,57 @@
<template>
<md-field-group class="foget_view">
<md-field
v-model="password"
icon="lock"
:is-error="isErrow"
placeholder="请输入新密码"/>
<md-field
v-model="passwordRepeat"
type="password"
icon="lock"
:is-error="isErrow"
placeholder="请再次输入密码" />
<div class="red" v-show="isErrow">两次密码输入不一致</div>
<div class="foget_submit">
<van-button size="large" type="danger" @click="submitCode">重置</van-button>
</div>
</md-field-group>
</template>
<script>
import field from '@/vue/components/field/';
import fieldGroup from '@/vue/components/field-group/';
export default {
data() {
return {
isErrow: true,
password: '',
passwordRepeat: ''
};
},
methods: {
submitCode() {}
},
components: {
[field.name]: field,
[fieldGroup.name]: fieldGroup
}
};
</script>
<style lang="scss" scoped>
div.foget_view {
background-color: #fff;
padding-top: 30px;
}
div.foget_submit {
padding-top: 30px;
padding-bottom: 20px;
}
</style>

View File

@@ -0,0 +1,81 @@
<template>
<div class="payment_status">
<div class="status_top">
<van-icon :name="statusIcon" :class="statusClass" />
<div>{{statusText}}</div>
</div>
<div class="status_text"><span class="red">3</span>后返回到登录页, 您也可以<router-link to="/login" class="red">点此登录</router-link></div>
</div>
</template>
<script>
export default {
name: 'payment-status',
props: {
status: String
},
data() {
return {
isSuccess: true
};
},
computed: {
statusText() {
return this.isSuccess ? '修改成功' : '修改失败';
},
statusIcon() {
return this.isSuccess ? 'checked' : 'fail';
},
statusClass() {
return this.isSuccess ? 'success_icon' : 'fail_icon';
}
},
activated() {
this.isSuccess = this.status === 'success';
}
};
</script>
<style lang="scss" scopd>
.payment_status {
padding-top: 30px;
box-sizing: border-box;
background-color: #fff;
text-align: center;
}
.status_top {
margin-bottom: 15px;
i {
margin-bottom: 5px;
}
> div {
font-size: 18px;
}
}
.status_text {
color: $font-color-gray;
margin-bottom: 50px;
}
.status_icon {
font-size: 80px;
}
i.success_icon {
@extend .status_icon;
color: #06bf04;
}
i.fail_icon {
@extend .status_icon;
color: #f44;
}
</style>

View File

@@ -0,0 +1,80 @@
<template>
<md-field-group class="foget_view">
<md-field
v-model="mobile"
icon="mobile"
placeholder="请输入手机号"/>
<md-field
v-model="code"
icon="lock"
placeholder="请输入短信验证码"
>
<div slot="rightIcon" @click="getCode" class="getCode red">
<countdown v-if="counting" :time="60000" @countdownend="countdownend">
<template slot-scope="props">{{ +props.seconds || 60 }}秒后获取</template>
</countdown>
<span v-else>获取验证码</span>
</div>
</md-field >
<div class="foget_submit">
<van-button size="large" type="danger" @click="submitCode">下一步</van-button>
</div>
</md-field-group>
</template>
<script>
import field from '@/vue/components/field/';
import fieldGroup from '@/vue/components/field-group/';
export default {
data() {
return {
counting: false,
mobile: '',
code: ''
};
},
methods: {
submitCode() {
this.$router.push({ name: 'forgetReset' });
},
getCode() {
this.counting = true;
},
countdownend() {
this.counting = false;
}
},
components: {
[field.name]: field,
[fieldGroup.name]: fieldGroup
}
};
</script>
<style lang="scss" scoped>
@import '../../../assets/scss/mixin';
div.foget_view {
background-color: #fff;
padding-top: 30px;
}
div.foget_submit {
padding-top: 30px;
padding-bottom: 20px;
}
.getCode {
@include one-border(left);
text-align: center;
}
.time_down {
color: $red;
}
</style>

View File

@@ -0,0 +1,18 @@
<template>
<div class="text-desc text-center bottom_positon">技术支持: litemall</div>
</template>
<script>
export default {
name: 'login-footer'
};
</script>
<style scoped>
.bottom_positon {
position: absolute;
bottom: 30px;
width: 100%;
}
</style>

View File

@@ -0,0 +1,29 @@
<template>
<div class="store_header">
<div class="store_avatar">
<img src="../../assets/images/avatar_default.png" alt="头像" width="55" height="55">
</div>
<div class="store_name">litemall-vue</div>
</div>
</template>
<script>
export default {
name: 'login-header'
};
</script>
<style lang="scss" scoped>
.store_header {
text-align: center;
padding: 30px 0;
.store_avatar img {
border-radius: 50%;
}
.store_name {
padding-top: 5px;
font-size: 16px;
}
}
</style>

View File

@@ -0,0 +1,182 @@
<template>
<div>
<md-field-group>
<md-field
v-model="account"
icon="username"
placeholder="随便输"
right-icon="clear-full"
v-validate="'required'"
name="user"
data-vv-as="帐号"
@right-click="clearText"
/>
<md-field
v-model="password"
icon="lock"
placeholder="随便输"
:type="visiblePass ? 'text' : 'password'"
:right-icon="visiblePass ? 'eye-open' : 'eye-close'"
v-validate="'required'"
data-vv-as="密码"
name="password"
@right-click="visiblePass = !visiblePass"
/>
<div class="clearfix">
<div class="float-r">
<router-link to="/login/forget">忘记密码</router-link>
</div>
</div>
<van-button size="large" type="danger" :loading="isLogining" @click="loginSubmit">登录</van-button>
</md-field-group>
<div class="register clearfix">
<div class="float-l connect">
<!-- <span @click="showKefu = true">联系客服</span> -->
</div>
<div class="float-r">
<router-link to="/login/registerGetCode">免费注册</router-link>
</div>
</div>
<van-popup v-model="showKefu">
<md-kefu mobile="16454193338"/>
</van-popup>
</div>
</template>
<script>
import field from '@/vue/components/field/';
import fieldGroup from '@/vue/components/field-group/';
import md_kefu from '@/vue/components/md-kefu/';
import { USER_LOGIN, USER_PROFILE } from '@/api/user';
import { setLocalStorage } from 'core/utils/local-storage';
import { emailReg, mobileReg } from '@/core/regexp';
import { Popup, Toast } from 'vant';
export default {
name: 'login-request',
data() {
return {
account: '',
password: '',
visiblePass: false,
showKefu: false,
isLogining: false,
userInfo: {}
};
},
methods: {
clearText() {
this.account = '';
},
async validate() {
const result = await this.$validator.validate();
if (!result) {
const errMsg = this.errors.items[0].msg;
Toast(errMsg);
throw new Error(`表单验证: ${errMsg}`);
}
},
async login() {
let loginData = this.getLoginData();
let { data } = await this.$reqPost(USER_LOGIN, loginData);
this.userInfo = data.data.userInfo;
console.log(this.userInfo);
setLocalStorage({
Authorization: data.data.token
});
this.getUserProfile();
},
async loginSubmit() {
this.isLogining = true;
try {
await this.validate();
await this.login();
this.isLogining = false;
} catch (err) {
console.log(err.message);
this.isLogining = false;
}
},
getUserProfile() {
// const {
// data: { data }
// } = await this.$reqGet(USER_PROFILE);
setLocalStorage({
avatar: this.userInfo.avatarUrl,
// user_id: data.user_id,
// background_image: data.background_image,
nickName: this.userInfo.nickName
});
this.routerRedirect();
},
routerRedirect() {
// const { query } = this.$route;
// this.$router.replace({
// name: query.redirect || 'home',
// query: query
// });
window.location = '#/user/';
},
getLoginData() {
const password = this.password;
const username = this.getUserType(this.account);
return {
username: this.account,
password: password
};
},
getUserType(account) {
const accountType = mobileReg.test(account)
? 'mobile'
: emailReg.test(account)
? 'email'
: 'username';
return accountType;
}
},
components: {
[field.name]: field,
[fieldGroup.name]: fieldGroup,
[md_kefu.name]: md_kefu,
[Popup.name]: Popup
}
};
</script>
<style lang="scss" scoped>
@import '../../assets/scss/mixin';
.register {
padding-top: 40px;
color: $font-color-gray;
a {
color: $font-color-gray;
}
> div {
width: 50%;
box-sizing: border-box;
padding: 0 20px;
}
.connect {
@include one-border(right);
text-align: right;
}
}
</style>

View File

@@ -0,0 +1,29 @@
<template>
<div class="login">
<login-header />
<login-request />
<login-footer />
</div>
</template>
<script>
import loginHeader from './login-header';
import loginRequest from './login-request';
import loginFooter from './login-footer';
export default {
components: {
[loginHeader.name]: loginHeader,
[loginRequest.name]: loginRequest,
[loginFooter.name]: loginFooter
}
};
</script>
<style lang="scss" scoped>
.login {
position: relative;
background-color: #fff;
}
</style>

View File

@@ -0,0 +1,59 @@
<template>
<md-field-group class="register_view">
<div>我们将发送验证码到您的手机</div>
<md-field
v-model="mobile"
icon="mobile"
placeholder="请输入手机号"/>
<div class="register_submit">
<van-button size="large" type="danger" @click="submitCode">下一步</van-button>
</div>
<div class="register_footer">
已有账号?
<router-link to="/login" class="red">登录</router-link>
</div>
</md-field-group>
</template>
<script>
import field from '@/vue/components/field/';
import fieldGroup from '@/vue/components/field-group/';
export default {
data() {
return {
mobile: ''
};
},
methods: {
submitCode() {
this.$router.push({ name: 'registerSubmit' });
}
},
components: {
[field.name]: field,
[fieldGroup.name]: fieldGroup
}
};
</script>
<style lang="scss" scoped>
div.register_view {
background-color: #fff;
padding-top: 30px;
}
div.register_submit {
padding-top: 30px;
padding-bottom: 20px;
}
.register_footer {
text-align: right;
color: $font-color-gray;
}
</style>

View File

@@ -0,0 +1,81 @@
<template>
<div class="payment_status">
<div class="status_top">
<van-icon :name="statusIcon" :class="statusClass" />
<div>{{statusText}}</div>
</div>
<div class="status_text"><span class="red">3</span>后返回到登录页, 您也可以<router-link to="/login" class="red">点此登录</router-link></div>
</div>
</template>
<script>
export default {
name: 'payment-status',
props: {
status: String
},
data() {
return {
isSuccess: true
};
},
computed: {
statusText() {
return this.isSuccess ? '注册成功' : '注册失败';
},
statusIcon() {
return this.isSuccess ? 'checked' : 'fail';
},
statusClass() {
return this.isSuccess ? 'success_icon' : 'fail_icon';
}
},
activated() {
this.isSuccess = this.status === 'success';
}
};
</script>
<style lang="scss" scopd>
.payment_status {
padding-top: 30px;
box-sizing: border-box;
background-color: #fff;
text-align: center;
}
.status_top {
margin-bottom: 15px;
i {
margin-bottom: 5px;
}
> div {
font-size: 18px;
}
}
.status_text {
color: $font-color-gray;
margin-bottom: 50px;
}
.status_icon {
font-size: 80px;
}
i.success_icon {
@extend .status_icon;
color: #06bf04;
}
i.fail_icon {
@extend .status_icon;
color: #f44;
}
</style>

View File

@@ -0,0 +1,78 @@
<template>
<md-field-group class="register_submit">
<md-field v-model="code" icon="mobile" placeholder="请输入验证码">
<div slot="rightIcon" @click="getCode" class="getCode red">
<countdown v-if="counting" :time="60000" @countdownend="countdownend">
<template slot-scope="props">{{ +props.seconds || 60 }}秒后获取</template>
</countdown>
<span v-else>获取验证码</span>
</div>
</md-field>
<md-field v-model="password" icon="lock" placeholder="请输入密码"/>
<md-field v-model="repeatPassword" icon="lock" placeholder="请再次确认密码"/>
<div class="register_submit_btn">
<van-button type="danger" size="large" @click="registerSubmit">确定</van-button>
</div>
</md-field-group>
</template>
<script>
import field from '@/vue/components/field/';
import fieldGroup from '@/vue/components/field-group/';
export default {
data() {
return {
counting: true,
code: '',
password: '',
repeatPassword: ''
};
},
methods: {
registerSubmit() {
this.$router.push({
name: 'registerStatus',
params: { status: 'success' }
});
},
getCode() {
this.counting = true;
},
countdownend() {
this.counting = false;
}
},
components: {
[field.name]: field,
[fieldGroup.name]: fieldGroup
}
};
</script>
<style lang="scss" scoped>
@import '../../../assets/scss/mixin';
.register_submit {
padding-top: 40px;
background-color: #fff;
}
.register_submit_btn {
padding-top: 30px;
}
.getCode {
@include one-border(left);
text-align: center;
}
.time_down {
color: $red;
}
</style>

View File

@@ -0,0 +1,66 @@
<template>
<div class="order-goods">
<van-card
v-for="item in goodsInfo.orderGoods"
:key="item.id"
:title="item.goodsName"
desc="暂无描述"
:num="item.number"
:price="item.price +'.00'"
:thumb="item.picUrl"
></van-card>
<van-cell-group>
<!-- <van-field v-model="remark" placeholder="请输入备注" label="订单备注"> -->
<!-- <template slot="icon">{{remark.length}}/50</template> -->
<!-- </van-field> -->
<van-cell title="商品金额">
<span class="red">{{goodsInfo.orderInfo.goodsPrice * 100 | yuan}}</span>
</van-cell>
<van-cell title="邮费" :value="goodsInfo.orderInfo.freightPrice "></van-cell>
<!-- <van-cell title="税费" value="¥8.96"></van-cell> -->
<!-- <van-cell title="优惠券">
<span class="red">-{{ goodsInfo.orderInfo.xxx * 100 || 0 | yuan}}</span>
</van-cell>-->
</van-cell-group>
</div>
</template>
<script>
import { Card, Field } from 'vant';
export default {
name: 'bottom-goods-info',
props: {
goodsInfo: {
type: Object,
default: () => ({})
}
},
data() {
return {
remark: ''
// goodsInfo: {}
};
},
created() {},
methods: {
// async init() {
// let { data } = await this.$reqGet(
// '/wx/cart/checkout?cartId=0&addressId=0&couponId=0&grouponRulesId=0'
// );
// debugger;
// this.goodsInfo = data.data;
// }
},
components: {
[Card.name]: Card,
[Field.name]: Field
}
};
</script>
<style lang="scss" scoped>
.order-goods {
background-color: #fff;
}
</style>

View File

@@ -0,0 +1,257 @@
<template>
<div class="place_order_entity">
<top-user-info :goodsInfo="goodsInfo" style="margin-bottom: 20px;"/>
<bottom-goods-info :goodsInfo="goodsInfo"/>
<van-cell-group style="margin-top: 20px;">
<van-cell title="下单时间">
<span>{{goodsInfo.orderInfo.addTime }}</span>
</van-cell>
<van-cell title="订单编号">
<span>{{goodsInfo.orderInfo.orderSn }}</span>
</van-cell>
<van-cell>
<template slot="title">
<span class="custom-text">实付款</span>
<span class="red">{{goodsInfo.orderInfo.actualPrice * 100 | yuan}}</span>
</template>
<!-- 订单动作 -->
<van-button
v-if="getStatus() !== ''"
size="small"
@click="orderAction"
style=" float:right"
type="danger"
>{{getCurrentButtonText()}}</van-button>
<!-- 未付款的时候价格取消的动作 -->
<van-button
size="small"
v-if="getStatus() === 'pay'"
@click="cancelOrder"
style=" float:right"
type="danger"
>取消订单</van-button>
</van-cell>
</van-cell-group>
<van-cell-group v-if="showExp" style="margin-top: 20px;">
<van-cell title="快递公司">
<span>{{goodsInfo.orderInfo.expCode }}</span>
</van-cell>
<van-cell title="快递编号">
<span>{{goodsInfo.orderInfo.expNo }}</span>
</van-cell>
</van-cell-group>
<!-- <van-button @click="cancelOrder" style=" position: absolute;bottom: 4px;z-index: 1000;" type="danger">取消订单</van-button> -->
<!-- <van-submit-bar
v-if="showSubmit()"
:price="goodsInfo.orderInfo.actualPrice*100"
label="总计:"
buttonText="支付"
:loading="isSubmit"
:disabled="isDisabled"
@submit="onSubmit"
></van-submit-bar>-->
</div>
</template>
<script>
import topUserInfo from './top-user-info';
import bottomGoodsInfo from './bottom-goods-info';
import { SubmitBar, Button, Cell, CellGroup, Dialog } from 'vant';
import _ from 'lodash';
export default {
data() {
return {
isSubmit: false,
isDisabled: false,
goodsInfo: {}
};
},
created() {
this.init();
},
methods: {
getStatus() {
let options = this.goodsInfo.orderInfo.handleOption;
let current_allow_option = '';
_.each(options, (v, k) => {
if (v) current_allow_option = k;
});
return current_allow_option;
},
/**\
* cancel: false
comment: false
confirm: false
delete: true
pay: false
rebuy: false
refund: false
*/
showExp() {
return _.has(goodsInfo.orderInfo, 'expNo');
},
getCurrentButtonText() {
let option = this.getStatus();
if (option === 'cancel') return '取消订单';
if (option === 'delete') return '删除订单';
if (option === 'confirm') return '确认收货';
if (option === 'pay') return '支付订单';
if (option === 'rebuy') return '删除订单';
if (option === 'refund') return '申请退款';
if (option === 'comment') return '评价';
},
showSubmit() {
if (this.getStatus() === 'refund') {
return false;
}
},
async orderAction() {
let option = this.getStatus();
if (option === 'cancel') {
this.cancelOrder();
}
if (option === 'confirm') {
await Dialog.confirm({
message: '确定收货?'
}).then(() => {
this.doAction(option);
});
}
if (option === 'refund') {
await Dialog.confirm({
message: '确定要取消此订单?'
}).then(() => {
this.doAction(option);
});
}
if (option === 'rebuy') {
await Dialog.confirm({
message: '确定要删除此订单?'
}).then(() => {
this.doAction('delete');
});
}
if (option === 'pay') {
this.doAction('prepay', false);
}
// if (option === 'pay')
// if (option === 'rebuy')
// if (option === 'refund')
// if (option === 'comment')
},
async doAction(status, skip) {
let { data } = await this.$reqPost(`/wx/order/${status}`, {
orderId: this.$route.query.orderId
});
if (skip != false) {
if (data.errno == 0) this.$router.go(-1);
}
if (status == 'prepay') {
function onBridgeReady() {
console.log(JSON.stringify(data));
// var timeStamp = Date.parse(new Date());
// var packageV = 'prepay_id=' + data.data.prepay_id;
var params = {
appId: data.data.appId, //公众号名称,由商户传入
timeStamp: data.data.timeStamp, //时间戳自1970年以来的秒数
nonceStr: data.data.nonceStr, //随机串
package: data.data.packageValue,
signType: data.data.signType, //微信签名方式:
paySign: data.data.paySign //微信签名
};
console.log(params);
WeixinJSBridge.invoke('getBrandWCPayRequest', params, function(res) {
console.log(JSON.stringify(res));
if (res.err_msg == 'get_brand_wcpay_request:ok') {
// 使用以上方式判断前端返回,微信团队郑重提示:
//res.err_msg将在用户支付成功后返回ok但并不保证它绝对可靠。
that.$router.push({
name: 'paymentStatus',
params: {
status: 'success'
}
});
}
});
}
if (typeof WeixinJSBridge == 'undefined') {
if (document.addEventListener) {
document.addEventListener(
'WeixinJSBridgeReady',
onBridgeReady,
false
);
} else if (document.attachEvent) {
document.attachEvent('WeixinJSBridgeReady', onBridgeReady);
document.attachEvent('onWeixinJSBridgeReady', onBridgeReady);
}
} else {
onBridgeReady();
}
}
},
async cancelOrder() {
await Dialog.confirm({
message: '确定取消订单?'
}).then(() => {
let { data } = this.$reqPost('/wx/order/cancel', {
orderId: this.$route.query.orderId
});
// if (data.errno == 0)
this.$router.go(-1);
});
},
async delOrder() {
let { data } = await this.$reqPost('/wx/order/delete', {
orderId: this.$route.query.orderId
});
if (data.errno == 0) this.$router.go(-1);
},
async onSubmit() {
this.isSubmit = true;
let cartId = this.$route.params.cartId;
let { data } = await this.$reqPost('/wx/order/submit', {
addressId: this.goodsInfo.addressId,
cartId: cartId || 0,
couponId: 0,
grouponLinkId: 0,
grouponRulesId: 0,
message: ''
});
this.$router.replace({ name: 'payment' });
},
async init() {
// let cartId = this.$route.params.cartId;
let orderId = this.$route.query.orderId;
let { data } = await this.$reqGet(
`/wx/order/detail?orderId=${orderId}`
);
this.goodsInfo = data.data;
}
},
components: {
[Dialog.name]: Dialog,
[CellGroup.name]: CellGroup,
[Cell.name]: Cell,
[Button.name]: Button,
[SubmitBar.name]: SubmitBar,
[topUserInfo.name]: topUserInfo,
[bottomGoodsInfo.name]: bottomGoodsInfo
}
};
</script>
<style lang="scss" scoped>
.place_order_entity {
padding-bottom: 70px;
}
</style>

View File

@@ -0,0 +1,62 @@
<template>
<van-cell-group>
<van-cell
icon="dingwei"
isLink
:title="`${goodsInfo.orderInfo.consignee} ${goodsInfo.orderInfo.mobile}`"
:label="goodsInfo.orderInfo.address"
/>
<!-- <van-cell class="daodian" title="到店自提" label="浙江省 杭州市 西湖区 创新创业园">
<van-checkbox v-model="isDaoDian" slot="icon"></van-checkbox>
</van-cell>-->
<!-- <van-cell icon="id-card" title="张三" label="330327********1574" isLink/> -->
</van-cell-group>
</template>
<script>
import { Checkbox } from 'vant';
export default {
name: 'top-user-info',
props: {
goodsInfo: {
type: Object,
default: () => ({})
}
},
data() {
return {
isDaoDian: false
};
},
components: {
[Checkbox.name]: Checkbox
}
};
</script>
<style lang="scss">
.daodian {
.van-checkbox .van-icon-success {
height: 16px;
width: 16px;
font-size: $font-size-small;
&::before {
line-height: 16px;
}
}
.van-checkbox__input {
height: 16px;
}
.van-checkbox__label {
margin-left: 0;
}
.shop_address {
padding-left: 25px;
box-sizing: border-box;
}
}
</style>

View File

@@ -0,0 +1,94 @@
<template>
<div class="payment_status">
<div class="status_top">
<van-icon :name="statusIcon" :class="statusClass"/>
<div>{{statusText}}</div>
</div>
<div class="status_text" v-if="isSuccess">
<span class="red">3</span>跳转订单
</div>
<div class="status_text" v-else>系统繁忙, 支付遇到问题, 请您稍后再试!</div>
<div class="status_goLink">
<router-link class="red" :to="{name: 'user'}">查看订单
<van-icon name="arrow"/>
</router-link>
</div>
</div>
</template>
<script>
export default {
name: 'payment-status',
props: {
status: String
},
created() {
setTimeout(() => {
this.$router.push({ path: 'user/order/list/0' });
}, 3000);
},
data() {
return {
isSuccess: true
};
},
computed: {
statusText() {
return this.isSuccess ? '支付成功' : '支付失败';
},
statusIcon() {
return this.isSuccess ? 'checked' : 'fail';
},
statusClass() {
return this.isSuccess ? 'success_icon' : 'fail_icon';
}
},
activated() {
this.isSuccess = this.status === 'success';
}
};
</script>
<style lang="scss" scopd>
.payment_status {
padding-top: 30px;
box-sizing: border-box;
background-color: #fff;
text-align: center;
}
.status_top {
margin-bottom: 15px;
i {
margin-bottom: 5px;
}
> div {
font-size: 18px;
}
}
.status_text {
color: $font-color-gray;
margin-bottom: 50px;
}
.status_icon {
font-size: 80px;
}
i.success_icon {
@extend .status_icon;
color: #06bf04;
}
i.fail_icon {
@extend .status_icon;
color: #f44;
}
</style>

View File

@@ -0,0 +1,165 @@
<template>
<div class="payment">
<div class="time_down payment_group">
请在
<span class="red">半小时内</span>
完成付款否则系统自动取消订单
</div>
<van-cell-group class="payment_group">
<van-cell title="订单编号" :value="order_id"/>
<van-cell title="实付金额">
<span class="red">{{order_info.orderInfo.actualPrice *100 | yuan}}</span>
</van-cell>
</van-cell-group>
<div class="pay_way_group">
<div class="pay_way_title">选择支付方式</div>
<van-radio-group v-model="payWay" @change="payWay">
<van-cell-group>
<!-- <van-cell>
<van-radio @click-native="payWay" name="ali">
<img src="../../../assets/images/ali_pay.png" alt="支付宝" width="82" height="29">
</van-radio>
</van-cell>-->
<van-cell>
<van-radio name="wx">
<img src="../../../assets/images/wx_pay.png" alt="微信支付" width="113" height="23">
</van-radio>
<!-- <button @click="pay">wx-pay-test</button> -->
</van-cell>
</van-cell-group>
</van-radio-group>
</div>
<van-button class="pay_submit" @click="pay" :loading="isSubmit" type="primary" bottomAction>去支付</van-button>
</div>
</template>
<script>
import { Radio, RadioGroup } from 'vant';
import _ from 'lodash';
import md5 from 'js-md5';
export default {
name: 'payment',
data() {
return {
isSubmit: false,
payWay: 'wx',
order_info: {},
order_id: '',
checkedName: '1'
};
},
created() {
if (_.has(this.$route.params, 'order_id')) {
this.order_id = this.$route.params.order_id;
this.getOrder(this.order_id);
}
},
methods: {
payWay(name) {
console.log(name);
},
async getOrder(id) {
let { data } = await this.$reqGet(
`/wx/order/detail?orderId=${id}`
);
this.order_info = data.data;
},
async pay() {
let params = {};
params.orderId = this.order_info.orderInfo.id;
let { data } = await this.$reqPost(
`/wx/order/prepay`,
params
);
var that = this;
function onBridgeReady() {
console.log(JSON.stringify(data));
// var timeStamp = Date.parse(new Date());
// var packageV = 'prepay_id=' + data.data.prepay_id;
var params = {
appId: data.data.appId, //公众号名称,由商户传入
timeStamp: data.data.timeStamp, //时间戳自1970年以来的秒数
nonceStr: data.data.nonceStr, //随机串
package: data.data.packageValue,
signType: data.data.signType, //微信签名方式:
paySign: data.data.paySign //微信签名
};
console.log(params);
WeixinJSBridge.invoke('getBrandWCPayRequest', params, function(res) {
console.log(JSON.stringify(res));
if (res.err_msg == 'get_brand_wcpay_request:ok') {
// 使用以上方式判断前端返回,微信团队郑重提示:
//res.err_msg将在用户支付成功后返回ok但并不保证它绝对可靠。
that.$router.push({
name: 'paymentStatus',
params: {
status: 'success'
}
});
}
});
}
if (typeof WeixinJSBridge == 'undefined') {
if (document.addEventListener) {
document.addEventListener(
'WeixinJSBridgeReady',
onBridgeReady,
false
);
} else if (document.attachEvent) {
document.attachEvent('WeixinJSBridgeReady', onBridgeReady);
document.attachEvent('onWeixinJSBridgeReady', onBridgeReady);
}
} else {
onBridgeReady();
}
this.order_info = data.data;
},
paySubmit() {
this.$router.push({
name: 'paymentStatus',
params: {
status: 'success'
}
});
}
},
components: {
[Radio.name]: Radio,
[RadioGroup.name]: RadioGroup
}
};
</script>
<style lang="scss" scoped>
.payment_group {
margin-bottom: 10px;
}
.time_down {
background-color: #fffeec;
padding: 10px 15px;
}
.pay_submit {
position: fixed;
bottom: 0;
width: 100%;
}
.pay_way_group img {
vertical-align: middle;
}
.pay_way_title {
padding: 15px;
background-color: #fff;
}
</style>

View File

@@ -0,0 +1,68 @@
<template>
<div class="order-goods">
<van-card
v-for="item in goodsInfo.checkedGoodsList"
:key="item.id"
:title="item.goodsName"
desc="暂无描述"
:num="item.number"
:price="item.price +'.00'"
:thumb="item.picUrl"
>
<div slot="footer">添加日期 {{item.addTime}}</div>
</van-card>
<van-cell-group>
<!-- <van-field v-model="remark" placeholder="请输入备注" label="订单备注"> -->
<!-- <template slot="icon">{{remark.length}}/50</template> -->
<!-- </van-field> -->
<van-cell title="商品金额">
<span class="red">{{goodsInfo.actualPrice * 100 | yuan}}</span>
</van-cell>
<van-cell title="邮费" :value="goodsInfo.freightPrice "></van-cell>
<!-- <van-cell title="税费" value="¥8.96"></van-cell> -->
<van-cell title="优惠券">
<span class="red">-{{ goodsInfo.couponPrice * 100| yuan}}</span>
</van-cell>
</van-cell-group>
</div>
</template>
<script>
import { Card, Field } from 'vant';
export default {
name: 'bottom-goods-info',
props: {
goodsInfo: {
type: Object,
default: () => ({})
}
},
data() {
return {
remark: ''
// goodsInfo: {}
};
},
created() {},
methods: {
// async init() {
// let { data } = await this.$reqGet(
// '/wx/cart/checkout?cartId=0&addressId=0&couponId=0&grouponRulesId=0'
// );
// debugger;
// this.goodsInfo = data.data;
// }
},
components: {
[Card.name]: Card,
[Field.name]: Field
}
};
</script>
<style lang="scss" scoped>
.order-goods {
background-color: #fff;
}
</style>

View File

@@ -0,0 +1,83 @@
<template>
<div class="place_order_entity">
<top-user-info :goodsInfo="goodsInfo" style="margin-bottom: 20px;"/>
<bottom-goods-info :goodsInfo="goodsInfo"/>
<van-submit-bar
:price="goodsInfo.orderTotalPrice*100"
label="总计:"
buttonText="提交订单"
:loading="isSubmit"
:disabled="isDisabled"
@submit="onSubmit"
/>
</div>
</template>
<script>
import topUserInfo from './top-user-info';
import bottomGoodsInfo from './bottom-goods-info';
import { SubmitBar, Dialog } from 'vant';
export default {
data() {
return {
isSubmit: false,
isDisabled: false,
goodsInfo: {}
};
},
created() {
this.init();
},
methods: {
async onSubmit() {
this.isSubmit = true;
let cartId = this.$route.params.cartId;
if (this.goodsInfo.addressId === 0) {
Dialog.alert({
message: '未选择收货地址'
}).then(() => {
// on close
});
}
let { data } = await this.$reqPost('/wx/order/submit', {
addressId: this.goodsInfo.addressId,
cartId: cartId || 0,
couponId: 0,
grouponLinkId: 0,
grouponRulesId: 0,
message: ''
});
this.$router.push({
name: 'payment',
params: { order_id: data.data.orderId }
});
},
async init() {
let cartId = this.$route.params.cartId;
let { data } = await this.$reqGet(
`/wx/cart/checkout?cartId=${cartId ||
0}&addressId=0&couponId=0&grouponRulesId=0`
);
this.goodsInfo = data.data;
}
},
components: {
[Dialog.name]: Dialog,
[SubmitBar.name]: SubmitBar,
[topUserInfo.name]: topUserInfo,
[bottomGoodsInfo.name]: bottomGoodsInfo
}
};
</script>
<style lang="scss" scoped>
.place_order_entity {
padding-bottom: 70px;
}
</style>

View File

@@ -0,0 +1,78 @@
<template>
<van-cell-group>
<van-cell
v-if="goodsInfo.checkedAddress.id !== 0"
icon="dingwei"
isLink
@click="goAddressList(goodsInfo)"
:title="`${goodsInfo.checkedAddress.name} ${goodsInfo.checkedAddress.mobile}`"
:label="goodsInfo.checkedAddress.address"
/>
<van-cell
v-else
icon="dingwei"
isLink
@click="goAddressList(goodsInfo)"
title="您暂时没有配送地址,请输入配送地址"
:label="goodsInfo.checkedAddress.address"
/>
<!-- <van-cell class="daodian" title="到店自提" label="浙江省 杭州市 西湖区 创新创业园">
<van-checkbox v-model="isDaoDian" slot="icon"></van-checkbox>
</van-cell>-->
<!-- <van-cell icon="id-card" title="张三" label="330327********1574" isLink/> -->
</van-cell-group>
</template>
<script>
import { Checkbox } from 'vant';
export default {
name: 'top-user-info',
props: {
goodsInfo: {
type: Object,
default: () => ({})
}
},
data() {
return {
isDaoDian: false
};
},
methods: {
goAddressList(goodInfo) {
this.$router.push({
path: '/user/address'
});
}
},
components: {
[Checkbox.name]: Checkbox
}
};
</script>
<style lang="scss">
.daodian {
.van-checkbox .van-icon-success {
height: 16px;
width: 16px;
font-size: $font-size-small;
&::before {
line-height: 16px;
}
}
.van-checkbox__input {
height: 16px;
}
.van-checkbox__label {
margin-left: 0;
}
.shop_address {
padding-left: 25px;
box-sizing: border-box;
}
}
</style>

View File

@@ -0,0 +1,122 @@
<template>
<div class="place_order_virtual">
<van-panel>
<van-card
slot="header"
:title="goods.title"
:desc="goods.desc"
num="2"
price="2.00"
:thumb="goods.thumb"
/>
<div class="panel_content">
<van-icon :name="showNotice ? 'arrow-up' : 'arrow-down'" class="panel_notice"/>
<div @click="showNotice = !showNotice">注意事项</div>
<ol v-if="showNotice">
<li>注意事项1</li>
<li>注意事项2</li>
<li>注意事项3</li>
<li>注意事项4</li>
</ol>
</div>
<div slot="footer" class="clearfix">
<div class="float-l">商品金额</div>
<div class="float-r red">{{7200 | yuan}}</div>
</div>
</van-panel>
<van-submit-bar
:price="3050"
label="总计:"
buttonText="提交订单"
:loading="isSubmit"
:disabled="isDisabled"
@submit="onSubmit"
/>
</div>
</template>
<script>
import { Panel, SubmitBar, Card } from 'vant';
export default {
data() {
return {
showNotice: false,
isSubmit: false,
isDisabled: false,
goods: {
id: '2',
title: '陕西蜜梨',
desc: '约600g',
price: 690,
status: 1,
num: 3,
thumb:
'https://img.yzcdn.cn/public_files/2017/10/24/f6aabd6ac5521195e01e8e89ee9fc63f.jpeg'
}
};
},
methods: {
async onSubmit() {
let { data } = await this.$reqPost('/wx/order/submit', {
addressId: 2,
cartId: 0,
couponId: 0,
grouponLinkId: 0,
grouponRulesId: 0,
message: ''
});
this.isSubmit = true;
console.log('提交订单');
}
},
components: {
[Panel.name]: Panel,
[Card.name]: Card,
[SubmitBar.name]: SubmitBar
}
};
</script>
<style scoped lang="scss">
.panel_content {
position: relative;
padding: 10px 15px;
.panel_notice {
position: absolute;
top: 15px;
right: 15px;
}
ol {
padding-left: 12px;
padding-top: 10px;
list-style: decimal;
color: $font-color-gray;
overflow: hidden;
li {
margin-bottom: 10px;
font-size: $font-size-small;
&:last-child {
margin: 0;
}
}
}
}
/*
.slide-enter,.slide-leave-to{
height: 0;
}
.slide-enter-active,.slide-leave-active{
transition: all 1s;
}
*/
</style>

View File

@@ -0,0 +1,311 @@
<template>
<div class="tab-cart">
<div class="editor_head" v-show="goods.length">
<van-icon :name="isEditor ? 'success' : 'editor'"/>
<span @click="isEditor = !isEditor">{{isEditor ? '完成' : '编辑'}}</span>
</div>
<van-checkbox-group @change="toggle" class="card-goods" v-model="checkedGoods">
<div v-for="(item, i) in goods" :key="i" class="card-goods__item">
<van-checkbox :key="item.id" :name="item.id" v-model="item.checked"></van-checkbox>
<van-card desc="暂无描述" :num="item.number" :thumb="item.picUrl">
<div class="van-card__row" slot="title">
<div class="van-card__title">
<!-- <van-tag plain type="danger">海淘</van-tag> -->
{{item.goodsName}}
</div>
<div class="van-card__price">{{item.price * 100 | yuan}}</div>
</div>
<div slot="footer" v-if="isEditor">
<van-stepper v-model="item.number" @change="stepperEvent(item,arguments)" disableInput/>
</div>
<div slot="footer" v-else>添加日期 {{item.addTime}}</div>
</van-card>
<div class="cart_delete" v-if="isEditor" @click="deleteCart(i)">删除</div>
</div>
</van-checkbox-group>
<div class="clear_invalid" v-if="goods.length" @click="clearInvalid">
<van-icon name="lajitong"/>清除失效商品
</div>
<is-empty v-if="!goods.length">您的购物车空空如也~</is-empty>
<van-submit-bar
style="bottom: 50px"
:price="totalPrice"
:disabled="!checkedGoods.length"
:buttonText="submitBarText"
:loading="isSubmit"
label="总计"
@submit="cartSubmit"
>
<van-checkbox v-model="allCheckedStatus" @change="setCheckAll" style="padding: 0 10px;">全选</van-checkbox>
</van-submit-bar>
</div>
</template>
<script>
import { Checkbox, CheckboxGroup, Card, SubmitBar, Stepper, Tag } from 'vant';
import isEmpty from '@/vue/components/is-empty/';
import _ from 'lodash';
import { debug } from 'util';
export default {
data() {
return {
isEditor: false,
checkedAll: false,
isSubmit: false,
checkedGoods: [],
AllGoods: [],
allCheckedStatus: false,
goods: [],
count: 0
};
},
activated() {
this.checkedAll = false;
this.isEditor = false;
this.isSubmit = false;
},
created() {
this.init();
},
computed: {
submitBarText() {
const count = this.count;
return this.isEditor ? '删除' : `结算${count ? `(${count})` : ''}`;
},
totalPrice() {
return this.goods.reduce(
(total, item) =>
total +
(this.checkedGoods.indexOf(item.id) !== -1
? item.price * item.number * 100
: 0),
0
);
}
},
methods: {
async stepperEvent(item, arg) {
let number = arg[0];
await this.$reqPost('/wx/cart/update', {
number: number,
goodsId: item.goodsId,
id: item.id,
productId: item.productId
});
},
async init() {
let { data } = await this.$reqGet('/wx/cart/index');
this.goods = data.data.cartList;
this.AllGoods = this.getAllList();
this.checkedGoods = this.getCheckedList(this.goods);
this.count = this.checkedGoods.length;
},
getAllList() {
let result = [];
_.each(this.goods, v => {
result.push(v.id);
});
return result;
},
getCheckedList(goods) {
let result = [];
_.each(goods, v => {
if (v.checked) {
result.push(v.id);
}
});
return result;
},
async cartSubmit(data) {
let productIds = [];
let checkedGoods = this.checkedGoods;
_.each(checkedGoods, id => {
productIds.push(
_.find(this.goods, vv => {
return id === vv.id;
}).productId
);
});
console.log(this.goods);
if (this.isEditor) {
this.$dialog
.confirm({
message: '确定删除所选商品吗?',
cancelButtonText: '再想想'
})
.then(() => {
this.deleteNext(productIds);
});
} else {
// for (check in checkedGoods){
// await this.doCheck(productIds);
// }
let { data } = await this.$reqGet(
'/wx/cart/checkout?cartId=0&addressId=0&couponId=0&grouponRulesId=0'
);
this.isSubmit = true;
this.$router.push({ name: 'placeOrderEntity' });
}
},
async doCheck(productIds, isChecked) {
// let good = _.find(this.goods, vv => {
// return id === vv.id;
// })
// let productId = good.productId;
let { data } = await this.$reqPost('/wx/cart/checked', {
productIds: productIds,
isChecked: isChecked
});
// if (this.checkedGoods.length == this.AllGoods.length) {
// this.allCheckedStatus = true;
// }
},
formatPrice(price) {
return (price / 100).toFixed(2);
},
setCheckAll(val) {
if (this.checkedGoods.length === this.AllGoods.length) {
this.checkedGoods = [];
} else {
this.checkedGoods = this.AllGoods;
}
},
deleteCart(o) {
let productId = this.goods[o].productId;
this.$dialog
.confirm({ message: '确定删除所选商品吗', cancelButtonText: '再想想' })
.then(() => {
// const goodsId = this.goods.splice(i, 1)[0].id;
this.$nextTick(() => {
this.deleteNext(productId);
});
});
},
toggle(index) {
let addProductIds = [];
_.each(index, v => {
let productId = _.find(this.goods, result => {
return result.id === v;
}).productId;
addProductIds.push(productId);
});
let delProductIds = [];
_.each(_.difference(this.AllGoods, index), v => {
let productId = _.find(this.goods, result => {
return result.id === v;
}).productId;
delProductIds.push(productId);
});
//没选中的不掉接口
if (delProductIds.length > 0) {
this.doCheck(delProductIds, 0);
}
if (addProductIds.length > 0) {
this.doCheck(addProductIds, 1);
}
},
async deleteNext(o) {
let productIds = [];
if (o instanceof Array) {
productIds = o;
} else {
productIds.push(o);
}
let { data } = await this.$reqPost('/wx/cart/delete', {
productIds: productIds
});
this.count = this.count - productIds.length;
this.goods = data.data.cartList;
// this.isEditor = !!this.goods.length;
// this.checkedGoods.forEach((goods, i) => {
// if (goods.id == goodsId) {
// this.checkedGoods.splice(i, 1);
// return false;
// }
// });
},
clearInvalid() {
this.$dialog
.confirm({
message: '确定清除所有失效商品吗?',
cancelButtonText: '再想想'
})
.then(() => {
this.goods = this.goods.filter(goods => goods.checked);
});
}
},
components: {
[Card.name]: Card,
[Tag.name]: Tag,
[Stepper.name]: Stepper,
[isEmpty.name]: isEmpty,
[Checkbox.name]: Checkbox,
[SubmitBar.name]: SubmitBar,
[CheckboxGroup.name]: CheckboxGroup
}
};
</script>
<style lang="scss" scoped>
@import '../../assets/scss/mixin';
.tab-cart {
padding-bottom: 50px;
box-sizing: border-box;
}
.editor_head {
@include one-border;
text-align: right;
padding: 10px;
font-size: $font-size-normal;
background-color: #fff;
}
.card-goods {
background-color: $bg-color;
.card-goods__item {
display: flex;
align-items: center;
margin-bottom: 10px;
background-color: #fff;
}
.cart_delete {
line-height: 100px;
padding: 0 10px;
color: #fff;
background-color: $red;
}
.card-goods__footer {
font-size: $font-size-normal;
color: $font-color-gray;
}
}
.clear_invalid {
width: 120px;
color: $font-color-gray;
border: 1px solid $font-color-gray;
margin: 0 auto;
text-align: center;
padding: 5px 3px;
margin-top: 20px;
border-radius: 3px;
}
</style>

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,112 @@
<template>
<div>
<van-nav-bar title="编辑地址" left-text="返回" left-arrow @click-left="goback"/>
<van-address-edit
style="background-color: #fff;"
:areaList="areaList"
:addressInfo="addressInfo"
:isSaving="isSave"
showPostal
@save="save"
/>
</div>
</template>
<script>
import { AddressEdit, NavBar } from 'vant';
import areaList from './area.json';
export default {
name: 'address-edit',
props: {
addressId: [Number, String]
},
data() {
return {
areaList,
isSave: false,
addressInfo: {}
};
},
created() {
if (this.addressId !== -1) {
this.init();
}
},
methods: {
async init() {
let { data } = await this.$reqGet(
`/wx/address/detail?id=${this.addressId}`
);
let address = data.data;
this.addressInfo = {
name: address.name,
tel: address.mobile,
// country: '中国',
province: address.provinceName,
city: address.cityName,
// county: '东城区',
areaCode: '110101',
// postalCode: '000000',
// addressDetail: '1',
isDefault: false,
id: this.addressId,
areaId: ''
// address_detail: '是的是的',
// area_code: '330106',
// postal_code: 123456
};
// litemall data
// {
// "errno":0,
// "data":{
// "isDefault":true,
// "areaId":377,
// "address":"adadada",
// "cityName":"市辖区",
// "areaName":"西城区",
// "name":"asd",
// "mobile":"13664552998",
// "id":2,
// "cityId":32,
// "provinceName":"北京市",
// "provinceId":1
// },
// "errmsg":"成功"
// }
console.log(areaList);
},
async save(data) {
let params = {};
params.address = data.addressDetail;
params.areaId = 376;
params.cityId = 32;
params.id = 0;
params.isDefault = true;
params.mobile = data.tel;
params.name = data.name;
params.provinceId = 1;
let { response } = await this.$reqPost(
'/wx/address/save',
params
);
this.$toast('成功');
this.$router.push({ path: '/user/address' });
},
goback() {
this.$router.go(-1);
}
},
components: {
[NavBar.name]: NavBar,
[AddressEdit.name]: AddressEdit
}
};
</script>

View File

@@ -0,0 +1,114 @@
<template>
<div>
<van-nav-bar title="收货地址" left-text="返回" left-arrow @click-left="goback"/>
<van-radio-group v-model="checkedId">
<van-cell-group v-for="item in addresses" :key="item.id" class="addressGroup">
<van-cell isLink icon="dingwei" :title="item.name" :label="item.detailedAddress"/>
<van-cell>
<van-radio slot="title" :name="item.id" @click="setDefaultAddress(item)">
<span>{{item.isDefault ? '默认地址' : '设为默认'}}</span>
</van-radio>
<div>
<router-link
:to="{name: 'address-edit', params: {addressId: item.id}}"
style="margin-right: 10px;"
>
<van-icon name="editor"/>编辑
</router-link>
<span @click="delAddress(item.id)">
<van-icon name="lajitong"/>删除
</span>
</div>
</van-cell>
</van-cell-group>
</van-radio-group>
<van-button class="bottom_btn" @click="setNewAddress" type="primary" bottomAction>添加地址</van-button>
</div>
</template>
<script>
import { Checkbox, Radio, RadioGroup, NavBar } from 'vant';
import { async } from 'q';
import _ from 'lodash';
export default {
data() {
return {
checkedId: '',
default_address: 1,
addressList: [
{
id: 1
},
{
id: 2
},
{
id: 3
},
{
id: 4
}
],
addresses: []
};
},
created() {
this.loadAddress();
},
methods: {
async setDefaultAddress(item) {
let { data } = await this.$reqGet(
`/wx/address/detail?id=${item.id}`
);
let params = data.data;
params.isDefault = true;
await this.$reqPost('/wx/address/save', params);
},
async delAddress(id) {
let { data } = await this.$reqPost('/wx/address/delete', {
id: id
});
this.loadAddress();
},
setNewAddress() {
this.$router.push({ name: 'address-edit', params: { addressId: -1 } });
},
goback() {
this.$router.go(-1);
},
async loadAddress() {
let { data } = await this.$reqGet('/wx/address/list');
this.addresses = data.data;
this.checkedId = _.find(this.addresses, result => {
return result.isDefault === true;
}).id;
}
},
components: {
[NavBar.name]: NavBar,
[Checkbox.name]: Checkbox,
[Radio.name]: Radio,
[RadioGroup.name]: RadioGroup
}
};
</script>
<style lang="scss" scoped>
.addressGroup {
margin-bottom: 10px;
&:last-child {
margin-bottom: 0;
}
}
.bottom_btn {
position: fixed;
bottom: 0;
}
</style>

View File

@@ -0,0 +1,115 @@
<template>
<div class="id_card_upload">
<div>身份证照片</div>
<van-row class="upload_hint text-desc">
<van-icon name="hint"/>
温馨提示: 请上传原始比例的身份证正反面, 请勿剪裁涂改,保证身份证信息清晰显示, 否则无法通过审核
</van-row>
<van-row gutter="20" class="id_card_row upload_box">
<van-col span=12>
<div class="upload_wrap">
<div v-if="frontUrl">
<img :src="frontUrl" alt="反面">
</div>
<div class="add_btn" v-else>
<van-icon name="add" />
<div>上传照片</div>
</div>
</div>
</van-col>
<van-col span=12>
<div class="upload_wrap">
<div v-if="reverseUrl">
<img :src="reverseUrl" alt="反面">
</div>
<div class="add_btn" v-else>
<van-icon name="add" />
<div>上传照片</div>
</div>
</div>
</van-col>
</van-row>
<van-row gutter="10" class="id_card_row">
<van-col span=12>
<div class="text-desc">正面示例</div>
<img src="../../../assets/images/id_card_front.png" alt="正面" width="50%">
</van-col>
<van-col span=12>
<div class="text-desc">反面示例</div>
<img src="../../../assets/images/id_card_reverse.png" alt="反面" width="50%">
</van-col>
</van-row>
</div>
</template>
<script>
import { Row, Col } from 'vant';
export default {
name: 'id-card-upload',
props: {
frontUrl: String,
reverseUrl: String
},
components: {
[Row.name]: Row,
[Col.name]: Col
}
};
</script>
<style lang="scss" scoped>
.id_card_upload {
margin: 10px 0 30px 0;
background-color: #fff;
padding: 15px 10px;
> div {
margin-bottom: 15px;
}
}
.upload_hint {
position: relative;
padding-left: 20px;
line-height: 1.6;
i {
position: absolute;
top: 2px;
left: 0;
}
}
.id_card_row {
> div {
text-align: center;
}
.text-desc {
margin-bottom: 8px;
}
}
.upload_box .upload_wrap {
position: relative;
border: 1px dashed $gray;
min-height: 100px;
box-sizing: border-box;
padding: 5px;
img {
max-width: 100%;
display: block;
}
.add_btn {
position: absolute;
top: 50%;
left: 50%;
font-size: 20px;
color: $gray;
transform: translate(-50%, -50%);
i {
font-size: 24px;
}
}
}
</style>

View File

@@ -0,0 +1,112 @@
<template>
<div>
<van-cell-group>
<van-field
v-model="field.username"
label="姓名"
placeholder="请输入姓名"
required
:error="nameErr"
@blur="checkUserName"
@focus="nameErr = false"/>
<van-field
v-model="field.idCard"
label="身份证号"
placeholder="请输入身份证号码"
required
:error="idCardErr"
@blur="checkIdCard"
@focus="idCardErr = false"/>
</van-cell-group>
<id-card-upload
:front.sync="field.frontUrl"
:reverse.sync="field.reverseUrl"/>
<div class="save_div">
<van-button type="danger" size="large" @click="save">保存</van-button>
</div>
<ul class="text-desc">
<li>根据海关规定: 购买跨境商品需要办理相关手续.</li>
<li>根据海关规定: 购买跨境商品需要办理相关手续.</li>
</ul>
</div>
</template>
<script>
import idCardUpload from './id-card-upload';
import { idCard } from 'core/regexp';
export default {
data() {
return {
field: {
username: '',
idCard: '',
frontUrl: '',
reverseUrl: ''
},
nameErr: true,
idCardErr: true
};
},
methods: {
save() {
const keys = Object.keys(this.field);
const isValid = keys.every(key => {
const message = this.getErrorMessageByKey(key);
if (message) {
this.$toast.fail({ message, duration: 1000 });
}
return !message;
});
if (isValid) {
console.log('保存');
}
},
checkUserName() {
this.nameErr =
this.field.username == '' || this.field.username.length > 5;
},
checkIdCard() {
this.idCardErr = !idCard.test(this.field.idCard);
},
getErrorMessageByKey(key) {
const val = this.field[key];
switch (key) {
case 'username':
return val ? (val.length < 5 ? '' : '名字太长了') : '请输入名字';
case 'idCard':
return val ? (idCard(val) ? '' : '请输入正确身份证') : '请输入身份证';
case 'frontUrl':
return val ? '' : '请上传正面照片';
case 'reverseUrl':
return val ? '' : '请上传背面照片';
}
}
},
components: {
[idCardUpload.name]: idCardUpload
}
};
</script>
<style lang="scss" scoped>
.save_div {
padding: 0 20px;
}
ul.text-desc {
padding: 20px;
list-style: inside;
li {
margin-bottom: 15px;
}
}
</style>

View File

@@ -0,0 +1,96 @@
<template>
<div>
<van-radio-group v-model="default_address" @change="setDefaultAddress">
<van-cell-group v-for="item in addressList" :key="item.id" class="addressGroup">
<van-cell
isLink
icon="id-card"
title="张三"
label="身份证: 3303271996455332544"
/>
<van-cell>
<van-radio
slot="title"
:name="item.id"
@change="setDefaultAddress(item.id)">
<span :class="item.isDefault && 'red'">{{item.isDefault ? '默认地址' : '设为默认'}}</span>
</van-radio>
<div>
<router-link
:to="{name: 'autonym-edit', params: {addressId: item.id}}"
style="margin-right: 10px;">
<van-icon name="editor" />
编辑
</router-link>
<span>
<van-icon name="lajitong" />
删除
</span>
</div>
</van-cell>
</van-cell-group>
</van-radio-group>
<van-button class="bottom_btn" @click="setNewAddress" type="primary" bottomAction>
添加认证
</van-button>
</div>
</template>
<script>
import { Checkbox, Radio, RadioGroup } from 'vant';
export default {
data() {
return {
default_address: 1,
addressList: [
{
id: 1
},
{
id: 2
},
{
id: 3
},
{
id: 4
}
]
};
},
methods: {
setDefaultAddress(id) {
console.log(id);
},
setNewAddress() {
this.$router.push({ name: 'autonym-edit', params: { addressId: -1 } });
}
},
components: {
[Checkbox.name]: Checkbox,
[Radio.name]: Radio,
[RadioGroup.name]: RadioGroup
}
};
</script>
<style lang="scss" scoped>
.addressGroup {
margin-bottom: 10px;
&:last-child {
margin-bottom: 0;
}
}
.bottom_btn {
position: fixed;
bottom: 0;
}
</style>

View File

@@ -0,0 +1,127 @@
<template>
<div class="user_collect">
<form action="/search" class="fixedTop">
<van-search placeholder="请输入商品名称" v-model="searchVal"/>
</form>
<van-list
v-model="loading"
:finished="finished"
:immediate-check="false"
:offset="100"
@load="loadMore"
>
<item-group>
<item-card-hori
v-for="(item, i) in items"
:style="{backgroundColor: !item.goods_status && '#fcfcfc'}"
:key="i"
:goods="item"
@click="itemClick(i,item)"
>
<van-icon
name="lajitong"
slot="footer"
@click.stop="cancelCollect($event, i,item)"
style="float: right;"
/>
</item-card-hori>
</item-group>
</van-list>
<is-empty v-if="isEmpty">没有商品收藏</is-empty>
<!-- <div class="clear_invalid" v-if="items.length" @click="clearInvalid">
<van-icon name="lajitong"/>清除失效商品
</div>-->
</div>
</template>
<script>
import { GOODS_COLLECT_LIST } from '@/api/user';
import ItemGroup from '@/vue/components/item-group/';
import ItemCardHori from '@/vue/components/item-card-hori/';
import IsEmpty from '@/vue/components/is-empty/';
import { Search, List } from 'vant';
import loadMore from '@/vue/mixin/list-load-more';
import scrollFixed from '@/vue/mixin/scroll-fixed';
export default {
mixins: [loadMore, scrollFixed],
data() {
return {
shop_id: 1,
items: [],
searchVal: ''
};
},
created() {
this.resetInit();
},
methods: {
initData() {
return this.$reqGet(
'/wx/collect/list?type=0&page=1&size=100',
{},
{
hideLoading: true
}
).then(res => {
// debugger;
const { collectList, page } = res.data.data;
this.items.push(...collectList);
return page;
});
},
cancelCollect(event, i, item) {
this.$dialog.confirm({ message: '是否取消收藏该商品' }).then(() => {
this.$reqPost(
'/wx/collect/addordelete',
{ valueId: item.valueId, type: 0 },
{
hideLoading: true
}
).then(res => {
this.items.splice(i, 1);
});
});
},
clearInvalid() {
this.$dialog.confirm({ message: '确定清除所有失效商品吗?' });
},
itemClick(i, item) {
// const item_id = this.items[i].item_id;
// const status = this.items[i].goods_status;
// status &&
this.$router.push(`/items/detail/${item.valueId}`);
// !status && this.$toast('该商品已失效');
}
},
components: {
[ItemGroup.name]: ItemGroup,
[ItemCardHori.name]: ItemCardHori,
[Search.name]: Search,
[IsEmpty.name]: IsEmpty,
[List.name]: List
}
};
</script>
<style lang="scss" scoped>
.clear_invalid {
width: 120px;
color: $font-color-gray;
border: 1px solid $font-color-gray;
margin: 0 auto;
text-align: center;
padding: 5px 3px;
margin-top: 20px;
border-radius: 3px;
}
</style>

View File

@@ -0,0 +1,65 @@
<template>
<div class="user_invitation">
<div class="invitation_wrap">
<div>
<h1>会员奖励计划</h1>
<p>sdsad</p>
<p>sdsad</p>
<p>sdsad</p>
<p>sdsad</p>
<p>sdsad</p>
<p>sdsad</p>
<p>sdsad</p>
<p>sdsad</p>
<p>sdsad</p>
<p>sdsad</p>
<p>sdsad</p>
<p>sdsad</p>
<p>sdsad</p>
<p>sdsad</p>
<p>sdsad</p>
<p>sdsad</p>
<p>sdsad</p>
</div>
</div>
<van-button class="bottom_btn" type="primary" bottomAction>立即分享,邀请朋友</van-button>
</div>
</template>
<script>
export default {};
</script>
<style lang="scss" scoped>
.user_invitation {
height: 100%;
}
.invitation_wrap {
height: 90%;
box-sizing: border-box;
padding: 30px 20px 0 20px;
> div {
height: 100%;
background-color: #fff;
padding: 30px 20px;
box-sizing: border-box;
overflow-x: hidden;
overflow-y: auto;
}
h1 {
margin: 0;
font-weight: normal;
font-size: 20px;
text-align: center;
margin-bottom: 10px;
}
p {
margin: 0;
line-height: 22px;
}
}
.bottom_btn {
position: fixed;
bottom: 0;
}
</style>

View File

@@ -0,0 +1,30 @@
<template>
<div>
<van-cell-group>
<van-cell title="联系客服" @click="showKefu = true" isLink></van-cell>
<van-cell title="意见反馈" isLink></van-cell>
<van-cell title="常见问题" isLink></van-cell>
</van-cell-group>
<van-popup v-model="showKefu">
<md-kefu mobile="16454193338"/>
</van-popup>
</div>
</template>
<script>
import { Popup } from 'vant';
import md_kefu from '@/vue/components/md-kefu/';
export default {
data() {
return {
showKefu: false
};
},
components: {
[Popup.name]: Popup,
[md_kefu.name]: md_kefu
}
};
</script>

View File

@@ -0,0 +1,75 @@
<template>
<div class="user_team">
<van-cell-group>
<van-cell title="我的团队(50)">
<div>
<van-icon name="invitation" />
<router-link to="/user/invitation">邀请会员</router-link>
</div>
</van-cell>
</van-cell-group>
<van-cell-group>
<van-cell>
<div slot="title" class="user_member">
<div class="user_avatar float-l">
<img src="../../../assets/images/avatar_default.png" alt="头像">
</div>
<div class="user_info">
<div>张三</div>
<div>一级会员</div>
</div>
</div>
</van-cell>
<van-cell>
<div slot="title" class="user_member">
<div class="user_avatar float-l">
<img src="../../../assets/images/avatar_default.png" alt="头像">
</div>
<div class="user_info">
<div>张三</div>
<div>一级会员</div>
</div>
</div>
</van-cell>
<van-cell>
<div slot="title" class="user_member">
<div class="user_avatar float-l">
<img src="../../../assets/images/avatar_default.png" alt="头像">
</div>
<div class="user_info">
<div>张三</div>
<div>一级会员</div>
</div>
</div>
</van-cell>
</van-cell-group>
</div>
</template>
<script>
export default {};
</script>
<style lang="scss" scoped>
.user_team {
background-color: #fff;
}
.user_member {
.user_avatar {
height: 55px;
width: 55px;
border-radius: 50%;
overflow: hidden;
img {
max-width: 100%;
max-height: 100%;
}
}
.user_info {
margin-left: 70px;
}
}
</style>

View File

@@ -0,0 +1,6 @@
<template>
<div slot="footer" class="order_list--footer_btn">
<van-button size="small" @click="$emit('cancel')">取消订单</van-button>
<van-button size="small" type="danger" @click="$emit('pay')">去支付</van-button>
</div>
</template>

View File

@@ -0,0 +1,5 @@
<template>
<div slot="footer" class="order_list--footer_btn">
<van-button size="small">去使用</van-button>
</div>
</template>

View File

@@ -0,0 +1,5 @@
<template>
<div slot="footer" class="order_list--footer_btn">
<van-button size="small">查看详情</van-button>
</div>
</template>

Some files were not shown because too many files have changed in this diff Show More