vue手机外卖点餐系统

This commit is contained in:
ylinn 2018-04-12 14:49:30 +08:00
commit f98fdf6d73
108 changed files with 6000 additions and 0 deletions

5
.babelrc Normal file
View File

@ -0,0 +1,5 @@
{
"presets": ["es2015", "stage-2"],
"plugins": ["transform-runtime"],
"comments": false
}

9
.editorconfig Normal file
View File

@ -0,0 +1,9 @@
root = true
[*]
charset = utf-8
indent_style = space
indent_size = 2
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true

2
.eslintignore Normal file
View File

@ -0,0 +1,2 @@
build/*.js
config/*.js

32
.eslintrc.js Normal file
View File

@ -0,0 +1,32 @@
module.exports = {
root: true,
parser: 'babel-eslint',
parserOptions: {
sourceType: 'module'
},
// https://github.com/feross/standard/blob/master/RULES.md#javascript-standard-style
extends: 'standard',
// required to lint *.vue files
plugins: [
'html'
],
// add your custom rules here
'rules': {
// allow paren-less arrow functions
'arrow-parens': 0,
// allow async-await
'generator-star-spacing': 0,
// allow debugger during development
'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0,
'no-multiple-empty-lines': ['error', {
max: 2,
maxEOF: 2,
maxBOF: 2
}],
'space-before-function-paren':0,
'semi':0,
'no-new':0,
'no-unused-vars':0,
'no-undef':0
}
}

6
.gitignore vendored Normal file
View File

@ -0,0 +1,6 @@
.DS_Store
node_modules/
resource/
dist/
npm-debug.log
.idea

21
LICENSE Normal file
View File

@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2016 VueDemo_Sell_Eleme
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

145
README.md Normal file
View File

@ -0,0 +1,145 @@
#点餐系统
> vue2.0、vuex、vue-router、axios、webpack、eslint、better-scroll
## 组件
- [x] 购物车
- [x] 购买物品小球飞入动画
- [x] 评价star组件
- [x] 商品添加、删除组件
- [x] 优惠图标组件
- [x] 目录、列表联动滚动
- [x] 画廊
- [x] 评论的是否满意和内容筛选
- [x] 商品列表页面
- [x] 店铺评价页面
- [x] 商家介绍页面
- [x] 优惠活动页面
- [x] 商品详情页面
## 构建
vue有自己的脚手架构建工具vue-cli,使用起来非常方便使用webpack来集成各种开发便捷工具比如
- 代码热更新,修改代码之后网页无刷新改变,对前端开发来说非常的方便
- PostCss再也不用去管兼容性的问题了只针对chrome写css代码会自动编译生成支持多款浏览器的css代码
- Eslint统一代码风格规避低级错误对于有代码洁癖的人来说是绝对的好东西不过有些地方的代码校验有时候也挺麻烦的-.-
- bableES2015出来已经有一段时间了但是不少浏览器还没有兼容ES6.有了bable放心使用ES6语法它会自动转义成ES5语法。
- Stylus类似于SASS/SCSS但是可以不写{}和“:”,使用起来还是很方便的
- ...
除此之外vue-cli已经使用node配置了一套本地服务器和安装命令等本地运行和打包只需要一个命令就可以搞定非常的方便
## 开发
vue非常好的融合了react的组件化思想和angular的指令思想。
一个vue的组件将HTML、CSS、JS代码写在一个文件里面这样既方便编写也方便管理和修改
### Axios
在vue1.x的时候vue的官方推荐HTTP请求工具是vue-resource但是在vue2.0的时候将推荐工具改成了axios。
使用方式都差不多但需要注意的是接口返回的res并不直接是返回的数据而是经过axios本身处理过的json对象。真正的数据在res.data里
```javascript
axios.get(url).then((res)=>{
this.data = res.data
})
```
### Vuex
vue提供了一个数据管理工具vuex有点类似于angular中factory和service可以进行数据上的通信。
比如存储一些公共变量或者是不同组件间的数据处理等。
这个有一些高级用法在这里不细说,想要了解的可以去官方文档看,有中文版本。
```javascript
const store = new Vuex.Store({
state: {
count: 0
},
mutations: {
increment(state) {
state.count++
}
}
})
```
### Vue-Router
vue-router是vue的路由系统可以用来创建单页应用。基本思想是在主页面中引入<router-view>标签然后定义路由把router挂在到app上然后把各个子页面渲染到view里面。使用起来还是很方便的
跳转页面只需要
```javascript
router.push('test')
```
### 获取元素节点
vue2.0废除了v-el指令所有的节点指令修改为ref然后通过ref来获取元素节点
```html
<div ref="testHook">test</div>
...js code
this.$ref.testHook
```
### 组件间的通信
一。如果是和子组件通信则使用ref就可以实现
```html
<test ref="testHook"></test>
...js code
this.$ref.testHook.add() //调用test子组件的add方法
```
二。使用emit来发送广播
vue2提供了一套广播机制即一边发送广播一边接收广播来执行相应操作。使用方法如下
比如想要给test组件发送一个“相加”广播:
```javascript
export default {
method:{
click(){
Vue.$emit('add',{}) //第二个参数可作为传递数据传送到监听端口,不需要则传空对象
}
}
}
```
那么test组件中就需要监听在created方法里写
```javascript
export default {
created(){
Vue.$on('add',this.add)
},
method:{
add(){
this.count++
}
}
}
```
## 安装步骤
``` bash
# install dependencies
npm install
# serve with hot reload at localhost:8080
npm run dev
# build for production with minification
npm run build
```

36
build/build.js Normal file
View File

@ -0,0 +1,36 @@
// https://github.com/shelljs/shelljs
require('./check-versions')()
require('shelljs/global')
env.NODE_ENV = 'production'
var path = require('path')
var config = require('../config')
var ora = require('ora')
var webpack = require('webpack')
var webpackConfig = require('./webpack.prod.conf')
console.log(
' Tip:\n' +
' Built files are meant to be served over an HTTP server.\n' +
' Opening index.html over file:// won\'t work.\n'
)
var spinner = ora('building for production...')
spinner.start()
var assetsPath = path.join(config.build.assetsRoot, config.build.assetsSubDirectory)
rm('-rf', assetsPath)
mkdir('-p', assetsPath)
cp('-R', 'static/*', assetsPath)
webpack(webpackConfig, function (err, stats) {
spinner.stop()
if (err) throw err
process.stdout.write(stats.toString({
colors: true,
modules: false,
children: false,
chunks: false,
chunkModules: false
}) + '\n')
})

45
build/check-versions.js Normal file
View File

@ -0,0 +1,45 @@
var semver = require('semver')
var chalk = require('chalk')
var packageConfig = require('../package.json')
var exec = function (cmd) {
return require('child_process')
.execSync(cmd).toString().trim()
}
var versionRequirements = [
{
name: 'node',
currentVersion: semver.clean(process.version),
versionRequirement: packageConfig.engines.node
},
{
name: 'npm',
currentVersion: exec('npm --version'),
versionRequirement: packageConfig.engines.npm
}
]
module.exports = function () {
var warnings = []
for (var i = 0; i < versionRequirements.length; i++) {
var mod = versionRequirements[i]
if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) {
warnings.push(mod.name + ': ' +
chalk.red(mod.currentVersion) + ' should be ' +
chalk.green(mod.versionRequirement)
)
}
}
if (warnings.length) {
console.log('')
console.log(chalk.yellow('To use this template, you must update following to modules:'))
console.log()
for (var i = 0; i < warnings.length; i++) {
var warning = warnings[i]
console.log(' ' + warning)
}
console.log()
process.exit(1)
}
}

9
build/dev-client.js Normal file
View File

@ -0,0 +1,9 @@
/* eslint-disable */
require('eventsource-polyfill')
var hotClient = require('webpack-hot-middleware/client?noInfo=true&reload=true')
hotClient.subscribe(function (event) {
if (event.action === 'reload') {
window.location.reload()
}
})

108
build/dev-server.js Normal file
View File

