I am having a video site. Where users can upload YouTube videos. The teasers of the videos should be playing on top of the same thumbnail. However they are playing on the thumbnail to the right behind the next thumbnail. and the last one is playing outside the container. Below is my code
kYoutube.vue
<script>
const getLogger = require('webpack-log');
const log = getLogger({ name: 'vue-khal' });
import VueYoutube from 'vue-youtube';
export default {
extends: VueYoutube,
methods : {
playerStateChange(e) {
log.info ("PSC on " + e.data);
if (e.data !== null && e.data !== -1) {
this.$emit(this.events[e.data], e.target)
}
}
}
}
</script>
PreVideo.vue
<template>
<div class="vid-cont">
<div class="thumb-place">
<PreImage :post="post" :sizes="sizes" #loaded="handleLoad"></PreImage>
</div>
<label v-if="post.proof_img == null">
<div #mouseover="startVideo" #click="emitClick" v-bind:class="thumbZ">
<img ref="pimage" :src="post.post_image.image"
:srcset="post.post_image.thumbs"
:sizes="sizes" style="object-fit: cover;"/>
<img v-if="playImg" class="pre-play-button" :src="playImg" :key="playImg"/>
</div>
<div v-if="iReady" class="player-cont" :key="iReady">
<youtube :video-id="videoId" ref="youtube"
#playing="playing" #ended="ended" #cued="ended" #paused="paused"
:width="width" :height="height"
:player-vars="playerVars" :fitParent="true"></youtube>
</div>
<div class="player-cont player-trans" #mouseout="pausePreview" #click="emitClick">
</div>
</label>
</div>
</template>
<script>
import PreImage from './PreImage';
const getLogger = require('webpack-log');
const log = getLogger({ name: 'vue-khal' });
export default {
name: 'Video',
props: [
'post',
'sizes',
],
components: {
PreImage
//VueYoutube
},
data() {
return {
'first': this.post.post_image,
'videoId': this.post.video_id,
'width': 0,
'height': 0,
'iReady': false,
'playerVars': {
'autoplay': 0,
'rel': 0,
'enablejsapi': 1,
'controls': 0,
'disablekb': 1,
'fs': 0,
'modestbranding': 1,
'cc_load_policy': 0,
'origin': document.location.origin
},
'playImg': null,
'pReload': false,
'rates': [],
'preview': false,
'duration': 0,
'count': 1,
'userState': -1,
'numSkips': 3,
};
},
computed: {
thumbZ: function() {
return {
'thumb-cont': true,
'thumb-top': (this.userState < 3)
};
},
},
mounted() {
var that = this;
this.api.map_prom.then(() => {
that.playImg = that.api.mkstatic("youtube_red.png");
that.pReload = !that.pReload;
});
},
updated() {
var that = this;
this.$nextTick(function() {
if (that.iReady && that.userState < 0) {
that.$refs.youtube.player.mute().then(() => {
that.toState(0);
that.$refs.youtube.player.playVideo().then(() => {
});
});
}
});
},
methods: {
emitClick() {
this.$emit("clicked");
},
pausePreview() {
this.toState(1);
this.$refs.youtube.player.stopVideo();
},
delayedPause() {
var that = this;
this.toState(2);
that.$refs.youtube.player.pauseVideo().then(function(){
//log.info ("Paused ?? ");
that.$refs.youtube.player.getPlayerState();/*.then(data => {
//log.info ("STATE is " + data);
});*/
});
},
jump() {
if (this.userState <= 2)
return;
var that = this;
var ct = parseInt(this.duration * this.count / this.numSkips);
//log.info ("Jump call " + that.count);
if (this.count <= this.numSkips) {
this.$refs.youtube.player.seekTo(ct, true).then(function(){
//that.$refs.youtube.player.getPlayerState().then(data => {
//log.info ("STATE " + data + " Jumped to " + ct + " at " + (new Date()).getTime());
that.count++;
that.$refs.youtube.player.pauseVideo().then(function(){
/*that.$refs.youtube.player.playVideo().then(function(){
//setTimeout(that.jump.bind(that), 10000);
});*/
});
//});
});
}
else {
this.$refs.youtube.player.stopVideo();
}
},
toState(state) {
//log.info ("From " + this.userState + " to " + state);
this.userState = state;
},
/* If you are paused, play */
paused() {
switch (this.userState) {
case 2:
//log.info ("Paused, playback " + this.count);
if (this.count <= this.numSkips) {
this.toState(3);
this.$refs.youtube.player.playVideo();
}
else {
this.$refs.youtube.player.stopVideo();
}
break;
case 3:
case 4:
var that = this;
that.$refs.youtube.player.playVideo().then(function(){
setTimeout(that.jump.bind(that), 2000);
});
break;
}
},
/* If you're playing, pause in a sec */
playing() {
//log.info ("Playing called " + this.userState);
switch (this.userState) {
case 0:
var that = this;
that.$refs.youtube.player.pauseVideo().then(() => {
if (!that.duration) {
that.$refs.youtube.player.mute().then(() => {
that.$refs.youtube.player.getDuration().then(data => {
that.duration = data;
//log.info ("Duration " + that.duration);
that.$refs.youtube.player.getAvailablePlaybackRates().then (data => {
//log.info ("Data is " + data);
that.rates = data;
let rate = Math.max.apply(null, that.rates);
that.$refs.youtube.player.setPlaybackRate(rate).then(() => {
//log.info ("Suggested is " + rate);
this.toState(1);
});
});
});
});
}
else {
this.toState(1);
}
})
break;
case 3:
this.toState(4);
setTimeout(this.jump.bind(that), 2000);
}
},
startVideo() {
if (this.userState == 1) {
this.toState(2);
this.paused();
}
},
handleLoad(ev, post) {
this.post = post;
if (!this.$parent.modalOpen) {
//this.post.post_image = post_image;
this.height = ev.target.clientHeight;
this.width = ev.target.clientWidth;
log.info ("PREvid handle load** " + this.width);
this.iReady = true;
}
},
ended() {
//log.info ("ENDED indeed");
this.count = 1;
this.toState(0);
this.$refs.youtube.player.playVideo();
},
}
}
</script>
<style>
</style>
PostList.vue
<template>
<div>
<div ref="me" :class="postListClass" :style="`top: -${scrollOff}px`">
<Loading :loading="loading == 'L'" :loaderror="loading =='E'"></Loading>
<table class="table">
<tbody>
<tr v-for="row in posts" :key="row">
<td v-for="post in row" :key="post">
<div class="post-grid" v-if="post.post_image != null" ref="postgrid" :key="post">
<PreVideo v-if="post.video_id" :post="post" :sizes="sizes" #clicked="selectPost(post.pk)">
</PreVideo>
<PreVideoFile v-else-if="post.video_file" :post="post" :sizes="sizes" #clicked="selectPost(post.pk)">
</PreVideoFile>
<PreImage v-else :post="post" :sizes="sizes" #clicked="selectPost(post.pk)">
</PreImage>
<div class="post-title" #click="selectPost(post.pk)">
<KTruncate :inhtml="post.title" :stripped="post.title" :nomore="true" length="30"></KTruncate>
</div>
<div class="post-user">
by <a :href="post.user.profile.url">{{post.user.full_name}}</a>
</div>
<div class="post-synopsis">
<KTruncate :inhtml="post.message" :stripped="post.stripped" :nomore="true" length="60">
</KTruncate>
</div>
</div>
</td>
</tr>
</tbody>
</table>
<div class="way-container" v-if="nextPageURL !== '' || loading == 'E'">
<Loading :loading="'L'" :key="loading" :loaderror="loading == 'E'" class="way-div" v-waypoint="{ active: true, callback: onWaypoint, options: intersectionOptions }" id="More">
</Loading>
</div>
</div>
<PostModal v-if="selectedPost > 0" ref="modal" :pk="selectedPost"
#closed="onHideDetail" #modalOut="onModalOut" :key="selectedPost"></PostModal>
</div>
</template>
<script>
/* eslint-disable no-console */
import Loading from './Loading';
import PostModal from './PostModal';
import KTruncate from './KTruncate';
import PreVideo from './PreVideo';
import PreVideoFile from './PreVideoFile';
import PreImage from './PreImage';
const getLogger = require('webpack-log');
const log = getLogger({ name: 'vue-khal' });
const getOffsetTop = element => {
let offsetTop = 0;
while(element) {
offsetTop += element.offsetTop;
element = element.offsetParent;
}
return offsetTop;
}
export default {
name: 'PostList',
components: {
Loading,
PostModal,
KTruncate,
PreVideo,
PreVideoFile,
PreImage
},
data() {
return {
scrollOff: 0,
origTop: null,
postListClass: "post-scroll",
selectedPost:0,
posts: [],
numberOfPages:0,
pages : [],
numberOfPosts:0,
loading: null, //false,
nextPageURL:'',
previousPageURL:'',
size:'',
winWidth: window.innerWidth,
intersectionOptions: {
root: null,
//rootMargin: '0px 0px 0px 0px',
threshold: [0.25, 0.75] // [0.25, 0.75] if you want a 25% offset!
},
divisor: 4,
modalOpen: false,
};
},
watch: {
pk: function (newpk, oldpk) {
log.info ("Change from " + oldpk + " to " + newpk);
}
},
methods: {
onWaypoint ({ going, direction }) {
if (!this.loading) {
// going: in, out
// direction: top, right, bottom, left
if (going === this.$waypointMap.GOING_IN &&
direction === this.$waypointMap.DIRECTION_TOP &&
this.nextPageURL != '') {
log.info ("Waypoint " + this.nextPageURL + " ??");
this.loading = 'L'; //true;
this.api.getItemsByURL(this.nextPageURL).then((page) => {
this.posts = this.posts.concat(this.breakupPosts(page.data));// this.posts = page.data;
this.nextPageURL = page.nextlink;
this.previousPageURL = page.prevlink;
this.loading = null; //false;
}).catch (err => {
log.info ("Waypoint err " + err.code + " " + err.message);
this.loading = 'E';
});
}
}
},
breakupPosts(data) {
let posts = [];
for (let i = 0; i < data.length; i++) {
let row = parseInt(i / this.divisor);
if (row >= posts.length)
posts.push([]);
posts[parseInt(i / this.divisor)].push(data[i]);
}
return posts;
/*this.posts = page.data.reduce((acc, k) => {
acc[parseInt(k/3)].push(page.data[k])
return acc;
}, {});*/
},
getPosts(){
if (this.origTop == null) {
this.origTop = getOffsetTop(this.$refs.me);
}
this.loading = 'L'; //true;
this.api.getFeedPosts().then((page) => {
this.setPage(page);
this.numberOfPosts = page.count;
this.numberOfPages = page.numpages;
if(this.numberOfPages) {
for(var i = 1 ; i <= this.numberOfPages ; i++) {
const link = `/api/posts/?page=${i}`;
this.pages.push({pageNumber: i , link: link})
}
}
}).catch (err => {
log.info ("Final status* " + err.code + " " + err.message);
this.loading = 'E';
});
},
getPage(link){
this.loading = 'L'; //true;
this.api.getItemsByURL(link).then((page) => {
this.setPage(page);
}).catch (err => {
log.info ("GetPage err " + err.code + " " + err.message);
this.loading = 'E';
});
},
getNextPage(){
this.loading = 'L'; // true;
this.api.getItemsByURL(this.nextPageURL).then((page) => {
this.setPage(page);
}).catch (err => {
log.info ("GetNextPage err " + err.code + " " + err.message);
this.loading = 'E';
});
},
setPage(page) {
this.posts = this.breakupPosts(page.data);//this.posts = page.data;
this.nextPageURL = page.nextlink;
this.previousPageURL = page.prevlink;
this.sizes = page.sizes;
this.loading = null; //false;
},
getPreviousPage(){
this.loading = 'L'; // true;
this.api.getItemsByURL(this.previousPageURL).then((page) => {
this.setPage(page);
}).catch (err => {
log.info ("Get Prev err " + err.code + " " + err.message);
this.loading = 'E';
});
},
onHideDetail() {
this.postListClass = "post-scroll";
let sides = document.getElementsByClassName ("tofix");
for (let i = 0; i < sides.length; i++) {
sides[i].classList.remove ("postbg-fixed");
}
log.info ("On hide detail " + this.selectedPost);
this.selectedPost = 0;
},
onModalOut() {
setTimeout(this.scroller.bind(this), 100);
},
scroller() {
document.documentElement.scrollTop = document.body.scrollTop = this.scrollOff + this.origTop; //document.body.scrollTop = this.scrollOff;
this.scrollOff = 0;
},
selectPost(post) {
log.info ("Select post called " + post );
this.selectedPost = post;
this.modalOpen = true;
this.scrollOff = (window.pageYOffset || document.documentElement.scrollTop) - this.origTop;
this.postListClass = "post-fixed";
let sides = document.querySelectorAll ("tofix");
for (let i = 0; i < sides.length; i++) {
sides[i].classList.add ("postbg-fixed");
}
log.info ("Select post done " + this.selectedPost);
},
getDivisor() {
let u600 = window.matchMedia('(min-width: 600px)');
let u900 = window.matchMedia('(min-width: 900px)');
this.divisor = u600.matches ? (u900.matches ? 4 : 3) : 2;
},
handleResize() {
if (window.innerWidth != this.winWidth) {
this.winWidth = window.innerWidth;
this.getDivisor();
this.posts = this.breakupPosts(this.posts.flat());
}
},
},
beforeDestroy() {
window.removeEventListener('resize', this.handleResize);
},
updated() {
},
mounted() {
this.getPosts();
this.getDivisor();
window.addEventListener('resize', this.handleResize);
},
}
</script>
<style scoped>
.table>tbody>tr>td {
border-top: transparent;
}
.post-fixed {
position: fixed;
z-index: -1;
opacity: 0;
}
#More {
-height: 100px;
}
</style>
Related
In my ticket processing application I currently have a back and forward button contained in my TicketRunner.vue Component, I would like to change it so that these buttons only appear if I have an associated case file, for which I've used V-If:
TicketRunner.Vue
<div class="level nav-btns" v-if='!currentTicketCaseFiles.length'>
<div class="buttons has-addons level-left">
<b-button
#click.prevent="navGoPrev()"
:disabled="currentStepIndex === 0 || navWaiting"
size="is-medium"
>
</div>
export default {
name: 'TicketRunner',
mixins: [NavStepsByIndexMixin()],
components: {
StagePresenter,
CaseFilesStage,
ParticipantsStage,
AttachmentsStage,
CaseFilesRunner,
TicketContextButtons,
},
data: function() {
return {
firstComponentsInitialization: true,
loadingConfirm: false,
confirmationModalActive: false,
confirmationSucceeded: undefined
}
},
props: {
ticketId: {
type: Number,
required: true,
},
},
provide() {
return {
contextButtons: {
capture: (name, callback, title) => this.$refs['contextButtons'].captureButton(name, callback, title),
release: (name) => this.$refs['contextButtons'].releaseButton(name),
enable: (name) => this.$refs['contextButtons'].enableButton(name),
disable: (name) => this.$refs['contextButtons'].disableButton(name),
},
};
},
computed: {
...mapGetters(['currentTicket', 'ticketCaseFiles', 'allCurrentTicketAttachments', 'currentTicketCaseFileNotAssociated',
'currentRequesterType', 'currentTicketStage', 'lastCaseFile']),
caseFiles() {
return this.ticketCaseFiles(this.ticketId);
},
ticketHasAttachments() {
return this.allCurrentTicketAttachments.length > 0;
},
isTicketAssociatedWithCaseFile() {
return !this.currentTicketCaseFileNotAssociated;
},
isFirstNavInitializationInProgress() {
return !this.navReady && this.firstComponentsInitialization;
},
isShowAttachmentsStep() {
return this.ticketHasAttachments && this.currentRequesterType !== 'unknown' &&
(this.isFirstNavInitializationInProgress || this.isTicketAssociatedWithCaseFile)
},
isCurrentTicketResolved() {
return this.currentTicket.status === 'resolved';
},
islastStep() {
return this.navLastStep() && this.lastCaseFile;
}
},
watch: {
ticketId(){
this.navigator.reset();
},
navReady() {
this.moveForwardIfReady();
this.firstComponentsInitialization = false;
}
},
methods: {
...mapActions(['confirmTicket']),
moveForwardIfReady() {
if (this.navigator.currentIndex === 0 && this.firstComponentsInitialization) {
let steps = 0
const step_names = ['case_files_stage']
for(const [_idx, name] of step_names.entries()) {
const ref_name = `step[${name}]`;
if (this.$refs.hasOwnProperty(ref_name) && this.$refs[ref_name].navReady) {
steps += 1
} else {
break
}
}
this.navigator.currentIndex += steps
}
},
confirm() {
this.$buefy.dialog.confirm({
message: this.t('tickets.stages.confirmation.simplified_confirm_reply'),
onConfirm: () => this.confirmStep()
})
},
async confirmStep() {
this.loadingConfirm = true;
const promise = this.confirmTicket(this.ticketId);
return promise.then((response) => {
this.confirmationModalActive = true;
this.confirmationSucceeded = true;
return true; // true is correct here. for goNext it makes parent to stay on on the current step
}).catch(() => {
this.confirmationModalActive = true;
this.confirmationSucceeded = false;
return true; // true is correct here. for goNext it makes parent to stay on on the current step
}).finally(() => this.loadingConfirm = false);
},
},
};
I then receive the following Console Error:
[Vue warn]: Property or method "currentTicketCaseFiles" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property.
I know that "!currentTicketCaseFiles.length" works successfully in the Component CaseFilesStage.vue, which makes me believe I should somehow connect the two? But importing it doesn't seem right to me either. I'm not quite sure how to tackle this issue as I'm quite new at VueJS, and would be happy for any pointers. I'll attach the CaseFilesStage.vue Component below.
CaseFilesStage.vue
<template>
<div class="hero">
<div class="block">
<template v-if="!currentTicket.spamTicket">
<b-field>
<b-input
v-model="filter"
:loading="loading"
:placeholder="t('tickets.stages.case_files.search.tooltip')"
v-on:keyup.enter.native="searchCaseFiles"
type="search"
icon="search"
:class="{ 'preview-enabled': showAttachmentsPreview}"
/>
</b-field>
<template v-if="foundCaseFiles.length">
<h4 class="title is-4 table-title">{{ t('tickets.stages.case_files.search.table_title') }}</h4>
<CaseFilesSearchTable
:case-files="foundCaseFilxes"
:found-by-data-points="foundCaseFilesByParticipant"
:show-header="true"
v-slot="cf">
<b-checkbox v-if="cfBelongsToCurrentTicket(cf.id)" :disabled="true" :value="true"></b-checkbox>
<b-checkbox v-else #input="onFoundCaseFile(cf.id, $event)"></b-checkbox>
</CaseFilesSearchTable>
</template>
<div v-else-if="lookupStatus === 'notFound'">
{{ t('tickets.stages.case_files.search.not_found') }}
<!-- display button here if above is activated -->
</div>
</template>
</div>
<template v-if='currentTicketCaseFiles.length'>
<h4 class="title is-4 table-title">{{ t('tickets.stages.case_files.table_title') }}</h4>
<CaseFilesTable :case-files="currentTicketCaseFiles" :show-header="true" v-slot="cf">
<DeleteButton
:model-id="cf.id"
modelName="CaseFile" >
</DeleteButton>
</CaseFilesTable>
</template>
</div>
</template>
<script>
import CaseFilesTable from '../tables/CaseFilesTable';
import CaseFilesSearchTable from '../tables/CaseFilesSearchTable';
import DeleteButton from '../../../../shared/components/controls/DeleteButton';
import { mapGetters, mapActions } from 'vuex';
import { mapServerActions } from "../../../../../../_frontend_infrastructure/javascript/lib/crudvuex_new";
export default {
name: 'CaseFilesStage',
data() {
return {
lookupStatus: 'waitingInput',
filter: '',
waiting: {},
foundCaseFiles: [],
foundCaseFilesByParticipant: {}
};
},
components: {
CaseFilesTable,
CaseFilesSearchTable,
DeleteButton
},
computed: {
...mapGetters(
['currentTicketCaseFiles', 'currentTicketCaseFileNotAssociated', 'currentTicket', 'showAttachmentsPreview']
),
loading() {
return this.lookupStatus === 'waitingServer';
},
allCaseFilesMix(){
this.currentTicketCaseFiles + this.foundCaseFiles
},
foundCaseFilesEx() {
return this.foundCaseFiles.filter((x) => !this.cfBelongsToCurrentTicket(x.id))
},
checkboxValue() {
if(!this.currentTicketCaseFileNotAssociated) {
return null;
}
return true;
},
navReady() {
return this.currentTicket.spamTicket || this.currentTicketCaseFiles.length > 0 || this.checkboxValue;
},
markSpam: {
get: function() {
return this.currentTicket.spamTicket
},
set: function(val) {
return this.updateTicket([this.currentTicket.id, { spam_ticket: val }]);
},
}
},
methods: {
...mapActions(['updateTicket']),
...mapServerActions(['createCaseFile', 'deleteCaseFile']),
cfBelongsToCurrentTicket(id){
return this.currentTicketCaseFiles.map((x) => x.caseFileId).includes(id);
},
cantAssignCaseFileCheckbox(isChecked){
if(isChecked) {
this.createCaseFile({ isCfNotAssociated: true });
} else {
this.deleteCaseFile(this.currentTicketCaseFileNotAssociated);
}
},
onFoundCaseFile(id, useIt){
console.log("onFoundCaseFile: ", id, useIt);
if(useIt) {
this.createCaseFile({ caseFileId: id });
} else {
this.deleteCaseFile(this.currentTicketCaseFiles.find({ caseFileId: id }));
}
},
searchCaseFiles() {
const newData = this.filter;
if (newData.length < 3) { // TODO: some smarter condition here
this.foundCaseFiles = [];
this.lookupStatus = 'waitingInput';
return;
}
this.lookupStatus = 'waitingServer';
this.$axios.get('case_files', { params: { "case_files.filter" : newData } })
.then((response) => {
this.foundCaseFiles = response.data.caseFilesSearchResult.caseFiles;
this.foundCaseFilesByParticipant = response.data.caseFilesSearchResult.foundByPrivateInfo;
if(this.foundCaseFiles.length > 0) {
this.lookupStatus = 'success';
} else {
this.lookupStatus = 'notFound';
}
}).catch(() => this.lookupStatus = 'error');
}
},
};
</script>
</style>
Add this to your TicketRunner.vue Component script:
computed: {
...mapGetters(['currentTicketCaseFiles'])
}
I am new to vuejs and I am trying to implement a javascript shopping cart,so I have this method,the output is a html table with some v-on:click actions that do not work,what is wrong
<div v-html="str"></div>
<div v-html="total"></div>
<div v-html="count"></div>
var vm = new Vue({
el: '#vue_det',
data: {
str:"",
total:0,
count:0
},
methods: {
displaycart: function ()
{
var cartArray = shoppingCart.listCart();
var output = "<table>";
for (var i in cartArray) {
output += "<tr>"
+ "<td>" + cartArray[i].name + "</td>"
+ "<td>(" + cartArray[i].price + ")</td>"
+ "<td><div class='input-group'><button v-on:click=minusitem('" + cartArray[i].name + "') class='minus-item input-group-addon btn btn-primary' >-</button>"
+ "<input type='number' class='item-count form-control' data-name='" + cartArray[i].name + "' value='" + cartArray[i].count + "'>"
+ "<button v-on:click=plusitem('" + cartArray[i].name + "') class='plus-item btn btn-primary input-group-addon' >+</button></div></td>"
+ "<td><button v-on:click=deleteitem('" + cartArray[i].name + "') class='delete-item btn btn-danger' >X</button></td>"
+ "<td>" + cartArray[i].total + "</td>"
+ "</tr>";
}
output +="</table>";
this.str=output;
this.total=shoppingCart.totalCart()
this.count=shoppingCart.totalCount()
// $('.show-cart').html(output);
// $('.total-cart').html(shoppingCart.totalCart());
// $('.total-count').html(shoppingCart.totalCount());
}
},
I make this code for my shopping cart
<div id="vue_det">
<div id="products">
<p>
Orange,pricε:20 Add
</p>
<p>
Paok,pricε:22 Add
</p>
</div>
<div v-for="entry in listCart()" class="entry">{{ entry.name }}
<button v-on:click=removeItemFromCart(entry.name) >-</button>
<input type='number' v-model.number="entry.count" >
<button v-on:click=addItemToCart(entry.name)>+</button>
<button v-on:click=removeItemFromCartAll(entry.name) >X</button>
{{entry.total}}
</div>
<div>Total:{{totalCart()}}</div>
<div>Totalcount:{{totalCount()}}</div>
</div>
</div>
<script type="text/javascript">
var vm = new Vue({
el: '#vue_det',
data: {
item:
{
name: "",
price: 0,
count: 0
},
cart: []
},
methods: {
addItemToCart: function (name, price, count) {
for (var item in this.cart) {
if (this.cart[item].name === name) {
this.cart[item].count++;
this.saveCart();
return;
}
}
var item = {};
item.count = count;
item.price = price;
item.name = name;
this.cart.push(item);
this.saveCart();
},
saveCart: function ()
{
sessionStorage.setItem('shoppingCart', JSON.stringify(this.cart));
return;
},
// Load cart
loadCart: function () {
this.cart = JSON.parse(sessionStorage.getItem('shoppingCart'));
},
setCountForItem : function (name, count) {
for (var i in this.cart) {
if (this.cart[i].name === name) {
this.cart[i].count = count;
this.saveCart();
break;
}
}
},
removeItemFromCart : function (name) {
for (var item in this.cart) {
if (this.cart[item].name === name) {
this.cart[item].count--;
if (this.cart[item].count === 0) {
this.cart.splice(item, 1);
}
this.saveCart();
break;
}
}
this.saveCart();
},
removeItemFromCartAll :function (name) {
for (var item in this.cart) {
if (this.cart[item].name === name) {
this.cart.splice(item, 1);
this.saveCart();
break;
}
}
//this.saveCart();
},
clearCart : function () {
cart = [];
this.saveCart();
},
totalCount : function () {
var totalCount = 0;
for (var item in this.cart) {
totalCount += this.cart[item].count;
}
return totalCount;
},
totalCart : function () {
var totalCart = 0;
for (var item in this.cart) {
totalCart += this.cart[item].price * this.cart[item].count;
}
return Number(totalCart.toFixed(2));
},
listCart : function () {
var cartCopy = [];
for (i in this.cart) {
//item =cart[i];
itemCopy = {};
for (p in this.cart[i]) {
// itemCopy[p] = this.cart[i][p];
itemCopy[p] = this.cart[i][p];
}
itemCopy.total = Number(this.cart[i].price * this.cart[i].count).toFixed(2);
cartCopy.push(itemCopy)
}
return cartCopy;
},
},
created: function () {
if (sessionStorage.getItem("shoppingCart") != null) {
this.loadCart();
}
},
})
</script>
so I am trying to mimic this code pen https://codepen.io/airnan/pen/ZLVJmq but when I try to load the same animation in with lottie-web npm package everything works great until the last animation. Then it all gets messed up. I'm trying to figure it out but I am so confused. Any ideas as to why? The JSON file is exactly the same except for text_data variable. I just import the whole thing and access it directly. Heres the code. it's just the last frame that doesn't work. it doesn't animate the text at all instead it looks like this.
<template>
<div>
<button #click="render">Render</button>
<h2>Backgrounds</h2>
<template v-for="background in backgrounds">
<img
:src="background.poster"
class="backgrounds"
#click="changeBackground(background.video)"
/>
</template>
<h2>Images</h2>
<template v-for="image in images">
<img
:src="image.source"
#click="addImage(image.source, image.type || null)"
class="images"
/>
</template>
<br />
<button #click="addText">Add Text</button>
<button v-if="selectedNode" #click="removeNode">
Remove selected {{ selectedNode.type }}
</button>
<label>Font:</label>
<select v-model="selectedFont">
<option value="Arial">Arial</option>
<option value="Courier New">Courier New</option>
<option value="Times New Roman">Times New Roman</option>
<option value="Desoto">Desoto</option>
<option value="Kalam">Kalam</option>
</select>
<label>Font Size</label>
<input type="number" v-model="selectedFontSize" />
<label>Font Style:</label>
<select v-model="selectedFontStyle">
<option value="normal">Normal</option>
<option value="bold">Bold</option>
<option value="italic">Italic</option>
</select>
<label>Color:</label>
<input type="color" v-model="selectedColor" />
<button
v-if="selectedNode && selectedNode.type === 'text'"
#click="updateText"
>
Update Text
</button>
<template v-if="selectedNode && selectedNode.lottie">
<input type="text" v-model="text">
<button #click="updateAnim(selectedNode.lottie.imgSrc, 'anim')">
Update Animation
</button>
</template>
<br />
<video
id="preview"
v-show="preview"
:src="preview"
:width="width"
:height="height"
preload="auto"
controls
/>
<a v-if="file" :href="file" download="dopeness.mp4">download</a>
<div id="container"></div>
</div>
</template>
<script>
import lottie from "lottie-web";
import * as animationData from "../data.json";
animationData.layers[0].t.d.k[0].s.t = "text";
animationData.layers[1].t.d.k[0].s.t = "text";
animationData.layers[2].t.d.k[0].s.t = "text";
animationData.layers[3].t.d.k[0].s.t = "text";
animationData.layers[4].t.d.k[0].s.t = "text";
export default {
data() {
return {
source: null,
stage: null,
layer: null,
video: null,
animations: [],
text: "",
animationData: animationData.default,
captures: [],
backgrounds: [
{
poster: "/api/files/stock/3oref310k1uud86w/poster/poster.jpg",
video:
"/api/files/stock/3oref310k1uud86w/main/1080/3oref310k1uud86w_1080.mp4"
},
{
poster: "/api/files/stock/3yj2e30tk5x6x0ww/poster/poster.jpg",
video:
"/api/files/stock/3yj2e30tk5x6x0ww/main/1080/3yj2e30tk5x6x0ww_1080.mp4"
},
{
poster: "/api/files/stock/2ez931ik1mggd6j/poster/poster.jpg",
video:
"/api/files/stock/2ez931ik1mggd6j/main/1080/2ez931ik1mggd6j_1080.mp4"
},
{
poster: "/api/files/stock/yxrt4ej4jvimyk15/poster/poster.jpg",
video:
"/api/files/stock/yxrt4ej4jvimyk15/main/1080/yxrt4ej4jvimyk15_1080.mp4"
},
{
poster:
"https://images.costco-static.com/ImageDelivery/imageService?profileId=12026540&itemId=100424771-847&recipeName=680",
video: "/api/files/jedi/surfing.mp4"
},
{
poster:
"https://thedefensepost.com/wp-content/uploads/2018/04/us-soldiers-afghanistan-4308413-1170x610.jpg",
video: "/api/files/jedi/soldiers.mp4"
}
],
images: [
{ source: "/api/files/jedi/solo.jpg" },
{ source: "api/files/jedi/yoda.jpg" },
{ source: "api/files/jedi/yodaChristmas.jpg" },
{ source: "api/files/jedi/darthMaul.jpg" },
{ source: "api/files/jedi/darthMaul1.jpg" },
{ source: "api/files/jedi/trump.jpg" },
{ source: "api/files/jedi/hat.png" },
{ source: "api/files/jedi/trump.png" },
{ source: "api/files/jedi/bernie.png" },
{ source: "api/files/jedi/skywalker.png" },
{ source: "api/files/jedi/vader.gif" },
{ source: "api/files/jedi/vader2.gif" },
{ source: "api/files/jedi/yoda.gif" },
{ source: "api/files/jedi/kylo.gif" },
{
source: "https://media3.giphy.com/media/R3IxJW14a3QNa/source.gif",
type: "anim"
}
],
backgroundVideo: null,
imageGroups: [],
anim: null,
selectedNode: null,
selectedFont: "Arial",
selectedColor: "black",
selectedFontSize: 20,
selectedFontStyle: "normal",
width: 1920,
height: 1080,
texts: [],
preview: null,
file: null,
canvas: null
};
},
mounted: function() {
this.initCanvas();
},
methods: {
changeBackground(source) {
this.source = source;
this.video.src = this.source;
this.anim.stop();
this.anim.start();
this.video.play();
},
removeNode() {
if (this.selectedNode && this.selectedNode.type === "text") {
this.selectedNode.transformer.destroy(
this.selectedNode.text.transformer
);
this.selectedNode.text.destroy(this.selectedNode.text);
this.texts.splice(this.selectedNode.text.index - 1, 1);
this.selectedNode = null;
this.layer.draw();
} else if (this.selectedNode && this.selectedNode.type == "image") {
this.selectedNode.group.destroy(this.selectedNode);
this.imageGroups.splice(this.selectedNode.group.index - 1, 1);
if (this.selectedNode.lottie) {
clearTimeout(this.animations.interval);
this.selectedNode.lottie.destroy();
this.animations.splice(this.selectedNode.lottie.index - 1, 1);
}
this.selectedNode = null;
this.layer.draw();
}
},
async addImage(src, anim, isUpdate) {
let lottieAnimation = null;
let imageObj = null;
const type = anim || src.slice(src.lastIndexOf("."));
const vm = this;
function process(img) {
return new Promise((resolve, reject) => {
img.onload = () => resolve({ width: img.width, height: img.height });
});
}
imageObj = new Image();
imageObj.src = src;
imageObj.width = 200;
imageObj.height = 200;
await process(imageObj);
if (type === ".gif") {
const canvas = document.createElement("canvas");
canvas.setAttribute("id", "gif");
async function onDrawFrame(ctx, frame) {
ctx.drawImage(frame.buffer, frame.x, frame.y);
// redraw the layer
vm.layer.draw();
}
gifler(src).frames(canvas, onDrawFrame);
canvas.onload = async () => {
canvas.parentNode.removeChild(canvas);
};
imageObj = canvas;
const gif = new Image();
gif.src = src;
const gifImage = await process(gif);
imageObj.width = gifImage.width;
imageObj.height = gifImage.height;
} else if (type === "anim") {
if(!isUpdate){this.text = "new text";}
const canvas = document.createElement("canvas");
canvas.style.height = 200;
canvas.style.width = 200;
canvas.setAttribute("id", "animationCanvas");
const ctx = canvas.getContext("2d");
const div = document.createElement("div");
div.setAttribute("id", "animationContainer");
div.style.display = "none";
canvas.style.display = "none";
this.animationData.layers[0].t.d.k[0].s.t = this.text;
this.animationData.layers[1].t.d.k[0].s.t = this.text;
this.animationData.layers[2].t.d.k[0].s.t = this.text;
this.animationData.layers[3].t.d.k[0].s.t = this.text;
this.animationData.layers[4].t.d.k[0].s.t = this.text;
lottieAnimation = lottie.loadAnimation({
container: div, // the dom element that will contain the animation
renderer: "svg",
loop: true,
autoplay: true,
animationData: this.animationData
});
lottieAnimation.imgSrc = src;
lottieAnimation.text = this.text;
const timer = setInterval(async () => {
const svg = await div.getElementsByTagName("svg")[0];
const xml = new XMLSerializer().serializeToString(svg);
const svg64 = window.btoa(xml);
const b64Start = "data:image/svg+xml;base64,";
const image64 = b64Start + svg64;
imageObj = new Image({ width: 200, height: 200 });
imageObj.src = image64;
await process(imageObj);
ctx.clearRect(0, 0, canvas.width, canvas.height);
ctx.drawImage(imageObj, 0, 0, canvas.width, canvas.height);
this.layer.draw();
}, 1000 / 30);
this.animations.push({ lottie: lottieAnimation, interval: timer });
imageObj = canvas;
canvas.onload = async () => {
canvas.parentNode.removeChild(canvas);
};
}
const image = new Konva.Image({
x: 50,
y: 50,
image: imageObj,
width: imageObj.width,
height: imageObj.height,
position: (0, 0),
strokeWidth: 10,
stroke: "blue",
strokeEnabled: false
});
const group = new Konva.Group({
draggable: true
});
// add the shape to the layer
addAnchor(group, 0, 0, "topLeft");
addAnchor(group, imageObj.width, 0, "topRight");
addAnchor(group, imageObj.width, imageObj.height, "bottomRight");
addAnchor(group, 0, imageObj.height, "bottomLeft");
imageObj = null;
image.on("click", function () {
vm.hideAllHelpers();
vm.selectedNode = {
type: "image",
group,
lottie: lottieAnimation
};
if(lottieAnimation && lottieAnimation.text){vm.text = lottieAnimation.text}
group.find("Circle").show();
vm.layer.draw();
});
image.on("mouseover", function(evt) {
if (vm.selectedNode && vm.selectedNode.type === "image") {
const index = image.getParent().index;
const groupId = vm.selectedNode.group.index;
if (index != groupId) {
evt.target.strokeEnabled(true);
vm.layer.draw();
}
} else {
evt.target.strokeEnabled(true);
vm.layer.draw();
}
});
image.on("mouseout", function(evt) {
evt.target.strokeEnabled(false);
vm.layer.draw();
});
vm.hideAllHelpers();
group.find("Circle").show();
group.add(image);
vm.layer.add(group);
vm.imageGroups.push(group);
vm.selectedNode = {
type: "image",
group,
lottie: lottieAnimation
};
vm.layer.draw();
function update(activeAnchor) {
const group = activeAnchor.getParent();
let topLeft = group.get(".topLeft")[0];
let topRight = group.get(".topRight")[0];
let bottomRight = group.get(".bottomRight")[0];
let bottomLeft = group.get(".bottomLeft")[0];
let image = group.get("Image")[0];
let anchorX = activeAnchor.getX();
let anchorY = activeAnchor.getY();
// update anchor positions
switch (activeAnchor.getName()) {
case "topLeft":
topRight.y(anchorY);
bottomLeft.x(anchorX);
break;
case "topRight":
topLeft.y(anchorY);
bottomRight.x(anchorX);
break;
case "bottomRight":
bottomLeft.y(anchorY);
topRight.x(anchorX);
break;
case "bottomLeft":
bottomRight.y(anchorY);
topLeft.x(anchorX);
break;
}
image.position(topLeft.position());
let width = topRight.getX() - topLeft.getX();
let height = bottomLeft.getY() - topLeft.getY();
if (width && height) {
image.width(width);
image.height(height);
}
}
function addAnchor(group, x, y, name) {
let stage = vm.stage;
let layer = vm.layer;
let anchor = new Konva.Circle({
x: x,
y: y,
stroke: "#666",
fill: "#ddd",
strokeWidth: 2,
radius: 8,
name: name,
draggable: true,
dragOnTop: false
});
anchor.on("dragmove", function() {
update(this);
layer.draw();
});
anchor.on("mousedown touchstart", function() {
group.draggable(false);
this.moveToTop();
});
anchor.on("dragend", function() {
group.draggable(true);
layer.draw();
});
// add hover styling
anchor.on("mouseover", function() {
let layer = this.getLayer();
document.body.style.cursor = "pointer";
this.strokeWidth(4);
layer.draw();
});
anchor.on("mouseout", function() {
let layer = this.getLayer();
document.body.style.cursor = "default";
this.strokeWidth(2);
layer.draw();
});
group.add(anchor);
}
},
async updateAnim(src, type){
this.addImage(src, type, true);
this.removeNode();
},
hideAllHelpers() {
for (let i = 0; i < this.texts.length; i++) {
this.texts[i].transformer.hide();
}
for (let b = 0; b < this.imageGroups.length; b++) {
this.imageGroups[b].find("Circle").hide();
}
},
async startRecording(duration) {
const chunks = []; // here we will store our recorded media chunks (Blobs)
const stream = this.canvas.captureStream(30); // grab our canvas MediaStream
const rec = new MediaRecorder(stream, {
videoBitsPerSecond: 20000 * 1000
});
// every time the recorder has new data, we will store it in our array
rec.ondataavailable = e => chunks.push(e.data);
// only when the recorder stops, we construct a complete Blob from all the chunks
rec.onstop = async e => {
this.anim.stop();
const blob = new Blob(chunks, {
type: "video/webm"
});
this.preview = await URL.createObjectURL(blob);
const video = window.document.getElementById("preview");
const previewVideo = new Konva.Image({
image: video,
draggable: false,
width: this.width,
height: this.height
});
this.layer.add(previewVideo);
console.log("video", video);
video.addEventListener("ended", () => {
console.log("preview ended");
if (!this.file) {
const vid = new Whammy.fromImageArray(this.captures, 30);
this.file = URL.createObjectURL(vid);
}
previewVideo.destroy();
this.anim.stop();
this.anim.start();
this.video.play();
});
let seekResolve;
video.addEventListener("seeked", async () => {
if (seekResolve) seekResolve();
});
video.addEventListener("loadeddata", async () => {
let interval = 1 / 30;
let currentTime = 0;
while (currentTime <= duration && !this.file) {
video.currentTime = currentTime;
await new Promise(r => (seekResolve = r));
this.layer.draw();
let base64ImageData = this.canvas.toDataURL("image/webp");
this.captures.push(base64ImageData);
currentTime += interval;
video.currentTime = currentTime;
}
this.layer.draw();
});
};
rec.start();
setTimeout(() => rec.stop(), duration);
},
async render() {
this.captures = [];
this.preview = null;
this.file = null;
console.log(this.captures.length);
this.hideAllHelpers();
this.selectedNode = null;
this.video.currentTime = 0;
this.video.loop = false;
const duration = this.video.duration * 1000;
this.startRecording(duration);
this.layer.draw();
},
updateText() {
if (this.selectedNode && this.selectedNode.type === "text") {
const text = this.selectedNode.text;
const transformer = this.selectedNode.transformer;
text.fontSize(this.selectedFontSize);
text.fontFamily(this.selectedFont);
text.fontStyle(this.selectedFontStyle);
text.fill(this.selectedColor);
this.layer.draw();
}
},
addText() {
const vm = this;
const text = new Konva.Text({
text: "new text " + (vm.texts.length + 1),
x: 50,
y: 80,
fontSize: this.selectedFontSize,
fontFamily: this.selectedFont,
fontStyle: this.selectedFontStyle,
fill: this.selectedColor,
align: "center",
width: this.width * 0.5,
draggable: true
});
const transformer = new Konva.Transformer({
node: text,
keepRatio: true,
enabledAnchors: ["top-left", "top-right", "bottom-left", "bottom-right"]
});
text.on("click", async () => {
for (let i = 0; i < this.texts.length; i++) {
let item = this.texts[i];
if (item.index === text.index) {
let transformer = item.transformer;
this.selectedNode = { type: "text", text, transformer };
this.selectedFontSize = text.fontSize();
this.selectedFont = text.fontFamily();
this.selectedFontStyle = text.fontStyle();
this.selectedColor = text.fill();
vm.hideAllHelpers();
transformer.show();
transformer.moveToTop();
text.moveToTop();
vm.layer.draw();
break;
}
}
});
text.on("mouseover", () => {
transformer.show();
this.layer.draw();
});
text.on("mouseout", () => {
if (
(this.selectedNode &&
this.selectedNode.text &&
this.selectedNode.text.index != text.index) ||
(this.selectedNode && this.selectedNode.type === "image") ||
!this.selectedNode
) {
transformer.hide();
this.layer.draw();
}
});
text.on("dblclick", () => {
text.hide();
transformer.hide();
vm.layer.draw();
let textPosition = text.absolutePosition();
let stageBox = vm.stage.container().getBoundingClientRect();
let areaPosition = {
x: stageBox.left + textPosition.x,
y: stageBox.top + textPosition.y
};
let textarea = document.createElement("textarea");
window.document.body.appendChild(textarea);
textarea.value = text.text();
textarea.style.position = "absolute";
textarea.style.top = areaPosition.y + "px";
textarea.style.left = areaPosition.x + "px";
textarea.style.width = text.width() - text.padding() * 2 + "px";
textarea.style.height = text.height() - text.padding() * 2 + 5 + "px";
textarea.style.fontSize = text.fontSize() + "px";
textarea.style.border = "none";
textarea.style.padding = "0px";
textarea.style.margin = "0px";
textarea.style.overflow = "hidden";
textarea.style.background = "none";
textarea.style.outline = "none";
textarea.style.resize = "none";
textarea.style.lineHeight = text.lineHeight();
textarea.style.fontFamily = text.fontFamily();
textarea.style.transformOrigin = "left top";
textarea.style.textAlign = text.align();
textarea.style.color = text.fill();
let rotation = text.rotation();
let transform = "";
if (rotation) {
transform += "rotateZ(" + rotation + "deg)";
}
let px = 0;
let isFirefox =
navigator.userAgent.toLowerCase().indexOf("firefox") > -1;
if (isFirefox) {
px += 2 + Math.round(text.fontSize() / 20);
}
transform += "translateY(-" + px + "px)";
textarea.style.transform = transform;
textarea.style.height = "auto";
textarea.focus();
// start
function removeTextarea() {
textarea.parentNode.removeChild(textarea);
window.removeEventListener("click", handleOutsideClick);
text.show();
transformer.show();
transformer.forceUpdate();
vm.layer.draw();
}
function setTextareaWidth(newWidth) {
if (!newWidth) {
// set width for placeholder
newWidth = text.placeholder.length * text.fontSize();
}
// some extra fixes on different browsers
let isSafari = /^((?!chrome|android).)*safari/i.test(
navigator.userAgent
);
let isFirefox =
navigator.userAgent.toLowerCase().indexOf("firefox") > -1;
if (isSafari || isFirefox) {
newWidth = Math.ceil(newWidth);
}
let isEdge =
document.documentMode || /Edge/.test(navigator.userAgent);
if (isEdge) {
newWidth += 1;
}
textarea.style.width = newWidth + "px";
}
textarea.addEventListener("keydown", function(e) {
// hide on enter
// but don't hide on shift + enter
if (e.keyCode === 13 && !e.shiftKey) {
text.text(textarea.value);
removeTextarea();
}
// on esc do not set value back to node
if (e.keyCode === 27) {
removeTextarea();
}
});
textarea.addEventListener("keydown", function(e) {
let scale = text.getAbsoluteScale().x;
setTextareaWidth(text.width() * scale);
textarea.style.height = "auto";
textarea.style.height =
textarea.scrollHeight + text.fontSize() + "px";
});
function handleOutsideClick(e) {
if (e.target !== textarea) {
text.text(textarea.value);
removeTextarea();
}
}
setTimeout(() => {
window.addEventListener("click", handleOutsideClick);
});
// end
});
text.transformer = transformer;
this.texts.push(text);
this.layer.add(text);
this.layer.add(transformer);
this.hideAllHelpers();
this.selectedNode = { type: "text", text, transformer };
transformer.show();
this.layer.draw();
},
initCanvas() {
const vm = this;
this.stage = new Konva.Stage({
container: "container",
width: vm.width,
height: vm.height
});
this.layer = new Konva.Layer();
this.stage.add(this.layer);
let video = document.createElement("video");
video.setAttribute("id", "video");
video.setAttribute("ref", "video");
if (this.source) {
video.src = this.source;
}
video.preload = "auto";
video.loop = "loop";
video.style.display = "none";
this.video = video;
this.backgroundVideo = new Konva.Image({
image: vm.video,
draggable: false
});
this.video.addEventListener("loadedmetadata", function(e) {
vm.backgroundVideo.width(vm.width);
vm.backgroundVideo.height(vm.height);
});
this.video.addEventListener("ended", () => {
console.log("the video ended");
this.anim.stop();
this.anim.start();
this.video.loop = "loop";
this.video.play();
});
this.anim = new Konva.Animation(function() {
console.log("animation called");
// do nothing, animation just need to update the layer
}, vm.layer);
this.layer.add(this.backgroundVideo);
this.layer.draw();
const canvas = document.getElementsByTagName("canvas")[0];
canvas.style.border = "3px solid red";
this.canvas = canvas;
}
}
};
</script>
<style scoped>
body {
margin: 0;
padding: 0;
background-color: #f0f0f0;
}
.backgrounds,
.images {
width: 100px;
height: 100px;
padding-left: 2px;
padding-right: 2px;
}
</style>
You must use keyframes in order to get the animation to render properly once you do that you will have no issues.
I have a problem about dynamite vue component beyond my ability, here is the question:
1.click icons
icons
2.put components into an array and render components dynamitically
click icons and render components
3.click tabs, tabs will be removed and, components relatived with tabs will be removed too
close tabs and windows
when I close tabs or windows from right, everything is ok, but when I close tabs or windows which index less then active window, the active window will refresh。I tried a lot of ways to resolve it, but it doesnt work.
Is there someone else had the same problem, or give me some suggestions, thanks.
Router:
var router = [{
path: '',
redirect: '/layout'
}, {
path: '/',
component: App,
redirect: '/layout',
children: [{
path: '/login',
component: login,
beforeEnter: redirectToHome
},
{
path: '/layout',
component: layout
},
{
path: '/getbackPwd',
component: getbackPwd
},
{
path: '/*',
component: notfound
}]
}]
Layout:
<div class="mu-layout">
<LayoutHead>
<div class="mu-layout-head-left">
<a class="mu-layout-showdesk" #click="toggleShowDesk"></a>
<a class="mu-layout-toggle-sidebar" #click="toggleSidebar">
<i class="el-icon-menu"></i>
</a>
<layout-tab v-for="(tab, i) in iconTabs"
:key="i"
:title="tab.title"
:component="tab.componentName"
:img="tab.img"
:minisize="tab.showMinisize"
:current="tab.current"
#close="closeTab"
#choose="chooseTab"
:tabIdx="i"
></layout-tab>
</div>
<div class="mu-layout-head-right">
<a class="mu-layout-head-link">
<i class="el-icon-search"></i>
</a>
<a class="mu-layout-head-link">
<i class="el-icon-warning" title="通知"></i>
</a>
<a class="mu-layout-head-link">
<i class="iconfont icon-act7"></i>
<span class="head-user-wrap">
{{loginUser && loginUser.username}}
</span>
</a>
<el-popover placement="bottom"
width="160"
trigger="hover"
popper-class="rect-popper"
>
<ul class="mu-layout-list mu-setting-list">
<li class="mu-layout-item">
<a class="mu-layout-item-inner" #click="openSetting"><i class="el-icon-edit-outline"></i>修改密码</a>
<a class="mu-layout-item-inner" #click="logout"><i class="el-icon-logout"></i>注销</a>
</li>
</ul>
<a class="mu-layout-head-link" slot="reference"><i class="icon-more"></i></a>
</el-popover>
</div>
</LayoutHead>
<div class="mu-layout-content">
<div class="swiper-container swiper-no-swiping">
<div class="swiper-wrapper">
<div class="swiper-slide">
<layout-iconmenu-panel>
<layout-icon-menu :img="imenu.img"
:component="imenu.component"
:title="imenu.title"
:menu-id="imenu.menu_id"
v-for="(imenu, i) in iconMenus"
:key="i"
#click="clickIconMenu"
></layout-icon-menu>
</layout-iconmenu-panel>
</div>
<div class="swiper-slide">Slide 2</div>
<div class="swiper-slide">Slide 3</div>
</div>
<div class="swiper-pagination"></div>
<div class="swiper-button-next"></div>
<div class="swiper-button-prev"></div>
</div>
</div>
<win-panel :title="comp.title"
:tool-title="`${comp.title}(${comp.componentName})`"
:minisize-event="minisize"
#close-event="close"
:component="comp.componentName"
:ref="comp.componentName"
#choose="chooseWin"
:current="comp.current"
v-for="(comp, i) in wpanels"
:key="i"
v-model="comp.show"
:resizable="comp.resizable"
:win-width="comp.width"
:win-height="comp.height"
:is-sys="comp.isSys"
:clazz="comp.classname"
#monitor-win="monitorWin"
:rect="{drag_x: comp.drag_x, drag_y: comp.drag_y , resize_x: comp.resize_x, resize_y: comp.resize_y}"
>
<component :is="comp.comp" ></component>
</win-panel>
<layout-sidebar v-model="sidebarModel"></layout-sidebar>
</div>
import $ from 'jquery'
import LayoutHead from './head'
import LayoutFoot from './foot'
import LayoutLaunch from './button'
import LayoutIconMenu from './iconmenu'
import LayoutIconmenuPanel from './iconmenu-panel'
import LayoutTab from './tabs'
import LayoutPost from './post'
import LayoutSidebar from './sidebar'
import WinPanel from './winpanel'
import * as Components from '../getComponent'
import {
setActivePanel,
getActivePanel,
removeActivePanel,
logout,
getLoginUser,
getLoguserMenus,
setMenuId,
getMenuId
} from '../utils/auth'
import { stopPropagation } from '../utils'
import Swiper from 'swiper'
export default {
components: {
LayoutTab,
LayoutHead,
LayoutPost,
LayoutFoot,
LayoutLaunch,
LayoutSidebar,
LayoutIconMenu,
LayoutIconmenuPanel,
WinPanel,
},
data() {
var _userMenus = getLoguserMenus()
var $menus = _userMenus ? JSON.parse(_userMenus).map(e => {
return {
img: e.menu_icon,
component: e.menu_component,
title: e.menu_name,
menu_id: e.menu_id
}
}).filter(e => e.component!= 'login') : [] //排除登录组件
return {
Components,
sidebarModel: false,
loginUser: getLoginUser(),
iconMenus: $menus,
iconTabs: [],
wpanels: [],
showDeskModel: false,
activeIdx: 0, //当前激活的索引
}
},
methods: {
/*
* 根据传入的arrs进行map 操作,在回调中传入遍历的参数
*/
filterArrs(comName, cb, arrs) {
arrs = arrs ? arrs : this.iconTabs
arrs = arrs.map(e => {
cb(e)
return e
})
},
/*
* 切换sidebar事件
*/
toggleSidebar(e) {
stopPropagation(e)
this.sidebarModel = !this.sidebarModel
},
/*
* 选择窗口
*/
chooseWin(comName) {
this.toggleTab( comName )
},
/*
* 点击tab的操作事件
*/
chooseTab(comName, minisize, current) {
this.setMenuids(comName)
if(current) {
this.filterArrs(comName, e => {
if(e.componentName == comName) {
e.current = false
}
})
this.filterArrs(comName, e => {
if(e.componentName == comName ) {
e.show = false
}
}, this.wpanels)
}
else {
this.filterArrs(comName, e => {
if(e.componentName == comName ) {
e.current = true
} else {
e.current = false
}
})
this.filterArrs(comName, e => {
if(e.componentName == comName ) {
e.show = true
e.current = true
}
}, this.wpanels)
}
},
/*
* 切换不同的tab
*/
toggleTab(comName) {
this.filterArrs(comName, e => {
if(e.componentName == comName) {
e.current = true
} else {
e.current = false
}
})
this.showDeskModel = !this.showDeskModel
setActivePanel(comName)
this.setMenuids(comName)
},
/*
* 点击icon 菜单
*/
clickIconMenu(componentName, title, img, menuId, config) {
var filters = this.iconTabs.filter(e => e.componentName == componentName)
if(filters.length == 0) {
var _config = {
title,
componentName,
img,
showMinisize: false,
current: true,
comp: Components[componentName],
show: true,
menuId
}
if(componentName == 'sysSetting') {
_config.isSys = true
}
if(config) {
_config = Object.assign(_config, config)
}
this.iconTabs.push(_config)
this.wpanels = this.iconTabs.slice(0)
} else {
this.wpanels = this.wpanels.map(function(e) {
if(e.componentName == componentName) {
e.current = true
e.show = true
} else {
e.current = false
}
return e
})
this.iconTabs = this.iconTabs.map(function(e) {
if(e.componentName == componentName) {
e.current =true
} else {
e.current = false
}
return e
})
}
this.toggleTab( componentName )
this.filterArrs(componentName, e => {
if(e.componentName == componentName) {
e.current = true
} else {
e.current = false
}
}, this.wpanels)
},
/*
* 自动选择最后一个tab 和 窗口
*/
autoChooseLast() {
if(this.iconTabs.length > 0) {
var $fs = this.iconTabs.filter(e => e.current)
if($fs.length == 0) {
setTimeout(_ => {
this.iconTabs.forEach((e, i) => {
if(i == this.iconTabs.length - 1) {
e.current = true
this.wpanels[i].current = true
this.wpanels[i].show = true
setActivePanel(e.componentName)
}
})
})
}
} else {
removeActivePanel()
}
},
/*
* 窗口最小化
*/
minisize(id, component, e) {
// stopPropagation(e)
this.filterArrs(component, el => {
if(el.componentName == component) {
el.current = false
}
})
this.filterArrs(component, el => {
if(el.componentName == component) {
el.show = false
el.current = false
}
}, this.wpanels)
// this.autoChooseLast()
},
/*
* 关闭窗口事件
* 和关闭tab 相同,需要阻止冒泡,否则最外层的事件会冲突
*/
close(component, e) {
stopPropagation(e)
this.closeTab(component, e)
},
/*
* 关闭tab,同时关闭相关联的窗口
*/
closeTab(comName, e) {
stopPropagation(e)
this.iconTabs = this.iconTabs.filter(el => el.componentName != comName)
this.wpanels = this.wpanels.filter(e => e.componentName != comName)
this.autoChooseLast()
},
/*
* 切换显示菜单
*/
toggleShowDesk() {
if(this.iconTabs.length == 0 || this.wpanels.length == 0) {
return
}
if(!this.showDeskModel) {
this.iconTabs = this.iconTabs.map(function(e) {
e.current = false
return e
})
this.wpanels = this.wpanels.map(function(e) {
e.show = false
return e
})
} else {
this.wpanels = this.wpanels.map(function(e) {
e.show = true
return e
})
this.autoChooseLast()
}
this.showDeskModel = !this.showDeskModel
}
},
}
Actrually, most of my components dont relate with router, but dynamitecally component rendering.
The website is just like an web os, every icon has its own page, they are all in layout.vue page, the router path always is '/layout'
I am using NuxtJs in my project, I a have list of checkboxes, on click of each checkbox I am sending an array of checkboxes to a my POST api which return data.
Here, when I check the first checkbox it returns the data. But when I check the second checkbox it does not does return the data.
I mean it only returns the data on single checkbox checked.
Its working with normal vuejs but not in nuxtjs
My Code:
<script>
import axios from "axios";
import uniq from "lodash/uniq";
export default {
async asyncData({ req, params }) {
let [storeInfo, feedsInfo] = await Promise.all([
axios.get(
process.env.apiURL +
"/stores/findOne?filter[where][store_name]" +
"=" +
params.id
),
axios.post(process.env.apiURL + "feeds/feedsByStores", {
stores: [params.id]
})
]);
return {
stores: storeInfo.data,
feeds: feedsInfo.data,
categories: uniq(feedsInfo.data.map(p => p.feed_category))
};
},
data() {
return {
checkedCategories: [],
checkedCategory: false,
selectedCategories: []
};
},
methods: {
feedsByCategories: function(categories) {
console.log(categories);
axios.post(process.env.apiURL + "feeds/feedsByCategories", {
categories: [categories]
}).then((res) => {
console.log(res);
})
},
categoryChecked: function(category, checked) {
this.display = "inline";
if (checked) {
this.selectedCategories.push(category);
console.log(this.selectedCategories);
this.feedsByCategories(this.selectedCategories);
} else if (!checked) {
const index = this.selectedCategories.indexOf(category);
this.selectedCategories.splice(index, 1);
this.feedsByCategories(this.selectedCategories);
if (this.selectedCategories == "") {
this.display = "none";
this.getFeeds();
}
}
if (!checked && this.selectedCategories.length === 0) {
this.getFeeds();
}
},
uncheckCategory: function(checkedCategory) {
this.checkedCategories = this.checkedCategories.filter(
name => name !== checkedCategory
);
const index = this.selectedCategories.indexOf(checkedCategory);
this.selectedCategories.splice(index, 1);
this.feedsByCategories(this.selectedCategories);
if (this.checkedCategories == "") {
this.display = "none";
this.getFeeds();
}
},
uncheckallCategories: function(event) {
this.checkedCategories = [];
this.display = "none";
this.search = "";
this.Search = "";
this.filteredCategories;
},
getFeeds() {
return this.feeds;
}
}
};
</script>
<template>
<v-layout>
<ul class="list-unstyled scrollbar">
<li v-for="(feedcategory, index) in categories" :key="feedcategory.id">
<input type="checkbox" name="category" #change="categoryChecked(feedcategory,$event.target.checked)"
:id="index + 1" :value="feedcategory" v-model="checkedCategories">
{{ feedcategory }}
</li>
</ul>
</v-layout>
</template>
My Typo,
I removed the brackets for my categories array and it worked:
feedsByCategories: function(categories) {
console.log(categories);
axios.post(process.env.apiURL + "feeds/feedsByCategories", {
categories: categories
}).then((res) => {
console.log(res);
})
}