Browse Source

add mall logic for tid1878

tid1878
刘嘉炜 4 months ago
parent
commit
d4a40f7abc
  1. 132
      src/subpackage/mall/components/appraise_item.vue
  2. 6
      src/subpackage/mall/components/bottom_modal.vue
  3. 10
      src/subpackage/mall/components/goods_item.vue
  4. 25
      src/subpackage/mall/components/specification_modal.vue
  5. 3
      src/subpackage/mall/js/api.js
  6. 286
      src/subpackage/mall/pages/goods_info.vue
  7. 7
      src/subpackage/mall/pages/index.vue
  8. 9
      src/subpackage/mall/pages/modules/goods_info/delivery_modal.vue
  9. 29
      src/subpackage/mall/pages/modules/goods_info/service_modal.vue
  10. BIN
      src/subpackage/mall/static/images/arrow_874.png

132
src/subpackage/mall/components/appraise_item.vue

@ -0,0 +1,132 @@
<template>
<!-- 商品详情 评价列表部分 -->
<view class="appraise-item">
<view class="ai-head">
<view class="ah-info">
<image class="ai-img" mode="aspectFill" :src="avater"></image>
<view class="ai-box">
<view class="ab-name">{{ name || '-' }}</view>
<view class="ab-star">
<image src="/subpackage/mall/static/images/star.png" v-for="i in formatLevelNum" :key="i"></image>
<text>{{ getLevelTxt }}</text>
</view>
</view>
</view>
<view class="ah-time">{{ time || '-' }}</view>
</view>
<view class="ai-desc">{{ appraise || '' }}</view>
<block v-for="(e, i) in imgList" :key="i">
<image class="ai-img-out" mode="aspectFill" :src="e"></image>
</block>
</view>
</template>
<script>
export default {
props:{
name: {
type: String,
default: ''
},
avater: {
type: String,
default: ''
},
time: {
type: String,
default: ''
},
appraise: {
type: String,
default: ''
},
levelNum: {
type: Number,
default: -1
},
imgList: {
type: Array,
default: ()=>[]
}
},
computed: {
getLevelTxt(){
let { levelNum } = this;
let arr = ['非常不满意', '不满意', '一般', '比较满意', '非常满意'];
if(!isNaN(levelNum)&&levelNum>5)levelNum = 5;
if(!isNaN(levelNum)&&levelNum >= 1&&levelNum<=5)return arr[levelNum - 1];
return '';
},
formatLevelNum(){
let { levelNum } = this;
if(!isNaN(levelNum)&&levelNum>5)levelNum = 5;
if(!isNaN(levelNum)&&levelNum >= 1&&levelNum<=5)return levelNum;
return 0;
}
},
}
</script>
<style lang="scss">
.appraise-item{
padding: 34rpx 0rpx 6rpx 30rpx;
background-color: #fff;
.ai-head{
display: flex;
// align-items: baseline;
flex-direction: row;
justify-content: space-between;
margin-right: 26rpx;
.ah-info{
@include ctf(flex-start);
.ai-img{
flex-shrink: 0;
width: 64rpx;
height: 64rpx;
border-radius: 64rpx;
margin-top: -4rpx;
}
.ai-box{
margin-left: 10rpx;
.ab-name{
color: #333;
font-size: 24rpx;
@include tHide;
}
.ab-star{
@include ctf(flex-start);
>image{
display: inline-block;
width: 28rpx;
height: 28rpx;
}
>text{
color: #9A9A9D;
font-size: 24rpx;
margin-left: 6rpx;
}
}
}
}
.ah-time{
flex-shrink: 0;
color: #9A9A9D;
font-size: 20rpx;
}
}
.ai-desc{
margin-top: 20rpx;
color: #333;
font-size: 26rpx;
margin-right: 26rpx;
}
.ai-img-out{
display: inline-block;
width: 216rpx;
height: 216rpx;
margin: 20rpx 21rpx 0 2rpx;
border-radius: 12rpx;
}
}
</style>

