fix: 断点续答and编辑检测代码cr优化 (#428)

* fix: 优化代码以及去掉无用字段

* fix: 断点续答验收问题优化

* fix: 优化字段

* fix: lint

* fix: 编辑检测相关问题优化

* fix: lint

* fix: 第二批cr优化

* fix: 去掉无用代码

* fix: 调整session守卫位置

* fix: 文件大小写
This commit is contained in:
dayou 2024-09-20 13:26:59 +08:00 committed by sudoooooo
parent 628872f27c
commit bf5db3f47b
53 changed files with 462 additions and 630 deletions

1
.gitignore vendored
View File

@ -3,7 +3,6 @@ node_modules
dist
package-lock.json
yarn.lock
# local env files
.env.local

View File

@ -48,8 +48,7 @@
"reflect-metadata": "^0.1.13",
"rxjs": "^7.8.1",
"svg-captcha": "^1.4.0",
"typeorm": "^0.3.19",
"xss": "^1.0.15"
"typeorm": "^0.3.19"
},
"devDependencies": {
"@nestjs/cli": "^10.0.0",

View File

@ -6,7 +6,7 @@ export enum RECORD_STATUS {
PUBLISHED = 'published', // 发布
REMOVED = 'removed', // 删除
FORCE_REMOVED = 'forceRemoved', // 从回收站删除
COMOPUTETING = 'computing', // 计算中
COMPUTING = 'computing', // 计算中
FINISHED = 'finished', // 已完成
ERROR = 'error', // 错误
}

View File

@ -2,25 +2,16 @@ import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Reflector } from '@nestjs/core';
import { get } from 'lodash';
import { NoPermissionException } from 'src/exceptions/noPermissionException';
import { SurveyNotFoundException } from 'src/exceptions/surveyNotFoundException';
import { SessionService } from 'src/modules/survey/services/session.service';
import { SurveyMetaService } from 'src/modules/survey/services/surveyMeta.service';
import { WorkspaceMemberService } from 'src/modules/workspace/services/workspaceMember.service';
import { CollaboratorService } from 'src/modules/survey/services/collaborator.service';
@Injectable()
export class SessionGuard implements CanActivate {
constructor(
private reflector: Reflector,
private readonly sessionService: SessionService,
private readonly surveyMetaService: SurveyMetaService,
private readonly workspaceMemberService: WorkspaceMemberService,
private readonly collaboratorService: CollaboratorService,
) {}
async canActivate(context: ExecutionContext): Promise<boolean> {
const request = context.switchToHttp().getRequest();
const user = request.user;
const sessionIdKey = this.reflector.get<string>(
'sessionId',
context.getHandler(),
@ -31,64 +22,8 @@ export class SessionGuard implements CanActivate {
if (!sessionId) {
throw new NoPermissionException('没有权限');
}
const saveSession = await this.sessionService.findOne(sessionId);
request.saveSession = saveSession;
const surveyId = saveSession.surveyId;
const surveyMeta = await this.surveyMetaService.getSurveyById({ surveyId });
if (!surveyMeta) {
throw new SurveyNotFoundException('问卷不存在');
}
request.surveyMeta = surveyMeta;
// 兼容老的问卷没有ownerId
if (
surveyMeta.ownerId === user._id.toString() ||
surveyMeta.owner === user.username
) {
// 问卷的owner可以访问和操作问卷
return true;
}
if (surveyMeta.workspaceId) {
const memberInfo = await this.workspaceMemberService.findOne({
workspaceId: surveyMeta.workspaceId,
userId: user._id.toString(),
});
if (!memberInfo) {
throw new NoPermissionException('没有权限');
}
return true;
}
const permissions = this.reflector.get<string[]>(
'surveyPermission',
context.getHandler(),
);
if (!Array.isArray(permissions) || permissions.length === 0) {
throw new NoPermissionException('没有权限');
}
const info = await this.collaboratorService.getCollaborator({
surveyId,
userId: user._id.toString(),
});
if (!info) {
throw new NoPermissionException('没有权限');
}
request.collaborator = info;
if (
permissions.some((permission) => info.permissions.includes(permission))
) {
return true;
}
throw new NoPermissionException('没有权限');
const sessionInfo = await this.sessionService.findOne(sessionId);
request.sessionInfo = sessionInfo;
return true;
}
}

View File

@ -19,7 +19,4 @@ export class SurveyHistory extends BaseEntity {
username: string;
_id: string;
};
@Column('string')
sessionId: string;
}

View File

@ -35,13 +35,4 @@ export class AuthService {
}
return user;
}
async expiredCheck(token: string) {
try {
verify(token, this.configService.get<string>('XIAOJU_SURVEY_JWT_SECRET'));
} catch (err) {
return true;
}
return false;
}
}

View File

