💩 产品功能代码
This commit is contained in:
parent
0e58df5ff5
commit
5ca5bcb18b
158
components/common/product/list.vue
Normal file
158
components/common/product/list.vue
Normal file
@ -0,0 +1,158 @@
|
|||||||
|
<template>
|
||||||
|
<div class="wrapper">
|
||||||
|
<el-row class="row-cards row-deck" :gutter="20">
|
||||||
|
<el-col :span="8" v-for="product in products.products" :key="product.idProduct" style="margin-right: 20px;">
|
||||||
|
<el-card :body-style="{ padding: '20px' }">
|
||||||
|
<el-col :span="24">
|
||||||
|
<el-image :src="product.productImgUrl"
|
||||||
|
style="border-radius: 10px;background: #f5f7fa;border: #f5f7fa solid 1px;" fit="cover"></el-image>
|
||||||
|
</el-col>
|
||||||
|
<el-col style="padding-top: 20px;font-size: 16px;line-height: 22px;font-weight: 500;margin-bottom: 4px;">
|
||||||
|
<span v-html="product.productTitle"></span>
|
||||||
|
</el-col>
|
||||||
|
<el-col style="padding-top: 30px;text-align: right;">
|
||||||
|
<!-- <el-button type="text" class="button">立即购买</el-button>-->
|
||||||
|
<el-button type="text" class="button" @click="handleClick(product.idProduct)">查看详情</el-button>
|
||||||
|
</el-col>
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
export default {
|
||||||
|
name: "ProductList",
|
||||||
|
props: {
|
||||||
|
products: {
|
||||||
|
type: Object
|
||||||
|
}
|
||||||
|
},
|
||||||
|
methods: {
|
||||||
|
handleClick(idProduct) {
|
||||||
|
this.$router.push({
|
||||||
|
path: `/product/${idProduct}`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped>
|
||||||
|
body {
|
||||||
|
overflow-x: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);
|
||||||
|
position: relative;
|
||||||
|
margin-bottom: 1.5rem;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card {
|
||||||
|
position: relative;
|
||||||
|
display: -ms-flexbox;
|
||||||
|
display: flex;
|
||||||
|
-ms-flex-direction: column;
|
||||||
|
flex-direction: column;
|
||||||
|
min-width: 0;
|
||||||
|
word-wrap: break-word;
|
||||||
|
background-color: #fff;
|
||||||
|
background-clip: border-box;
|
||||||
|
border: 1px solid rgba(0, 40, 100, 0.12);
|
||||||
|
border-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-profile .card-header {
|
||||||
|
height: 20rem;
|
||||||
|
background-size: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header:first-child {
|
||||||
|
border-radius: calc(3px - 1px) calc(3px - 1px) 0 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
background: none;
|
||||||
|
padding: 0.5rem 1.5rem;
|
||||||
|
display: -ms-flexbox;
|
||||||
|
display: flex;
|
||||||
|
min-height: 3.5rem;
|
||||||
|
-ms-flex-align: center;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-header {
|
||||||
|
padding: 1.5rem 1.5rem;
|
||||||
|
margin-bottom: 0;
|
||||||
|
background-color: rgba(0, 0, 0, 0.03);
|
||||||
|
border-bottom: 1px solid rgba(0, 40, 100, 0.12);
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-body {
|
||||||
|
-ms-flex: 1;
|
||||||
|
flex: 1;
|
||||||
|
margin: 0;
|
||||||
|
padding: 1.5rem 1.5rem;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-profile-img {
|
||||||
|
max-width: 6rem;
|
||||||
|
margin-top: -5rem;
|
||||||
|
margin-bottom: 1rem;
|
||||||
|
border: 3px solid #fff;
|
||||||
|
border-radius: 100%;
|
||||||
|
box-shadow: 0 1px 1px rgba(0, 0, 0, 0.1);
|
||||||
|
background-color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-img-top {
|
||||||
|
border-top-left-radius: 3px;
|
||||||
|
border-top-right-radius: 3px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-img-top {
|
||||||
|
width: 100%;
|
||||||
|
border-top-left-radius: calc(3px - 1px);
|
||||||
|
border-top-right-radius: calc(3px - 1px);
|
||||||
|
}
|
||||||
|
|
||||||
|
.mb-3, .my-3 {
|
||||||
|
margin-bottom: 0.75rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
h3, .h3 {
|
||||||
|
font-size: 1.5rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mb-4, .my-4 {
|
||||||
|
margin-bottom: 1rem !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-header-md {
|
||||||
|
position: relative;
|
||||||
|
line-height: 1.4em;
|
||||||
|
height: 1.4em;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-header-md a {
|
||||||
|
font-weight: bold;
|
||||||
|
font-size: 1em;
|
||||||
|
}
|
||||||
|
|
||||||
|
.article-summary-md {
|
||||||
|
position: relative;
|
||||||
|
line-height: 1.4em;
|
||||||
|
height: 4.2em;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.el-col-6 {
|
||||||
|
padding-right: 0.75rem;
|
||||||
|
padding-left: 0.75rem;
|
||||||
|
}
|
||||||
|
|
||||||
|
</style>
|
56
pages/product/_product_id.vue
Normal file
56
pages/product/_product_id.vue
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
<template>
|
||||||
|
<el-row class="article__wrapper">
|
||||||
|
<el-col>
|
||||||
|
<el-card>
|
||||||
|
<div class="card-body d-flex flex-column article">
|
||||||
|
<div class="article__item">
|
||||||
|
<h1 class="list__title">
|
||||||
|
{{ product.productTitle }}
|
||||||
|
</h1>
|
||||||
|
</div>
|
||||||
|
<div class="pt-7 pipe-content__reset vditor-reset" id="articleContent" v-html="product.productContent"
|
||||||
|
style="overflow: hidden;"></div>
|
||||||
|
</div>
|
||||||
|
</el-card>
|
||||||
|
</el-col>
|
||||||
|
</el-row>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script>
|
||||||
|
import {mapState} from 'vuex';
|
||||||
|
|
||||||
|
export default {
|
||||||
|
name: "ProductDetail",
|
||||||
|
validate({params, store}) {
|
||||||
|
return params.product_id && !isNaN(Number(params.product_id))
|
||||||
|
},
|
||||||
|
fetch({store, params, error}) {
|
||||||
|
return Promise.all([
|
||||||
|
store
|
||||||
|
.dispatch('product/fetchDetail', params)
|
||||||
|
.catch(err => error({statusCode: 404}))
|
||||||
|
])
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState({
|
||||||
|
product: state => state.product.detail.data
|
||||||
|
})
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$store.commit('setActiveMenu', 'product');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style lang="less">
|
||||||
|
@import "~vditor/src/assets/less/index.less";
|
||||||
|
|
||||||
|
.article__wrapper {
|
||||||
|
max-width: 980px;
|
||||||
|
margin: 20px auto;
|
||||||
|
display: block;
|
||||||
|
padding-left: 1rem;
|
||||||
|
padding-right: 1rem;
|
||||||
|
box-sizing: border-box;
|
||||||
|
}
|
||||||
|
</style>
|
@ -1,35 +1,52 @@
|
|||||||
<template>
|
<template>
|
||||||
<el-row class="product__wrapper">
|
<el-row class="product__wrapper">
|
||||||
<el-col :span="8">
|
<el-col>
|
||||||
<el-card :body-style="{ padding: '0px' }">
|
<product-list :products="products" @currentChange="currentChangeProduct"></product-list>
|
||||||
<img src="https://static.rymcu.com/article/1648960741563.jpg"
|
|
||||||
class="image">
|
|
||||||
<div style="padding: 14px;">
|
|
||||||
<span>Nebula Pi</span>
|
|
||||||
<div class="bottom clearfix">
|
|
||||||
<el-button type="text" class="button">立即购买</el-button>
|
|
||||||
<el-button type="text" class="button" @click="handleClick">相关内容</el-button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</el-card>
|
|
||||||
</el-col>
|
</el-col>
|
||||||
</el-row>
|
</el-row>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
|
import {mapState} from "vuex";
|
||||||
|
import ProductList from "~/components/common/product/list";
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
name: "products",
|
name: "products",
|
||||||
|
components: {ProductList},
|
||||||
|
fetch({store, query, error}) {
|
||||||
|
return Promise.all([
|
||||||
|
store
|
||||||
|
.dispatch('product/fetchList', {page: query.page || 1})
|
||||||
|
.catch(err => error({statusCode: 404}))
|
||||||
|
])
|
||||||
|
},
|
||||||
|
watch: {
|
||||||
|
'$route.query': function () {
|
||||||
|
this.$store.dispatch('product/fetchList', {page: this.$route.query.page || 1})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
computed: {
|
||||||
|
...mapState({
|
||||||
|
products: state => state.product.list.data
|
||||||
|
})
|
||||||
|
},
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
currentDate: new Date().toLocaleString()
|
currentDate: new Date().toLocaleString()
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
handleClick() {
|
currentChangeProduct(page) {
|
||||||
this.$router.push({
|
this.$router.push({
|
||||||
path: "/nebula-pi"
|
name: 'products',
|
||||||
});
|
query: {
|
||||||
|
page: page
|
||||||
}
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
},
|
||||||
|
mounted() {
|
||||||
|
this.$store.commit('setActiveMenu', 'products');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
</script>
|
</script>
|
||||||
|
152
store/product.js
152
store/product.js
@ -0,0 +1,152 @@
|
|||||||
|
import Vue from 'vue';
|
||||||
|
import { isBrowser } from '~/environment';
|
||||||
|
|
||||||
|
export const BASE_API_PATH = '/api/console'
|
||||||
|
export const PRODUCT_API_PATH = '/api/product'
|
||||||
|
|
||||||
|
const getDefaultListData = () => {
|
||||||
|
return {
|
||||||
|
products: [],
|
||||||
|
pagination: {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const state = () => {
|
||||||
|
return {
|
||||||
|
list: {
|
||||||
|
fetching: false,
|
||||||
|
data: getDefaultListData()
|
||||||
|
},
|
||||||
|
detail: {
|
||||||
|
fetching: false,
|
||||||
|
data: {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const mutations = {
|
||||||
|
// 作品集列表
|
||||||
|
updateListFetching(state, action) {
|
||||||
|
state.list.fetching = action
|
||||||
|
},
|
||||||
|
updateListData(state, action) {
|
||||||
|
state.list.data = action
|
||||||
|
},
|
||||||
|
updateExistingListData(state, action) {
|
||||||
|
state.list.data.data.push(...action.data)
|
||||||
|
state.list.data.pagination = action.pagination
|
||||||
|
},
|
||||||
|
|
||||||
|
// 作品集详情
|
||||||
|
updateDetailFetching(state, action) {
|
||||||
|
state.detail.fetching = action
|
||||||
|
},
|
||||||
|
updateDetailData(state, action) {
|
||||||
|
state.detail.data = action.product
|
||||||
|
},
|
||||||
|
// 更新作品集阅读全文状态
|
||||||
|
updateDetailRenderedState(state, action) {
|
||||||
|
Vue.set(
|
||||||
|
state.detail.data,
|
||||||
|
'isRenderedFullContent',
|
||||||
|
action == null ? true : action
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const actions = {
|
||||||
|
// 获取作品集列表
|
||||||
|
fetchList({commit, state}, params = {}) {
|
||||||
|
|
||||||
|
// 清空已有数据
|
||||||
|
commit('updateListFetching', true)
|
||||||
|
let currentData = JSON.parse(JSON.stringify(state)).list.data
|
||||||
|
if (Number(params.page) === currentData.pagination.currentPage) {
|
||||||
|
commit('updateListFetching', false)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
let data = {
|
||||||
|
page: params.page,
|
||||||
|
topicUri: params.topic_uri
|
||||||
|
}
|
||||||
|
commit('updateListData', getDefaultListData())
|
||||||
|
|
||||||
|
return this.$axios
|
||||||
|
.$get(`${BASE_API_PATH}/products`, {
|
||||||
|
params: data
|
||||||
|
})
|
||||||
|
.then(response => {
|
||||||
|
commit('updateListFetching', false)
|
||||||
|
commit('updateListData', response)
|
||||||
|
if (isBrowser) {
|
||||||
|
Vue.nextTick(() => {
|
||||||
|
window.scrollTo(0,0);
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => commit('updateListFetching', false))
|
||||||
|
},
|
||||||
|
|
||||||
|
// 获取作品集详情
|
||||||
|
fetchDetail({ commit }, params = {}) {
|
||||||
|
// const delay = fetchDelay(
|
||||||
|
// isBrowser
|
||||||
|
// )
|
||||||
|
// if (isBrowser) {
|
||||||
|
// Vue.nextTick(() => {
|
||||||
|
// window.scrollTo(0, 300);
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
commit('updateDetailFetching', true)
|
||||||
|
// commit('updateDetailData', {})
|
||||||
|
return this.$axios
|
||||||
|
.$get(`${BASE_API_PATH}/product/${params.product_id}`)
|
||||||
|
.then(response => {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
commit('updateDetailData', response)
|
||||||
|
commit('updateDetailFetching', false)
|
||||||
|
resolve(response)
|
||||||
|
// delay(() => {
|
||||||
|
// resolve(response)
|
||||||
|
// })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
commit('updateDetailFetching', false)
|
||||||
|
return Promise.reject(error)
|
||||||
|
})
|
||||||
|
},
|
||||||
|
fetchPostDetail({ commit }, params = {}) {
|
||||||
|
// const delay = fetchDelay(
|
||||||
|
// isBrowser
|
||||||
|
// )
|
||||||
|
// if (isBrowser) {
|
||||||
|
// Vue.nextTick(() => {
|
||||||
|
// window.scrollTo(0, 300);
|
||||||
|
// })
|
||||||
|
// }
|
||||||
|
|
||||||
|
if (typeof params.product_id === 'undefined') {
|
||||||
|
commit('updateDetailData', getDefaultListData())
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
commit('updateDetailFetching', true)
|
||||||
|
// commit('updateDetailData', {})
|
||||||
|
return this.$axios
|
||||||
|
.$get(`${PRODUCT_API_PATH}/detail/${params.product_id}`)
|
||||||
|
.then(response => {
|
||||||
|
return new Promise(resolve => {
|
||||||
|
commit('updateDetailData', response)
|
||||||
|
commit('updateDetailFetching', false)
|
||||||
|
resolve(response)
|
||||||
|
// delay(() => {
|
||||||
|
// resolve(response)
|
||||||
|
// })
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
commit('updateDetailFetching', false)
|
||||||
|
return Promise.reject(error)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user