Authored by huzhiming

feat(二手):添加二手详情页

{
// 使用 IntelliSense 了解相关属性。
// 悬停以查看现有属性的描述。
// 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "attach",
"name": "Attach by Process ID",
"processId": "5390",
"skipFiles": [
"<node_internals>/**",
],
"sourceMaps": true,
"internalConsoleOptions": "openOnSessionStart"
}
]
}
... ...
... ... @@ -9,40 +9,110 @@
!-->
<template>
<LayoutApp :show-back="true" title="商品详情">
<div class="second-detail-wrap">
UfoSecondProductDetail
<LayoutScroll
ref="pageScroll"
class="second-detail-wrap"
@scroll-end="''">
<cube-slide v-if="info.imageList" :options="slideOptions" :data="info.imageList" :loop='false'>
<cube-slide-item v-for="(item, index) in info.imageList" :key="index">
<img-size :src="item" :width="375" :height="375" />
<div class="pole-dot-area"></div>
</cube-slide-item>
<template slot="dots" slot-scope="props">
<div class="dot-wrap">
<span class="cube-dot" :class="{active: props.current === index}" v-for="(item, index) in props.dots" :key="item">&bull;</span>
</div>
</template>
</cube-slide>
<div class="primary">
<div>
<p class="price">¥{{info.price}}</p>
<p class="size">{{info.sizeName}}</p>
</div>
<p class="name">{{info.describeInfo}}</p>
</div>
<div class="other-info">
<p>男款 Jordan brand</p>
<p>状况:氧化泛黄</p>
<p>鞋盒:{{ info.shoeBoxDesc }}</p>
<p>售出时间:{{ info.soldTime }}</p>
<p>{{ info.shoeQualityDesc }}</p>
</div>
<div class="extra-card">
<img-size class='image' src="//upyun.h800.com.cn/goods/1522828276980.png!/sq/1000" :width="375" :height="375" />
<div class="middle">
<p class="name ellipsis">Air Jordan 1 Rebel 20 Chicago Air Jordan 1 Rebel 20 Chicago</p>
<p class="number ellipsis">货号 8764212-001</p>
</div>
<span class="iconfont iconright"></span>
</div>
</LayoutScroll>
<div class="fixed-footer">
<cube-button class="buy active" @click="buy">购买 <span class="price">¥1209.00</span></cube-button>
</div>
<Filtrate ref="filtrate" :yasParams="{P_NAME: 'XY_UFOSearchList', TYPE_ID: 1}"></Filtrate>
</LayoutApp>
</template>
<script>
import { Button, Slide } from 'cube-ui';
import { mapState } from 'vuex'
import ImgSize from '@/components/img-size';
import Filtrate from './filtrate';
const info = {
"brandName": "NIKE",
"colorName": "黑色",
"describeInfo": "ggAir Jordan 4 Retro Motorsport Away BG AJ4 赛车白40839787-002g",
"gender": "男款",
"image": "",
"imageList": [
"http://img11.static.yhbimg.com/goodsimg/2019/11/07/17/01f5f36a2ce3e935b7481a658466c574af.jpg",
"http://img11.static.yhbimg.com/goodsimg/2019/11/07/17/0150f24aed5fe9f26df7516c9479ee39c8.jpg",
"http://img11.static.yhbimg.com/goodsimg/2019/11/07/17/01f247d64097dfadd5d510621e9d69abc3.jpg",
"http://img11.static.yhbimg.com/goodsimg/2019/11/07/17/01e758d028aeb48d40e656a708488844dc.jpg",
"http://img11.static.yhbimg.com/goodsimg/2019/11/07/17/017d63f422ee227a6fa519d44d7aa27f7f.jpg",
"http://img11.static.yhbimg.com/goodsimg/2019/11/07/17/01a15766233785ea70f2fda57e1e906ce4.jpg",
"http://img11.static.yhbimg.com/goodsimg/2019/11/07/17/01701885a5ca6b7cf8cae2fca6c0e36795.jpg"
],
"price": "99.00",
"productCode": "TB-011-A-BLK-GLD-49",
"productId": 10014795,
"productName": "DXF测试商品",
"sechondHandTypeName": "二手",
"shoeBoxDesc": "完好",
"shoeQualityDesc": "该商品没有明显瑕疵",
"sizeName": "37 2/3码",
"soldTime": "2019.03.12",
"status": 1
}
export default {
// watchQuery: [],
// layouts: '',
// head() {
// return {
// title: '',
// meta: [{ hid: 'keywords', name: 'keywords', content: '' },{ hid: 'hid', name: 'description', content: '' }],
// // link: [{ rel: 'stylesheet', href: '' }],
// // script: [{ src: '' }]
// }
// },
name: 'UfoSecondProductDetail',
mixins: [],
props: {
skup: Number
},
// // 服务端渲染函数
// async asyncData ({ isDev, route, store, env, params, query, req, res, redirect, error }) {
// return {}
// },
data() {
return {}
// 服务端渲染函数
asyncData({store, router}) {
return store.dispatch('second/fetchDetailById', { skup: router.params.skup });
},
data(){
return {
slideOptions: {
eventPassthrough: 'vertical'
},
info
}
},
created() {},
mounted() {
//
// this.skup
console.log('mounted', this.info);
},
activated() {},
deactivated() {},
... ... @@ -50,18 +120,193 @@ export default {
// beforeRouteUpdate(to, from, next) {},
// beforeRouteLeave(to, from, next) {},
destroyed() {},
methods: {},
computed: {},
methods: {
// 购买
buy() {
this.$refs.filtrate.show();
/**
* 数据埋点
* 商品详情页点击出售/购买/求购按钮
* event: XY_UFO_PRD_DT_SALE_C
* params: 1.TAB_ID:1-出售,2-购买,3-求购;
* 2.PRD_ID:商品ID;
*/
this.$store.dispatch('reportYas', {
params: {
appop: 'XY_UFO_PRD_DT_SALE_C',
param: {
TAB_ID: 2,
PRD_ID: this.productId
},
}
});
this.resetSelectedSize();
this.selectSizeConfig = {
dest: 'OrderBuyConfirm',
type: 'buy',
title: '购买',
};
this.showSizeSelectSheet = true;
}
},
computed: {
// ...mapState('second', ['info'])
},
watch: {},
components: {}
components: {
ImgSize,
Filtrate,
'cube-button': Button,
'cube-slide': Slide,
'cube-slide-item': Slide.Item,
}
};
</script>
/* 定义局部样式,添加外围容器,scss嵌套尽量不要超过三层,会影响查找器性能 */
<style rel='stylesheet/scss' lang='scss' scoped>
.second-detail-wrap {
background: red;
@import '@/pages/product/product-detail.scss';
.second-detail-wrap {}
.ellipsis {
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
word-break: break-all;
}
/* banner */
.cube-slide {
/deep/ .cube-slide-dots {
padding: 0;
}
.cube-slide-item {
img {
display: flex;
height: 375px;
}
}
.pole-dot-area {
width: inherit;
height: 21px;
}
.cube-dot {
display: inline-block;
width: 4px;
height: 4px;
margin: 0 5px;
background: rgba(0, 0, 0, 0.15);
border-radius: 50%;
&.active {
transform-origin: 50% 50%;
transform: scale(1.5);
background: rgba(0, 0, 0, 1);
}
}
}
// product information display
.primary {
margin: 0 40px 40px;
padding: 9px 0 0 0;
border-bottom: 1px solid #eee;
& > div:first-child {
display: flex;
justify-content: space-between;
align-items: center;
margin-bottom: 5px;
}
.price {
font-size: 48px;
line-height: 56px;
color: #D0021B;
&::first-letter {
font-size: 36px;
letter-spacing: 2px;
}
}
.size {
font-size: 32px;
color: #000;
line-height: 38px;
}
.name {
margin-bottom: 16px;
font-size: 28px;
color: #999;
}
}
// other-info
.other-info {
margin: 0 40px 40px;
color: #000;
font-size: 28px;
& > p:not(:last-child) {
margin-bottom: 5px;
}
}
// extra-card
.extra-card {
display: flex;
justify-content: space-between;
align-items: center;
margin: 0 40px 40px;
border: 1px solid #f2f2f2;
border-radius: 4px;
.image {
max-width: 140px;
max-height: 140px;
margin: 0 20px;
}
.middle {
flex: 1 1 auto;
display: flex;
flex-direction: column;
justify-content: space-between;
max-width: 420px;
height: 82px;
font-size: 28px;
line-height: 32px;
.number {
font-size: 24px;
color: #999;
line-height: 28px;
}
}
.iconfont {
font-size: 48px;
color: #999;
margin-right: 20px;
}
}
// fixed-footer
.fixed-footer {
position: sticky;
bottom: 0;
display: flex;
height: 120px;
padding: 16px 30px;
box-shadow: 0 0 4px rgba(0, 0, 0, 0.08);
text-align: center;
background: #fff;
@include cube-ufo-btn;
.price {
font-size: 28px;
line-height: 32px;
color: #65AB85;
&::first-letter {
font-size: 20px;
letter-spacing: 2px;
}
}
}
</style>
/* 定义全局样式,添加外围容器,避免覆盖全局样式, 若不需要,请删除 */
... ...
<!--
* @description: 筛选组件
* @fileName: filtrate.vue
* @author: huzhiming
* @date: 2019-11-21 18:22:33
* @后台人员:
* @version: v1.0.0
* @path: 页面访问路径及参数说明
* @调用示例说明:
import Filtrate from './filtrate';
<filtrate ref="filtrate" :yasParams="null"></filtrate>
components: { Filtrate }
this.$refs.filtrate.show(); // 开启
this.$refs.filtrate.hide(); // 关闭
this.$refs.filtrate.getParams(); // 获取筛选参数
!-->
<template>
<div
class="container"
v-show="showType"
>
<!-- <div class="title">筛选
<div class="cancel" @click="back()">取消</div>
</div> -->
<div class="content-search" v-if="filterData && filterData.length">
<div
class="item"
v-for="(filter,row) in filterData" :key="filter.filterId"
>
<div class="item-title">{{filter.filterName}}</div>
<div
v-if="filter.itemList && filter.itemList.length"
class="item-list"
>
<template v-if="filter.filterId === 'brand'">
<div
class="item-img-content"
:class="filterParams.brand.includes(item.itemId) && 'item-img-selected'"
v-for="(item,i) in filter.itemList" :key="item.itemId"
:style="i===0 && 'margin-left:1rem'"
@click="select({filterId:filter.filterId, itemId:item.itemId, row})"
>
<div class="item-img">
<img :src="(item.itemUrl || '').replace('http://', '//').replace('{width}', 280).replace('{height}', 140)" />
</div>
<div class="item-img-text">{{item.itemName}}</div>
</div>
</template>
<template v-if="filter.filterId === 'size'">
<div
class="item-text item-size"
:style="i===0 && 'margin-left:1rem'"
:class="filterParams.size.includes(item.itemId) && 'item-text-selected'"
v-for="(item,i) in filter.itemList" :key="item.itemId"
@click="select({filterId:filter.filterId, itemId:item.itemId, row})"
>
{{item.itemName}}
</div>
</template>
<template v-if="filter.filterId === 'sort' || filter.filterId === 'gender'">
<div
class="item-text"
:style="i===0 && 'margin-left:1rem'"
:class="(filterParams.sort.includes(item.itemId) || filterParams.gender.includes(item.itemId)) && 'item-text-selected'"
v-for="(item,i) in filter.itemList" :key="item.itemId"
@click="select({filterId:filter.filterId, itemId:item.itemId, row})"
>
{{item.itemName}}
</div>
</template>
</div>
</div>
</div>
<div class="bottom">
<div
class="clear"
@click="clear()"
>清除</div>
<div
class="submit"
@click="submit()"
>确定</div>
</div>
</div>
</template>
<script>
import { mapState, createNamespacedHelpers } from 'vuex';
const { mapActions } = createNamespacedHelpers('list');
import ImgSize from '../../components/img-size';
import { Scroll } from 'cube-ui';
const filterData = [
{
"filterId": "sort",
"filterName": "品类",
"itemList": [
{
"itemId": "44",
"itemName": "运动鞋"
},
{
"itemId": "46",
"itemName": "跑步鞋"
},
{
"itemId": "48",
"itemName": "篮球鞋"
},
{
"itemId": "50",
"itemName": "夹克"
},
{
"itemId": "52",
"itemName": "卫衣"
},
{
"itemId": "53",
"itemName": "羽绒服"
},
{
"itemId": "55",
"itemName": "短袖T恤"
},
{
"itemId": "77",
"itemName": "蜡烛"
},
{
"itemId": "2,3",
"itemName": "WOMEN"
}
],
"multiSelect": false
},
{
"filterId": "size",
"filterName": "尺码",
"itemList": [
{
"itemId": "353,557",
"itemName": "XXXXL"
},
{
"itemId": "355",
"itemName": "11.5C"
},
{
"itemId": "357",
"itemName": "12.5C"
},
{
"itemId": "251",
"itemName": "48.5"
},
{
"itemId": "253",
"itemName": "49"
},
{
"itemId": "527",
"itemName": "49 1/3"
},
{
"itemId": "465",
"itemName": "49.5"
},
{
"itemId": "467",
"itemName": "50.5"
},
{
"itemId": "667",
"itemName": "36 1/2"
},
{
"itemId": "669",
"itemName": "38 1/2"
},
{
"itemId": "671",
"itemName": "40 1/2"
},
{
"itemId": "673",
"itemName": "42 1/2"
},
{
"itemId": "469",
"itemName": "51.5"
}
],
"multiSelect": false
},
{
"filterId": "brand",
"filterName": "品牌",
"itemList": [
{
"itemId": "3",
"itemName": "NIKE",
"itemUrl": "http://img11.static.yhbimg.com/brandLogo/2018/12/26/14/01184d5c4485f4dcd91498fa4acb8282fc.png?imageMogr2/thumbnail/{width}x{height}/background/d2hpdGU=/position/center/quality/80"
},
{
"itemId": "5",
"itemName": "AIR JORDAN",
"itemUrl": "http://img11.static.yhbimg.com/brandLogo/2018/12/26/13/017147f8b0cbfb5ca24b19d89c49ae150a.png?imageMogr2/thumbnail/{width}x{height}/background/d2hpdGU=/position/center/quality/80"
},
{
"itemId": "67",
"itemName": "Timberland",
"itemUrl": "http://img11.static.yhbimg.com/brandLogo/2019/06/07/21/010dad728577dda6f5052bcb0b40ecbfd0.jpg?imageMogr2/thumbnail/{width}x{height}/background/d2hpdGU=/position/center/quality/80"
},
{
"itemId": "45",
"itemName": "CASIO",
"itemUrl": "http://img11.static.yhbimg.com/brandLogo/2019/02/26/15/0150054789d34910ac0fd2ac400c5599e7.png?imageMogr2/thumbnail/{width}x{height}/background/d2hpdGU=/position/center/quality/80"
}
],
"multiSelect": false
}
]
export default {
name: 'Filtrate',
components: { ImgSize, Scroll },
props: {
yasParams: Object,
},
data () {
return {
showType: false,
filterParams: {
sort: [], // 品类id
brand: [], // 品牌id
gender: [], // 性别
size: [], // 尺码id
},
filterData,
filterVisible: false,
copyFilterParams: {
sort: [], // 品类id
brand: [], // 品牌id
gender: [], // 性别
size: [], // 尺码id
}
};
},
activated () {
let params = { ...this.$route.query };
if (this.yoho.direction === 'forword') {
Object.assign(this.$data, this.$options.data());
!params.order && (params.order = 'sale_desc');
this.fetchData(params);
}
},
computed: {
...mapState(['yoho'])
},
methods: {
...mapActions(['fetchFilterData']),
clear () {
let filterParams = {
sort: [], // 品类id
brand: [], // 品牌id
gender: [], // 性别
size: [], // 尺码id
};
for (let item of this.filterData) {
if (item.itemList && item.itemList.length === 1) {
filterParams[item.filterId].push(item.itemList[0].itemId);
}
}
this.setFilterParam(filterParams);
},
fetchData: async function (params) {
let filterData = await this.fetchFilterData(params);
let filterParams = this.filterParams;
for (let item of filterData) {
if (item.itemList && item.itemList.length === 1) {
filterParams[item.filterId].push(item.itemList[0].itemId);
}
}
this.filterData = filterData;
this.filterParams = filterParams;
},
back () {
this.filterParams = { ...this.copyFilterParams };
this.hide();
},
submit () {
let routeParams = this.$route.query;
this.copyFilterParams = { ...this.filterParams };
let params = {
sort: this.filterParams.sort.join(',') || routeParams.sort,
brand: this.filterParams.brand.join(','), // 品牌id
gender: this.filterParams.gender.join(','), // 性别
size: this.filterParams.size.join(','), // 尺码id
};
this.filterData.length > 0 && this.filterData.map(row => {
if (row.itemList && row.itemList.length === 1) {
params[row.filterId] = row.itemList[0].itemId;
}
});
let ENT_PARAMS = {}, ENT_ID = [], ENT_NAME = [];
for (let key in params) {
if (key === 'sort' && params[key]) {
ENT_PARAMS.category = params[key];
ENT_ID.push(params[key]);
ENT_NAME.push('品类');
}
if (key === 'brand' && params[key]) {
ENT_PARAMS.brand = params[key];
ENT_ID.push(params[key]);
ENT_NAME.push('品牌');
}
if (key === 'size' && params[key]) {
ENT_PARAMS.size = params[key];
ENT_ID.push(params[key]);
ENT_NAME.push('尺寸');
}
if (key === 'gender' && params[key]) {
ENT_PARAMS.sex = params[key];
ENT_ID.push(params[key]);
ENT_NAME.push('性别');
}
}
this.yasParams.ENT_PARAMS = JSON.stringify(ENT_PARAMS);
this.yasParams.ENT_ID = ENT_ID.toString();
this.yasParams.ENT_NAME = ENT_NAME.toString();
params.isReset = true;
this.yas(this.yasParams);
this.$parent.fetchList(params);
this.$parent.$refs.scroll.scrollTo(0, 0, 300);
this.hide();
},
getParams () {
return this.filterParams;
},
select ({ filterId, itemId, row }) {
let optParams = this.filterParams;
let filterItemArray = optParams[filterId];
let rowData = this.filterData[row];
if (rowData && rowData.itemList && rowData.itemList.length === 1) {
return;
}
if (filterItemArray.includes(itemId)) {
filterItemArray = filterItemArray.filter(item => item !== itemId);
} else {
if (this.filterData[row].multiSelect) {
filterItemArray.push(itemId);
} else {
filterItemArray = [itemId];
}
}
optParams[filterId] = filterItemArray;
this.setFilterParam(optParams);
},
setFilterParam (filter) {
let filterParams = this.filterParams;
if (typeof filter === 'object' && Object.keys(filter).length) {
for (let key in filter) {
filterParams[key] = filter[key];
}
this.filterParams = filterParams;
}
},
show () {
this.showType = true;
},
hide () {
this.showType = false;
},
yas (param) {
this.$store.dispatch('reportYas', {
params: {
param,
appop: 'XY_UFO_PRD_LIST_SCREEN_C'
}
});
}
}
};
</script>
<style lang="scss" scoped>
.header {
width: 100%;
height: 45px;
padding-left: 20px;
padding-right: 20px;
background-color: #fff;
display: flex;
align-items: stretch;
box-sizing: border-box;
}
.container {
position: absolute;
top: 0;
left: 0;
right: 0;
bottom: 0;
background: #fff;
z-index: 999;
}
.title {
font-size: 68px;
font-weight: bold;
color: #000;
height: 82px;
line-height: 82px;
margin: 40px;
flex-direction: row;
display: flex;
justify-content: space-between;
.cancel {
font-size: 36px;
color: #888;
}
}
.content-search {
margin-top: 80px;
.item {
margin-bottom: 60px;
}
.item-title {
margin-left: 40px;
margin-bottom: 20px;
font-size: 40px;
color: #000;
letter-spacing: 0;
font-weight: 500;
}
.item-list {
overflow-y: hidden;
overflow-x: scroll;
display: -webkit-box;
width: 100%;
}
.item-list ::-webkit-scrollbar {
display: none;
}
.item-text {
background: #f5f5f5;
font-size: 28px;
color: #222;
text-align: center;
width: 200px;
height: 80px;
line-height: 80px;
margin-right: 20px;
border-radius: 40px;
}
.item-text-selected {
background: #08304b;
color: #fff;
}
.item-size {
width: 130px;
height: 80px;
line-height: 80px;
}
.item-img {
width: 140px;
height: 70px;
margin-bottom: 22px;
& > img {
width: 100%;
height: 100%;
}
}
.item-img-selected {
opacity: 1 !important;
}
.item-img-content {
opacity: 0.2;
width: 160px;
height: 140px;
display: flex;
justify-content: center;
flex-direction: column;
margin-right: 20px;
}
.item-img-text {
font-size: 20px;
color: #000;
letter-spacing: 0;
text-align: center;
width: 140px;
height: 64px;
}
}
.bottom {
height: 120px;
position: fixed;
bottom: 0;
z-index: 9;
width: 100%;
background: #fff;
display: flex;
border-top: 1px solid rgba(0, 0, 0, 0.12);
justify-content: space-between;
align-items: center;
}
.clear {
margin-left: 32px;
line-height: 80px;
text-align: center;
width: 330px;
height: 80px;
color: #000;
font-size: 32px;
border: 1px solid rgba(0, 0, 0, 0.12);
border-radius: 40px;
}
.submit {
line-height: 80px;
background-color: #08304b;
width: 330px;
height: 80px;
text-align: center;
color: #fff;
font-size: 32px;
border-radius: 40px;
margin-right: 32px;
}
.back {
margin-top: 24px;
width: 24px;
height: 24px;
background: url(~statics/image/order/back@3x.png) no-repeat;
background-size: cover;
}
</style>
... ...
... ... @@ -3,5 +3,10 @@ import { get } from 'lodash';
import Vue from 'vue';
export default {
async fetchDetailById({ commit, state }, { skup = null }) {
let { data: info = null } = await this.$api.post('/api/ufo/secondhand/productDetail', { skup });
console.log('====', info);
commit(Types.UPDATE_PRODUCT_DETAIL, {info});
},
};
... ...
import actions from './actions';
import mutations from './mutations';
export function defaultState () {
return {
info: {
imageList: [],
price: '',
sizeName: '',
shoeBoxDesc: '',
}
};
}
export default function() {
return {
namespaced: true,
state: {},
state: defaultState(),
mutations,
actions,
};
... ...
... ... @@ -2,7 +2,7 @@ import * as Types from './types';
import { find, set } from 'lodash';
export default {
[Types.ENSURE_PRODUCT_DETAIL](state, { productId }) {
[Types.UPDATE_PRODUCT_DETAIL](state, { info }) {
state.info = { ...state.info, ...info };
}
};
... ...
export const ENSURE_PRODUCT_DETAIL = 'UPDATE_PRODUCT_DETAIL';
export const UPDATE_PRODUCT_DETAIL = 'UPDATE_PRODUCT_DETAIL';
... ...
... ... @@ -10,6 +10,7 @@ const categoryApi = require('./category-api-map');
const sellerAskApi = require('./sellerask-api-map');
const systemApi = require('./system-api-map');
const activitysApi = require('./activitys-api-map');
const secondHandApi = require('./second-hand-api-map');
module.exports = {
...orderApi,
... ... @@ -23,5 +24,6 @@ module.exports = {
...categoryApi,
...sellerAskApi,
...systemApi,
...activitysApi
...activitysApi,
...secondHandApi,
};
... ...
module.exports = {
'/api/ufo/secondhand/productDetail': {
ufo: true,
auth: false,
api: 'ufo.product.secondHand.data',
params: {
skup: { type: Number } // 商品池id
... ...