6
src/subpackage/mall/components/bottom_modal.vue

@ -1,10 +1,10 @@
<template>
<view class="bottom-modal" @touchmove.stop.prevent="_=>false" v-show="show">
<view class="bm-container">
<view class="bottom-modal" @touchmove.stop.prevent="_=>false" v-show="show" @click="$emit('click:mask')">
<view class="bm-container" @click.stop.prevent="_=>false">
<view class="bc-title" v-if="title">{{ title }}</view>
<slot></slot>
<view class="bc-bot-btn">
<view class="bbb-item">关闭</view>
<view class="bbb-item" @click="$emit('click:close')">关闭</view>
</view>
</view>
</view>

10
src/subpackage/mall/components/goods_item.vue

@ -24,7 +24,11 @@
import { routeTo } from '@/utils/util';
export default {
props: {
id: {
productId: {
type: Number,
default: 0
},
brandId: {
type: Number,
default: 0
},
@ -51,8 +55,8 @@ export default {
},
methods: {
itemClick(){
let { id } = this;
routeTo(`/subpackage/mall/pages/goods_info/goods_info?product_id=${id}`,'nT')
let { productId, brandId } = this;
routeTo(`/subpackage/mall/pages/goods_info?id=${productId}&brand_id=${brandId}`,'nT')
}
}
}

25
src/subpackage/mall/components/specification_modal.vue

@ -36,7 +36,7 @@
<view class="nb-txt">购买数量</view>
<number-operate v-model="goodsNum" :min='1' :max="getMaxStock"></number-operate>
</view>
<line-button @click="addCartBtn">加入购物车</line-button>
<line-button @click="buttonClick">{{ buttonTxt }}</line-button>
</view>
</modal-box>
</template>
@ -54,6 +54,12 @@ export default {
lineButton,
numberOperate
},
props: {
buttonTxt: {
type: String,
default: '确定'
}
},
computed: {
goodsSpecArr(){
let { initData } = this;
@ -109,6 +115,10 @@ export default {
* @param {Number} specArr[].id 规格id
* @param {String} specArr[].name 规格名称
* @param {Array} specArr[].value 规格值
* @param {Boolean} onlyGet 是否只获取选择数据
* @param {Function} success 回调函数
}
}
*/
},
@ -131,12 +141,21 @@ export default {
},
methods: {
//
addCartBtn: debounce(async function() {
buttonClick: debounce(async function() {
let { goodsNum, initData, goodsSpecSelInfo, specSelectedArr, isCompleteSpecSelected } = this;
if(!isCompleteSpecSelected)return showNone('请选择规格!');
if(goodsNum == 0)return showNone('请选择购买数量!');
if(goodsSpecSelInfo === null)return showNone('库存加载失败,稍后重试!');
if(goodsSpecSelInfo?.nums < goodsNum)return showNone('库存不足!');
if(initData?.onlyGet){
let _selectInfo = {
nums: goodsNum,
spec_arr: specSelectedArr.map(item => item.val)
}
initData?.success?.(_selectInfo);
this.$emit('click:confirm')
return
}
await this.goodsCartAdd({
brand_id: initData?.brand_id || '',
id: initData?.id || '',
@ -205,7 +224,7 @@ export default {
if(_data.code === 0){
console.log('subpackage mall components specification goodsCartAdd res --->', _data);
showNone('加入购物车成功~');
return _data?.data;
return _data?.data ?? true;
}else{
return Promise.reject(_data);
}

3
src/subpackage/mall/js/api.js

@ -5,6 +5,9 @@ export const MALL_API = {
goodsCartAdd:`${ORIGIN}/shop2/goodsCartAdd`, //购物车 - 添加商品
goodsSpecSel:`${ORIGIN}/shop2/goodsSpecSel`, //商品规格选择【返回库存&价格】
goodsInfo:`${ORIGIN}/shop2/goodsInfo`, //商品详情
homeGoodsBuy:`${ORIGIN}/shop2/homeGoodsBuy`, //商品详情 - 立即购买
goodsCartList:`${ORIGIN}/shop2/goodsCartList`, //购物车 - 商品列表
goodsComment:`${ORIGIN}/shop2/goodsComment`, //商品评价列表
}
export default { ORIGIN, MALL_API };

286
src/subpackage/mall/pages/goods_info.vue

@ -3,7 +3,7 @@
<view class="gi-header">
<!-- banner -->
<view class="gi-box">
<swiper class="gi-banner" @change="curBannerIdx = $event.detail.current" autoplay>
<swiper class="gi-banner" @change="curBannerIdx = $event.detail.current" autoplay circular>
<swiper-item v-for="(e, i) in goodsInfo.product_imgs" :key="i">
<image class="gb-img" mode="aspectFill" :src="e"></image>
</swiper-item>
@ -47,14 +47,14 @@
</view>
<!-- 配送 说明 已选 -->
<view class="gi-selected">
<view class="gs-item">
<view class="gs-item" @click="deliveryLineClick">
<view class="gs-name">配送</view>
<view class="gs-content">
<text class="gc-txt">{{ getDeliveryTxt }}</text>
</view>
<image class="gs-icon" mode="aspectFit" src="/subpackage/mall/static/images/arrow_9ad.png"></image>
</view>
<view class="gs-item">
<view class="gs-item" @click="explainLineClick">
<view class="gs-name">说明</view>
<view class="gs-content">
<block v-for="(e, i) in getExplianKeyTxt" :key="i">
@ -64,17 +64,37 @@
</view>
<image class="gs-icon" mode="aspectFit" src="/subpackage/mall/static/images/arrow_9ad.png"></image>
</view>
<view class="gs-item">
<view class="gs-item" @click="specificationLineClick" v-if="goodsInfo.product_spec_multi === 1">
<view class="gs-name">已选</view>
<view class="gs-content">
<block v-for="i in 3" :key="i">
<image class="gc-icon"></image>
<text class="gc-txt">配送/自提</text>
</block>
<text class="gc-txt">{{ selectedSpecificationText }}</text>
</view>
<image class="gs-icon" mode="aspectFit" src="/subpackage/mall/static/images/arrow_9ad.png"></image>
</view>
</view>
<view class="gi-appraise-info">
<view class="gai-title-bar">
<view class="gtb-tit">评价({{ appraiseList.length || 0 }})</view>
<view class="gtb-link">
<text>查看全部</text>
<image class="gl-icon" mode="aspectFit" src="/subpackage/mall/static/images/arrow_874.png"></image>
</view>
</view>
<view class="gai-ls">
<block v-for="(e, i) in appraiseList" :key="i">
<appraise-item
:name="e.sys_optuname"
:avater="e.sys_optuimgs"
:time="e.created_at"
:appraise="e.product_comment_text"
:level-num="e.product_comment_level"
:img-list="e.product_comment_imgs"
></appraise-item>
</block>
</view>
</view>
<!-- 商品详情 -->
<view class="gi-detail">
<view class="gd-title">
@ -94,17 +114,19 @@
<view class="gl-box">
<image class="gb-icon" mode="aspectFit" src="/subpackage/mall/static/images/shopping_cart.png"></image>
<view class="gb-txt">购物车</view>
<view class="gb-num">99</view>
<view class="gb-num" v-if="goodsCartNum>0">{{ goodsCartNum }}</view>
</view>
</view>
<view class="gfb-btns">
<view class="gb-item">加入购物车</view>
<view class="gb-item">立即购买</view>
<view class="gb-item" @click="addShoppingCart">加入购物车</view>
<view class="gb-item" @click="buyImmediately">立即购买</view>
</view>
</view>
<service-modal ref="serviceModal"></service-modal>
<delivery-modal ref="deliveryModal"></delivery-modal>
<service-modal ref="serviceModal" :explian-obj="goodsInfo.product_service_tags"></service-modal>
<delivery-modal ref="deliveryModal" :explian-str="goodsInfo.product_logistics_desc || ''"></delivery-modal>
<specification-modal ref="specificationModal" ></specification-modal>
</view>
</template>
@ -112,17 +134,24 @@
import { routeTo, showLoad, hideLoad, showModal, tsRoute, jsonStr, jsonPar } from "@/utils/util.js";
import serviceModal from "./modules/goods_info/service_modal.vue";
import deliveryModal from "./modules/goods_info/delivery_modal.vue";
import specificationModal from "../components/specification_modal.vue";
import appraiseItem from "../components/appraise_item.vue";
import { MALL_API } from "../js/api";
import server from "../js/server";
export default {
components: {
serviceModal,
deliveryModal,
specificationModal,
appraiseItem,
},
data(){
return {
goodsInfo: {},
curBannerIdx: 0,
goodsInfo: {}, //
curBannerIdx: 0, // banner
selectedSpecificationInfo: {}, //
goodsCartNum: 0, //
appraiseList: [], //
}
},
computed: {
@ -180,15 +209,137 @@ export default {
if(isNaN(_num))return 0;
let _rNum = Math.round(_num)
return _rNum > 5 ? 5 : _rNum;
},
selectedSpecificationText(){
let { selectedSpecificationInfo } = this;
if(selectedSpecificationInfo?.spec_arr?.length)return selectedSpecificationInfo?.spec_arr?.join(';');
return ''
}
},
onLoad(options){
let _bid = options?.brand_id ?? '';
this.getGoodsInfo({
brand_id: options?.brand_id ?? '',
brand_id: _bid,
id: options?.id ?? ''
})
});
this.goodsCartList({ brand_id: _bid });
this.goodsComment({
brand_id: _bid,
proid: options?.id ?? ''
});
},
methods: {
specificationLineClick(){
let { goodsInfo, getQueryForSpecificationModal } = this;
let _sModalRef = this.$refs.specificationModal;
_sModalRef.alert({
...getQueryForSpecificationModal(goodsInfo),
onlyGet: true,
success: async res => {
this.selectedSpecificationInfo = res;
_sModalRef.hide();
}
});
},
//
addShoppingCart(){
let { goodsInfo, getQueryForSpecificationModal } = this;
let _sModalRef = this.$refs.specificationModal;
//
if(goodsInfo?.product_spec_multi === 0)return this.addShoppingCartOperate({
brand_id: goodsInfo?.brand_id,
id: goodsInfo?.id,
});
//
_sModalRef.alert({
...getQueryForSpecificationModal(goodsInfo),
onlyGet: true,
success: async res => {
this.selectedSpecificationInfo = res;
this.addShoppingCartOperate({
brand_id: goodsInfo?.brand_id ?? '',
id: goodsInfo?.id ?? '',
nums: res?.nums ?? '',
spec_arr: res?.spec_arr ?? []
})
}
});
},
async addShoppingCartOperate({ brand_id, id, nums = 1, spec_arr = [] }){
let _sModalRef = this.$refs.specificationModal;
let _cartAddRes = await _sModalRef.goodsCartAdd({ brand_id, id, nums, spec_arr });
if(_cartAddRes){
_sModalRef.hide();
this.goodsCartList({ brand_id });
}
},
//
buyImmediately(){
let { goodsInfo, getQueryForSpecificationModal } = this;
let _sModalRef = this.$refs.specificationModal;
//
if(goodsInfo?.product_spec_multi === 0)return this.buyOperate({
brand_id: goodsInfo?.brand_id,
id: goodsInfo?.id,
});
//
_sModalRef.alert({
...getQueryForSpecificationModal(goodsInfo),
onlyGet: true,
success: async res => {
this.selectedSpecificationInfo = res;
this.buyOperate({
brand_id: goodsInfo?.brand_id ?? '',
id: goodsInfo?.id ?? '',
nums: res?.nums ?? '',
spec_arr: res?.spec_arr ?? []
});
}
});
},
async buyOperate({ brand_id, id, nums = 1, spec_arr = [] }){
let hgbRes = await this.homeGoodsBuy({ brand_id, id, nums, spec_arr });
console.log('hgbRes = ', hgbRes);
//
if(hgbRes?.total&&hgbRes?.list.length){
let _amount = hgbRes.list.reduce((total, item, idx)=>{
let _unitPrice = 0;
if(item?.product_spec_multi === 0)_unitPrice = item?.product_spec_single_info?.price ?? 0;
if(item?.product_spec_multi === 1)_unitPrice = item?.product_spec_multi_info?.price ?? 0;
let _price = (item?.product_nums ?? 0) * _unitPrice;
return total + _price;
}, 0);
let _query = {
goodsList: hgbRes?.list,
product_ids: [ id ],
go_buy: 1,
price_amount: _amount
}
console.log(_query);
}else{
showModal({content: '购买操作失败,请稍后重试!'});
console.warn('homeGoodsBuy res =>', hgbRes);
}
},
getQueryForSpecificationModal(goodsInfo){
return {
id: goodsInfo?.id ?? '',
brand_id: goodsInfo?.brand_id ?? '',
poster: goodsInfo?.product_imgs?.[0] ?? '',
name: goodsInfo?.product_name ?? '',
price: goodsInfo?.product_price ?? '',
specArr: goodsInfo?.product_spec_multi_info?.spec_data ?? [],
}
},
//
explainLineClick(){
this.$refs?.serviceModal?.show?.();
},
//
deliveryLineClick(){
this.$refs?.deliveryModal?.show?.();
},
//
getGoodsInfo({ brand_id, id }){
showLoad();
return server.post({
@ -215,6 +366,82 @@ export default {
console.warn('subpackage mall pages goods_info getGoodsInfo err --->', err);
})
},
//
homeGoodsBuy({ brand_id, id, nums, spec_arr }){
showLoad();
return server.post({
url: MALL_API.homeGoodsBuy,
data: { brand_id, id, nums, spec_arr },
isDefaultGet: false,
})
.then(res => {
hideLoad();
let _data = res?.data || {};
if(_data.code === 0){
console.log('subpackage mall pages goods_info homeGoodsBuy res --->', _data);
return _data?.data;
}else{
return Promise.reject(_data);
}
})
.catch(err => {
hideLoad();
showModal({
title: '提示',
content: err.message || '操作失败!'
})
console.warn('subpackage mall pages goods_info homeGoodsBuy err --->', err);
})
},
//
goodsCartList({ brand_id }){
return server.post({
url: MALL_API.goodsCartList,
data: { brand_id },
isDefaultGet: false,
})
.then(res => {
let _data = res?.data || {};
if(_data.code === 0){
console.log('subpackage mall pages goods_info goodsCartList res --->', _data);
let _ls = _data?.data ?? [];
//product_invalid 0- 1-
let validLs = _ls.filter(item => item.product_invalid === 0);
let _length = validLs?.length ?? 0;
this.goodsCartNum = _length > 100 ? 99 : _length;
return _ls;
}else{
return Promise.reject(_data);
}
})
.catch(err => {
console.warn('subpackage mall pages goods_info goodsCartList err --->', err);
})
},
/**
* 获取商品评价列表
* @param {Number} proid 商品id
* */
goodsComment({ brand_id, proid }){
return server.post({
url: MALL_API.goodsComment,
data: { brand_id, proid },
isDefaultGet: false,
})
.then(res => {
let _data = res?.data || {};
if(_data.code === 0){
console.log('subpackage mall pages goods_info goodsComment res --->', _data);
let _ls = _data?.data?.list ?? [];
return this.appraiseList = _ls;
}else{
return Promise.reject(_data);
}
})
.catch(err => {
console.warn('subpackage mall pages goods_info goodsComment err --->', err);
})
},
}
}
</script>
@ -382,6 +609,30 @@ export default {
}
}
}
.gi-appraise-info{
margin-top: 20rpx;
padding: 40upx 0upx;
background: #fff;
.gai-title-bar{
padding: 0 28upx;
@include ctf(space-between);
.gtb-tit{
@include flcw(28upx, 40upx, #333);
@include tHide;
}
.gtb-link{
flex-shrink: 0;
@include flcw(24upx, 34upx, $mColor);
.gl-icon{
vertical-align: middle;
width: 28upx;
height: 28upx;
}
}
}
}
.gi-detail{
margin-top: 20rpx;
background-color: #fff;
@ -462,6 +713,7 @@ export default {
border-radius: 36rpx;
background-color: #e60213;
@include flcw(22rpx, 36rpx, #fff);
@include tHide;
}
}
}

7
src/subpackage/mall/pages/index.vue

@ -55,7 +55,8 @@
:poster="(e.product_imgs&&e.product_imgs[0]) || ''"
:name="e.product_name"
:price="e.product_price"
:id="e.id"
:product-id="e.id"
:brand-id="brand_id"
:is-del-price="e.product_spec_multi === 0&&e.product_price_show != 0"
:del-price="e.product_price_show || 0"
@click:add="goodsItemAddBtn(e)"
@ -65,9 +66,7 @@
</block>
<!-- 分类 -->
<spacification-modal
ref="spacificationModal"
></spacification-modal>
<spacification-modal ref="spacificationModal" button-txt="加入购物车"></spacification-modal>
</view>
</template>

9
src/subpackage/mall/pages/modules/goods_info/delivery_modal.vue

@ -1,9 +1,7 @@
<template>
<bottom-modal title="配送说明" :show="isShow">
<bottom-modal title="配送说明" :show="isShow" @click:mask="hide" @click:close="hide">
<scroll-view class="gi-desc" scroll-y>
<view class="gd-item">撒发大沙发舒服</view>
<view class="gd-item">撒发大沙发舒服撒发大沙发舒服</view>
<view class="gd-item" v-for="i in 10" :key="i">撒发大沙发舒服撒发大沙发舒服撒发大沙发舒服</view>
<view class="gd-item" v-for="(e, i) in explainArr" :key="i">{{ e || '-' }}</view>
</scroll-view>
</bottom-modal>
</template>
@ -22,7 +20,8 @@ export default {
explainArr(){
let { explianStr } = this;
if(!explianStr)return [];
let _eArr = explianStr.split(/[(\r\n)\r\n]+/);
return _eArr.filter(item => !!item);
}
},
data() {

29
src/subpackage/mall/pages/modules/goods_info/service_modal.vue

@ -1,12 +1,12 @@
<template>
<bottom-modal title="服务说明">
<bottom-modal title="服务说明" :show="isShow" @click:mask="hide" @click:close="hide">
<scroll-view class="gi-service" scroll-y>
<view class="gs-item" v-for="i in 10" :key="i">
<view class="gs-item" v-for="(val, key, idx) in explianObj" :key="idx">
<view class="gi-tit">
<image class="gt-icon" mode="aspectFit" src="/subpackage/mall/static/images/selected.png"></image>
<text class="gt-txt">7天无理由退换货</text>
<text class="gt-txt">{{ key || '-' }}</text>
</view>
<view class="gi-line">支持物流配送全国范围包邮部分偏远地区仅发EMS快递港澳台地区暂不支持配送</view>
<view class="gi-line">{{ val || '-' }}</view>
</view>
</scroll-view>
</bottom-modal>
@ -15,7 +15,26 @@
<script>
import bottomModal from "../../../components/bottom_modal.vue";
export default {
components: { bottomModal }
components: { bottomModal },
props: {
explianObj: {
type: Object,
default: {}
}
},
data() {
return {
isShow: false
}
},
methods: {
show(){
this.isShow = true
},
hide(){
this.isShow = false
}
}
}
</script>

BIN
src/subpackage/mall/static/images/arrow_874.png

After

Width: 28  |  Height: 30  |  Size: 243 B

Loading…
Cancel
Save