@ -0,0 +1,108 @@
require('./check-versions')()
var config = require('../config')
if (!process.env.NODE_ENV) process.env.NODE_ENV = JSON.parse(config.dev.env.NODE_ENV)
var path = require('path')
var express = require('express')
var webpack = require('webpack')
var opn = require('opn')
var proxyMiddleware = require('http-proxy-middleware')
var webpackConfig = require('./webpack.dev.conf')
// default port where dev server listens for incoming traffic
var port = process.env.PORT || config.dev.port
// Define HTTP proxies to your custom API backend
// https://github.com/chimurai/http-proxy-middleware
var proxyTable = config.dev.proxyTable
var app = express()
// 数据mock
var appData = require('../static/data.json')
var seller = appData.seller
var goods = appData.goods
var ratings = appData.ratings
var apiRoutes = express.Router()
apiRoutes.get('/seller', function(req, res) {
res.json({
errno: 0,
data: seller
})
})
apiRoutes.get('/goods', function(req, res) {
res.json({
errno: 0,
data: goods
})
})
apiRoutes.get('/ratings', function(req, res) {
res.json({
errno: 0,
data: ratings
})
})
app.use('/api', apiRoutes)
var compiler = webpack(webpackConfig)
var devMiddleware = require('webpack-dev-middleware')(compiler, {
publicPath: webpackConfig.output.publicPath,
stats: {
colors: true,
chunks: false
}
})
var hotMiddleware = require('webpack-hot-middleware')(compiler)
// force page reload when html-webpack-plugin template changes
compiler.plugin('compilation', function(compilation) {
compilation.plugin('html-webpack-plugin-after-emit', function(data, cb) {
hotMiddleware.publish({
action: 'reload'
})
cb()
})
})
// proxy api requests
Object.keys(proxyTable).forEach(function(context) {
var options = proxyTable[context]
if (typeof options === 'string') {
options = {
target: options
}
}
app.use(proxyMiddleware(context, options))
})
// handle fallback for HTML5 history API
app.use(require('connect-history-api-fallback')())
// serve webpack bundle output
app.use(devMiddleware)
// enable hot-reload and state-preserving
// compilation error display
app.use(hotMiddleware)
// serve pure static assets
var staticPath = path.posix.join(config.dev.assetsPublicPath, config.dev.assetsSubDirectory)
app.use(staticPath, express.static('./static'))
module.exports = app.listen(port, function(err) {
if (err) {
console.log(err)
return
}
var uri = 'http://localhost:' + port
console.log('Listening at ' + uri + '\n')
// when env is testing, don't need open it
if (process.env.NODE_ENV !== 'testing') {
opn(uri)
}
})

61
build/utils.js Normal file
View File

@ -0,0 +1,61 @@
var path = require('path')
var config = require('../config')
var ExtractTextPlugin = require('extract-text-webpack-plugin')
exports.assetsPath = function (_path) {
var assetsSubDirectory = process.env.NODE_ENV === 'production'
? config.build.assetsSubDirectory
: config.dev.assetsSubDirectory
return path.posix.join(assetsSubDirectory, _path)
}
exports.cssLoaders = function (options) {
options = options || {}
// generate loader string to be used with extract text plugin
function generateLoaders (loaders) {
var sourceLoader = loaders.map(function (loader) {
var extraParamChar
if (/\?/.test(loader)) {
loader = loader.replace(/\?/, '-loader?')
extraParamChar = '&'
} else {
loader = loader + '-loader'
extraParamChar = '?'
}
return loader + (options.sourceMap ? extraParamChar + 'sourceMap' : '')
}).join('!')
// Extract CSS when that option is specified
// (which is the case during production build)
if (options.extract) {
return ExtractTextPlugin.extract('vue-style-loader', sourceLoader)
} else {
return ['vue-style-loader', sourceLoader].join('!')
}
}
// http://vuejs.github.io/vue-loader/en/configurations/extract-css.html
return {
css: generateLoaders(['css']),
postcss: generateLoaders(['css']),
less: generateLoaders(['css', 'less']),
sass: generateLoaders(['css', 'sass?indentedSyntax']),
scss: generateLoaders(['css', 'sass']),
stylus: generateLoaders(['css', 'stylus']),
styl: generateLoaders(['css', 'stylus'])
}
}
// Generate loaders for standalone style files (outside of .vue)
exports.styleLoaders = function (options) {
var output = []
var loaders = exports.cssLoaders(options)
for (var extension in loaders) {
var loader = loaders[extension]
output.push({
test: new RegExp('\\.' + extension + '$'),
loader: loader
})
}
return output
}

View File

@ -0,0 +1,89 @@
var path = require('path')
var config = require('../config')
var utils = require('./utils')
var projectRoot = path.resolve(__dirname, '../')
var env = process.env.NODE_ENV
// check env & config/index.js to decide weither to enable CSS Sourcemaps for the
// various preprocessor loaders added to vue-loader at the end of this file
var cssSourceMapDev = (env === 'development' && config.dev.cssSourceMap)
var cssSourceMapProd = (env === 'production' && config.build.productionSourceMap)
var useCssSourceMap = cssSourceMapDev || cssSourceMapProd
module.exports = {
entry: {
app: './src/main.js'
},
output: {
path: config.build.assetsRoot,
publicPath: process.env.NODE_ENV === 'production' ? config.build.assetsPublicPath : config.dev.assetsPublicPath,
filename: '[name].js'
},
resolve: {
extensions: ['', '.js', '.vue'],
fallback: [path.join(__dirname, '../node_modules')],
alias: {
'vue$': 'vue/dist/vue.common.js',
'src': path.resolve(__dirname, '../src'),
'assets': path.resolve(__dirname, '../src/assets'),
'components': path.resolve(__dirname, '../src/components'),
'common': path.resolve(__dirname, '../src/common'),
'img': path.resolve(__dirname, '../resource/img')
}
},
resolveLoader: {
fallback: [path.join(__dirname, '../node_modules')]
},
module: {
preLoaders: [{
test: /\.vue$/,
loader: 'eslint',
include: projectRoot,
exclude: /node_modules/
}, {
test: /\.js$/,
loader: 'eslint',
include: projectRoot,
exclude: /node_modules/
}],
loaders: [{
test: /\.vue$/,
loader: 'vue'
}, {
test: /\.js$/,
loader: 'babel',
include: projectRoot,
exclude: /node_modules/
}, {
test: /\.json$/,
loader: 'json'
}, {
test: /\.(png|jpe?g|gif|svg)(\?.*)?$/,
loader: 'url',
query: {
limit: 10000,
name: utils.assetsPath('img/[name].[hash:7].[ext]')
}
}, {
test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/,
loader: 'url',
query: {
limit: 10000,
name: utils.assetsPath('fonts/[name].[hash:7].[ext]')
}
}]
},
eslint: {
formatter: require('eslint-friendly-formatter')
},
vue: {
loaders: utils.cssLoaders({
sourceMap: useCssSourceMap
}),
postcss: [
require('autoprefixer')({
browsers: ['last 2 versions']
})
]
}
}

34
build/webpack.dev.conf.js Normal file
View File

@ -0,0 +1,34 @@
var config = require('../config')
var webpack = require('webpack')
var merge = require('webpack-merge')
var utils = require('./utils')
var baseWebpackConfig = require('./webpack.base.conf')
var HtmlWebpackPlugin = require('html-webpack-plugin')
// add hot-reload related code to entry chunks
Object.keys(baseWebpackConfig.entry).forEach(function (name) {
baseWebpackConfig.entry[name] = ['./build/dev-client'].concat(baseWebpackConfig.entry[name])
})
module.exports = merge(baseWebpackConfig, {
module: {
loaders: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap })
},
// eval-source-map is faster for development
devtool: '#eval-source-map',
plugins: [
new webpack.DefinePlugin({
'process.env': config.dev.env
}),
// https://github.com/glenjamin/webpack-hot-middleware#installation--usage
new webpack.optimize.OccurenceOrderPlugin(),
new webpack.HotModuleReplacementPlugin(),
new webpack.NoErrorsPlugin(),
// https://github.com/ampedandwired/html-webpack-plugin
new HtmlWebpackPlugin({
filename: 'index.html',
template: 'index.html',
inject: true
})
]
})

View File

@ -0,0 +1,98 @@
var path = require('path')
var config = require('../config')
var utils = require('./utils')
var webpack = require('webpack')
var merge = require('webpack-merge')
var baseWebpackConfig = require('./webpack.base.conf')
var ExtractTextPlugin = require('extract-text-webpack-plugin')
var HtmlWebpackPlugin = require('html-webpack-plugin')
var env = config.build.env
var webpackConfig = merge(baseWebpackConfig, {
module: {
loaders: utils.styleLoaders({ sourceMap: config.build.productionSourceMap, extract: true })
},
devtool: config.build.productionSourceMap ? '#source-map' : false,
output: {
path: config.build.assetsRoot,
filename: utils.assetsPath('js/[name].[chunkhash].js'),
chunkFilename: utils.assetsPath('js/[id].[chunkhash].js')
},
vue: {
loaders: utils.cssLoaders({
sourceMap: config.build.productionSourceMap,
extract: true
})
},
plugins: [
// http://vuejs.github.io/vue-loader/en/workflow/production.html
new webpack.DefinePlugin({
'process.env': env
}),
new webpack.optimize.UglifyJsPlugin({
compress: {
warnings: false
}
}),
new webpack.optimize.OccurrenceOrderPlugin(),
// extract css into its own file
new ExtractTextPlugin(utils.assetsPath('css/[name].[contenthash].css')),
// generate dist index.html with correct asset hash for caching.
// you can customize output by editing /index.html
// see https://github.com/ampedandwired/html-webpack-plugin
new HtmlWebpackPlugin({
filename: config.build.index,
template: 'index.html',
inject: true,
minify: {
removeComments: true,
collapseWhitespace: true,
removeAttributeQuotes: true
// more options:
// https://github.com/kangax/html-minifier#options-quick-reference
},
// necessary to consistently work with multiple chunks via CommonsChunkPlugin
chunksSortMode: 'dependency'
}),
// split vendor js into its own file
new webpack.optimize.CommonsChunkPlugin({
name: 'vendor',
minChunks: function (module, count) {
// any required modules inside node_modules are extracted to vendor
return (
module.resource &&
/\.js$/.test(module.resource) &&
module.resource.indexOf(
path.join(__dirname, '../node_modules')
) === 0
)
}
}),
// extract webpack runtime and module manifest to its own file in order to
// prevent vendor hash from being updated whenever app bundle is updated
new webpack.optimize.CommonsChunkPlugin({
name: 'manifest',
chunks: ['vendor']
})
]
})
if (config.build.productionGzip) {
var CompressionWebpackPlugin = require('compression-webpack-plugin')
webpackConfig.plugins.push(
new CompressionWebpackPlugin({
asset: '[path].gz[query]',
algorithm: 'gzip',
test: new RegExp(
'\\.(' +
config.build.productionGzipExtensions.join('|') +
')$'
),
threshold: 10240,
minRatio: 0.8
})
)
}
module.exports = webpackConfig

6
config/dev.env.js Normal file
View File

@ -0,0 +1,6 @@
var merge = require('webpack-merge')
var prodEnv = require('./prod.env')
module.exports = merge(prodEnv, {
NODE_ENV: '"development"'
})

32
config/index.js Normal file
View File

@ -0,0 +1,32 @@
// see http://vuejs-templates.github.io/webpack for documentation.
var path = require('path')
module.exports = {
build: {
env: require('./prod.env'),
index: path.resolve(__dirname, '../dist/index.html'),
assetsRoot: path.resolve(__dirname, '../dist'),
assetsSubDirectory: 'static',
assetsPublicPath: '/',
productionSourceMap: true,
// Gzip off by default as many popular static hosts such as
// Surge or Netlify already gzip all static assets for you.
// Before setting to `true`, make sure to:
// npm install --save-dev compression-webpack-plugin
productionGzip: false,
productionGzipExtensions: ['js', 'css']
},
dev: {
env: require('./dev.env'),
port: 8080,
assetsSubDirectory: 'static',
assetsPublicPath: '/',
proxyTable: {},
// CSS Sourcemaps off by default because relative paths are "buggy"
// with this option, according to the CSS-Loader README
// (https://github.com/webpack/css-loader#sourcemaps)
// In our experience, they generally work as expected,
// just be aware of this issue when enabling this option.
cssSourceMap: false
}
}

3
config/prod.env.js Normal file
View File

@ -0,0 +1,3 @@
module.exports = {
NODE_ENV: '"production"'
}

1379
data.json Normal file

File diff suppressed because it is too large Load Diff

13
index.html Normal file
View File

@ -0,0 +1,13 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>sell</title>
<meta name="viewport" content="width=device-width,initial-scale=1.0,maximum-scale=1.0,minimum-scale=1.0,user-scalable=no">
<link rel="stylesheet" href="static/css/reset.css">
</head>
<body>
<div id="app"></div>
<!-- built files will be auto injected -->
</body>
</html>

76
package.json Normal file
View File

@ -0,0 +1,76 @@
{
"name": "sell",
"version": "1.0.0",
"description": "sell app",
"author": "<simonzhangr@foxmail.com>",
"private": true,
"scripts": {
"dev": "node build/dev-server.js",
"build": "node build/build.js",
"lint": "eslint --ext .js,.vue src"
},
"dependencies": {
"axios": "^0.15.3",
"babel-runtime": "^6.9.0",
"better-scroll": "^0.1.10",
"eslint-config-standard": "^6.2.1",
"fastclick": "^1.0.6",
"iscroll": "^5.2.0",
"moment": "^2.17.1",
"stylus": "^0.54.5",
"v-tap": "^2.0.2",
"vue": "^2.1.0",
"vue-resource": "^1.0.3",
"vue-router": "^2.1.1",
"vue-scroll": "^2.0.1",
"vuex": "^2.1.1",
"watchpack": "^1.2.0",
"webpack": "^1.14.0"
},
"devDependencies": {
"autoprefixer": "^6.4.0",
"babel-core": "^6.0.0",
"babel-eslint": "^7.0.0",
"babel-loader": "^6.0.0",
"babel-plugin-transform-runtime": "^6.0.0",
"babel-preset-es2015": "^6.0.0",
"babel-preset-stage-2": "^6.0.0",
"babel-register": "^6.0.0",
"better-scroll": "^0.1.10",
"chalk": "^1.1.3",
"connect-history-api-fallback": "^1.1.0",
"css-loader": "^0.25.0",
"eslint": "^3.7.1",
"eslint-config-standard": "^6.1.0",
"eslint-friendly-formatter": "^2.0.5",
"eslint-loader": "^1.5.0",
"eslint-plugin-html": "^1.3.0",
"eslint-plugin-promise": "^3.5.0",
"eslint-plugin-standard": "^2.0.1",
"eventsource-polyfill": "^0.9.6",
"express": "^4.13.3",
"extract-text-webpack-plugin": "^1.0.1",
"file-loader": "^0.9.0",
"function-bind": "^1.0.2",
"html-webpack-plugin": "^2.8.1",
"http-proxy-middleware": "^0.17.2",
"json-loader": "^0.5.4",
"opn": "^4.0.2",
"ora": "^0.3.0",
"semver": "^5.3.0",
"shelljs": "^0.7.4",
"stylus-loader": "^2.1.1",
"url-loader": "^0.5.7",
"vue-loader": "^10.0.0",
"vue-style-loader": "^1.0.0",
"vue-template-compiler": "^2.1.0",
"webpack": "^1.13.2",
"webpack-dev-middleware": "^1.8.3",
"webpack-hot-middleware": "^2.12.2",
"webpack-merge": "^0.14.1"
},
"engines": {
"node": ">= 4.0.0",
"npm": ">= 3.0.0"
}
}

64
src/App.vue Normal file
View File

@ -0,0 +1,64 @@
<style lang="stylus" rel="stylesheet/stylus">
@import 'common/stylus/index'
.tab
display:flex
width:100%
height:40px
line-height:40px
border-1px(rgba(7,17,27,0.1))
.tab-item
flex:1
text-align:center
a
display:block
font-size:14px
color rgb(77,85,93)
&.active
font-size 14px
color rgb(240,20,20)
</style>
<template>
<div>
<v-header :seller="seller"></v-header>
<div class="tab">
<div class="tab-item">
<router-link to="/goods">商品</router-link>
</div>
<div class="tab-item">
<router-link to="/ratings">评论</router-link>
</div>
<div class="tab-item">
<router-link to="/seller">商家</router-link>
</div>
</div>
<keep-alive>
<router-view :seller="seller"></router-view>
</keep-alive>
</div>
</template>
<script>
import header from 'components/header/header'
import axios from 'axios'
const ERR_OK = 0
export default {
data() {
return {
seller: {}
}
},
created() {
axios.get('static/data.json').then((res) => {
this.seller = res.data.seller
})
},
components: {
'v-header': header
}
}
</script>

Binary file not shown.

View File

@ -0,0 +1,20 @@
<?xml version="1.0" standalone="no"?>
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd" >
<svg xmlns="http://www.w3.org/2000/svg">
<metadata>Generated by IcoMoon</metadata>
<defs>
<font id="sell-icon" horiz-adv-x="1024">
<font-face units-per-em="1024" ascent="960" descent="-64" />
<missing-glyph horiz-adv-x="1024" />
<glyph unicode="&#x20;" horiz-adv-x="512" d="" />
<glyph unicode="&#x1f4;" glyph-name="add_circle" d="M726 406v84h-172v172h-84v-172h-172v-84h172v-172h84v172h172zM512 874q176 0 301-125t125-301-125-301-301-125-301 125-125 301 125 301 301 125z" />
<glyph unicode="&#xe900;" glyph-name="arrow_lift" d="M246.784 491.691l443.051 442.709c24.917 24.917 62.464 24.917 87.381 0s24.917-62.464 0-87.381l-399.36-399.36 399.36-399.36c24.917-24.917 24.917-62.464 0-87.381s-62.464-24.917-87.381 0l-443.051 443.392c-24.917 24.917-24.917 62.464 0 87.381z" />
<glyph unicode="&#xe901;" glyph-name="check_circle" d="M426 234l384 384-60 62-324-324-152 152-60-60zM512 874q176 0 301-125t125-301-125-301-301-125-301 125-125 301 125 301 301 125z" />
<glyph unicode="&#xe902;" glyph-name="close" d="M810 686l-238-238 238-238-60-60-238 238-238-238-60 60 238 238-238 238 60 60 238-238 238 238z" />
<glyph unicode="&#xe903;" glyph-name="favorite" d="M512 50l-62 56c-220 200-364 330-364 492 0 132 102 234 234 234 74 0 146-36 192-90 46 54 118 90 192 90 132 0 234-102 234-234 0-162-144-294-364-494z" />
<glyph unicode="&#xe904;" glyph-name="keyboard_arrow_right" d="M366 262l196 196-196 196 60 60 256-256-256-256z" />
<glyph unicode="&#xe905;" glyph-name="remove_circle_outline" d="M512 106q140 0 241 101t101 241-101 241-241 101-241-101-101-241 101-241 241-101zM512 874q176 0 301-125t125-301-125-301-301-125-301 125-125 301 125 301 301 125zM298 490h428v-84h-428v84z" />
<glyph unicode="&#xe906;" glyph-name="shopping_cart" d="M726 192q34 0 59-26t25-60-25-59-59-25-60 25-26 59 26 60 60 26zM42 874h140l40-84h632q18 0 30-13t12-31q0-10-6-20l-152-276q-24-44-74-44h-318l-38-70-2-6q0-10 10-10h494v-86h-512q-34 0-59 26t-25 60q0 20 10 40l58 106-154 324h-86v84zM298 192q34 0 60-26t26-60-26-59-60-25-59 25-25 59 25 60 59 26z" />
<glyph unicode="&#xe907;" glyph-name="thumb_down" d="M810 832h172v-512h-172v512zM640 832q34 0 60-26t26-60v-426q0-34-26-60l-280-282-46 46q-18 18-18 44v14l42 196h-270q-34 0-60 25t-26 59l2 4h-2v82q0 16 6 32l130 300q20 52 78 52h384z" />
<glyph unicode="&#xe908;" glyph-name="thumb_up" d="M982 534l-2-4h2v-82q0-16-6-32l-130-300q-20-52-78-52h-384q-34 0-60 26t-26 60v426q0 34 26 60l280 282 46-46q18-18 18-44v-14l-42-196h270q34 0 60-25t26-59zM42 64v512h172v-512h-172z" />
</font></defs></svg>

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