@ -83,7 +83,6 @@ describe('SurveyHistoryService', () => {
schema,
type,
user,
sessionId: '',
});
expect(spyCreate).toHaveBeenCalledWith({

View File

@ -67,7 +67,9 @@ export class SessionController {
@Post('/seize')
@HttpCode(200)
@UseGuards(SurveyGuard)
@UseGuards(SessionGuard)
@SetMetadata('sessionId', 'body.sessionId')
@SetMetadata('surveyPermission', [SURVEY_PERMISSION.SURVEY_CONF_MANAGE])
@UseGuards(Authentication)
@ -75,11 +77,11 @@ export class SessionController {
@Request()
req,
) {
const saveSession = req.saveSession;
const sessionInfo = req.sessionInfo;
await this.sessionService.updateSessionToEditing({
sessionId: saveSession._id.toString(),
surveyId: saveSession.surveyId,
sessionId: sessionInfo._id.toString(),
surveyId: sessionInfo.surveyId,
});
return {

View File

@ -156,7 +156,7 @@ export class DownloadTaskService {
{
$set: {
curStatus: {
status: RECORD_STATUS.COMOPUTETING,
status: RECORD_STATUS.COMPUTING,
date: Date.now(),
},
},

View File

@ -43,7 +43,6 @@
"options": [
{
"text": "选项1",
"imageUrl": "",
"others": false,
"mustOthers": false,
"othersKey": "",
@ -52,7 +51,6 @@
},
{
"text": "选项2",
"imageUrl": "",
"others": false,
"mustOthers": false,
"othersKey": "",

View File

@ -47,7 +47,6 @@
{
"text": "课程1",
"hash": "115019",
"imageUrl": "",
"others": false,
"mustOthers": false,
"othersKey": "",
@ -56,7 +55,6 @@
{
"text": "课程2",
"hash": "115020",
"imageUrl": "",
"others": false,
"mustOthers": false,
"othersKey": "",
@ -65,7 +63,6 @@
{
"text": "课程3",
"hash": "115021",
"imageUrl": "",
"others": false,
"mustOthers": false,
"othersKey": "",
@ -74,7 +71,6 @@
{
"text": "课程4",
"hash": "115022",
"imageUrl": "",
"others": false,
"mustOthers": false,
"othersKey": "",

View File

@ -46,7 +46,6 @@
"options": [
{
"text": "选项1",
"imageUrl": "",
"others": false,
"mustOthers": false,
"othersKey": "",
@ -55,7 +54,6 @@
},
{
"text": "选项2",
"imageUrl": "",
"others": false,
"mustOthers": false,
"othersKey": "",

View File

@ -51,6 +51,7 @@
},
"pageConf": [],
"logicConf": {
"showLogicConf": []
"showLogicConf": [],
"jumpLogicConf": []
}
}

View File

@ -1,53 +0,0 @@
import xss from 'xss';
const myxss = new (xss as any).FilterXSS({
onIgnoreTagAttr(tag, name, value) {
if (name === 'style' || name === 'class') {
return `${name}="${value}"`;
}
return undefined;
},
onIgnoreTag(tag, html) {
// <xxx>过滤为空,否则不过滤为空
const re1 = new RegExp('<.+?>', 'g');
if (re1.test(html)) {
return '';
} else {
return html;
}
},
});
export const cleanRichTextWithMediaTag = (text) => {
if (!text) {
return text === 0 ? 0 : '';
}
const html = transformHtmlTag(text)
.replace(/<img([\w\W]+?)\/>/g, '[图片]')
.replace(/<video.*\/video>/g, '[视频]');
const content = html.replace(/<[^<>]+>/g, '').replace(/&nbsp;/g, '');
return content;
};
export function escapeHtml(html) {
return html.replace(/</g, '&lt;').replace(/>/g, '&gt;');
}
export const transformHtmlTag = (html) => {
if (!html) return '';
if (typeof html !== 'string') return html + '';
return html
.replace(html ? /&(?!#?\w+;)/g : /&/g, '&amp;')
.replace(/&lt;/g, '<')
.replace(/&gt;/g, '>')
.replace(/&quot;/g, '"')
.replace(/&#39;/g, "'")
.replace(/\\\n/g, '\\n');
//.replace(/&nbsp;/g, "")
};
const filterXSSClone = myxss.process.bind(myxss);
export const filterXSS = (html) => filterXSSClone(transformHtmlTag(html));
export const escapeFilterXSS = (html) => escapeHtml(filterXSS(html));

View File

@ -0,0 +1,58 @@
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

@ -9,7 +9,7 @@ const myxss = new xss.FilterXSS({
},
onIgnoreTag(tag, html) {
// <xxx>过滤为空,否则不过滤为空
var re1 = new RegExp('<.+?>', 'g')
const re1 = new RegExp('<.+?>', 'g')
if (re1.test(html)) {
return ''
} else {

View File

@ -3,7 +3,7 @@
</template>
<script setup lang="ts">
import { watch } from 'vue'
import { watch, onBeforeUnmount } from 'vue'
import { get as _get } from 'lodash-es'
import { useUserStore } from '@/management/stores/user'
import { useRouter } from 'vue-router'
@ -23,11 +23,11 @@ const showConfirmBox = () => {
showClose: false,
callback: (action: Action) => {
if (action === 'confirm') {
userStore.logout();
router.replace({ name: 'login' });
userStore.logout()
router.replace({ name: 'login' })
}
}
});
})
}
const checkAuth = async () => {
@ -41,32 +41,40 @@ const checkAuth = async () => {
}
})
if (res.data.code !== 200) {
showConfirmBox();
showConfirmBox()
} else {
timer = setTimeout(() => {
checkAuth()
}, 30 * 60 * 1000);
timer = setTimeout(
() => {
checkAuth()
},
30 * 60 * 1000
)
}
} catch (error) {
const e = error as any
ElMessage.error(e.message)
}
}
watch(() => userStore.hasLogined, (hasLogined) => {
if (hasLogined) {
timer = setTimeout(() => {
checkAuth()
}, 30 * 60 * 1000);
} else {
clearTimeout(timer);
watch(
() => userStore.hasLogined,
(hasLogined) => {
if (hasLogined) {
timer = setTimeout(
() => {
checkAuth()
},
30 * 60 * 1000
)
} else {
clearTimeout(timer)
}
}
)
onBeforeUnmount(() => {
clearTimeout(timer)
})
</script>
<style lang="scss">

View File

@ -16,4 +16,3 @@ export const getStatisticList = (data) => {
}
})
}

View File

@ -7,11 +7,10 @@ export const createDownloadSurveyResponseTask = ({ surveyId, isDesensitive }) =>
})
}
export const getDownloadTask = taskId => {
export const getDownloadTask = (taskId) => {
return axios.get('/downloadTask/getDownloadTask', { params: { taskId } })
}
export const getDownloadTaskList = ({ pageIndex, pageSize }) => {
return axios.get('/downloadTask/getDownloadTaskList', {
params: {
@ -24,6 +23,6 @@ export const getDownloadTaskList = ({ pageIndex, pageSize }) => {
//问卷删除
export const deleteDownloadTask = (taskId) => {
return axios.post('/downloadTask/deleteDownloadTask', {
taskId,
taskId
})
}

View File

@ -59,4 +59,4 @@ export const getSessionId = ({ surveyId }) => {
export const seizeSession = ({ sessionId }) => {
return axios.post('/session/seize', { sessionId })
}
}

View File

@ -80,7 +80,7 @@ const popoverContent = ref('')
const getContent = (content) => {
if (Array.isArray(content)) {
return content.map(item => getContent(item)).join(',');
return content.map((item) => getContent(item)).join(',')
}
if (content === null || content === undefined) {
return ''

View File

@ -28,13 +28,8 @@
<EmptyIndex :data="noDataConfig" />
</div>
<el-dialog
v-model="downloadDialogVisible"
title="导出确认"
width="500"
style="padding: 40px;"
>
<el-form :model="downloadForm" label-width="100px" label-position="left" >
<el-dialog v-model="downloadDialogVisible" title="导出确认" width="500" style="padding: 40px">
<el-form :model="downloadForm" label-width="100px" label-position="left">
<el-form-item label="导出内容">
<el-radio-group v-model="downloadForm.isDesensitive">
<el-radio :value="true">脱敏数据</el-radio>
@ -52,9 +47,7 @@
<template #footer>
<div class="dialog-footer">
<el-button @click="downloadDialogVisible = false">取消</el-button>
<el-button type="primary" @click="confirmDownload()">
确认
</el-button>
<el-button type="primary" @click="confirmDownload()"> 确认 </el-button>
</div>
</template>
</el-dialog>
@ -85,11 +78,12 @@ const dataTableState = reactive({
isDownloading: false,
downloadDialogVisible: false,
downloadForm: {
isDesensitive: true,
},
isDesensitive: true
}
})
const { mainTableLoading, tableData, isShowOriginData, downloadDialogVisible, isDownloading } = toRefs(dataTableState)
const { mainTableLoading, tableData, isShowOriginData, downloadDialogVisible, isDownloading } =
toRefs(dataTableState)
const downloadForm = dataTableState.downloadForm
const route = useRoute()
@ -168,7 +162,10 @@ const confirmDownload = async () => {
}
try {
isDownloading.value = true
const createRes = await createDownloadSurveyResponseTask({ surveyId: route.params.id, isDesensitive: downloadForm.isDesensitive })
const createRes = await createDownloadSurveyResponseTask({
surveyId: route.params.id,
isDesensitive: downloadForm.isDesensitive
})
dataTableState.downloadDialogVisible = false
if (createRes.code === 200) {
ElMessage.success(`下载文件计算中,可前往“下载中心”查看`)
@ -176,12 +173,11 @@ const confirmDownload = async () => {
const taskInfo = await checkIsTaskFinished(createRes.data.taskId)
if (taskInfo.url) {
window.open(taskInfo.url)
ElMessage.success("导出成功")
ElMessage.success('导出成功')
}
} catch (error) {
ElMessage.error('导出失败,请重试')
}
} else {
ElMessage.error('导出失败,请重试')
}
@ -190,13 +186,12 @@ const confirmDownload = async () => {
} finally {
isDownloading.value = false
}
}
const checkIsTaskFinished = (taskId) => {
return new Promise((resolve, reject) => {
const run = () => {
getDownloadTask(taskId).then(res => {
getDownloadTask(taskId).then((res) => {
if (res.code === 200 && res.data) {
const status = res.data.curStatus.status
if (status === 'new' || status === 'computing') {
@ -207,15 +202,13 @@ const checkIsTaskFinished = (taskId) => {
resolve(res.data)
}
} else {
reject("导出失败");
reject('导出失败')
}
})
}
run()
})
}
</script>
<style lang="scss" scoped>

View File

@ -25,7 +25,13 @@
</el-table-column>
<el-table-column label="操作" width="200">
<template v-slot="{ row }">
<span v-if="row.curStatus?.status === 'finished'" class="text-btn download-btn" @click="handleDownload(row)"> 下载 </span>
<span
v-if="row.curStatus?.status === 'finished'"
class="text-btn download-btn"
@click="handleDownload(row)"
>
下载
</span>
<span class="text-btn delete-btn" @click="openDeleteDialog(row)"> 删除 </span>
</template>
</el-table-column>
@ -71,14 +77,14 @@ const getList = async ({ pageIndex }: { pageIndex: number }) => {
}
const params = {
pageSize: pageSize.value,
pageIndex,
pageIndex
}
const res: Record<string, any> = await getDownloadTaskList(params)
if (res.code === CODE_MAP.SUCCESS) {
total.value = res.data.total
const list = res.data.list as any
dataList.splice(0, dataList.length, ...list);
dataList.splice(0, dataList.length, ...list)
}
loading.value = false
}
@ -87,8 +93,8 @@ const statusTextMap: Record<string, string> = {
new: '排队中',
computing: '计算中',
finished: '已完成',
removed: '已删除',
};
removed: '已删除'
}
let currentDelRow: Record<string, any> = {}
//
@ -123,11 +129,11 @@ const confirmDelete = async () => {
if (res.code !== CODE_MAP.SUCCESS) {
ElMessage.error(res.errmsg)
} else {
ElMessage.success('删除成功');
ElMessage.success('删除成功')
await getList({ pageIndex: 1 })
}
} catch (error) {
ElMessage.error("删除失败,请刷新重试")
ElMessage.error('删除失败,请刷新重试')
}
}
@ -162,7 +168,7 @@ const downloadListConfig = {
formatter(row: Record<string, any>, column: Record<string, any>) {
console.log({
row,
column,
column
})
return statusTextMap[get(row, column.rawColumnKey)]
}

View File

@ -18,10 +18,15 @@
</CooperationPanel>
<PreviewPanel></PreviewPanel>
<HistoryPanel></HistoryPanel>
<SavePanel :updateLogicConf="updateLogicConf" :updateWhiteConf="updateWhiteConf"></SavePanel>
<SavePanel
:updateLogicConf="updateLogicConf"
:updateWhiteConf="updateWhiteConf"
:seize="seize"
></SavePanel>
<PublishPanel
:updateLogicConf="updateLogicConf"
:updateWhiteConf="updateWhiteConf"
:seize="seize"
></PublishPanel>
</div>
</div>
@ -30,6 +35,8 @@
import { computed } from 'vue'
import { useEditStore } from '@/management/stores/edit'
import { storeToRefs } from 'pinia'
import { ElMessage } from 'element-plus'
import 'element-plus/theme-chalk/src/message.scss'
import BackPanel from '../modules/generalModule/BackPanel.vue'
import TitlePanel from '../modules/generalModule/TitlePanel.vue'
@ -39,6 +46,7 @@ import PreviewPanel from '../modules/contentModule/PreviewPanel.vue'
import SavePanel from '../modules/contentModule/SavePanel.vue'
import PublishPanel from '../modules/contentModule/PublishPanel.vue'
import CooperationPanel from '../modules/contentModule/CooperationPanel.vue'
import { seizeSession } from '@/management/api/survey'
const editStore = useEditStore()
const { schema, changeSchema } = editStore
@ -68,15 +76,15 @@ const updateLogicConf = () => {
}
const showLogicConf = showLogicEngine.value.toJson()
if(JSON.stringify(schema.logicConf.showLogicConf) !== JSON.stringify(showLogicConf)) {
if (JSON.stringify(schema.logicConf.showLogicConf) !== JSON.stringify(showLogicConf)) {
//
changeSchema({ key: 'logicConf', value: { showLogicConf } })
}
return res
}
const jumpLogicConf = jumpLogicEngine.value.toJson()
if(JSON.stringify(schema.logicConf.jumpLogicConf) !== JSON.stringify(jumpLogicConf)){
if (JSON.stringify(schema.logicConf.jumpLogicConf) !== JSON.stringify(jumpLogicConf)) {
changeSchema({ key: 'logicConf', value: { jumpLogicConf } })
}
@ -106,6 +114,16 @@ const updateWhiteConf = () => {
}
return res
}
// sessionid
const seize = async (sessionId: string) => {
const seizeRes: Record<string, any> = await seizeSession({ sessionId })
if (seizeRes.code === 200) {
location.reload()
} else {
ElMessage.error('获取权限失败,请重试')
}
}
</script>
<style lang="scss" scoped>
@import url('@/management/styles/edit-btn.scss');

View File

@ -24,7 +24,6 @@ import LeftMenu from '@/management/components/LeftMenu.vue'
import CommonTemplate from './components/CommonTemplate.vue'
import Navbar from './components/ModuleNavbar.vue'
const editStore = useEditStore()
const { init, setSurveyId } = editStore

View File

@ -9,7 +9,7 @@ import { useEditStore } from '@/management/stores/edit'
import { useRouter } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import 'element-plus/theme-chalk/src/message.scss'
import { publishSurvey, saveSurvey, seizeSession } from '@/management/api/survey'
import { publishSurvey, saveSurvey } from '@/management/api/survey'
import buildData from './buildData'
import { storeToRefs } from 'pinia'
import { CODE_MAP } from '@/management/api/base'
@ -17,6 +17,7 @@ import { CODE_MAP } from '@/management/api/base'
interface Props {
updateLogicConf: any
updateWhiteConf: any
seize: any
}
const props = defineProps<Props>()
@ -29,15 +30,6 @@ const saveData = computed(() => {
return buildData(schema.value, sessionId.value)
})
const seize = async () => {
const seizeRes: Record<string, any> = await seizeSession({ sessionId: sessionId.value })
if (seizeRes.code === 200) {
location.reload();
} else {
ElMessage.error('获取权限失败,请重试')
}
}
const router = useRouter()
const validate = () => {
@ -61,12 +53,11 @@ const validate = () => {
}
const onSave = async () => {
if (!saveData.value.sessionId) {
ElMessage.error('未获取到sessionId')
return null
}
if (!saveData.value.surveyId) {
ElMessage.error('未获取到问卷id')
return null
@ -74,7 +65,7 @@ const onSave = async () => {
try {
const res: any = await saveSurvey(saveData.value)
if(!res) {
if (!res) {
return null
}
if (res.code === 200) {
@ -85,10 +76,10 @@ const onSave = async () => {
confirmButtonText: '刷新同步',
callback: (action: string) => {
if (action === 'confirm') {
seize();
props.seize(sessionId.value)
}
}
});
})
return null
} else {
ElMessage.error(res.errmsg)
@ -98,7 +89,6 @@ const onSave = async () => {
ElMessage.error('保存问卷失败')
return null
}
}
const handlePublish = async () => {
if (isPublishing.value) {

View File

@ -21,12 +21,13 @@ import { useEditStore } from '@/management/stores/edit'
import { ElMessage, ElMessageBox } from 'element-plus'
import 'element-plus/theme-chalk/src/message.scss'
import { saveSurvey, seizeSession } from '@/management/api/survey'
import { saveSurvey } from '@/management/api/survey'
import buildData from './buildData'
interface Props {
updateLogicConf: any
updateWhiteConf: any
seize: any
}
const route = useRoute()
@ -46,7 +47,6 @@ const saveText = computed(
const editStore = useEditStore()
const { schemaUpdateTime, schema, sessionId } = storeToRefs(editStore)
const validate = () => {
let checked = true
let msg = ''
@ -69,7 +69,7 @@ const validate = () => {
}
const onSave = async () => {
const saveData = buildData(schema.value, sessionId.value);
const saveData = buildData(schema.value, sessionId.value)
if (!saveData.sessionId) {
ElMessage.error('sessionId有误')
return null
@ -81,17 +81,8 @@ const onSave = async () => {
}
const res: Record<string, any> = await saveSurvey(saveData)
return res
}
const seize = async () => {
const seizeRes: Record<string, any> = await seizeSession({ sessionId: sessionId.value })
if (seizeRes.code === 200) {
location.reload();
} else {
ElMessage.error('获取权限失败,请重试')
}
return res
}
const timerHandle = ref<NodeJS.Timeout | number | null>(null)
@ -147,7 +138,7 @@ const handleSave = async () => {
try {
const res: any = await onSave()
if(!res) {
if (!res) {
return
}
if (res.code === 200) {
@ -158,10 +149,10 @@ const handleSave = async () => {
confirmButtonText: '刷新同步',
callback: (action: string) => {
if (action === 'confirm') {
seize();
props.seize(sessionId.value)
}
}
});
})
} else {
ElMessage.error(res.errmsg)
}

View File

@ -141,20 +141,13 @@ export default {
this.initCurOption()
},
addOption(text = '选项', others = false, index = -1, fieldId) {
let addOne
if (this.curOptions[0]) {
addOne = _cloneDeep(this.curOptions[0])
} else {
addOne = {
text: '',
hash: '',
others: false,
mustOthers: false,
othersKey: '',
placeholderDesc: '',
score: 0,
limit: ''
}
let addOne = {
text: '',
hash: '',
others: false,
mustOthers: false,
othersKey: '',
placeholderDesc: ''
}
for (const i in addOne) {
if (i === 'others') {

View File

@ -7,7 +7,7 @@ export default [
{
title: '提交限制',
key: 'limitConfig',
formList: ['limit_tLimit', 'limit_breakAnswer', 'limit_backAnswer']
formList: ['limit_tLimit', 'limit_fillAnswer', 'limit_fillSubmitAnswer']
},
{
title: '作答限制',

View File

@ -22,21 +22,21 @@ export default {
type: 'QuestionTimeHour',
placement: 'top'
},
limit_breakAnswer: {
key: 'breakAnswer',
limit_fillAnswer: {
key: 'fillAnswer',
label: '允许断点续答',
tip: '回填前一次作答中的内容(注:更换设备/浏览器/清除缓存/更改内容重新发布则此功能失效)',
placement: 'top',
type: 'CustomedSwitch',
value: false,
value: false
},
limit_backAnswer: {
key: 'backAnswer',
limit_fillSubmitAnswer: {
key: 'fillSubmitAnswer',
label: '自动填充上次提交内容',
tip: '回填前一次提交的内容(注:更换设备/浏览器/清除缓存/更改内容重新发布则此功能失效)',
placement: 'top',
type: 'CustomedSwitch',
value: false,
value: false
},
interview_pwd_switch: {
key: 'passwordSwitch',
@ -112,5 +112,5 @@ export default {
relyFunc: (data) => {
return data.whitelistType === 'MEMBER'
}
},
}
}

View File

@ -39,7 +39,7 @@
<el-form-item label="验证码" prop="captcha">
<div class="captcha-wrapper">
<el-input style="width: 280px" v-model="formData.captcha" size="large"></el-input>
<div class="captcha-img" click="refreshCaptcha" v-html="captchaImgData"></div>
<div class="captcha-img" @click="refreshCaptcha" v-html="captchaImgData"></div>
</div>
</el-form-item>

View File

@ -6,6 +6,7 @@ import {
set as _set,
isNumber as _isNumber
} from 'lodash-es'
import { ElMessageBox } from 'element-plus'
import { QUESTION_TYPE } from '@/common/typeEnum'
import { getQuestionByType } from '@/management/utils/index'
import { filterQuestionPreviewData } from '@/management/utils/index'
@ -24,7 +25,6 @@ import { CODE_MAP } from '../api/base'
import { RuleBuild } from '@/common/logicEngine/RuleBuild'
import { useShowLogicInfo } from '@/management/hooks/useShowLogicInfo'
import { useJumpLogicInfo } from '@/management/hooks/useJumpLogicInfo'
import { ElMessageBox } from 'element-plus'
const innerMetaConfig = {
submit: {
@ -69,7 +69,6 @@ function useInitializeSchema(surveyId: Ref<string>, initializeSchemaCallBack: ()
begTime: '',
endTime: '',
language: 'chinese',
showVoteProcess: 'allow',
tLimit: 0,
answerBegTime: '',
answerEndTime: '',
@ -93,7 +92,7 @@ function useInitializeSchema(surveyId: Ref<string>, initializeSchemaCallBack: ()
})
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)
@ -107,6 +106,21 @@ function useInitializeSchema(surveyId: Ref<string>, initializeSchemaCallBack: ()
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) {
@ -149,10 +163,11 @@ function useInitializeSchema(surveyId: Ref<string>, initializeSchemaCallBack: ()
return {
schema,
initSchema,
getSchemaFromRemote,
showLogicEngine,
jumpLogicEngine,
sessionId,
initSessionId
}
}
@ -479,7 +494,6 @@ function useLogicEngine(schema: any) {
}
}
type IBannerItem = {
name: string
key: string
@ -487,37 +501,54 @@ type IBannerItem = {
}
type IBannerList = Record<string, IBannerItem>
export const useEditStore = defineStore('edit', () => {
const surveyId = ref('')
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
}
const { schema, initSchema, getSchemaFromRemote, showLogicEngine, jumpLogicEngine } =
useInitializeSchema(surveyId, () => {
editGlobalBaseConf.initCounts()
// 初始化schem相关
const {
schema,
sessionId,
initSessionId,
getSchemaFromRemote,
showLogicEngine,
jumpLogicEngine
} = useInitializeSchema(surveyId, () => {
editGlobalBaseConf.initCounts()
})
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 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)
}
}
}
const questionDataList = toRef(schema, 'questionDataList')
@ -527,86 +558,14 @@ export const useEditStore = defineStore('edit', () => {
schema.questionDataList = data
}
const fetchBannerData = async () => {
const res: any = await getBannerData()
if (res.code === CODE_MAP.SUCCESS) {
bannerList.value = res.data
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
}
}
const fetchCooperPermissions = async (id: string) => {
const res: any = await getCollaboratorPermissions(id)
if (res.code === CODE_MAP.SUCCESS) {
cooperPermissions.value = res.data.permissions
}
}
// const { showLogicEngine, initShowLogicEngine, jumpLogicEngine, initJumpLogicEngine } = useLogicEngine(schema)
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 getSchemaFromRemote()
await initSessionId()
}
currentEditOne.value = null
currentEditStatus.value = 'Success'
}
function updateTime() {
schemaUpdateTime.value = Date.now()
}
const {
pageEditOne,
pageConf,
isFinallyPage,
pageCount,
pageQuestionData,
getSorter,
updatePageEditOne,
deletePage,
pageOperations,
addPage,
getPageQuestionData,
copyPage,
swapArrayRanges,
setPage
} = usePageEdit({ schema, questionDataList }, updateTime)
const { copyQuestion, addQuestion, deleteQuestion, moveQuestion } = useQuestionDataListOperations(
{
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 newQuestion
}
const compareQuestionSeq = (val: Array<any>) => {
@ -644,25 +603,70 @@ export const useEditStore = defineStore('edit', () => {
}
})
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
// 当前编辑题目
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()])
}
return newQuestion
currentEditOne.value = null
currentEditStatus.value = 'Success'
}
function changeSchema({ key, value }: { key: string; value: any }) {
_set(schema, key, value)
updateTime()
}
// 分页相关
const {
pageEditOne,
pageConf,
isFinallyPage,
pageCount,
pageQuestionData,
getSorter,
updatePageEditOne,
deletePage,
pageOperations,
addPage,
getPageQuestionData,
copyPage,
swapArrayRanges,
setPage
} = usePageEdit({ schema, questionDataList }, updateTime)
function changeThemePreset(presets: any) {
Object.keys(presets).forEach((key) => {
_set(schema, key, presets[key])
})
// 问卷列表相关操作
const { copyQuestion, addQuestion, deleteQuestion, moveQuestion } = useQuestionDataListOperations(
{
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 {
@ -670,7 +674,6 @@ export const useEditStore = defineStore('edit', () => {
surveyId,
sessionId,
setSurveyId,
initSessionId,
bannerList,
fetchBannerData,
cooperPermissions,
@ -703,7 +706,6 @@ export const useEditStore = defineStore('edit', () => {
setQuestionDataList,
moveQuestionDataList,
init,
initSchema,
getSchemaFromRemote,
copyQuestion,
addQuestion,

View File

@ -1,6 +1,8 @@
import { defineStore } from 'pinia'
import { ref } from 'vue'
import localstorage from '@/common/localstorage'
type IUserInfo = {
username: string
token: string
@ -17,12 +19,12 @@ export const useUserStore = defineStore('user', () => {
const initialized = ref(false)
const init = () => {
const localData = localStorage.getItem(USER_INFO_KEY)
const localData = localstorage.getItem(USER_INFO_KEY)
if (localData) {
try {
const { userInfo: info, loginTime: time } = JSON.parse(localData)
const { userInfo: info, loginTime: time } = localData as any
if (Date.now() - time > 7 * 3600000) {
localStorage.removeItem(USER_INFO_KEY)
localstorage.removeItem(USER_INFO_KEY)
} else {
login(info)
}
@ -36,18 +38,15 @@ export const useUserStore = defineStore('user', () => {
userInfo.value = data
hasLogined.value = true
loginTime.value = Date.now()
localStorage.setItem(
USER_INFO_KEY,
JSON.stringify({
userInfo: data,
loginTime: loginTime
})
)
localstorage.setItem(USER_INFO_KEY, {
userInfo: data,
loginTime: loginTime
})
}
const logout = () => {
userInfo.value = null
hasLogined.value = false
localStorage.removeItem(USER_INFO_KEY)
localstorage.removeItem(USER_INFO_KEY)
}
return { userInfo, hasLogined, loginTime, initialized, init, login, logout }

View File

@ -19,7 +19,6 @@ export function getRandom(len) {
const optionListItem = [
'text',
'imageUrl',
'others',
'mustOthers',
'limit',

View File

@ -25,14 +25,12 @@
padding: 0 0.2rem !important;
box-sizing: border-box;
// .choice-content, .choice-item {
// width: 100%;
// }
&.vertical {
display: flex;
display: flex;
flex-direction: column;
.choice-outer {

View File

@ -54,7 +54,6 @@ const meta = {
defaultValue: [
{
text: '对',
imageUrl: '',
others: false,
mustOthers: false,
othersKey: '',
@ -63,7 +62,6 @@ const meta = {
},
{
text: '错',
imageUrl: '',
others: false,
mustOthers: false,
othersKey: '',
@ -79,29 +77,32 @@ const meta = {
defaultValue: 'vertical'
}
],
formConfig: [basicConfig, {
name: 'optionConfig',
title: '选项配置',
type: 'Customed',
content: [
{
label: '排列方式',
type: 'RadioGroup',
key: 'layout',
value: 'vertical',
options: [
{
label: '竖排',
value: 'vertical'
},
{
label: '横排',
value: 'horizontal'
},
]
},
]
}],
formConfig: [
basicConfig,
{
name: 'optionConfig',
title: '选项配置',
type: 'Customed',
content: [
{
label: '排列方式',
type: 'RadioGroup',
key: 'layout',
value: 'vertical',
options: [
{
label: '竖排',
value: 'vertical'
},
{
label: '横排',
value: 'horizontal'
}
]
}
]
}
],
editConfigure: {
optionEdit: {
show: false

View File

@ -1,4 +1,4 @@
import { computed, defineComponent, shallowRef, defineAsyncComponent, watch } from 'vue'
import { computed, defineComponent, shallowRef, defineAsyncComponent } from 'vue'
import { includes } from 'lodash-es'
import BaseChoice from '../BaseChoice'

View File

@ -53,7 +53,6 @@ const meta = {
defaultValue: [
{
text: '选项1',
imageUrl: '',
others: false,
mustOthers: false,
othersKey: '',
@ -62,7 +61,6 @@ const meta = {
},
{
text: '选项2',
imageUrl: '',
others: false,
mustOthers: false,
othersKey: '',

View File

@ -12,7 +12,7 @@ function useOptionBase(options) {
others: false,
mustOthers: false,
othersKey: '',
placeholderDesc: '',
placeholderDesc: ''
}
if (typeof text !== 'string') {
text = '选项'

View File

@ -1,4 +1,4 @@
import { defineComponent, shallowRef, watch, defineAsyncComponent } from 'vue'
import { defineComponent, shallowRef, defineAsyncComponent } from 'vue'
import BaseChoice from '../BaseChoice'
/**

View File

@ -54,7 +54,6 @@ const meta = {
defaultValue: [
{
text: '选项1',
imageUrl: '',
others: false,
mustOthers: false,
othersKey: '',
@ -63,7 +62,6 @@ const meta = {
},
{
text: '选项2',
imageUrl: '',
others: false,
mustOthers: false,
othersKey: '',

View File

@ -48,9 +48,9 @@ export default defineComponent({
watch(status, (v) => {
if (v === 'edit') {
document.addEventListener('click', handleDocumentClick, {capture: true})
document.addEventListener('click', handleDocumentClick, { capture: true })
} else {
document.removeEventListener('click', handleDocumentClick, {capture: true})
document.removeEventListener('click', handleDocumentClick, { capture: true })
}
})

View File

@ -54,7 +54,6 @@ const meta = {
defaultValue: [
{
text: '选项1',
imageUrl: '',
others: false,
mustOthers: false,
othersKey: '',
@ -63,7 +62,6 @@ const meta = {
},
{
text: '选项2',
imageUrl: '',
others: false,
mustOthers: false,
othersKey: '',
@ -120,7 +118,9 @@ const meta = {
key: 'minNum',
value: '',
min: 0,
max: moduleConfig => { return moduleConfig?.maxNum || 0 },
max: (moduleConfig) => {
return moduleConfig?.maxNum || 0
},
contentClass: 'input-number-config'
},
{
@ -128,8 +128,12 @@ const meta = {
type: 'InputNumber',
key: 'maxNum',
value: '',
min: moduleConfig => { return moduleConfig?.minNum || 0 },
max: moduleConfig => { return moduleConfig?.options?.length || 0 },
min: (moduleConfig) => {
return moduleConfig?.minNum || 0
},
max: (moduleConfig) => {
return moduleConfig?.options?.length || 0
},
contentClass: 'input-number-config'
}
]

View File

@ -13,7 +13,6 @@ import { ElMessage } from 'element-plus'
import 'element-plus/theme-chalk/src/message.scss'
import { FORM_CHANGE_EVENT_KEY } from '@/materials/setters/constant'
interface Props {
formConfig: any
moduleConfig: any
@ -66,9 +65,12 @@ const handleInputChange = (value: number) => {
emit(FORM_CHANGE_EVENT_KEY, { key, value })
}
watch(() => props.moduleConfig, (newVal) => {
myModuleConfig.value = newVal
})
watch(
() => props.moduleConfig,
(newVal) => {
myModuleConfig.value = newVal
}
)
watch(
() => props.formConfig.value,
(newVal) => {

View File

@ -1,79 +0,0 @@
<template>
<div class="mask" v-show="visible">
<div class="box">
<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>
</div>
</div>
</template>
<script setup lang="ts">
interface Props {
visible?: boolean
cancelBtnText?: string
confirmBtnText?: string
title?: string
}
interface Emit {
(ev: 'confirm', callback: () => void): void
(ev: 'cancel', callback: () => void): void
(ev: 'close'): void
}
const emit = defineEmits<Emit>()
withDefaults(defineProps<Props>(), {
visible: false,
cancelBtnText: '取消',
confirmBtnText: '确定',
title: ''
})
const handleConfirm = () => {
emit('confirm', () => {
emit('close')
})
}
const handleCancel = () => {
emit('cancel', () => {
emit('close')
})
}
</script>
<style lang="scss" scoped>
@import url('../styles/dialog.scss');
.btn-box {
padding: 20px 0 0;
display: flex;
align-items: center;
justify-content: space-between;
.btn {
width: 48%;
font-size: 0.28rem;
border-radius: 0.04rem;
text-align: center;
padding: 0.16rem 0;
line-height: 0.4rem;
cursor: pointer;
&.cancel {
background: #fff;
color: #92949d;
border: 1px solid #e3e4e8;
}
&.confirm {
background-color: #4a4c5b;
border: 1px solid #4a4c5b;
color: #fff;
}
}
}
</style>

View File

@ -9,17 +9,18 @@
></QuestionRuleContainer>
</template>
<script setup>
import { unref, computed, watch } from 'vue'
import { unref, computed, watch, ref } 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 { cloneDeep } from 'lodash-es'
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'
const props = defineProps({
indexNumber: {
@ -46,7 +47,7 @@ const { changeField, changeIndex, needHideFields } = storeToRefs(questionStore)
const questionConfig = computed(() => {
let moduleConfig = props.moduleConfig
const { type, field, options = [], ...rest } = cloneDeep(moduleConfig)
// console.log(field,'formValuechange')
let alloptions = options
if (type === QUESTION_TYPE.VOTE) {
@ -126,14 +127,28 @@ const handleChange = (data) => {
if (props.moduleConfig.type === QUESTION_TYPE.VOTE) {
questionStore.updateVoteData(data)
}
//
localStorageBack()
processJumpSkip()
valueTemp.value = data.value
debounceStorageSave()
}
const valueTemp = ref()
const handleInput = (e) => {
valueTemp.value = e.target.value
debounceStorageSave()
}
const handleInput = () => {
localStorageBack()
}
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
@ -175,12 +190,9 @@ const processJumpSkip = () => {
.map((item) => item.field)
questionStore.addNeedHideFields(skipKey)
}
const localStorageBack = () => {
var formData = Object.assign({}, surveyStore.formValues)
//
localStorage.removeItem(surveyStore.surveyPath + '_questionData')
localStorage.setItem(surveyStore.surveyPath + '_questionData', JSON.stringify(formData))
localStorage.setItem('isSubmit', JSON.stringify(false))
const localStorageSave = (formData) => {
localstorage.removeItem(surveyStore.surveyPath + FORMDATA_SUFFIX)
localstorage.setItem(surveyStore.surveyPath + FORMDATA_SUFFIX, formData)
localstorage.setItem(SUBMIT_FLAG, false)
}
</script>

View File

@ -4,15 +4,17 @@ export const useQuestionInfo = (field: string) => {
const questionstore = useQuestionStore()
const questionTitle = cleanRichText(questionstore.questionData[field]?.title)
const getOptionTitle = (value:any) => {
const options = questionstore.questionData[field]?.options || []
if (value instanceof Array) {
return options
.filter((item:any) => value.includes(item.hash))
.map((item:any) => cleanRichText(item.text))
} else {
return options.filter((item:any) => item.hash === value).map((item:any) => cleanRichText(item.text))
}
const getOptionTitle = (value: any) => {
const options = questionstore.questionData[field]?.options || []
if (value instanceof Array) {
return options
.filter((item: any) => value.includes(item.hash))
.map((item: any) => cleanRichText(item.text))
} else {
return options
.filter((item: any) => item.hash === value)
.map((item: any) => cleanRichText(item.text))
}
}
return { questionTitle, getOptionTitle }
}

View File

@ -9,9 +9,9 @@
<script>
;(function () {
function resetRemUnit() {
var PC_W = 750
var docEl = window.document.documentElement
var width = docEl.getBoundingClientRect().width || 375
const PC_W = 750
let docEl = window.document.documentElement
let width = docEl.getBoundingClientRect().width || 375
if (!/Android|webOS|iPhone|iPad|iPod|BlackBerry/i.test(navigator.userAgent)) {
width = width < PC_W ? width : PC_W
@ -20,15 +20,15 @@
}
}
var f = Math.min(width / 7.5, 50)
const f = Math.min(width / 7.5, 50)
docEl.style.fontSize = f + 'px'
var d = window.document.createElement('div')
let d = window.document.createElement('div')
d.style.width = '1rem'
d.style.display = 'none'
var head = window.document.getElementsByTagName('head')[0]
let head = window.document.getElementsByTagName('head')[0]
head.appendChild(d)
var realf = parseFloat(window.getComputedStyle(d, null).getPropertyValue('width'))
const realf = parseFloat(window.getComputedStyle(d, null).getPropertyValue('width'))
if (f !== realf) {
docEl.style.fontSize = f * (f / realf) + 'px'

View File

@ -38,7 +38,8 @@ import encrypt from '../utils/encrypt'
import useCommandComponent from '../hooks/useCommandComponent'
import { getPublishedSurveyInfo, getPreviewSchema } from '../api/survey'
import { useQuestionInfo } from '../hooks/useQuestionInfo'
import { FORMDATA_SUFFIX, SUBMIT_FLAG } from '@/render/utils/constant'
import localstorage from '@/common/localstorage'
interface Props {
questionInfo?: any
@ -151,13 +152,13 @@ const normalizationRequestBody = () => {
}
//
localStorage.removeItem(surveyPath.value + '_questionData')
localStorage.removeItem('isSubmit')
localstorage.removeItem(surveyPath.value + FORMDATA_SUFFIX)
localstorage.removeItem(SUBMIT_FLAG)
//
var formData: Record<string, any> = Object.assign({}, surveyStore.formValues)
let formData: Record<string, any> = Object.assign({}, surveyStore.formValues)
localStorage.setItem(surveyPath.value + '_questionData', JSON.stringify(formData))
localStorage.setItem('isSubmit', JSON.stringify(true))
localstorage.setItem(surveyPath.value + FORMDATA_SUFFIX, formData)
localstorage.setItem(SUBMIT_FLAG, true)
if (encryptInfo?.encryptType) {
result.encryptType = encryptInfo.encryptType

View File

@ -3,9 +3,9 @@ import { defineStore } from 'pinia'
import { set } from 'lodash-es'
import { useSurveyStore } from '@/render/stores/survey'
import { queryVote } from '@/render/api/survey'
import { QUESTION_TYPE, NORMAL_CHOICES } from '@/common/typeEnum'
const VOTE_INFO_KEY = 'voteinfo'
import { QUESTION_TYPE } from '@/common/typeEnum'
import { VOTE_INFO_KEY } from '@/render/utils/constant'
import localstorage from '@/common/localstorage'
// 投票进度逻辑聚合
const usevVoteMap = (questionData) => {
@ -28,19 +28,16 @@ const usevVoteMap = (questionData) => {
return
}
try {
localStorage.removeItem(VOTE_INFO_KEY)
localstorage.removeItem(VOTE_INFO_KEY)
const voteRes = await queryVote({
surveyPath,
fieldList: fieldList.join(',')
})
if (voteRes.code === 200) {
localStorage.setItem(
VOTE_INFO_KEY,
JSON.stringify({
...voteRes.data
})
)
localstorage.setItem(VOTE_INFO_KEY, {
...voteRes.data
})
setVoteMap(voteRes.data)
}
} catch (error) {
@ -60,9 +57,8 @@ const usevVoteMap = (questionData) => {
}
const updateVoteData = (data) => {
const { key: questionKey, value: questionVal } = data
// 更新前获取接口缓存在localStorage中的数据
const localData = localStorage.getItem(VOTE_INFO_KEY)
const voteinfo = JSON.parse(localData)
// 更新前获取接口缓存在localstorage中的数据
const voteinfo = localstorage.getItem(VOTE_INFO_KEY)
const currentQuestion = questionData.value[questionKey]
const options = currentQuestion.options
const voteTotal = voteinfo?.[questionKey]?.total || 0

View File

@ -2,23 +2,24 @@ import { ref } from 'vue'
import { useRouter } from 'vue-router'
import { defineStore } from 'pinia'
import { cloneDeep, pick } from 'lodash-es'
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 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 BackAnswerDialog from '../components/BackAnswerDialog.vue'
import ConfirmDialog from '../components/ConfirmDialog.vue'
import localstorage from '@/common/localstorage'
const confirm = useCommandComponent(BackAnswerDialog)
const confirm = useCommandComponent(ConfirmDialog)
moment.locale('zh-cn')
/**
@ -134,7 +135,7 @@ export const useSurveyStore = defineStore('survey', () => {
'pageConf'
])
)
// todo: 建议通过questionStore提供setqueationdata方法修改属性否则不好跟踪变化
questionStore.questionData = questionData
questionStore.questionSeq = questionSeq
@ -168,52 +169,29 @@ export const useSurveyStore = defineStore('survey', () => {
// 加载空白问卷
clearFormData(option)
const { breakAnswer, backAnswer } = option.baseConf
const localData = JSON.parse(localStorage.getItem(surveyPath.value + '_questionData'))
const { fillAnswer, fillSubmitAnswer } = option.baseConf
const localData = localstorage.getItem(surveyPath.value + FORMDATA_SUFFIX)
const isSubmit = JSON.parse(localStorage.getItem('isSubmit'))
if (localData) {
// 断点续答
if (breakAnswer) {
confirm({
title: '是否继续上次填写的内容?',
onConfirm: async () => {
try {
// 回填答题内容
fillFormData(localData)
} catch (error) {
console.log(error)
} finally {
confirm.close()
}
},
onCancel: async () => {
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()
}
})
} else if (backAnswer) {
if (isSubmit) {
confirm({
title: '是否继续上次提交的内容?',
onConfirm: async () => {
try {
// 回填答题内容
fillFormData(localData)
} catch (error) {
console.log(error)
} finally {
confirm.close()
}
},
onCancel: async () => {
confirm.close()
}
})
},
onClose: async () => {
confirm.close()
}
}
} else {
clearFormData(option)
})
}
}

View File

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

View File

@ -16,7 +16,7 @@ export default class EventBus {
off(eventName, fn) {
if (this.events[eventName]) {
for (var i = 0; i < this.events[eventName].length; i++) {
for (let i = 0; i < this.events[eventName].length; i++) {
if (this.events[eventName][i] === fn) {
this.events[eventName].splice(i, 1)
break