feat: 登录失效检测 & 协作冲突检测 (#287)
Co-authored-by: Liuxinyi <liuxy0406@163.com> Co-authored-by: dayou <853094838@qq.com>
This commit is contained in:
parent
b5bcb7ff7e
commit
3003c2cbfa
@ -40,6 +40,7 @@
|
|||||||
"moment": "^2.30.1",
|
"moment": "^2.30.1",
|
||||||
"mongodb": "^5.9.2",
|
"mongodb": "^5.9.2",
|
||||||
"nanoid": "^3.3.7",
|
"nanoid": "^3.3.7",
|
||||||
|
"node-cron": "^3.0.3",
|
||||||
"node-fetch": "^2.7.0",
|
"node-fetch": "^2.7.0",
|
||||||
"node-forge": "^1.3.1",
|
"node-forge": "^1.3.1",
|
||||||
"qiniu": "^7.11.1",
|
"qiniu": "^7.11.1",
|
||||||
|
@ -18,5 +18,6 @@ export class SurveyHistory extends BaseEntity {
|
|||||||
operator: {
|
operator: {
|
||||||
username: string;
|
username: string;
|
||||||
_id: string;
|
_id: string;
|
||||||
|
sessionId: string;
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Controller, Post, Body, HttpCode } from '@nestjs/common';
|
import { Controller, Post, Get, Body, HttpCode, Req, UnauthorizedException } from '@nestjs/common';
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
import { UserService } from '../services/user.service';
|
import { UserService } from '../services/user.service';
|
||||||
import { CaptchaService } from '../services/captcha.service';
|
import { CaptchaService } from '../services/captcha.service';
|
||||||
@ -7,6 +7,7 @@ import { HttpException } from 'src/exceptions/httpException';
|
|||||||
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
||||||
import { create } from 'svg-captcha';
|
import { create } from 'svg-captcha';
|
||||||
import { ApiTags } from '@nestjs/swagger';
|
import { ApiTags } from '@nestjs/swagger';
|
||||||
|
import { Request } from 'express';
|
||||||
@ApiTags('auth')
|
@ApiTags('auth')
|
||||||
@Controller('/api/auth')
|
@Controller('/api/auth')
|
||||||
export class AuthController {
|
export class AuthController {
|
||||||
@ -162,4 +163,25 @@ export class AuthController {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Get('/statuscheck')
|
||||||
|
@HttpCode(200)
|
||||||
|
async checkStatus(@Req() request: Request) {
|
||||||
|
const token = request.headers.authorization?.split(' ')[1];
|
||||||
|
if (!token) {
|
||||||
|
throw new UnauthorizedException('请登录');
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
const expired = await this.authService.expiredCheck(token);
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
data: {
|
||||||
|
expired: expired
|
||||||
|
},
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
throw new UnauthorizedException(error?.message || '用户凭证检测失败');
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -35,4 +35,17 @@ export class AuthService {
|
|||||||
}
|
}
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async expiredCheck(token: string) {
|
||||||
|
let decoded;
|
||||||
|
try {
|
||||||
|
decoded = verify(
|
||||||
|
token,
|
||||||
|
this.configService.get<string>('XIAOJU_SURVEY_JWT_SECRET'),
|
||||||
|
);
|
||||||
|
} catch (err) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -130,6 +130,7 @@ export class SurveyController {
|
|||||||
const { value, error } = Joi.object({
|
const { value, error } = Joi.object({
|
||||||
surveyId: Joi.string().required(),
|
surveyId: Joi.string().required(),
|
||||||
configData: Joi.any().required(),
|
configData: Joi.any().required(),
|
||||||
|
sessionId: Joi.string().required(),
|
||||||
}).validate(surveyInfo);
|
}).validate(surveyInfo);
|
||||||
if (error) {
|
if (error) {
|
||||||
this.logger.error(error.message, { req });
|
this.logger.error(error.message, { req });
|
||||||
@ -137,7 +138,7 @@ export class SurveyController {
|
|||||||
}
|
}
|
||||||
const username = req.user.username;
|
const username = req.user.username;
|
||||||
const surveyId = value.surveyId;
|
const surveyId = value.surveyId;
|
||||||
|
const sessionId = value.sessionId;
|
||||||
const configData = value.configData;
|
const configData = value.configData;
|
||||||
await this.surveyConfService.saveSurveyConf({
|
await this.surveyConfService.saveSurveyConf({
|
||||||
surveyId,
|
surveyId,
|
||||||
@ -151,6 +152,7 @@ export class SurveyController {
|
|||||||
_id: req.user._id.toString(),
|
_id: req.user._id.toString(),
|
||||||
username,
|
username,
|
||||||
},
|
},
|
||||||
|
sessionId: sessionId,
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
code: 200,
|
code: 200,
|
||||||
@ -271,6 +273,7 @@ export class SurveyController {
|
|||||||
) {
|
) {
|
||||||
const { value, error } = Joi.object({
|
const { value, error } = Joi.object({
|
||||||
surveyId: Joi.string().required(),
|
surveyId: Joi.string().required(),
|
||||||
|
sessionId: Joi.string().required(),
|
||||||
}).validate(surveyInfo);
|
}).validate(surveyInfo);
|
||||||
if (error) {
|
if (error) {
|
||||||
this.logger.error(error.message, { req });
|
this.logger.error(error.message, { req });
|
||||||
@ -278,6 +281,7 @@ export class SurveyController {
|
|||||||
}
|
}
|
||||||
const username = req.user.username;
|
const username = req.user.username;
|
||||||
const surveyId = value.surveyId;
|
const surveyId = value.surveyId;
|
||||||
|
const sessionId = value.sessionId;
|
||||||
const surveyMeta = req.surveyMeta;
|
const surveyMeta = req.surveyMeta;
|
||||||
const surveyConf =
|
const surveyConf =
|
||||||
await this.surveyConfService.getSurveyConfBySurveyId(surveyId);
|
await this.surveyConfService.getSurveyConfBySurveyId(surveyId);
|
||||||
@ -317,6 +321,7 @@ export class SurveyController {
|
|||||||
_id: req.user._id.toString(),
|
_id: req.user._id.toString(),
|
||||||
username,
|
username,
|
||||||
},
|
},
|
||||||
|
sessionId: sessionId,
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
code: 200,
|
code: 200,
|
||||||
|
@ -18,6 +18,7 @@ import { SURVEY_PERMISSION } from 'src/enums/surveyPermission';
|
|||||||
import { Logger } from 'src/logger';
|
import { Logger } from 'src/logger';
|
||||||
import { HttpException } from 'src/exceptions/httpException';
|
import { HttpException } from 'src/exceptions/httpException';
|
||||||
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
||||||
|
import { val } from 'cheerio/lib/api/attributes';
|
||||||
|
|
||||||
@ApiTags('survey')
|
@ApiTags('survey')
|
||||||
@Controller('/api/surveyHisotry')
|
@Controller('/api/surveyHisotry')
|
||||||
@ -66,4 +67,52 @@ export class SurveyHistoryController {
|
|||||||
data,
|
data,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@Get('/getConflictList')
|
||||||
|
@HttpCode(200)
|
||||||
|
@UseGuards(SurveyGuard)
|
||||||
|
@SetMetadata('surveyId', 'query.surveyId')
|
||||||
|
@SetMetadata('surveyPermission', [
|
||||||
|
SURVEY_PERMISSION.SURVEY_CONF_MANAGE,
|
||||||
|
SURVEY_PERMISSION.SURVEY_COOPERATION_MANAGE,
|
||||||
|
SURVEY_PERMISSION.SURVEY_RESPONSE_MANAGE,
|
||||||
|
])
|
||||||
|
@UseGuards(Authentication)
|
||||||
|
async getConflictList(
|
||||||
|
@Query()
|
||||||
|
queryInfo: {
|
||||||
|
surveyId: string;
|
||||||
|
historyType: string;
|
||||||
|
sessionId: string;
|
||||||
|
},
|
||||||
|
@Request() req,
|
||||||
|
) {
|
||||||
|
const { value, error } = Joi.object({
|
||||||
|
surveyId: Joi.string().required(),
|
||||||
|
historyType: Joi.string().required(),
|
||||||
|
sessionId: Joi.string().required(),
|
||||||
|
}).validate(queryInfo);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
this.logger.error(error.message, { req });
|
||||||
|
throw new HttpException('参数有误', EXCEPTION_CODE.PARAMETER_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
const surveyId = value.surveyId;
|
||||||
|
const historyType = value.historyType;
|
||||||
|
const sessionId = value.sessionId;
|
||||||
|
|
||||||
|
const data = await this.surveyHistoryService.getConflictList({
|
||||||
|
surveyId,
|
||||||
|
historyType,
|
||||||
|
sessionId,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
data,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -17,8 +17,9 @@ export class SurveyHistoryService {
|
|||||||
schema: SurveySchemaInterface;
|
schema: SurveySchemaInterface;
|
||||||
type: HISTORY_TYPE;
|
type: HISTORY_TYPE;
|
||||||
user: any;
|
user: any;
|
||||||
|
sessionId: string;
|
||||||
}) {
|
}) {
|
||||||
const { surveyId, schema, type, user } = params;
|
const { surveyId, schema, type, user, sessionId } = params;
|
||||||
const newHistory = this.surveyHistory.create({
|
const newHistory = this.surveyHistory.create({
|
||||||
pageId: surveyId,
|
pageId: surveyId,
|
||||||
type,
|
type,
|
||||||
@ -26,6 +27,7 @@ export class SurveyHistoryService {
|
|||||||
operator: {
|
operator: {
|
||||||
_id: user._id.toString(),
|
_id: user._id.toString(),
|
||||||
username: user.username,
|
username: user.username,
|
||||||
|
sessionId: sessionId,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return this.surveyHistory.save(newHistory);
|
return this.surveyHistory.save(newHistory);
|
||||||
@ -50,4 +52,29 @@ export class SurveyHistoryService {
|
|||||||
select: ['createDate', 'operator', 'type', '_id'],
|
select: ['createDate', 'operator', 'type', '_id'],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getConflictList({
|
||||||
|
surveyId,
|
||||||
|
historyType,
|
||||||
|
sessionId,
|
||||||
|
}: {
|
||||||
|
surveyId: string;
|
||||||
|
historyType: HISTORY_TYPE;
|
||||||
|
sessionId: string;
|
||||||
|
}) {
|
||||||
|
const result = await this.surveyHistory.find({
|
||||||
|
where: {
|
||||||
|
pageId: surveyId,
|
||||||
|
type: historyType,
|
||||||
|
// 排除掉sessionid相同的历史,这些保存不构成冲突
|
||||||
|
'operator.sessionId': { $ne: sessionId },
|
||||||
|
},
|
||||||
|
order: { createDate: 'DESC' },
|
||||||
|
take: 100,
|
||||||
|
select: ['createDate', 'operator', 'type', '_id'],
|
||||||
|
});
|
||||||
|
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
6326
server/yarn.lock
Normal file
6326
server/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
@ -27,6 +27,7 @@
|
|||||||
"nanoid": "^5.0.7",
|
"nanoid": "^5.0.7",
|
||||||
"node-forge": "^1.3.1",
|
"node-forge": "^1.3.1",
|
||||||
"qrcode": "^1.5.3",
|
"qrcode": "^1.5.3",
|
||||||
|
"uuid": "^10.0.0",
|
||||||
"vue": "^3.4.15",
|
"vue": "^3.4.15",
|
||||||
"vue-router": "^4.2.5",
|
"vue-router": "^4.2.5",
|
||||||
"vuedraggable": "^4.1.0",
|
"vuedraggable": "^4.1.0",
|
||||||
|
@ -20,13 +20,13 @@ export const getSurveyById = (id) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
export const saveSurvey = ({ surveyId, configData }) => {
|
export const saveSurvey = ({ surveyId, configData, sessionId }) => {
|
||||||
return axios.post('/survey/updateConf', { surveyId, configData })
|
return axios.post('/survey/updateConf', { surveyId, configData, sessionId })
|
||||||
}
|
}
|
||||||
|
|
||||||
export const publishSurvey = ({ surveyId }) => {
|
export const publishSurvey = ({ surveyId, sessionId }) => {
|
||||||
return axios.post('/survey/publishSurvey', {
|
return axios.post('/survey/publishSurvey', {
|
||||||
surveyId
|
surveyId, sessionId
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -43,6 +43,16 @@ export const getSurveyHistory = ({ surveyId, historyType }) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getConflictHistory = ({ surveyId, historyType, sessionId }) => {
|
||||||
|
return axios.get('/surveyHisotry/getConflictList', {
|
||||||
|
params: {
|
||||||
|
surveyId,
|
||||||
|
historyType,
|
||||||
|
sessionId
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
export const deleteSurvey = (surveyId) => {
|
export const deleteSurvey = (surveyId) => {
|
||||||
return axios.post('/survey/deleteSurvey', {
|
return axios.post('/survey/deleteSurvey', {
|
||||||
surveyId
|
surveyId
|
||||||
|
@ -14,22 +14,63 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { onMounted } from 'vue'
|
import { onMounted, ref, onUnmounted } from 'vue'
|
||||||
import { useStore } from 'vuex'
|
import { useStore } from 'vuex'
|
||||||
import { useRouter, useRoute } from 'vue-router'
|
import { useRouter, useRoute } from 'vue-router'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
import 'element-plus/theme-chalk/src/message.scss'
|
import 'element-plus/theme-chalk/src/message.scss'
|
||||||
|
|
||||||
import LeftMenu from '@/management/components/LeftMenu.vue'
|
import LeftMenu from '@/management/components/LeftMenu.vue'
|
||||||
import CommonTemplate from './components/CommonTemplate.vue'
|
import CommonTemplate from './components/CommonTemplate.vue'
|
||||||
import Navbar from './components/ModuleNavbar.vue'
|
import Navbar from './components/ModuleNavbar.vue'
|
||||||
|
import axios from '../../api/base'
|
||||||
import { initShowLogicEngine } from '@/management/hooks/useShowLogicEngine'
|
import { initShowLogicEngine } from '@/management/hooks/useShowLogicEngine'
|
||||||
|
|
||||||
const store = useStore()
|
const store = useStore()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const route = useRoute()
|
const route = useRoute()
|
||||||
|
const authCheckInterval = ref<any>(null)
|
||||||
|
const showConfirmBox = () => {
|
||||||
|
const token = store.state.user.userInfo.token
|
||||||
|
ElMessageBox.alert('登录状态已失效,请刷新同步。页面将展示最新保存的内容。', '提示', {
|
||||||
|
confirmButtonText: '确认',
|
||||||
|
callback: () => {
|
||||||
|
axios.get('/auth/statuscheck', {
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.then((response) => {
|
||||||
|
if (response.data.expired) {
|
||||||
|
store.dispatch('user/logout').then(() => {
|
||||||
|
router.replace({name: 'login'}); // 仍然失效,登出,跳转到登录界面
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
location.reload(); // 已登录,刷新页面
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch((error) => {
|
||||||
|
console.log("error: " + error);
|
||||||
|
ElMessage.error(error.message || '请求失败');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
const checkAuth = () => {
|
||||||
|
const token = store.state.user.userInfo.token
|
||||||
|
axios.get('/auth/statuscheck', {
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${token}`
|
||||||
|
}
|
||||||
|
}).then((response) => {
|
||||||
|
if (response.data.expired) {
|
||||||
|
showConfirmBox();
|
||||||
|
}
|
||||||
|
}).catch((error) => {
|
||||||
|
console.log("erro:" + error)
|
||||||
|
ElMessage.error(err)
|
||||||
|
});
|
||||||
|
}
|
||||||
onMounted(async () => {
|
onMounted(async () => {
|
||||||
store.commit('edit/setSurveyId', route.params.id)
|
store.commit('edit/setSurveyId', route.params.id)
|
||||||
|
|
||||||
@ -43,6 +84,13 @@ onMounted(async () => {
|
|||||||
router.replace({ name: 'survey' })
|
router.replace({ name: 'survey' })
|
||||||
}, 1000)
|
}, 1000)
|
||||||
}
|
}
|
||||||
|
// 启动定时器,每30分钟调用一次
|
||||||
|
authCheckInterval.value = setInterval(() => {
|
||||||
|
checkAuth()
|
||||||
|
}, 30 * 60 * 1000);
|
||||||
|
})
|
||||||
|
onUnmounted(() => {
|
||||||
|
clearInterval(authCheckInterval.value);
|
||||||
})
|
})
|
||||||
</script>
|
</script>
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
|
@ -4,20 +4,21 @@
|
|||||||
</el-button>
|
</el-button>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref } from 'vue'
|
import { ref, computed } from 'vue'
|
||||||
import { useStore } from 'vuex'
|
import { useStore } from 'vuex'
|
||||||
import { useRouter } from 'vue-router'
|
import { useRouter } from 'vue-router'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
import 'element-plus/theme-chalk/src/message.scss'
|
import 'element-plus/theme-chalk/src/message.scss'
|
||||||
|
import { publishSurvey, saveSurvey, getConflictHistory } from '@/management/api/survey'
|
||||||
import { publishSurvey, saveSurvey } from '@/management/api/survey'
|
|
||||||
import { showLogicEngine } from '@/management/hooks/useShowLogicEngine'
|
import { showLogicEngine } from '@/management/hooks/useShowLogicEngine'
|
||||||
import buildData from './buildData'
|
import buildData from './buildData'
|
||||||
|
|
||||||
const isPublishing = ref<boolean>(false)
|
const isPublishing = ref<boolean>(false)
|
||||||
const store = useStore()
|
const store = useStore()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const saveData = computed(() => {
|
||||||
|
return buildData(store.state.edit.schema, sessionStorage.getItem('sessionUUID'))
|
||||||
|
})
|
||||||
const updateLogicConf = () => {
|
const updateLogicConf = () => {
|
||||||
if (
|
if (
|
||||||
showLogicEngine.value &&
|
showLogicEngine.value &&
|
||||||
@ -31,6 +32,55 @@ const updateLogicConf = () => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const checkConflict = async (surveyid:string) => {
|
||||||
|
try {
|
||||||
|
const dailyHis = await getConflictHistory({surveyId: surveyid, historyType: 'dailyHis', sessionId: sessionStorage.getItem('sessionUUID')})
|
||||||
|
console.log(dailyHis)
|
||||||
|
if (dailyHis.data.length > 0) {
|
||||||
|
const lastHis = dailyHis.data.at(0)
|
||||||
|
if (Date.now() - lastHis.createDate > 2 * 60 * 1000) {
|
||||||
|
return [false, '']
|
||||||
|
} else {
|
||||||
|
return [true, lastHis.operator.username]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
}
|
||||||
|
return [false, '']
|
||||||
|
}
|
||||||
|
const onSave = async () => {
|
||||||
|
let res
|
||||||
|
|
||||||
|
if (!saveData.value.surveyId) {
|
||||||
|
ElMessage.error('未获取到问卷id')
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
// 增加冲突检测
|
||||||
|
const [isconflict, conflictName] = await checkConflict(saveData.value.surveyId)
|
||||||
|
if(isconflict) {
|
||||||
|
if (conflictName == store.state.user.userInfo.username) {
|
||||||
|
ElMessageBox.alert('当前问卷已在其它页面开启编辑,刷新以获取最新内容。', '提示', {
|
||||||
|
confirmButtonText: '确认',
|
||||||
|
callback: () => {
|
||||||
|
location.reload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
ElMessageBox.alert(`当前问卷2分钟内由${conflictName}编辑,刷新以获取最新内容。`, '提示', {
|
||||||
|
confirmButtonText: '确认',
|
||||||
|
callback: () => {
|
||||||
|
location.reload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
} else {
|
||||||
|
// 保存数据
|
||||||
|
res = await saveSurvey(saveData.value)
|
||||||
|
}
|
||||||
|
return res
|
||||||
|
}
|
||||||
const handlePublish = async () => {
|
const handlePublish = async () => {
|
||||||
if (isPublishing.value) {
|
if (isPublishing.value) {
|
||||||
return
|
return
|
||||||
@ -46,22 +96,15 @@ const handlePublish = async () => {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
const saveData = buildData(store.state.edit.schema)
|
|
||||||
if (!saveData.surveyId) {
|
|
||||||
isPublishing.value = false
|
|
||||||
ElMessage.error('未获取到问卷id')
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const saveRes: any = await saveSurvey(saveData)
|
const saveRes: any = await onSave()
|
||||||
if (saveRes.code !== 200) {
|
if (!saveRes) {
|
||||||
isPublishing.value = false
|
|
||||||
ElMessage.error(saveRes.errmsg || '问卷保存失败')
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
if(saveRes && saveRes?.code !== 200) {
|
||||||
const publishRes: any = await publishSurvey({ surveyId: saveData.surveyId })
|
ElMessage.error(`保存失败 ${saveRes.errmsg}`)
|
||||||
|
}
|
||||||
|
const publishRes: any = await publishSurvey({ surveyId: saveData.value.surveyId, sessionId: sessionStorage.getItem('sessionUUID') })
|
||||||
if (publishRes.code === 200) {
|
if (publishRes.code === 200) {
|
||||||
ElMessage.success('发布成功')
|
ElMessage.success('发布成功')
|
||||||
store.dispatch('edit/getSchemaFromRemote')
|
store.dispatch('edit/getSchemaFromRemote')
|
||||||
|
@ -14,15 +14,18 @@
|
|||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import { ref, computed, nextTick, watch } from 'vue'
|
import { ref, computed, nextTick, watch, onMounted } from 'vue'
|
||||||
import { useStore } from 'vuex'
|
import { useStore } from 'vuex'
|
||||||
import { get as _get } from 'lodash-es'
|
import { get as _get } from 'lodash-es'
|
||||||
import { ElMessage } from 'element-plus'
|
import { v4 as uuidv4 } from 'uuid'
|
||||||
|
|
||||||
|
import { ElMessage, ElMessageBox } from 'element-plus'
|
||||||
import 'element-plus/theme-chalk/src/message.scss'
|
import 'element-plus/theme-chalk/src/message.scss'
|
||||||
|
|
||||||
import { saveSurvey } from '@/management/api/survey'
|
import { saveSurvey } from '@/management/api/survey'
|
||||||
import { showLogicEngine } from '@/management/hooks/useShowLogicEngine'
|
import { showLogicEngine } from '@/management/hooks/useShowLogicEngine'
|
||||||
import buildData from './buildData'
|
import buildData from './buildData'
|
||||||
|
import { getSurveyHistory, getConflictHistory } from '@/management/api/survey'
|
||||||
|
|
||||||
const isSaving = ref<boolean>(false)
|
const isSaving = ref<boolean>(false)
|
||||||
const isShowAutoSave = ref<boolean>(false)
|
const isShowAutoSave = ref<boolean>(false)
|
||||||
@ -38,15 +41,61 @@ const saveText = computed(
|
|||||||
|
|
||||||
const store = useStore()
|
const store = useStore()
|
||||||
|
|
||||||
const saveData = async () => {
|
onMounted(() => {
|
||||||
const saveData = buildData(store.state.edit.schema)
|
if (!sessionStorage.getItem('sessionUUID')) {
|
||||||
|
sessionStorage.setItem('sessionUUID', uuidv4());
|
||||||
|
}
|
||||||
|
})
|
||||||
|
const checkConflict = async (surveyid: string) => {
|
||||||
|
try {
|
||||||
|
const dailyHis = await getConflictHistory({surveyId: surveyid, historyType: 'dailyHis', sessionId: sessionStorage.getItem('sessionUUID')})
|
||||||
|
//sconsole.log(dailyHis)
|
||||||
|
if (dailyHis.data.length > 0) {
|
||||||
|
const lastHis = dailyHis.data.at(0)
|
||||||
|
if (Date.now() - lastHis.createDate > 2 * 60 * 1000) {
|
||||||
|
return [false, '']
|
||||||
|
} else {
|
||||||
|
return [true, lastHis.operator.username]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}catch (error) {
|
||||||
|
console.log(error)
|
||||||
|
}
|
||||||
|
return [false, '']
|
||||||
|
}
|
||||||
|
|
||||||
|
const onSave = async () => {
|
||||||
|
let res
|
||||||
|
const saveData = buildData(store.state.edit.schema, sessionStorage.getItem('sessionUUID'))
|
||||||
|
|
||||||
if (!saveData.surveyId) {
|
if (!saveData.surveyId) {
|
||||||
ElMessage.error('未获取到问卷id')
|
ElMessage.error('未获取到问卷id')
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
const res = await saveSurvey(saveData)
|
// 增加冲突检测
|
||||||
|
const [isconflict, conflictName] = await checkConflict(saveData.surveyId)
|
||||||
|
if(isconflict) {
|
||||||
|
if (conflictName == store.state.user.userInfo.username) {
|
||||||
|
ElMessageBox.alert('当前问卷已在其它页面开启编辑,刷新以获取最新内容。', '提示', {
|
||||||
|
confirmButtonText: '确认',
|
||||||
|
callback: () => {
|
||||||
|
location.reload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
ElMessageBox.alert(`当前问卷2分钟内由${conflictName}编辑,刷新以获取最新内容。`, '提示', {
|
||||||
|
confirmButtonText: '确认',
|
||||||
|
callback: () => {
|
||||||
|
location.reload();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return null
|
||||||
|
} else {
|
||||||
|
// 保存数据
|
||||||
|
res = await saveSurvey(saveData)
|
||||||
|
}
|
||||||
return res
|
return res
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -78,7 +127,7 @@ const triggerAutoSave = () => {
|
|||||||
isShowAutoSave.value = true
|
isShowAutoSave.value = true
|
||||||
nextTick(async () => {
|
nextTick(async () => {
|
||||||
try {
|
try {
|
||||||
const res: any = await saveData()
|
const res: any = await handleSave()
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
autoSaveStatus.value = 'succeed'
|
autoSaveStatus.value = 'succeed'
|
||||||
} else {
|
} else {
|
||||||
@ -115,12 +164,17 @@ const handleSave = async () => {
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res: any = await saveData()
|
const res: any = await onSave()
|
||||||
|
if(!res) {
|
||||||
|
return
|
||||||
|
}
|
||||||
if (res.code === 200) {
|
if (res.code === 200) {
|
||||||
ElMessage.success('保存成功')
|
ElMessage.success('保存成功')
|
||||||
} else {
|
}
|
||||||
|
if(res.code !== 200) {
|
||||||
ElMessage.error(res.errmsg)
|
ElMessage.error(res.errmsg)
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
ElMessage.error('保存问卷失败')
|
ElMessage.error('保存问卷失败')
|
||||||
} finally {
|
} finally {
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { pick as _pick, get as _get } from 'lodash-es'
|
import { pick as _pick, get as _get } from 'lodash-es'
|
||||||
|
|
||||||
// 生成需要保存到接口的数据
|
// 生成需要保存到接口的数据
|
||||||
export default function (schema) {
|
export default function (schema, sessionId) {
|
||||||
const surveyId = _get(schema, 'metaData._id')
|
const surveyId = _get(schema, 'metaData._id')
|
||||||
const configData = _pick(schema, [
|
const configData = _pick(schema, [
|
||||||
'bannerConf',
|
'bannerConf',
|
||||||
@ -18,6 +18,7 @@ export default function (schema) {
|
|||||||
delete configData.questionDataList
|
delete configData.questionDataList
|
||||||
return {
|
return {
|
||||||
surveyId,
|
surveyId,
|
||||||
configData
|
configData,
|
||||||
|
sessionId
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user