新增营销系统、分销系统、会员功能、门店、提现功能
This commit is contained in:
212
root/goodsCategory/goodsCategory.vue
Normal file
212
root/goodsCategory/goodsCategory.vue
Normal file
@ -0,0 +1,212 @@
|
||||
<template>
|
||||
<Header ref="headerRef" :scroll-top="scrollTop"> 分类</Header>
|
||||
<view
|
||||
class="goods-category"
|
||||
:style="computeMainBoxStyle"
|
||||
>
|
||||
<uv-vtabs
|
||||
:list="categoryData"
|
||||
:hdHeight="`${headerRef&&headerRef.containerHeight || 0}px`"
|
||||
>
|
||||
<template
|
||||
v-for="(item, index) in categoryData"
|
||||
:key="index"
|
||||
>
|
||||
<uv-vtabs-item :index="index">
|
||||
<view class="category-list">
|
||||
<view
|
||||
class="category"
|
||||
>
|
||||
<view class="category-title">
|
||||
<view class="line"></view>
|
||||
<view class="category-title-text">
|
||||
{{ item.name }}
|
||||
</view>
|
||||
<view class="line"></view>
|
||||
</view>
|
||||
<view class="category-content">
|
||||
<uv-grid
|
||||
:border="false"
|
||||
:col="3"
|
||||
>
|
||||
<uv-grid-item
|
||||
v-for="goodCategory in item.children"
|
||||
@click="toGoodsCategoryList(goodCategory.id)"
|
||||
:key="goodCategory.id"
|
||||
>
|
||||
<view class="category-item">
|
||||
<view class="category-item-icon">
|
||||
<image
|
||||
class="image"
|
||||
:src="goodCategory.picUrl"
|
||||
mode="aspectFit"
|
||||
/>
|
||||
</view>
|
||||
<view class="category-item-name">
|
||||
{{ goodCategory.name }}
|
||||
</view>
|
||||
</view>
|
||||
</uv-grid-item>
|
||||
</uv-grid>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</uv-vtabs-item>
|
||||
</template>
|
||||
<uv-gap
|
||||
bg-color="#fff"
|
||||
height="600"
|
||||
></uv-gap>
|
||||
</uv-vtabs>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { computed, ref } from 'vue'
|
||||
import { getCategoryList } from '@/api/product'
|
||||
import { useRouter } from "@/hooks/useRouter";
|
||||
import Header from "@/components/Header/index.vue"
|
||||
import { onLoad } from "@dcloudio/uni-app";
|
||||
import UvVtabs from "@/uni_modules/uv-vtabs/components/uv-vtabs/uv-vtabs.vue";
|
||||
import UvVtabsItem from "@/uni_modules/uv-vtabs/components/uv-vtabs-item/uv-vtabs-item.vue";
|
||||
import UvGrid from "@/uni_modules/uv-grid/components/uv-grid/uv-grid.vue";
|
||||
import UvGridItem from "@/uni_modules/uv-grid/components/uv-grid-item/uv-grid-item.vue";
|
||||
import UvGap from "@/uni_modules/uv-gap/components/uv-gap/uv-gap.vue";
|
||||
import { useScroll } from "@/hooks/useScroll";
|
||||
|
||||
const {push} = useRouter()
|
||||
const {scrollTop} = useScroll()
|
||||
const headerRef = ref() //导航条
|
||||
|
||||
// 中心高度 100bh - 上导航栏 - h5底部高度
|
||||
const computeMainBoxStyle = computed(() => {
|
||||
const height = headerRef.value?.containerHeight ?? 0
|
||||
return {
|
||||
height: `calc(100vh - ${ height }rpx - var(--window-bottom))`
|
||||
}
|
||||
})
|
||||
|
||||
const categoryData = ref([]) // 分类列表
|
||||
|
||||
/**
|
||||
* 获取分类
|
||||
*/
|
||||
async function doGetCategoryList() {
|
||||
const category = await getCategoryList()
|
||||
if (!category) return
|
||||
// 二级分类,需要处理一下
|
||||
categoryData.value = arrayToTree(category)
|
||||
}
|
||||
|
||||
/**
|
||||
* 数组转tree
|
||||
* @param items
|
||||
* @returns {*[]}
|
||||
*/
|
||||
function arrayToTree(items) {
|
||||
const rootItems = [];
|
||||
const itemMap = {};
|
||||
|
||||
// 首先,将所有项按照id映射到itemMap中,并找到根项(没有父项的项)
|
||||
items.forEach(item => {
|
||||
itemMap[item.id] = {...item, children: []};
|
||||
|
||||
if (item.parentId === 0) {
|
||||
rootItems.push(itemMap[item.id]);
|
||||
}
|
||||
});
|
||||
|
||||
// 然后,将子项添加到父项的children属性中
|
||||
items.forEach(item => {
|
||||
if (item.parentId !== 0) {
|
||||
itemMap[item.parentId].children.push(itemMap[item.id]);
|
||||
}
|
||||
});
|
||||
|
||||
return rootItems;
|
||||
}
|
||||
|
||||
/**
|
||||
* 去商品列表
|
||||
* @param categoryId
|
||||
*/
|
||||
function toGoodsCategoryList(categoryId) {
|
||||
push({
|
||||
url: '/pages/goodsList/goodsList'
|
||||
}, {
|
||||
data: {sid: categoryId}
|
||||
})
|
||||
}
|
||||
|
||||
|
||||
onLoad(() => {
|
||||
doGetCategoryList()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.goods-category {
|
||||
width: 100%;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.category-list {
|
||||
padding: 0 20rpx;
|
||||
}
|
||||
|
||||
.category {
|
||||
margin-bottom: 20rpx;
|
||||
|
||||
&-title {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-top: 50rpx;
|
||||
margin-bottom: 30rpx;
|
||||
|
||||
&-text {
|
||||
line-height: 45rpx;
|
||||
font-size: 32rpx;
|
||||
color: #333333;
|
||||
margin: 0 30rpx;
|
||||
}
|
||||
|
||||
.line {
|
||||
width: 30rpx;
|
||||
height: 1rpx;
|
||||
background: #CCCCCC;
|
||||
}
|
||||
}
|
||||
|
||||
&-item {
|
||||
flex: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-bottom: 30rpx;
|
||||
|
||||
.image {
|
||||
width: 150rpx;
|
||||
height: 110rpx;
|
||||
display: block;
|
||||
background: #efefef;
|
||||
}
|
||||
|
||||
&-name {
|
||||
margin-top: 20rpx;
|
||||
line-height: 32rpx;
|
||||
font-size: 24rpx;
|
||||
color: #999999;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.goods-category :deep(.uv-vtabs__bar) {
|
||||
background: #F5F5F5;
|
||||
|
||||
.uv-vtabs__bar-item {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
</style>
|
103
root/index/index.vue
Normal file
103
root/index/index.vue
Normal file
@ -0,0 +1,103 @@
|
||||
<template>
|
||||
<view class="home-container" :style="{ 'margin-top': `${statusBarHeight}px` }">
|
||||
<CanvasPage />
|
||||
<!-- h5 tabbar 底部 -->
|
||||
<!-- <view class="h5-tabbar-height"></view> -->
|
||||
<adWindow></adWindow>
|
||||
<ReturnTop :scroll-top="scrollTop" />
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import { onLoad, onReachBottom, onShareAppMessage, onShareTimeline } from '@dcloudio/uni-app'
|
||||
import { useMainStore } from '@/store/store'
|
||||
import { useRouter } from "@/hooks/useRouter";
|
||||
import CanvasPage from '@/components/canvasShow/canvasShowPage.vue'
|
||||
import ReturnTop from "@/components/ReturnTop/index.vue"
|
||||
import adWindow from "@/components/adWindow/adWindow.vue"
|
||||
import UvIcon from "@/uni_modules/uv-icon/components/uv-icon/uv-icon.vue";
|
||||
import { useScroll } from "@/hooks/useScroll";
|
||||
import { homeLogoIcon } from "@/utils/images";
|
||||
import { useShare } from "@/hooks/useShare";
|
||||
const main = useMainStore()
|
||||
const {push} = useRouter()
|
||||
const recommendRef = ref(null)
|
||||
const statusBarHeight = ref(0)
|
||||
|
||||
|
||||
const searchShadow = ref({
|
||||
boxShadow: '0 0 0 #000'
|
||||
})
|
||||
|
||||
function handleHeaderAnimation(numericalValue) {
|
||||
searchShadow.value = {
|
||||
boxShadow: `0 0 ${ numericalValue * 15 }px #EFEFEF inset`
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
onLoad(() => {
|
||||
main.init()
|
||||
|
||||
|
||||
// 导航栏距顶距离
|
||||
uni.getSystemInfo({
|
||||
//获取系统信息
|
||||
success: res => {
|
||||
statusBarHeight.value = res.statusBarHeight;
|
||||
},
|
||||
fail(err) {
|
||||
// console.log(err);
|
||||
}
|
||||
});
|
||||
})
|
||||
|
||||
const {shareAppMessage,shareTimeline} = useShare();
|
||||
onShareAppMessage(shareAppMessage)
|
||||
onShareTimeline(shareTimeline)
|
||||
const {scrollTop} = useScroll()
|
||||
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.home-container {
|
||||
width: 100%;
|
||||
position: relative;
|
||||
.header-row {
|
||||
.logo-col {
|
||||
@include useFlex(flex-start, center);
|
||||
width: 126rpx;
|
||||
aspect-ratio: 126 / 50;
|
||||
|
||||
.logo {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
}
|
||||
|
||||
.search-col {
|
||||
@include useFlex(flex-start, center);
|
||||
@include usePadding(30, 15);
|
||||
width: 100%;
|
||||
height: 60rpx;
|
||||
border-radius: 50rpx;
|
||||
background: $white-color;
|
||||
margin-left: 50rpx;
|
||||
|
||||
.search-input {
|
||||
margin-left: 30rpx;
|
||||
font-size: 24rpx;
|
||||
color: #999999;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
.goods-row {
|
||||
padding: 0 8rpx;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
47
root/shoppingCart/components/CartEmpty.vue
Normal file
47
root/shoppingCart/components/CartEmpty.vue
Normal file
@ -0,0 +1,47 @@
|
||||
<!--
|
||||
@name: 空购物车
|
||||
@author: kahu4
|
||||
@date: 2023-11-06 15:59
|
||||
@description:CartEmpty
|
||||
@update: 2023-11-06 15:59
|
||||
-->
|
||||
<script setup>
|
||||
import Empty from '@/components/Empty/index.vue'
|
||||
import { emptyCartIcon } from "@/utils/images";
|
||||
import { useRouter } from "@/hooks/useRouter";
|
||||
|
||||
const {pushToTab} = useRouter()
|
||||
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<Empty
|
||||
:iconSrc="emptyCartIcon"
|
||||
:padding="'220rpx 0 0 0'"
|
||||
>
|
||||
<template #default>
|
||||
购物车里空空如也~
|
||||
</template>
|
||||
<template #bottom>
|
||||
<view
|
||||
class="go-on-btn"
|
||||
@click="pushToTab({url:'/root/index/index'})"
|
||||
>
|
||||
继续逛逛
|
||||
</view>
|
||||
</template>
|
||||
</Empty>
|
||||
</template>
|
||||
|
||||
<style
|
||||
scoped
|
||||
lang="scss"
|
||||
>
|
||||
.go-on-btn {
|
||||
padding: 15rpx 50rpx;
|
||||
margin-top: 20rpx;
|
||||
background: #333333;
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
</style>
|
30
root/shoppingCart/index.data.js
Normal file
30
root/shoppingCart/index.data.js
Normal file
@ -0,0 +1,30 @@
|
||||
/**
|
||||
* @name: index.data
|
||||
* @author: kahu4
|
||||
* @date: 2023-11-06 14:15
|
||||
* @description:index.data
|
||||
* @update: 2023-11-06 14:15
|
||||
* */
|
||||
// 购物车统计信息
|
||||
export const settleFields = [
|
||||
{
|
||||
label: '商品总价',
|
||||
field: 'costPrice',
|
||||
prefix: '¥'
|
||||
},
|
||||
{
|
||||
label: '优惠',
|
||||
field: 'couponPrice',
|
||||
prefix: '-¥'
|
||||
},
|
||||
{
|
||||
label: '运费',
|
||||
field: 'storePostage',
|
||||
prefix: '¥'
|
||||
},
|
||||
{
|
||||
label: '总计',
|
||||
field: 'totalPrice',
|
||||
prefix: '¥'
|
||||
}
|
||||
]
|
319
root/shoppingCart/index.utils.js
Normal file
319
root/shoppingCart/index.utils.js
Normal file
@ -0,0 +1,319 @@
|
||||
/**
|
||||
* @name: 购物车相关操作方法
|
||||
* @author: kahu4
|
||||
* @date: 2023-11-06 15:03
|
||||
* @description:index.utils
|
||||
* @update: 2023-11-06 15:03
|
||||
* */
|
||||
import { computed, nextTick, ref, unref } from "vue";
|
||||
import { changeCartSku, computeSelectInfo, deleteCartByIds, getCartList, updateCartNumber } from "@/api/cart";
|
||||
import { onShow } from '@dcloudio/uni-app'
|
||||
import _ from "loadsh";
|
||||
import { useInterface } from "@/hooks/useInterface";
|
||||
import { useRouter } from "@/hooks/useRouter";
|
||||
import { getProductDetail } from "@/api/product";
|
||||
|
||||
|
||||
/**
|
||||
* 购物车数据
|
||||
*/
|
||||
export function useCartData() {
|
||||
const cartListLoading = ref(false)
|
||||
const cartList = ref([])
|
||||
|
||||
const showEmpty = computed(() => cartList.value.length <= 0)
|
||||
|
||||
/**
|
||||
* 获取购物车列表
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function doGetCartList() {
|
||||
try {
|
||||
cartListLoading.value = true
|
||||
const res = await getCartList()
|
||||
cartList.value = res?.valid ?? []
|
||||
} finally {
|
||||
cartListLoading.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onShow(async () => {
|
||||
await doGetCartList()
|
||||
})
|
||||
|
||||
return {
|
||||
showEmpty,
|
||||
cartListLoading,
|
||||
cartList,
|
||||
doGetCartList
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户操作相关
|
||||
*/
|
||||
export function useCartOption(options) {
|
||||
const {cartList, doGetCartList} = options
|
||||
const {toast} = useInterface()
|
||||
const {push} = useRouter()
|
||||
|
||||
const manage = ref(false)
|
||||
const manageStr = computed(() => {
|
||||
return manage.value ? '取消' : '管理'
|
||||
})
|
||||
|
||||
const shoppingSelect = ref([]) // 选中数据
|
||||
const shoppingSelectAll = ref(false) // 是否全选
|
||||
|
||||
/**
|
||||
* 用户单选
|
||||
* @param value
|
||||
*/
|
||||
async function handleSingleSelect(value) {
|
||||
shoppingSelectAll.value = value.length === cartList.value.length
|
||||
setTimeout(async () => {
|
||||
await computeSelectInfoByShoppingSelect()
|
||||
}, 100)
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户全选
|
||||
* @param e
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function handleSelectAll(e) {
|
||||
shoppingSelect.value = !!e ? cartList.value.map(item => item.id) : []
|
||||
shoppingSelectAll.value = e
|
||||
await computeSelectInfoByShoppingSelect()
|
||||
}
|
||||
|
||||
const statisticsInfo = ref(undefined) // 统计信息
|
||||
|
||||
/**
|
||||
* 根据shoppingSelect去计算选中数据
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function computeSelectInfoByShoppingSelect() {
|
||||
if (unref(shoppingSelect).length <= 0) return statisticsInfo.value = void (0)
|
||||
const res = await computeSelectInfo({
|
||||
cartId: unref(shoppingSelect).join(','),
|
||||
orderType: 1,
|
||||
useIntegral: false
|
||||
});
|
||||
statisticsInfo.value = res.priceGroup
|
||||
}
|
||||
|
||||
/**
|
||||
* 重新设置选中信息
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function resetUserSelect() {
|
||||
shoppingSelect.value = []
|
||||
shoppingSelectAll.value = false
|
||||
await computeSelectInfoByShoppingSelect()
|
||||
}
|
||||
|
||||
/**
|
||||
* 打开删除弹窗
|
||||
* @returns {*}
|
||||
*/
|
||||
function openDelModal(modalRef) {
|
||||
if (unref(shoppingSelect).length <= 0) return toast({title: '请勾选需要删除的商品'})
|
||||
unref(modalRef).show()
|
||||
}
|
||||
|
||||
/**
|
||||
* 删除数据
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
async function doDelete() {
|
||||
await deleteCartByIds({
|
||||
ids: shoppingSelect.value
|
||||
})
|
||||
await doGetCartList()
|
||||
await resetUserSelect()
|
||||
}
|
||||
|
||||
/**
|
||||
* 提交订单
|
||||
* @returns {*}
|
||||
*/
|
||||
function submitOrder() {
|
||||
if (unref(shoppingSelect).length <= 0) return toast({title: '请勾选需要购买的商品'})
|
||||
push({url: '/pages/submitOrder/submitOrder'}, {
|
||||
data: {cartId: shoppingSelect.value.toString()}
|
||||
})
|
||||
}
|
||||
|
||||
return {
|
||||
manage,
|
||||
manageStr,
|
||||
shoppingSelect,
|
||||
shoppingSelectAll,
|
||||
statisticsInfo,
|
||||
handleSingleSelect,
|
||||
handleSelectAll,
|
||||
computeSelectInfoByShoppingSelect,
|
||||
openDelModal,
|
||||
doDelete,
|
||||
submitOrder
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 更改sku
|
||||
*/
|
||||
export function useSku() {
|
||||
const {toast} = useInterface()
|
||||
const openSkuProductId = ref(undefined) // 当前选中的sku商品id
|
||||
const openSkuSkuId = ref(undefined) // 当前选中sku的sku id
|
||||
const openSkuCartId = ref(undefined)
|
||||
const openProductItem = ref(undefined)
|
||||
|
||||
/**
|
||||
* 获取商品详情
|
||||
* @param data
|
||||
* @param data.skuId skuId
|
||||
* @param data.productId productId
|
||||
* @return {Promise<void>}
|
||||
*/
|
||||
const handleGetDetail = async (data) => {
|
||||
openProductItem.value = await getProductDetail(data);
|
||||
};
|
||||
|
||||
/**
|
||||
* 打开sku选择器
|
||||
* @param item
|
||||
* @param modalRef
|
||||
*/
|
||||
async function handleOpenSkuSelect(item, modalRef) {
|
||||
await handleGetDetail({productId: item.productId})
|
||||
openSkuSkuId.value = item.productInfo.attrInfo.id
|
||||
openSkuProductId.value = item.productId
|
||||
openSkuCartId.value = item.id
|
||||
await nextTick(() => {
|
||||
modalRef.open(item.cartNum)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 关闭sku选择器
|
||||
*/
|
||||
function handleCloseSkuSelect() {
|
||||
openSkuProductId.value = openSkuCartId.value = void (0)
|
||||
}
|
||||
|
||||
async function handleSubmitSkuSelect(e, modalRef, cartList, func, doGetCartList) {
|
||||
if (!openSkuProductId.value) return
|
||||
const {store, num} = e
|
||||
await changeCartSku({
|
||||
id: openSkuCartId.value,
|
||||
productId: openSkuProductId.value,
|
||||
productAttrUnique: store.unique
|
||||
})
|
||||
if (typeof func === 'function') {
|
||||
const find = cartList.find(item => item.id === openSkuCartId.value);
|
||||
await func({detail: {value: num}}, find)
|
||||
}
|
||||
setTimeout(async () => {
|
||||
await doGetCartList()
|
||||
toast({title: '修改成功', icon: 'success'})
|
||||
handleCloseSkuSelect()
|
||||
}, 400)
|
||||
}
|
||||
|
||||
return {
|
||||
openProductItem,
|
||||
openSkuSkuId,
|
||||
openSkuProductId,
|
||||
handleOpenSkuSelect,
|
||||
handleCloseSkuSelect,
|
||||
handleSubmitSkuSelect
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* 更改购物车数量
|
||||
*/
|
||||
export function useCartNumber(options) {
|
||||
const {toast} = useInterface()
|
||||
|
||||
/**
|
||||
* 用户手动输入改变数量
|
||||
* @param e
|
||||
* @param item
|
||||
* @returns {*}
|
||||
*/
|
||||
function handleCartNumberInputChange(e, item) {
|
||||
const value = parseInt(e.detail.value.toString().replace(/^0+/, ''))
|
||||
if (value <= 0) {
|
||||
item.cartNum = 1
|
||||
toast({title: '至少选一件哦~'})
|
||||
return
|
||||
}
|
||||
if (value > item.trueStock) {
|
||||
item.cartNum = item.productInfo.stock
|
||||
toast({title: '超出库存啦~'})
|
||||
return
|
||||
}
|
||||
nextTick(()=>{
|
||||
item.cartNum = value
|
||||
doCartNumberChangeRequest(item)
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 购买数量验证
|
||||
*/
|
||||
function cartNumberInput(e, item){
|
||||
const pattern = /^0+|[.]*/g;
|
||||
nextTick(() => {
|
||||
item.cartNum = e.detail.value.replace(pattern,'');
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* 用户点击购物车+-改变数量
|
||||
* @param item
|
||||
* @param type
|
||||
* @returns {*}
|
||||
*/
|
||||
function handleCartNumberChange(item, type = 'plus') {
|
||||
if (type === 'plus') {
|
||||
if (item.cartNum + 1 > item.trueStock) {
|
||||
item.cartNum = item.trueStock
|
||||
} else {
|
||||
item.cartNum += 1
|
||||
}
|
||||
|
||||
} else {
|
||||
if (item.cartNum <= 1) {
|
||||
item.cartNum = 1
|
||||
} else {
|
||||
item.cartNum -= 1
|
||||
}
|
||||
}
|
||||
doCartNumberChangeRequest(item)
|
||||
}
|
||||
|
||||
/**
|
||||
* 请求改变后台用户购物车数据
|
||||
* @param item
|
||||
* @returns {Promise<void>}
|
||||
*/
|
||||
const doCartNumberChangeRequest = _.debounce(async (item) => {
|
||||
await updateCartNumber({
|
||||
id: item.id,
|
||||
number: item.cartNum
|
||||
})
|
||||
options && options.afterChange && await options.afterChange()
|
||||
}, 300)
|
||||
|
||||
return {
|
||||
handleCartNumberInputChange,
|
||||
handleCartNumberChange,
|
||||
cartNumberInput
|
||||
}
|
||||
}
|
418
root/shoppingCart/shoppingCart.vue
Normal file
418
root/shoppingCart/shoppingCart.vue
Normal file
@ -0,0 +1,418 @@
|
||||
<template>
|
||||
<Header
|
||||
:scroll-top="scrollTop"
|
||||
system-bar-area-bg="#fff"
|
||||
header-area-bg="#fff"
|
||||
bg-change-by-scroll
|
||||
>
|
||||
购物车
|
||||
</Header>
|
||||
<!-- have data -->
|
||||
<view v-if="!showEmpty">
|
||||
<!-- manage row -->
|
||||
<view class="cart-manage">
|
||||
<view @click="manage=!manage">
|
||||
{{ manageStr }}
|
||||
</view>
|
||||
</view>
|
||||
<!-- cart body box-->
|
||||
<uv-checkbox-group
|
||||
v-model="shoppingSelect"
|
||||
shape="circle"
|
||||
activeColor="#ee6d46"
|
||||
@change="handleSingleSelect"
|
||||
>
|
||||
|
||||
<card class="shopping-checkbox">
|
||||
<!-- 购物车信息 -->
|
||||
<view
|
||||
v-for="(item) in cartList"
|
||||
:key="item.id"
|
||||
class="shopping-item"
|
||||
>
|
||||
<uv-checkbox
|
||||
:name="item.id"
|
||||
/>
|
||||
<view class="good">
|
||||
<Goods
|
||||
row
|
||||
imgWidth="200rpx"
|
||||
info-padding="0 0 0 40rpx"
|
||||
:goods="item.productInfo"
|
||||
>
|
||||
<template #options>
|
||||
<view class="goods-options">
|
||||
<!-- sku select -->
|
||||
<view class="sku-row flex">
|
||||
<view
|
||||
class="sku-info flex flex-jc__sb flex-ai__center"
|
||||
@click.stop="handleOpenSkuSelect(item,goodsAttrSelectRef)"
|
||||
>
|
||||
<view class="info">
|
||||
{{ item.productInfo && item.productInfo.attrInfo && item.productInfo.attrInfo.sku }}
|
||||
</view>
|
||||
<uv-icon
|
||||
class="icon"
|
||||
name="arrow-down"
|
||||
color="#ccc"
|
||||
size="12"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
<!-- bottom -->
|
||||
<view class="price-row flex flex-ai__center flex-jc__sb">
|
||||
<!-- price -->
|
||||
<view class="price-box flex flex-ai__end">
|
||||
¥{{ item.truePrice }}
|
||||
<view class="old-price">
|
||||
¥{{ item.productInfo.otPrice }}
|
||||
</view>
|
||||
</view>
|
||||
<!-- cart number -->
|
||||
<view
|
||||
class="cart-num flex flex-ai__center flex-jc__sb"
|
||||
@click.stop=""
|
||||
>
|
||||
<view
|
||||
class="button"
|
||||
:class="item.cartNum <= 1 && 'disabled'"
|
||||
@click="handleCartNumberChange(item,'minus')"
|
||||
>
|
||||
<uv-icon
|
||||
name="minus"
|
||||
color="#333"
|
||||
size="24rpx"
|
||||
></uv-icon>
|
||||
</view>
|
||||
<view class="input">
|
||||
<input
|
||||
type="number"
|
||||
inputmode="numeric"
|
||||
v-model="item.cartNum"
|
||||
@blur="(e)=>handleCartNumberInputChange(e,item)"
|
||||
@input="(e)=>cartNumberInput(e,item)"
|
||||
>
|
||||
</view>
|
||||
<view
|
||||
class="button"
|
||||
:class="item.cartNum >= item.trueStock && 'disabled'"
|
||||
@click="handleCartNumberChange(item,'plus')"
|
||||
>
|
||||
<uv-icon
|
||||
name="plus"
|
||||
color="#333"
|
||||
size="24rpx"
|
||||
></uv-icon>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
</Goods>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 选中了的结算统计信息 -->
|
||||
<view :class="{'select-product-settle-info':true,show:shoppingSelect.length>0}">
|
||||
<view
|
||||
class="row flex flex-ai__center flex-jc__sb"
|
||||
v-for="(item,index) in settleFields"
|
||||
:key="index"
|
||||
>
|
||||
<view class="label">
|
||||
{{ item.label }}
|
||||
</view>
|
||||
<view v-if="statisticsInfo">
|
||||
{{ item.prefix }} {{ statisticsInfo[item.field].toFixed(2) }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</card>
|
||||
</uv-checkbox-group>
|
||||
<!-- bottom action row -->
|
||||
<view class="screen action-bar ">
|
||||
<view class="action-info">
|
||||
<view class="action-checkbox">
|
||||
<uv-checkbox-group
|
||||
shape="circle"
|
||||
activeColor="#ee6d46"
|
||||
>
|
||||
<uv-checkbox
|
||||
name="all"
|
||||
:checked="shoppingSelectAll"
|
||||
@change="handleSelectAll"
|
||||
>
|
||||
全选
|
||||
</uv-checkbox>
|
||||
</uv-checkbox-group>
|
||||
</view>
|
||||
</view>
|
||||
<view class="action-btns">
|
||||
<view
|
||||
class="action-total"
|
||||
v-show="!manage"
|
||||
>
|
||||
总计:¥{{ statisticsInfo ? statisticsInfo.totalPrice.toFixed(2) : "0.00" }}
|
||||
</view>
|
||||
<view
|
||||
class="button"
|
||||
v-if="!manage"
|
||||
@click="submitOrder"
|
||||
>结算
|
||||
</view>
|
||||
<view
|
||||
v-else
|
||||
class="button"
|
||||
@click="openDelModal(modalRef)"
|
||||
>
|
||||
删除
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
</view>
|
||||
<!-- null data -->
|
||||
<CartEmpty v-else />
|
||||
<!-- 商品推荐 -->
|
||||
<Recommend />
|
||||
<view class="action-height"></view>
|
||||
<view class="h5-tabbar-height"></view>
|
||||
<!-- sku select -->
|
||||
<GoodAttrSelect
|
||||
style="z-index: 999"
|
||||
:id="openSkuProductId"
|
||||
ref="goodsAttrSelectRef"
|
||||
:goods-detail="openProductItem"
|
||||
:sku-id="openSkuSkuId"
|
||||
@select="(e)=>handleSubmitSkuSelect(e, goodsAttrSelectRef,cartList,handleCartNumberInputChange,doGetCartList)
|
||||
"
|
||||
/>
|
||||
<!-- delete modal -->
|
||||
<Modal
|
||||
ref="modalRef"
|
||||
content="确认要删除这些购物车数据吗?"
|
||||
@confirm="doDelete"
|
||||
/>
|
||||
<ReturnTop :scroll-top="scrollTop" />
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import Goods from "@/components/goodsComponents/Goods.vue";
|
||||
import GoodAttrSelect from "@/components/good-attr-select/good-attr-select.vue";
|
||||
import Modal from "@/components/Modal/index.vue"
|
||||
import { settleFields } from "@/root/shoppingCart/index.data";
|
||||
import { useCartData, useCartNumber, useCartOption, useSku } from "@/root/shoppingCart/index.utils";
|
||||
import CartEmpty from "@/root/shoppingCart/components/CartEmpty.vue";
|
||||
import Header from "@/components/Header/index.vue"
|
||||
import { onHide } from "@dcloudio/uni-app";
|
||||
import Recommend from "@/components/Recommend/index.vue";
|
||||
import { useScroll } from "@/hooks/useScroll";
|
||||
import ReturnTop from "@/components/ReturnTop/index.vue"
|
||||
|
||||
|
||||
const modalRef = ref() // 删除弹窗
|
||||
const goodsAttrSelectRef = ref() // 更改sku
|
||||
|
||||
const {
|
||||
showEmpty,
|
||||
cartList,
|
||||
doGetCartList
|
||||
} = useCartData();
|
||||
|
||||
const {
|
||||
manage,
|
||||
manageStr,
|
||||
shoppingSelect,
|
||||
shoppingSelectAll,
|
||||
handleSingleSelect,
|
||||
handleSelectAll,
|
||||
statisticsInfo,
|
||||
computeSelectInfoByShoppingSelect,
|
||||
openDelModal,
|
||||
doDelete,
|
||||
submitOrder,
|
||||
} = useCartOption({cartList, doGetCartList})
|
||||
|
||||
const {
|
||||
openSkuSkuId,
|
||||
openProductItem,
|
||||
openSkuProductId,
|
||||
handleOpenSkuSelect,
|
||||
handleCloseSkuSelect,
|
||||
handleSubmitSkuSelect
|
||||
} = useSku()
|
||||
|
||||
|
||||
const {
|
||||
handleCartNumberInputChange,
|
||||
handleCartNumberChange,
|
||||
cartNumberInput
|
||||
} = useCartNumber({afterChange: computeSelectInfoByShoppingSelect})
|
||||
|
||||
|
||||
onHide(() => {
|
||||
modalRef.value?.close()
|
||||
goodsAttrSelectRef.value?.close()
|
||||
})
|
||||
|
||||
const {scrollTop} = useScroll()
|
||||
</script>
|
||||
|
||||
<style lang="scss">
|
||||
.cart-manage {
|
||||
padding: 20rpx 34rpx;
|
||||
font-size: 28rpx;
|
||||
color: #333333;
|
||||
background-color: #fff;
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
border-bottom: 1rpx solid #f9f9f9;
|
||||
}
|
||||
|
||||
// 商品SKU 数量等操作条
|
||||
.goods-options {
|
||||
width: 100%;
|
||||
|
||||
.sku-row {
|
||||
margin-bottom: 30rpx;
|
||||
|
||||
.sku-info {
|
||||
@include usePadding(10, 4);
|
||||
border: 1rpx solid #ccc;
|
||||
border-radius: 5rpx;
|
||||
font-size: 24rpx;
|
||||
transition: all .3s;
|
||||
max-width: 100%;
|
||||
|
||||
|
||||
.info {
|
||||
width: 100%;
|
||||
white-space: nowrap;
|
||||
text-overflow: ellipsis;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
&:active {
|
||||
scale: 1.1;
|
||||
}
|
||||
|
||||
.icon {
|
||||
margin-left: 15rpx;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.price-row {
|
||||
width: 100%;
|
||||
|
||||
.price-box {
|
||||
font-size: 30rpx;
|
||||
|
||||
.old-price {
|
||||
font-size: 20rpx;
|
||||
color: $tips-color;
|
||||
text-decoration: line-through;
|
||||
margin-left: 10rpx;
|
||||
}
|
||||
}
|
||||
|
||||
.cart-num {
|
||||
font-size: 24rpx;
|
||||
|
||||
.input {
|
||||
width: 70rpx;
|
||||
|
||||
|
||||
input {
|
||||
width: 100%;
|
||||
text-align: center;
|
||||
color: #333;
|
||||
}
|
||||
}
|
||||
|
||||
.button {
|
||||
font-size: 32rpx;
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
aspect-ratio: 1/1;
|
||||
border-radius: 5rpx;
|
||||
box-sizing: border-box;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
transition: all .3s;
|
||||
border: 1px solid #999999;
|
||||
|
||||
&:active {
|
||||
scale: 1.2;
|
||||
}
|
||||
|
||||
&.disabled {
|
||||
border-color: #dddddd;
|
||||
|
||||
:deep(.uv-icon__icon) {
|
||||
color: #dddddd !important;
|
||||
}
|
||||
|
||||
&:active {
|
||||
scale: 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.action-bar {
|
||||
z-index: 89;
|
||||
height: 100rpx;
|
||||
|
||||
|
||||
.action-btns {
|
||||
height: 100%;
|
||||
padding: 0 0;
|
||||
|
||||
.action-total {
|
||||
padding-right: 30rpx;
|
||||
font-size: 34rpx;
|
||||
}
|
||||
|
||||
.button {
|
||||
margin: 0 0;
|
||||
height: 100%;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
padding: 0 50rpx;
|
||||
background: $primary-color;
|
||||
color: $white-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// #ifndef H5
|
||||
.action-height {
|
||||
width: 100%;
|
||||
height: 100rpx;
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
// #endif
|
||||
|
||||
.shopping-checkbox {
|
||||
width: 100%;
|
||||
|
||||
.shopping-item {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
padding: 0 20rpx;
|
||||
box-sizing: border-box;
|
||||
margin-bottom: 50rpx;
|
||||
|
||||
.good {
|
||||
width: 93%;
|
||||
position: relative;
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
171
root/user/components/GridCard.vue
Normal file
171
root/user/components/GridCard.vue
Normal file
@ -0,0 +1,171 @@
|
||||
<!--
|
||||
@name: GridCard
|
||||
@author: kahu4
|
||||
@date: 2023-11-09 15:09
|
||||
@description:GridCard
|
||||
@update: 2023-11-09 15:09
|
||||
-->
|
||||
<script setup>
|
||||
import { toRefs } from "vue";
|
||||
import { useRouter } from "@/hooks/useRouter";
|
||||
import { useInterface } from "@/hooks/useInterface";
|
||||
import { storeToRefs } from "pinia";
|
||||
import { useMainStore } from "@/store/store";
|
||||
import { useService } from "@/hooks/useService";
|
||||
|
||||
const props = defineProps({
|
||||
list: {
|
||||
type: Array,
|
||||
default: () => ([])
|
||||
},
|
||||
dotInfo: {
|
||||
type: Object
|
||||
},
|
||||
title: {
|
||||
type: String,
|
||||
default: () => ''
|
||||
},
|
||||
buttonText: {
|
||||
type: String,
|
||||
default: ''
|
||||
}
|
||||
})
|
||||
const {list, title, buttonText, dotInfo} = toRefs(props)
|
||||
const emits = defineEmits(['buttonClick'])
|
||||
const {push} = useRouter();
|
||||
const {toast} = useInterface();
|
||||
const mainStore = useMainStore();
|
||||
const {user} = storeToRefs(mainStore);
|
||||
|
||||
async function toLink(listItem) {
|
||||
if (!user.value) return toast({title: '请先登录'})
|
||||
if (!listItem.path) return toast({title: ' 暂未开放 '})
|
||||
if (listItem.path === 'kf') {
|
||||
const {getServiceData,openService} = useService();
|
||||
await getServiceData()
|
||||
await openService()
|
||||
return
|
||||
}
|
||||
push({url: listItem.path}, listItem?.params ?? {})
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<view class="grid-container">
|
||||
<view
|
||||
class="title-row"
|
||||
v-if="title || buttonText"
|
||||
>
|
||||
<view>
|
||||
{{ title }}
|
||||
</view>
|
||||
<view
|
||||
class="right"
|
||||
@click="emits('buttonClick')"
|
||||
>
|
||||
{{ buttonText }}
|
||||
<uv-icon
|
||||
v-if="buttonText"
|
||||
name="arrow-right"
|
||||
color="#ccc"
|
||||
size="12"
|
||||
/>
|
||||
</view>
|
||||
</view>
|
||||
<view class="icon-box">
|
||||
<view
|
||||
class="icon-item"
|
||||
v-for="item in list"
|
||||
:key="item"
|
||||
@click="toLink(item)"
|
||||
>
|
||||
|
||||
<template v-if="item&&item.rightTopDot">
|
||||
<view
|
||||
class="dot"
|
||||
v-if="dotInfo && dotInfo[item.dotField] && dotInfo[item.dotField]>0"
|
||||
>
|
||||
{{ dotInfo[item.dotField] < 100 ? dotInfo[item.dotField] : `${ dotInfo[item.dotField] }+` }}
|
||||
</view>
|
||||
</template>
|
||||
<image
|
||||
class="icon"
|
||||
:src="item.icon"
|
||||
/>
|
||||
<view class="text">
|
||||
{{ item.label }}
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
<style
|
||||
scoped
|
||||
lang="scss"
|
||||
>
|
||||
.grid-container {
|
||||
width: 100%;
|
||||
background: $white-color;
|
||||
border-radius: 15rpx;
|
||||
margin: 20rpx 0;
|
||||
|
||||
.title-row {
|
||||
@include useFlex(space-between, center);
|
||||
@include usePadding(30, 30);
|
||||
|
||||
font-size: 32rpx;
|
||||
color: #333;
|
||||
font-weight: 500;
|
||||
border-bottom: 1rpx solid #f5f5f5;
|
||||
|
||||
.right {
|
||||
@include useFlex(space-between, center);
|
||||
font-size: 24rpx;
|
||||
color: $tips-color;
|
||||
font-weight: normal;
|
||||
}
|
||||
}
|
||||
|
||||
.icon-box {
|
||||
@include usePadding(30, 20);
|
||||
display: grid;
|
||||
grid-template-columns: repeat(4, 1fr);
|
||||
|
||||
.icon-item {
|
||||
@include useFlex(space-between, center, column);
|
||||
width: 100%;
|
||||
font-size: 24rpx;
|
||||
color: #333333;
|
||||
position: relative;
|
||||
|
||||
.dot {
|
||||
z-index: 99;
|
||||
position: absolute;
|
||||
background: #EE6D46;
|
||||
color: #fff;
|
||||
right: 20%;
|
||||
top: 0;
|
||||
transform: translateY(-20%);
|
||||
font-size: 18rpx;
|
||||
width: 38rpx;
|
||||
height: 38rpx;
|
||||
text-align: center;
|
||||
line-height: 38rpx;
|
||||
box-sizing: border-box;
|
||||
border-radius: 50%;
|
||||
border: 2rpx solid #ffffff;
|
||||
}
|
||||
|
||||
.icon {
|
||||
width: 60rpx;
|
||||
height: 60rpx;
|
||||
}
|
||||
|
||||
.text {
|
||||
margin: 14rpx 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
</style>
|
172
root/user/index.data.js
Normal file
172
root/user/index.data.js
Normal file
@ -0,0 +1,172 @@
|
||||
/**
|
||||
* @name: index.data
|
||||
* @author: kahu4
|
||||
* @date: 2023-11-09 15:21
|
||||
* @description:index.data
|
||||
* @update: 2023-11-09 15:21
|
||||
* */
|
||||
|
||||
import {
|
||||
myScan,
|
||||
toDZIcon,
|
||||
toFHIcon,
|
||||
toKFIcon,
|
||||
toKJIcon,
|
||||
toPayIcon,
|
||||
toPJIcon,
|
||||
toSCIcon,
|
||||
toSHIcon,
|
||||
toSHOIcon,
|
||||
toTGIcon,
|
||||
toYHQIcon,
|
||||
toZBIcon,
|
||||
toZHIcon,
|
||||
toZJIcon
|
||||
} from "@/utils/images";
|
||||
import { useJump } from "@/hooks/useJump";
|
||||
|
||||
const {goIntegral, goBalance, goCoupon} = useJump()
|
||||
export const orderIconList = [
|
||||
{
|
||||
id: 1,
|
||||
label: '待付款',
|
||||
icon: toPayIcon,
|
||||
rightTopDot: true,
|
||||
dotField: 'unpaidCount',
|
||||
path: '/pages/orderList/orderList',
|
||||
params: {data: {type: 0}}
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
label: '待发货',
|
||||
icon: toFHIcon,
|
||||
rightTopDot: true,
|
||||
dotField: 'unshippedCount',
|
||||
path: '/pages/orderList/orderList',
|
||||
params: {data: {type: 1}}
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
label: '待收货',
|
||||
icon: toSHIcon,
|
||||
rightTopDot: true,
|
||||
dotField: 'receivedCount',
|
||||
path: '/pages/orderList/orderList',
|
||||
params: {data: {type: 2}}
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
label: '待评价',
|
||||
icon: toPJIcon,
|
||||
rightTopDot: true,
|
||||
dotField: 'evaluatedCount',
|
||||
path: '/pages/orderList/orderList',
|
||||
params: {data: {type: 3}}
|
||||
},
|
||||
]
|
||||
|
||||
export const cardOneList = [
|
||||
{
|
||||
id: 1,
|
||||
label: '我的足迹',
|
||||
icon: toZJIcon,
|
||||
path: '/pages/footprint/footprint',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
label: '优惠券',
|
||||
icon: toYHQIcon,
|
||||
path: '/pages/discountCoupon/index',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
label: '我的收藏',
|
||||
icon: toSCIcon,
|
||||
rightTopDot: false,
|
||||
dotField: 'receivedCount',
|
||||
path: '/pages/collect/collect',
|
||||
},
|
||||
{
|
||||
id: 4,
|
||||
label: '开启直播',
|
||||
icon: toZBIcon,
|
||||
rightTopDot: false,
|
||||
dotField: 'evaluatedCount',
|
||||
path: '',
|
||||
params: {data: {type: 3}}
|
||||
},
|
||||
{
|
||||
id: 5,
|
||||
label: '我的推广',
|
||||
icon: toTGIcon,
|
||||
path: '/views/distribution/center/index',
|
||||
params: {}
|
||||
},
|
||||
{
|
||||
id: 6,
|
||||
label: '砍价记录',
|
||||
icon: toKJIcon,
|
||||
path: '',
|
||||
params: {data: {type: 1}}
|
||||
},
|
||||
{
|
||||
id: 7,
|
||||
label: '售后记录',
|
||||
icon: toSHOIcon,
|
||||
rightTopDot: false,
|
||||
dotField: 'receivedCount',
|
||||
path: '/pages/refundList/refundList',
|
||||
params: {data: {type: -1}}
|
||||
},
|
||||
{
|
||||
id: 8,
|
||||
label: '联系客服',
|
||||
icon: toKFIcon,
|
||||
rightTopDot: false,
|
||||
dotField: 'evaluatedCount',
|
||||
path: 'kf',
|
||||
params: {data: {type: 3}}
|
||||
},
|
||||
]
|
||||
|
||||
export const cardTwoList = [
|
||||
{
|
||||
id: 1,
|
||||
label: '地址管理',
|
||||
icon: toDZIcon,
|
||||
path: '/pages/address/address',
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
label: '账号管理',
|
||||
icon: toZHIcon,
|
||||
path: '/pages/userInfo/index',
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
label: '订单核销',
|
||||
icon: myScan,
|
||||
path: '/views/activity/afterVerification/index',
|
||||
},
|
||||
]
|
||||
|
||||
export const accountList = [
|
||||
{
|
||||
id: 1,
|
||||
label: '我的余额',
|
||||
field: 'nowMoney',
|
||||
path: goBalance
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
label: '我的积分',
|
||||
field: 'integral',
|
||||
path: goIntegral
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
label: '优惠券',
|
||||
field: 'couponNumber',
|
||||
path: goCoupon
|
||||
}
|
||||
]
|
388
root/user/user.vue
Normal file
388
root/user/user.vue
Normal file
@ -0,0 +1,388 @@
|
||||
<template>
|
||||
<view class="mine-container">
|
||||
|
||||
<Header :show-return="false">我的</Header>
|
||||
<!-- user info -->
|
||||
<view class="userinfo-box">
|
||||
<!-- 未登录 -->
|
||||
<view
|
||||
class="userinfo-box__inner no-login"
|
||||
v-if="!(user && user.id)"
|
||||
@click="toLogin"
|
||||
>
|
||||
<view class="flex flex-ai__center">
|
||||
<image
|
||||
class="head"
|
||||
:src="defaultAvatarIcon"
|
||||
/>
|
||||
<view class="user-info">
|
||||
点击登录
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 已登录 -->
|
||||
<view
|
||||
class="userinfo-box__inner"
|
||||
@click="toUserCenter"
|
||||
v-else
|
||||
>
|
||||
<view class="flex flex-ai__center">
|
||||
<image
|
||||
class="head"
|
||||
:src="user.avatar"
|
||||
/>
|
||||
<view class="user-info">
|
||||
{{ user.nickname }}
|
||||
</view>
|
||||
</view>
|
||||
<view
|
||||
class="sign-box"
|
||||
@click.stop="goSignIn">
|
||||
<image :src="mySignIn" />
|
||||
签到
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- 账户信息 -->
|
||||
<view class="account-box">
|
||||
<template
|
||||
v-for="item in accountList"
|
||||
:key="item.id">
|
||||
<view
|
||||
v-if="user"
|
||||
class="account-item"
|
||||
@click.stop="handleJump(item)">
|
||||
<view class="count">
|
||||
{{ user[item.field] || 0 }}
|
||||
</view>
|
||||
<view class="title">
|
||||
{{ item.label }}
|
||||
</view>
|
||||
</view>
|
||||
</template>
|
||||
|
||||
</view>
|
||||
|
||||
<!-- VIP 信息 未激活 -->
|
||||
<view
|
||||
class="vip-box vip-none"
|
||||
@click="goMemberCenter"
|
||||
v-if="!memberLeverInfo.currentLevel">
|
||||
<view
|
||||
class="vip-box__inner flex flex-ai__center flex-jc__sb"
|
||||
:style="{backgroundImage:`url(${myVip1})`}">
|
||||
<image
|
||||
class="icon"
|
||||
:src="noneVip" />
|
||||
<view class="vip-text">
|
||||
开通享更多特权,省钱又省心
|
||||
</view>
|
||||
<view class="vip-button">
|
||||
立即激活
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
|
||||
<!-- VIP 信息 激活 -->
|
||||
<view
|
||||
class="vip-box"
|
||||
@click="goMemberCenter"
|
||||
v-else>
|
||||
<view
|
||||
class="vip-box__inner "
|
||||
:style="{backgroundImage:`url(${myVip1})`}">
|
||||
<view class="flex flex-ai__center flex-jc__sb">
|
||||
<image
|
||||
class="icon"
|
||||
:src="memberLeverInfo.currentLevel.iconUrl" />
|
||||
<view class="vip-text flex flex-ai__center">
|
||||
{{ memberLeverInfo.currentLevel.levelName }}
|
||||
<view class="process">
|
||||
<view
|
||||
class="schedule"
|
||||
:style="{width: `${memberLeverInfo.needGrowthValue/memberLeverInfo.nextLevel.growthValue}%`}"></view>
|
||||
</view>
|
||||
</view>
|
||||
<view class="vip-button">
|
||||
查看权益 >
|
||||
</view>
|
||||
</view>
|
||||
<view class="tips">
|
||||
再获取{{ memberLeverInfo.needGrowthValue }}经验,可升级为{{ memberLeverInfo.nextLevel.levelName }}会员
|
||||
</view>
|
||||
</view>
|
||||
</view>
|
||||
<!-- 大卡片 -->
|
||||
<view class="big-card">
|
||||
<!-- order card -->
|
||||
<GridCard
|
||||
:list="orderIconList"
|
||||
:dot-info="orderUserCountData"
|
||||
title="我的订单"
|
||||
button-text="查看所有订单"
|
||||
@button-click="toAllOrder"
|
||||
/>
|
||||
|
||||
<!-- footprint card -->
|
||||
<GridCard
|
||||
:list="cardOneList"
|
||||
:dot-info="orderUserCountData"
|
||||
/>
|
||||
|
||||
<GridCard
|
||||
:list="filterCardTwo"
|
||||
:dot-info="orderUserCountData"
|
||||
/>
|
||||
</view>
|
||||
|
||||
|
||||
</view>
|
||||
</template>
|
||||
|
||||
|
||||
<script setup>
|
||||
import { computed, ref } from 'vue'
|
||||
import Header from '@/components/Header/index.vue'
|
||||
|
||||
import { orderUserCount } from '@/api/order'
|
||||
import { onShow } from '@dcloudio/uni-app'
|
||||
import { useMainStore } from '@/store/store'
|
||||
import { useRouter } from "@/hooks/useRouter";
|
||||
import { storeToRefs } from "pinia";
|
||||
import GridCard from "@/root/user/components/GridCard.vue";
|
||||
import { accountList, cardOneList, cardTwoList, orderIconList } from "@/root/user/index.data";
|
||||
import { defaultAvatarIcon, mySignIn, myVip1, myVipNone, noneVip } from "@/utils/images";
|
||||
import { useInterface } from "@/hooks/useInterface";
|
||||
import { useJump } from "@/hooks/useJump";
|
||||
import { getUserMemberLevel } from "@/api/member";
|
||||
|
||||
const mainStore = useMainStore()
|
||||
const {user} = storeToRefs(mainStore);
|
||||
const {push} = useRouter()
|
||||
const {toast} = useInterface();
|
||||
const {goSignIn, goMemberCenter} = useJump()
|
||||
const orderUserCountData = ref(null)
|
||||
|
||||
const filterCardTwo = computed(() => {
|
||||
if (!user.value) return []
|
||||
// 判断是否又核销权限
|
||||
if (user.value.writeOffAuthority) {
|
||||
return cardTwoList
|
||||
} else {
|
||||
return cardTwoList.filter(item => item.label !== '订单核销')
|
||||
}
|
||||
})
|
||||
|
||||
function toAllOrder() {
|
||||
if (!user.value) return toast({title: '请先登录'})
|
||||
push({url: '/pages/orderList/orderList'}, {data: {type: -1}})
|
||||
}
|
||||
|
||||
function toUserCenter() {
|
||||
if (!user.value) return toast({title: '请先登录'})
|
||||
push({url: '/pages/userInfo/index'})
|
||||
}
|
||||
|
||||
function toLogin() {
|
||||
push({url: '/pages/login/guid'})
|
||||
}
|
||||
|
||||
const handleOrderUserCount = async () => {
|
||||
orderUserCountData.value = await orderUserCount()
|
||||
}
|
||||
|
||||
// =============================== 会员信息 ====================================
|
||||
const memberLeverInfo = ref({
|
||||
currentGrowthValue: 0,
|
||||
currentLevel: null,
|
||||
needGrowthValue: 0,
|
||||
nextLevel: null
|
||||
})
|
||||
|
||||
async function doGetUserMemberLevel() {
|
||||
memberLeverInfo.value = await getUserMemberLevel()
|
||||
}
|
||||
|
||||
function handleJump(item) {
|
||||
if (!item.path) return toast({title: '暂未开放~'})
|
||||
if (typeof item.path === 'function') {
|
||||
item.path()
|
||||
}
|
||||
}
|
||||
|
||||
onShow(() => {
|
||||
mainStore.getUserInfo()
|
||||
handleOrderUserCount()
|
||||
doGetUserMemberLevel()
|
||||
})
|
||||
|
||||
|
||||
</script>
|
||||
|
||||
<style
|
||||
lang="scss"
|
||||
scoped
|
||||
>
|
||||
.mine-container {
|
||||
background: linear-gradient(to bottom, #fff 0%, #fff 28%, #b0b0b0 40%, #fff 41%);
|
||||
|
||||
.userinfo-box {
|
||||
@include usePadding(32, 0);
|
||||
width: 100%;
|
||||
margin: 50rpx 0 0 0;
|
||||
|
||||
&__inner {
|
||||
@include useFlex(space-between, center);
|
||||
|
||||
.head {
|
||||
width: 120rpx;
|
||||
height: 120rpx;
|
||||
border-radius: 50%;
|
||||
border: 5rpx solid #fff;
|
||||
}
|
||||
|
||||
.user-info {
|
||||
@include useFlex(space-between, center);
|
||||
padding-left: 30rpx;
|
||||
font-size: 34rpx;
|
||||
}
|
||||
|
||||
.sign-box {
|
||||
@include useFlex(center, center);
|
||||
@include usePadding(16, 12);
|
||||
border: 1rpx solid #E5E5E5;
|
||||
font-weight: bold;
|
||||
font-size: 24rpx;
|
||||
border-radius: 50rpx;
|
||||
|
||||
image {
|
||||
width: 40rpx;
|
||||
height: 40rpx;
|
||||
}
|
||||
}
|
||||
|
||||
&.no-login {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.account-box {
|
||||
width: 100%;
|
||||
display: grid;
|
||||
grid-template-columns: repeat(3, 1fr);
|
||||
margin: 32rpx 0;
|
||||
|
||||
.account-item {
|
||||
@include useFlex(center, center, column);
|
||||
font-weight: bold;
|
||||
font-size: 32rpx;
|
||||
color: #333;
|
||||
position: relative;
|
||||
|
||||
|
||||
&:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
width: 1rpx;
|
||||
height: 50%;
|
||||
background: $tips-color;
|
||||
}
|
||||
|
||||
&:last-child {
|
||||
&:after {
|
||||
width: 0;
|
||||
}
|
||||
}
|
||||
|
||||
.title {
|
||||
color: $tips-color;
|
||||
font-size: 24rpx;
|
||||
font-weight: normal;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.vip-box {
|
||||
width: 100%;
|
||||
height: 132rpx;
|
||||
@include usePadding(32, 0);
|
||||
|
||||
&__inner {
|
||||
@include usePadding(32, 25);
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-repeat: no-repeat;
|
||||
background-size: 100% 100%;
|
||||
|
||||
.icon {
|
||||
width: 56rpx;
|
||||
height: 56rpx;
|
||||
}
|
||||
|
||||
.vip-text {
|
||||
width: 60%;
|
||||
color: #FFF8E8;
|
||||
font-weight: bold;
|
||||
font-size: 28rpx;
|
||||
|
||||
.process {
|
||||
flex-grow: 1;
|
||||
height: 10rpx;
|
||||
margin-left: 12rpx;
|
||||
border-radius: 10rpx;
|
||||
overflow: hidden;
|
||||
background: rgba(255, 255, 255, 0.1);
|
||||
position: relative;
|
||||
|
||||
.schedule {
|
||||
height: 100%;
|
||||
background: #FFF8E8;
|
||||
border-radius: 10rpx;
|
||||
left: 0;
|
||||
top: 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.vip-button {
|
||||
@include usePadding(0, 10);
|
||||
color: #FFF8E8;
|
||||
font-size: 24rpx;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
.tips {
|
||||
margin-top: 10rpx;
|
||||
color: #FFF8E8;
|
||||
font-size: 20rpx
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.vip-none {
|
||||
.vip-button {
|
||||
@include usePadding(24, 10);
|
||||
background: linear-gradient(45deg, #FAEECB, #F2D7A9);
|
||||
color: #272A3F;
|
||||
font-size: 24rpx;
|
||||
border-radius: 8rpx;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.big-card {
|
||||
width: 100%;
|
||||
background: #f6f6f6;
|
||||
border-radius: 30rpx;
|
||||
@include usePadding(32, 32);
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
</style>
|
Reference in New Issue
Block a user