doc
35
README.md
@@ -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管理员前端 + 微信小程序用户前端
|
||||

|
||||
> 注意:此实例是测试小商场,开发者请不要尝试购买商品、付款、退款操作。
|
||||
|
||||
### 轻商场实例
|
||||
|
||||
目前未部署
|
||||
|
||||

|
||||
|
||||
### 管理后台实例
|
||||
|
||||

|
||||
@@ -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项目开发。
|
||||
|
||||
## 问题
|
||||
|
||||

|
||||
|
||||
@@ -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
@@ -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
|
After Width: | Height: | Size: 189 KiB |
@@ -2,30 +2,35 @@
|
||||
|
||||
## 1.1 简介
|
||||
|
||||
litemall是一个简单的商场系统,基于现有的开源项目,重新实现一个完整的前后端项目,包含小程序客户端和网页管理端。
|
||||
litemall是一个简单的商场系统,基于现有的开源项目,重新实现一个完整的前后端项目,包含小程序客户端、移动客户端和网页管理端。
|
||||
|
||||

|
||||
|
||||
|
||||
项目的架构是三个系统和六个模块:
|
||||
项目的架构是四个系统和九个模块:
|
||||
|
||||
* 基础系统子系统(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
@@ -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
@@ -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*
|
||||
5
litemall-vue/.postcssrc.js
Normal file
@@ -0,0 +1,5 @@
|
||||
module.exports = {
|
||||
plugins: {
|
||||
autoprefixer: {}
|
||||
}
|
||||
};
|
||||
4
litemall-vue/.prettierrc.js
Normal file
@@ -0,0 +1,4 @@
|
||||
module.exports = {
|
||||
tabWidth: 2,
|
||||
singleQuote: true
|
||||
};
|
||||
15
litemall-vue/babel.config.js
Normal 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
@@ -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"
|
||||
]
|
||||
}
|
||||
|
Before Width: | Height: | Size: 66 KiB After Width: | Height: | Size: 66 KiB |
25
litemall-vue/public/index.html
Normal 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
@@ -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
@@ -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
@@ -0,0 +1,5 @@
|
||||
export const ORDER_LIST = '/order-list';
|
||||
|
||||
export const ELE_COUPON_LIST = '/electronic-list';
|
||||
|
||||
export const REFUND_LIST = '/refund-list';
|
||||
0
litemall-vue/src/api/promotion.js
Executable file
7
litemall-vue/src/api/shop.js
Executable 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
@@ -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';
|
||||
BIN
litemall-vue/src/assets/images/ali_pay.png
Executable file
|
After Width: | Height: | Size: 12 KiB |
BIN
litemall-vue/src/assets/images/avatar_default.png
Executable file
|
After Width: | Height: | Size: 5.9 KiB |
BIN
litemall-vue/src/assets/images/goods_default.png
Executable file
|
After Width: | Height: | Size: 3.5 KiB |
BIN
litemall-vue/src/assets/images/id_card_front.png
Executable file
|
After Width: | Height: | Size: 42 KiB |
BIN
litemall-vue/src/assets/images/id_card_reverse.png
Executable file
|
After Width: | Height: | Size: 42 KiB |
BIN
litemall-vue/src/assets/images/index_collect.png
Executable file
|
After Width: | Height: | Size: 30 KiB |
BIN
litemall-vue/src/assets/images/is_empty.png
Executable file
|
After Width: | Height: | Size: 4.8 KiB |
BIN
litemall-vue/src/assets/images/mx_be_to.png
Executable file
|
After Width: | Height: | Size: 3.2 KiB |
BIN
litemall-vue/src/assets/images/mx_start.png
Executable file
|
After Width: | Height: | Size: 2.8 KiB |
BIN
litemall-vue/src/assets/images/not_enough.png
Executable file
|
After Width: | Height: | Size: 15 KiB |
BIN
litemall-vue/src/assets/images/qc_code.png
Executable file
|
After Width: | Height: | Size: 43 KiB |
BIN
litemall-vue/src/assets/images/store_default.png
Executable file
|
After Width: | Height: | Size: 8.8 KiB |
BIN
litemall-vue/src/assets/images/trumpet.png
Normal file
|
After Width: | Height: | Size: 1.4 KiB |
BIN
litemall-vue/src/assets/images/user_head_bg.png
Executable file
|
After Width: | Height: | Size: 23 KiB |
BIN
litemall-vue/src/assets/images/wx_pay.png
Executable file
|
After Width: | Height: | Size: 12 KiB |
1
litemall-vue/src/assets/scss/_mixin.scss
Executable file
@@ -0,0 +1 @@
|
||||
@import "./mixin/one-border";
|
||||
127
litemall-vue/src/assets/scss/_vant-theme.scss
Executable 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;
|
||||
}
|
||||
16
litemall-vue/src/assets/scss/_var.scss
Executable 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;
|
||||
59
litemall-vue/src/assets/scss/common.scss
Executable 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;
|
||||
}
|
||||
45
litemall-vue/src/assets/scss/global.scss
Executable 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;
|
||||
}
|
||||
318
litemall-vue/src/assets/scss/iconfont/iconfont.css
Executable 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";
|
||||
}
|
||||
BIN
litemall-vue/src/assets/scss/iconfont/iconfont.ttf
Executable file
17
litemall-vue/src/assets/scss/mixin/_one-border.scss
Executable 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;
|
||||
}
|
||||
}
|
||||
53
litemall-vue/src/assets/scss/spinner.scss
Normal 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%);
|
||||
}
|
||||
32
litemall-vue/src/core/async-loader.js
Normal 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);
|
||||
}
|
||||
});
|
||||
};
|
||||
12
litemall-vue/src/core/regexp/index.js
Executable 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+)+)$/;
|
||||
20
litemall-vue/src/core/utils/local-storage.js
Normal 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);
|
||||
});
|
||||
};
|
||||
6
litemall-vue/src/core/utils/location-param.js
Normal 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 '';
|
||||
};
|
||||
90
litemall-vue/src/core/utils/request.js
Executable 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
|
||||
31
litemall-vue/src/core/utils/scroll.js
Executable 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
@@ -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');
|
||||
67
litemall-vue/src/views/home/tabbar-home-shop-info.vue
Executable 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>
|
||||
69
litemall-vue/src/views/home/tabbar-home-sign-board.vue
Executable 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>
|
||||
510
litemall-vue/src/views/home/tabbar-home.vue
Executable 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>
|
||||
3607
litemall-vue/src/views/items/detail/EntityGroup/area.json
Executable file
513
litemall-vue/src/views/items/detail/EntityGroup/index.vue
Executable 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>
|
||||
168
litemall-vue/src/views/items/detail/EntityGroup/popup-address.vue
Executable 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>
|
||||
50
litemall-vue/src/views/items/detail/EntityGroup/popup-area.vue
Executable 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>
|
||||
79
litemall-vue/src/views/items/detail/EntityGroup/popup-props.vue
Executable 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>
|
||||
121
litemall-vue/src/views/items/detail/VirtualGroup/index.vue
Executable 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>
|
||||
274
litemall-vue/src/views/items/detail/index.vue
Executable 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>
|
||||
42
litemall-vue/src/views/items/detail/mix.js
Executable 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
|
||||
}
|
||||
};
|
||||
251
litemall-vue/src/views/items/list/index.vue
Executable 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>
|
||||
166
litemall-vue/src/views/items/search-result/index.vue
Executable 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>
|
||||
104
litemall-vue/src/views/items/search/index.vue
Executable 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>
|
||||
28
litemall-vue/src/views/items/search/search-tag.vue
Executable 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>
|
||||
170
litemall-vue/src/views/items/tabbar-class-tree.vue
Executable 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>
|
||||
112
litemall-vue/src/views/items/tabbar-class.vue
Executable 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>
|
||||
57
litemall-vue/src/views/login/forget-reset/index.vue
Executable 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>
|
||||
81
litemall-vue/src/views/login/forget-status/index.vue
Executable 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>
|
||||
80
litemall-vue/src/views/login/forget/index.vue
Executable 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>
|
||||
18
litemall-vue/src/views/login/login-footer.vue
Executable 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>
|
||||
29
litemall-vue/src/views/login/login-header.vue
Executable 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>
|
||||
182
litemall-vue/src/views/login/login-request.vue
Executable 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>
|
||||
29
litemall-vue/src/views/login/login.vue
Executable 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>
|
||||
59
litemall-vue/src/views/login/register-getCode/index.vue
Executable 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>
|
||||
81
litemall-vue/src/views/login/register-status/index.vue
Executable 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>
|
||||
78
litemall-vue/src/views/login/register-submit/index.vue
Executable 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>
|
||||
66
litemall-vue/src/views/order/orderDetail/bottom-goods-info.vue
Executable 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>
|
||||
257
litemall-vue/src/views/order/orderDetail/index.vue
Executable 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>
|
||||
62
litemall-vue/src/views/order/orderDetail/top-user-info.vue
Executable 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>
|
||||
94
litemall-vue/src/views/order/payment-status/index.vue
Executable 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>
|
||||
165
litemall-vue/src/views/order/payment/index.vue
Executable 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>
|
||||
68
litemall-vue/src/views/order/place-order-entity/bottom-goods-info.vue
Executable 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>
|
||||
83
litemall-vue/src/views/order/place-order-entity/index.vue
Executable 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>
|
||||
78
litemall-vue/src/views/order/place-order-entity/top-user-info.vue
Executable 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>
|
||||
122
litemall-vue/src/views/order/place-order-virtual/index.vue
Executable 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>
|
||||
311
litemall-vue/src/views/order/tabbar-cart.vue
Executable 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>
|
||||
3607
litemall-vue/src/views/user/module-address-edit/area.json
Executable file
112
litemall-vue/src/views/user/module-address-edit/index.vue
Executable 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>
|
||||
114
litemall-vue/src/views/user/module-address/index.vue
Executable 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>
|
||||
115
litemall-vue/src/views/user/module-autonym-edit/id-card-upload.vue
Executable 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>
|
||||
112
litemall-vue/src/views/user/module-autonym-edit/index.vue
Executable 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>
|
||||
96
litemall-vue/src/views/user/module-autonym/index.vue
Executable 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>
|
||||
127
litemall-vue/src/views/user/module-collect/index.vue
Normal 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>
|
||||
65
litemall-vue/src/views/user/module-invitation/index.vue
Executable 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>
|
||||
30
litemall-vue/src/views/user/module-server/index.vue
Executable 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>
|
||||
75
litemall-vue/src/views/user/module-team/index.vue
Executable 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>
|
||||
@@ -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>
|
||||
@@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<div slot="footer" class="order_list--footer_btn">
|
||||
<van-button size="small">去使用</van-button>
|
||||
</div>
|
||||
</template>
|
||||
@@ -0,0 +1,5 @@
|
||||
<template>
|
||||
<div slot="footer" class="order_list--footer_btn">
|
||||
<van-button size="small">查看详情</van-button>
|
||||
</div>
|
||||
</template>
|
||||