Binary file not shown.

View File

@ -0,0 +1,13 @@
body,html{
font-weight: 200
}
.clearfix
display: inline-block;
&:after
display: block;
content: ''
height: 0
line-height: 0
clear: both;
visibility: hidden;

View File

@ -0,0 +1,55 @@
@font-face
font-family: 'sell-icon'
src: url('common/fonts/sell-icon.eot?nowozp')
src: url('common/fonts/sell-icon.eot?nowozp#iefix') format('embedded-opentype'),
url('common/fonts/sell-icon.ttf?nowozp') format('truetype'),
url('common/fonts/sell-icon.woff?nowozp') format('woff'),
url('common/fonts/sell-icon.svg?nowozp#sell-icon') format('svg')
font-weight: normal
font-style: normal
[class^="icon-"], [class*=" icon-"]
/* use !important to prevent issues with browser extensions that change fonts */
font-family: 'sell-icon' !important
speak: none
font-style: normal
font-weight: normal
font-variant: normal
text-transform: none
line-height: 1
/* Better Font Rendering =========== */
-webkit-font-smoothing: antialiased
-moz-osx-font-smoothing: grayscale
.icon-arrow_lift:before
content: "\e900"
.icon-check_circle:before
content: "\e901"
.icon-close:before
content: "\e902"
.icon-favorite:before
content: "\e903"
.icon-keyboard_arrow_right:before
content: "\e904"
.icon-remove_circle_outline:before
content: "\e905"
.icon-shopping_cart:before
content: "\e906"
.icon-thumb_down:before
content: "\e907"
.icon-thumb_up:before
content: "\e908"
.icon-add_circle:before
content: "\1f4"

View File

@ -0,0 +1,3 @@
@import "./base"
@import "./icon"
@import "./mixin"

View File

@ -0,0 +1,16 @@
border-1px($color)
position relative
&:after
display block
position: absolute;
left: 0
bottom: 0
width: 100%
border-top: 1px solid $color
content: ''
bg-image($url)
background-image: url('img/'+$url+'@2x.png')
@media(-webkit-min-device-pixel-ratio:3),(min-device-pixel-ratio:3){
background-image: url('img/'+$url+'@3x.png')
}

View File

@ -0,0 +1,30 @@
<template lang="html">
<transition name="fade-backdrop">
<div class="backdrop" v-show="isShow"></div>
</transition>
</template>
<script>
export default {
props: {
isShow: Boolean
}
}
</script>
<style lang="stylus">
.backdrop
position fixed
top 0
bottom 0
left 0
right 0
background rgba(7,17,27,0.6)
backdrop-filter blur(10px)
z-index 40
&.fade-backdrop-enter-active,&.fade-backdrop-leave-active
transition opacity 0.5s
&.fade-backdrop-enter,&.fade-backdrop-leave-active
opacity 0
</style>

View File

@ -0,0 +1,86 @@
<template lang="html">
<div class="cartcontrol">
<transition name="fadeRotate">
<div class="cart-decrease" v-show="food.count>0" @click.stop.prevent="decreaseCart()">
<span class="icon-remove_circle_outline inner"></span>
</div>
</transition>
<div class="cart-count" v-show="food.count>0">
{{food.count}}
</div>
<div class="cart-add" @click.stop.prevent="addCart($event)">
<i class="icon-add_circle"></i>
</div>
</div>
</template>
<script>
import Vue from 'vue'
export default {
props: {
food: Object
},
methods: {
addCart(event) {
console.log(event.target);
if (!event._constructed) {
return
}
if (!this.food.count) {
Vue.set(this.food, 'count', 0)
}
this.food.count++;
this.$root.eventHub.$emit('cart.add', event.target)
},
decreaseCart() {
if (!event._constructed || !this.food.count) {
return
}
this.food.count--;
}
}
}
</script>
<style lang="stylus">
.cartcontrol
.cart-decrease
display inline-block
padding 6px
transition: all .4s linear
.inner
line-height 24px
font-size 24px
color rgb(0,160,220)
transition all 0.4s linear
&.fadeRotate-enter-active, &.fadeRotate-leave-active
transform translate3d(0,0,0)
.inner
display inline-block
transform rotate(0)
&.fadeRotate-enter, &.fadeRotate-leave-active
opacity: 0
transform translate3d(24px,0,0)
.inner
transform rotate(180deg)
.cart-count
display inline-block
vertical-align top
font-size 10px
color rgb(147,153,159)
line-height 24px
text-align center
padding 6px 0
.cart-add
display inline-block
vertical-align top
font-size 24px
color rgb(0,160,220)
line-height 24px
padding 6px
</style>

View File

@ -0,0 +1,26 @@
<template lang="html">
<div class="food" v-show="showDetails">
</div>
</template>
<script>
export default {
props: {
food: Object
}
}
</script>
<style lang="stylus" scoped>
.food
position fixed
left 0
top 0
right 0
bottom 48px
width 100%
background white
z-index 30
</style>

View File

