Update main.js to import Element Plus styles, enhance parent.html with new input types and improved layout
This commit is contained in:
parent
10e6944532
commit
1341a32484
@ -4,7 +4,12 @@
|
|||||||
<meta charset="UTF-8">
|
<meta charset="UTF-8">
|
||||||
<link rel="icon" href="/favicon.ico">
|
<link rel="icon" href="/favicon.ico">
|
||||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||||
<!-- <title>Vite App</title> -->
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: Inter, 'Helvetica Neue', Helvetica, 'PingFang SC',
|
||||||
|
'Hiragino Sans GB', 'Microsoft YaHei', '微软雅黑', Arial, sans-serif;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="app"></div>
|
<div id="app"></div>
|
||||||
|
@ -8,17 +8,41 @@
|
|||||||
type: 'spec',
|
type: 'spec',
|
||||||
title: 'Contact Form',
|
title: 'Contact Form',
|
||||||
items: [{
|
items: [{
|
||||||
type: 'text',
|
type: 'display',
|
||||||
title: 'Name'
|
title: 'Instructions',
|
||||||
|
desc: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.',
|
||||||
}, {
|
}, {
|
||||||
type: 'text',
|
type: 'text',
|
||||||
title: 'Email'
|
title: 'Text',
|
||||||
|
required: true,
|
||||||
|
desc: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.'
|
||||||
}, {
|
}, {
|
||||||
type: 'text',
|
type: 'text',
|
||||||
title: 'Phone'
|
title: 'Long text',
|
||||||
|
required: true,
|
||||||
|
desc: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.'
|
||||||
|
}, {
|
||||||
|
type: 'checkbox',
|
||||||
|
title: 'Checkbox',
|
||||||
|
required: true,
|
||||||
|
optTexts: ['Option 1', 'Option 2', 'Option 3'],
|
||||||
|
optValues: ['Option 1', 'Option 2', 'Option 3'],
|
||||||
|
}, {
|
||||||
|
type: 'scale',
|
||||||
|
title: 'Scale with auto-next',
|
||||||
|
required: true,
|
||||||
|
optTexts: ['Never', 'Almost never', 'Sometimes', 'Almost always', 'Always'],
|
||||||
|
optValues: [1, 2, 3, 4, 5],
|
||||||
}, {
|
}, {
|
||||||
type: 'radio',
|
type: 'radio',
|
||||||
title: 'Test',
|
title: 'Radio with auto-next',
|
||||||
|
required: true,
|
||||||
|
optTexts: ['Option 1', 'Option 2', 'Option 3'],
|
||||||
|
optValues: ['Option 1', 'Option 2', 'Option 3']
|
||||||
|
}, {
|
||||||
|
type: 'radio',
|
||||||
|
title: 'Radio without auto-next',
|
||||||
|
required: true,
|
||||||
optTexts: ['Option 1', 'Option 2', 'Option 3'],
|
optTexts: ['Option 1', 'Option 2', 'Option 3'],
|
||||||
optValues: ['Option 1', 'Option 2', 'Option 3']
|
optValues: ['Option 1', 'Option 2', 'Option 3']
|
||||||
}]
|
}]
|
||||||
@ -31,11 +55,17 @@
|
|||||||
iframe.contentWindow.postMessage(data, window.origin);
|
iframe.contentWindow.postMessage(data, window.origin);
|
||||||
};
|
};
|
||||||
</script>
|
</script>
|
||||||
|
<style>
|
||||||
|
body {
|
||||||
|
font-family: Inter, 'Helvetica Neue', Helvetica, 'PingFang SC',
|
||||||
|
'Hiragino Sans GB', 'Microsoft YaHei', '微软雅黑', Arial, sans-serif;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<iframe
|
<iframe
|
||||||
src="/index.html"
|
src="/index.html"
|
||||||
style="width: 100%; height: 100vh; border: none;"
|
style="width: 99vw; height: 95vh; border: none;"
|
||||||
id="iframe"></iframe>
|
id="iframe"></iframe>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
178
src/App.vue
178
src/App.vue
@ -1,4 +1,7 @@
|
|||||||
<script>
|
<script>
|
||||||
|
import { ElMessage } from 'element-plus';
|
||||||
|
import { isProxy } from 'vue';
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* Specification of item data structure:
|
* Specification of item data structure:
|
||||||
* {
|
* {
|
||||||
@ -6,12 +9,12 @@
|
|||||||
* title: string,
|
* title: string,
|
||||||
* optTexts: string[], // only for 'radio', 'checkbox', 'scale'
|
* optTexts: string[], // only for 'radio', 'checkbox', 'scale'
|
||||||
* optValues: string[], // only for 'radio', 'checkbox', 'scale'
|
* optValues: string[], // only for 'radio', 'checkbox', 'scale'
|
||||||
* required: boolean, // optional, whether the user must answer the question, default is true
|
* required: boolean, // whether the user must answer the question
|
||||||
* answer: string, // user's answer, appended after user submits
|
* answer: string, // user's answer, appended after user submits
|
||||||
* refilled: boolean, // whether the user refilled the answer, appended after user submits
|
* refilled: boolean, // whether the user refilled the answer, appended after user submits
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
const ITEM_TYPES = ['text', 'radio', 'checkbox', 'scale'];
|
const ITEM_TYPES = ['text', 'radio', 'checkbox', 'scale', 'display'];
|
||||||
function isValidItem({ type, title, optTexts, optValues = null }) {
|
function isValidItem({ type, title, optTexts, optValues = null }) {
|
||||||
if (!title || !ITEM_TYPES.includes(type)) {
|
if (!title || !ITEM_TYPES.includes(type)) {
|
||||||
return false;
|
return false;
|
||||||
@ -31,13 +34,14 @@ export default {
|
|||||||
title: undefined,
|
title: undefined,
|
||||||
started: undefined,
|
started: undefined,
|
||||||
items: [],
|
items: [],
|
||||||
currentTitle: undefined,
|
|
||||||
currentIdx: undefined,
|
currentIdx: undefined,
|
||||||
currentItem: undefined,
|
itemStatus: {
|
||||||
currentAnswer: undefined,
|
item: undefined,
|
||||||
currentAnswerValue: undefined,
|
timestamp: undefined,
|
||||||
currentRefilled: false,
|
title: undefined,
|
||||||
timestamp: undefined,
|
answer: undefined,
|
||||||
|
refilled: false,
|
||||||
|
},
|
||||||
uiStatus: {
|
uiStatus: {
|
||||||
backButtonDisabled: true,
|
backButtonDisabled: true,
|
||||||
nextButtonText: 'Next',
|
nextButtonText: 'Next',
|
||||||
@ -67,7 +71,7 @@ export default {
|
|||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
isReady() {
|
isReady() {
|
||||||
return this.items && this.title;
|
return this.items && this.items.length > 0;
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
@ -84,30 +88,45 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
updateTitle() {
|
updateTitle() {
|
||||||
this.currentTitle = this.currentItem ?
|
this.itemStatus.title = this.itemStatus.item ?
|
||||||
`${this.currentIdx + 1}. ${this.currentItem.title}` : '';
|
`${this.currentIdx + 1}. ${this.itemStatus.item.title}` : '';
|
||||||
},
|
},
|
||||||
updateItem() {
|
updateItem() {
|
||||||
this.currentItem = this.items[this.currentIdx];
|
this.itemStatus.item = this.items[this.currentIdx];
|
||||||
this.currentAnswer = this.items[this.currentIdx].answer;
|
this.itemStatus.answer = this.items[this.currentIdx].answer;
|
||||||
this.currentRefilled = (this.currentItem.answer !== undefined);
|
this.itemStatus.refilled = (this.itemStatus.answer !== undefined);
|
||||||
this.timestamp = new Date().getTime();
|
this.itemStatus.timestamp = new Date().getTime();
|
||||||
this.updateTitle();
|
this.updateTitle();
|
||||||
this.updateBackButton();
|
this.updateBackButton();
|
||||||
this.updateNextButton();
|
this.updateNextButton();
|
||||||
},
|
},
|
||||||
iterOptions() {
|
iterOptions() {
|
||||||
return this.currentItem.optTexts.map((optText, index) => [
|
return this.itemStatus.item.optTexts.map((optText, index) => [index, optText]);
|
||||||
index,
|
|
||||||
optText,
|
|
||||||
]);
|
|
||||||
},
|
},
|
||||||
clickNext() {
|
clickNext() {
|
||||||
const item = this.items[this.currentIdx];
|
const item = this.items[this.currentIdx];
|
||||||
item.answer = this.currentAnswer;
|
if (item.type !== 'display' && item.type !== 'checkbox' && item.required && this.itemStatus.answer === undefined) {
|
||||||
item.refilled = this.currentRefilled;
|
ElMessage({
|
||||||
item.responseTime = new Date().getTime() - this.timestamp;
|
message: 'This question is required.',
|
||||||
|
type: 'error',
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
item.answer = this.itemStatus.answer;
|
||||||
|
item.refilled = this.itemStatus.refilled;
|
||||||
|
item.responseTime = new Date().getTime() - this.itemStatus.timestamp;
|
||||||
|
if (item.type === 'radio' && item.answer !== undefined) {
|
||||||
|
item.answerText = item.optTexts[item.answer];
|
||||||
|
item.answerValue = item.optValues[item.answer];
|
||||||
|
} else if (item.type === 'checkbox') {
|
||||||
|
item.answerText = item.answer.map(idx => item.optTexts[idx]).join(', ');
|
||||||
|
item.answerValue = item.answer.map(idx => item.optValues[idx]).join(', ');
|
||||||
|
} if (item.type === 'scale' && item.answer !== undefined) {
|
||||||
|
item.answerText = item.optTexts[item.answer - 1];
|
||||||
|
item.answerValue = item.optValues[item.answer - 1];
|
||||||
|
}
|
||||||
|
|
||||||
if (this.currentIdx === this.items.length - 1) {
|
if (this.currentIdx === this.items.length - 1) {
|
||||||
this.submit();
|
this.submit();
|
||||||
} else {
|
} else {
|
||||||
@ -116,7 +135,7 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
autoNext() {
|
autoNext() {
|
||||||
if (this.currentIdx < this.items.length - 1) {
|
if (this.currentIdx < this.items.length - 1 && !this.itemStatus.refilled) {
|
||||||
this.clickNext();
|
this.clickNext();
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -127,10 +146,13 @@ export default {
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
submit() {
|
submit() {
|
||||||
const results = this.items.map((item, index) => ({
|
const results = this.items.map(item => ({
|
||||||
index: index,
|
|
||||||
title: item.title,
|
title: item.title,
|
||||||
answer: item.answer || '',
|
key: item.key ? item.key : item.title,
|
||||||
|
type: item.type,
|
||||||
|
answer: isProxy(item.answer) ? [...item.answer] : (item.answer || 'null'),
|
||||||
|
answerText: isProxy(item.answerText) ? [...item.answerText] : (item.answerText || 'null'),
|
||||||
|
answerValue: isProxy(item.answerValue) ? [...item.answerValue] : (item.answerValue || 'null'),
|
||||||
refilled: item.refilled,
|
refilled: item.refilled,
|
||||||
responseTime: item.responseTime,
|
responseTime: item.responseTime,
|
||||||
}));
|
}));
|
||||||
@ -152,48 +174,61 @@ export default {
|
|||||||
<template>
|
<template>
|
||||||
<div v-if="!isReady">Loading...</div>
|
<div v-if="!isReady">Loading...</div>
|
||||||
<el-container id="main" v-if="isReady">
|
<el-container id="main" v-if="isReady">
|
||||||
<el-header height=22pt id="display-title">{{ currentTitle }}</el-header>
|
<el-header height=24pt id="display-title">{{ itemStatus.title }}</el-header>
|
||||||
<el-container id="display-desc" v-if="true">
|
<el-main id="main-inner">
|
||||||
<el-text class="mx-1" size="large">Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor
|
<el-container id="display-desc" v-if="itemStatus.item.desc">
|
||||||
incididunt ut
|
<el-text class="mx-1" size="large"><span v-html="itemStatus.item.desc"></span></el-text>
|
||||||
labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut
|
</el-container>
|
||||||
aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu
|
|
||||||
fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit
|
<el-main id="display-content" :class="{ nodesc: !itemStatus.item.desc }">
|
||||||
anim id est laborum.</el-text>
|
<template v-if="itemStatus.item.type === 'text'">
|
||||||
</el-container>
|
<el-input v-model="itemStatus.answer" @keyup.enter="clickNext" autosize autofocus
|
||||||
<el-main id="display-content">
|
placeholder="Please input" />
|
||||||
<template v-if="currentItem.type === 'text'">
|
</template>
|
||||||
<el-input v-model="currentAnswer" autosize placeholder="Please input" />
|
<template v-else-if="itemStatus.item.type === 'radio'">
|
||||||
</template>
|
<el-radio-group v-model="itemStatus.answer" v-for="[index, optText] in iterOptions()" :key="index"
|
||||||
<template v-else-if="currentItem.type === 'radio'">
|
@change="autoNext">
|
||||||
<el-radio-group v-model="currentAnswer" v-for="[index, optText] in iterOptions()" :key="index"
|
<el-radio-button :label="optText" :value="index" />
|
||||||
@change="autoNext">
|
</el-radio-group>
|
||||||
<el-radio-button :label="optText" :value="index" />
|
</template>
|
||||||
</el-radio-group>
|
<template v-else-if="itemStatus.item.type === 'checkbox'">
|
||||||
</template>
|
<el-checkbox-group v-model="itemStatus.answer" v-for="[index, optText] in iterOptions()" :key="index">
|
||||||
|
<el-checkbox-button :label="optText" :value="index" />
|
||||||
|
</el-checkbox-group>
|
||||||
|
</template>
|
||||||
|
<template v-else-if="itemStatus.item.type === 'scale'">
|
||||||
|
<el-rate v-model="itemStatus.answer" :texts="itemStatus.item.optTexts"
|
||||||
|
show-text size="large" void-icon="ArrowRightBold"
|
||||||
|
:icons="['ArrowRightBold', 'ArrowRightBold', 'ArrowRightBold']"
|
||||||
|
@change="autoNext" />
|
||||||
|
</template>
|
||||||
|
</el-main>
|
||||||
|
|
||||||
|
<el-footer id="display-buttons">
|
||||||
|
<el-button-group>
|
||||||
|
<el-button type="info" round :disabled="uiStatus.backButtonDisabled" @click="clickBack">
|
||||||
|
<el-icon class="el-icon--left">
|
||||||
|
<ArrowLeft />
|
||||||
|
</el-icon>Back
|
||||||
|
</el-button>
|
||||||
|
<el-button :type="uiStatus.nextButtonStatus" round @click="clickNext">
|
||||||
|
{{ uiStatus.nextButtonText }}<el-icon class="el-icon--right">
|
||||||
|
<ArrowRight />
|
||||||
|
</el-icon>
|
||||||
|
</el-button>
|
||||||
|
</el-button-group>
|
||||||
|
</el-footer>
|
||||||
</el-main>
|
</el-main>
|
||||||
<el-footer id="display-buttons">
|
|
||||||
<el-button-group>
|
|
||||||
<el-button type="info" round :disabled="uiStatus.backButtonDisabled" @click="clickBack">
|
|
||||||
<el-icon class="el-icon--left">
|
|
||||||
<ArrowLeft />
|
|
||||||
</el-icon>Back
|
|
||||||
</el-button>
|
|
||||||
<el-button :type="uiStatus.nextButtonStatus" round @click="clickNext">
|
|
||||||
{{ uiStatus.nextButtonText }}<el-icon class="el-icon--right">
|
|
||||||
<ArrowRight />
|
|
||||||
</el-icon>
|
|
||||||
</el-button>
|
|
||||||
</el-button-group>
|
|
||||||
</el-footer>
|
|
||||||
</el-container>
|
</el-container>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
#main {
|
#main {
|
||||||
width: 50%;
|
width: 50vw;
|
||||||
|
max-height: 75vh;
|
||||||
|
overflow: hidden;
|
||||||
background-color: #FAFCFF;
|
background-color: #FAFCFF;
|
||||||
margin: 10% 25%;
|
margin: 12vh auto;
|
||||||
padding-top: 4ex;
|
padding-top: 4ex;
|
||||||
padding-left: 2ex;
|
padding-left: 2ex;
|
||||||
padding-right: 2ex;
|
padding-right: 2ex;
|
||||||
@ -202,6 +237,11 @@ export default {
|
|||||||
font-family: var(--el-font-family);
|
font-family: var(--el-font-family);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#main-inner {
|
||||||
|
margin-top: 0ex;
|
||||||
|
scrollbar-width: none;
|
||||||
|
}
|
||||||
|
|
||||||
#display-title {
|
#display-title {
|
||||||
text-align: left;
|
text-align: left;
|
||||||
font-size: 18pt;
|
font-size: 18pt;
|
||||||
@ -210,8 +250,9 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#display-desc {
|
#display-desc {
|
||||||
width: 90%;
|
width: 95%;
|
||||||
margin: 1ex auto;
|
margin: 0ex auto 0ex 2ex;
|
||||||
|
padding-top: 0ex;
|
||||||
}
|
}
|
||||||
|
|
||||||
#display-buttons {
|
#display-buttons {
|
||||||
@ -219,11 +260,20 @@ export default {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#display-content {
|
#display-content {
|
||||||
padding-top: 1ex;
|
margin-top: 2ex;
|
||||||
|
padding-top: 0ex;
|
||||||
|
}
|
||||||
|
|
||||||
|
#display-content.nodesc {
|
||||||
|
margin-top: 0ex;
|
||||||
}
|
}
|
||||||
|
|
||||||
#display-content el-input {
|
#display-content el-input {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
font-size: 16pt;
|
font-size: 16pt;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#display-content el-rate {
|
||||||
|
font-size: 24pt;
|
||||||
|
}
|
||||||
</style>
|
</style>
|
@ -1,4 +1,4 @@
|
|||||||
// import './assets/main.css'
|
import 'element-plus/dist/index.css'
|
||||||
|
|
||||||
import { createApp } from 'vue'
|
import { createApp } from 'vue'
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
|
Loading…
Reference in New Issue
Block a user