封装的确认框组件:
<template>
<div class="xtx-confirm" :class="{ fade: fade }">
<div class="wrapper" :class="{ fade: fade }">
<div class="header">
<h3>{{ title }}</h3>
<a href="JavaScript:;" class="iconfont icon-close-new" @click="CancelCallback"></a>
</div>
<div class="body">
<i class="iconfont icon-warning"></i>
<span>{{ text }}</span>
</div>
<div class="footer">
<XtxButton size="mini" type="gray" @click="CancelCallback">取消</XtxButton>
<XtxButton size="mini" type="primary" @click="ConfirmCallback">确认</XtxButton>
</div>
</div>
</div>
</template>
<script>
import XtxButton from '@/components/library/xtx-button.vue'
import { ref, onMounted } from 'vue'
export default {
name: 'XtxConfirm',
components: { XtxButton },
props: {
title: {
type: String,
default: ''
},
text: {
type: String,
default: ''
},
CancelCallback: {
type: Function,
required: true
},
ConfirmCallback: {
type: Function,
required: true
}
},
setup() {
// 实现弹层的动画
const fade = ref(false)
onMounted(() => {
// fade类名加了但是没有出现效果,使用onMounted的时候,如果没有使用setTimeout,fade已经是
// 模板已经渲染了,如果使用了setTimeout之后会再渲染之后,会重新渲染模板
setTimeout(() => {
fade.value = true
})
})
return { fade }
// // 点击取消
// const CancelCallback = () => {}
// // 点击确认
// const ConfirmCallback = () => {}
// return { CancelCallback, ConfirmCallback }
}
}
</script>
<style scoped lang="less">
.xtx-confirm {
position: fixed;
left: 0;
top: 0;
width: 100%;
height: 100%;
z-index: 8888;
background: rgba(0, 0, 0, 0);
&.fade {
transition: all 1s;
background: rgba(0, 0, 0, 0.5);
}
.wrapper {
width: 400px;
background: #fff;
border-radius: 4px;
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -60%);
opacity: 0;
&.fade {
transition: all 1s;
transform: translate(-50%, -50%);
opacity: 1;
}
.header,
.footer {
height: 50px;
line-height: 50px;
padding: 0 20px;
}
.body {
padding: 20px 40px;
font-size: 16px;
.icon-warning {
color: @priceColor;
margin-right: 3px;
font-size: 16px;
}
}
.footer {
text-align: right;
.xtx-button {
margin-left: 20px;
}
}
.header {
position: relative;
h3 {
font-weight: normal;
font-size: 18px;
}
a {
position: absolute;
right: 15px;
top: 15px;
font-size: 20px;
width: 20px;
height: 20px;
line-height: 20px;
text-align: center;
color: #999;
&:hover {
color: #666;
}
}
}
}
}
</style>
使用vue中的createVNode来创建虚拟Dom
confirm.js(确认框取消,使用render(null,container))
// 封装一个方法实现确认框的提示效果
import { createVNode, render } from 'vue'
import XtxConfirm from './xtx-confirm.vue'
// 动态创建一个Dom容器
const container = document.createElement('div')
container.setAttribute('class', 'xtx-confirm-container')
document.body.appendChild(container)
export default ({ title, text }) => {
return new Promise((resolve, reject) => {
// 点击确认
const ConfirmCallback = () => {
// 销毁确认框
render(null, container)
resolve()
}
// 点击取消
const CancelCallback = () => {
// 销毁确认框
render(null, container)
reject(new Error('cancel'))
}
const vnode = createVNode(XtxConfirm, { title, text, ConfirmCallback, CancelCallback })
// 把虚拟节点渲染Dom中
render(vnode, container)
// 如果想要让then触发,需要调用resolve(点击确认按钮触发)
// 如果想要用catch触发,需要调用reject(点击取消按钮触发)
})
}
cart/index.vue
<template>
<div class="xtx-cart-page">
<div class="container">
<XtxBread>
<XtxBreadItem to="/">首页</XtxBreadItem>
<XtxBreadItem>购物车</XtxBreadItem>
</XtxBread>
<div class="cart">
<table>
<thead>
<tr>
<th width="120">
<!-- getters是只读的不能直接修改 -->
<XtxCheckbox :update:modelValue="toggleAll" :modelValue="$store.getters['cart/isAll']"
>全选</XtxCheckbox
>
</th>
<th width="400">商品信息</th>
<th width="220">单价</th>
<th width="180">数量</th>
<th width="180">小计</th>
<th width="140">操作</th>
</tr>
</thead>
<!-- 有效商品 -->
<tbody>
<tr v-if="$store.getters['cart/validList'].length === 0">
<td colspan="6">
<CartNone />
</td>
</tr>
<template v-else>
<tr v-for="item in $store.getters['cart/validList']" :key="item.skuId">
<td>
<XtxCheckbox :modelValue="item.selected" @change="toggleItem(item.skuId, $event)" />
</td>
<td>
<div class="goods">
<RouterLink to="/"><img :src="item.picture" alt=""/></RouterLink>
<div>
<p class="name ellipsis">{{ item.name }}</p>
<!-- 选择规格组件 -->
</div>
</div>
</td>
<td class="tc">
<p>¥{{ item.nowPrice }}</p>
<p v-if="item.nowPrice < item.Price">
比加入时降价 <span class="red">¥{{ item.Price - item.nowPrice }}</span>
</p>
</td>
<td class="tc">
<XtxNumbox />
</td>
<td class="tc">
<p class="f16 red">¥{{ item.nowPrice * item.count }}</p>
</td>
<td class="tc">
<p><a href="javascript:;">移入收藏夹</a></p>
<p><a class="green" href="javascript:;" @click="handleDelete(item.skuId)">删除</a></p>
<p><a href="javascript:;">找相似</a></p>
</td>
</tr>
</template>
</tbody>
<!-- 无效商品 -->
<tbody>
<tr>
<td colspan="6"><h3 class="tit" v-if="cart / invalidList">失效商品</h3></td>
</tr>
<tr v-for="item in $store.getters['cart/invalidList']" :key="item.skuId">
<td><XtxCheckbox style="color:#eee;" /></td>
<td>
<div class="goods">
<RouterLink to="/"><img :src="item.pictrue" alt=""/></RouterLink>
<div>
<p class="name ellipsis">{{ item.name }}</p>
<p class="attr">{{ item.attrsText }}</p>
</div>
</div>
</td>
<td class="tc">
<p>¥{{ item.nowPrice }}</p>
</td>
<td class="tc">1</td>
<td class="tc">
<p>¥{{ item.count }}</p>
</td>
<td class="tc">
<p><a class="green" href="javascript:;">删除</a></p>
<p><a href="javascript:;">找相似</a></p>
</td>
</tr>
</tbody>
</table>
</div>
<!-- 操作栏 -->
<div class="action">
<div class="batch">
<XtxCheckbox>全选</XtxCheckbox>
<a href="javascript:;">删除商品</a>
<a href="javascript:;">移入收藏夹</a>
<a href="javascript:;">清空失效商品</a>
</div>
<div class="total">
共 {{ $store.getters['cart/validCount'] }} 件商品,已选择
{{ $store.getters['cart/selectedCount'] }} 件,商品合计:
<span class="red">¥{{ $store.getters['cart/selectedTotal'] }}</span>
<XtxButton type="primary">下单结算</XtxButton>
</div>
</div>
<!-- 猜你喜欢 -->
<GoodRelevant />
</div>
</div>
</template>
<script>
import Confirm from '@/components/library/confirm'
import CartNone from './components/cart-none.vue'
import Message from '@/components/library/Message'
import { useStore } from 'vuex'
import GoodRelevant from '@/views/goods/components/goods-relevant'
export default {
name: 'XtxCartPage',
components: { GoodRelevant, CartNone },
setup() {
const store = useStore()
const toggleItem = (skuId, flag) => {
// console.log(skuId, flag)
store.dispatch('cart/toggleItem', {
skuId: skuId,
selected: flag
})
}
// 控制全选的切换操作
const toggleAll = () => {
store.dispatch('cart/toggleAll')
}
// 控制商品的单个删除操作
const handleDelete = skuId => {
Confirm({ title: '删除确认提示', text: '确认要删除该商品吗?' })
.then(() => {
// 点击确认按钮触发
return store.dispatch('cart/deleteCart', skuId)
})
.then(res => {
Message({ text: res, type: 'success' })
})
.catch(err => {
// 点击取消按钮触发
console.log('取消')
if (err === 'cancel') return
Message({ text: err, type: 'error' })
})
// store
// .dispatch('cart/deleteCart', skuId)
// .then(res => {
// Message({ text: res, type: 'success' })
// })
// .catch(err => Message({ text: err, type: 'success' }))
}
return { toggleItem, toggleAll, handleDelete, Confirm }
}
}
</script>
<style scoped lang="less">
.tc {
text-align: center;
.xtx-numbox {
margin: 0 auto;
width: 120px;
}
}
.red {
color: @priceColor;
}
.green {
color: @xtxColor;
}
.f16 {
font-size: 16px;
}
.goods {
display: flex;
align-items: center;
img {
width: 100px;
height: 100px;
}
> div {
width: 280px;
font-size: 16px;
padding-left: 10px;
.attr {
font-size: 14px;
color: #999;
}
}
}
.action {
display: flex;
background: #fff;
margin-top: 20px;
height: 80px;
align-items: center;
font-size: 16px;
justify-content: space-between;
padding: 0 30px;
.xtx-checkbox {
color: #999;
}
.batch {
a {
margin-left: 20px;
}
}
.red {
font-size: 18px;
margin-right: 20px;
font-weight: bold;
}
}
.tit {
color: #666;
font-size: 16px;
font-weight: normal;
line-height: 50px;
}
.xtx-cart-page {
.cart {
background: #fff;
color: #666;
table {
border-spacing: 0;
border-collapse: collapse;
line-height: 24px;
th,
td {
padding: 10px;
border-bottom: 1px solid #f5f5f5;
&:first-child {
text-align: left;
padding-left: 30px;
color: #999;
}
}
th {
font-size: 16px;
font-weight: normal;
line-height: 50px;
}
}
}
}
</style>