@ -0,0 +1,323 @@
<template lang="html">
<transition name="move">
<div class="detailWrapper" ref="detailWrapper" v-show="showDetail">
<div class="foodDetail">
<div class="back" @click="showToggle()">
<i class="icon-arrow_lift"></i>
</div>
<img :src="food.image" height="425" width="100%">
<div class="info">
<div class="title">{{food.name}}</div>
<div class="desc">
<span>月售{{food.sellCount}}</span>
<span>好评率{{food.rating}}%</span>
</div>
<div class="price">
<span class="unit"></span>{{food.price}}
<span class="oldPrice" v-show="food.oldPrice">{{food.oldPrice}}</span>
</div>
<div class="shopCart">
<transition name="fade">
<div class="text" @click="addCart($event)" v-show="!food.count">加入购物车</div>
</transition>
</div>
<cartcontrol :food="food"></cartcontrol>
</div>
<div class="divider"></div>
<div class="desc">
<div class="title">商品介绍</div>
<div class="content">{{food.info}}</div>
</div>
<div class="divider"></div>
<div class="evaluation">
<div class="title">
商品评价
</div>
<div class="classify">
<span v-for="(item,index) in classifyArr" class="item" :class="{'active':item.active,'bad':index==2,'badActive':item.active&&index==2}" @click="filterEvel(item)">
{{item.name}}<span class="count">{{item.count}}</span>
</span>
</div>
<div class="switch" @click="evelflag=!evelflag">
<span class="icon-check_circle" :class="{'on':evelflag}"></span>
<span class="text">只看有内容的评价</span>
</div>
<div class="evel-list">
<ul>
<li class="evel" v-for="evel in evelArr">
<div class="userInfo">
<div class="time">{{evel.rateTime | time}}</div>
<div class="user">
<span>{{evel.username}}</span>
<span class="avatar"><img :src="evel.avatar" width="12" height="12"></span>
</div>
</div>
<div class="content">
<span class="icon" :class="evel.rateType?'icon-thumb_down':'icon-thumb_up'"></span>
<span class="text">{{evel.text}}</span>
</div>
</li>
</ul>
</div>
</div>
</div>
</div>
</transition>
</template>
<script>
import '../../filter/time.js'
import BScroll from 'better-scroll'
import cartcontrol from 'components/cartcontrol/cartcontrol'
export default {
components: {
cartcontrol
},
props: {
food: Object
},
data() {
return {
showDetail: false,
classifyArr: [{
name: '全部',
count: this.food.ratings.length,
active: true
}, {
name: '推荐',
count: this.food.ratings.filter((data) => data.rateType === 0).length,
active: false
}, {
name: '吐槽',
count: this.food.ratings.filter((data) => data.rateType).length,
active: false
}],
evelflag: true
}
},
computed: {
evelArr() {
let selectIndex = 0
this.classifyArr.forEach((data, index) => {
if (data.active) {
selectIndex = index
}
})
if (this.detailWrapper) {
this.$nextTick(() => {
this.detailWrapper.refresh()
})
}
return selectIndex ? this.food.ratings.filter((data) => this.evelflag ? data.rateType === selectIndex - 1 && data.text : data.rateType === selectIndex - 1) : this.food.ratings.filter((data) => this.evelflag ? data.text : true)
}
},
methods: {
showToggle() {
this.showDetail = !this.showDetail
if (this.showDetail) {
this.$nextTick(() => {
this._initScroll()
})
}
},
_initScroll() {
if (!this.detailWrapper) {
this.detailWrapper = new BScroll(this.$refs.detailWrapper, {
click: true
});
} else {
this.detailWrapper.refresh()
}
},
addCart(event) {
if (!event._constructed) {
return
}
this.$set(this.food, 'count', 1)
this.$root.eventHub.$emit('cart.add', event.target)
},
filterEvel(item) {
this.classifyArr.forEach((data) => {
data.active = false
})
item.active = true
}
}
}
</script>
<style lang="stylus" scoped>
.detailWrapper
position fixed
left 0
top 0
bottom 48px
width 100%
background white
transition all 0.4s ease
&.move-enter-avtive,&.move-leave-active{
transform translate3d(0,0,0)
}
&.move-enter,&.move-leave-active{
transform translate3d(100%,0,0)
}
.foodDetail
.back
position absolute
color white
top 12px
left 6px
font-size 20px
padding 10px
.info
position relative
box-sizing border-box
width 100%
padding 18px
.title
font-size 14px
font-weight 700
color rgb(7,17,27)
line-height 14px
.desc
display flex
padding 0
padding-top 8px
font-size 10px
color rgb(147,153,159)
line-height 10px
span:last-child
padding-left 12px
.price
display flex
padding-top 18px
font-size 14px
font-weight 700
color rgb(240,20,20)
line-height 24px
.unit
font-size 10px
font-weight normal
.oldPrice
padding-left 12px
font-size 10px
font-weight normal
color rgb(147,153,159)
line-height 24px
.shopCart
position absolute
right 18px
bottom 18px
height 24px
text-align center
z-index 2
.text
box-sizing border-box
height 100%
line-height 24px
color white
font-size 10px
padding 0 12px
border-radius 12px
background rgb(0,160,220)
&.fade-enter-active,&.fade-leave-active{
transition opacity .2s
}
&.fade-enter,&.fade-leave-active{
opacity 0
}
.cartcontrol
position absolute
right 12px
bottom 12px
.desc
padding 18px
.title
font-size 14px
font-weight 500
color #07111b
margin-bottom 6px
.content
font-size 12px
font-weight 200
color rgb(77,85,93)
line-height 24px
padding 0 8px
.evaluation
padding 18px 0
position relative
.title
padding-left 18px
font-size: 14px
font-weight 500
color: #07111b
.classify
padding 18px 0
margin 0 18px
border-bottom 1px solid rgba(7,17,27,0.1)
.item
display inline-block
font-size 12px
padding 8px 12px
line-height 16px
background rgba(0,160,220,0.2)
color rgb(77,85,95)
margin-right 8px
.count
font-size 8px
padding-left 2px
&.active
color white
background rgb(0,169,220)
&.bad
background rgba(77,85,93,0.2)
&.badActive
background #4d555d
.switch
font-size 12px
width 100%
padding 12px 0 12px 18px
color rgb(147,153,159)
border-bottom 1px solid rgba(7,17,27,0.1)
.icon-check_circle
font-size 24px
vertical-align middle
&.on
color #00c850
.evel-list
margin 0 18px
.evel
padding 16px 0
border-bottom 1px solid rgba(7,17,27,0.1)
.userInfo
display flex
color rgb(147,153,159)
font-size 10px
line-height 12px
.time
flex 1
.user
flex 1
text-align right
.avatar
img
padding-left 6px
border-radius 50%
.content
padding-top 6px
.icon
font-size 12px
line-height 24px
&.icon-thumb_up
color rgb(0,160,220)
&.icon-thumb_down
color rgb(147,153,159)
.text
font-size 12px
color rgb(7,17,27)
line-height 16px
padding-left 4px
</style>

View File

@ -0,0 +1,256 @@
<template lang="html">
<div class="goods">
<div class="menu-wrapper" ref="menuWrapper">
<ul>
<li v-for="(item,index) in goods" @click="menuClick(index,$event)" :class="index==menuCurrentIndex?'menu-item-selected':'menu-item'">
<span class="text">
<iconMap v-show="item.type>0" :iconType="item.type"></iconMap>
{{item.name}}
</span>
</li>
</ul>
</div>
<div class="foods-wrapper" id="wrapper" ref="foodsWrapper">
<ul>
<li v-for="item in goods" class="food-list food-list-hook">
<h1>{{item.name}}</h1>
<ul>
<li v-for="food in item.foods" class="food-item" @click="goDetail(food)">
<div class="icon">
<img width="57" height="57" :src="food.icon"/>
</div>
<div class="content">
<h2>{{food.name}}</h2>
<p class="description" v-show="food.description">{{food.description}}</p>
<div class="sell-info">
<span class="sellCount">月售{{food.sellCount}}</span>
<span class="rating">好评率{{food.rating}}%</span>
</div>
<div class="price">
<span class="newPrice"><span class="unit"></span>{{food.price}}</span>
<span v-show="food.oldPrice" class="oldPrice">{{food.oldPrice}}</span>
</div>
<div class="cartcontrol-wrapper">
<cartcontrol :food="food"></cartcontrol>
</div>
</div>
</li>
</ul>
</li>
</ul>
</div>
<shopCart :deliveryPrice="seller.deliveryPrice" :minPrice = "seller.minPrice" :selectFoods="selectFoods"></shopCart>
<foodDetail :food="selectedFood" v-if="selectedFood" ref="myFood"></foodDetail>
</div>
</template>
<script>
import iconMap from 'components/iconMap/iconMap'
import BScroll from 'better-scroll'
import shopCart from 'components/shopCart/shopCart'
import cartcontrol from 'components/cartcontrol/cartcontrol'
import foodDetail from 'components/foodDetail/foodDetail'
import axios from 'axios'
import Vue from 'vue'
const ERR_OK = 0
const eventHub = new Vue()
export default {
props: {
seller: Object
},
created() {
axios.get('static/data.json').then((res) => {
this.goods = res.data.goods
this.$nextTick(() => {
this._initScroll(); // scroll
this._calculateHeight(); //
})
});
},
data() {
return {
goods: [],
listHeight: [],
foodsScrollY: 0,
selectedFood: ''
}
},
computed: {
menuCurrentIndex() {
for (let i = 0, l = this.listHeight.length; i < l; i++) {
let topHeight = this.listHeight[i]
let bottomHeight = this.listHeight[i + 1]
if (!bottomHeight || (this.foodsScrollY >= topHeight && this.foodsScrollY < bottomHeight)) {
return i
}
}
return 0
},
selectFoods() {
let foods = []
this.goods.forEach((good) => {
good.foods.forEach((food) => {
if (food.count) {
foods.push(food)
}
})
})
return foods
}
},
methods: {
_initScroll() {
this.menuWrapper = new BScroll(this.$refs.menuWrapper, {
click: true
});
this.foodsScroll = new BScroll(this.$refs.foodsWrapper, {
click: true,
probeType: 3
});
//
this.foodsScroll.on('scroll', (pos) => {
this.foodsScrollY = Math.abs(Math.round(pos.y))
})
},
_calculateHeight() {
let foodList = this.$refs.foodsWrapper.querySelectorAll('.food-list-hook')
let height = 0
this.listHeight.push(height)
for (let i = 0, l = foodList.length; i < l; i++) {
let item = foodList[i]
height += item.clientHeight
this.listHeight.push(height)
}
},
menuClick(index, event) {
if (!event._constructed) {
return
}
this.foodsScroll.scrollTo(0, -this.listHeight[index], 300)
},
goDetail(food) {
this.selectedFood = food
this.$nextTick(() => {
this.$refs.myFood.showToggle()
})
}
},
components: {
iconMap,
shopCart,
cartcontrol,
foodDetail
}
}
</script>
<style lang="stylus">
@import '../../common/stylus/mixin'
.goods
display flex
position absolute
top 174px
bottom 46px
width 100%
overflow hidden
.menu-wrapper
flex 0 0 80px
width 80px
background #F3F5BD
margin-top: 2px;
.menu-item-selected
background white
font-weight 700
margin-top -1px
.menu-item,.menu-item-selected
position relative
display table
height 54px
line-height 14px
width 56px
padding 0 12px
&:last-child:after
content none
.menu-item:after
position: absolute
content: ''
left: 12px
width: 56px
bottom: 0
border-bottom: 1px solid rgba(7,17,27,0.1)
.text
display table-cell
vertical-align middle
font-size 12px
font-weight 200
white-space normal
line-height 14px
.iconMap
vertical-align middle
.foods-wrapper
flex 1
margin-top: 2px;
.food-list
h1
height 26px
line-height 26px
padding-left 12px
font-size 12px
color rgb(147,153,159)
background #f3f5f7
border-left 2px solid #d9dde1
.food-item
position relative
display flex
margin: 0 18px;
padding: 18px 0;
border-bottom 1px solid rgba(7,17,27,0.1)
.icon
flex 0 0 57px
&:last-child
border-bottom none
.content
flex 1
padding-left 10px
h2
margin 2px 0 8px 0
font-size 14px
line-height 14px
height 14px
font-weight 700
color rgb(7,17,27)
.sell-info,.description
font-size 10px
color rgb(147,153,159)
line-height 10px
.sellCount
margin-right 4px
.description
font-size 10px
margin-bottom 8px
line-height: 12px
.price
font-size 10px
font-weight 700
line-height 24px
.newPrice
font-size 14px
color rgb(240,20,20)
.unit
font-size 10px
font-weight normal
.oldPrice
text-decoration line-through
color rgb(147,153,159)
padding-left 4px
.cartcontrol-wrapper
position: absolute
right: 0
bottom 12px
z-index 20
</style>

