已填写过的不显示; 反馈表结果页面; 默认5星, 增加填写提示; 映射的链接格式

master
Lei OT 2 years ago
parent 96302e5bd6
commit 5dd052645f

@ -9,8 +9,8 @@
#result{text-align:center;padding:20px}.bi-check-circle-fill,.bi-exclamation-circle-fill,.bi-x-circle-fill{fill:#d54e21}.bi-x-circle-fill{fill:#d54e21} #result{text-align:center;padding:20px}.bi-check-circle-fill,.bi-exclamation-circle-fill,.bi-x-circle-fill{fill:#d54e21}.bi-x-circle-fill{fill:#d54e21}
</style> </style>
<style>#loading-mask{width:100vw;height:100vh;background-color:#00000099;position:fixed;left:0;top:0;display:flex}.lds-facebook{display:inline-block;position:relative;width:80px;height:80px;margin:auto}.lds-facebook div{display:inline-block;position:absolute;left:8px;width:16px;background:#fff;animation:lds-facebook 1.2s cubic-bezier(0,.5,.5,1) infinite}.lds-facebook div:nth-child(1){left:8px;animation-delay:-.24s}.lds-facebook div:nth-child(2){left:32px;animation-delay:-.12s}.lds-facebook div:nth-child(3){left:56px;animation-delay:0}@keyframes lds-facebook{0%{top:8px;height:64px}100%,50%{top:24px;height:32px}}</style> <style>#loading-mask{width:100vw;height:100vh;background-color:#00000099;position:fixed;left:0;top:0;display:flex}.lds-facebook{display:inline-block;position:relative;width:80px;height:80px;margin:auto}.lds-facebook div{display:inline-block;position:absolute;left:8px;width:16px;background:#fff;animation:lds-facebook 1.2s cubic-bezier(0,.5,.5,1) infinite}.lds-facebook div:nth-child(1){left:8px;animation-delay:-.24s}.lds-facebook div:nth-child(2){left:32px;animation-delay:-.12s}.lds-facebook div:nth-child(3){left:56px;animation-delay:0}@keyframes lds-facebook{0%{top:8px;height:64px}100%,50%{top:24px;height:32px}}</style>
<style>.city-page{display:flex;flex-direction:column;align-items:center}.city-list{list-style-type:none;padding:0;width:200px;background-color:#fff;border-radius:5px;box-shadow:0 0 10px rgba(0,0,0,.1)}.city-list li{padding:20px;border-top:1px solid #d54e21;border-bottom:1px solid #d54e21;transition:background-color .3s}.city-list li:hover{background-color:#d54e21;color:#fff}</style> <style>.city-page{display:flex;flex-direction:column;align-items:center}.city-list{list-style-type:none;padding:0;width:200px;background-color:#fff;border-radius:5px;box-shadow:0 0 10px rgba(0,0,0,.1)}.city-list li{padding:20px;border-top:1px solid #d54e21;border-bottom:1px solid #d54e21;transition:background-color .3s;position:relative}.city-list li.filled::after{content:'✔';position:absolute;right:20px;color:#d54e21;transition:color .3s}.city-list li:hover{background-color:#d54e21;color:#fff}.city-list li.filled:hover::after{color:#fff}</style>
<style>.float-button{position:fixed;right:20px;bottom:120px;background-color:transparent;color:#d54e21;border:1px solid #d54e21;border-radius:50%;width:60px;height:60px;text-align:center;line-height:60px;font-size:24px;cursor:pointer;box-shadow:0 0 10px rgba(0,0,0,.1);z-index:1}.float-button:hover{background-color:#d54e21;color:#fff}.float-text{position:fixed;right:20px;bottom:190px;color:#d54e21;font-size:18px;text-align:right;z-index:-1}.expand-list{list-style-type:none;position:fixed;right:20px;bottom:190px;width:200px;background-color:#fff;border-radius:5px;box-shadow:0 0 10px rgba(0,0,0,.1);padding:0;max-height:0;overflow:hidden;opacity:0;transition:max-height .5s ease,opacity .5s ease}.expand-list.open{max-height:500px;opacity:1;z-index: 99;}.expand-list li{padding:20px;border-bottom:1px solid #d54e21;transition:background-color .3s}.expand-list li:last-child{border-bottom:none}.expand-list li.active{color:#d54e21}.expand-list li:hover{background-color:#d54e21;color:#fff}</style> <style>.float-button{position:fixed;right:20px;bottom:120px;background-color:transparent;color:#d54e21;border:1px solid #d54e21;border-radius:50%;width:60px;height:60px;text-align:center;line-height:60px;font-size:24px;cursor:pointer;box-shadow:0 0 10px rgba(0,0,0,.1);z-index:1}.float-button:hover{background-color:#d54e21;color:#fff}.float-text{position:fixed;right:20px;bottom:190px;color:#d54e21;font-size:18px;text-align:right;z-index:-1}.expand-list{list-style-type:none;position:fixed;right:20px;bottom:190px;width:200px;background-color:#fff;border-radius:5px;box-shadow:0 0 10px rgba(0,0,0,.1);padding:0;max-height:0;overflow:hidden;opacity:0;transition:max-height .5s ease,opacity .5s ease}.expand-list.open{max-height:500px;opacity:1;z-index: 99;}.expand-list li{padding:20px;border-bottom:1px solid #d54e21;transition:background-color .3s;position: relative;}.expand-list li:last-child{border-bottom:none}.expand-list li.active{color:#d54e21}.expand-list li.filled::after{content:'✔';position:absolute;right:20px;color:#d54e21;transition:color .3s}.expand-list li:hover{background-color:#d54e21;color:#fff}.expand-list li.filled:hover::after{color:#fff}</style>
<style>.star-rating{direction:rtl;display:inline-block}.star-rating input[type=radio]{display:none}.star-rating label{font-size:30px;color:#d3d3d3;cursor:pointer}.star-rating input[type=radio]:checked~label,.star-rating input[type=radio]~label.checked,.star-rating label:hover,.star-rating label:hover~label{color:#d54e21}.rate-meaning{display:inline-block;margin-left:15px;vertical-align:middle;color:#d54e21}</style> <style>.star-rating{direction:rtl;display:inline-block}.star-rating input[type=radio]{display:none}.star-rating label{font-size:30px;color:#d3d3d3;cursor:pointer}.star-rating input[type=radio]:checked~label,.star-rating input[type=radio]~label.checked,.star-rating label:hover,.star-rating label:hover~label{color:#d54e21}.rate-meaning{display:inline-block;margin-left:15px;vertical-align:middle;color:#d54e21}</style>
<script src="./utils.js"></script> <script src="./utils.js"></script>
<script src="./vue@2.js"></script> <script src="./vue@2.js"></script>
@ -55,7 +55,7 @@
<h4 v-if="!isFilled">Thank you for completing the Feedback Evaluation. Once submitted the tour guide would not be able to view your comments. </h4> <h4 v-if="!isFilled">Thank you for completing the Feedback Evaluation. Once submitted the tour guide would not be able to view your comments. </h4>
<div class="city-page"> <div class="city-page">
<ul class="city-list" v-for="(city, index) in cityList" v-bind:key="city.cii_sn" v-on:click="selectCity(city)"> <ul class="city-list" v-for="(city, index) in cityList" v-bind:key="city.cii_sn" v-on:click="selectCity(city)">
<li>{{city.cityName}}</li> <li v-bind:class="[city.feedback_id ? 'filled' : '']">{{city.cityName}}</li>
</ul> </ul>
</div> </div>
</template> </template>
@ -79,58 +79,65 @@
</div> </div>
<hr> <hr>
<p >Thank you for completing the Feedback Evaluation. Once submitted the tour guide would not be able to view your comments. </p> <p >Thank you for completing the Feedback Evaluation. Once submitted the tour guide would not be able to view your comments. </p>
<p v-if="!isWideScreen">Please rate by clicking the stars below.</p>
<form class="form-container" id="myForm" action="/sub" method="post"> <form class="form-container" id="myForm" action="/sub" method="post" disabled>
<div class="question"> <div class="question">
<h4>1. How satisfied were you with your tour guide?</h4> <h4>1. How satisfied were you with your tour guide?</h4>
<div class="question-items" v-for="(q, qi) in feedbackItem.guide" v-bind:key="q.id"> <div class="question-items" v-for="(q, qi) in feedbackItem.guide" v-bind:key="q.id">
<h5>{{q.Describe}}</h5> <h5>{{q.Describe}}</h5>
<options-item v-if="isWideScreen" question="guide" si="1" v-bind:qitem="q" v-bind:qi="qi" v-bind:key="q.id"></options-item> <options-item v-if="isWideScreen" question="guide" si="1" v-bind:qitem="q" v-bind:qi="qi" v-bind:key="q.id" v-bind:disabled="isFilled"></options-item>
<options-item-star v-else question="guide" si="1" v-bind:qitem="q" v-bind:qi="qi" v-bind:key="q.id"></options-item-star> <options-item-star v-else question="guide" si="1" v-bind:qitem="q" v-bind:qi="qi" v-bind:key="q.id" v-bind:disabled="isFilled"></options-item-star>
</div> </div>
</div> </div>
<div class="question"> <div class="question">
<h4>2. How about the Driver and Car/Van?</h4> <h4>2. How about the Driver and Car/Van?</h4>
<div class="question-items" v-for="(q, qi) in feedbackItem.driver" v-bind:key="q.id"> <div class="question-items" v-for="(q, qi) in feedbackItem.driver" v-bind:key="q.id">
<h5>{{q.Describe}}</h5> <h5>{{q.Describe}}</h5>
<options-item v-if="isWideScreen"question="Driver" v-bind:qitem="q" v-bind:si="2" v-bind:qi="qi" v-bind:key="q.id"></options-item> <options-item v-if="isWideScreen"question="Driver" v-bind:qitem="q" v-bind:si="2" v-bind:qi="qi" v-bind:key="q.id" v-bind:disabled="isFilled"></options-item>
<options-item-star v-else question="Driver" v-bind:qitem="q" v-bind:si="2" v-bind:qi="qi" v-bind:key="q.id"></options-item> <options-item-star v-else question="Driver" v-bind:qitem="q" v-bind:si="2" v-bind:qi="qi" v-bind:key="q.id" v-bind:disabled="isFilled"></options-item>
</div> </div>
</div> </div>
<div class="question" v-if="feedbackItem.experience.length > 0"> <div class="question" v-if="feedbackItem.experience.length > 0">
<h4>3. General Experience with:</h4> <h4>3. General Experience with:</h4>
<div class="question-items" v-for="(q, qi) in feedbackItem.experience" v-bind:key="q.id"> <div class="question-items" v-for="(q, qi) in feedbackItem.experience" v-bind:key="q.id">
<h5>{{q.Describe}}</h5> <h5>{{q.Describe}}</h5>
<options-item v-if="isWideScreen" question="Experience" v-bind:qitem="q" v-bind:si="3" v-bind:qi="qi" v-bind:key="q.id"></options-item> <options-item v-if="isWideScreen" question="Experience" v-bind:qitem="q" v-bind:si="3" v-bind:qi="qi" v-bind:key="q.id" v-bind:disabled="isFilled"></options-item>
<options-item-star v-else question="Experience" v-bind:qitem="q" v-bind:si="3" v-bind:qi="qi" v-bind:key="q.id"></options-item> <options-item-star v-else question="Experience" v-bind:qitem="q" v-bind:si="3" v-bind:qi="qi" v-bind:key="q.id" v-bind:disabled="isFilled"></options-item>
</div> </div>
</div> </div>
<div class="question " v-if="showPhotos"> <div class="question " v-if="showPhotos">
<h4>4. Would you give Asia Highlights permission to use photos taken by your tour guide(s) that contain your picture?</h2> <h4>4. Would you give Asia Highlights permission to use photos taken by your tour guide(s) that contain your picture?</h2>
<judgment-item question="photo" v-bind:qitem="{id: 'photo_permission', rate: feedbackEvaluation.usePhotos}" si="photo" qi="photo" key="photo"></judgment-item> <judgment-item question="photo" v-bind:qitem="{id: 'photo_permission', rate: feedbackEvaluation.usePhotos}" si="photo" qi="photo" key="photo" v-bind:disabled="isFilled"></judgment-item>
</div> </div>
<div class="feedback question"> <div class="feedback question">
<h4>Any other comments that you would like to share with us?</h4> <h4>Any other comments that you would like to share with us?</h4>
<textarea name="comments" rows="4" v-bind:value="feedbackEvaluation.otherComments"></textarea> <p v-if="isFilled">{{feedbackEvaluation.otherComments ? feedbackEvaluation.otherComments : '--'}}</p>
<textarea v-else name="comments" rows="4" v-bind:value="feedbackEvaluation.otherComments" v-bind:disabled="isFilled"></textarea>
</div> </div>
<div class="signature"> <div class="signature">
<h4>Signature:</h4> <h4>Signature:</h4>
<div class="signature-container"> <div class="signature-container" v-if="!isFilled">
<canvas id="signature-pad" class="signature-pad" v-bind:width="drawWidth" height=200 ></canvas> <canvas id="signature-pad" class="signature-pad" v-bind:width="drawWidth" height=200 ></canvas>
<div class="button-container"> <div class="button-container">
<button type="button" id="clear" class="button" v-on:click="clearSignature()">Clear</button> <button type="button" id="clear" class="button" v-on:click="clearSignature()">Clear</button>
</div> </div>
</div> </div>
<div v-else>
<img id="signature-img" v-bind:src="feedbackEvaluation.signatureDataUrl" v-bind:width="drawWidth" />
</div>
</div> </div>
<button type="button" id="submit_a" class="form-btn submit-btn" v-on:click="submitForm">Finish & Submit</button> <template v-if="!isFilled">
<button type="button" id="back" class="form-btn cancel-btn" v-on:click="cancel">Cancel</button> <button type="button" id="submit_a" class="form-btn submit-btn" v-on:click="submitForm">Finish & Submit</button>
<button type="button" id="back" class="form-btn cancel-btn" v-on:click="cancel">Cancel</button>
</template>
</form> </form>
<span id="currentCity" class="float-text" v-show="!cityListOpen">{{group.cityName}}</span> <span id="currentCity" class="float-text" v-show="!cityListOpen">{{group.cityName}}</span>
<button class="float-button" v-on:click="toggleCityList"></button> <button class="float-button" v-on:click="toggleCityList"></button>
<ul v-bind:class="['expand-list', cityListOpen ? 'open' : '']"> <ul v-bind:class="['expand-list', cityListOpen ? 'open' : '']">
<li v-for="city in cityList" v-bind:key="city.cii_sn" v-on:click="selectCity(city)" v-bind:class="{'active': city.cii_sn === currentCity.cii_sn}">{{city.cityName}}</li> <li v-for="city in cityList" v-bind:key="city.cii_sn" v-on:click="selectCity(city)" v-bind:class="{'active': city.cii_sn === currentCity.cii_sn, 'filled': (currentCity.feedback_id ? true : false)}">{{city.cityName}}</li>
</ul> </ul>
</template> </template>
@ -179,19 +186,19 @@
<script defer> <script defer>
// 定义名为 options-item 的新组件 // 定义名为 options-item 的新组件
Vue.component('options-item', { Vue.component('options-item', {
props: ['question', 'si', 'qi', 'qitem'], props: ['question', 'si', 'qi', 'qitem', 'disabled'],
template: '<div :class="[\'options\', isWideScreen ? \'\': \'options-row-reverse\']">' + template: '<div :class="[\'options\', isWideScreen ? \'\': \'options-row-reverse\']">' +
// template: '<div :class="[\'options\', isWideScreen ? \'\': \'options-row1\']">' + // template: '<div :class="[\'options\', isWideScreen ? \'\': \'options-row1\']">' +
'<span class="rate-meaning" v-if="!isWideScreen"><span>{{selectedRatingText}}</span></span>' + '<span class="rate-meaning" v-if="!isWideScreen"><span>{{selectedRatingText}}</span></span>' +
'<div class="radio-container" v-for="item in rateList">' + '<div class="radio-container" v-for="item in rateList">' +
'<input type="radio" v-bind:id="item.value + \'-\' + si + \'-\' + qi" v-bind:name="\'rate-\'+qitem.id" v-bind:value="item.value" v-model="selectItem" >' + '<input type="radio" v-bind:id="item.value + \'-\' + si + \'-\' + qi" v-bind:name="\'rate-\'+qitem.id" v-bind:value="item.value" v-bind:disabled="disabled" v-model="selectItem" >' +
'<label v-bind:for="item.value+\'-\' + si + \'-\' + qi" class="radio-label"><span v-html="item.svg"></span> {{isWideScreen ? item.name : ""}}</label>' + '<label v-bind:for="item.value+\'-\' + si + \'-\' + qi" class="radio-label"><span v-html="item.svg"></span> {{isWideScreen ? item.name : ""}}</label>' +
'</div>' + '</div>' +
'</div>', '</div>',
data() { data() {
return { return {
rateList: rateList, rateList: rateList,
selectItem: String(this.qitem.rate), selectItem: String(this.qitem.rate || 5),
} }
}, },
computed: { computed: {
@ -204,9 +211,9 @@
}, },
}); });
Vue.component('options-item-star', { Vue.component('options-item-star', {
props: ['question', 'si', 'qi', 'qitem'], props: ['question', 'si', 'qi', 'qitem', 'disabled'],
template: '<div><div class="star-rating">' + template: '<div><div class="star-rating">' +
'<template v-for="item in rateList"><input type="radio" v-bind:id="item.value + \'-\' + si + \'-\' + qi" v-bind:name="\'rate-\'+qitem.id" v-bind:value="item.value" v-model="selectItem" >' + '<template v-for="item in rateList"><input type="radio" v-bind:id="item.value + \'-\' + si + \'-\' + qi" v-bind:name="\'rate-\'+qitem.id" v-bind:value="item.value" v-bind:disabled="disabled" v-model="selectItem" >' +
'<label v-bind:for="item.value+\'-\' + si + \'-\' + qi" :class="{\'checked\': selectItem>=item.value}"></label></template>' + '<label v-bind:for="item.value+\'-\' + si + \'-\' + qi" :class="{\'checked\': selectItem>=item.value}"></label></template>' +
'</div>' + '</div>' +
'<span class="rate-meaning"><span id="rate-text">{{selectedRatingText}}</span></span>' + '<span class="rate-meaning"><span id="rate-text">{{selectedRatingText}}</span></span>' +
@ -214,7 +221,7 @@
data() { data() {
return { return {
rateList: rateList, rateList: rateList,
selectItem: String(this.qitem.rate), selectItem: String(this.qitem.rate || 5),
} }
}, },
computed: { computed: {
@ -226,10 +233,10 @@
}); });
// yes or no // yes or no
Vue.component('judgment-item', { Vue.component('judgment-item', {
props: ['question', 'si', 'qi', 'qitem'], props: ['question', 'si', 'qi', 'qitem', 'disabled'],
template: '<div class="options options-row">' + template: '<div class="options options-row">' +
'<div class="radio-container" v-for="item in rateList">' + '<div class="radio-container" v-for="item in rateList">' +
'<input type="radio" v-bind:id="item.value + \'-\' + si + \'-\' + qi" v-bind:name="qitem.id" v-bind:value="item.value" v-bind:checked="qitem.rate===item.value">' + '<input type="radio" v-bind:id="item.value + \'-\' + si + \'-\' + qi" v-bind:name="qitem.id" v-bind:value="item.value" v-bind:checked="qitem.rate===item.value" v-bind:disabled="disabled">' +
'<label v-bind:for="item.value+\'-\' + si + \'-\' + qi" class="radio-label"><span v-html="item.svg"></span> {{item.name}}</label>' + '<label v-bind:for="item.value+\'-\' + si + \'-\' + qi" class="radio-label"><span v-html="item.svg"></span> {{item.name}}</label>' +
'</div>' + '</div>' +
'</div>', '</div>',
@ -262,6 +269,8 @@
loading: false, loading: false,
cityListPage: true, cityListPage: true,
resultPage: false,
cityList: [], cityList: [],
currentCity: {}, currentCity: {},
cityListOpen: false, cityListOpen: false,
@ -291,11 +300,14 @@
for (const param of urlParams) { for (const param of urlParams) {
this.param[param[0]] = param[1]; this.param[param[0]] = param[1];
} }
var urlPath = window.location.pathname.replace('/customerfeedback/', '').split('/'); this.resultPage = !isEmpty(this.param.r);
var urlPath = window.location.pathname.replace('/customerfeedback/', '').split('-').filter(s => s);
urlPath = urlPath.length >= 3 ? urlPath : window.location.pathname.replace('/customerfeedback/', '').split('/');
if (urlPath.length > 0 && isEmpty(this.param.g)) { if (urlPath.length > 0 && isEmpty(this.param.g)) {
this.param.g = urlPath[0]; this.param.g = urlPath[0];
this.param.v = urlPath[1]; this.param.v = urlPath[1];
this.param.l = urlPath[2] || 1; this.param.l = urlPath[2] || 1;
this.resultPage = !isEmpty(urlPath[3]);
} }
if (!(this.param.v && this.param.g)) { if (!(this.param.v && this.param.g)) {
this.error = true; this.error = true;
@ -318,6 +330,7 @@
).then((res) => { ).then((res) => {
that.loading = false; that.loading = false;
that.cityList = res.cityList; that.cityList = res.cityList;
console.log(groupBy(res.cityList, 'tourGuideId'));
that.error = res.cityList.length === 0; that.error = res.cityList.length === 0;
that.errorMsg = 'No city found'; that.errorMsg = 'No city found';
}); });
@ -333,7 +346,6 @@
}; };
this.currentCity = Object.assign({}, city, _param); this.currentCity = Object.assign({}, city, _param);
this.getData(_param); this.getData(_param);
window.scrollTo({ top: 100, behavior: 'smooth' }); window.scrollTo({ top: 100, behavior: 'smooth' });
this.cityListOpen = false; this.cityListOpen = false;
@ -399,13 +411,17 @@
that.feedbackEvaluation = res.feedbackEvaluation[0]; that.feedbackEvaluation = res.feedbackEvaluation[0];
that.group = res.group[0]; that.group = res.group[0];
// that.signaturePad.fromDataURL(that.feedbackEvaluation.signatureDataUrl); // debug: -1 // 已经填写过了
that.isFilled = !isEmpty(res.feedbackEvaluation[0].feedbackId);
that.cityListPage = !that.resultPage;
if (!isEmpty(res.feedbackEvaluation[0].feedbackId)) {
// that.signaturePad.fromDataURL(that.feedbackEvaluation.signatureDataUrl);
} else {
that.initSignature();
}
// that.isFilled = !isEmpty(res.feedbackEvaluation[0].feedbackId); // 已经填写过了
that.isFilled = false;
that.showPhotos = res.group[0].inTheEnd; that.showPhotos = res.group[0].inTheEnd;
// alert("Thank you for completing the Feedback Evaluation. Once submitted the tour guide would not be able to view your comments. "); // alert("Thank you for completing the Feedback Evaluation. Once submitted the tour guide would not be able to view your comments. ");
that.initSignature();
}); });
}, },

@ -93,7 +93,7 @@ function isEmpty(val) {
} }
function groupBy(array, callback) { function groupBy(array, callback) {
return array.reduce((groups, item) => { return array.reduce((groups, item) => {
const key = callback(item); const key = typeof callback === 'function' ? callback(item) : item[callback];
if (!groups[key]) { if (!groups[key]) {
groups[key] = []; groups[key] = [];

Loading…
Cancel
Save