vue手机外卖点餐系统
|
@ -0,0 +1,5 @@
|
|||
{
|
||||
"presets": ["es2015", "stage-2"],
|
||||
"plugins": ["transform-runtime"],
|
||||
"comments": false
|
||||
}
|
|
@ -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
|
|
@ -0,0 +1,2 @@
|
|||
build/*.js
|
||||
config/*.js
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
.DS_Store
|
||||
node_modules/
|
||||
resource/
|
||||
dist/
|
||||
npm-debug.log
|
||||
.idea
|
|
@ -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.
|
|
@ -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,统一代码风格,规避低级错误,对于有代码洁癖的人来说是绝对的好东西,不过有些地方的代码校验有时候也挺麻烦的-.-
|
||||
- bable,ES2015出来已经有一段时间了,但是不少浏览器还没有兼容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
|
||||
```
|
||||
|
||||
|
|
@ -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')
|
||||
})
|
|
@ -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)
|
||||
}
|
||||
}
|
|
@ -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()
|
||||
}
|
||||
})
|
|
@ -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)
|
||||
}
|
||||
})
|
|
@ -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
|
||||
}
|
|
@ -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']
|
||||
})
|
||||
]
|
||||
}
|
||||
}
|
|
@ -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
|
||||
})
|
||||
]
|
||||
})
|
|
@ -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
|
|
@ -0,0 +1,6 @@
|
|||
var merge = require('webpack-merge')
|
||||
var prodEnv = require('./prod.env')
|
||||
|
||||
module.exports = merge(prodEnv, {
|
||||
NODE_ENV: '"development"'
|
||||
})
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -0,0 +1,3 @@
|
|||
module.exports = {
|
||||
NODE_ENV: '"production"'
|
||||
}
|
|
@ -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>
|
|
@ -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"
|
||||
}
|
||||
}
|
|
@ -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>
|
|
@ -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=" " horiz-adv-x="512" d="" />
|
||||
<glyph unicode="Ǵ" 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="" 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="" 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="" 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="" 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="" glyph-name="keyboard_arrow_right" d="M366 262l196 196-196 196 60 60 256-256-256-256z" />
|
||||
<glyph unicode="" 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="" 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="" 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="" 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 |
|
@ -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;
|
|
@ -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"
|
|
@ -0,0 +1,3 @@
|
|||
@import "./base"
|
||||
@import "./icon"
|
||||
@import "./mixin"
|
|
@ -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')
|
||||
}
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
|
@ -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>
|
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 4.7 KiB |
After Width: | Height: | Size: 1.7 KiB |
After Width: | Height: | Size: 3.8 KiB |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 2.3 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 3.0 KiB |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 3.1 KiB |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 3.3 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 2.7 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 3.5 KiB |
After Width: | Height: | Size: 1.9 KiB |
After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 3.5 KiB |
|
@ -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>
|
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 2.6 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 3.5 KiB |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 3.4 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 2.0 KiB |
After Width: | Height: | Size: 1.4 KiB |
After Width: | Height: | Size: 2.5 KiB |
After Width: | Height: | Size: 2.1 KiB |
After Width: | Height: | Size: 2.8 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 3.8 KiB |
|
@ -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>
|
|
@ -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>
|
|
@ -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 // 触发浏览器重绘,offsetWidth、offsetTop等方法都可以触发
|
||||
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>
|
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 1.2 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 2.4 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 1.3 KiB |
After Width: | Height: | Size: 1.6 KiB |
After Width: | Height: | Size: 1.8 KiB |
After Width: | Height: | Size: 2.9 KiB |
After Width: | Height: | Size: 1.5 KiB |
After Width: | Height: | Size: 1.8 KiB |