View File

@ -0,0 +1,304 @@
<template lang="html">
<div class="header">
<div class="content-wrapper">
<div class="content">
<div class="title">
<span class="brand"></span>
<span class="name">{{seller.name}}</span>
</div>
<div class="description">
{{seller.description + ' / ' + seller.deliveryTime + '分钟送达'}}
</div>
<div class="supports" v-if="seller.supports">
<div class="supports_desc">
<span class="icon" :class="iconClassMap[seller.supports[0].type]"></span>
<span class="text">{{seller.supports[0].description}}</span>
</div>
</div>
</div>
<div class="support-count" v-if="seller.supports" @click="showDetails()">
<span class="count">{{seller.supports.length+'个'}}</span>
<i class="icon-keyboard_arrow_right"></i>
</div>
</div>
<div class="bulletin-wrapper" @click="showDetails()">
<span class="bulletin-title"></span>
<span class="bulletin-text">{{seller.bulletin}}</span>
<i class="icon-keyboard_arrow_right"></i>
</div>
<div class="background">
<img :src="seller.avatar" width="100%" height="100%"/>
</div>
<transition name="fade">
<div v-if="detailShow" class="detail">
<div class="detail-wrapper clearfix">
<div class="detail-main">
<h1 class="name">{{seller.name}}</h1>
<div class="star-wrapper">
<star :size="48" :score="seller.score"></star>
</div>
<div class="title">
<div class="line"> </div>
<div class="text">优惠信息</div>
<div class="line"></div>
</div>
<ul v-if="seller.supports" class="supports">
<li class="support-item" v-for="item in seller.supports">
<span class="icon" :class="iconClassMap[item.type]"></span>
<span class="text">{{item.description}}</span>
</li>
</ul>
<div class="title">
<div class="line"> </div>
<div class="text">商家公告</div>
<div class="line"></div>
</div>
<div class="bulletin">{{seller.bulletin}}</div>
</div>
</div>
<div class="detail-close">
<i class="icon-close" @click="hideDetail()"></i>
</div>
</div>
</transition>
</div>
</template>
<script>
import star from 'components/star/star'
export default {
props: {
seller: {
type: Object
}
},
created() {
this.iconClassMap = ['decrease', 'discount', 'special', 'invoice', 'guarantee']
},
components: {
star
},
data() {
return {
detailShow: false
}
},
methods: {
showDetails() {
this.detailShow = true;
},
hideDetail() {
this.detailShow = false;
}
}
}
</script>
<style lang="stylus" rel="stylesheet/stylus">
@import '../../common/stylus/mixin'
.header
position relative
background: steelblue;
color #fff
blur:10px
overflow hidden
.content-wrapper
position relative
display flex
padding: 24px 12px 18px 24px
font-size 12px
.avatar
img
border-radius 2px
.content
margin: 0 auto;
.title
margin 2px 0 8px 0
font-size 16px
.brand
display inline-block
vertical-align top
width 30px
height 18px
bg-image('brand')
background-size 30px 18px
background-repeat no-repeat
.name
margin-left 6px
font-size 16px
line-height 18px
font-weight bold
.description
font-size 12px
margin-bottom 10px
.supports
.icon
display inline-block
vertical-align top
width 12px
height 12px
margin-right 4px
background-size 12px 12px
background-repeat no-repeat
&.decrease
bg-image('decrease_1')
&.discount
bg-image('discount_1')
&.guarantee
bg-image('guarantee_1')
&.invoice
bg-image('invoice_1')
&.special
bg-image('special_1')
.text
line-height 12px
font-size 10px
.support-count
position absolute
right 12px
bottom 18px
padding 0 8px
height 24px
line-height 24px
border-radius 14px
background-color rgba(0,0,0,0.2)
text-align center
.count
vertical-align top
font-size 10px
.icon-keyboard_arrow_right
font-size 10px
margin-left 2px
line-height 24px
.bulletin-wrapper
position relative
height 28px
line-height 28px
padding 0 22px 0 12px
white-space nowrap
overflow hidden
text-overflow ellipsis
background rgba(7,17,27,0.2)
.bulletin-title
display inline-block
vertical-align top
margin-top 8px
width 22px
height 12px
bg-image('bulletin')
background-size 100% 100%
background-repeat no-repeat
.bulletin-text
font-size 10px
vertical-align middle
margin 0 4px
.icon-keyboard_arrow_right
position absolute
font-size 10px
right 12px
top 8px
.background
position absolute
top 0
left 0
width 100%
height 100%
filter blur(10px)
z-index -1
.detail
position fixed
top 0
left 0
z-index 100
width 100%
height 100%
background rgba(7,17,27,0.8)
backdrop-filter blur(10px)
.detail-wrapper
min-height 100%
width 100%
.detail-main
margin-top 64px
padding-bottom 64px
.name
font-size 16px
font-weight 700
width 100%
color rgb(255,255,255)
line-height 16px
text-align center
.star-wrapper
margin 16px 11px 28px 0
text-align center
.title
display flex
width 80%
margin 0 auto 24px auto;
.line
display inline-block
flex 1
height 1px
background rgba(255,255,255,0.2)
margin auto
.text
padding 0 12px
font-size 14px
font-weight 700
.supports
padding 0 0 28px 36px
.support-item
color white
padding 0 6px 12px 16px
.text
vertical-align middle
font-size 12px
font-weight 200
color rgb(255,255,255)
line-height 12px
.icon
display inline-block
vertical-align top
width 16px
height 16px
margin-right 6px
background-size 100% 100%
background-repeat no-repeat
&.decrease
bg-image('decrease_2')
&.discount
bg-image('discount_2')
&.guarantee
bg-image('guarantee_2')
&.invoice
bg-image('invoice_2')
&.special
bg-image('special_2')
.bulletin
padding 0 48px
font-size 12px
font-weight 200
color rgb(255,255,255)
line-height 24px
.detail-close
position relative
width 32px
height 32px
margin -64px auto 0 auto
clear both
font-size 32px
color rgba(255,255,255,0.5)
&.fade-enter-active, &.fade-leave-active {
transition: opacity .5s
}
&.fade-enter, &.fade-leave-active {
opacity: 0
}
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

View File

