【Vue.js】Vue.js中的Vuex、Vue-Ajax和京东购物车项目实战
时间:2022-07-26
本文章向大家介绍【Vue.js】Vue.js中的Vuex、Vue-Ajax和京东购物车项目实战,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。
1、Vuex
1. 单向数据流理念
组成部分
- state:驱动应用的数据源
- view:以声明方式将 state 映射到视图
- actions:响应在 view 上的用户输入导致的状态变化
图示
传统数据通信存在的问题
当我们的应用遇到多个组件共享状态时,单向数据流的简洁性很容易被破坏:(1)多个视图依赖于同一状态,传参的方法对于多层嵌套的组件将会非常繁琐,并且对于兄弟组件间的状态传递无能为力;(2)来自不同视图的行为需要变更同一状态,经常会采用父子组件直接引用或者通过事件来变更和同步状态的多份拷贝。
解决问题的钥匙
把组件的共享状态抽取出来,以一个全局单例模式管理,在这种模式下,项目中的组件树构成了一个巨大的“视图”,不管在树的哪个位置,任何组件都能获取状态或者触发行为。Vuex就是这样一个状态管理模式。
2. Vuex是什么
- Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式
- 它采用集中式存储管理应用的所有组件的状态,并以相应的规则保证状态以一种可预测的方式发生变化
- 一句话:Vuex相当于一个数据银行,对 vue 应用中多个组件的共享状态进行集中式的管理(读/写)
3. Vuex的组成
图示如下:
state
- vuex 管理的状态对象
- 它应该是唯一的:
const state = {
name: 'zhangsan'
}
mutations
- 包含多个直接更新 state 的方法(回调函数)的对象
- 在action中通过commit(‘mutation 名称’)触发mutations中更新state的方法
- 只能包含同步的代码, 不能写异步代码
const mutations = {
aaaa (state, {data}) {
// 更新 state 的 data 属性
}
}
actions
- 包含多个事件回调函数的对象
- Mutation必须是同步的,Action是异步的Mutation
- 组件中通过 $store.dispatch(‘action 名称’, data)触发
- 可以包含异步代码(定时器, ajax)
const actions = {
bbbb({commit, state}, data1) {
commit('aaaa', {data1})
}
}
getters
- 有时候我们需要从 store 中的 state 中派生出一些状态,我们可以理解为vuex中数据的computed功能
## store.js
getters:{
money: state => `¥${state.count*1000}`
},
## page.vue
computed: {
money() {
return this.$store.getters.money;
}
}
mapState
- 更方便的使用api,当一个组件需要获取多个状态时候,将这些状态都声明为计算属性会有些重复和冗余
- 为了解决这个问题,我们可以使用 mapState 辅助函数帮助我们生成计算属性
...mapState({
count:state=>state.count
}),
mapActions
- 方便快捷的使用action
methods:{
...mapActions(['dealCount']),
...mapMutations(['count'])
},
- this.$store.dispatch可以变为
this.dealCount({
amount: 10
})
mapMutions
...mapMutations(['add'])
this.add()
Modules
- 面对复杂的应用程序,当管理的状态比较多时, 我们需要将Vuex的store对象分割成多个模块(modules)
const moduleA = {
state: { ... },
mutations: { ... },
actions: { ... },
getters: { ... }
}
const moduleB = {
state: { ... },
mutations: { ... },
actions: { ... },
getters: { ... }
}
const store = new Vuex.Store({
modules: {
a: moduleA,
b: moduleB
}
});
4. 代码示例
初始化项目
>> vue create lk-vuex-demo
>> vue add vuex
>> npm run serve
## store.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex);
export default new Vuex.Store({
state: {
count: 0 // 初始化数据
},
mutations: {
INCREMENT(state){
state.count++;
},
DECREMENT(state){
state.count--;
}
},
actions: {
increment({commit}){
commit('INCREMENT');
},
decrement({commit}){
commit('DECREMENT');
},
incrementIfEven({commit, state}){
if(state.count % 2 === 0){
commit('INCREMENT');
}
},
incrementAsync({commit}){
setTimeout(()=>{
commit('INCREMENT');
}, 1000);
}
},
getters: {
evenOrOdd(state){
return state.count % 2 === 0 ? '偶数': '奇数'
}
}
})
## main.js
import Vue from 'vue'
import App from './App.vue'
import store from './store'
Vue.config.productionTip = false;
new Vue({
store,
render: h => h(App)
}).$mount('#app');
## App.vue
<template>
<div id="app">
<Counter />
</div>
</template>
<script>
import Counter from './components/Counter'
export default {
name: 'app',
components: {
Counter
}
}
</script>
<style>
#app {
font-family: 'Avenir', Helvetica, Arial, sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
text-align: center;
color: #2c3e50;
margin-top: 60px;
}
</style>
## Counter01.vue
<template>
<div>
<p>点击了{{count}}次</p>
<button @click="increment">增加+1</button>
<button @click="decrement">减少-1</button>
</div>
</template>
<script>
// import {mapMutations} from 'vuex'
export default {
name: "Counter",
computed: {
count(){
return this.$store.state.count
}
},
methods:{
// ...mapMutations(['INCREMENT', 'DECREMENT']),
increment(){
// this.INCREMENT();
// this.$store.commit('INCREMENT');
this.$store.dispatch('increment');
},
decrement(){
// this.DECREMENT();
// this.$store.commit('DECREMENT');
this.$store.dispatch('decrement');
}
}
}
</script>
<style scoped>
</style>
## Counter02.vue
<template>
<div>
<p>点击了{{count}}次, count是{{evenOrOdd}}</p>
<button @click="increment">增加+1</button>
<button @click="decrement">减少-1</button>
<button @click="incrementIfEven">偶数+1</button>
<button @click="incrementAsync">异步+1</button>
</div>
</template>
<script>
import {mapState, mapGetters, mapActions} from 'vuex'
export default {
name: "Counter",
computed: {
...mapState(['count']),
...mapGetters(['evenOrOdd'])
},
methods:{
...mapActions(['increment', 'decrement', 'incrementIfEven', 'incrementAsync'])
}
}
</script>
<style scoped>
</style>
## Counter03.vue
<template>
<div>
<p>点击了{{count}}次, count是{{evenOrOdd}}</p>
<button @click="increment">增加+1</button>
<button @click="decrement">减少-1</button>
<button @click="incrementIfEven">偶数+1</button>
<button @click="incrementAsync">异步+1</button>
</div>
</template>
<script>
export default {
name: "Counter",
computed: {
count(){
return this.$store.state.count
},
evenOrOdd(){
return this.$store.getters.evenOrOdd
}
},
methods:{
increment(){
this.$store.dispatch('increment');
},
decrement(){
this.$store.dispatch('decrement');
},
incrementIfEven(){
this.$store.dispatch('incrementIfEven');
},
incrementAsync(){
this.$store.dispatch('incrementAsync');
}
}
}
</script>
<style scoped>
</style>
2、Vue-Ajax
Vue 项目中常用的 2 个 Ajax
- vue-resource:vue 插件,非官方库,vue1.x 使用广泛
- axios:通用的 ajax 请求库,官方推荐,vue2.x 使用广泛
axios 的使用
- 官方文档:https://github.com/pagekit/vue-resource/blob/develop/docs/http.md
- 安装:
>> npm install axios --save
// 引入模块
import axios from 'axios'
// 发送 ajax 请求
axios.get(url)
.then(response => {
console.log(response.data) ; // 得到返回结果数据
}).catch(error => {
console.log(error.message);
}
测试接口
https://www.easy-mock.com/mock/5d40032d6a3ae527e747fea9/example/itlike/p_list
GET请求实操
axios.get('https://www.easy-mock.com/mock/5d40032d6a3ae527e747fea9/example/itlike/p_list').then((response) => {
console.log(response);
}).catch(function (error) {
console.log(error);
});
3、Vuex版本TodoList
代码结构:
## main.js
import Vue from 'vue'
import App from './App.vue'
import './assets/index.css'
import store from './store/index'
Vue.config.productionTip = false;
new Vue({
store,
render: h => h(App)
}).$mount('#app');
## index.js
/*
Vuex核心管理模块 - Store对象
*/
import Vue from 'vue'
import Vuex from 'vuex'
import state from './state'
import mutations from './mutations'
import actions from './actions'
import getters from './getters'
Vue.use(Vuex);
export default new Vuex.Store({
state,
mutations,
actions,
getters
});
## state.js
/*
状态对象模块
*/
import localStorageUtil from './../utils/localStorageUtil'
export default {
todos: localStorageUtil.readTodos()
}
## mutations.js
/*
多个可以直接同步更新状态的方法 对象模块
*/
import {ADD_TODO, DELETE_TODO, SELECT_ALL_TODO, DELETE_FINISHED_TODO} from './mutations-type'
export default {
[ADD_TODO](state, {todo}){ // ADD_TODO并不是方法名, add_to
state.todos.unshift(todo);
},
[DELETE_TODO](state, {index}){
state.todos.splice(index, 1);
},
[SELECT_ALL_TODO](state, {isCheck}){
state.todos.forEach(todo => {
todo.finished = isCheck
})
},
[DELETE_FINISHED_TODO](state){
state.todos = state.todos.filter(todo=> !todo.finished)
},
}
## mutations-type.js
/*
包含多个mutations中方法名称的常量
*/
export const ADD_TODO = 'add_todo'; // 添加todo
export const DELETE_TODO = 'delete_todo'; // 删除todo
export const SELECT_ALL_TODO = 'select_all_todo'; // 全选/取消全选的todo
export const DELETE_FINISHED_TODO = 'delete_finished_todo'; // 清除已经完成的todo
## actions.js
/*
包含多个间接更新state的方法 对象模块
*/
import {ADD_TODO, DELETE_TODO, SELECT_ALL_TODO, DELETE_FINISHED_TODO} from './mutations-type'
export default {
addTodo({commit}, todo){
commit(ADD_TODO, {todo});
},
delTodo({commit}, index){
commit(DELETE_TODO, {index});
},
selectedAllTodo({commit}, isCheck){
commit(SELECT_ALL_TODO, {isCheck});
},
delFinishedTodos({commit}){
commit(DELETE_FINISHED_TODO);
}
}
## getters.js
/*
服务于 state
*/
export default {
// 任务总数量
todosCount(state){
return state.todos.length;
},
// 已经完成的任务数量
finishedCount(state) {
return state.todos.reduce((total, todo) => total + (todo.finished ? 1 : 0), 0);
},
// 判断是否是全选
isCheck(state, getters){
return getters.finishedCount === getters.todosCount && getters.todosCount > 0
}
}
## localStorageUtil.js
const LK_TODO = 'lk_todo';
export default {
readTodos(){
return JSON.parse(localStorage.getItem(LK_TODO) || '[]');
},
saveTodos(todos){
console.log(todos);
localStorage.setItem(LK_TODO, JSON.stringify(todos));
}
}
## App.vue
<template>
<div class="todo-container">
<div class="todo-wrap">
<Header/>
<List/>
<Footer/>
<button @click="reqData">获取网络数据</button>
</div>
</div>
</template>
<script>
// 引入组件
import Header from './components/Header'
import List from './components/List'
import Footer from './components/Footer'
import axios from 'axios'
export default {
name: 'app',
components: {
Header,
List,
Footer
},
methods: {
reqData(){
axios.get('https://www.easy-mock.com/mock/5d40032d6a3ae527e747fea9/example/itlike/p_list').then((response)=>{
console.log(response);
}).catch((error)=>{
console.log(error);
})
}
}
}
</script>
<style>
.todo-container {
width: 600px;
margin: 0 auto;
}
.todo-container .todo-wrap {
padding: 10px;
border: 1px solid #ddd;
border-radius: 5px;
}
</style>
## Header.vue
<template>
<div class="todo-header">
<input
type="text"
placeholder="请输入今天的任务清单,按回车键确认"
v-model="title"
@keyup.enter="addItem"
/>
</div>
</template>
<script>
export default {
name: "Header",
data(){
return {
title: ''
}
},
methods: {
addItem(){
// 1. 判断是否为空
const title = this.title.trim();
if(!title){
alert('输入的任务不能为空!');
return;
}
// 2. 生成一个todo对象
let todo = {title, finished: false};
// 3. 调用父组件的插入方法
this.$store.dispatch('addTodo', todo);
// 4. 清空输入框
this.title = '';
}
}
}
</script>
<style scoped>
.todo-header input {
width: 560px;
height: 28px;
font-size: 14px;
border: 1px solid #ccc;
border-radius: 4px;
padding: 4px 7px;
outline: none;
}
.todo-header input:focus {
outline: none;
border-color: rgba(255, 0, 0, 0.8);
box-shadow: inset 0 1px 1px rgba(255, 0, 0, 0.075), 0 0 8px rgba(255, 0, 0, 0.6);
}
</style>
## List.vue
<template>
<ul class="todo-main">
<Item
v-for="(todo, index) in todos"
:todo="todo"
:index ="index"
/>
</ul>
</template>
<script>
import localStorageUtil from './../utils/localStorageUtil'
import Item from './Item'
import {mapState} from 'vuex'
export default {
name: "List",
computed:{
...mapState(['todos'])
},
components: {
Item
},
watch: {
todos: {
deep: true,
handler: localStorageUtil.saveTodos
}
}
}
</script>
<style scoped>
.todo-main {
margin-left: 0;
border: 1px solid #ddd;
border-radius: 2px;
padding: 0;
}
</style>
## Item.vue
<template>
<li
@mouseenter="dealShow(true)"
@mouseleave="dealShow(false)"
:style="{backgroundColor: bgColor}"
>
<label>
<input type="checkbox" v-model="todo.finished"/>
<span>{{todo.title}}</span>
</label>
<button v-show="isShowDelButton" class="btn btn-warning" @click="delItem">删除</button>
</li>
</template>
<script>
export default {
name: "Item",
props: {
todo: Object,
index: Number // 当前任务在总任务数组中的下标位置
},
data(){
return{
isShowDelButton: false, // false 隐藏 true 显示
bgColor: '#fff'
}
},
methods: {
dealShow(isShow){
// 控制按钮的显示和隐藏
this.isShowDelButton = isShow;
// 控制背景颜色
this.bgColor = isShow ? '#ddd' : '#fff';
},
delItem(){
if(window.confirm(`您确定删除 ${this.todo.title} 吗?`)){
this.$store.dispatch('delTodo', this.index);
}
}
}
}
</script>
<style scoped>
li {
list-style: none;
height: 36px;
line-height: 36px;
padding: 0 5px;
border-bottom: 1px solid #ddd;
}
li label {
float: left;
cursor: pointer;
}
li label li input {
vertical-align: middle;
margin-right: 6px;
position: relative;
top: -1px;
}
li button {
padding: 4px 10px;
float: right;
margin-top: 3px;
}
li:before {
content: initial;
}
li:last-child {
border-bottom: none;
}
</style>
## Footer.vue
<template>
<div class="todo-footer">
<label>
<input slot="isCheck" type="checkbox" v-model="selectedAllOrNot"/>
</label>
<span>
<span slot="finish">已完成{{finishedCount}}件 / 总计{{todosCount}}件</span>
</span>
<button slot="delete" class="btn btn-warning" @click="delFinishedTodos">清除已完成任务</button>
</div>
</template>
<script>
import {mapGetters, mapActions} from 'vuex'
export default {
name: "Footer",
computed:{
...mapGetters(['todosCount', 'finishedCount', 'isCheck']),
selectedAllOrNot: {
get(){ // 决定是否勾选
return this.isCheck;
},
set(value){
this.selectedAllTodo(value)
}
}
},
methods: {
...mapActions(['selectedAllTodo', 'delFinishedTodos'])
}
}
</script>
<style scoped>
.todo-footer {
height: 40px;
line-height: 40px;
padding-left: 6px;
margin-top: 5px;
}
.todo-footer label {
display: inline-block;
margin-right: 20px;
cursor: pointer;
}
.todo-footer label input {
position: relative;
top: -1px;
vertical-align: middle;
}
.todo-footer button {
float: right;
margin-top: 5px;
}
</style>
4、京东购物车项目实战
代码结构:
## main.js
import Vue from 'vue'
import App from './App.vue'
import store from './store'
Vue.config.productionTip = false;
new Vue({
store,
render: h => h(App)
}).$mount('#app');
## store.js
import Vue from 'vue'
import Vuex from 'vuex'
Vue.use(Vuex)
export default new Vuex.Store({
state: {
},
mutations: {
},
actions: {
}
})
App.vue
<template>
<div id="app">
<Cart />
</div>
</template>
<script>
import Cart from './components/Cart'
export default {
name: 'app',
components: {
Cart
}
}
</script>
<style>
</style>
## Cart.vue
<template>
<div>
<!--头部区域-->
<header class="header">
<a href="index.html" class="icon-back"></a>
<h3>购物车</h3>
<a href="" class="icon-menu"></a>
</header>
<!--安全提示-->
<section class="jd-safe-tip">
<p class="tip-word">
您正在安全购物环境中,请放心购物
</p>
</section>
<!--中间内容-->
<main class="jd-shop-cart-list">
<section>
<div class="shop-cart-list-title">
<div class="left">
<span class="cart-title">撩课自营</span>
</div>
<span class="right">您享受满100元免运费服务</span>
</div>
<div class="shop-cart-list-con" v-for="(shop, index) in shopListArr" :key="shop.shopId">
<div class="left">
<a
href="javascript:;"
class="cart-check-box"
:checked="shop.checked"
@click="singerShopSelected(shop)"
>
</a>
</div>
<div class="center">
<img :src="shop.shopImage" :alt="shop.shopName">
</div>
<div class="right">
<a href="#">{{shop.shopName}}</a>
<div class="shop-price">
<div class="singer-price">{{shop.shopPrice | moneyFormat}}</div>
<div class="total-price">总价:{{ shop.shopPrice * shop.shopNumber | moneyFormat}}</div>
</div>
<div class="shop-deal">
<span @click="singerShopPrice(shop, false)">-</span>
<input disabled="flase" type="number" v-model="shop.shopNumber">
<span @click="singerShopPrice(shop, true)">+</span>
</div>
<div class="shop-deal-right" @click="clickTrash(shop, $event)">
<span></span>
<span></span>
</div>
</div>
</div>
</section>
</main>
<!--面板-->
<div ref="panel" class="panel" style="display: none;">
<div ref="panelContent" class="panel-content">
<div class="panel-title">您确认删除这个商品吗?</div>
<div class="panel-footer">
<a @click.prevent="hidePanel" href="javascript:;" class="cancel">取消</a>
<a @click.prevent="delShop" href="javascript:;" class="submit">确定</a>
</div>
</div>
</div>
<!--底部通栏-->
<div id="tab_bar">
<div class="tab-bar-left">
<a
href="javascript:;"
class="cart-check-box"
:checked="isSelectedAll"
@click="selectedAll(isSelectedAll)"
></a>
<span style="font-size: 16px;">全选</span>
<div class="select-all">
合计:<span class="total-price">{{totalPrice | moneyFormat}}</span>
</div>
</div>
<div class="tab-bar-right">
<a href="index.html" class="pay">去结算</a>
</div>
</div>
</div>
</template>
<script>
import './../assets/css/base.css'
import './../assets/css/cart.css'
import axios from 'axios'
export default {
name: "Cart",
data() {
return {
shopListArr: [], // 购物车中的商品数据
totalPrice: 0,
isSelectedAll: false, // 标识是否全选
up: '', // 盖子
currentDelShop: {}, // 要删除的商品
}
},
created() {
this.getProduct();
},
methods: {
// 1. 获取网络数据
getProduct() {
axios.get('http://demo.itlike.com/web/jdm/api/shoplist').then((response) => {
if (response.data.status === 200) {
this.shopListArr = response.data.result.shopList;
}
}).catch((error) => {
alert('网络出现异常!');
})
},
// 2. 单个商品的加减
singerShopPrice(shop, flag) { // true + false -
if (flag) { // 加
shop.shopNumber += 1;
} else { // 减
if (shop.shopNumber <= 1) {
shop.shopNumber = 1;
alert('只有一件商品啦~');
return;
}
shop.shopNumber -= 1;
}
// 2.1 计算总价
this.getAllShopPrice();
},
// 3. 全选
selectedAll(flag) {
// 3.1 属性控制
this.isSelectedAll = !flag;
// 3.2 遍历购物车中所有的商品数据
this.shopListArr.forEach((value, index) => {
// 3.3 判断
if (typeof value.checked === 'undefined') { // 当前对象中没有该属性
this.$set(value, 'checked', !flag);
} else {
value.checked = !flag;
}
});
// 3.3 计算总价
this.getAllShopPrice();
},
// 4. 单个商品的选中和全校选中
singerShopSelected(shop) {
// 4.1 判断有没有该属性
if (typeof shop.checked === 'undefined') { // 当前对象中没有该属性
this.$set(shop, 'checked', true);
} else {
shop.checked = !shop.checked;
}
// 4.2 判断是否全选
this.hasSelectedAll();
// 4.3 计算总价
this.getAllShopPrice();
},
// 5. 判断是否要全选
hasSelectedAll() {
let flag = true;
this.shopListArr.forEach((value, index) => {
if (!value.checked) {
flag = false;
}
});
this.isSelectedAll = flag && this.shopListArr.length > 0;
},
// 6. 计算商品的总价格
getAllShopPrice() {
let tPrice = 0;
// 6.1 遍历所有的商品
this.shopListArr.forEach((value, index) => {
// 6.2 判断是否选中
if (value.checked) {
tPrice += value.shopPrice * value.shopNumber;
}
});
// 6.3 更新总价格
this.totalPrice = tPrice;
},
// 7. 点击垃圾篓
clickTrash(shop, event) {
// 7.1 获取父标签
let trashes = event.target.parentNode;
let up = trashes.firstElementChild;
// console.log(up);
// 7.2 加过渡
up.style.transition = 'all .2s ease';
up.style.webkitTransition = 'all .2s ease';
// 7.3 实现动画
up.style.transformOrigin = '0 0.5rem';
up.style.webkitTransformOrigin = '0 0.5rem';
up.style.transform = 'rotate(-45deg)';
up.style.webkitTransform = 'rotate(-45deg)';
this.up = up;
// 7.4 显示面板
this.$refs.panel.style.display = 'block';
this.$refs.panelContent.className = 'panel-content jump';
// 7.5 计算要被删除的商品
this.currentDelShop = shop;
},
// 8. 点击取消
hidePanel(){
// 8.1 面板隐藏
this.$refs.panel.style.display = 'none';
this.$refs.panelContent.className = 'panel-content';
// 8.2 盖子闭合
this.up.style.transform = 'rotate(0deg)';
this.up.style.webkitTransform = 'rotate(0deg)';
},
// 9. 删除当前的商品
delShop(){
// 9.1 隐藏面板
this.$refs.panel.style.display = 'none';
// 获取索引
let index = this.shopListArr.indexOf(this.currentDelShop);
this.shopListArr.splice(index, 1);
// 9.2 计算总价
this.getAllShopPrice();
this.hasSelectedAll();
}
},
filters: {
// 格式化金钱
moneyFormat(money) {
return '¥' + Number(money).toFixed(2)
}
}
}
</script>
<style scoped>
</style>
## base.css
*, ::before, ::after{
margin: 0;padding: 0;
-webkit-box-sizing: border-box;
box-sizing: border-box;
/* 去除移动端点击产生的高亮状态 */
-webkit-tap-highlight-color: transparent;
}
html{font-size: 10px;font-family: 'Microsoft Yahei', sans-serif;color: #000;}
a{text-decoration: none;color: #666666;}
ul,ol{list-style: none;}
input{
border: none;
outline: none;
/* 针对iOS浏览器, 清除默认非扁平化分格 */
-webkit-appearance: none;
}
.clearfix::before,
.clearfix::after{
content: '';
height: 0;
line-height: 0;
display: block;
visibility: hidden;
clear: both;
}
[class^='icon-'],
[class*=' icon-']{
background: url("../images/sprites.png") no-repeat;
-webkit-background-size: 20rem 20rem;
background-size: 20rem 20rem;
}
## cart.css
body{
background-color: #f5f5f5;
font-size: 1.4rem;
padding-top: 4.4rem;
}
.header{
z-index: 999;
}
/*************************导航样式***************************/
.header{
width: 100%;
height: 4.4rem;
background: url("./../images/header-bg.png") repeat-x;
-webkit-background-size: 0.1rem 4.4rem;
background-size: 0.1rem 4.4rem;
position: fixed;
left: 0;
top: 0;
}
.header .icon-back,
.header .icon-menu{
width: 4rem;
height: 4rem;
position: absolute;
top: 0;
padding: 10px;
/* 背景图的定位从内容开始计算 */
-webkit-background-origin: content-box;
background-origin: content-box;
/* 背景图从内容开始显示 */
background-clip: content-box;
}
.header .icon-back{
left: 0;
background-position: -2rem 0;
}
.header .icon-menu{
right: 0;
background-position: -6rem 0;
}
.header h3{
width: 100%;
height: 4.4rem;
line-height: 4.4rem;
text-align: center;
/*background-color: red;*/
padding-left: 4rem;
padding-right: 4rem;
overflow: hidden;
font-size: 1.6rem;
color: #666666;
}
.header form{
width: 100%;
height: 4.4rem;
padding-left: 4rem;
padding-right: 4rem;
}
.header form input{
width: 100%;
height: 3.4rem;
border: 1px solid #e0e0e0;
margin-top: 0.5rem;
padding-left: 0.5rem;
}
/* 安全提示 */
.jd-safe-tip{
height: 3.6rem;
line-height: 3.6rem;
background-color: #fff;
border-bottom: 1px solid #e0e0e0;
text-align: center;
}
.jd-safe-tip .tip-word{
position: relative;
/*background-color: red;*/
/* 改变标签类型 */
display: inline-block;
}
.jd-safe-tip .tip-word::before{
content: '';
width: 1.8rem;
height: 1.8rem;
background: url("./../images/safe_icon.png ") no-repeat;
-webkit-background-size: 1.8rem 1.8rem;
background-size: 1.8rem 1.8rem;
position: absolute;
left: -2.1rem;
top: 0.9rem;
}
/* 列表内容 */
.jd-shop-cart-list{
padding-bottom: 6rem;
}
.jd-shop-cart-list section{
margin-top: 1.5rem;
border-top: 0.1rem solid #e0e0e0;
background-color: #fff;
}
.jd-shop-cart-list section .shop-cart-list-title{
display: flex;
justify-content: space-between;
height: 4.4rem;
line-height: 4.4rem;
}
.jd-shop-cart-list section .shop-cart-list-title .left{
flex: 1;
/*background-color: red;*/
padding-left: 8px;
display: flex;
/*justify-content: space-between;*/
align-items: center;
}
.jd-shop-cart-list section .cart-logo{
background: url("./../images/buy-logo.png") no-repeat;
-webkit-background-size: 1.5rem 1.5rem;
background-size: 1.5rem 1.5rem;
width: 1.5rem;
height: 1.5rem;
margin: 0 0.5rem;
}
.cart-check-box{
background: url("./../images/shop-icon.png ") no-repeat;
-webkit-background-size: 5rem 10rem;
background-size: 5rem 10rem;
width: 2rem;
height: 2rem;
}
.cart-check-box[checked]{
background-position: -2.5rem 0;
}
.jd-shop-cart-list section .shop-cart-list-title .right{
/* background-color: red; */
flex: 1;
color: red;
}
.shop-cart-list-con{
/* background-color: red; */
/* 伸缩布局 */
display: flex;
height: 10rem;
border-bottom: 0.1rem solid #e0e0e0;
margin-bottom: 0.7rem;
}
.shop-cart-list-con .left{
/* background: purple; */
flex: 1;
display: flex;
/* justify-content: center; */
}
.shop-cart-list-con .left a{
display: inline-block;
margin-top: 0.5rem;
margin-left: 0.7rem;
}
.shop-cart-list-con .center{
/* background: blue; */
flex: 3;
}
.shop-cart-list-con .center img{
width: 100%;
height: 85%;
}
.shop-cart-list-con .right{
/* background: orangered; */
flex: 9;
display: flex;
flex-direction: column;
margin-left: 0.5rem;
margin-right: 0.5rem;
position: relative;
}
.shop-cart-list-con .right a{
height: 4rem;
line-height: 2rem;
overflow: hidden;
/* background-color: red; */
margin-bottom: 0.3rem;
}
.shop-cart-list-con .right .shop-deal span{
border: 1px solid #e0e0e0;
display: inline-block;
width: 3rem;
height: 2.5rem;
line-height: 2.5rem;
text-align: center;
float: left;
}
.shop-cart-list-con .right .shop-deal span:first-child{
border-top-left-radius: 0.3rem;
border-bottom-left-radius: 0.3rem;
}
.shop-cart-list-con .right .shop-deal span:last-child{
border-top-right-radius: 0.3rem;
border-bottom-right-radius: 0.3rem;
}
.shop-cart-list-con .right .shop-deal input{
border-top: 1px solid #e0e0e0;
border-bottom: 1px solid #e0e0e0;
float: left;
width: 5rem;
height: 2.5rem;
text-align: center;
}
.shop-cart-list-con .right .shop-deal-right{
width: 3rem;
height: 3rem;
/*background-color: red;*/
position: absolute;
right: 0.5rem;
bottom: 0.1rem;
}
.shop-cart-list-con .right .shop-deal-right span:first-child{
background: url("./../images/delete_up.png") no-repeat;
-webkit-background-size: 1.8rem 0.4rem;
background-size: 1.8rem 0.4rem;
width: 1.8rem;
height: 0.4rem;
display: block;
margin: 0 auto;
}
.shop-cart-list-con .right .shop-deal-right span:last-child{
background: url("./../images/delete_down.png") no-repeat;
-webkit-background-size: 1.7rem 1.7rem;
background-size: 1.7rem 1.7rem;
width: 1.7rem;
height: 1.7rem;
display: block;
margin: -0.3rem auto 0;
}
.shop-price{
display: flex;
flex-direction: row;
justify-content: space-between;
align-items: center;
margin-bottom: 0.5rem;
}
.shop-price .total-price{
color: red;
}
/* 面板 */
.panel{
width: 100%;
height: 100%;
position: fixed;
left: 0;
top: 0;
background-color: rgba(0, 0, 0, .6);
z-index: 1000;
}
.panel-content{
width:84%;
position: absolute;
left:8%;
top: 200px;
background-color: #fff;
border: 1px solid #e0e0e0;
border-radius: 5px;
padding: 15px;
}
.panel-title{
text-align: center;
font-size: 17px;
padding-bottom: 30px;
border-bottom: 1px solid #e0e0e0;
margin-bottom: 10px;
}
.panel-footer{
width: 100%;
height: 50px;
/* background-color: green; */
}
.panel-footer a{
width: 120px;
height: 40px;
border: 1px solid #e0e0e0;
margin-top: 10px;
text-align: center;
line-height: 40px;
font-size: 18px;
border-radius: 5px;
}
.panel-footer .cancel{
float: left;
}
.panel-footer .submit{
float: right;
background-color: #E9232C;
color:#fff;
border: none;
}
.panel-is-show{
display: none;
}
/* 实现动画效果 */
.jump{
animation: jump 1s ease;
}
@keyframes jump {
0%{
opacity: 0;
transform: translateY(-300rem);
-webkit-transform: translateY(-300rem);
}
25%{
opacity: 0.3;
transform: translateY(1rem);
-webkit-transform: translateY(1rem);
}
50%{
opacity: 0.6;
transform: translateY(3rem);
-webkit-transform: translateY(3rem);
}
80%{
opacity: 0.8;
transform: translateY(-1rem);
-webkit-transform: translateY(-1rem);
}
90%{
opacity: 1;
transform: translateY(0.5rem);
-webkit-transform: translateY(0.5rem);
}
100%{
opacity: 1;
transform: none;
-webkit-transform: none;
}
}
/* 底部通栏 */
#tab_bar{
position: fixed;
left:0;
bottom:0;
width:100%;
height: 44px;
background-color: #fff;
display: flex;
justify-content: space-between;
align-items: center;
box-shadow: 5px 5px 5px #000;
}
.tab-bar-left{
display: flex;
align-items: center;
margin-left: 7px;
}
.tab-bar-left .select-all{
margin-left: 8px;
font-size: 16px;
}
.tab-bar-right .pay{
width: 90px;
height: 44px;
background-color: #E9232C;
display: flex;
justify-content: center;
align-items: center;
font-size: 18px;
color: #fff;
}
运行结果:
- JavaScript 教程
- JavaScript 编辑工具
- JavaScript 与HTML
- JavaScript 与Java
- JavaScript 数据结构
- JavaScript 基本数据类型
- JavaScript 特殊数据类型
- JavaScript 运算符
- JavaScript typeof 运算符
- JavaScript 表达式
- JavaScript 类型转换
- JavaScript 基本语法
- JavaScript 注释
- Javascript 基本处理流程
- Javascript 选择结构
- Javascript if 语句
- Javascript if 语句的嵌套
- Javascript switch 语句
- Javascript 循环结构
- Javascript 循环结构实例
- Javascript 跳转语句
- Javascript 控制语句总结
- Javascript 函数介绍
- Javascript 函数的定义
- Javascript 函数调用
- Javascript 几种特殊的函数
- JavaScript 内置函数简介
- Javascript eval() 函数
- Javascript isFinite() 函数
- Javascript isNaN() 函数
- parseInt() 与 parseFloat()
- escape() 与 unescape()
- Javascript 字符串介绍
- Javascript length属性
- javascript 字符串函数
- Javascript 日期对象简介
- Javascript 日期对象用途
- Date 对象属性和方法
- Javascript 数组是什么
- Javascript 创建数组
- Javascript 数组赋值与取值
- Javascript 数组属性和方法
- Android 删除指定包名的App实例代码
- 在Ubuntu20.04 LTS中配置Java开发环境
- Android 加载GIF图最佳实践方案
- Android编程之方向传感器用法示例
- Ubuntu20.04安装cuda10.1的步骤(图文教程)
- Android的WebView与H5前端JS代码交互的实例代码
- Android 图片缓存机制的深入理解
- Ubuntu18.04安装Nvidia显卡驱动教程(图文)
- Android控件Spinner的使用方法(1)
- 学习使用Material Design控件(四)Android实现标题栏自动缩放、放大效果
- Ubuntu 20.04 CUDA&cuDNN安装方法(图文教程)
- Android开发之基于DialogFragment创建对话框的方法示例
- Android图片压缩的实例详解
- Android编程之手机壁纸WallPaper设置方法示例
- 手把手教你在腾讯云上搭建hadoop3.x伪集群的方法