fix: 修复写法问题、C端组件问题

This commit is contained in:
sudoooooo 2024-09-22 01:41:32 +08:00
parent 56c37fce3c
commit cdb8b6532a
49 changed files with 1259 additions and 1199 deletions

View File

@ -33,7 +33,6 @@ import { SURVEY_PERMISSION } from 'src/enums/surveyPermission';
import { WorkspaceGuard } from 'src/guards/workspace.guard';
import { PERMISSION as WORKSPACE_PERMISSION } from 'src/enums/workspace';
import { SessionService } from '../services/session.service';
import { MemberType, WhitelistType } from 'src/interfaces/survey';
import { UserService } from 'src/modules/auth/services/user.service';
@ApiTags('survey')
@ -247,16 +246,6 @@ export class SurveyController {
surveyMeta.isCollaborated = false;
}
// 白名单相关字段的默认值
const baseConf = surveyConf.code?.baseConf;
if (baseConf) {
baseConf.passwordSwitch = baseConf.passwordSwitch ?? false;
baseConf.password = baseConf.password ?? '';
baseConf.whitelistType = baseConf.whitelistType ?? WhitelistType.ALL;
baseConf.whitelist = baseConf.whitelist ?? [];
baseConf.memberType = baseConf.memberType ?? MemberType.MOBILE;
}
return {
code: 200,
data: {

View File

@ -34,7 +34,14 @@
"tLimit": 0,
"language": "chinese",
"answerBegTime": "00:00:00",
"answerEndTime": "23:59:59"
"answerEndTime": "23:59:59",
"passwordSwitch": false,
"password": "",
"whitelistType": "ALL",
"whitelist": [],
"memberType": "MOBILE",
"fillAnswer": false,
"fillSubmitAnswer": false
},
"skinConf": {
"skinColor": "#4a4c5b",

View File

@ -97,14 +97,17 @@ export class ResponseSchemaController {
// 密码校验
if (passwordSwitch) {
if (settingPassword !== password) {
throw new HttpException('验证失败', EXCEPTION_CODE.WHITELIST_ERROR);
throw new HttpException('密码验证失败', EXCEPTION_CODE.WHITELIST_ERROR);
}
}
// 名单校验(手机号/邮箱)
if (whitelistType === WhitelistType.CUSTOM) {
if (!whitelist.includes(whitelistValue)) {
throw new HttpException('验证失败', EXCEPTION_CODE.WHITELIST_ERROR);
throw new HttpException(
'白名单验证失败',
EXCEPTION_CODE.WHITELIST_ERROR,
);
}
}
@ -112,7 +115,7 @@ export class ResponseSchemaController {
if (whitelistType === WhitelistType.MEMBER) {
const user = await this.userService.getUserByUsername(whitelistValue);
if (!user) {
throw new HttpException('验证失败', EXCEPTION_CODE.WHITELIST_ERROR);
throw new HttpException('名单验证失败', EXCEPTION_CODE.WHITELIST_ERROR);
}
const workspaceMember = await this.workspaceMemberService.findAllByUserId(

View File

@ -2,6 +2,7 @@ import { Injectable } from '@nestjs/common';
import { InjectRepository } from '@nestjs/typeorm';
import { MongoRepository } from 'typeorm';
import { SurveyResponse } from 'src/models/surveyResponse.entity';
import { RECORD_STATUS } from 'src/enums';
@Injectable()
export class SurveyResponseService {
constructor(
@ -35,14 +36,14 @@ export class SurveyResponseService {
}
async getSurveyResponseTotalByPath(surveyPath: string) {
const count = await this.surveyResponseRepository.count({
const data = await this.surveyResponseRepository.find({
where: {
surveyPath,
'curStatus.status': {
$ne: 'removed',
$ne: RECORD_STATUS.REMOVED,
},
},
});
return count;
return (data || []).length;
}
}

View File

@ -1,58 +0,0 @@
interface ExtendedItem {
value: any
expires?: number // 可选属性
}
const localstorage = {
// 检查是否支持localStorage
isSupported(): boolean {
return typeof window !== 'undefined' && 'localStorage' in window
},
// 设置值
setItem(key: string, value: any, expires?: number): void {
if (!this.isSupported()) return
let item: ExtendedItem = { value }
if (expires !== undefined) {
item = { ...item, expires: Date.now() + expires * 1000 }
}
const serializedValue = JSON.stringify(item)
localStorage.setItem(key, serializedValue)
},
// 获取值
getItem<T>(key: string): T | null {
if (!this.isSupported()) return null
const serializedValue = localStorage.getItem(key) as string
if (!serializedValue) return null
let item: any
try {
item = JSON.parse(serializedValue)
} catch (e) {
console.error('Error parsing JSON from localStorage')
return null
}
if (item.expires && item.expires < Date.now()) {
this.removeItem(key)
return null
}
return item.value as T
},
// 移除值
removeItem(key: string): void {
if (!this.isSupported()) return
localStorage.removeItem(key)
}
}
export default localstorage

View File

@ -18,7 +18,7 @@ const router = useRouter()
let timer: any
const showConfirmBox = () => {
ElMessageBox.alert('登录状态已失效,请重新登。', '提示', {
ElMessageBox.alert('登录状态已失效,请重新登。', '提示', {
confirmButtonText: '确认',
showClose: false,
callback: (action: Action) => {
@ -57,9 +57,9 @@ const checkAuth = async () => {
}
watch(
() => userStore.hasLogined,
(hasLogined) => {
if (hasLogined) {
() => userStore.hasLogin,
(hasLogin) => {
if (hasLogin) {
timer = setTimeout(
() => {
checkAuth()

View File

@ -37,9 +37,9 @@ instance.interceptors.response.use(
instance.interceptors.request.use((config) => {
const userStore = useUserStore()
const hasLogined = _get(userStore, 'hasLogined')
const hasLogin = _get(userStore, 'hasLogin')
const token = _get(userStore, 'userInfo.token')
if (hasLogined && token) {
if (hasLogin && token) {
if (!config.headers) {
config.headers = {}
}

View File

@ -1,5 +1,5 @@
// 引入防抖函数
import { debounce as _debounce } from 'lodash-es'
import { debounce } from 'lodash-es'
/**
* @description: 监听元素尺寸变化
* @param {*} el 元素dom
@ -8,7 +8,7 @@ import { debounce as _debounce } from 'lodash-es'
* @return {*}
*/
export default (el, cb, wait = 200) => {
const resizeObserver = new ResizeObserver(_debounce(cb, wait))
const resizeObserver = new ResizeObserver(debounce(cb, wait))
resizeObserver.observe(el)

View File

@ -7,7 +7,7 @@
>
<div><slot v-if="moduleConfig.type !== 'section'"></slot></div>
<div :class="[showHover ? 'visibily' : 'hidden', 'hoverItem']">
<div :class="[showHover ? 'visibility' : 'hidden', 'hoverItem']">
<div
class="item el-icon-rank"
@click.stop.prevent

View File

@ -29,8 +29,6 @@ import { ref, watch } from 'vue'
import { storeToRefs } from 'pinia'
import { useEditStore } from '@/management/stores/edit'
import moment from 'moment'
import 'moment/locale/zh-cn'
moment.locale('zh-cn')
import { getSurveyHistory } from '@/management/api/survey'

View File

@ -65,7 +65,7 @@ const getSpaceMenus = async () => {
id: v.ownerId,
label: v.name,
children: members?.map((v) => ({
id: v.userId,
id: `${v.workspaceId}_${v.userId}`,
label: v.username
}))
})

View File

@ -26,15 +26,21 @@
</div>
</template>
<script setup lang="ts">
import { computed, ref } from 'vue'
import { computed, ref, onMounted } from 'vue'
import { useEditStore } from '@/management/stores/edit'
import { getBannerData } from '@/management/api/skin.js'
import skinPresets from '@/management/config/skinPresets.js'
const editStore = useEditStore()
const { changeThemePreset } = editStore
const groupName = ref<string>('temp')
const bannerList = computed(() => editStore.bannerList || [])
let bannerList = ref([])
onMounted(async () => {
const res = await getBannerData()
bannerList.value = res.data
})
const groupList = computed(() =>
Object.keys(bannerList.value).map((key) => ({
label: bannerList.value[key].name,
@ -53,11 +59,11 @@ const currentBannerList = computed(() => {
})
})
const allbanner = arr.reduce((acc, curr) => {
const allBanner = arr.reduce((acc, curr) => {
return acc.concat(curr)
}, [])
return allbanner.filter((item: any) => {
return allBanner.filter((item: any) => {
if (groupName.value === 'temp') {
return true
} else {

View File

@ -12,19 +12,19 @@
</CommonTemplate>
</template>
<script setup lang="ts">
import { onMounted } from 'vue'
import { useEditStore } from '@/management/stores/edit'
// import { onMounted } from 'vue'
// import { useEditStore } from '@/management/stores/edit'
import CommonTemplate from '../../components/CommonTemplate.vue'
import CatalogPanel from '../../modules/skinModule/CatalogPanel.vue'
import PreviewPanel from '../../modules/skinModule/PreviewPanel.vue'
import SetterPanel from '../../modules/skinModule/SetterPanel.vue'
const editStore = useEditStore()
// const editStore = useEditStore()
onMounted(() => {
editStore.fetchBannerData()
})
// onMounted(() => {
// editStore.fetchBannerData()
// })
</script>
<style lang="scss" scoped>
.navbar {

View File

@ -27,16 +27,14 @@ export default {
label: '允许断点续答',
tip: '回填前一次作答中的内容(注:更换设备/浏览器/清除缓存/更改内容重新发布则此功能失效)',
placement: 'top',
type: 'CustomedSwitch',
value: false
type: 'CustomedSwitch'
},
limit_fillSubmitAnswer: {
key: 'fillSubmitAnswer',
label: '自动填充上次提交内容',
tip: '回填前一次提交的内容(注:更换设备/浏览器/清除缓存/更改内容重新发布则此功能失效)',
placement: 'top',
type: 'CustomedSwitch',
value: false
type: 'CustomedSwitch'
},
interview_pwd_switch: {
key: 'passwordSwitch',
@ -81,6 +79,10 @@ export default {
{
key: 'whitelist', // 切换tab清空名单列表
value: []
},
{
key: 'memberType',
value: ''
}
]
}

View File

@ -113,12 +113,6 @@ import { ElMessage, ElMessageBox } from 'element-plus'
import 'element-plus/theme-chalk/src/message.scss'
import 'element-plus/theme-chalk/src/message-box.scss'
import moment from 'moment'
//
import 'moment/locale/zh-cn'
//
moment.locale('zh-cn')
import EmptyIndex from '@/management/components/EmptyIndex.vue'
import CooperModify from '@/management/components/CooperModify/ModifyDialog.vue'
import { CODE_MAP } from '@/management/api/base'

View File

@ -158,15 +158,11 @@ const handleModify = async (id: string) => {
showSpaceModify.value = true
}
const handleDelete = (id: string) => {
ElMessageBox.confirm(
'删除团队后,团队内的问卷将同步被删除,请谨慎考虑!是否确认本次删除?',
'提示',
{
ElMessageBox.confirm('删除后团队内的问卷将同步被删除,是否确认本次删除?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
}
)
})
.then(async () => {
await workSpaceStore.deleteSpace(id)
await workSpaceStore.getSpaceList()

View File

@ -44,20 +44,22 @@
</el-form-item>
<el-form-item class="button-group">
<el-button
:loading="pending.register"
class="button register-button"
@click="submitForm('register')"
>
注册
</el-button>
<el-button
:loading="pending.login"
size="small"
type="primary"
class="button"
@click="submitForm('login')"
>登录</el-button
>
<el-button
:loading="pending.register"
class="button register-button"
@click="submitForm('register')"
>注册</el-button
>
登录
</el-button>
</el-form-item>
</el-form>
</div>
@ -71,7 +73,7 @@ import { useRoute, useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import 'element-plus/theme-chalk/src/message.scss'
import { debounce as _debounce } from 'lodash-es'
import { debounce } from 'lodash-es'
import { getPasswordStrength, login, register } from '@/management/api/auth'
import { refreshCaptcha as refreshCaptchaApi } from '@/management/api/captcha'
@ -161,7 +163,7 @@ const rules = {
],
password: [
{
validator: _debounce(passwordValidator, 500),
validator: debounce(passwordValidator, 500),
trigger: 'change'
}
],
@ -285,7 +287,7 @@ const refreshCaptcha = async () => {
}
.button {
width: 200px;
width: 204px;
height: 40px;
font-size: 14px;
}
@ -293,7 +295,6 @@ const refreshCaptcha = async () => {
.register-button {
border-color: #faa600;
color: #faa600;
margin-left: 20px;
}
}

View File

@ -197,7 +197,7 @@ async function handleLoginGuard(
next: NavigationGuardNext
) {
const userStore = useUserStore()
if (userStore?.hasLogined) {
if (userStore?.hasLogin) {
await handlePermissionsGuard(to, from, next)
} else {
next({
@ -221,8 +221,8 @@ async function handlePermissionsGuard(
} else {
// 如果跳转编辑页面且跳转页面和上一页的surveyId不同判断是否有对应页面权限
if (currSurveyId !== prevSurveyId) {
await editStore.fetchCooperPermissions(currSurveyId as string)
if (hasRequiredPermissions(to.meta.permissions as string[], editStore.cooperPermissions)) {
const cooperPermissions = await editStore.fetchCooperPermissions(currSurveyId as string)
if (hasRequiredPermissions(to.meta.permissions as string[], cooperPermissions)) {
next()
} else {
ElMessage.warning('您没有该问卷的相关协作权限')

View File

@ -1,4 +1,4 @@
import { reactive, type Ref } from 'vue'
import { type Ref, reactive } from 'vue'
export type TypeMethod = 'INIT' | 'MODIFY' | 'REMOVE' | 'ADD'

View File

@ -0,0 +1,100 @@
import { type Ref, ref, computed } from 'vue'
import submitFormConfig from '@/management/pages/edit/setterConfig/submitConfig'
import questionLoader from '@/materials/questions/questionLoader'
const innerMetaConfig = {
submit: {
title: '提交配置',
formConfig: submitFormConfig
}
}
export default function useCurrentEdit({
schema,
questionDataList
}: {
schema: any
questionDataList: Ref<any[]>
}) {
const currentEditOne = ref()
const currentEditStatus = ref('Success')
const currentEditKey = computed(() => {
if (currentEditOne.value === null) {
return null
}
let key = ''
switch (currentEditOne.value) {
case 'banner':
case 'mainTitle':
key = 'bannerConf'
break
case 'submit':
key = 'submitConf'
break
case 'logo':
key = 'bottomConf'
break
default:
key = `questionDataList.${currentEditOne.value}`
}
return key
})
const currentEditMeta = computed(() => {
if (currentEditOne.value === null) {
return null
} else if (innerMetaConfig[currentEditOne.value as keyof typeof innerMetaConfig]) {
return innerMetaConfig[currentEditOne.value as keyof typeof innerMetaConfig]
} else {
const questionType = questionDataList.value?.[currentEditOne.value]?.type
return questionLoader.getMeta(questionType)
}
})
const moduleConfig = computed(() => {
if (currentEditOne.value === null) {
return null
}
if (currentEditOne.value === 'banner' || currentEditOne.value === 'mainTitle') {
return schema?.bannerConf
} else if (currentEditOne.value === 'submit') {
return schema?.submitConf
} else if (currentEditOne.value === 'logo') {
return schema?.bottomConf
} else if (!Number.isNaN(currentEditOne.value)) {
return questionDataList.value?.[currentEditOne.value]
} else {
return null
}
})
const formConfigList = computed(() => {
if (currentEditOne.value === null) {
return null
}
return currentEditMeta.value?.formConfig || []
})
function setCurrentEditOne(data: any) {
currentEditOne.value = data
}
function changeCurrentEditStatus(status: string) {
currentEditStatus.value = status
}
return {
currentEditOne,
currentEditKey,
currentEditStatus,
moduleConfig,
formConfigList,
currentEditMeta,
setCurrentEditOne,
changeCurrentEditStatus
}
}

View File

@ -0,0 +1,146 @@
import { type Ref, ref, reactive } from 'vue'
import { merge } from 'lodash-es'
import { getSurveyById, getSessionId } from '@/management/api/survey'
import useLogicEngine from './useLogicEngine'
export default function useInitializeSchema(
surveyId: Ref<string>,
initializeSchemaCallBack: () => void
) {
const schema = reactive({
metaData: null,
bannerConf: {
titleConfig: {
mainTitle: '<h3 style="text-align: center">欢迎填写问卷</h3>',
subTitle: `<p>为了给您提供更好的服务,希望您能抽出几分钟时间,将您的感受和建议告诉我们,<span style="color: rgb(204, 0, 0)">期待您的参与!</span></p>`,
applyTitle: ''
},
bannerConfig: {
bgImage: '',
bgImageAllowJump: false,
bgImageJumpLink: '',
videoLink: '',
postImg: ''
}
},
bottomConf: {
logoImage: '',
logoImageWidth: '28%'
},
skinConf: {
backgroundConf: {
color: '#fff'
},
themeConf: {
color: '#ffa600'
},
contentConf: {
opacity: 100
}
},
baseConf: {
begTime: '',
endTime: '',
language: 'chinese',
tLimit: 0,
answerBegTime: '',
answerEndTime: '',
answerLimitTime: 0
},
submitConf: {
submitTitle: '',
msgContent: {},
confirmAgain: {
is_again: true
},
link: ''
},
questionDataList: [],
pageEditOne: 1,
pageConf: [], // 分页逻辑
logicConf: {
showLogicConf: [],
jumpLogicConf: []
}
})
const { showLogicEngine, initShowLogicEngine, jumpLogicEngine, initJumpLogicEngine } =
useLogicEngine(schema)
function initSchema({ metaData, codeData }: { metaData: any; codeData: any }) {
schema.metaData = metaData
schema.bannerConf = merge({}, schema.bannerConf, codeData.bannerConf)
schema.bottomConf = merge({}, schema.bottomConf, codeData.bottomConf)
schema.skinConf = merge({}, schema.skinConf, codeData.skinConf)
schema.baseConf = merge({}, schema.baseConf, codeData.baseConf)
schema.submitConf = merge({}, schema.submitConf, codeData.submitConf)
schema.questionDataList = codeData.questionDataList || []
schema.logicConf = codeData.logicConf
schema.pageEditOne = 1
schema.pageConf = codeData.pageConf
}
const sessionId = ref('')
async function initSessionId() {
const sessionIdKey = `${surveyId.value}_sessionId`
const localSessionId = sessionStorage.getItem(sessionIdKey)
if (localSessionId) {
sessionId.value = localSessionId
} else {
const res: Record<string, any> = await getSessionId({ surveyId: surveyId.value })
if (res.code === 200) {
sessionId.value = res.data.sessionId
sessionStorage.setItem(sessionIdKey, sessionId.value)
}
}
}
async function getSchemaFromRemote() {
const res: any = await getSurveyById(surveyId.value)
if (res.code === 200) {
const metaData = res.data.surveyMetaRes
document.title = metaData.title
const data = res.data.surveyConfRes.code
const {
bannerConf,
bottomConf,
skinConf,
baseConf,
submitConf,
dataConf,
logicConf = {}
} = data
if (!data.pageConf || data.pageConf.length === 0) {
data.pageConf = [dataConf.dataList.length]
}
initSchema({
metaData,
codeData: {
bannerConf,
bottomConf,
skinConf,
baseConf,
submitConf,
questionDataList: dataConf.dataList,
pageConf: data.pageConf,
logicConf
}
})
initializeSchemaCallBack()
initShowLogicEngine()
initJumpLogicEngine()
} else {
throw new Error(res.errmsg || '问卷不存在')
}
}
return {
schema,
getSchemaFromRemote,
showLogicEngine,
jumpLogicEngine,
sessionId,
initSessionId
}
}

View File

@ -0,0 +1,20 @@
import { ref, toRef } from 'vue'
import { RuleBuild } from '@/common/logicEngine/RuleBuild'
export default function useLogicEngine(schema: any) {
const logicConf = toRef(schema, 'logicConf')
const showLogicEngine = ref()
const jumpLogicEngine = ref()
function initShowLogicEngine() {
showLogicEngine.value = new RuleBuild().fromJson(logicConf.value?.showLogicConf)
}
function initJumpLogicEngine() {
jumpLogicEngine.value = new RuleBuild().fromJson(logicConf.value?.jumpLogicConf)
}
return {
showLogicEngine,
jumpLogicEngine,
initShowLogicEngine,
initJumpLogicEngine
}
}

View File

@ -0,0 +1,156 @@
import { type Ref, computed } from 'vue'
import { cloneDeep } from 'lodash-es'
import { ElMessageBox } from 'element-plus'
import { filterQuestionPreviewData, getNewField } from '@/management/utils/index'
import { useShowLogicInfo } from '@/management/hooks/useShowLogicInfo'
import { useJumpLogicInfo } from '@/management/hooks/useJumpLogicInfo'
export default function usePageEdit(
{
schema,
questionDataList
}: {
schema: any
questionDataList: Ref<any[]>
},
updateTime: () => void
) {
const pageConf = computed(() => schema.pageConf)
const pageEditOne = computed(() => schema.pageEditOne)
const isFinallyPage = computed(() => {
return pageEditOne.value === pageConf.value.length
})
const pageCount = computed(() => pageConf.value.length || 0)
const pageQuestionData = computed(() => {
return getPageQuestionData(pageEditOne.value)
})
const getPageQuestionData = (index: number) => {
const { startIndex, endIndex } = getSorter(index)
return filterQuestionPreviewData(questionDataList.value).slice(startIndex, endIndex)
}
const getSorter = (index?: number) => {
let startIndex = 0
const newPageEditOne = index || pageEditOne.value
const endIndex = pageConf.value[newPageEditOne - 1]
for (let index = 0; index < pageConf.value.length; index++) {
const item = pageConf.value[index]
if (newPageEditOne - 1 == index) {
break
}
startIndex += item
}
return {
startIndex,
endIndex: startIndex + endIndex
}
}
const addPage = () => {
schema.pageConf.push(1)
}
const updatePageEditOne = (index: number) => {
schema.pageEditOne = index
}
const deletePage = (index: number) => {
if (pageConf.value.length <= 1) return
const { startIndex, endIndex } = getSorter(index)
const newQuestionList = cloneDeep(questionDataList.value)
const deleteFields = newQuestionList
.slice(startIndex, endIndex - startIndex)
.map((i) => i.field)
// 删除分页判断题目是否存在逻辑关联
const hasLogic = deleteFields.filter((field) => {
const { hasShowLogic } = useShowLogicInfo(field)
const { hasJumpLogic } = useJumpLogicInfo(field)
return hasShowLogic || hasJumpLogic
})
if (hasLogic.length) {
ElMessageBox.alert('该分页下有题目被显示或跳转逻辑关联,请先清除', '提示', {
confirmButtonText: '确定',
type: 'warning'
})
return
}
updatePageEditOne(1)
newQuestionList.splice(startIndex, endIndex - startIndex)
schema.pageConf.splice(index - 1, 1)
questionDataList.value = newQuestionList
updateTime()
}
const swapArrayRanges = (index: number, range: number) => {
const { startIndex: start1, endIndex: end1 } = getSorter(index)
const { startIndex: start2, endIndex: end2 } = getSorter(range)
const newQuestion = cloneDeep(questionDataList.value)
const range1 = newQuestion.slice(start1, end1)
const range2 = newQuestion.slice(start2, end2)
newQuestion.splice(start1, range1.length, ...range2)
newQuestion.splice(start2, range2.length, ...range1)
questionDataList.value = newQuestion
const rangeCount = schema.pageConf[range - 1]
schema.pageConf[range - 1] = schema.pageConf[index - 1]
schema.pageConf[index - 1] = rangeCount
updateTime()
}
const copyPage = (index: number) => {
const newQuestionList = cloneDeep(getPageQuestionData(index))
newQuestionList.forEach((item) => {
item.field = getNewField(questionDataList.value.map((item) => item.field))
})
schema.pageConf.splice(index, 0, newQuestionList.length)
const { endIndex } = getSorter(index)
questionDataList.value.splice(endIndex, 0, ...newQuestionList)
updateTime()
}
const pageOperations = (type: string) => {
const count = pageConf.value[pageEditOne.value - 1]
if (type == 'add') {
if (count != undefined) {
schema.pageConf[pageEditOne.value - 1] = count + 1
}
return
}
if (type == 'remove') {
if (count) {
schema.pageConf[pageEditOne.value - 1] = count - 1
}
}
}
const setPage = (data: Array<number>) => {
for (let index = 0; index < pageConf.value.length; index++) {
const newIndex = data[index]
const oldIndex = pageConf.value[index]
if (newIndex != oldIndex) {
schema.pageConf[index] = newIndex
}
}
}
return {
pageEditOne,
pageConf,
isFinallyPage,
pageCount,
pageQuestionData,
getSorter,
updatePageEditOne,
deletePage,
addPage,
copyPage,
getPageQuestionData,
pageOperations,
swapArrayRanges,
setPage
}
}

View File

@ -0,0 +1,72 @@
import { type Ref } from 'vue'
import { cloneDeep } from 'lodash-es'
import { getNewField } from '@/management/utils'
import { type TypeMethod } from '../composables/useBaseConfig'
export default function useQuestionData({
questionDataList,
updateTime,
pageOperations,
updateCounts
}: {
questionDataList: Ref<any[]>
updateTime: () => void
pageOperations: (type: string) => void
updateCounts: (type: TypeMethod, data: any) => void
}) {
function copyQuestion({ index }: { index: number }) {
const newQuestion = cloneDeep(questionDataList.value[index])
newQuestion.field = getNewField(questionDataList.value.map((item) => item.field))
addQuestion({ question: newQuestion, index })
}
function addQuestion({ question, index }: { question: any; index: number }) {
questionDataList.value.splice(index, 0, question)
pageOperations('add')
updateTime()
updateCounts('ADD', { question })
}
function deleteQuestion({ index }: { index: number }) {
pageOperations('remove')
const [question] = questionDataList.value.splice(index, 1)
updateTime()
updateCounts('REMOVE', { question })
}
function moveQuestion({ index, range }: { index: number; range: number }) {
console.log('move')
let start, end
if (range < 0) {
// 向上移动
start = index + range
end = index
} else if (range > 0) {
// 向下移动
start = index + 1
end = index + range + 1
} else {
// 无变化
return
}
const currentData = questionDataList.value[index]
// 新位置和老位置之间所有的题目
const comparedList = questionDataList.value.slice(start, end)
if (range < 0) {
// 向上移动
questionDataList.value.splice(index + range, 1 - range, currentData, ...comparedList)
} else if (range > 0) {
// 向下移动
questionDataList.value.splice(index, range + 1, ...comparedList, currentData)
}
updateTime()
}
return {
copyQuestion,
addQuestion,
deleteQuestion,
moveQuestion
}
}

View File

@ -1,534 +1,27 @@
import { type Ref, ref, reactive, toRef, computed } from 'vue'
import { type Ref, ref, toRef, computed } from 'vue'
import { defineStore } from 'pinia'
import {
merge as _merge,
cloneDeep as _cloneDeep,
set as _set,
isNumber as _isNumber
} from 'lodash-es'
import { ElMessageBox } from 'element-plus'
import { set as _set, isNumber as _isNumber } from 'lodash-es'
import { QUESTION_TYPE } from '@/common/typeEnum'
import { getQuestionByType } from '@/management/utils/index'
import { filterQuestionPreviewData } from '@/management/utils/index'
import { getSurveyById, getSessionId } from '@/management/api/survey'
import { getNewField } from '@/management/utils'
import submitFormConfig from '@/management/pages/edit/setterConfig/submitConfig'
import questionLoader from '@/materials/questions/questionLoader'
import { SurveyPermissions } from '@/management/utils/types/workSpace'
import { getBannerData } from '@/management/api/skin.js'
import { getCollaboratorPermissions } from '@/management/api/space'
import useEditGlobalBaseConf, { type TypeMethod } from './composables/useEditGlobalBaseConf'
import useInitializeSchema from './composables/useInitializeSchema'
import useBaseConfig from './composables/useBaseConfig'
import useQuestionData from './composables/useQuestionData'
import useCurrentEdit from './composables/useCurrentEdit'
import usePageEdit from './composables/usePageEdit'
import { CODE_MAP } from '../api/base'
import { RuleBuild } from '@/common/logicEngine/RuleBuild'
import { useShowLogicInfo } from '@/management/hooks/useShowLogicInfo'
import { useJumpLogicInfo } from '@/management/hooks/useJumpLogicInfo'
const innerMetaConfig = {
submit: {
title: '提交配置',
formConfig: submitFormConfig
}
}
function useInitializeSchema(surveyId: Ref<string>, initializeSchemaCallBack: () => void) {
const schema = reactive({
metaData: null,
bannerConf: {
titleConfig: {
mainTitle: '<h3 style="text-align: center">欢迎填写问卷</h3>',
subTitle: `<p>为了给您提供更好的服务,希望您能抽出几分钟时间,将您的感受和建议告诉我们,<span style="color: rgb(204, 0, 0)">期待您的参与!</span></p>`,
applyTitle: ''
},
bannerConfig: {
bgImage: '',
bgImageAllowJump: false,
bgImageJumpLink: '',
videoLink: '',
postImg: ''
}
},
bottomConf: {
logoImage: '',
logoImageWidth: '28%'
},
skinConf: {
backgroundConf: {
color: '#fff'
},
themeConf: {
color: '#ffa600'
},
contentConf: {
opacity: 100
}
},
baseConf: {
begTime: '',
endTime: '',
language: 'chinese',
tLimit: 0,
answerBegTime: '',
answerEndTime: '',
answerLimitTime: 0
},
submitConf: {
submitTitle: '',
msgContent: {},
confirmAgain: {
is_again: true
},
link: ''
},
questionDataList: [],
pageEditOne: 1,
pageConf: [], // 分页逻辑
logicConf: {
showLogicConf: [],
jumpLogicConf: []
}
})
const { showLogicEngine, initShowLogicEngine, jumpLogicEngine, initJumpLogicEngine } =
useLogicEngine(schema)
function initSchema({ metaData, codeData }: { metaData: any; codeData: any }) {
schema.metaData = metaData
schema.bannerConf = _merge({}, schema.bannerConf, codeData.bannerConf)
schema.bottomConf = _merge({}, schema.bottomConf, codeData.bottomConf)
schema.skinConf = _merge({}, schema.skinConf, codeData.skinConf)
schema.baseConf = _merge({}, schema.baseConf, codeData.baseConf)
schema.submitConf = _merge({}, schema.submitConf, codeData.submitConf)
schema.questionDataList = codeData.questionDataList || []
schema.logicConf = codeData.logicConf
schema.pageEditOne = 1
schema.pageConf = codeData.pageConf
}
const sessionId = ref('')
async function initSessionId() {
const sessionIdKey = `${surveyId.value}_sessionId`
const localSessionId = sessionStorage.getItem(sessionIdKey)
if (localSessionId) {
sessionId.value = localSessionId
} else {
const res: Record<string, any> = await getSessionId({ surveyId: surveyId.value })
if (res.code === 200) {
sessionId.value = res.data.sessionId
sessionStorage.setItem(sessionIdKey, sessionId.value)
}
}
}
async function getSchemaFromRemote() {
const res: any = await getSurveyById(surveyId.value)
if (res.code === 200) {
const metaData = res.data.surveyMetaRes
document.title = metaData.title
const data = res.data.surveyConfRes.code
const {
bannerConf,
bottomConf,
skinConf,
baseConf,
submitConf,
dataConf,
logicConf = {}
} = data
if (!data.pageConf || data.pageConf.length === 0) {
data.pageConf = [dataConf.dataList.length]
}
initSchema({
metaData,
codeData: {
bannerConf,
bottomConf,
skinConf,
baseConf,
submitConf,
questionDataList: dataConf.dataList,
pageConf: data.pageConf,
logicConf
}
})
initializeSchemaCallBack()
initShowLogicEngine()
initJumpLogicEngine()
} else {
throw new Error(res.errmsg || '问卷不存在')
}
}
return {
schema,
getSchemaFromRemote,
showLogicEngine,
jumpLogicEngine,
sessionId,
initSessionId
}
}
function useQuestionDataListOperations({
questionDataList,
updateTime,
pageOperations,
updateCounts
}: {
questionDataList: Ref<any[]>
updateTime: () => void
pageOperations: (type: string) => void
updateCounts: (type: TypeMethod, data: any) => void
}) {
function copyQuestion({ index }: { index: number }) {
const newQuestion = _cloneDeep(questionDataList.value[index])
newQuestion.field = getNewField(questionDataList.value.map((item) => item.field))
addQuestion({ question: newQuestion, index })
}
function addQuestion({ question, index }: { question: any; index: number }) {
questionDataList.value.splice(index, 0, question)
pageOperations('add')
updateTime()
updateCounts('ADD', { question })
}
function deleteQuestion({ index }: { index: number }) {
pageOperations('remove')
const [question] = questionDataList.value.splice(index, 1)
updateTime()
updateCounts('REMOVE', { question })
}
function moveQuestion({ index, range }: { index: number; range: number }) {
console.log('move')
let start, end
if (range < 0) {
// 向上移动
start = index + range
end = index
} else if (range > 0) {
// 向下移动
start = index + 1
end = index + range + 1
} else {
// 无变化
return
}
const currentData = questionDataList.value[index]
// 新位置和老位置之间所有的题目
const comparedList = questionDataList.value.slice(start, end)
if (range < 0) {
// 向上移动
questionDataList.value.splice(index + range, 1 - range, currentData, ...comparedList)
} else if (range > 0) {
// 向下移动
questionDataList.value.splice(index, range + 1, ...comparedList, currentData)
}
updateTime()
}
return {
copyQuestion,
addQuestion,
deleteQuestion,
moveQuestion
}
}
function useCurrentEdit({
schema,
questionDataList
}: {
schema: any
questionDataList: Ref<any[]>
}) {
const currentEditOne = ref()
const currentEditStatus = ref('Success')
const currentEditKey = computed(() => {
if (currentEditOne.value === null) {
return null
}
let key = ''
switch (currentEditOne.value) {
case 'banner':
case 'mainTitle':
key = 'bannerConf'
break
case 'submit':
key = 'submitConf'
break
case 'logo':
key = 'bottomConf'
break
default:
key = `questionDataList.${currentEditOne.value}`
}
return key
})
const currentEditMeta = computed(() => {
if (currentEditOne.value === null) {
return null
} else if (innerMetaConfig[currentEditOne.value as keyof typeof innerMetaConfig]) {
return innerMetaConfig[currentEditOne.value as keyof typeof innerMetaConfig]
} else {
const questionType = questionDataList.value?.[currentEditOne.value]?.type
return questionLoader.getMeta(questionType)
}
})
const moduleConfig = computed(() => {
if (currentEditOne.value === null) {
return null
}
if (currentEditOne.value === 'banner' || currentEditOne.value === 'mainTitle') {
return schema?.bannerConf
} else if (currentEditOne.value === 'submit') {
return schema?.submitConf
} else if (currentEditOne.value === 'logo') {
return schema?.bottomConf
} else if (!Number.isNaN(currentEditOne.value)) {
return questionDataList.value?.[currentEditOne.value]
} else {
return null
}
})
const formConfigList = computed(() => {
if (currentEditOne.value === null) {
return null
}
return currentEditMeta.value?.formConfig || []
})
function setCurrentEditOne(data: any) {
currentEditOne.value = data
}
function changeCurrentEditStatus(status: string) {
currentEditStatus.value = status
}
return {
currentEditOne,
currentEditKey,
currentEditStatus,
moduleConfig,
formConfigList,
currentEditMeta,
setCurrentEditOne,
changeCurrentEditStatus
}
}
function usePageEdit(
{
schema,
questionDataList
}: {
schema: any
questionDataList: Ref<any[]>
},
updateTime: () => void
) {
const pageConf = computed(() => schema.pageConf)
const pageEditOne = computed(() => schema.pageEditOne)
const isFinallyPage = computed(() => {
return pageEditOne.value === pageConf.value.length
})
const pageCount = computed(() => pageConf.value.length || 0)
const pageQuestionData = computed(() => {
return getPageQuestionData(pageEditOne.value)
})
const getPageQuestionData = (index: number) => {
const { startIndex, endIndex } = getSorter(index)
return filterQuestionPreviewData(questionDataList.value).slice(startIndex, endIndex)
}
const getSorter = (index?: number) => {
let startIndex = 0
const newPageEditOne = index || pageEditOne.value
const endIndex = pageConf.value[newPageEditOne - 1]
for (let index = 0; index < pageConf.value.length; index++) {
const item = pageConf.value[index]
if (newPageEditOne - 1 == index) {
break
}
startIndex += item
}
return {
startIndex,
endIndex: startIndex + endIndex
}
}
const addPage = () => {
schema.pageConf.push(1)
}
const updatePageEditOne = (index: number) => {
schema.pageEditOne = index
}
const deletePage = (index: number) => {
if (pageConf.value.length <= 1) return
const { startIndex, endIndex } = getSorter(index)
const newQuestionList = _cloneDeep(questionDataList.value)
const deleteFields = newQuestionList
.slice(startIndex, endIndex - startIndex)
.map((i) => i.field)
// 删除分页判断题目是否存在逻辑关联
const hasLogic = deleteFields.filter((field) => {
const { hasShowLogic } = useShowLogicInfo(field)
const { hasJumpLogic } = useJumpLogicInfo(field)
return hasShowLogic || hasJumpLogic
})
if (hasLogic.length) {
ElMessageBox.alert('该分页下有题目被显示或跳转逻辑关联,请先清除', '提示', {
confirmButtonText: '确定',
type: 'warning'
})
return
}
updatePageEditOne(1)
newQuestionList.splice(startIndex, endIndex - startIndex)
schema.pageConf.splice(index - 1, 1)
questionDataList.value = newQuestionList
updateTime()
}
const swapArrayRanges = (index: number, range: number) => {
const { startIndex: start1, endIndex: end1 } = getSorter(index)
const { startIndex: start2, endIndex: end2 } = getSorter(range)
const newQuestion = _cloneDeep(questionDataList.value)
const range1 = newQuestion.slice(start1, end1)
const range2 = newQuestion.slice(start2, end2)
newQuestion.splice(start1, range1.length, ...range2)
newQuestion.splice(start2, range2.length, ...range1)
questionDataList.value = newQuestion
const rangeCount = schema.pageConf[range - 1]
schema.pageConf[range - 1] = schema.pageConf[index - 1]
schema.pageConf[index - 1] = rangeCount
updateTime()
}
const copyPage = (index: number) => {
const newQuestionList = _cloneDeep(getPageQuestionData(index))
newQuestionList.forEach((item) => {
item.field = getNewField(questionDataList.value.map((item) => item.field))
})
schema.pageConf.splice(index, 0, newQuestionList.length)
const { endIndex } = getSorter(index)
questionDataList.value.splice(endIndex, 0, ...newQuestionList)
updateTime()
}
const pageOperations = (type: string) => {
const count = pageConf.value[pageEditOne.value - 1]
if (type == 'add') {
if (count != undefined) {
schema.pageConf[pageEditOne.value - 1] = count + 1
}
return
}
if (type == 'remove') {
if (count) {
schema.pageConf[pageEditOne.value - 1] = count - 1
}
}
}
const setPage = (data: Array<number>) => {
for (let index = 0; index < pageConf.value.length; index++) {
const newIndex = data[index]
const oldIndex = pageConf.value[index]
if (newIndex != oldIndex) {
schema.pageConf[index] = newIndex
}
}
}
return {
pageEditOne,
pageConf,
isFinallyPage,
pageCount,
pageQuestionData,
getSorter,
updatePageEditOne,
deletePage,
addPage,
copyPage,
getPageQuestionData,
pageOperations,
swapArrayRanges,
setPage
}
}
function useLogicEngine(schema: any) {
const logicConf = toRef(schema, 'logicConf')
const showLogicEngine = ref()
const jumpLogicEngine = ref()
function initShowLogicEngine() {
showLogicEngine.value = new RuleBuild().fromJson(logicConf.value?.showLogicConf)
}
function initJumpLogicEngine() {
jumpLogicEngine.value = new RuleBuild().fromJson(logicConf.value?.jumpLogicConf)
}
return {
showLogicEngine,
jumpLogicEngine,
initShowLogicEngine,
initJumpLogicEngine
}
}
type IBannerItem = {
name: string
key: string
list: Array<Object>
}
type IBannerList = Record<string, IBannerItem>
export const useEditStore = defineStore('edit', () => {
const bannerList: Ref<IBannerList> = ref({})
const fetchBannerData = async () => {
const res: any = await getBannerData()
if (res.code === CODE_MAP.SUCCESS) {
bannerList.value = res.data
}
}
const cooperPermissions = ref(Object.values(SurveyPermissions))
const fetchCooperPermissions = async (id: string) => {
const res: any = await getCollaboratorPermissions(id)
if (res.code === CODE_MAP.SUCCESS) {
cooperPermissions.value = res.data.permissions
}
}
const schemaUpdateTime = ref(Date.now())
function updateTime() {
schemaUpdateTime.value = Date.now()
}
const surveyId = ref('')
function setSurveyId(id: string) {
surveyId.value = id
}
// 初始化schem相关
// 初始化问卷内容
const {
schema,
sessionId,
@ -539,45 +32,45 @@ export const useEditStore = defineStore('edit', () => {
} = useInitializeSchema(surveyId, () => {
editGlobalBaseConf.initCounts()
})
function changeSchema({ key, value }: { key: string; value: any }) {
_set(schema, key, value)
updateTime()
async function init() {
const { metaData } = schema
if (!metaData || (metaData as any)?._id !== surveyId.value) {
await Promise.all([getSchemaFromRemote(), initSessionId()])
}
currentEditOne.value = null
currentEditStatus.value = 'Success'
}
function changeThemePreset(presets: any) {
Object.keys(presets).forEach((key) => {
_set(schema, key, presets[key])
})
// 问卷协作权限
const cooperPermissions = ref(Object.values(SurveyPermissions))
const fetchCooperPermissions = async (id: string) => {
const res: any = await getCollaboratorPermissions(id)
if (res.code === CODE_MAP.SUCCESS) {
cooperPermissions.value = res.data.permissions
}
return res.data.permissions
}
// 问卷题目列表
const questionDataList = toRef(schema, 'questionDataList')
const editGlobalBaseConf = useEditGlobalBaseConf(questionDataList, updateTime)
function setQuestionDataList(data: any) {
schema.questionDataList = data
}
// 整卷配置
const editGlobalBaseConf = useBaseConfig(questionDataList, updateTime)
const createNewQuestion = ({ type }: { type: QUESTION_TYPE }) => {
const fields = questionDataList.value.map((item: any) => item.field)
const newQuestion = getQuestionByType(type, fields)
newQuestion.title = newQuestion.title = `标题${newQuestionIndex.value + 1}`
if (type === QUESTION_TYPE.VOTE) {
newQuestion.innerType = QUESTION_TYPE.RADIO
}
return newQuestion
}
// 题目大纲移动题目顺序
const compareQuestionSeq = (val: Array<any>) => {
const newSeq: Array<string> = []
const oldSeq: Array<string> = []
let status = false
val.map((v) => {
for (const v of val) {
newSeq.push(v.field)
})
;(questionDataList.value as Array<any>).map((v) => {
}
for (const v of questionDataList.value as Array<any>) {
oldSeq.push(v.field)
})
}
for (let index = 0; index < newSeq.length; index++) {
if (newSeq[index] !== oldSeq[index]) {
status = true
@ -589,40 +82,38 @@ export const useEditStore = defineStore('edit', () => {
}
}
const newQuestionIndex = computed(() => {
if (_isNumber(currentEditOne.value)) {
return currentEditOne.value + 1
} else {
const pageConf = schema.pageConf
const questCount = pageConf[schema.pageEditOne - 1]
// 画布区域移动题目顺序
function moveQuestionDataList(data: any) {
const { startIndex, endIndex } = getSorter()
if (!questCount) {
return startIndex
const newData = [
...questionDataList.value.slice(0, startIndex),
...data,
...questionDataList.value.slice(endIndex)
]
const countTotal: number = (schema.pageConf as Array<number>).reduce(
(v: number, i: number) => v + i
)
if (countTotal != newData.length) {
schema.pageConf[pageEditOne.value - 1] = (schema.pageConf[pageEditOne.value - 1] + 1) as never
}
return endIndex
setQuestionDataList(newData)
}
// 问卷schema更新
const schemaUpdateTime = ref(Date.now())
function updateTime() {
schemaUpdateTime.value = Date.now()
}
function changeSchema({ key, value }: { key: string; value: any }) {
_set(schema, key, value)
updateTime()
}
function changeThemePreset(presets: any) {
Object.keys(presets).forEach((key) => {
_set(schema, key, presets[key])
})
// 当前编辑题目
const {
currentEditOne,
currentEditKey,
currentEditStatus,
moduleConfig,
formConfigList,
currentEditMeta,
setCurrentEditOne,
changeCurrentEditStatus
} = useCurrentEdit({ schema, questionDataList })
// 初始化问卷
async function init() {
const { metaData } = schema
if (!metaData || (metaData as any)?._id !== surveyId.value) {
await Promise.all([getSchemaFromRemote(), initSessionId()])
}
currentEditOne.value = null
currentEditStatus.value = 'Success'
}
// 分页相关
@ -642,42 +133,60 @@ export const useEditStore = defineStore('edit', () => {
swapArrayRanges,
setPage
} = usePageEdit({ schema, questionDataList }, updateTime)
// 增加新分页时新增题目
const createNewQuestion = ({ type }: { type: QUESTION_TYPE }) => {
const fields = questionDataList.value.map((item: any) => item.field)
const newQuestion = getQuestionByType(type, fields)
newQuestion.title = newQuestion.title = `标题${newQuestionIndex.value + 1}`
if (type === QUESTION_TYPE.VOTE) {
newQuestion.innerType = QUESTION_TYPE.RADIO
}
return newQuestion
}
// 问卷列表相关操作
const { copyQuestion, addQuestion, deleteQuestion, moveQuestion } = useQuestionDataListOperations(
{
// 当前编辑的题目信息
const {
currentEditOne,
currentEditKey,
currentEditStatus,
moduleConfig,
formConfigList,
currentEditMeta,
setCurrentEditOne,
changeCurrentEditStatus
} = useCurrentEdit({ schema, questionDataList })
const newQuestionIndex = computed(() => {
if (_isNumber(currentEditOne.value)) {
return currentEditOne.value + 1
} else {
const pageConf = schema.pageConf
const questCount = pageConf[schema.pageEditOne - 1]
const { startIndex, endIndex } = getSorter()
if (!questCount) {
return startIndex
}
return endIndex
}
})
// 题目操作:增删改
const { copyQuestion, addQuestion, deleteQuestion, moveQuestion } = useQuestionData({
questionDataList,
updateTime,
pageOperations,
updateCounts: editGlobalBaseConf.updateCounts
}
)
function moveQuestionDataList(data: any) {
const { startIndex, endIndex } = getSorter()
const newData = [
...questionDataList.value.slice(0, startIndex),
...data,
...questionDataList.value.slice(endIndex)
]
const countTotal: number = (schema.pageConf as Array<number>).reduce(
(v: number, i: number) => v + i
)
if (countTotal != newData.length) {
schema.pageConf[pageEditOne.value - 1] = (schema.pageConf[pageEditOne.value - 1] + 1) as never
}
setQuestionDataList(newData)
}
})
return {
editGlobalBaseConf,
surveyId,
sessionId,
setSurveyId,
bannerList,
fetchBannerData,
cooperPermissions,
fetchCooperPermissions,
currentEditOne,
moduleConfig,
formConfigList,
@ -687,6 +196,7 @@ export const useEditStore = defineStore('edit', () => {
newQuestionIndex,
setCurrentEditOne,
changeCurrentEditStatus,
pageEditOne,
pageConf,
isFinallyPage,
@ -700,6 +210,7 @@ export const useEditStore = defineStore('edit', () => {
copyPage,
swapArrayRanges,
setPage,
schemaUpdateTime,
schema,
questionDataList,
@ -715,6 +226,7 @@ export const useEditStore = defineStore('edit', () => {
changeSchema,
changeThemePreset,
compareQuestionSeq,
showLogicEngine,
jumpLogicEngine
}

View File

@ -1,30 +1,29 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
import localstorage from '@/common/localstorage'
import { getUserInfo, setUserInfo, clearUserInfo } from '@/management/utils/storage'
type IUserInfo = {
username: string
token: string
}
const USER_INFO_KEY = 'surveyUserInfo'
export const useUserStore = defineStore('user', () => {
const userInfo = ref<IUserInfo | null>({
username: '',
token: ''
})
const hasLogined = ref(false)
const hasLogin = ref(false)
const loginTime = ref<number | null>(null)
const initialized = ref(false)
const init = () => {
const localData = localstorage.getItem(USER_INFO_KEY)
const localData = getUserInfo()
if (localData) {
try {
const { userInfo: info, loginTime: time } = localData as any
if (Date.now() - time > 7 * 3600000) {
localstorage.removeItem(USER_INFO_KEY)
clearUserInfo()
} else {
login(info)
}
@ -36,18 +35,18 @@ export const useUserStore = defineStore('user', () => {
}
const login = (data: IUserInfo) => {
userInfo.value = data
hasLogined.value = true
hasLogin.value = true
loginTime.value = Date.now()
localstorage.setItem(USER_INFO_KEY, {
setUserInfo({
userInfo: data,
loginTime: loginTime
})
}
const logout = () => {
userInfo.value = null
hasLogined.value = false
localstorage.removeItem(USER_INFO_KEY)
hasLogin.value = false
clearUserInfo()
}
return { userInfo, hasLogined, loginTime, initialized, init, login, logout }
return { userInfo, hasLogin, loginTime, initialized, init, login, logout }
})

View File

@ -0,0 +1,22 @@
export const getUserInfo = (): any => {
try {
return JSON.parse(localStorage.getItem('surveyUserInfo') as string) || {}
} catch (e) {
console.log(e)
}
return {}
}
export const setUserInfo = (params: { userInfo: any; loginTime: any }) => {
const { userInfo, loginTime } = params
localStorage.setItem(
'surveyUserInfo',
JSON.stringify({
userInfo,
loginTime
})
)
}
export const clearUserInfo = () => localStorage.removeItem('surveyUserInfo')

View File

@ -6,7 +6,7 @@ export default defineComponent({
props: {
uiTarget: {
type: String,
default: 'radio'
default: 'input'
},
customClass: {
type: String,

View File

@ -2,7 +2,7 @@
<div class="mask" v-if="visible">
<div class="box">
<div class="title">{{ title }}</div>
<div class="btn" @click="handleConfirm">{{ btnText }}</div>
<div class="btn btn-primary btn-base" @click="handleConfirm">{{ btnText }}</div>
</div>
</div>
</template>
@ -34,18 +34,8 @@ const handleConfirm = () => {
<style lang="scss" scoped>
@import url('../styles/dialog.scss');
.btn {
display: inline-block;
text-align: center;
width: 100%;
height: 42px;
.btn-base {
// display: inline-block;
margin-top: 20px;
line-height: 42px;
border-radius: 2px;
font-size: 16px;
background-color: #4a4c5b;
border: 1px solid #4a4c5b;
color: #fff;
cursor: pointer;
}
</style>

View File

@ -1,74 +1,115 @@
<template>
<div class="mask" v-show="visible">
<div class="mask" v-show="isVisible">
<div class="box">
<div class="head-wrapper">
<div class="title">{{ title }}</div>
<div class="btn-box">
<div class="btn cancel" @click="handleCancel">{{ cancelBtnText }}</div>
<div class="btn confirm" @click="handleConfirm">{{ confirmBtnText }}</div>
<div v-if="tips" class="tips">{{ tips }}</div>
</div>
<div class="body-wrapper">
<div v-if="formValues && formValues.length" class="body-content">
<div class="form-item" v-for="item in formValues" :key="item.key">
<div class="input-wrapper">
<input
class="input-inner"
:name="item.key"
v-model="item.value"
:placeholder="item.placeholder"
/>
</div>
</div>
</div>
<slot name="body"></slot>
</div>
<div class="btn-wrapper">
<div class="btn btn-shallow btn-base" v-if="cancel" @click="handleCancel">
{{ cancelBtnText }}
</div>
<div class="btn btn-primary btn-base" @click="handleConfirm">{{ confirmBtnText }}</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref, reactive } from 'vue'
type FormItem = {
key: string
value: string
placeholder?: string
}
interface Props {
visible?: boolean
cancel?: boolean //
cancelBtnText?: string
confirmBtnText?: string
title?: string
tips?: string
bodyContent?: FormItem[]
autoClose?: boolean //
}
interface Emit {
(ev: 'confirm', callback: () => void): void
(ev: 'confirm', data, callback: () => void): void
(ev: 'close'): void
}
const emit = defineEmits<Emit>()
withDefaults(defineProps<Props>(), {
const props = withDefaults(defineProps<Props>(), {
visible: false,
cancel: true,
cancelBtnText: '取消',
confirmBtnText: '确定',
title: ''
title: '',
tips: '',
autoClose: true
})
const isVisible = ref(props.visible)
const formValues = reactive(props.bodyContent || [])
const handleConfirm = () => {
emit('confirm', () => {
emit('close')
const data = {}
formValues.forEach((item) => {
data[item.key] = item.value
})
emit('confirm', data, handleCancel)
props.autoClose && handleCancel()
}
const handleCancel = () => {
isVisible.value = false
emit('close')
}
</script>
<style lang="scss" scoped>
@import url('../styles/dialog.scss');
.btn-box {
padding: 20px 0 0;
.tips {
text-align: center;
font-size: 0.24rem;
color: #92949d;
margin-top: 0.15rem;
margin-bottom: 0.4rem;
}
.btn-wrapper {
margin-top: 20px;
display: flex;
align-items: center;
justify-content: space-between;
flex: 1;
.btn {
width: 48%;
font-size: 0.28rem;
border-radius: 0.04rem;
text-align: center;
padding: 0.16rem 0;
line-height: 0.4rem;
cursor: pointer;
.btn-base {
width: 100%;
margin-right: 0.2rem;
&.cancel {
background: #fff;
color: #92949d;
border: 1px solid #e3e4e8;
}
&.confirm {
background-color: #4a4c5b;
border: 1px solid #4a4c5b;
color: #fff;
&:last-child {
margin-right: 0;
}
}
}

View File

@ -32,7 +32,6 @@ const { percent } = useProgressBar()
</script>
<style lang="scss" scoped>
.progress-outer {
z-index: 10000;
position: relative;
overflow: hidden;
.progress-inner-wrapper-pc {

View File

@ -1,6 +1,6 @@
<template>
<QuestionRuleContainer
v-if="visibily"
v-if="visibility"
:moduleConfig="questionConfig"
:indexNumber="indexNumber"
:showTitle="true"
@ -9,18 +9,21 @@
></QuestionRuleContainer>
</template>
<script setup>
import { unref, computed, watch, ref } from 'vue'
import { unref, computed, watch } from 'vue'
import { storeToRefs } from 'pinia'
import QuestionRuleContainer from '../../materials/questions/QuestionRuleContainer'
import { useVoteMap } from '@/render/hooks/useVoteMap'
import { useShowOthers } from '@/render/hooks/useShowOthers'
import { useShowInput } from '@/render/hooks/useShowInput'
import { debounce, cloneDeep } from 'lodash-es'
import { useQuestionStore } from '../stores/question'
import { useSurveyStore } from '../stores/survey'
import { FORMDATA_SUFFIX, SUBMIT_FLAG } from '../utils/constant'
import { NORMAL_CHOICES, RATES, QUESTION_TYPE } from '@/common/typeEnum.ts'
import localstorage from '@/common/localstorage'
import QuestionRuleContainer from '@/materials/questions/QuestionRuleContainer'
import { useVoteMap } from '@/render/hooks/useVoteMap'
import { useOthersData } from '@/render/hooks/useOthersData'
import { useInputData } from '@/render/hooks/useInputData'
import { useQuestionStore } from '@/render/stores/question'
import { useSurveyStore } from '@/render/stores/survey'
import { setSurveyData, clearSurveyData, setSurveySubmit } from '@/render/utils/storage'
const props = defineProps({
indexNumber: {
@ -41,27 +44,28 @@ const surveyStore = useSurveyStore()
const formValues = computed(() => {
return surveyStore.formValues
})
const { showLogicEngine } = storeToRefs(surveyStore)
const { showLogicEngine, baseConf } = storeToRefs(surveyStore)
const { changeField, changeIndex, needHideFields } = storeToRefs(questionStore)
//
const questionConfig = computed(() => {
let moduleConfig = props.moduleConfig
const { type, field, options = [], ...rest } = cloneDeep(moduleConfig)
let alloptions = options
let allOptions = options
if (type === QUESTION_TYPE.VOTE) {
//
const { options, voteTotal } = useVoteMap(field)
const voteOptions = unref(options)
alloptions = alloptions.map((obj, index) => Object.assign(obj, voteOptions[index]))
allOptions = allOptions.map((obj, index) => Object.assign(obj, voteOptions[index]))
moduleConfig.voteTotal = unref(voteTotal)
}
if (NORMAL_CHOICES.includes(type) && options.some((option) => option.others)) {
//
let { options, othersValue } = useShowOthers(field)
let { options, othersValue } = useOthersData(field)
const othersOptions = unref(options)
alloptions = alloptions.map((obj, index) => Object.assign(obj, othersOptions[index]))
allOptions = allOptions.map((obj, index) => Object.assign(obj, othersOptions[index]))
moduleConfig.othersValue = unref(othersValue)
}
@ -72,34 +76,79 @@ const questionConfig = computed(() => {
0
) {
//
let { rangeConfig, othersValue } = useShowInput(field)
let { rangeConfig, othersValue } = useInputData(field)
moduleConfig.rangeConfig = unref(rangeConfig)
moduleConfig.othersValue = unref(othersValue)
}
return {
...moduleConfig,
options: alloptions,
options: allOptions,
value: formValues.value[props.moduleConfig.field]
}
})
const logicshow = computed(() => {
const updateFormData = (value) => {
const key = props.moduleConfig.field
const formData = cloneDeep(formValues.value)
if (key in formData) {
formData[key] = value
}
return formData
}
const handleChange = (data) => {
emit('change', data)
// questionConfig
if (props.moduleConfig.type === QUESTION_TYPE.VOTE) {
questionStore.updateVoteData(data)
}
processJumpSkip()
//
if (baseConf.value.fillAnswer) {
const formData = updateFormData(data.value)
storageAnswer(formData)
}
}
const handleInput = debounce((e) => {
//
if (baseConf.value.fillAnswer) {
const formData = updateFormData(e.target.value)
storageAnswer(formData)
}
}, 500)
//
const storageAnswer = (formData) => {
const id = surveyStore.surveyPath
clearSurveyData(id)
setSurveyData(id, formData)
setSurveySubmit(id, 0)
}
/** 问卷逻辑处理 */
//
const logicShow = computed(() => {
// computedmatch
const result = showLogicEngine.value.match(props.moduleConfig.field, 'question', formValues.value)
return result === undefined ? true : result
})
const logicskip = computed(() => {
//
const logicSkip = computed(() => {
return needHideFields.value.includes(props.moduleConfig.field)
})
const visibily = computed(() => {
return logicshow.value && !logicskip.value
const visibility = computed(() => {
return logicShow.value && !logicSkip.value
})
// abbcbc
watch(
() => visibily.value,
() => visibility.value,
(newVal, oldVal) => {
const { field, type, innerType } = props.moduleConfig
if (!newVal && oldVal) {
@ -110,46 +159,20 @@ watch(
if (type === QUESTION_TYPE.CHECKBOX || innerType === QUESTION_TYPE.CHECKBOX) {
value = value ? [value] : []
}
const data = {
key: field,
value: value
}
surveyStore.changeData(data)
processJumpSkip()
}
}
}
)
const handleChange = (data) => {
emit('change', data)
//
if (props.moduleConfig.type === QUESTION_TYPE.VOTE) {
questionStore.updateVoteData(data)
}
processJumpSkip()
valueTemp.value = data.value
debounceStorageSave()
}
const valueTemp = ref()
const handleInput = (e) => {
valueTemp.value = e.target.value
debounceStorageSave()
}
const debounceStorageSave = debounce(() => {
let data = {
key: props.moduleConfig.field,
value: valueTemp.value
}
const formData = cloneDeep(formValues.value)
let { key, value } = data
if (key in formData) {
formData[key] = value
}
localStorageSave(formData)
}, 500)
//
const processJumpSkip = () => {
const targetResult = surveyStore.jumpLogicEngine
.getResultsByField(changeField.value, surveyStore.formValues)
@ -190,9 +213,5 @@ const processJumpSkip = () => {
.map((item) => item.field)
questionStore.addNeedHideFields(skipKey)
}
const localStorageSave = (formData) => {
localstorage.removeItem(surveyStore.surveyPath + FORMDATA_SUFFIX)
localstorage.setItem(surveyStore.surveyPath + FORMDATA_SUFFIX, formData)
localstorage.setItem(SUBMIT_FLAG, false)
}
/** 问卷逻辑处理 */
</script>

View File

@ -0,0 +1,23 @@
<template>
<ConfirmDialog
:title="fillAnswer ? '是否继续上次填写的内容?' : '是否继续上次提交的内容?'"
@confirm="handleSubmit"
/>
</template>
<script setup>
import { useSurveyStore } from '../../stores/survey'
import { getSurveyData, getSurveySubmit } from '../../utils/storage'
import ConfirmDialog from '../ConfirmDialog.vue'
const surveyStore = useSurveyStore()
const { fillAnswer } = surveyStore.baseConf || {}
const handleSubmit = () => {
const surveyPath = surveyStore.surveyPath
const data = getSurveyData(surveyPath)
surveyStore.setFormValues(data)
}
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,80 @@
<template>
<ConfirmDialog
title="验证"
:tips="whitelistTip"
:bodyContent="bodyContent"
:autoClose="false"
:cancel="false"
@confirm="handleSubmit"
/>
</template>
<script setup lang="ts">
import { computed } from 'vue'
import { validate } from '../../api/survey'
import { useSurveyStore } from '../../stores/survey'
import ConfirmDialog from '../ConfirmDialog.vue'
interface Emit {
(ev: 'confirm'): void
}
const emit = defineEmits<Emit>()
const surveyStore = useSurveyStore()
const { passwordSwitch, whitelistType, memberType, whitelistTip } = surveyStore.baseConf || {}
const bodyContent = computed(() => {
const content = []
if (passwordSwitch) {
content.push({
key: 'password',
value: '',
placeholder: '请输入访问密码'
})
}
if (whitelistType && whitelistType != 'ALL') {
let placeholder = ''
if (whitelistType == 'MEMBER') {
placeholder = '请输入用户名'
}
if (memberType == 'MOBILE') {
placeholder = '请输入手机号'
}
if (memberType == 'EMAIL') {
placeholder = '请输入邮箱'
}
content.push({
key: 'whitelist',
value: '',
placeholder
})
}
return content
})
const handleSubmit = async (data, close) => {
const params = {
surveyPath: surveyStore.surveyPath
}
if (data.whitelist) {
params.whitelist = data.whitelist
}
if (data.password) {
params.password = data.password
}
const res = await validate(params)
if (res.code != 200) {
ElMessage.error(res.errmsg || '验证失败')
return
}
close()
emit('confirm')
surveyStore.setWhiteData(params)
}
</script>
<style lang="scss" scoped></style>

View File

@ -0,0 +1,73 @@
<template>
<div>
<WhiteListDialog
v-if="showWhiteList"
:visible="showWhiteList"
ref="whiteListRef"
@confirm="whiteListConfirm"
/>
<FillDataDialog v-if="showFillData" :visible="showFillData" ref="fillDataRef" />
</div>
</template>
<script setup lang="ts">
import { watch, computed, ref } from 'vue'
import { useSurveyStore } from '../../stores/survey'
import { getSurveyData, getSurveySubmit } from '../../utils/storage'
import WhiteListDialog from './WhiteListDialog.vue'
import FillDataDialog from './FillDataDialog.vue'
const surveyStore = useSurveyStore()
const baseConf = computed(() => surveyStore?.baseConf || {})
const showWhiteList = ref(false)
const showFillData = ref(false)
watch(
() => baseConf.value,
() => {
const { passwordSwitch, whitelistType } = baseConf.value || {}
// or
if ((whitelistType && whitelistType != 'ALL') || passwordSwitch) {
showWhiteList.value = true
return
}
whiteListConfirm()
}
)
const whiteListConfirm = () => {
const { surveyPath } = surveyStore || {}
const { fillAnswer, fillSubmitAnswer } = baseConf.value || {}
// or
const localData = getSurveyData(surveyPath)
const isSubmit = getSurveySubmit(surveyPath)
if ((fillAnswer || (fillSubmitAnswer && isSubmit)) && localData) {
showFillData.value = true
}
}
// export default {
// components: { DialogComponent },
// data() {
// return {
// showWhiteListDialog: false,
// showFillDataDialog: false,
// };
// },
// methods: {
// startValidation() {
// this.showDialog1 = true
// await this.$refs.dialog1.validate() //
// this.showDialog1 = false
// this.showDialog2 = true
// await this.$refs.dialog2.validate() //
// this.showDialog2 = false
// }
// }
// };
</script>

View File

@ -1,147 +0,0 @@
<template>
<el-dialog
v-model="whiteVisible"
title="验证"
:show-close="false"
class="verify-white-wrap"
width="315"
:close-on-press-escape="false"
:close-on-click-modal="false"
align-center
>
<template #header>
<div class="verify-white-head">
<div class="verify-white-title">验证</div>
<div v-if="whitelistTip" class="verify-white-tips">{{ whitelistTip }}</div>
</div>
</template>
<div class="verify-white-body">
<el-input
v-if="isPwd"
v-model="state.password"
class="wd255 mb16"
placeholder="请输入6位字符串类型访问密码"
/>
<el-input
v-if="isValue"
v-model="state.value"
class="wd255 mb16"
:placeholder="placeholder"
/>
<div class="submit-btn" @click="handleSubmit">验证并开始答题</div>
</div>
</el-dialog>
</template>
<script setup>
import { ref, reactive, computed, watch } from 'vue'
import { ElMessage } from 'element-plus'
import 'element-plus/theme-chalk/src/message.scss'
import { validate } from '../api/survey'
import { useSurveyStore } from '../stores/survey'
const whiteVisible = ref(false)
const surveyStore = useSurveyStore()
const state = reactive({
password: '',
value: '',
is_submit: false
})
const baseConf = computed(() => surveyStore?.baseConf || {})
const isPwd = computed(() => baseConf.value.passwordSwitch)
const whitelistType = computed(() => baseConf.value.whitelistType)
const memberType = computed(() => baseConf.value.memberType)
const whitelistTip = computed(() => baseConf.value.whitelistTip)
const surveyPath = computed(() => surveyStore?.surveyPath || '')
const isValue = computed(() => {
if (!whitelistType.value) return false
return whitelistType.value != 'ALL'
})
const placeholder = computed(() => {
if (whitelistType.value == 'MEMBER') {
return '请输入用户名'
}
if (memberType.value == 'MOBILE') {
return '请输入手机号'
}
if (memberType.value == 'EMAIL') {
return '请输入邮箱'
}
return ''
})
const handleSubmit = async () => {
if (state.is_submit) return
const params = {
surveyPath: surveyPath.value
}
if (isValue.value) {
params.whitelist = state.value
}
if (isPwd.value) {
params.password = state.password
}
const res = await validate(params)
if (res.code != 200) {
ElMessage.error(res.errmsg || '验证失败')
return
}
whiteVisible.value = false
surveyStore.setWhiteData(params)
}
watch(
() => baseConf.value,
() => {
if (whiteVisible.value) return
if (isValue.value || isPwd.value) {
whiteVisible.value = true
}
}
)
</script>
<style lang="scss" scoped>
.verify-white-wrap {
.verify-white-body {
padding: 0 14px;
}
.verify-white-head {
padding: 0 14px;
margin-bottom: 8px;
margin-top: 2px;
}
.mb16 {
margin-bottom: 16px;
}
.verify-white-tips {
text-align: center;
margin-top: 8px;
font-size: 14px;
color: #92949d;
}
.verify-white-title {
font-size: 16px;
color: #292a36;
font-weight: 500;
display: flex;
align-items: center;
justify-content: center;
}
.submit-btn {
background: #faa600;
border-radius: 2px;
width: 255px;
height: 32px;
color: #fff;
display: flex;
align-items: center;
justify-content: center;
margin-top: 4px;
margin-bottom: 14px;
}
}
</style>

View File

@ -1,7 +1,7 @@
import { useQuestionStore } from '../stores/question'
import { useSurveyStore } from '../stores/survey'
export const useShowInput = (questionKey) => {
export const useInputData = (questionKey) => {
const questionStore = useQuestionStore()
const surveyStore = useSurveyStore()
const formValues = surveyStore.formValues

View File

@ -1,7 +1,7 @@
import { useQuestionStore } from '../stores/question'
import { useSurveyStore } from '../stores/survey'
export const useShowOthers = (questionKey) => {
export const useOthersData = (questionKey) => {
const questionStore = useQuestionStore()
const surveyStore = useSurveyStore()
const formValues = surveyStore.formValues

View File

@ -38,7 +38,7 @@ export const useProgressBar = () => {
const percent = computed(() => {
const { fillCount, topicCount } = surveySchedule.value
return Math.floor((100 / topicCount) * fillCount) + '%'
return Math.floor((100 / topicCount) * fillCount) || 0 + '%'
})
return { surveySchedule, percent }

View File

@ -1,9 +1,7 @@
<template>
<div class="container">
<img src="/imgs/nodata.webp" alt="" class="ui-404" />
<div class="ui-title">
<div class="ui-maintitle">问卷找不到啦~</div>
</div>
<div class="ui-title">问卷找不到啦~</div>
</div>
</template>

View File

@ -2,10 +2,15 @@
<router-view></router-view>
</template>
<script setup lang="ts">
import { watch } from 'vue'
import { watch, onMounted } from 'vue'
import { useRoute } from 'vue-router'
import { getPublishedSurveyInfo, getPreviewSchema } from '../api/survey'
import AlertDialog from '../components/AlertDialog.vue'
import { useSurveyStore } from '../stores/survey'
import useCommandComponent from '../hooks/useCommandComponent'
const route = useRoute()
const surveyStore = useSurveyStore()
watch(
() => route.query.t,
@ -13,4 +18,65 @@ watch(
location.reload()
}
)
onMounted(() => {
const surveyId = route.params.surveyId
console.log({ surveyId })
surveyStore.setSurveyPath(surveyId)
getDetail(surveyId as string)
})
const loadData = (res: any, surveyPath: string) => {
if (res.code === 200) {
const data = res.data
const {
bannerConf,
baseConf,
bottomConf,
dataConf,
skinConf,
submitConf,
logicConf,
pageConf
} = data.code
const questionData = {
bannerConf,
baseConf,
bottomConf,
dataConf,
skinConf,
submitConf,
pageConf
}
if (!pageConf || pageConf?.length == 0) {
questionData.pageConf = [dataConf.dataList.length]
}
document.title = data.title
surveyStore.setSurveyPath(surveyPath)
surveyStore.initSurvey(questionData)
surveyStore.initShowLogicEngine(logicConf?.showLogicConf)
surveyStore.initJumpLogicEngine(logicConf?.jumpLogicConf)
} else {
throw new Error(res.errmsg)
}
}
const getDetail = async (surveyPath: string) => {
const alert = useCommandComponent(AlertDialog)
try {
if (surveyPath.length > 8) {
const res: any = await getPreviewSchema({ surveyPath })
loadData(res, surveyPath)
} else {
const res: any = await getPublishedSurveyInfo({ surveyPath })
loadData(res, surveyPath)
surveyStore.getEncryptInfo()
}
} catch (error: any) {
console.log(error)
alert({ title: error.message || '获取问卷失败' })
}
}
</script>

View File

@ -16,31 +16,36 @@
></SubmitButton>
</div>
<LogoIcon :logo-conf="logoConf" :readonly="true" />
<VerifyWhiteDialog />
<VerifyDialog />
</div>
</div>
</template>
<script setup lang="ts">
import { computed, ref, onMounted } from 'vue'
import { computed, ref } from 'vue'
import { storeToRefs } from 'pinia'
import { useRoute, useRouter } from 'vue-router'
import { useRouter } from 'vue-router'
// @ts-ignore
import communalLoader from '@materials/communals/communalLoader.js'
import useCommandComponent from '../hooks/useCommandComponent'
import MainRenderer from '../components/MainRenderer.vue'
import AlertDialog from '../components/AlertDialog.vue'
import VerifyWhiteDialog from '../components/VerifyWhiteDialog.vue'
import ConfirmDialog from '../components/ConfirmDialog.vue'
import VerifyDialog from '../components/VerifyDialog/index.vue'
import ProgressBar from '../components/ProgressBar.vue'
import { useSurveyStore } from '../stores/survey'
import { useQuestionStore } from '../stores/question'
import { submitForm } from '../api/survey'
import encrypt from '../utils/encrypt'
import useCommandComponent from '../hooks/useCommandComponent'
import { getPublishedSurveyInfo, getPreviewSchema } from '../api/survey'
import { FORMDATA_SUFFIX, SUBMIT_FLAG } from '@/render/utils/constant'
import localstorage from '@/common/localstorage'
import {
clearSurveyData,
setSurveyData,
clearSurveySubmit,
setSurveySubmit
} from '../utils/storage'
interface Props {
questionInfo?: any
@ -73,76 +78,16 @@ const pageIndex = computed(() => questionStore.pageIndex)
const { bannerConf, submitConf, bottomConf: logoConf, whiteData } = storeToRefs(surveyStore)
const surveyPath = computed(() => surveyStore.surveyPath || '')
const route = useRoute()
onMounted(() => {
const surveyId = route.params.surveyId
console.log({ surveyId })
surveyStore.setSurveyPath(surveyId)
getDetail(surveyId as string)
})
const loadData = (res: any, surveyPath: string) => {
if (res.code === 200) {
const data = res.data
const {
bannerConf,
baseConf,
bottomConf,
dataConf,
skinConf,
submitConf,
logicConf,
pageConf
} = data.code
const questionData = {
bannerConf,
baseConf,
bottomConf,
dataConf,
skinConf,
submitConf,
pageConf
}
if (!pageConf || pageConf?.length == 0) {
questionData.pageConf = [dataConf.dataList.length]
}
document.title = data.title
surveyStore.setSurveyPath(surveyPath)
surveyStore.initSurvey(questionData)
surveyStore.initShowLogicEngine(logicConf?.showLogicConf)
surveyStore.initJumpLogicEngine(logicConf?.jumpLogicConf)
} else {
throw new Error(res.errmsg)
}
}
const getDetail = async (surveyPath: string) => {
const alert = useCommandComponent(AlertDialog)
try {
if (surveyPath.length > 8) {
const res: any = await getPreviewSchema({ surveyPath })
loadData(res, surveyPath)
} else {
const res: any = await getPublishedSurveyInfo({ surveyPath })
loadData(res, surveyPath)
surveyStore.getEncryptInfo()
}
} catch (error: any) {
console.log(error)
alert({ title: error.message || '获取问卷失败' })
}
}
const validate = (cbk: (v: boolean) => void) => {
const validate = (callback: (v: boolean) => void) => {
const index = 0
mainRef.value.$refs.formGroup[index].validate(cbk)
mainRef.value.$refs.formGroup[index].validate(callback)
}
const normalizationRequestBody = () => {
const enterTime = surveyStore.enterTime
const encryptInfo = surveyStore.encryptInfo as any
const formValues = surveyStore.formValues
const baseConf = surveyStore.baseConf
const result: any = {
surveyPath: surveyPath.value,
@ -152,21 +97,24 @@ const normalizationRequestBody = () => {
...whiteData.value
}
//
localstorage.removeItem(surveyPath.value + FORMDATA_SUFFIX)
localstorage.removeItem(SUBMIT_FLAG)
//
let formData: Record<string, any> = Object.assign({}, surveyStore.formValues)
//
if (baseConf.fillSubmitAnswer) {
clearSurveyData(surveyPath.value)
clearSurveySubmit(surveyPath.value)
localstorage.setItem(surveyPath.value + FORMDATA_SUFFIX, formData)
localstorage.setItem(SUBMIT_FLAG, true)
setSurveyData(surveyPath.value, formValues)
setSurveySubmit(surveyPath.value, 1)
}
//
if (encryptInfo?.encryptType) {
result.encryptType = encryptInfo.encryptType
result.data = encrypt[result.encryptType as 'rsa']({
data: result.data,
secretKey: encryptInfo?.data?.secretKey
})
if (encryptInfo?.data?.sessionId) {
result.sessionId = encryptInfo.data.sessionId
}
@ -177,7 +125,7 @@ const normalizationRequestBody = () => {
return result
}
const submitSurver = async () => {
const submitSurvey = async () => {
if (surveyPath.value.length > 8) {
router.push({ name: 'successPage' })
return
@ -209,7 +157,7 @@ const handleSubmit = () => {
title: again_text,
onConfirm: async () => {
try {
submitSurver()
submitSurvey()
} catch (error) {
console.log(error)
} finally {
@ -218,7 +166,7 @@ const handleSubmit = () => {
}
})
} else {
submitSurver()
submitSurvey()
}
}
</script>

View File

@ -30,7 +30,9 @@ import communalLoader from '@materials/communals/communalLoader.js'
const LogoIcon = communalLoader.loadComponent('LogoIcon')
const surveyStore = useSurveyStore()
const logoConf = computed(() => surveyStore?.bottomConf || {})
const logoConf = computed(() => {
return surveyStore?.bottomConf || {}
})
const successMsg = computed(() => {
const msgContent = (surveyStore?.submitConf as any)?.msgContent || {}
return msgContent?.msg_200 || '提交成功'

View File

@ -1,56 +0,0 @@
import ConfirmDialog from '../../components/ConfirmDialog.vue'
import AlertDialog from '../../components/AlertDialog.vue'
import { isFunction as _isFunction } from 'lodash-es'
export default {
install(Vue) {
Vue.prototype.$dialog = {
confirm(options) {
const MyComponent = Vue.extend(ConfirmDialog)
const instance = new MyComponent({
propsData: options
})
const closeConfirm = () => {
if (instance && instance.$el) {
instance.$el.remove()
}
}
instance.$on('cancel', () => {
if (options?.onCancel && _isFunction(options.onCancel)) {
options.onCancel(closeConfirm)
} else {
closeConfirm()
}
})
instance.$on('confirm', () => {
if (options?.onConfirm && _isFunction(options.onConfirm)) {
options.onConfirm(closeConfirm)
}
})
instance.$mount()
document.body.append(instance.$el)
},
alert(options) {
const MyComponent = Vue.extend(AlertDialog)
const instance = new MyComponent({
propsData: options
})
const closeConfirm = () => {
if (instance && instance.$el) {
instance.$el.remove()
}
}
instance.$on('confirm', () => {
if (options?.onConfirm && _isFunction(options.onConfirm)) {
options.onConfirm(closeConfirm)
} else {
closeConfirm()
}
})
instance.$mount()
document.body.append(instance.$el)
}
}
}
}

View File

@ -4,11 +4,11 @@ import { set } from 'lodash-es'
import { useSurveyStore } from '@/render/stores/survey'
import { queryVote } from '@/render/api/survey'
import { QUESTION_TYPE } from '@/common/typeEnum'
import { VOTE_INFO_KEY } from '@/render/utils/constant'
import localstorage from '@/common/localstorage'
import { getVoteData, setVoteData, clearVoteData } from '@/render/utils/storage'
// 投票进度逻辑聚合
const usevVoteMap = (questionData) => {
const useVoteMap = (questionData) => {
const voteMap = ref({})
//初始化投票题的数据
const initVoteData = async () => {
@ -28,16 +28,14 @@ const usevVoteMap = (questionData) => {
return
}
try {
localstorage.removeItem(VOTE_INFO_KEY)
clearVoteData()
const voteRes = await queryVote({
surveyPath,
fieldList: fieldList.join(',')
})
if (voteRes.code === 200) {
localstorage.setItem(VOTE_INFO_KEY, {
...voteRes.data
})
setVoteData(voteRes.data)
setVoteMap(voteRes.data)
}
} catch (error) {
@ -58,25 +56,25 @@ const usevVoteMap = (questionData) => {
const updateVoteData = (data) => {
const { key: questionKey, value: questionVal } = data
// 更新前获取接口缓存在localstorage中的数据
const voteinfo = localstorage.getItem(VOTE_INFO_KEY)
const voteInfo = getVoteData()
const currentQuestion = questionData.value[questionKey]
const options = currentQuestion.options
const voteTotal = voteinfo?.[questionKey]?.total || 0
const voteTotal = voteInfo?.[questionKey]?.total || 0
let totalPayload = {
questionKey,
voteKey: 'total',
voteValue: voteTotal
}
options.forEach((option) => {
const optionhash = option.hash
const voteCount = voteinfo?.[questionKey]?.[optionhash] || 0
const optionHash = option.hash
const voteCount = voteInfo?.[questionKey]?.[optionHash] || 0
// 如果选中值包含该选项对应voteCount 和 voteTotal + 1
if (
Array.isArray(questionVal) ? questionVal.includes(optionhash) : questionVal === optionhash
Array.isArray(questionVal) ? questionVal.includes(optionHash) : questionVal === optionHash
) {
const countPayload = {
questionKey,
voteKey: optionhash,
voteKey: optionHash,
voteValue: voteCount + 1
}
totalPayload.voteValue += 1
@ -84,7 +82,7 @@ const usevVoteMap = (questionData) => {
} else {
const countPayload = {
questionKey,
voteKey: optionhash,
voteKey: optionHash,
voteValue: voteCount
}
updateVoteMapByKey(countPayload)
@ -174,7 +172,7 @@ export const useQuestionStore = defineStore('question', () => {
const setQuestionData = (data) => {
questionData.value = data
}
const { voteMap, setVoteMap, initVoteData, updateVoteData } = usevVoteMap(questionData)
const { voteMap, setVoteMap, initVoteData, updateVoteData } = useVoteMap(questionData)
const changeSelectMoreData = (data) => {
const { key, value, field } = data

View File

@ -1,25 +1,20 @@
import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { defineStore } from 'pinia'
import { cloneDeep, pick } from 'lodash-es'
import { pick } from 'lodash-es'
import moment from 'moment'
// 引入中文
import 'moment/locale/zh-cn'
// 设置中文
import { isMobile as isInMobile } from '@/render/utils/index'
import { getEncryptInfo as getEncryptInfoApi } from '@/render/api/survey'
import { useQuestionStore } from '@/render/stores/question'
import { useErrorInfo } from '@/render/stores/errorInfo'
import { FORMDATA_SUFFIX, SUBMIT_FLAG } from '@/render/utils/constant'
import adapter from '../adapter'
import { RuleMatch } from '@/common/logicEngine/RulesMatch'
import useCommandComponent from '../hooks/useCommandComponent'
import ConfirmDialog from '../components/ConfirmDialog.vue'
import localstorage from '@/common/localstorage'
const confirm = useCommandComponent(ConfirmDialog)
moment.locale('zh-cn')
/**
@ -63,6 +58,10 @@ export const useSurveyStore = defineStore('survey', () => {
enterTime.value = Date.now()
}
const setFormValues = (data) => {
formValues.value = data
}
const getEncryptInfo = async () => {
try {
const res = await getEncryptInfoApi()
@ -154,13 +153,7 @@ export const useSurveyStore = defineStore('survey', () => {
// 获取已投票数据
questionStore.initVoteData()
}
function fillFormData(formData) {
const _formValues = cloneDeep(formValues.value)
for (const key in formData) {
_formValues[key] = formData[key]
}
formValues.value = _formValues
}
const initSurvey = (option) => {
setEnterTime()
if (!canFillQuestionnaire(option.baseConf, option.submitConf)) {
@ -168,31 +161,6 @@ export const useSurveyStore = defineStore('survey', () => {
}
// 加载空白问卷
clearFormData(option)
const { fillAnswer, fillSubmitAnswer } = option.baseConf
const localData = localstorage.getItem(surveyPath.value + FORMDATA_SUFFIX)
const isSubmit = localstorage.getItem(SUBMIT_FLAG)
// 开启了断点续答 or 回填上一次提交内容
if ((fillAnswer || (fillSubmitAnswer && isSubmit)) && localData) {
const title = fillAnswer ? '是否继续上次填写的内容?' : '是否继续上次提交的内容?'
confirm({
title: title,
onConfirm: async () => {
try {
// 回填答题内容
fillFormData(localData)
} catch (error) {
console.error(error)
} finally {
confirm.close()
}
},
onClose: async () => {
confirm.close()
}
})
}
}
// 用户输入或者选择后,更新表单数据
@ -204,6 +172,7 @@ export const useSurveyStore = defineStore('survey', () => {
questionStore.setChangeField(key)
}
// 初始化逻辑引擎
const showLogicEngine = ref()
const initShowLogicEngine = (showLogicConf) => {
showLogicEngine.value = new RuleMatch().fromJson(showLogicConf || [])
@ -231,6 +200,7 @@ export const useSurveyStore = defineStore('survey', () => {
initSurvey,
changeData,
setWhiteData,
setFormValues,
setSurveyPath,
setEnterTime,
getEncryptInfo,

View File

@ -8,19 +8,70 @@
flex-direction: column;
align-items: center;
justify-content: center;
background-color: rgba(0, 0, 0, 0.3);
background-color: #292a36f2;
}
.box {
width: 6rem;
width: 6.3rem;
background: #fff;
padding: 0.56rem 0.44rem 0.32rem;
padding: 0.56rem 0.44rem 0.4rem;
}
.head-wrapper {
margin-bottom: 0.5rem;
}
.title {
font-size: 0.3rem;
font-size: 0.33rem;
color: #4a4c5b;
letter-spacing: 0;
text-align: center;
font-weight: 600;
}
.form-item {
margin-top: 0.2rem;
}
.input-wrapper {
padding: 1px 11px;
background-color: #fff;
border-radius: 0.1rem;
box-shadow: 0 0 0 1px #dcdfe6 inset;
}
.input-wrapper:hover {
box-shadow: 0 0 0 1px var(--primary-color) inset;
}
.input-inner {
width: 100%;
color: #606266;
font-size: 0.27rem;
height: 0.6rem;
line-height: 0.6rem;
padding: 0;
outline: none;
border: none;
background: none;
box-sizing: border-box;
}
.btn {
font-size: 0.26rem;
border-radius: 0.04rem;
text-align: center;
padding: 0.1rem 0;
line-height: 0.5rem;
cursor: pointer;
&.btn-shallow {
background: #fff;
color: #92949d;
border: 1px solid #e3e4e8;
}
&.btn-primary {
background-color: var(--primary-color);
color: #fff;
}
}

View File

@ -1,5 +0,0 @@
export const VOTE_INFO_KEY = 'voteinfo'
export const QUOTA_INFO_KEY = 'limitinfo'
export const SUBMIT_FLAG = 'isSubmit'
export const FORMDATA_SUFFIX = '_questionData'

View File

@ -0,0 +1,44 @@
// 用于记录“问卷断点续答”的数据
export const getSurveyData = (id: string): any => {
try {
return JSON.parse(localStorage.getItem(`${id}_questionData`) as string) || null
} catch (e) {
console.log(e)
}
return null
}
export const setSurveyData = (id: string, formData: any = {}) => {
localStorage.setItem(`${id}_questionData`, JSON.stringify(formData))
}
export const clearSurveyData = (id: string) => localStorage.removeItem(`${id}_questionData`)
// 问卷是否提交过,用于“自动填充上次填写内容”
export const getSurveySubmit = (id: string): number => {
try {
return Number(JSON.parse(localStorage.getItem(`${id}_submit`) as string)) || 0
} catch (e) {
console.log(e)
}
return 0
}
export const setSurveySubmit = (id: string, value: number) => {
localStorage.setItem(`${id}_submit`, JSON.stringify(value))
}
export const clearSurveySubmit = (id: string) => localStorage.removeItem(`${id}_submit`)
// 投票记录
export const getVoteData = (): any => {
try {
return JSON.parse(localStorage.getItem('voteData') as string) || null
} catch (e) {
console.log(e)
}
return null
}
export const setVoteData = (params: any) => {
localStorage.setItem('voteData', JSON.stringify(params))
}
export const clearVoteData = () => localStorage.removeItem('voteData')