@ -0,0 +1,35 @@
<template lang="html">
<span class="iconMap" :class="iconClassMap[iconType]"></span>
</template>
<script>
export default {
props: {
iconType: Number
},
created() {
this.iconClassMap = ['decrease', 'discount', 'special', 'invoice', 'guarantee']
}
}
</script>
<style lang="stylus" scoped>
@import '../../common/stylus/mixin'
.iconMap
display inline-block
background-size 100% 100%
background-repeat no-repeat
width 12px
height 12px
&.decrease
bg-image('decrease_4')
&.discount
bg-image('discount_4')
&.guarantee
bg-image('guarantee_4')
&.invoice
bg-image('invoice_4')
&.special
bg-image('special_4')
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@ -0,0 +1,290 @@
<style lang="stylus" scoped>
.ratingsWrapper
position: absolute
top: 174px
bottom: 0
left: 0
width: 100%
overflow: hidden
.ratings-content
.info
display flex
.mark
flex 0 0 138px
margin 18px 0
border-right 1px solid rgba(7,17,27,0.1)
text-align center
.num
font-size 24px
color rgb(255,153,0)
line-height 28px
.text
padding 6px 0 8px 0
font-size 12px
color rgb(7,17,27)
line-height 12px
.contrast
font-size 10px
color rgb(7,17,27)
line-height 10px
margin-bottom 6px
.stars
padding 18px 24px
.serviceScore,.foodScore,.deliveryTime
display flex
margin-bottom 8px
.text
font-size 12px
color rgb(7,17,27)
line-height 18px
margin-right 12px
.num
font-size 12px
line-height 18px
color rgb(255,153,0)
padding-left 12px
.deliveryTime
margin-bottom 0
.time
font-size 12px
color rgb(147,153,159)
line-height 18px
.evaluation
padding 18px 0
position relative
.classify
padding-bottom 18px
margin 0 18px
border-bottom 1px solid rgba(7,17,27,0.1)
.item
display inline-block
font-size 12px
padding 8px 12px
line-height 16px
background rgba(0,160,220,0.2)
color rgb(77,85,95)
margin-right 8px
.count
font-size 8px
padding-left 2px
&.active
color white
background rgb(0,169,220)
&.bad
background rgba(77,85,93,0.2)
&.badActive
background #4d555d
.switch
font-size 12px
width 100%
padding 12px 0 12px 18px
color rgb(147,153,159)
border-bottom 1px solid rgba(7,17,27,0.1)
.icon-check_circle
font-size 24px
vertical-align middle
&.on
color #00c850
.evel-list
.evel
display flex
padding 18px 0
margin 0 18px
border-bottom 1px solid rgba(7,17,27,0.1)
.avatar
flex 0 0 28px
margin-right 12px
img
border-radius 50%
.content
flex 1
.user
font-size 10px
color rgb(7,17,27)
line-height 12px
.rateTime
position absolute
font-weight 200
right 18px
color rgb(147,153,159)
.star-wrapper
font-size 0
padding-top 4px
margin-bottom 6px
.star
display inline-block
.deliveryTime
font-size 10px
padding-left 6px
font-weight 200
color rgb(147,153,159)
.text
font-size 12px
color rgb(7,17,27)
line-height 18px
.recommend
padding-top 4px
.icon
font-size 12px
color rgb(0,160,220)
line-height 16px
.dish
display inline-block
font-size 9px
color rgb(147,153,159)
line-height 16px
border 1px solid rgba(7,17,27,0.1)
padding 2px 6px
margin-right 8px
white-space normal
margin-top 4px
</style>
<template lang="html">
<div class="ratingsWrapper" ref="ratingsWrapper">
<div class="ratings-content">
<div class="info">
<div class="mark">
<div class="num">{{seller.score}}</div>
<div class="text">综合评分</div>
<div class="contrast">高于周边商家{{seller.rankRate}}%</div>
</div>
<div class="stars">
<div class="serviceScore">
<span class="text">服务态度</span>
<star :size="36" :score="seller.serviceScore"></star>
<span class="num">{{seller.serviceScore}}</span>
</div>
<div class="foodScore">
<span class="text">服务态度</span>
<star :size="36" :score="seller.foodScore"></star>
<span class="num">{{seller.foodScore}}</span>
</div>
<div class="deliveryTime">
<span class="text">送达时间</span>
<span class="time">{{seller.deliveryTime}}分钟</span>
</div>
</div>
</div>
<div class="divider"></div>
<div class="evaluation">
<div class="classify">
<span v-for="(item,index) in classifyArr" class="item" :class="{'active':item.active,'bad':index==2,'badActive':item.active&&index==2}" @click="filterEvel(item)">
{{item.name}}<span class="count">{{item.count}}</span>
</span>
</div>
<div class="switch" @click="evelflag=!evelflag">
<span class="icon-check_circle" :class="{'on':evelflag}"></span>
<span class="text">只看有内容的评价</span>
</div>
<div class="evel-list">
<ul>
<li class="evel" v-for="evel in evelArr">
<div class="avatar">
<img :src="evel.avatar" width="28" height="28">
</div>
<div class="content">
<div class="user">
<span class="name">{{evel.username}}</span>
<span class="rateTime">{{evel.rateTime | time}}</span>
</div>
<div class="star-wrapper">
<star :size="24" :score="evel.score"></star>
<span class="deliveryTime">{{evel.deliveryTime}}分钟送达</span>
</div>
<div class="text">
{{evel.text}}
</div>
<div class="recommend">
<span class="icon icon-thumb_up" v-show="evel.recommend.length"></span>
<span class="dish" v-for="dish in evel.recommend">{{dish}}</span>
</div>
</div>
</li>
</ul>
</div>
</div>
</div>
</div>
</template>
<script>
import axios from 'axios'
import star from 'components/star/star'
import BScroll from 'better-scroll'
export default {
components: {
star: star
},
data() {
return {
ratings: [],
seller: {},
classifyArr: [{
name: '全部',
count: 0,
active: true
}, {
name: '推荐',
count: 0,
active: false
}, {
name: '吐槽',
count: 0,
active: false
}],
evelflag: true
}
},
created() {
this._init()
},
computed: {
evelArr() {
let selectIndex = 0
this.classifyArr.forEach((data, index) => {
if (data.active) {
selectIndex = index
}
})
if (this.scroll) {
this.$nextTick(() => {
this.scroll.refresh()
})
}
return selectIndex ? this.ratings.filter((data) => this.evelflag ? data.rateType === selectIndex - 1 && data.text : data.rateType === selectIndex - 1) : this.ratings.filter((data) => this.evelflag ? data.text : true)
}
},
methods: {
_init() {
axios.get('static/data.json').then((res) => {
this.ratings = res.data.ratings
this.seller = res.data.seller
this._initClassifyArr()
this.$nextTick(() => {
this.scroll = new BScroll(this.$refs.ratingsWrapper, {
click: true
})
})
})
},
_initClassifyArr() {
this.classifyArr.forEach((data, index) => {
if (index) {
data.count = this.ratings.filter((temp) => temp.rateType === index - 1).length
} else {
data.count = this.ratings.length
}
})
},
filterEvel(item) {
this.classifyArr.forEach((data) => {
data.active = false
})
item.active = true
}
}
}
</script>

View File

@ -0,0 +1,253 @@
<style lang="stylus" scoped>
.seller-wrapper
position absolute
top 174px
bottom 0
left 0
width 100%
overflow hidden
.seller-content
.info
padding 18px 0
margin 0 18px
.title
padding-bottom 18px
border-bottom 1px solid rgba(7,17,27,0.1)
.text
font-size 14px
color rgb(7,17,27)
line-height 14px
.star-wrapper
padding-top 8px
font-size 0
.star
display inline-block
vertical-align top
.rate-count,.sell-count
display inline-block
font-size 10px
color rgb(77,85,93)
line-height 18px
.rate-count
padding 0 12px 0 8px
.collect
position absolute
top 18px
right 8px
width 50px
text-align center
.icon-favorite
font-size 24px
line-height 24px
color #d4d6d9
&.active
color rgb(240,20,20)
.text
display block
font-size 10px
color rgb(77,85,93)
line-height 10px
padding-top 4px
.remark
display flex
.block
flex 1
margin-top 18px
text-align center
border-right 1px solid rgba(7,17,27,0.1)
&:last-child
border none
h2
font-size 10px
color rgb(147,153,159)
line-height 10px
margin-bottom 4px
.content
font-size 10px
color rgb(7,17,27)
line-height 24px
font-weight 200
.num
position relative
top 2px
font-size 24px
.activities
padding-top 18px
.bulletin
margin 0 18px
border-bottom 1px solid rgba(7,17,27,0.1)
h1
font-size 14px
color #07111b
line-height 14px
.content
padding 8px 12px 16px 12px
font-size 12px
font-weight 200
color rgb(240,20,20)
line-height 24px
.supports
margin 0 18px
.item
padding 16px
border-bottom 1px solid rgba(7,17,27,0.1)
font-size 0
.iconMap
width 16px
height 16px
vertical-align top
margin-right 6px
.text
font-size 12px
font-weight 200
color rgb(7,17,27)
line-height 16px
.seller-imgs
margin 18px
white-space nowrap
overflow hidden
h1
font-size 14px
line-height 14px
margin-bottom 12px
img
margin-right 6px
.seller-info
h1
margin 0 18px
padding 18px 0 12px 0
border-bottom 1px solid rgba(7,17,27,0.1)
.info-list
.info
font-size 12px
font-weight 200
color rgb(7,17,27)
line-height 16px
padding 16px 12px
border-bottom 1px solid rgba(7,17,27,0.1)
&:last-child
border none
</style>
<template lang="html">
<div class="seller-wrapper" ref="sellerWrapper">
<div class="seller-content">
<div class="info">
<div class="title">
<div class="text">{{seller.name}}</div>
<div class="star-wrapper">
<star :size="36" :score="seller.score"></star>
<span class="rate-count">({{seller.ratingCount}})</span>
<span class="sell-count">月售{{seller.sellCount}}</span>
</div>
<div class="collect" @click="collectflag=!collectflag">
<span class="icon-favorite" :class="{'active':collectflag}"></span>
<span class="text">{{collectflag?'已收藏':'收藏'}}</span>
</div>
</div>
<div class="remark">
<div class="block">
<h2>起送价</h2>
<div class="content">
<span class="num">{{seller.minPrice}}</span>
</div>
</div>
<div class="block">
<h2>商家配送</h2>
<div class="content">
<span class="num">{{seller.deliveryPrice}}</span>
</div>
</div>
<div class="block">
<h2>平均配送时间</h2>
<div class="content">
<span class="num">{{seller.deliveryTime}}</span>分钟
</div>
</div>
</div>
</div>
<div class="divider"></div>
<div class="activities">
<div class="bulletin">
<h1>公告与活动</h1>
<div class="content">
{{seller.bulletin}}
</div>
</div>
</div>
<div class="supports">
<ul>
<li class="item" v-for="item in seller.supports">
<iconMap :iconType="item.type"></iconMap>
<span class="text">{{item.description}}</span>
</li>
</ul>
</div>
<div class="divider"></div>
<div class="seller-imgs">
<h1>商家实景</h1>
<div class="img-wrapper" ref="picsWrapper">
<div ref="picList">
<img v-for="pic in seller.pics" :src="pic" width="120" height="90">
</div>
</div>
</div>
<div class="divider"></div>
<div class="seller-info">
<h1>商家信息</h1>
<ul class="info-list">
<li class="info" v-for="info in seller.infos">{{info}}</li>
</ul>
</div>
</div>
</div>
</template>
<script>
import axios from 'axios'
import BScroll from 'better-scroll'
import star from 'components/star/star'
import iconMap from 'components/iconMap/iconMap'
export default {
components: {
star: star,
iconMap: iconMap
},
data() {
return {
seller: {},
collectflag: false
}
},
created() {
this._init()
},
methods: {
_init() {
axios.get('static/data.json').then((res) => {
this.seller = res.data.seller
this.$nextTick(() => {
this.sellerScroll = new BScroll(this.$refs.sellerWrapper, {
click: true
})
this._initPicScroll()
})
})
},
_initPicScroll() {
if (this.picsScroll) {
return
}
const PIC_WIDTH = 120
const MARGIN = 6
let picLen = this.seller.pics.length
this.$refs.picList.style.width = PIC_WIDTH * picLen + MARGIN * (picLen - 1) + 'px'
this.picsScroll = new BScroll(this.$refs.picsWrapper, {
scrollX: true
})
}
}
}
</script>

