refactor:统一B,C端渲染组件,将其抽离到物料区 (#184)
* feat:抽离B,C端通用组件 * refactor: 通用组件统一渲染 * 兼容修复问题 (+1 squashed commit) Squashed commits: [8d168ef] refactor: 替换统一渲染组件 * refactor:统一B,C端渲染组件,将其抽离到物料区
This commit is contained in:
parent
b18677e709
commit
4115ff9847
@ -1,85 +0,0 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<div class="question-logo" @click="onSelect">
|
||||
<img
|
||||
v-if="logoImg !== ''"
|
||||
:style="{ width: logoConf.logoImageWidth }"
|
||||
class="bottom-logo"
|
||||
:src="logoImg"
|
||||
/>
|
||||
<div class="logo-placeholder-wrapper" v-else>
|
||||
<div class="logo-placeholder">LOGO</div>
|
||||
<div class="no-logo-tip">若不配置logo,该图片将不会在问卷中展示</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'LogoPreview',
|
||||
props: {
|
||||
logoConf: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
isSelected: Boolean
|
||||
},
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
methods: {
|
||||
onSelect() {
|
||||
this.$emit('select')
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
logoImg() {
|
||||
const { logoImage } = this.logoConf
|
||||
return logoImage
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" rel="stylesheet/scss" scoped>
|
||||
.container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
.question-logo {
|
||||
max-width: 300px;
|
||||
text-align: center;
|
||||
padding: 0 0 0.6rem;
|
||||
margin-top: -0.2rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.logo-placeholder-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.logo-placeholder {
|
||||
width: 200px;
|
||||
height: 50px;
|
||||
font-size: 40px;
|
||||
color: #d8d8d8;
|
||||
letter-spacing: 0;
|
||||
line-height: 36px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1px dashed #d8d8d8;
|
||||
}
|
||||
|
||||
.no-logo-tip {
|
||||
font-size: 13px;
|
||||
line-height: 13px;
|
||||
opacity: 0.5;
|
||||
margin-top: 13px;
|
||||
color: #92949d;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,75 +0,0 @@
|
||||
<template>
|
||||
<div class="title-wrapper" @click="handleClick()">
|
||||
<div class="main-title" :class="{ active: isSelected }">
|
||||
<RichEditor
|
||||
:modelValue="bannerConf?.titleConfig?.mainTitle"
|
||||
@input="onTitleInput"
|
||||
></RichEditor>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import RichEditor from '@/common/Editor/RichEditor.vue'
|
||||
|
||||
export default {
|
||||
name: 'mainTitlePreview',
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
props: {
|
||||
preview: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
bannerConf: {
|
||||
type: Object
|
||||
},
|
||||
isSelected: {
|
||||
type: Boolean
|
||||
}
|
||||
},
|
||||
computed: {},
|
||||
methods: {
|
||||
handleClick() {
|
||||
this.$emit('select')
|
||||
},
|
||||
onTitleInput(val) {
|
||||
if (!this.isSelected) {
|
||||
return
|
||||
}
|
||||
this.$emit('change', {
|
||||
key: 'titleConfig.mainTitle',
|
||||
value: val
|
||||
})
|
||||
}
|
||||
},
|
||||
components: {
|
||||
RichEditor
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.title-wrapper {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.main-title {
|
||||
border: 1px solid transparent;
|
||||
|
||||
&.active {
|
||||
border: 1px solid #e3e4e6;
|
||||
background-color: #f6f7f9;
|
||||
box-shadow: 0 0 5px #dedede;
|
||||
|
||||
:deep(.w-e-text-container) {
|
||||
background-color: #f6f7f9;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.main-title:hover {
|
||||
border: 1px dashed #eee;
|
||||
}
|
||||
</style>
|
@ -1,42 +0,0 @@
|
||||
<template>
|
||||
<div class="submit-wrapper" @click="onClick" :class="{ isSelected: isSelected }">
|
||||
<el-button class="submit-btn" type="primary">{{ submitConf.submitTitle }}</el-button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
export default {
|
||||
name: 'SubmitButton',
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
props: {
|
||||
submitConf: Object,
|
||||
isSelected: Boolean,
|
||||
skinConf: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
onClick() {
|
||||
this.$emit('select')
|
||||
}
|
||||
}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.submit-wrapper {
|
||||
padding: 25px;
|
||||
text-align: center;
|
||||
|
||||
.submit-btn {
|
||||
color: white;
|
||||
border: none;
|
||||
width: 100%;
|
||||
height: 44px;
|
||||
background-color: var(--primary-color);
|
||||
}
|
||||
}
|
||||
</style>
|
@ -4,6 +4,7 @@
|
||||
<div class="box content" ref="box">
|
||||
<MainTitle
|
||||
:bannerConf="bannerConf"
|
||||
:readonly="false"
|
||||
:is-selected="currentEditOne === 'mainTitle'"
|
||||
@select="onSelectEditOne('mainTitle')"
|
||||
@change="handleChange"
|
||||
@ -18,6 +19,7 @@
|
||||
/>
|
||||
<SubmitButton
|
||||
:submit-conf="submitConf"
|
||||
:readonly="false"
|
||||
:skin-conf="skinConf"
|
||||
:is-selected="currentEditOne === 'submit'"
|
||||
@select="onSelectEditOne('submit')"
|
||||
@ -27,139 +29,107 @@
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
<script setup>
|
||||
import { computed, ref, watch } from 'vue'
|
||||
import communalLoader from '@materials/communals/communalLoader.js'
|
||||
import MaterialGroup from '@/management/pages/edit/components/MaterialGroup.vue'
|
||||
import MainTitle from '@/management/pages/edit/components/MainTitle.vue'
|
||||
import SubmitButton from '@/management/pages/edit/components/SubmitButton.vue'
|
||||
import { mapState, mapGetters } from 'vuex'
|
||||
import { get as _get } from 'lodash-es'
|
||||
import { useStore } from 'vuex'
|
||||
|
||||
export default {
|
||||
name: 'PreviewPanel',
|
||||
components: {
|
||||
MainTitle,
|
||||
SubmitButton,
|
||||
MaterialGroup
|
||||
},
|
||||
data() {
|
||||
return {
|
||||
isAnimating: false
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
bannerConf: (state) => _get(state, 'edit.schema.bannerConf'),
|
||||
submitConf: (state) => _get(state, 'edit.schema.submitConf'),
|
||||
skinConf: (state) => _get(state, 'edit.schema.skinConf'),
|
||||
bottomConf: (state) => _get(state, 'edit.schema.bottomConf'),
|
||||
questionDataList: (state) => _get(state, 'edit.schema.questionDataList'),
|
||||
currentEditOne: (state) => _get(state, 'edit.currentEditOne')
|
||||
}),
|
||||
...mapGetters({
|
||||
currentEditKey: 'edit/currentEditKey'
|
||||
}),
|
||||
autoScrollData() {
|
||||
return {
|
||||
currentEditOne: this.currentEditOne,
|
||||
len: this.questionDataList.length
|
||||
}
|
||||
}
|
||||
},
|
||||
watch: {
|
||||
skinConf: {
|
||||
handler(skinConf) {
|
||||
const { themeConf, backgroundConf, contentConf } = skinConf
|
||||
const root = document.documentElement
|
||||
if (themeConf?.color) {
|
||||
root.style.setProperty('--primary-color', themeConf?.color) // 设置主题颜色
|
||||
}
|
||||
if (backgroundConf?.color) {
|
||||
root.style.setProperty('--primary-background-color', backgroundConf?.color) // 设置背景颜色
|
||||
}
|
||||
if (contentConf?.opacity) {
|
||||
root.style.setProperty('--opacity', contentConf?.opacity / 100) // 设置全局透明度
|
||||
}
|
||||
},
|
||||
immediate: true, // 立即触发回调函数
|
||||
deep: true
|
||||
},
|
||||
autoScrollData(newVal) {
|
||||
const { currentEditOne } = newVal
|
||||
if (typeof currentEditOne === 'number') {
|
||||
setTimeout(() => {
|
||||
const field = this.questionDataList?.[currentEditOne]?.field
|
||||
if (field) {
|
||||
const questionModule = this.$refs.materialGroup.getQuestionRefByField(field)
|
||||
if (questionModule && questionModule.$el) {
|
||||
questionModule.$el.scrollIntoView({
|
||||
behavior: 'smooth'
|
||||
})
|
||||
}
|
||||
}
|
||||
}, 0)
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
animate(dom, property, targetValue) {
|
||||
const origin = dom[property]
|
||||
const subVal = targetValue - origin
|
||||
const MainTitle = communalLoader.loadComponent('MainTitle')
|
||||
const SubmitButton = communalLoader.loadComponent('SubmitButton')
|
||||
|
||||
const flag = subVal < 0 ? -1 : 1
|
||||
const store = useStore()
|
||||
const mainOperation = ref(null)
|
||||
const materialGroup = ref(null)
|
||||
|
||||
const step = flag * 50
|
||||
const bannerConf = computed(() => store.state.edit.schema.bannerConf)
|
||||
const submitConf = computed(() => store.state.edit.schema.submitConf)
|
||||
const skinConf = computed(() => store.state.edit.schema.skinConf)
|
||||
const questionDataList = computed(() => store.state.edit.schema.questionDataList)
|
||||
const currentEditOne = computed(() => store.state.edit.currentEditOne)
|
||||
const currentEditKey = computed(() => store.getters['edit/currentEditKey'])
|
||||
const autoScrollData = computed(() => {
|
||||
return {
|
||||
currentEditOne: currentEditOne.value,
|
||||
len: questionDataList.value.length
|
||||
}
|
||||
})
|
||||
|
||||
const totalCount = Math.floor(subVal / step) + 1
|
||||
const onSelectEditOne = async (currentEditOne) => {
|
||||
store.commit('edit/setCurrentEditOne', currentEditOne)
|
||||
}
|
||||
|
||||
let runCount = 0
|
||||
const run = () => {
|
||||
dom[property] += step
|
||||
runCount++
|
||||
if (runCount < totalCount) {
|
||||
requestAnimationFrame(run)
|
||||
} else {
|
||||
this.isAnimating = false
|
||||
}
|
||||
}
|
||||
const handleChange = (data) => {
|
||||
if (currentEditOne.value === null) {
|
||||
return
|
||||
}
|
||||
const { key, value } = data
|
||||
const resultKey = `${currentEditKey.value}.${key}`
|
||||
store.dispatch('edit/changeSchema', { key: resultKey, value })
|
||||
}
|
||||
|
||||
requestAnimationFrame(run)
|
||||
},
|
||||
async onSelectEditOne(currentEditOne) {
|
||||
this.$store.commit('edit/setCurrentEditOne', currentEditOne)
|
||||
},
|
||||
handleChange(data) {
|
||||
if (this.currentEditOne === null) {
|
||||
return
|
||||
}
|
||||
const { key, value } = data
|
||||
const resultKey = `${this.currentEditKey}.${key}`
|
||||
this.$store.dispatch('edit/changeSchema', { key: resultKey, value })
|
||||
},
|
||||
onMainClick(e) {
|
||||
if (e.target === this.$refs.mainOperation) {
|
||||
this.$store.commit('edit/setCurrentEditOne', null)
|
||||
}
|
||||
},
|
||||
onQuestionOperation(data) {
|
||||
switch (data.type) {
|
||||
case 'move':
|
||||
this.$store.dispatch('edit/moveQuestion', {
|
||||
index: data.index,
|
||||
range: data.range
|
||||
})
|
||||
break
|
||||
case 'delete':
|
||||
this.$store.dispatch('edit/deleteQuestion', { index: data.index })
|
||||
break
|
||||
case 'copy':
|
||||
this.$store.dispatch('edit/copyQuestion', { index: data.index })
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
const onMainClick = (e) => {
|
||||
if (e.target === mainOperation.value) {
|
||||
store.commit('edit/setCurrentEditOne', null)
|
||||
}
|
||||
}
|
||||
|
||||
const onQuestionOperation = (data) => {
|
||||
switch (data.type) {
|
||||
case 'move':
|
||||
store.dispatch('edit/moveQuestion', {
|
||||
index: data.index,
|
||||
range: data.range
|
||||
})
|
||||
break
|
||||
case 'delete':
|
||||
store.dispatch('edit/deleteQuestion', { index: data.index })
|
||||
break
|
||||
case 'copy':
|
||||
store.dispatch('edit/copyQuestion', { index: data.index })
|
||||
break
|
||||
default:
|
||||
break
|
||||
}
|
||||
}
|
||||
|
||||
watch(
|
||||
skinConf,
|
||||
(newVal) => {
|
||||
const { themeConf, backgroundConf, contentConf } = newVal
|
||||
const root = document.documentElement
|
||||
if (themeConf?.color) {
|
||||
root.style.setProperty('--primary-color', themeConf?.color) // 设置主题颜色
|
||||
}
|
||||
if (backgroundConf?.color) {
|
||||
root.style.setProperty('--primary-background-color', backgroundConf?.color) // 设置背景颜色
|
||||
}
|
||||
if (contentConf?.opacity) {
|
||||
root.style.setProperty('--opacity', contentConf?.opacity / 100) // 设置全局透明度
|
||||
}
|
||||
},
|
||||
{
|
||||
immediate: true,
|
||||
deep: true
|
||||
}
|
||||
)
|
||||
|
||||
watch(autoScrollData, (newVal) => {
|
||||
const { currentEditOne } = newVal
|
||||
if (typeof currentEditOne === 'number') {
|
||||
setTimeout(() => {
|
||||
const field = questionDataList.value?.[currentEditOne]?.field
|
||||
if (field) {
|
||||
const questionModule = materialGroup.value?.getQuestionRefByField(field)
|
||||
if (questionModule && questionModule.$el) {
|
||||
questionModule.$el.scrollIntoView({
|
||||
behavior: 'smooth'
|
||||
})
|
||||
}
|
||||
}
|
||||
}, 0)
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@ -198,6 +168,7 @@ export default {
|
||||
|
||||
.content {
|
||||
background-color: #fff;
|
||||
padding-top: 40px;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
|
@ -1,122 +0,0 @@
|
||||
<template>
|
||||
<div class="banner-wrap" @click="handleClick('banner')">
|
||||
<div class="banner" v-if="bgImage">
|
||||
<img :src="bgImage" />
|
||||
</div>
|
||||
<div v-else :class="{ 'empty-banner': true, 'banner-active': isSelected }">
|
||||
<p>点击配置头图</p>
|
||||
<p>若不配置头图,将不在问卷中展示</p>
|
||||
</div>
|
||||
<div class="banner" v-if="bannerConf.bannerConfig.videoLink">
|
||||
<div class="video">
|
||||
<video
|
||||
class="custom-video"
|
||||
:poster="bannerConf.bannerConfigpostImg"
|
||||
preload="auto"
|
||||
controls
|
||||
:src="bannerConf.bannerConfig.videoLink"
|
||||
></video>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { get as _get } from 'lodash-es'
|
||||
|
||||
export default {
|
||||
name: 'BannerContent',
|
||||
data() {
|
||||
return {}
|
||||
},
|
||||
props: {
|
||||
bannerConf: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
isSelected: {
|
||||
type: Boolean
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
bgImage() {
|
||||
return _get(this.bannerConf, 'bannerConfig.bgImage', '')
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
handleClick() {
|
||||
this.$emit('select')
|
||||
}
|
||||
},
|
||||
components: {}
|
||||
}
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.banner-preview {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.banner-wrap {
|
||||
width: 100%;
|
||||
|
||||
.banner {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.custom-video {
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
.empty-banner {
|
||||
height: 120px;
|
||||
border: 1px dashed #e3e4e8;
|
||||
|
||||
p {
|
||||
text-align: center;
|
||||
color: #c8c9cd;
|
||||
font-size: 16px;
|
||||
&:first-child {
|
||||
font-size: 24px;
|
||||
color: #92949d;
|
||||
margin: 20px 0 24px 0;
|
||||
}
|
||||
}
|
||||
|
||||
&.banner-active {
|
||||
background-color: #f2f4f7;
|
||||
box-shadow: 0 0 5px #e3e4e8;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.title-wrapper {
|
||||
padding: 15px;
|
||||
}
|
||||
|
||||
.main-title {
|
||||
border: 1px solid transparent;
|
||||
|
||||
&.active {
|
||||
border: 1px solid #e3e4e6;
|
||||
background-color: #f6f7f9;
|
||||
box-shadow: 0 0 5px #dedede;
|
||||
|
||||
:deep(.w-e-text-container) {
|
||||
background-color: #f6f7f9;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.main-title:hover {
|
||||
border: 1px dashed #eee;
|
||||
}
|
||||
</style>
|
@ -3,62 +3,70 @@
|
||||
<div class="operation-wrapper">
|
||||
<div class="box" ref="box">
|
||||
<div class="mask"></div>
|
||||
<BannerContent :bannerConf="bannerConf" />
|
||||
<HeaderContent :bannerConf="bannerConf" :readonly="false" />
|
||||
<div class="content">
|
||||
<MainTitle :isSelected="false" :bannerConf="bannerConf" />
|
||||
<MainTitle :isSelected="false" :bannerConf="bannerConf" :readonly="false" />
|
||||
<MaterialGroup :questionDataList="questionDataList" ref="MaterialGroup" />
|
||||
<SubmitButton
|
||||
:submit-conf="submitConf"
|
||||
:skin-conf="skinConf"
|
||||
:readonly="false"
|
||||
:is-selected="currentEditOne === 'submit'"
|
||||
/>
|
||||
<LogoPreview :logo-conf="bottomConf" :is-selected="currentEditOne === 'logo'" />
|
||||
<LogoIcon
|
||||
:logo-conf="bottomConf"
|
||||
:readonly="false"
|
||||
:is-selected="currentEditOne === 'logo'"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script>
|
||||
import { computed, defineComponent } from 'vue'
|
||||
import MaterialGroup from '@/management/pages/edit/components/MaterialGroup.vue'
|
||||
import BannerContent from '../components/BannerContent.vue'
|
||||
import MainTitle from '@/management/pages/edit/components/MainTitle.vue'
|
||||
import SubmitButton from '@/management/pages/edit/components/SubmitButton.vue'
|
||||
import LogoPreview from '@/management/pages/edit/components/LogoPreview.vue'
|
||||
import { mapState, mapGetters } from 'vuex'
|
||||
import { get as _get } from 'lodash-es'
|
||||
import { useStore } from 'vuex'
|
||||
import communalLoader from '@materials/communals/communalLoader.js'
|
||||
|
||||
export default {
|
||||
name: 'PreviewPanel',
|
||||
const HeaderContent = ()=>communalLoader.loadComponent('HeaderContent')
|
||||
const MainTitle = ()=>communalLoader.loadComponent('MainTitle')
|
||||
const SubmitButton = ()=>communalLoader.loadComponent('SubmitButton')
|
||||
const LogoIcon = ()=>communalLoader.loadComponent('LogoIcon')
|
||||
|
||||
export default defineComponent({
|
||||
components: {
|
||||
BannerContent,
|
||||
MainTitle,
|
||||
SubmitButton,
|
||||
LogoPreview,
|
||||
MaterialGroup
|
||||
MaterialGroup,
|
||||
HeaderContent:HeaderContent(),
|
||||
MainTitle:MainTitle(),
|
||||
SubmitButton:SubmitButton(),
|
||||
LogoIcon:LogoIcon()
|
||||
},
|
||||
data() {
|
||||
setup() {
|
||||
const store = useStore()
|
||||
|
||||
const bannerConf = computed(() => store.state.edit.schema.bannerConf)
|
||||
const submitConf = computed(() => store.state.edit.schema.submitConf)
|
||||
const bottomConf = computed(() => store.state.edit.schema.bottomConf)
|
||||
const skinConf = computed(() => store.state.edit.schema.skinConf)
|
||||
const questionDataList = computed(() => store.state.edit.schema.questionDataList)
|
||||
const currentEditOne = computed(() => store.state.edit.currentEditOne)
|
||||
const currentEditKey = computed(() => store.getters['edit/currentEditKey'])
|
||||
|
||||
return {
|
||||
isAnimating: false
|
||||
bannerConf,
|
||||
submitConf,
|
||||
bottomConf,
|
||||
skinConf,
|
||||
questionDataList,
|
||||
currentEditOne,
|
||||
currentEditKey
|
||||
}
|
||||
},
|
||||
computed: {
|
||||
...mapState({
|
||||
bannerConf: (state) => _get(state, 'edit.schema.bannerConf'),
|
||||
submitConf: (state) => _get(state, 'edit.schema.submitConf'),
|
||||
bottomConf: (state) => _get(state, 'edit.schema.bottomConf'),
|
||||
skinConf: (state) => _get(state, 'edit.schema.skinConf'),
|
||||
questionDataList: (state) => _get(state, 'edit.schema.questionDataList'),
|
||||
currentEditOne: (state) => _get(state, 'edit.currentEditOne')
|
||||
}),
|
||||
...mapGetters({
|
||||
currentEditKey: 'edit/currentEditKey'
|
||||
})
|
||||
},
|
||||
watch: {
|
||||
skinConf: {
|
||||
handler(skinConf) {
|
||||
const { themeConf, backgroundConf, contentConf } = skinConf
|
||||
handler(newVal) {
|
||||
const { themeConf, backgroundConf, contentConf } = newVal
|
||||
const root = document.documentElement
|
||||
if (themeConf?.color) {
|
||||
root.style.setProperty('--primary-color', themeConf?.color) // 设置主题颜色
|
||||
@ -70,36 +78,11 @@ export default {
|
||||
root.style.setProperty('--opacity', contentConf?.opacity / 100) // 设置全局透明度
|
||||
}
|
||||
},
|
||||
immediate: true, // 立即触发回调函数
|
||||
immediate: true,
|
||||
deep: true
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
animate(dom, property, targetValue) {
|
||||
const origin = dom[property]
|
||||
const subVal = targetValue - origin
|
||||
|
||||
const flag = subVal < 0 ? -1 : 1
|
||||
|
||||
const step = flag * 50
|
||||
|
||||
const totalCount = Math.floor(subVal / step) + 1
|
||||
|
||||
let runCount = 0
|
||||
const run = () => {
|
||||
dom[property] += step
|
||||
runCount++
|
||||
if (runCount < totalCount) {
|
||||
requestAnimationFrame(run)
|
||||
} else {
|
||||
this.isAnimating = false
|
||||
}
|
||||
}
|
||||
|
||||
requestAnimationFrame(run)
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
@ -140,6 +123,7 @@ export default {
|
||||
.box {
|
||||
background-color: var(--primary-background-color);
|
||||
position: relative;
|
||||
|
||||
.mask {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
@ -148,6 +132,7 @@ export default {
|
||||
right: 0;
|
||||
z-index: 999;
|
||||
}
|
||||
|
||||
.content {
|
||||
margin: 0 0.3rem;
|
||||
background: rgba(255, 255, 255, var(--opacity));
|
||||
|
11
web/src/materials/communals/common/utils/index.js
Normal file
11
web/src/materials/communals/common/utils/index.js
Normal file
@ -0,0 +1,11 @@
|
||||
// 对链接做一个兼容转换,支持用户不配置http开头或者配置 // 开头
|
||||
export const formatLink = (url) => {
|
||||
url = url.trim()
|
||||
if (!url) {
|
||||
return url
|
||||
}
|
||||
if (url.startsWith('http') || url.startsWith('//')) {
|
||||
return url
|
||||
}
|
||||
return `http://${url}`
|
||||
}
|
15
web/src/materials/communals/communalLoader.js
Normal file
15
web/src/materials/communals/communalLoader.js
Normal file
@ -0,0 +1,15 @@
|
||||
import { defineAsyncComponent } from 'vue'
|
||||
|
||||
export class CommunalLoader {
|
||||
loadComponent(path) {
|
||||
return defineAsyncComponent({
|
||||
loader: () => import(`./widgets/${path}/index.jsx`),
|
||||
delay: 200,
|
||||
timeout: 3000
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const communalLoader = new CommunalLoader()
|
||||
|
||||
export default communalLoader
|
@ -0,0 +1,82 @@
|
||||
import { defineComponent, computed } from 'vue'
|
||||
import { get as _get } from 'lodash-es'
|
||||
import { formatLink } from '@materials/communals/common/utils'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'HeaderContent',
|
||||
props: {
|
||||
bannerConf: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
readonly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
isSelected: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
setup(props) {
|
||||
const bgImage = computed(() => {
|
||||
return _get(props.bannerConf, 'bannerConfig.bgImage', '')
|
||||
})
|
||||
|
||||
const onBannerClick = () => {
|
||||
const allow = _get(props.bannerConf, 'bannerConfig.bgImageAllowJump', false)
|
||||
const jumpLink = _get(props.bannerConf, 'bannerConfig.bgImageJumpLink', '')
|
||||
if (!allow || !jumpLink) {
|
||||
return
|
||||
}
|
||||
window.open(formatLink(jumpLink))
|
||||
}
|
||||
|
||||
const bannerRender = () => {
|
||||
let attribute = {
|
||||
class: 'banner-img'
|
||||
}
|
||||
if (props.readonly) {
|
||||
const allow = _get(props.bannerConf, 'bannerConfig.bgImageAllowJump', false)
|
||||
attribute = {
|
||||
class: `${attribute.class} ${allow ? 'pointer' : ''}`,
|
||||
onClick: onBannerClick
|
||||
}
|
||||
}
|
||||
if (bgImage.value) {
|
||||
return (
|
||||
<div class="banner">
|
||||
<img src={bgImage.value} {...attribute} />
|
||||
</div>
|
||||
)
|
||||
}
|
||||
if (!bgImage.value && !props.readonly) {
|
||||
let classStr = 'empty-banner'
|
||||
if (props.isSelected) {
|
||||
classStr += 'banner-active'
|
||||
}
|
||||
return (
|
||||
<div class={classStr}>
|
||||
<p>点击配置头图</p>
|
||||
<p>若不配置头图,将不在问卷中展示</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
return ''
|
||||
}
|
||||
|
||||
return {
|
||||
bgImage,
|
||||
// emptyBannerRender,
|
||||
bannerRender
|
||||
}
|
||||
},
|
||||
render() {
|
||||
return (
|
||||
<div class="header-banner-wrap">
|
||||
{this.bannerRender()}
|
||||
{/* {this.emptyBannerRender()} */}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
@ -0,0 +1,66 @@
|
||||
import { defineComponent, computed } from 'vue'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'HeaderVideo',
|
||||
props: {
|
||||
bannerConf: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
readonly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
setup(props) {
|
||||
const attributeVideo = computed(() => {
|
||||
const { bannerConf, readonly } = props
|
||||
let data = {
|
||||
id: 'video',
|
||||
preload: 'auto',
|
||||
style: readonly ? 'margin: 0 auto; width: 100%; display: block;' : '',
|
||||
poster: bannerConf?.bannerConfig?.postImg,
|
||||
controls: '',
|
||||
class: readonly ? '' : 'custom-video'
|
||||
}
|
||||
return data
|
||||
})
|
||||
|
||||
const play = () => {
|
||||
const video = document.getElementById('video')
|
||||
if (!video) return
|
||||
document.querySelector('.play-icon').style.display = 'none'
|
||||
document.querySelector('.video-modal').style.display = 'none'
|
||||
video.play()
|
||||
}
|
||||
|
||||
return {
|
||||
attributeVideo,
|
||||
props,
|
||||
play
|
||||
}
|
||||
},
|
||||
render() {
|
||||
const { readonly } = this.props
|
||||
return (
|
||||
<div class="header-video-warp">
|
||||
<div class="video">
|
||||
<video {...this.attributeVideo} >
|
||||
<source src={this.bannerConf?.bannerConfig?.videoLink} type="video/mp4" />
|
||||
</video>
|
||||
{readonly ? (
|
||||
<>
|
||||
<div
|
||||
class="video-modal"
|
||||
style={`background-image:url(${this.attributeVideo.poster})`}
|
||||
></div>
|
||||
<div class="iconfont icon-kaishi play-icon" onClick={this.play}></div>
|
||||
</>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
47
web/src/materials/communals/widgets/HeaderContent/index.jsx
Normal file
47
web/src/materials/communals/widgets/HeaderContent/index.jsx
Normal file
@ -0,0 +1,47 @@
|
||||
import { defineComponent } from 'vue'
|
||||
import './index.scss'
|
||||
import HeaderBanner from './Components/HeaderBanner'
|
||||
import HeaderVideo from './Components/HeaderVideo'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'HeaderContent',
|
||||
props: {
|
||||
bannerConf: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
readonly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
isSelected: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
emits: ['select'],
|
||||
setup(props, { emit }) {
|
||||
const handleClick = () => {
|
||||
if (props.readonly) return
|
||||
emit('select')
|
||||
}
|
||||
|
||||
return {
|
||||
handleClick,
|
||||
props
|
||||
}
|
||||
},
|
||||
render() {
|
||||
const { bannerConf, readonly, isSelected } = this.props
|
||||
return (
|
||||
<div class="header-content-warp" onClick={this.handleClick}>
|
||||
<HeaderBanner bannerConf={bannerConf} readonly={readonly} isSelected={isSelected} />
|
||||
{bannerConf?.bannerConfig?.videoLink ? (
|
||||
<HeaderVideo bannerConf={bannerConf} readonly={readonly} />
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
69
web/src/materials/communals/widgets/HeaderContent/index.scss
Normal file
69
web/src/materials/communals/widgets/HeaderContent/index.scss
Normal file
@ -0,0 +1,69 @@
|
||||
.header-content-warp {
|
||||
width: 100%;
|
||||
.header-banner-wrap {
|
||||
.banner {
|
||||
width: 100%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
.banner-img {
|
||||
width: 100%;
|
||||
&.pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
.empty-banner {
|
||||
height: 120px;
|
||||
border: 1px dashed #e3e4e8;
|
||||
|
||||
p {
|
||||
text-align: center;
|
||||
color: #c8c9cd;
|
||||
font-size: 16px;
|
||||
&:first-child {
|
||||
font-size: 24px;
|
||||
color: #92949d;
|
||||
margin: 20px 0 24px 0;
|
||||
}
|
||||
}
|
||||
|
||||
&.banner-active {
|
||||
background-color: #f2f4f7;
|
||||
box-shadow: 0 0 5px #e3e4e8;
|
||||
}
|
||||
}
|
||||
}
|
||||
.header-video-warp {
|
||||
.video {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: relative;
|
||||
}
|
||||
.video-modal {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
z-index: 100;
|
||||
}
|
||||
.play-icon {
|
||||
position: absolute;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
font-size: 1.8rem;
|
||||
left: 50%;
|
||||
top: 49%;
|
||||
-webkit-transform: translate(-50%, -50%);
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 101;
|
||||
}
|
||||
.custom-video {
|
||||
margin: 0 auto;
|
||||
width: 100%;
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
}
|
67
web/src/materials/communals/widgets/LogoIcon/index.jsx
Normal file
67
web/src/materials/communals/widgets/LogoIcon/index.jsx
Normal file
@ -0,0 +1,67 @@
|
||||
import { defineComponent, computed } from 'vue'
|
||||
import './index.scss'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'LogoIcon',
|
||||
props: {
|
||||
logoConf: {
|
||||
type: Object,
|
||||
default: () => ({
|
||||
logoImageWidth: Number,
|
||||
logoImage: String
|
||||
})
|
||||
},
|
||||
readonly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
emits: ['select'],
|
||||
setup(props, { emit }) {
|
||||
|
||||
const logoImage = computed(() => {
|
||||
return props.logoConf?.logoImage
|
||||
})
|
||||
|
||||
const logoImageWidth = computed(() => {
|
||||
return props.logoConf?.logoImageWidth
|
||||
})
|
||||
|
||||
const onSelect = () => {
|
||||
if (props.readonly) return
|
||||
emit('select')
|
||||
}
|
||||
|
||||
const noLogoRender = () => {
|
||||
if (!props.readonly) {
|
||||
return (
|
||||
<div class="logo-placeholder-wrapper">
|
||||
<div class="logo-placeholder">LOGO</div>
|
||||
<div class="no-logo-tip">若不配置logo,该图片将不会在问卷中展示</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
logoImage,
|
||||
logoImageWidth,
|
||||
props,
|
||||
onSelect,
|
||||
noLogoRender
|
||||
}
|
||||
},
|
||||
render() {
|
||||
return (
|
||||
<div class="logo-icon-warp" onClick={this.onSelect}>
|
||||
<div class="question-logo">
|
||||
{this.logoImage ? (
|
||||
<img src={this.logoImage} style={{width: this.logoImageWidth}} />
|
||||
) : (
|
||||
this.noLogoRender()
|
||||
)}
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
43
web/src/materials/communals/widgets/LogoIcon/index.scss
Normal file
43
web/src/materials/communals/widgets/LogoIcon/index.scss
Normal file
@ -0,0 +1,43 @@
|
||||
.logo-icon-warp {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
.logo-wrapper {
|
||||
text-align: center;
|
||||
font-size: 0;
|
||||
padding: 0.1rem 0 0.5rem;
|
||||
}
|
||||
.question-logo {
|
||||
max-width: 300px;
|
||||
text-align: center;
|
||||
padding: 0 0 0.6rem;
|
||||
margin-top: -0.2rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
.logo-placeholder-wrapper {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
|
||||
.logo-placeholder {
|
||||
width: 200px;
|
||||
height: 50px;
|
||||
font-size: 40px;
|
||||
color: #d8d8d8;
|
||||
letter-spacing: 0;
|
||||
line-height: 36px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border: 1px dashed #d8d8d8;
|
||||
}
|
||||
|
||||
.no-logo-tip {
|
||||
font-size: 13px;
|
||||
line-height: 13px;
|
||||
opacity: 0.5;
|
||||
margin-top: 13px;
|
||||
color: #92949d;
|
||||
}
|
||||
}
|
||||
}
|
103
web/src/materials/communals/widgets/MainTitle/index.jsx
Normal file
103
web/src/materials/communals/widgets/MainTitle/index.jsx
Normal file
@ -0,0 +1,103 @@
|
||||
import { defineComponent, computed,shallowRef,defineAsyncComponent} from 'vue'
|
||||
import '@/render/styles/variable.scss'
|
||||
import './index.scss'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'MainTitle',
|
||||
props: {
|
||||
bannerConf: {
|
||||
type: Object,
|
||||
default: () => {}
|
||||
},
|
||||
readonly: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
},
|
||||
isSelected: {
|
||||
type: Boolean,
|
||||
default: false
|
||||
}
|
||||
},
|
||||
emits: ['select', 'change'],
|
||||
setup(props, { emit }) {
|
||||
|
||||
const titleClass = computed(() => {
|
||||
let classStr = ''
|
||||
if (!props.readonly) {
|
||||
classStr = `main-title ${props.isSelected ? 'active' : ''}`
|
||||
} else {
|
||||
classStr = 'titlePanel'
|
||||
}
|
||||
return classStr
|
||||
})
|
||||
|
||||
const isTitleHide = computed(() => {
|
||||
if (props.readonly && !mainTitle.value) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
})
|
||||
|
||||
const mainTitle = computed(() => {
|
||||
return props.bannerConf.titleConfig?.mainTitle
|
||||
})
|
||||
|
||||
const handleClick = () => {
|
||||
if (props.readonly) return
|
||||
emit('select')
|
||||
}
|
||||
|
||||
const onTitleInput = (val) => {
|
||||
if (!props.isSelected) {
|
||||
return
|
||||
}
|
||||
emit('change', {
|
||||
key: 'titleConfig.mainTitle',
|
||||
value: val
|
||||
})
|
||||
}
|
||||
|
||||
const richEditorView = shallowRef(null)
|
||||
if (!props.readonly) {
|
||||
richEditorView.value = defineAsyncComponent(
|
||||
() => import('@/common/Editor/RichEditor.vue')
|
||||
)
|
||||
}
|
||||
|
||||
return {
|
||||
props,
|
||||
titleClass,
|
||||
isTitleHide,
|
||||
mainTitle,
|
||||
richEditorView,
|
||||
handleClick,
|
||||
onTitleInput
|
||||
}
|
||||
},
|
||||
render() {
|
||||
const { readonly,mainTitle,onTitleInput,richEditorView} = this;
|
||||
return (
|
||||
<div
|
||||
class={['main-title-warp', !readonly ? 'pd15' : '']}
|
||||
onClick={this.handleClick}
|
||||
>
|
||||
{this.isTitleHide ? (
|
||||
<div class={this.titleClass}>
|
||||
{!readonly ? (
|
||||
<richEditorView
|
||||
modelValue={mainTitle}
|
||||
onInput={onTitleInput}
|
||||
placeholder="请输入标题"
|
||||
class="mainTitle"
|
||||
/>
|
||||
) : (
|
||||
<div class="mainTitle" v-html={mainTitle}></div>
|
||||
)}
|
||||
</div>
|
||||
) : (
|
||||
''
|
||||
)}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
63
web/src/materials/communals/widgets/MainTitle/index.scss
Normal file
63
web/src/materials/communals/widgets/MainTitle/index.scss
Normal file
@ -0,0 +1,63 @@
|
||||
.main-title-warp {
|
||||
.mainTitle {
|
||||
font-size: 0.28rem;
|
||||
line-height: 0.4rem;
|
||||
color: $title-color;
|
||||
|
||||
ol {
|
||||
list-style: decimal;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: disc;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
}
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6,
|
||||
p {
|
||||
line-height: 0.6rem;
|
||||
font-size: 0.28rem;
|
||||
color: $title-color-deep;
|
||||
margin-bottom: 0.35rem;
|
||||
}
|
||||
p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
.title-wrapper {
|
||||
padding: 15px;
|
||||
}
|
||||
.main-title {
|
||||
border: 1px solid transparent;
|
||||
|
||||
&.active {
|
||||
border: 1px solid #e3e4e6;
|
||||
background-color: #f6f7f9;
|
||||
box-shadow: 0 0 5px #dedede;
|
||||
|
||||
:deep(.w-e-text-container) {
|
||||
background-color: #f6f7f9;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.main-title:hover {
|
||||
border: 1px dashed #eee;
|
||||
}
|
||||
&.pd15 {
|
||||
padding: 15px;
|
||||
}
|
||||
.titlePanel {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
padding: 0.4rem 0.4rem;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
56
web/src/materials/communals/widgets/SubmitButton/index.jsx
Normal file
56
web/src/materials/communals/widgets/SubmitButton/index.jsx
Normal file
@ -0,0 +1,56 @@
|
||||
import { defineComponent } from 'vue'
|
||||
import '@/render/styles/variable.scss'
|
||||
import './index.scss'
|
||||
|
||||
export default defineComponent({
|
||||
name: 'SubmitButton',
|
||||
props: {
|
||||
submitConf: Object,
|
||||
skinConf: {
|
||||
type: Object,
|
||||
default: () => ({})
|
||||
},
|
||||
readonly: Boolean,
|
||||
validate: Function,
|
||||
renderData: Array
|
||||
},
|
||||
emits: ['submit', 'select'],
|
||||
setup(props, { emit }) {
|
||||
const submit = (e) => {
|
||||
if (!props.readonly) return
|
||||
const validate = props.validate
|
||||
if (e) {
|
||||
e.preventDefault()
|
||||
validate((valid) => {
|
||||
if (valid) {
|
||||
emit('submit')
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
const handleClick = () => {
|
||||
if (props.readonly) return
|
||||
emit('select')
|
||||
}
|
||||
|
||||
return {
|
||||
props,
|
||||
submit,
|
||||
handleClick
|
||||
}
|
||||
},
|
||||
render() {
|
||||
const { submitConf } = this.props
|
||||
return (
|
||||
<div
|
||||
class={['submit-warp', 'preview-submit_wrapper']}
|
||||
onClick={this.handleClick}
|
||||
>
|
||||
<button class="submit-btn" type="primary" onClick={this.submit}>
|
||||
{submitConf.submitTitle}
|
||||
</button>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
})
|
27
web/src/materials/communals/widgets/SubmitButton/index.scss
Normal file
27
web/src/materials/communals/widgets/SubmitButton/index.scss
Normal file
@ -0,0 +1,27 @@
|
||||
.submit-warp {
|
||||
&.question-submit_wrapper {
|
||||
margin: 0 0.4rem;
|
||||
font-size: 0;
|
||||
position: relative;
|
||||
}
|
||||
&.preview-submit_wrapper {
|
||||
padding: 25px;
|
||||
text-align: center;
|
||||
}
|
||||
.submit-btn {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
padding: 0.25rem 0;
|
||||
font-size: 0.36rem;
|
||||
line-height: 0.5rem;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
background: var(--primary-color);
|
||||
border-radius: 0.08rem;
|
||||
margin: 0.4rem 0;
|
||||
cursor: pointer;
|
||||
border: none;
|
||||
}
|
||||
}
|
@ -9,7 +9,7 @@
|
||||
"
|
||||
>
|
||||
</Component>
|
||||
<LogoIcon v-if="!['successPage', 'indexPage'].includes(store.state.router)" />
|
||||
<LogoIcon v-if="!['successPage', 'indexPage'].includes(store.state.router)" :logo-conf="logoConf" :readonly="true" />
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
@ -24,12 +24,15 @@ import IndexPage from './pages/IndexPage.vue'
|
||||
import ErrorPage from './pages/ErrorPage.vue'
|
||||
import SuccessPage from './pages/SuccessPage.vue'
|
||||
import AlertDialog from './components/AlertDialog.vue'
|
||||
|
||||
import LogoIcon from './components/LogoIcon.vue'
|
||||
// @ts-ignore
|
||||
import communalLoader from '@materials/communals/communalLoader.js'
|
||||
import { get as _get, upperFirst } from 'lodash-es'
|
||||
import { initRuleEngine } from '@/render/hooks/useRuleEngine.js'
|
||||
|
||||
const LogoIcon = communalLoader.loadComponent('LogoIcon')
|
||||
|
||||
const store = useStore()
|
||||
const logoConf = computed(() => store.state?.bottomConf || {})
|
||||
const skinConf = computed(() => _get(store, 'state.skinConf', {}))
|
||||
const components = {
|
||||
EmptyPage,
|
||||
|
@ -1,123 +0,0 @@
|
||||
<template>
|
||||
<div class="question-header">
|
||||
<div class="banner" v-if="bannerConf.bannerConfig && bannerConf.bannerConfig.bgImage">
|
||||
<img
|
||||
class="banner-img"
|
||||
:src="bannerConf.bannerConfig.bgImage"
|
||||
:class="{ pointer: bannerConf.bannerConfig.bgImageAllowJump }"
|
||||
@click="handleBannerClick"
|
||||
/>
|
||||
</div>
|
||||
<div class="banner" v-if="bannerConf.bannerConfig && bannerConf.bannerConfig.videoLink">
|
||||
<div class="video">
|
||||
<video
|
||||
ref="videoRef"
|
||||
controls
|
||||
style="margin: 0 auto; width: 100%; display: block"
|
||||
preload="auto"
|
||||
:poster="bannerConf.bannerConfig.postImg"
|
||||
>
|
||||
<source :src="bannerConf.bannerConfig.videoLink" type="video/mp4" />
|
||||
</video>
|
||||
<div
|
||||
class="video-modal"
|
||||
:style="{
|
||||
backgroundImage:
|
||||
bannerConf.bannerConfig.postImg && `url(${bannerConf.bannerConfig.postImg})`,
|
||||
display: displayModel
|
||||
}"
|
||||
></div>
|
||||
<div
|
||||
class="iconfont icon-kaishi play-icon"
|
||||
:style="{ display: displayModel }"
|
||||
@click="handlePlay()"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import { useStore } from 'vuex'
|
||||
import { get as _get } from 'lodash-es'
|
||||
|
||||
import { formatLink } from '../utils/index.js'
|
||||
|
||||
const store = useStore()
|
||||
|
||||
const bannerConf = computed<any>(() => _get(store, 'state.bannerConf', {}))
|
||||
|
||||
const handleBannerClick = () => {
|
||||
const allow = _get(bannerConf.value, 'bannerConfig.bgImageAllowJump', false)
|
||||
const jumpLink = _get(bannerConf.value, 'bannerConfig.bgImageJumpLink', '')
|
||||
|
||||
if (!allow || !jumpLink) {
|
||||
return
|
||||
}
|
||||
|
||||
window.open(formatLink(jumpLink))
|
||||
}
|
||||
|
||||
const videoRef = ref<HTMLVideoElement | null>(null)
|
||||
const displayModel = ref('block')
|
||||
|
||||
const handlePlay = () => {
|
||||
if (bannerConf.value.bannerConfig && bannerConf.value.bannerConfig.videoLink) {
|
||||
videoRef.value?.play()
|
||||
displayModel.value = 'none'
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.question-header {
|
||||
.banner,
|
||||
.titlePanel {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.banner {
|
||||
display: flex;
|
||||
.banner-img {
|
||||
width: 100%;
|
||||
|
||||
&.pointer {
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.titlePanel {
|
||||
padding: 0.4rem 0.4rem;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
.video {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.video-modal {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
background-size: cover;
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.play-icon {
|
||||
position: absolute;
|
||||
background-repeat: no-repeat;
|
||||
background-position: center;
|
||||
font-size: 1.8rem;
|
||||
left: 50%;
|
||||
top: 49%;
|
||||
-webkit-transform: translate(-50%, -50%);
|
||||
transform: translate(-50%, -50%);
|
||||
z-index: 101;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,29 +0,0 @@
|
||||
<template>
|
||||
<div class="container">
|
||||
<div v-if="logoImage" class="logo-wrapper">
|
||||
<img :style="{ width: !isMobile ? '20%' : logoImageWidth || '20%' }" :src="logoImage" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useStore } from 'vuex'
|
||||
|
||||
const store = useStore()
|
||||
|
||||
const logoImage = computed(() => store.state?.bottomConf?.logoImage)
|
||||
const logoImageWidth = computed(() => store.state?.bottomConf?.logoImageWidth)
|
||||
const isMobile = computed(() => store.state?.isMobile)
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
.container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.logo-wrapper {
|
||||
text-align: center;
|
||||
font-size: 0;
|
||||
padding: 0.1rem 0 0.5rem;
|
||||
}
|
||||
</style>
|
@ -1,60 +0,0 @@
|
||||
<template>
|
||||
<div class="question-header">
|
||||
<div class="titlePanel" v-if="bannerConf.titleConfig && bannerConf.titleConfig.mainTitle">
|
||||
<div
|
||||
class="mainTitle"
|
||||
v-if="bannerConf.titleConfig.mainTitle"
|
||||
v-html="bannerConf.titleConfig.mainTitle"
|
||||
></div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useStore } from 'vuex'
|
||||
|
||||
const store = useStore()
|
||||
const bannerConf = computed(() => store.state?.bannerConf || {})
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@import '@/render/styles/variable.scss';
|
||||
.question-header {
|
||||
.titlePanel {
|
||||
position: relative;
|
||||
width: 100%;
|
||||
padding: 0.4rem 0.4rem;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
}
|
||||
.mainTitle {
|
||||
font-size: 0.28rem;
|
||||
line-height: 0.4rem;
|
||||
color: $title-color;
|
||||
|
||||
ol {
|
||||
list-style: decimal;
|
||||
}
|
||||
|
||||
ul {
|
||||
list-style: disc;
|
||||
}
|
||||
|
||||
img {
|
||||
width: 100%;
|
||||
}
|
||||
h1,
|
||||
h2,
|
||||
h3,
|
||||
h4,
|
||||
h5,
|
||||
h6,
|
||||
p {
|
||||
line-height: 0.6rem;
|
||||
color: $title-color-deep;
|
||||
margin-bottom: 0.35rem;
|
||||
}
|
||||
p {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -1,62 +0,0 @@
|
||||
<template>
|
||||
<div class="question-submit_wrapper">
|
||||
<button class="question-submit-btn" @click="handleSubmit">
|
||||
{{ submitConf.submitTitle }}
|
||||
</button>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useStore } from 'vuex'
|
||||
|
||||
interface Props {
|
||||
validate: (fn: (valid: boolean) => void) => void
|
||||
renderData?: Array<any>
|
||||
}
|
||||
|
||||
interface Emit {
|
||||
(ev: 'submit'): void
|
||||
}
|
||||
|
||||
const props = defineProps<Props>()
|
||||
const emit = defineEmits<Emit>()
|
||||
|
||||
const store = useStore()
|
||||
|
||||
const submitConf = computed(() => store.state?.submitConf || {})
|
||||
|
||||
const handleSubmit = (e: Event) => {
|
||||
const validate = props.validate
|
||||
if (e) {
|
||||
e.preventDefault()
|
||||
validate((valid) => {
|
||||
if (valid) {
|
||||
emit('submit')
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
</script>
|
||||
<style lang="scss" scoped>
|
||||
@import '@/render/styles/variable.scss';
|
||||
.question-submit_wrapper {
|
||||
margin: 0 0.4rem;
|
||||
font-size: 0;
|
||||
position: relative;
|
||||
.question-submit-btn {
|
||||
position: relative;
|
||||
display: inline-block;
|
||||
width: 100%;
|
||||
padding: 0.25rem 0;
|
||||
font-size: 0.36rem;
|
||||
line-height: 0.5rem;
|
||||
font-weight: 500;
|
||||
text-align: center;
|
||||
color: #fff;
|
||||
background: var(--primary-color);
|
||||
border-radius: 0.08rem;
|
||||
margin: 0.4rem 0;
|
||||
cursor: pointer;
|
||||
}
|
||||
}
|
||||
</style>
|
@ -10,7 +10,6 @@ export const useShowInput = (questionKey) => {
|
||||
if (curRange.isShowInput) {
|
||||
const rangeKey = `${questionKey}_${key}`
|
||||
othersValue[rangeKey] = formValues[rangeKey]
|
||||
|
||||
;(curRange.othersKey = rangeKey), (curRange.othersValue = formValues[rangeKey])
|
||||
if (!questionVal.toString().includes(key) && formValues[rangeKey]) {
|
||||
// 如果分值被未被选中且对应的填写更多有值,则清空填写更多
|
||||
|
@ -2,12 +2,18 @@
|
||||
<div class="index">
|
||||
<ProgressBar />
|
||||
<div class="wrapper" ref="boxRef">
|
||||
<HeaderSetter></HeaderSetter>
|
||||
<HeaderContent :bannerConf="bannerConf" :readonly="true" />
|
||||
<div class="content">
|
||||
<MainTitle></MainTitle>
|
||||
<MainTitle :bannerConf="bannerConf" :readonly="true"></MainTitle>
|
||||
<MainRenderer ref="mainRef"></MainRenderer>
|
||||
<Submit :validate="validate" :renderData="renderData" @submit="handleSubmit"></Submit>
|
||||
<LogoIcon />
|
||||
<SubmitButton
|
||||
:validate="validate"
|
||||
:submitConf="submitConf"
|
||||
:readonly="true"
|
||||
:renderData="renderData"
|
||||
@submit="handleSubmit"
|
||||
></SubmitButton>
|
||||
<LogoIcon :logo-conf="logoConf" :readonly="true" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
@ -15,15 +21,12 @@
|
||||
<script setup lang="ts">
|
||||
import { computed, ref } from 'vue'
|
||||
import { useStore } from 'vuex'
|
||||
|
||||
import HeaderSetter from '../components/HeaderSetter.vue'
|
||||
import MainTitle from '../components/MainTitle.vue'
|
||||
import Submit from '../components/SubmitSetter.vue'
|
||||
// @ts-ignore
|
||||
import communalLoader from '@materials/communals/communalLoader.js'
|
||||
import MainRenderer from '../components/MainRenderer.vue'
|
||||
import AlertDialog from '../components/AlertDialog.vue'
|
||||
import ConfirmDialog from '../components/ConfirmDialog.vue'
|
||||
import ProgressBar from '../components/ProgressBar.vue'
|
||||
import LogoIcon from '../components/LogoIcon.vue'
|
||||
|
||||
import { submitForm } from '../api/survey'
|
||||
import encrypt from '../utils/encrypt'
|
||||
@ -40,6 +43,11 @@ withDefaults(defineProps<Props>(), {
|
||||
isMobile: false
|
||||
})
|
||||
|
||||
const HeaderContent = communalLoader.loadComponent('HeaderContent')
|
||||
const MainTitle = communalLoader.loadComponent('MainTitle')
|
||||
const SubmitButton = communalLoader.loadComponent('SubmitButton')
|
||||
const LogoIcon = communalLoader.loadComponent('LogoIcon')
|
||||
|
||||
const mainRef = ref<any>()
|
||||
const boxRef = ref<HTMLElement>()
|
||||
|
||||
@ -48,7 +56,10 @@ const confirm = useCommandComponent(ConfirmDialog)
|
||||
|
||||
const store = useStore()
|
||||
|
||||
const bannerConf = computed(() => store.state?.bannerConf || {})
|
||||
const renderData = computed(() => store.getters.renderData)
|
||||
const submitConf = computed(() => store.state?.submitConf || {})
|
||||
const logoConf = computed(() => store.state?.bottomConf || {})
|
||||
|
||||
const validate = (cbk: (v: boolean) => void) => {
|
||||
const index = 0
|
||||
@ -58,12 +69,12 @@ const validate = (cbk: (v: boolean) => void) => {
|
||||
const normalizationRequestBody = () => {
|
||||
const enterTime = store.state.enterTime
|
||||
const encryptInfo = store.state.encryptInfo
|
||||
const formValues = store.state.formValues
|
||||
const formModel = store.getters.formModel
|
||||
const surveyPath = store.state.surveyPath
|
||||
|
||||
const result: any = {
|
||||
surveyPath,
|
||||
data: JSON.stringify(formValues),
|
||||
data: JSON.stringify(formModel),
|
||||
difTime: Date.now() - enterTime,
|
||||
clientTime: Date.now()
|
||||
}
|
||||
@ -126,11 +137,13 @@ const handleSubmit = () => {
|
||||
<style scoped lang="scss">
|
||||
.index {
|
||||
min-height: 100%;
|
||||
|
||||
.wrapper {
|
||||
min-height: 100%;
|
||||
background-color: var(--primary-background-color);
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
|
||||
.content {
|
||||
flex: 1;
|
||||
margin: 0 0.3rem;
|
||||
|
@ -5,17 +5,20 @@
|
||||
<img src="/imgs/icons/success.webp" />
|
||||
<div class="msg" v-html="successMsg"></div>
|
||||
</div>
|
||||
<LogoIcon />
|
||||
<LogoIcon :logo-conf="logoConf" :readonly="true" />
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
import { computed } from 'vue'
|
||||
import { useStore } from 'vuex'
|
||||
import LogoIcon from '../components/LogoIcon.vue'
|
||||
// @ts-ignore
|
||||
import communalLoader from '@materials/communals/communalLoader.js'
|
||||
|
||||
const LogoIcon = communalLoader.loadComponent('LogoIcon')
|
||||
const store = useStore()
|
||||
|
||||
const logoConf = computed(() => store.state?.bottomConf || {})
|
||||
const successMsg = computed(() => {
|
||||
const msgContent = store.state?.submitConf?.msgContent || {}
|
||||
return msgContent?.msg_200 || '提交成功'
|
||||
|
Loading…
Reference in New Issue
Block a user