Commit aaa4b0a3 authored by 郝聪敏's avatar 郝聪敏

feature: 添加优惠券

parent e79407ce
......@@ -37,7 +37,7 @@ export default class EditorController extends Controller {
pageDescribe: pageDescribe && { like: `%${pageDescribe}%`},
isPublish,
enable: 1
}, v => !trim(v));
}, v => !v);
if (type === 'list') {
where = { ...where, isPublish: 1 };
} else if (type === 'my') {
......
......@@ -44,4 +44,150 @@ export default {
accessToken: true
});
},
// todo: 对外接口需提供x-auth-token
// 购物车-添加商品
addShopCart(params) {
return http.post(`${config.kdspHost}/api/kdsp/shop-cart/add-update`, params);
},
// 商品组或专题查询
getGoods(params) {
// return http.post(`${config.kdspHost}/api/kdsp/activity/activity-goods-special/skus`, params);
return {
skus: [
{
skuNo: '100014565800',
skuName: '【自营】【自营】小米手机 陶瓷黑 8GB+128GB 官方标配',
skuUrl: 'https://img14.360buyimg.com/n0/jfs/t1/141986/32/5318/98164/5f3236baE713fd239/5f2746db41f3e9c0.jpg',
salePrice: 2.85,
marketPrice: 293
},
{
skuNo: '100014565820',
skuName: '【自营】[自营][自营]0',
skuUrl: 'https://img14.360buyimg.com/n0/jfs/t1/141986/32/5318/98164/5f3236baE713fd239/5f2746db41f3e9c0.jpg',
salePrice: 264,
marketPrice: 300
},
{
skuNo: '100014565820',
skuName: '【自营】[自营][自营]0',
skuUrl: 'https://img14.360buyimg.com/n0/jfs/t1/141986/32/5318/98164/5f3236baE713fd239/5f2746db41f3e9c0.jpg',
salePrice: 264,
marketPrice: 300
},
{
skuNo: '100014565800',
skuName: '【自营】【自营】小米手机 陶瓷黑 8GB+128GB 官方标配',
skuUrl: 'https://img14.360buyimg.com/n0/jfs/t1/141986/32/5318/98164/5f3236baE713fd239/5f2746db41f3e9c0.jpg',
salePrice: 2.85,
marketPrice: 293
},
{
skuNo: '100014565820',
skuName: '【自营】[自营][自营]0',
skuUrl: 'https://img14.360buyimg.com/n0/jfs/t1/141986/32/5318/98164/5f3236baE713fd239/5f2746db41f3e9c0.jpg',
salePrice: 264,
marketPrice: 300
},
{
skuNo: '100014565820',
skuName: '【自营】[自营][自营]0',
skuUrl: 'https://img14.360buyimg.com/n0/jfs/t1/141986/32/5318/98164/5f3236baE713fd239/5f2746db41f3e9c0.jpg',
salePrice: 264,
marketPrice: 300
}
]
};
},
// 根据id查询优惠券列表
getCoupons(params) {
// return http.post(`${config.kdspHost}/api/kdsp/coupon/list`, params, {
// hideToken: true,
// headers: {
// 'x-auth-token': '97466ed6-ec03-452e-be8f-763a8ffafefe'
// }
// });
return {
coupons: [
{
id: 1,
pickupId: 1,
name: '3C数码会场',
startDate: '2021-02-22',
endDate: '2021-02-22',
faceValue: 100,
limitAmount: 300,
limitDesc: '满300减100',
couponCategory: 1,
iconUrl: 'in esse',
description: 'officia do',
status: 19667180,
navUrl: 'exercitation est',
pickupAble: false,
publishCountFinished: false
},
{
id: 2,
pickupId: 1,
name: '3C数码会场',
startDate: '2021-02-22',
endDate: '2021-02-22',
faceValue: 100,
limitAmount: 300,
limitDesc: '满300减100',
couponCategory: 1,
iconUrl: 'oc',
description: 'minim dolore tempor',
status: 63205995,
navUrl: 'cupidat',
pickupAble: true,
publishCountFinished: false
},
{
id: 3,
pickupId: 1,
name: '3C数码会场',
startDate: '2021-02-22',
endDate: '2021-02-22',
faceValue: 100,
limitAmount: 300,
limitDesc: '满300减100',
couponCategory: 1,
iconUrl: 'eu fugiat commodo voluptate exercitation',
description: 'nisi',
status: 43999314,
navUrl: 'commodo in reprehenderit',
pickupAble: false,
publishCountFinished: true
},
{
id: 4,
pickupId: 1,
name: '3C数码会场',
startDate: '2021-02-22',
endDate: '2021-02-22',
faceValue: 100,
limitAmount: 300,
limitDesc: '满300减100',
couponCategory: 1,
iconUrl: 'eu fugiat commodo voluptate exercitation',
description: 'nisi',
status: 43999314,
navUrl: 'commodo in reprehenderit',
pickupAble: false,
publishCountFinished: true
}
]
};
},
// 领取优惠券
pickupCoupon(params) {
return http.post(`${config.kdspHost}/api/kdsp/coupon/pickup`, params, {
// todo: header里二者参数需特殊处理
headers: {
'x-user-terminal': 'H5',
'vccChannel': '',
}
});
},
};
\ No newline at end of file
......@@ -2,8 +2,10 @@ const protocol = EASY_ENV_IS_BROWSER ? window.location.protocol : 'http';
export default {
apiHost: `http://localhost:7001/`,
// apiHost: `http://192.168.28.199:7001/`,
qiniuHost: `https://appsync.lkbang.net/`,
shenceUrl: `${protocol}//bn.xyqb.com/sa?project=default`,
opapiHost: `https://opapi-vcc2.liangkebang.net`,
qiniuUpHost: `${protocol}//up-z0.qiniup.com`,
kdspHost: 'https://kdsp-api-vcc2.liangkebang.net',
};
......@@ -62,6 +62,7 @@ export default class App {
server() {
return context => {
// console.log('server', context.state);
const vm = this.create(context.state);
const { store, router } = vm;
router.push(context.state.url);
......
<template>
<div class="ad" :class="['ad', { 'ad_one': column === 1, 'ad_two': column === 2, 'ad_three': column === 3 }]" :id="id">
<div :class="['ad', { 'ad_one': column === 1, 'ad_two': column === 2, 'ad_three': column === 3 }]">
<a :href="item.link" v-for="(item, index) in list" :key="index">
<cr-image :src="item.img" height="2.61rem" width="100%"></cr-image>
</a>
......
<template>
<div class="goods" v-if="column === 1">
<div class="goods-item_one" :style="style" :key="goods.id" v-for="goods in couponList">
<div class="coupon" v-if="column === 1">
<div class="coupon-item_one" :style="style" :key="coupon.id" v-for="coupon in couponList">
<div class="Gi_one-left">
<p>¥<span>{{ goods.couponAmt || 0 }}</span></p>
<p>{{ `满${goods.couponAmt || 0}元减${goods.limitAmt || 0}元` }}</p>
<p>¥<span>{{ coupon.faceValue || '-' }}</span></p>
<p>{{coupon.limitDesc}}</p>
</div>
<div class="Gi_one-middle">
<p>{{ goods.name }}</p>
<p>2020.8.4-2020.9.4</p>
<p>{{ coupon.name }}</p>
<p>{{`${coupon.startDate} - ${coupon.endDate}`}}</p>
</div>
<cr-button class="Gi_one-right" type="primary">点击领取</cr-button>
<div class="goods-item-mask">
<cr-button class="Gi_one-right" type="primary" @click="getCoupon(coupon)">{{btnText(coupon.pickupAble)}}</cr-button>
<div class="coupon-item-mask" v-if="!coupon.pickupAble && coupon.publishCountFinished">
<p>已抢空</p>
</div>
</div>
</div>
<div v-else :class="['goods', {'goods_two': column === 2, 'goods_multiple': column === 3}]">
<div class="goods-item" :style="style" :key="goods.id" v-for="goods in couponList">
<p class="goods-item-title">{{ goods.name }}</p>
<p class="goods-item-amount">¥<span>{{ goods.couponAmt || 0 }}</span></p>
<p class="goods-item-condition">{{ `满${goods.couponAmt || 0}元减${goods.limitAmt || 0}元` }}</p>
<cr-button shape="circle" type="primary" >
点击领取
<div v-else :class="['coupon', {'coupon_two': column === 2, 'coupon_multiple': column === 3}]">
<div class="coupon-item" :style="style" :key="coupon.id" v-for="coupon in couponList">
<p class="coupon-item-title">{{ coupon.name }}</p>
<p class="coupon-item-amount">¥<span>{{ coupon.faceValue || '-' }}</span></p>
<p class="coupon-item-amount">{{coupon.limitDesc}}</p>
<cr-button shape="circle" type="primary" @click="getCoupon(coupon)">
{{btnText(coupon.pickupAble)}}
</cr-button>
<div class="goods-item-mask">
<div class="coupon-item-mask" v-if="!coupon.pickupAble && coupon.publishCountFinished">
<p>已抢空</p>
</div>
</div>
......@@ -59,7 +59,7 @@
},
style() {
return {
background: `url(${this.bgImage}) no-repeat 0 0 / cover`,
background: this.bgImage ? `url(${this.bgImage}) no-repeat 0 0 / cover` : '',
backgroundColor: this.bgColor
}
}
......@@ -67,15 +67,25 @@
watch: {
couponsList: {
handler: async function (newVal) {
for(let i = 0; i < newVal.length;i++) {
const res = await operationApi.couponList({ couponId: newVal[i] });
const couponInfoList = res.couponInfoList;
if(couponInfoList && couponInfoList.length) this.list.push(couponInfoList[0]);
}
console.log('watch', this.list);
const { coupons } = await operationApi.getCoupons({ couponIds: newVal.join(',') });
if(coupons && coupons.length) this.list = coupons;
// console.log('coupons', this.list);
},
immediate: true
}
},
methods: {
async getCoupon(coupon) {
if (!coupon.pickupAble) {
await operationApi.pickupCoupon({ couponId: coupon.couponId });
this.$toast.success('领取成功');
} else {
window.location.href = coupon.navUrl;
}
},
btnText(pickupAble) {
return pickupAble ? '去使用' : '立即领取';
}
}
}
</script>
......@@ -83,9 +93,11 @@
::-webkit-scrollbar {
display: none; /* Chrome Safari */
}
.goods {
.coupon {
width: 100%;
padding: 0 12px;
box-sizing: border-box;
font-size: 0;
&-item {
position: relative;
padding: 4px 8px;
......@@ -143,7 +155,7 @@
overflow-x: auto;
white-space: nowrap;
padding: 0 4px;
.goods-item {
.coupon-item {
width: 114px;
margin-right: 4px;
}
......@@ -153,7 +165,7 @@
display: flex;
flex-wrap: wrap;
justify-content: space-between;
.goods-item {
.coupon-item {
margin-bottom: 8px;
width: 170px;
&_empty {
......
<template>
<div class="container">
<cr-image width="100%" height="2.4rem" :src="specialRecommend" @click="go()" v-if="showSpecial"></cr-image>
<cr-image width="100%" height="2.4rem" :src="specialRecommend" @click="go('special')" v-if="showSpecial"></cr-image>
<div class="goods" v-if="column === 1">
<div class="goods-item goods-item_one" v-for="goods in goodsList" :key="goods.id">
<cr-image width="100%" height="3.15rem" class="goods-item-img" fit="contain" src="https://img14.360buyimg.com/n0/jfs/t1/141986/32/5318/98164/5f3236baE713fd239/5f2746db41f3e9c0.jpg"></cr-image>
<div class="goods-item goods-item_one" @click="go('detail', goods.skuNo)" v-for="goods in goodsList" :key="goods.id">
<cr-image width="100%" height="3.15rem" class="goods-item-img" fit="contain" :src="goods.skuUrl"></cr-image>
<div class="goods-item_one-right">
<p class="goods-item-title">商品名称商品名称商品名称商品商品名称商品商品名称商品名称商品名称商品商品名称商品商品名称商品名称商品名称商品商品名称商品</p>
<p class="goods-item-title">{{goods.skuName}}</p>
<div class="goods-item-bottom">
<div class="Gi-bottom-left">
<p>¥320.00</p>
<p>¥999.00</p>
<p>¥{{goods.salePrice || '-'}}</p>
<p>¥{{goods.marketPrice || '-'}}</p>
</div>
<cr-button
shape="circle"
type="primary"
@click="go('detail', goods.skuNo)"
>
立即购买
立即抢购
</cr-button>
</div>
</div>
</div>
</div>
<div class="goods" v-else>
<div :class="['goods-item', {'goods-item_three': column === 3, 'goods-item_two': column === 2}]" v-for="goods in goodsList" :key="goods.id">
<cr-image width="100%" height="3.15rem" class="goods-item-img" fit="contain" src="https://img14.360buyimg.com/n0/jfs/t1/141986/32/5318/98164/5f3236baE713fd239/5f2746db41f3e9c0.jpg"></cr-image>
<p class="goods-item-title">商品名称商品名称商品名称商品商品名称商品</p>
<div :class="['goods-item', {'goods-item_three': column === 3, 'goods-item_two': column === 2}]" v-for="goods in goodsList" :key="goods.id" @click="go('detail', goods.skuNo)">
<cr-image width="100%" height="3.15rem" class="goods-item-img" fit="contain" :src="goods.skuUrl"></cr-image>
<p class="goods-item-title">{{goods.skuName}}</p>
<div class="goods-item-bottom">
<div class="Gi-bottom-left">
<p>¥320.00</p>
<p>¥999.00</p>
<p>¥{{goods.salePrice || '-'}}</p>
<p>¥{{goods.marketPrice || '-'}}</p>
</div>
<div class="Gi-bottom-right">
<div class="Gi-bottom-right" @click="addShopCart(goods)">
<cr-image width="0.45rem" height="0.45rem" :src="shoppingCart"></cr-image>
</div>
</div>
......@@ -84,20 +85,33 @@
watch: {
goods: {
handler: async function (newVal) {
if(newVal.type === 'goodsGroup' && newVal.ids.length) {
const { records } = await operationApi.skuInfo({ brandId: newVal.ids[0] });
if(records && records.length) this.list = records;
if (newVal.ids.length) {
let records = [];
if(newVal.type === 'goodsGroup') {
({ skus: records } = await operationApi.getGoods({ specialId: newVal.ids[0] }));
} else if (newVal.type === 'goods') {
({ skus: records } = await operationApi.getGoods({ skus: newVal.ids }));
}
if(records.length) this.list = records;
}
console.log('watch', this.list);
// console.log('watch', this.list);
},
immediate: true
}
},
methods: {
go() {
if (this.specialLink) {
go(type, params) {
if (type === 'special' && this.specialLink) {
window.location.href = this.specialLink;
} else if (type === 'goodsDetail') {
window.location.href = `xyqb://homepage/goodsdetail?skuNo=${params}`;
}
},
async addShopCart(goods) {
const { skuId, skuNum, skuSource } = goods;
const params = { skuId, skuNum, skuSource, type: 1 }
await operationApi.addShopCart(params);
this.$toast.success('添加成功');
}
}
}
......@@ -117,7 +131,8 @@
margin-bottom: 4px;
overflow: hidden;
&-title {
font-size: 13px;
height: 36px;
font-size: 12px;
line-height: 18px;
display: -webkit-box;
overflow: hidden;
......@@ -130,7 +145,14 @@
justify-content: space-between;
align-items: center;
.Gi-bottom-left {
display: flex;
flex-direction: column;
align-items: center;
p {
max-width: 55px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
text-align: left;
&:first-child {
font-size: 15px;
......@@ -176,7 +198,14 @@
flex: 1;
display: flex;
justify-content: space-around;
flex-direction: row;
align-items: flex-end;
p {
max-width: 55px;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
}
// 组件库替换变量处理
/deep/ .cr-button--primary {
......
<template>
<div class="tabs">
<cr-tabs @change="tabsChange" animated :underline-color="underlineColor" :title-active-color="activeColor" :title-inactive-color="inactiveColor">
<cr-tabs sticky ref="GoodsTabs" @change="tabsChange" animated :color="underlineColor" :title-active-color="activeColor" :title-inactive-color="inactiveColor">
<cr-tab v-for="(tab, index) in tabs" :key="index" :name="index" :title="tab.tabTitle">
<component :is="tab.name" v-bind="tab.props" :style="transformStyle(tab.commonStyle)"></component>
</cr-tab>
......@@ -52,20 +52,25 @@
})
return rs;
});
console.log('tabs', this.childItem.child, list);
// console.log('tabs', this.childItem.child, list);
return list;
}
},
mounted() {
setTimeout(() => {
this.$refs.GoodsTabs.resize();
})
},
methods: {
tabsChange(name) {
console.log('change', name);
// console.log('change', name);
},
transformStyle(styleObj = {}) {
// console.log('transformStyle', styleObj);
const style = {};
for (const key of Object.keys(styleObj)) {
style[key] = styleObj[key];
if (key === 'backgroundImage') {
if (key === 'backgroundImage' && style.backgroundImage) {
style.background = `url(${style.backgroundImage}) no-repeat 0 0 / cover`;
}
}
......
<template>
<div class="guide-cube">
<swiper v-if="showSwiper" class="guide-cube-swiper" ref="mySwiper" :options="swiperOptions">
<swiper-slide :style="style" :class="{'swiper-slide_two': slidesPerColumn === 2, 'swiper-slide_one': slidesPerColumn === 1 }" :key="index" v-for="(item, index) in list" @click="go(item.link)">
<cr-image width="" height="" :src="item.img" fit="cover" :show-loading="false"></cr-image>
</swiper-slide>
</swiper>
<div v-if="showSwiper" class="guide-cube-swiper" ref="mySwiper" v-swiper:mySwiper="swiperOptions">
<div class="swiper-wrapper">
<div :style="style" :class="{'swiper-slide_two': slidesPerColumn === 2, 'swiper-slide_one': slidesPerColumn === 1, 'swiper-slide': true }" :key="index" v-for="(item, index) in list" @click="go(item.link)">
<cr-image width="" height="" :src="item.img" fit="cover" :show-loading="false"></cr-image>
</div>
</div>
</div>
</div>
</template>
<script>
import { swiper, swiperSlide } from 'vue-awesome-swiper';
import Vue from "vue";
import 'swiper/dist/css/swiper.css';
if (EASY_ENV_IS_BROWSER) {
const VueAwesomeSwiper = require('vue-awesome-swiper/dist/ssr')
Vue.use(VueAwesomeSwiper)
}
export default {
inject: ['editor'],
components: {
swiper,
swiperSlide,
},
props: {
list: {
type: Array,
......@@ -27,13 +30,17 @@
default: 1
},
loop: Boolean,
autoplay: Boolean
autoplay: Boolean,
animation: Boolean
},
data() {
const vm = this;
let isEven = true;
let lastEvenProgress = 0;
let lastProgress = 0;
return {
showSwiper: true,
style: {},
// style: {},
swiperOptions: {
loop: this.loop,
slidesPerView: 4,
......@@ -43,29 +50,60 @@
observeParents:true,
watchSlidesProgress: true,
autoplay: this.autoplay,
on: {
progress: function() {
// console.log('memorys', vm.memorys);
const slidesLength = this.slides.length;
for (let i = 0; i < this.slides.length; i++) {
const slide = this.slides.eq(i);
let scale = Math.abs(this.slides[i].progress % 1).toFixed(10) * 0.2;
// 记录初始状态,用来复位
if (i === 0) this.slides[0].scale = scale;
if (!this.slides[0].scale) {
vm.memorys[i] = vm.memorys[i] ? 0 : 1;
scale = 0.2;
}
// 差别对待不同索引
if (vm.memorys[i]) {
scale = 1 - scale;
} else {
scale = 0.8 + scale;
}
slide.transform(`scale3d(${scale}, ${scale}, 1)`);
on: {
progress: function() {
const slidesLength = this.slides.length;
if (!vm.animation) return;
let ratio = Math.abs(this.slides[0].progress % 1).toFixed(10) * 0.2;
if ([0, 1].includes(Math.abs(this.slides[0].progress % 2))) {
isEven = Math.abs(this.slides[0].progress % 2) === 0 ? true : false;
}
if (Math.abs(this.slides[0].progress - lastEvenProgress) > 1) {
lastEvenProgress = Math.floor(this.slides[0].progress);
isEven = !isEven;
}
const isLeft = lastProgress < this.slides[0].progress;
lastProgress = this.slides[0].progress;
for (let i = 0; i < this.slides.length; i++) {
const slide = this.slides.eq(i);
let scale = 1;
if (Math.abs(this.slides[0].progress % 2) === 0) {
scale = vm.memorys[i] ? 0.8 : 1;
} else if(Math.abs(this.slides[0].progress % 2) === 1) {
scale = vm.memorys[i] ? 1 : 0.8;
} else if (isEven) {
if (isLeft) {
if (vm.memorys[i]) {
scale = 0.8 + ratio;
} else {
scale = 1 - ratio;
}
} else {
if (vm.memorys[i]) {
scale = 1 - ratio;
} else {
scale = 0.8 + ratio;
}
}
} else if (!isEven) {
if (isLeft) {
if (vm.memorys[i]) {
scale = 1 - ratio;
} else {
scale = 0.8 + ratio;
}
} else {
if (vm.memorys[i]) {
scale = 0.8 + ratio;
} else {
scale = 1 - ratio;
}
}
}
slide.transform(`scale3d(${scale}, ${scale}, 1)`);
}
},
},
}
},
}
}
},
......@@ -77,6 +115,11 @@
const length = this.list.length;
const baseNumber = this.slidesPerColumn === 1 ? 2 : 4;
return new Array(length).fill(0).map((v, i) => [0, 3].includes(i % baseNumber) ? 1 : 0);
},
style() {
return {
transition: 'all .2s cubic-bezier(.4, 0, .2, 1)'
};
}
},
watch: {
......@@ -90,11 +133,6 @@
this.refreshSwiper('loop', newVal);
},
},
mounted() {
this.style = {
transition: 'all .4s cubic-bezier(.4, 0, .2, 1)'
}
},
methods: {
go(url) {
window.location.href = url;
......@@ -110,6 +148,7 @@
</script>
<style lang="less" scoped>
.guide-cube {
width: 100%;
padding: 12px;
&-swiper {
width: 100%;
......
<template>
<div class="marquee">
<swiper class="marquee-swiper" ref="mySwiper" :options="swiperOptions">
<swiper-slide :key="index" v-for="(item, index) in list">
<span :style="styles">{{item.text}}</span>
</swiper-slide>
</swiper>
<div class="marquee-swiper" v-if='showSwiper' ref="mySwiper" v-swiper:mySwiper="swiperOptions">
<div class="swiper-wrapper">
<div class="swiper-slide" :key="index" v-for="(item, index) in list">
<a class="swiper-slide-a" @click="go(item.link)">
<span :style="styles">{{item.text}}</span>
</a>
</div>
</div>
</div>
</div>
</template>
<script>
import { swiper, swiperSlide } from 'vue-awesome-swiper';
import Vue from "vue";
import 'swiper/dist/css/swiper.css';
if (EASY_ENV_IS_BROWSER) {
const VueAwesomeSwiper = require('vue-awesome-swiper/dist/ssr')
Vue.use(VueAwesomeSwiper)
}
export default {
inject: ['editor'],
components: {
swiper,
swiperSlide,
},
props: {
list: {
type: Array,
......@@ -29,8 +34,9 @@
},
data() {
return {
showSwiper: true,
swiperOptions: {
// loop: true,
loop: true,
observer:true,
observeParents:true,
direction: 'vertical',
......@@ -49,6 +55,24 @@
}
}
},
watch: {
list: {
deep: true,
handler() {
this.refreshSwiper();
}
}
},
methods: {
go(link) {
window.location.href = link;
},
refreshSwiper() {
console.log('refreshSwiper');
this.showSwiper= false;
this.$nextTick(() => this.showSwiper= true);
}
}
}
</script>
<style lang="less" scoped>
......@@ -63,6 +87,11 @@
display: flex;
justify-content: center;
align-items: center;
a {
display: flex;
justify-content: center;
align-items: center;
}
span {
font-size: 14px;
line-height: 20px;
......
......@@ -481,10 +481,10 @@ export const businessComponents = [
}
],
value: {
couponsList: [153, 152, 151, 150, 149],
couponsList: [],
couponsNumber: 3,
column: 3,
bgColor: '#fff',
bgColor: '',
bgImage: '',
},
commonStyle: {}
......@@ -560,6 +560,11 @@ export const businessComponents = [
name: '自动播放',
type: 'checkbox'
},
{
key: 'animation',
name: '切换动画',
type: 'checkbox'
},
{
key: 'list',
name: '添加导购',
......@@ -618,11 +623,12 @@ export const businessComponents = [
}],
slidesPerColumn: 1,
loop: false,
autoplay: false
autoplay: false,
animation: false
}
},
{
eleName: 'Marquee',
eleName: 'CustomMarquee',
title: '跑马灯',
config: [
{
......@@ -640,6 +646,11 @@ export const businessComponents = [
name: '文案',
type: 'text'
},
{
key: 'link',
name: '链接',
type: 'text'
},
]
}
],
......
......@@ -2,9 +2,10 @@ import { cloneDeep } from 'lodash';
import { Component, Prop, Vue, Mixins } from 'vue-property-decorator';
import { Action, Mutation, State, Getter } from 'vuex-class';
import TransformStyleMixin from '@/page/mixins/transformStyle.mixin';
import CustomMarquee from '@/lib/Marquee/index.vue';
import { resizeDiv } from '@/service/utils.service';
@Component({ name: 'FreedomContainer' })
@Component({ components: { CustomMarquee }, name: 'FreedomContainer' })
export default class FreedomContainer extends Mixins(TransformStyleMixin) {
@Getter('pageData') pageData;
@State(state => state.editor.curChildIndex) curChildIndex;
......
<template>
<div class="freedom">
<div class="freedom-body" :style="{background: `url(${backgroundImage}) no-repeat 0 0 / cover`}">
<component :class="['freedom-body-item', { 'Fb-item_selected': curChildIndex === index }]" v-for="(item, index) in childItem.child" :style="transformStyle(item.commonStyle)" :is="item.name" :key="index" v-bind="item.props"></component>
<div class="freedom-body">
<component :class="['freedom-body-item', { 'Fb-item_selected': curChildIndex === index }]" v-for="(item, index) in childItem.child" :style="transformStyle(item.commonStyle, 'container')" :is="item.name" :key="index" v-bind="item.props"></component>
</div>
</div>
</template>
......
......@@ -9,9 +9,12 @@ import DownloadGuide from '@/lib/DownloadGuide/index.vue';
import { Getter, State, Mutation } from 'vuex-class';
import GuideCube from '@/lib/GuideCube/index.vue';
import GoodsTabs from '@/lib/GoodsTabs/index.vue';
import Coupon from '@/lib/Coupon/index.vue';
import Advertisement from '@/lib/Advertisement/index.vue';
import TransformStyleMixin from '@/page/mixins/transformStyle.mixin';
import { getStyle } from '@/service/utils.service';
@Component({ components: { FreedomContainer, GridLayout, GridItem, DownloadGuide, GoodsTabs, GuideCube }, name: 'Activity'})
@Component({ components: { FreedomContainer, GridLayout, GridItem, DownloadGuide, GoodsTabs, GuideCube, Advertisement, Coupon }, name: 'Activity'})
export default class Activity extends Mixins(TransformStyleMixin) {
@Getter('pageData') pageData;
@State(state => state.editor.pageInfo.pageName) pageName;
......@@ -20,6 +23,8 @@ export default class Activity extends Mixins(TransformStyleMixin) {
@Provide('editor');
isLayoutComReady = false;
showBackTop = false;
targetEle: HTMLElement | null = null;
get layout() {
return this.pageData && this.pageData.elements.map(v => v.point) || [];
......@@ -42,9 +47,18 @@ export default class Activity extends Mixins(TransformStyleMixin) {
}
}
mounted() {
this.targetEle = document.querySelector('body');
this.showBackTop = true;
setTimeout(() => {
this.modfiTabsStyle();
}, 300);
}
fetchApi(options) {
const { store, route } = options;
const { pageId } = route.params;
console.log('fetchApi', route);
return store.dispatch('getPageDate', { pageId });
}
......@@ -53,4 +67,26 @@ export default class Activity extends Mixins(TransformStyleMixin) {
height: `${h * this.rowHeight}px`,
} : {};
}
modfiTabsStyle() {
const tabsEle = document.querySelector('.tabs');
if (tabsEle) {
const gridItemEle = tabsEle?.parentNode;
if (gridItemEle?.classList.contains('vue-grid-item')) {
// 处理transform
const transform = getStyle(gridItemEle, 'transform');
const transformY = transform.split('(')[1].split(')')[0].split(',')[5];
gridItemEle.style.transform = 'none';
gridItemEle.style.top = `${transformY}px`;
// 处理backgroundColor
const backgroundColor = getStyle(tabsEle, 'backgroundColor');
const crTabs = tabsEle.childNodes[0];
crTabs.style.backgroundColor = backgroundColor;
const stickyEle = crTabs?.childNodes[0];
if (stickyEle?.classList.contains('cr-sticky') && stickyEle?.childNodes) {
stickyEle.childNodes[0]?.style.backgroundColor = backgroundColor;
}
}
}
}
}
\ No newline at end of file
......@@ -12,6 +12,7 @@
:is-mirrored="false"
:vertical-compact="true"
:use-css-transforms="true"
@layout-ready="modfiTabsStyle"
>
<grid-item :style="createStyle(item.point)" v-for="(item, index) in pageData.elements"
:x="item.point.x"
......@@ -20,13 +21,14 @@
:h="item.point.h"
:i="item.point.i"
:key="item.point.i">
<component :style="transformStyle(item.commonStyle)" class="Dcmc-panel-com" :data-index="index" :containerIndex="index" :childItem="item" :is="item.name" :key="index" v-bind="item.props"></component>
<component :style="transformStyle(item.commonStyle)" :data-index="index" :containerIndex="index" :childItem="item" :is="item.name" :key="index" v-bind="item.props"></component>
</grid-item>
</grid-layout>
<cr-back-top v-if="showBackTop && pageData.props.showBackTop" />
</div>
</template>
<script lang="ts" src="./index.ts"></script>
<style lang="less">
<style lang="less" scoped>
html,
body,
#app {
......@@ -41,23 +43,21 @@
width: 100%;
height: 100%;
min-height: 100%;
overflow-y: scroll;
background-color: rgb(244, 244, 244);
box-shadow: 2px 0px 10px rgba(0, 0, 0, 0.2);
/deep/ .vue-grid-layout {
min-height: 667px;
// transform: translateY(-10px);
background-color: rgb(244, 244, 244);
box-shadow: 2px 0px 10px rgba(0, 0, 0, 0.2);
/deep/ .vue-grid-layout {
min-height: 667px;
// transform: translateY(-10px);
transition-property: none;
.vue-grid-item {
transition-property: none;
.vue-grid-item {
transition-property: none;
display: flex;
justify-content: center;
align-items: center;
background: #fff;
&>*:first-child {
height: 100%;
}
display: flex;
justify-content: center;
align-items: center;
&>*:first-child {
height: 100%;
}
}
}
}
</style>
<template>
<Layout>
<transition name="fade" mode="out-in">
<router-view></router-view>
</transition>
<router-view></router-view>
</Layout>
</template>
<script lang="ts" src="./index.ts"></script>
......@@ -47,12 +47,16 @@ export default class DynamicForm extends Vue {
}
async createCoverImage() {
const imgName = btoa(`coverImage-${uuidv4().substr(0, 8)}`);
const gridEle = document.querySelector('.Dcm-container-panel');
const canvas = await html2canvas(gridEle as HTMLElement, { useCORS: true });
const base64 = canvas.toDataURL();
const { data: { uptoken } } = await editorApi.getUpToken();
const { data: { key } } = await editorApi.uploadBase64(base64.split(',')[1], imgName, `UpToken ${uptoken}`);
this.formCustom.coverImage = config.qiniuHost + key;
try {
const imgName = btoa(`coverImage-${uuidv4().substr(0, 8)}`);
const gridEle = document.querySelector('.Dcm-container-panel');
const canvas = await html2canvas(gridEle as HTMLElement, { useCORS: true });
const base64 = canvas.toDataURL();
const { data: { uptoken } } = await editorApi.getUpToken();
const { data: { key } } = await editorApi.uploadBase64(base64.split(',')[1], imgName, `UpToken ${uptoken}`);
this.formCustom.coverImage = config.qiniuHost + key;
} catch (e) {
console.log(e);
}
}
}
\ No newline at end of file
<template>
<div class="color-selector">
<Input class="color-selector-input" v-model="color" placeholder="请输入" @on-change="change"></Input>
<ColorPicker v-model="color" @on-change="change" />
<Input class="color-selector-input" v-model="color" placeholder="请输入" @input="change"></Input>
<ColorPicker v-model="color" @on-change="change($event)" />
</div>
</template>
<script>
......
......@@ -9,7 +9,7 @@ export default class CouponTableModal extends Vue {
@Prop({ default: () => ([]), type: Array }) value;
@Prop({ default: () => ([]), type: Array }) formControl;
goods: object = cloneDeep(this.value);
coupon: object = cloneDeep(this.value);
table: object[] = [
{
title: '选择优惠券',
......@@ -20,16 +20,20 @@ export default class CouponTableModal extends Vue {
}
];
@Watch('goods')
@Watch('coupon')
onFormChange(newVal) {
this.$emit('input', newVal);
}
async query(data) {
const res = await operationApi.couponList(data);
// receiverType 领取方式 1:主动领取 2:自动发放 3:不限
const res = await operationApi.couponList({...data, receiverType: 1});
const couponInfoList = res?.couponInfoList?.map(item => {
item.receiverTime = `${item.receiverStartTime.slice(0, 10)}--${item.receiverEndTime.slice(0, 10)}`;
item.useTime = item.useTimeStart ? `${item.useTimeStart}-${item.useTimeEnd}` : `自领取${item.receiverDaysValid}天后生效,有效天数${item.validDays}天`;
if (this.coupon.some(v => v === item.id)) {
item._checked = true;
}
return item;
});
......
<template>
<table-modal :table="table" :formControl="formControl" v-model="goods" title="优惠券"></table-modal>
<table-modal :table="table" :formControl="formControl" v-model="coupon" title="优惠券"></table-modal>
</template>
<script lang="ts" src="./index.ts"></script>
<style></style>
\ No newline at end of file
......@@ -18,6 +18,7 @@ export default class GoodsTableModal extends Vue {
{
title: '选择商品',
type: 'goods',
key: 'skuNo',
multiple: true,
columns: goodsColumn.call(this),
query: this.query
......@@ -25,6 +26,7 @@ export default class GoodsTableModal extends Vue {
{
title: '选择商品组',
type: 'goodsGroup',
key: 'id',
multiple: false,
columns: goodsGroupColumn.call(this),
query: this.queryGroup
......@@ -39,7 +41,7 @@ export default class GoodsTableModal extends Vue {
async query(data) {
const { records, total } = await operationApi.skuInfo({ type: 'list', ...data });
records.forEach(record => {
if (this.goods.ids.some(v => v === record.id)) {
if (this.goods.ids.some(v => v === record.skuNo)) {
record._checked = true;
}
});
......@@ -63,7 +65,7 @@ export default class GoodsTableModal extends Vue {
async queryGroup(data) {
const { records, total } = await operationApi.specialPage(data);
records.forEach(record => {
if (this.goods.ids.some(v => v === record.id)) {
if (this.goods.ids.some(v => v === record.skuNo)) {
record._checked = true;
}
});
......
......@@ -62,7 +62,7 @@ export default class DynamicForm extends Mixins(DynamicFormMixin) {
if (this.table.length > 1) {
this.$emit('input', {
type: this.table[this.activeName].type,
ids: this.selections.map(v => v.id)
ids: this.selections.map(v => v[this.table[this.activeName].key])
});
} else {
this.$emit('input', this.selections.map(v => v.id));
......
......@@ -7,7 +7,7 @@
</div>
</div>
<div id="upload">
<div id="upload_pic">
<div :id="id">
<Icon type="camera" size="20"></Icon>
</div>
</div>
......@@ -16,20 +16,28 @@
<script>
import '@/service/qiniu.service';
import config from '@/config';
import uuidv4 from 'uuid/v4';
// const id = uuidv4().substr(0, 8);
export default {
props: {
value: String,
},
data() {
return {
id: uuidv4().substr(0, 8)
}
},
methods: {
handleRemove () {
this.$emit('input', '');
this.$emit('on-change');
this.$emit('change', '');
},
uploadQiniu() {
var uploader = Qiniu.uploader({
runtimes: 'html5', // 上传模式,依次退化
browse_button: 'upload_pic', // 上传选择的点选按钮,**必需**
browse_button: this.id, // 上传选择的点选按钮,**必需**
uptoken_url: 'https://opapi.xyqb.com/upload/getToken',
// uptoken_url: `${config.apiHost}/upload/getToken`, //Ajax请求upToken的Url,**强烈建议设置**(服务端提供)
save_key: true, // 默认 false。若在服务端生成uptoken的上传策略中指定了 `sava_key`,则开启,SDK会忽略对key的处理
......@@ -55,7 +63,7 @@
desc: '',
});
this.$emit('input', config.qiniuHost + res.hash);
this.$emit('on-change', config.qiniuHost + res.hash);
this.$emit('change', config.qiniuHost + res.hash);
// this.url = config.qiniuHost + res.hash;
}
},
......@@ -63,16 +71,18 @@
});
},
},
mounted () {
this.uploadQiniu();
mounted () {
this.uploadQiniu();
}
}
</script>
<style lang="less" scoped="">
.upload {
position: relative;
display: flex;
align-items: center;
width: 100%;
overflow: hidden;
&-img {
display: inline-block;
width: 60px;
......
......@@ -22,6 +22,113 @@ export default class DynamicForm extends Mixins(ContextMenuMixin, DynamicFormMix
@Getter('pageData') pageData;
form: object = {};
styleSchame: object = {
curEle: [
{
label: '定位',
list: [
{
content: '上对齐',
icon: 'arrow-up-c',
args: 'top'
},
{
content: '右对齐',
icon: 'arrow-right-c',
args: 'right'
},
{
content: '下对齐',
icon: 'arrow-down-c',
args: 'bottom'
},
{
content: '左对齐',
icon: 'arrow-left-c',
args: 'left'
},
{
content: '垂直居中',
icon: 'android-film',
args: 'vertical'
},
{
content: '水平居中',
icon: 'android-film',
args: 'horizontal'
},
]
},
{
label: '位置'
},
{
label: '尺寸',
list: [
{
content: '全屏',
icon: 'arrow-resize',
args: 'full'
},
{
content: '宽100%',
icon: 'arrow-swap',
args: 'width'
},
{
content: '高100%',
icon: 'arrow-swap',
args: 'height'
},
]
},
{
label: '宽高'
},
{
label: '背景图片'
},
{
label: '背景颜色'
}
],
curChild: [
{
label: '容器尺寸',
list: [
{
content: '全屏',
icon: 'arrow-resize',
args: [667, 375],
},
{
content: '根据背景图片或组件自动调整宽高',
icon: 'image',
args: [667, 375, true],
},
{
content: '宽100%',
icon: 'arrow-swap',
args: [null, 375]
},
{
content: '高100%',
icon: 'arrow-swap',
args: [667, null]
}
]
},
{
label: '容器宽高'
},
{
label: '背景图片'
},
{
label: '背景颜色'
}
]
};
get curElement() {
let element = {};
......@@ -32,7 +139,7 @@ export default class DynamicForm extends Mixins(ContextMenuMixin, DynamicFormMix
element = this.pageData.elements[this.curEleIndex];
}
}
console.log('curElement', element);
// console.log('curElement', element);
return element;
}
......@@ -43,9 +150,10 @@ export default class DynamicForm extends Mixins(ContextMenuMixin, DynamicFormMix
get commonStyle() {
let rs = { backgroundColor: '', backgroundImage: '' };
if (this.curEleIndex || this.curEleIndex === 0) {
rs = cloneDeep({ ...rs, ...this.pageData.elements[this.curEleIndex].commonStyle });
if (this.curChildIndex || this.curChildIndex === 0) {
rs = cloneDeep({ ...rs, ...this.pageData.elements[this.curEleIndex].child[this.curChildIndex].commonStyle });
} else {
rs = cloneDeep({ ...rs, ...this.pageData.elements[this.curEleIndex].commonStyle });
}
}
// console.log('commonStyle', rs);
......@@ -78,6 +186,18 @@ export default class DynamicForm extends Mixins(ContextMenuMixin, DynamicFormMix
return result;
}
get isSelected() {
return this.curChildIndex || this.curChildIndex === 0 || this.curEleIndex || this.curEleIndex === 0;
}
get childSelected() {
return this.curChildIndex || this.curChildIndex === 0;
}
get parentSelected() {
return (this.curEleIndex || this.curEleIndex === 0) && !this.curChildIndex && this.curChildIndex !== 0;
}
// 监听curElement变化, 更新form
@Watch('curElement', { immediate: true, deep: true })
onElementChange(newVal) {
......@@ -92,7 +212,7 @@ export default class DynamicForm extends Mixins(ContextMenuMixin, DynamicFormMix
}
});
console.log('curElement', newVal, this.form);
// console.log('curElement', newVal, this.form);
}
// 监听form变化, 更新pageData
......
<template>
<div class="dynamic-form">
<div class="dynamic-form" v-if="isSelected">
<h2>{{curElement.title}}</h2>
<template>
<Form class="dynamic-form-component" :label-width="80" :model="form">
<h3 v-if="!hasGroup">组件属性</h3>
<template v-for="(item, index) in curElement.schame">
<div v-if="item.title">
<h3>{{ item.title }}</h3>
<FormItem :label="child.name" :key="child.key" v-for="child in item.children">
<component :is="getComponent(child.type)" :options="child.options" :formControl="child.formControl" v-model="form[child.key]" />
</FormItem>
</div>
<!-- <component v-else-if="item.formControl" :is="getComponent(item.type)" :options="item.options" :formControl="item.formControl" :name="item.name" v-model="form[item.key]" /> -->
<FormItem class="Df-component-formitem" v-else :label="item.name" >
<component :is="getComponent(item.type)" :options="item.options" :formControl="item.formControl" v-model="form[item.key]" />
<Form class="dynamic-form-component" :label-width="80" :model="form" v-if="curElement.schame && curElement.schame.length">
<h3 v-if="!hasGroup">组件属性</h3>
<template v-for="(item, index) in curElement.schame">
<div v-if="item.title">
<h3>{{ item.title }}</h3>
<FormItem :label="child.name" :key="child.key" v-for="child in item.children">
<component :is="getComponent(child.type)" :options="child.options" :formControl="child.formControl" v-model="form[child.key]" />
</FormItem>
</template>
</Form>
</template>
<template>
<!-- <h3>基础样式</h3> -->
<Form class="dynamic-form-basic" :label-width="80">
<h3>基础样式</h3>
<template v-if="curChildIndex || curChildIndex === 0">
<FormItem label="定位">
<Tooltip placement="top" content="上对齐">
<Button type="ghost" icon="arrow-up-c" @click="changeAlignType('top')"></Button>
</Tooltip>
<Tooltip placement="top" content="右对齐">
<Button type="ghost" icon="arrow-right-c" @click="changeAlignType('right')"></Button>
</Tooltip>
<Tooltip placement="top" content="下对齐">
<Button type="ghost" icon="arrow-down-c" @click="changeAlignType('bottom')"></Button>
</Tooltip>
<Tooltip placement="top" content="左对齐">
<Button type="ghost" icon="arrow-left-c" @click="changeAlignType('left')"></Button>
</Tooltip>
<Tooltip placement="top" content="垂直居中">
<Button type="ghost" icon="android-film" @click="changeAlignType('vertical')"></Button>
</Tooltip>
<Tooltip placement="top" content="水平居中">
<Button type="ghost" icon="android-film" @click="changeAlignType('horizontal')"></Button>
</Tooltip>
</FormItem>
<FormItem label="位置">
<InputNumber class="Df-basic-inputnumber" v-model="commonStyle.left" @on-change="updateStyle($event, 'left')"></InputNumber>
<InputNumber v-model="commonStyle.top" @on-change="updateStyle($event, 'top')"></InputNumber>
</FormItem>
<FormItem label="尺寸">
<Tooltip placement="top" content="全屏">
<Button type="ghost" icon="arrow-resize" @click="resizedChildEvent('full')"></Button>
</Tooltip>
<Tooltip placement="top" content="宽100%">
<Button type="ghost" icon="arrow-swap" @click="resizedChildEvent('width')"></Button>
</Tooltip>
<Tooltip placement="top" content="高100%">
<Button type="ghost" icon="arrow-swap" @click="resizedChildEvent('height')" ></Button>
</Tooltip>
</FormItem>
<FormItem label="宽高">
<InputNumber class="Df-basic-inputnumber" :max="375" :min="0" v-model="commonStyle.width" @on-change="updateStyle($event, 'width')"></InputNumber>
<InputNumber :max="667" :min="0" v-model="commonStyle.height" @on-change="updateStyle($event, 'height')"></InputNumber>
</div>
<FormItem class="Df-component-formitem" v-else :label="item.name" >
<component :is="getComponent(item.type)" :options="item.options" :formControl="item.formControl" v-model="form[item.key]" />
</FormItem>
</template>
</Form>
<Form class="dynamic-form-basic" :label-width="80">
<h3>基础样式</h3>
<template v-if="childSelected">
<template v-for="item in styleSchame.curEle">
<FormItem :label="item.label">
<template v-if="item.label === '宽高'">
<InputNumber class="Df-basic-inputnumber" :max="375" :min="0" v-model="commonStyle.width" @on-change="updateStyle($event, 'width')"></InputNumber>
<InputNumber :max="667" :min="0" v-model="commonStyle.height" @on-change="updateStyle($event, 'height')"></InputNumber>
</template>
<template v-else-if="item.label === '位置'">
<InputNumber class="Df-basic-inputnumber" v-model="commonStyle.left" @on-change="updateStyle($event, 'left')"></InputNumber>
<InputNumber v-model="commonStyle.top" @on-change="updateStyle($event, 'top')"></InputNumber>
</template>
<upload v-else-if="item.label === '背景图片'" v-model="commonStyle.backgroundImage" @change="updateStyle($event, 'backgroundImage')"></upload>
<ColorSelector v-else-if="item.label === '背景颜色'" v-model="commonStyle.backgroundColor" @input="updateStyle($event, 'backgroundColor')"></ColorSelector>
<template v-else>
<Tooltip placement="top" :content="child.content" v-for="child in item.list" :key="child.content">
<Button type="ghost" :icon="child.icon" @click="changeAlignType(...child.args)"></Button>
</Tooltip>
</template>
</FormItem>
</template>
<template v-if="(curEleIndex || curEleIndex === 0) && !curChildIndex && curChildIndex !== 0">
<FormItem label="容器尺寸">
<Tooltip placement="top" content="全屏">
<Button type="ghost" icon="arrow-resize" @click="resizedEvent(667, 375)"></Button>
</Tooltip>
<Tooltip placement="top" content="根据背景图片或组件自动调整宽高">
<Button type="ghost" icon="image" @click="resizedEvent(667, 375, true)" ></Button>
</Tooltip>
<Tooltip placement="top" content="宽100%">
<Button type="ghost" icon="arrow-swap" @click="resizedEvent(null, 375)"></Button>
</Tooltip>
<Tooltip placement="top" content="高100%">
<Button type="ghost" icon="arrow-swap" @click="resizedEvent(667, null)" ></Button>
</Tooltip>
</FormItem>
<FormItem label="容器宽高">
<InputNumber class="Df-basic-inputnumber" :max="375" :min="0" v-model="point.w" @on-change="updatePoint($event, 'w')"></InputNumber>
<InputNumber :max="667" :min="0" v-model="point.h" @on-change="updatePoint($event, 'h')"></InputNumber>
</template>
<template v-if="parentSelected">
<template v-for="item in styleSchame.curChild">
<FormItem :label="item.label">
<template v-if="item.label === '容器宽高'">
<InputNumber class="Df-basic-inputnumber" :max="375" :min="0" v-model="point.w" @on-change="updatePoint($event, 'w')"></InputNumber>
<InputNumber :max="667" :min="0" v-model="point.h" @on-change="updatePoint($event, 'h')"></InputNumber>
</template>
<upload v-else-if="item.label === '背景图片'" v-model="commonStyle.backgroundImage" @change="updateStyle($event, 'backgroundImage')"></upload>
<ColorSelector v-else-if="item.label === '背景颜色'" v-model="commonStyle.backgroundColor" @input="updateStyle($event, 'backgroundColor')"></ColorSelector>
<template v-else>
<Tooltip placement="top" :content="child.content" v-for="child in item.list" :key="child.content">
<Button type="ghost" :icon="child.icon" @click="resizedEvent(...child.args)"></Button>
</Tooltip>
</template>
</FormItem>
</template>
<FormItem label="背景图片">
<Upload v-model="commonStyle.backgroundImage" @change="updateStyle($event, 'backgroundImage')"></Upload>
</FormItem>
<FormItem label="背景颜色">
<ColorSelector v-model="commonStyle.backgroundColor" @input="updateStyle($event, 'backgroundColor')"></ColorSelector>
</FormItem>
</Form>
</template>
</template>
</Form>
</div>
</template>
<script lang="ts" src="./index.ts"></script>
......
import {Component, Mixins, Prop, Watch} from 'vue-property-decorator';
import { Getter, State } from 'vuex-class';
import { reduce, ceil, subtract, divide } from 'lodash';
import { reduce, ceil, subtract, divide, cloneDeep, assign } from 'lodash';
import ContextMenuMixin from '@editor/mixins/contextMenu.mixin';
import Upload from '../DynamicForm/component/Upload/index.vue';
import ColorSelector from '../DynamicForm/component/ColorSelector/index.vue';
......@@ -13,33 +13,48 @@ import { resizeDiv, getStyle } from '@/service/utils.service';
export default class DynamicPageForm extends Mixins(ContextMenuMixin) {
@Getter('pageData') pageData;
title: string = '页面背景';
form: object = {};
schame: object[] = [
title: string = '页面';
commonStyleForm: object = {};
propsForm: object = {};
commonStyleSchame: object[] = [
{
key: 'backgroundImage',
name: '背景图片',
type: 'Upload'
type: 'Upload',
},
{
key: 'backgroundColor',
name: '背景颜色',
type: 'ColorSelector'
},
}
];
propsSchame: object[] = [
{
key: 'showBackTop',
name: '返回顶部',
type: 'checkbox'
}
];
@Watch('pageData', { immediate: true, deep: true })
onElementChange(newVal) {
const keys = Object.keys(this.schame);
// this.form = {};
this.schame.forEach(schame => {
this.$set(this.form, schame.key, this.pageData?.commonStyle[schame.key]);
this.commonStyleSchame.forEach(schame => {
this.$set(this.commonStyleForm, schame.key, this.pageData?.commonStyle[schame.key]);
});
this.propsSchame.forEach(schame => {
this.$set(this.propsForm, schame.key, this.pageData?.props[schame.key]);
});
}
@Watch('commonStyleForm', { immediate: true, deep: true })
onCommonStyleFormChange(newVal) {
this.$emit('modProps', newVal, 'page', 'commonStyle');
}
@Watch('form', { immediate: true, deep: true })
onFormChange(newVal) {
this.$emit('modProps', this.form, 'page');
@Watch('propsForm', { immediate: true, deep: true })
onPropsFormChange(newVal) {
this.$emit('modProps', newVal, 'page', 'props');
}
// resizedChildEvent(type) {
......
<template>
<div class="dynamic-form">
<h2>{{title}}</h2>
<Form class="dynamic-form-component" :label-width="80" :model="form">
<h3>组件属性</h3>
<template v-for="(item, index) in schame">
<Form class="dynamic-form-component" :label-width="80" :model="propsForm">
<h3>基础属性</h3>
<template v-for="(item, index) in propsSchame">
<FormItem class="Df-component-formitem" :label="item.name" >
<component :is="item.type" :options="item.options" v-model="form[item.key]" />
<component :is="item.type" :options="item.options" v-model="propsForm[item.key]" />
</FormItem>
</template>
</Form>
<Form class="dynamic-form-component" :label-width="80" :model="commonStyleForm">
<h3>基础样式</h3>
<template v-for="(item, index) in commonStyleSchame">
<FormItem class="Df-component-formitem" :label="item.name" >
<component :is="item.type" :options="item.options" v-model="commonStyleForm[item.key]" />
</FormItem>
</template>
</Form>
......
import { Component, Prop, Mixins, Watch } from 'vue-property-decorator';
// import LoginForm from '@/lib/Form/index.vue';
import DownloadGuide from '@/lib/DownloadGuide/index.vue';
import Marquee from '@/lib/Marquee/index.vue';
import CustomMarquee from '@/lib/Marquee/index.vue';
import ContextMenuMixin from '@editor/mixins/contextMenu.mixin';
import TransformStyleMixin from '@/page/mixins/transformStyle.mixin';
import { cloneDeep, pick, omit, throttle } from 'lodash';
import { Action, Mutation, State, Getter } from 'vuex-class';
import { convertPointStyle, getStyle } from '@/service/utils.service';
@Component({ components: { DownloadGuide, Marquee }, name: 'FreedomContainer' })
@Component({ components: { DownloadGuide, CustomMarquee }, name: 'FreedomContainer' })
export default class FreedomContainer extends Mixins(ContextMenuMixin, TransformStyleMixin) {
@Action('setDragable') setDragable;
@State(state => state.editor.curEleIndex) curEleIndex;
......
......@@ -33,6 +33,7 @@ export default class DashBoard extends Mixins(ContextMenuMixin, GoodsTabsMixin,
@Mutation('SET_PAGE_INFO') setPageInfo;
@Mutation('SET_PAGE_DATA') setPageData;
@Mutation('UPDATE_PAGE_STYLE') setPageStyle;
@Mutation('UPDATE_PAGE_PROPS') setPageProps;
@Action('resetPageData') resetPageData;
@Action('savePageData') savePageData;
@Action('getPageDate') getPageDate;
......@@ -138,9 +139,13 @@ export default class DashBoard extends Mixins(ContextMenuMixin, GoodsTabsMixin,
}
}
modProps(props, ele) {
modProps(props, ele, type) {
if (ele === 'page') {
this.setPageStyle({ data: props });
if (type === 'commonStyle') {
this.setPageStyle({ data: props });
} else if (type === 'props') {
this.setPageProps({ data: props });
}
} else if (ele === 'component') {
let currentEle = {};
if (this.curEleIndex !== null) {
......
......@@ -81,10 +81,10 @@
</Col>
<Col span="8" :class="[{'Dcm-sider_none': isCollapsed}, 'Dc-middle-sider']">
<Tabs class="Dc-middle-editing" type="card">
<TabPane label="属性">
<TabPane label="组件设置">
<dynamic-form @modProps="modProps" @resizedChildEvent="resizedChildEvent"></dynamic-form>
</TabPane>
<TabPane label="事件">事件</TabPane>
<!-- <TabPane label="事件">事件</TabPane> -->
<TabPane label="页面设置">
<dynamic-page-form @modProps="modProps" @resizedChildEvent="resizedChildEvent"></dynamic-page-form>
</TabPane>
......
......@@ -14,12 +14,14 @@ export default class TransformStyleMixin extends Vue {
} else {
style[key] = styleObj[key]?.includes('px') ? `${(+(styleObj[key].slice(0, -2)) / 37.5).toFixed(2)}rem` : styleObj[key];
}
if (key === 'backgroundImage') {
style.backgroundImage = `url(${style.backgroundImage})`;
if (key === 'backgroundImage' && style.backgroundImage) {
// style.backgroundImage = `url(${style.backgroundImage})`;
style.background = `url(${style.backgroundImage}) no-repeat 0 0 / cover`;
}
}
const transformFun = element === 'container' ? pick : omit;
style = transformFun(style, ['position', 'top', 'left']);
if (element !== 'container') {
style = omit(style, ['position', 'top', 'left']);
}
return style;
}
}
\ No newline at end of file
......@@ -15,6 +15,7 @@ import {
SET_PAGE_DATA,
UPDATE_COMMON_STYLE,
UPDATE_PAGE_STYLE,
UPDATE_PAGE_PROPS
} from './type';
import RootState from '../../state';
......@@ -135,6 +136,9 @@ export default class EditorModule implements Module<EditorState, RootState> {
[UPDATE_PAGE_STYLE](state, { data }) {
(state.pageInfo.page as Page).commonStyle = data;
},
[UPDATE_PAGE_PROPS](state, { data }) {
(state.pageInfo.page as Page).props = data;
},
[ADD_ELEMENTS](state, { containerIndex, data }) {
const page = (state.pageInfo.page as Page).elements;
if (containerIndex || containerIndex === 0) {
......
......@@ -35,6 +35,7 @@ export interface PageElement {
export interface Page {
commonStyle: CommonStyle;
props: object;
elements: PageElement[];
}
......@@ -62,6 +63,9 @@ export const defaultState = {
backgroundColor: '#f7f8fa',
backgroundImage: ''
},
props: {
showBackTop: false
},
elements: [],
}
},
......
......@@ -10,4 +10,5 @@ export const RESET_PAGE_DATA = 'RESET_PAGE_DATA';
export const SET_TEMPLATE_LIST = 'SET_TEMPLATE_LIST';
export const SET_PAGE_DATA = 'SET_PAGE_DATA';
export const UPDATE_COMMON_STYLE = 'UPDATE_COMMON_STYLE';
export const UPDATE_PAGE_STYLE = 'UPDATE_PAGE_STYLE';
\ No newline at end of file
export const UPDATE_PAGE_STYLE = 'UPDATE_PAGE_STYLE';
export const UPDATE_PAGE_PROPS = 'UPDATE_PAGE_PROPS';
......@@ -63,7 +63,7 @@ instance.interceptors.request.use(
config.cancelToken = new CancelToken(c => (pending[(config.url + JSON.stringify(config.data)) as string] = c));
// 添加token
const token = localStorage.get('token');
if (token) {
if (token && !config.hideToken) {
config.headers['X-Auth-Token'] = token;
if (config.accessToken) { config.headers['Access-Token'] = token; }
}
......
......@@ -24,7 +24,9 @@ import {
Tabs,
Notify,
Swipe,
SwipeItem
SwipeItem,
Toast,
BackTop
} from '@qg/cherry-ui';
import { KaLoginForm } from '@qg/citrus-ui';
......@@ -49,9 +51,13 @@ Vue.use(Form);
// Vue.use(List);
Vue.use(Tab);
Vue.use(Tabs);
// Vue.use(Toast);
// Vue.use(Swipe);
// Vue.use(SwipeItem);
// Vue.use(Swipe);
Vue.use(BackTop);
Vue.use(KaLoginForm);
Vue.prototype.$notify = Notify;
Vue.prototype.$toast = Toast;
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment