Update main.js to import Element Plus styles, enhance parent.html with new input types and improved layout

This commit is contained in:
HoshinoKoji 2025-03-03 18:07:09 +08:00
parent 10e6944532
commit 1341a32484
4 changed files with 157 additions and 72 deletions

View File

@ -4,7 +4,12 @@
<meta charset="UTF-8">
<link rel="icon" href="/favicon.ico">
<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>
<body>
<div id="app"></div>

View File

@ -8,17 +8,41 @@
type: 'spec',
title: 'Contact Form',
items: [{
type: 'text',
title: 'Name'
type: 'display',
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',
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',
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',
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'],
optValues: ['Option 1', 'Option 2', 'Option 3']
}]
@ -31,11 +55,17 @@
iframe.contentWindow.postMessage(data, window.origin);
};
</script>
<style>
body {
font-family: Inter, 'Helvetica Neue', Helvetica, 'PingFang SC',
'Hiragino Sans GB', 'Microsoft YaHei', '微软雅黑', Arial, sans-serif;
}
</style>
</head>
<body>
<iframe
src="/index.html"
style="width: 100%; height: 100vh; border: none;"
style="width: 99vw; height: 95vh; border: none;"
id="iframe"></iframe>
</body>
</html>

View File

@ -1,4 +1,7 @@
<script>
import { ElMessage } from 'element-plus';
import { isProxy } from 'vue';
/*
* Specification of item data structure:
* {
@ -6,12 +9,12 @@
* title: string,
* optTexts: 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
* 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 }) {
if (!title || !ITEM_TYPES.includes(type)) {
return false;
@ -31,13 +34,14 @@ export default {
title: undefined,
started: undefined,
items: [],
currentTitle: undefined,
currentIdx: undefined,
currentItem: undefined,
currentAnswer: undefined,
currentAnswerValue: undefined,
currentRefilled: false,
timestamp: undefined,
itemStatus: {
item: undefined,
timestamp: undefined,
title: undefined,
answer: undefined,
refilled: false,
},
uiStatus: {
backButtonDisabled: true,
nextButtonText: 'Next',
@ -67,7 +71,7 @@ export default {
},
computed: {
isReady() {
return this.items && this.title;
return this.items && this.items.length > 0;
},
},
methods: {
@ -84,30 +88,45 @@ export default {
}
},
updateTitle() {
this.currentTitle = this.currentItem ?
`${this.currentIdx + 1}. ${this.currentItem.title}` : '';
this.itemStatus.title = this.itemStatus.item ?
`${this.currentIdx + 1}. ${this.itemStatus.item.title}` : '';
},
updateItem() {
this.currentItem = this.items[this.currentIdx];
this.currentAnswer = this.items[this.currentIdx].answer;
this.currentRefilled = (this.currentItem.answer !== undefined);
this.timestamp = new Date().getTime();
this.itemStatus.item = this.items[this.currentIdx];
this.itemStatus.answer = this.items[this.currentIdx].answer;
this.itemStatus.refilled = (this.itemStatus.answer !== undefined);
this.itemStatus.timestamp = new Date().getTime();
this.updateTitle();
this.updateBackButton();
this.updateNextButton();
},
iterOptions() {
return this.currentItem.optTexts.map((optText, index) => [
index,
optText,
]);
return this.itemStatus.item.optTexts.map((optText, index) => [index, optText]);
},
clickNext() {
const item = this.items[this.currentIdx];
item.answer = this.currentAnswer;
item.refilled = this.currentRefilled;
item.responseTime = new Date().getTime() - this.timestamp;
if (item.type !== 'display' && item.type !== 'checkbox' && item.required && this.itemStatus.answer === undefined) {
ElMessage({
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) {
this.submit();
} else {
@ -116,7 +135,7 @@ export default {
}
},
autoNext() {
if (this.currentIdx < this.items.length - 1) {
if (this.currentIdx < this.items.length - 1 && !this.itemStatus.refilled) {
this.clickNext();
}
},
@ -127,10 +146,13 @@ export default {
}
},
submit() {
const results = this.items.map((item, index) => ({
index: index,
const results = this.items.map(item => ({
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,
responseTime: item.responseTime,
}));
@ -152,48 +174,61 @@ export default {
<template>
<div v-if="!isReady">Loading...</div>
<el-container id="main" v-if="isReady">
<el-header height=22pt id="display-title">{{ currentTitle }}</el-header>
<el-container id="display-desc" v-if="true">
<el-text class="mx-1" size="large">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.</el-text>
</el-container>
<el-main id="display-content">
<template v-if="currentItem.type === 'text'">
<el-input v-model="currentAnswer" autosize placeholder="Please input" />
</template>
<template v-else-if="currentItem.type === 'radio'">
<el-radio-group v-model="currentAnswer" v-for="[index, optText] in iterOptions()" :key="index"
@change="autoNext">
<el-radio-button :label="optText" :value="index" />
</el-radio-group>
</template>
<el-header height=24pt id="display-title">{{ itemStatus.title }}</el-header>
<el-main id="main-inner">
<el-container id="display-desc" v-if="itemStatus.item.desc">
<el-text class="mx-1" size="large"><span v-html="itemStatus.item.desc"></span></el-text>
</el-container>
<el-main id="display-content" :class="{ nodesc: !itemStatus.item.desc }">
<template v-if="itemStatus.item.type === 'text'">
<el-input v-model="itemStatus.answer" @keyup.enter="clickNext" autosize autofocus
placeholder="Please input" />
</template>
<template v-else-if="itemStatus.item.type === 'radio'">
<el-radio-group v-model="itemStatus.answer" v-for="[index, optText] in iterOptions()" :key="index"
@change="autoNext">
<el-radio-button :label="optText" :value="index" />
</el-radio-group>
</template>
<template v-else-if="itemStatus.item.type === 'checkbox'">
<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-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>
</template>
<style scoped>
#main {
width: 50%;
width: 50vw;
max-height: 75vh;
overflow: hidden;
background-color: #FAFCFF;
margin: 10% 25%;
margin: 12vh auto;
padding-top: 4ex;
padding-left: 2ex;
padding-right: 2ex;
@ -202,6 +237,11 @@ export default {
font-family: var(--el-font-family);
}
#main-inner {
margin-top: 0ex;
scrollbar-width: none;
}
#display-title {
text-align: left;
font-size: 18pt;
@ -210,8 +250,9 @@ export default {
}
#display-desc {
width: 90%;
margin: 1ex auto;
width: 95%;
margin: 0ex auto 0ex 2ex;
padding-top: 0ex;
}
#display-buttons {
@ -219,11 +260,20 @@ export default {
}
#display-content {
padding-top: 1ex;
margin-top: 2ex;
padding-top: 0ex;
}
#display-content.nodesc {
margin-top: 0ex;
}
#display-content el-input {
width: 100%;
font-size: 16pt;
}
#display-content el-rate {
font-size: 24pt;
}
</style>

View File

@ -1,4 +1,4 @@
// import './assets/main.css'
import 'element-plus/dist/index.css'
import { createApp } from 'vue'
import App from './App.vue'