View File

@ -0,0 +1,393 @@
<template lang="html">
<div class="">
<div class="shopCart">
<div class="content">
<div class="content-left" @click="listToggle">
<div class="logo-wrapper">
<div class="badge" v-show="totalCount">
{{totalCount}}
</div>
<div class="logo" :class="{'active':totalPrice}">
<i class="icon-shopping_cart"></i>
</div>
</div>
<div class="price" :class="{'active':totalPrice}">
{{totalPrice}}
</div>
<div class="desc">
另需要配送费{{deliveryPrice}}
</div>
</div>
<div class="content-right" :class="{'enough':totalPrice>=minPrice}">
{{payDesc}}
</div>
</div>
<div class="ball-container">
<transition name="drop" v-on:before-enter="beforeEnter"
v-on:enter="enter" v-on:after-enter="afterEnter"
v-for="(ball,index) in balls">
<div class="ball" v-show="ball.show">
<div class="inner inner-hook"></div>
</div>
</transition>
</div>
<transition name="transHeight">
<div class="shopcart-list" v-show="listShow">
<div class="list-header">
<h1 class="title">购物车</h1>
<span class="empty" @click="setEmpty()">清空</span>
</div>
<div class="list-content" ref="foodlist">
<ul>
<li class="food" v-for="food in selectFoods">
<span class="name">{{food.name}}</span>
<div class="price">
<span>{{food.price * food.count}}</span>
</div>
<div class="cartcontrol-wrapper">
<cartcontrol :food="food"></cartcontrol>
</div>
</li>
</ul>
</div>
</div>
</transition>
</div>
<transition name="fade-backdrop">
<div class="backdrop" v-show="showBackdrop" @click="hideBackdrop"></div>
</transition>
</div>
</template>
<script>
import cartcontrol from 'components/cartcontrol/cartcontrol'
import backdrop from 'components/backdrop/backdrop'
import BScroll from 'better-scroll'
export default {
props: {
selectFoods: {
type: Array,
default: []
},
deliveryPrice: {
type: Number,
default: 0
},
minPrice: {
type: Number,
default: 0
}
},
data() {
return {
balls: [{
show: false
}, {
show: false
}, {
show: false
}, {
show: false
}, {
show: false
}],
dropBalls: [],
listShow: false
}
},
created() {
this.$root.eventHub.$on('cart.add', this.drop)
},
computed: {
showBackdrop() {
if (this.listShow && this.totalPrice) {
return true
}
this.listShow = false
return false
},
totalPrice() {
let total = 0
this.selectFoods.forEach((food) => {
if (food.count) {
total += food.price * food.count
}
})
return total
},
totalCount() {
let count = 0
this.selectFoods.forEach((food) => {
count += food.count
})
return count
},
leftAmount() {
if (this.minPrice - this.totalPrice > 0 && totalPrice) {
return true;
}
return false
},
payDesc() {
let diff = this.minPrice - this.totalPrice
if (!this.totalPrice) {
return `${this.totalPrice}起送`
} else if (diff > 0) {
return `还差¥${diff}`
} else {
return '去结算'
}
}
},
methods: {
drop(el) {
for (let i = 0, l = this.balls.length; i < l; i++) {
let ball = this.balls[i]
if (!ball.show) {
ball.show = true
ball.el = el
this.dropBalls.push(ball)
return
}
}
},
setEmpty() {
this.selectFoods.forEach((food) => {
food.count = 0
})
},
hideBackdrop() {
this.listShow = false
},
_initScroll() {
this.foodlistScroll = new BScroll(this.$refs.foodlist, {
click: true
});
},
listToggle() {
if (!this.selectFoods.length) {
return
}
this.listShow = !this.listShow
if (this.listShow) {
this.$nextTick(() => {
if (!this.foodlistScroll) {
this._initScroll()
} else {
this.foodlistScroll.refresh()
}
})
}
},
beforeEnter(el) {
let count = this.balls.length
while (count--) {
let ball = this.balls[count]
if (ball.show) {
let rect = ball.el.getBoundingClientRect()
let x = rect.left - 32;
let y = -(window.innerHeight - rect.top - 22)
el.style.display = ''
el.style.webkitTransform = `translate3d(0,${y}px,0)`
el.style.transform = `translate3d(0,${y}px,0)`
let inner = el.querySelector('.inner-hook')
inner.style.webkitTransform = `translate3d(${x}px,0,0)`
inner.style.transform = `translate3d(${x}px,0,0)`
}
}
},
enter(el) {
el.offsetHeight // offsetWidthoffsetTop
this.$nextTick(() => {
el.style.webkitTransform = 'translate3d(0,0,0)'
el.style.transform = 'translate3d(0,0,0)'
let inner = el.querySelector('.inner-hook')
inner.style.webkitTransform = 'translate3d(0,0,0)'
inner.style.transform = 'translate3d(0,0,0)'
})
},
afterEnter(el) {
let ball = this.dropBalls.shift()
if (ball) {
ball.show = false
el.style.display = 'none'
}
}
},
components: {
cartcontrol,
backdrop
}
}
</script>
<style lang="stylus" scoped>
.shopCart
position fixed
left 0
bottom 0
width 100%
height 48px
z-index 50
.content
display flex
background #141d27
.content-left
flex 1
height 48px
.logo-wrapper
display inline-block
vertical-align top
position: relative
height: 56px
line-height: 56px
border-radius: 50%
width: 56px
top: -10px
background: #141d27
margin:0 12px
padding 6px
box-sizing border-box
text-align: center
.badge
position absolute
top: 0;
right 0
background: rgb(240,20,20);
color: white;
width 24px
height 16px
line-height: 16px;
font-size: 9px;
box-shadow: 0px 4px 8px 0px rgba(0,0,0,0.4);
font-weight: 700;
border-radius: 16px;
text-align center
.logo
width 100%
height 100%
background: #2b343c
border-radius: 50%
font-size: 24px
color: #80858a
line-height: 44px
font-weight: 700
&.active
background: rgb(0,160,220);
color: white;
.price
display inline-block
vertical-align top
font-size 16px
margin-top 12px
padding-right 12px
box-sizing border-box
color rgba(255,255,255,0.4)
font-weight 700
line-height 24px
border-right 1px solid rgba(255,255,255,0.1)
&.active
color white
.desc
position relative
display inline-block
vertical-align top
margin 12px 0 0 12px
font-size 10px
color rgba(255,255,255,0.4)
font-weight 700
line-height 24px
.content-right
flex 0 0 105px
font-size 12px
font-weight 700
background #2b343c
color rgba(255,255,255,0.4)
line-height 48px
text-align center
&.enough
background #00b43c
color white
.ball-container
.ball
position fixed
left 32px
bottom 22px
z-index 200
&.drop-enter,&.drop-enter-active
transition all 0.4s cubic-bezier(0.49,-0.29,0.75,0.41)
.inner
width 16px
height 16px
border-radius 50%
background rgb(0,160,220)
transition all 0.4s linear
.shopcart-list
position absolute
top 0
left 0
width 100%
background white
transform translate3d(0,-100%,0)
z-index -1
&.transHeight-enter-active,&.transHeight-leave-active
transition all 0.5s
&.transHeight-enter,&.transHeight-leave-active
transform translate3d(0,0,0)
.list-header
height 40px
line-height 40px
background #f3f5f7
border-bottom 1px solid rgba(7,17,27,0.1)
.title
display inline-block
font-size 14px
font-weight 200
color rgb(7,17,27)
padding-left 18px
.empty
position absolute
right 8px
font-size 12px
color rgb(0,160,220)
padding 0 10px
.list-content
max-height 217px
overflow hidden
.food
position relative
display flex
height 48px
margin 0 18px
border-bottom 1px solid rgba(7,17,27,0.1)
.name
flex 1
font-size 14px
color rgb(7,17,27)
line-height 48px
font-weight 700
.price
font-size 14px
font-weight 700
color rgb(240,20,20)
padding 0 12px 0 18px
line-height 48px
.cartcontrol-wrapper
font-size 14px
margin-top 6px
.backdrop
position fixed
top 0
bottom 0
left 0
right 0
background rgba(7,17,27,0.6)
backdrop-filter blur(10px)
z-index 40
&.fade-backdrop-enter-active,&.fade-backdrop-leave-active
transition opacity 0.5s
&.fade-backdrop-enter,&.fade-backdrop-leave-active
opacity 0
</style>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.8 KiB

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