feat: 修改验收问题 (#421)

This commit is contained in:
luch 2024-09-12 18:06:16 +08:00 committed by GitHub
parent 6cbfe20be1
commit b484b786ea
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
19 changed files with 235 additions and 146 deletions

View File

@ -1,5 +1,5 @@
XIAOJU_SURVEY_MONGO_DB_NAME= xiaojuSurvey XIAOJU_SURVEY_MONGO_DB_NAME=xiaojuSurvey
XIAOJU_SURVEY_MONGO_URL= # mongodb://localhost:27017 # 建议设置强密码 XIAOJU_SURVEY_MONGO_URL= # mongodb://127.0.0.1:27017 # 建议设置强密码
XIAOJU_SURVEY_MONGO_AUTH_SOURCE= # admin XIAOJU_SURVEY_MONGO_AUTH_SOURCE= # admin
XIAOJU_SURVEY_REDIS_HOST= XIAOJU_SURVEY_REDIS_HOST=

2
server/.gitignore vendored
View File

@ -39,3 +39,5 @@ yarn.lock
!.vscode/extensions.json !.vscode/extensions.json
tmp tmp
exportfile
userUpload

View File

@ -12,4 +12,7 @@ export class Session extends BaseEntity {
@Column() @Column()
surveyId: string; surveyId: string;
@Column()
userId: string;
} }

View File

@ -79,15 +79,16 @@ export class DownloadTaskController {
async downloadList( async downloadList(
@Query() @Query()
queryInfo: GetDownloadTaskListDto, queryInfo: GetDownloadTaskListDto,
@Request() req,
) { ) {
const { value, error } = GetDownloadTaskListDto.validate(queryInfo); const { value, error } = GetDownloadTaskListDto.validate(queryInfo);
if (error) { if (error) {
this.logger.error(error.message); this.logger.error(error.message);
throw new HttpException('参数有误', EXCEPTION_CODE.PARAMETER_ERROR); throw new HttpException('参数有误', EXCEPTION_CODE.PARAMETER_ERROR);
} }
const { ownerId, pageIndex, pageSize } = value; const { pageIndex, pageSize } = value;
const { total, list } = await this.downloadTaskService.getDownloadTaskList({ const { total, list } = await this.downloadTaskService.getDownloadTaskList({
ownerId, ownerId: req.user._id.toString(),
pageIndex, pageIndex,
pageSize, pageSize,
}); });

View File

@ -39,6 +39,8 @@ export class SessionController {
reqBody: { reqBody: {
surveyId: string; surveyId: string;
}, },
@Request()
req,
) { ) {
const { value, error } = Joi.object({ const { value, error } = Joi.object({
surveyId: Joi.string().required(), surveyId: Joi.string().required(),
@ -50,7 +52,10 @@ export class SessionController {
} }
const surveyId = value.surveyId; const surveyId = value.surveyId;
const session = await this.sessionService.create({ surveyId }); const session = await this.sessionService.create({
surveyId,
userId: req.user._id.toString(),
});
return { return {
code: 200, code: 200,

View File

@ -34,6 +34,7 @@ import { WorkspaceGuard } from 'src/guards/workspace.guard';
import { PERMISSION as WORKSPACE_PERMISSION } from 'src/enums/workspace'; import { PERMISSION as WORKSPACE_PERMISSION } from 'src/enums/workspace';
import { SessionService } from '../services/session.service'; import { SessionService } from '../services/session.service';
import { MemberType, WhitelistType } from 'src/interfaces/survey'; import { MemberType, WhitelistType } from 'src/interfaces/survey';
import { UserService } from 'src/modules/auth/services/user.service';
@ApiTags('survey') @ApiTags('survey')
@Controller('/api/survey') @Controller('/api/survey')
@ -47,6 +48,7 @@ export class SurveyController {
private readonly logger: XiaojuSurveyLogger, private readonly logger: XiaojuSurveyLogger,
private readonly counterService: CounterService, private readonly counterService: CounterService,
private readonly sessionService: SessionService, private readonly sessionService: SessionService,
private readonly userService: UserService,
) {} ) {}
@Get('/getBannerData') @Get('/getBannerData')
@ -146,11 +148,21 @@ export class SurveyController {
if (latestEditingOne && latestEditingOne._id.toString() !== sessionId) { if (latestEditingOne && latestEditingOne._id.toString() !== sessionId) {
const curSession = await this.sessionService.findOne(sessionId); const curSession = await this.sessionService.findOne(sessionId);
if (curSession.createDate <= latestEditingOne.updateDate) { if (curSession.createDate <= latestEditingOne.updateDate) {
// 在当前用户打开之后,有人保存过了 // 在当前用户打开之后,被其他页面保存过了
throw new HttpException( const isSameOperator =
'当前问卷已在其它页面开启编辑', latestEditingOne.userId === req.user._id.toString();
EXCEPTION_CODE.SURVEY_SAVE_CONFLICT, let preOperator;
); if (!isSameOperator) {
preOperator = await this.userService.getUserById(
latestEditingOne.userId,
);
}
return {
code: EXCEPTION_CODE.SURVEY_SAVE_CONFLICT,
errmsg: isSameOperator
? '当前问卷已在其它页面开启编辑,刷新以获取最新内容'
: `当前问卷已由 ${preOperator.username} 编辑,刷新以获取最新内容`,
};
} }
} }
await this.sessionService.updateSessionToEditing({ sessionId, surveyId }); await this.sessionService.updateSessionToEditing({ sessionId, surveyId });
@ -331,11 +343,6 @@ export class SurveyController {
pageId: surveyId, pageId: surveyId,
}); });
await this.counterService.createCounters({
surveyPath: surveyMeta.surveyPath,
dataList: surveyConf.code.dataConf.dataList,
});
await this.surveyHistoryService.addHistory({ await this.surveyHistoryService.addHistory({
surveyId, surveyId,
schema: surveyConf.code, schema: surveyConf.code,

View File

@ -13,6 +13,7 @@ import { load } from 'cheerio';
import { get } from 'lodash'; import { get } from 'lodash';
import { FileService } from 'src/modules/file/services/file.service'; import { FileService } from 'src/modules/file/services/file.service';
import { XiaojuSurveyLogger } from 'src/logger'; import { XiaojuSurveyLogger } from 'src/logger';
import moment from 'moment';
@Injectable() @Injectable()
export class DownloadTaskService { export class DownloadTaskService {
@ -41,6 +42,7 @@ export class DownloadTaskService {
operatorId: string; operatorId: string;
params: any; params: any;
}) { }) {
const filename = `${responseSchema.title}-${params.isDesensitive ? '脱敏' : '原'}回收数据-${moment().format('YYYYMMDDHHmmss')}.xlsx`;
const downloadTask = this.downloadTaskRepository.create({ const downloadTask = this.downloadTaskRepository.create({
surveyId, surveyId,
surveyPath: responseSchema.surveyPath, surveyPath: responseSchema.surveyPath,
@ -50,6 +52,7 @@ export class DownloadTaskService {
...params, ...params,
title: responseSchema.title, title: responseSchema.title,
}, },
filename,
}); });
await this.downloadTaskRepository.save(downloadTask); await this.downloadTaskRepository.save(downloadTask);
return downloadTask._id.toString(); return downloadTask._id.toString();
@ -65,7 +68,7 @@ export class DownloadTaskService {
pageSize: number; pageSize: number;
}) { }) {
const where = { const where = {
onwer: ownerId, ownerId,
'curStatus.status': { 'curStatus.status': {
$ne: RECORD_STATUS.REMOVED, $ne: RECORD_STATUS.REMOVED,
}, },
@ -209,16 +212,12 @@ export class DownloadTaskService {
{ name: 'sheet1', data: xlsxData, options: {} }, { name: 'sheet1', data: xlsxData, options: {} },
]); ]);
const isDesensitive = taskInfo.params?.isDesensitive;
const originalname = `${taskInfo.params.title}-${isDesensitive ? '脱敏' : '原'}回收数据.xlsx`;
const file: Express.Multer.File = { const file: Express.Multer.File = {
fieldname: 'file', fieldname: 'file',
originalname: originalname, originalname: taskInfo.filename,
encoding: '7bit', encoding: '7bit',
mimetype: 'application/octet-stream', mimetype: 'application/octet-stream',
filename: originalname, filename: taskInfo.filename,
size: buffer.length, size: buffer.length,
buffer: buffer, buffer: buffer,
stream: null, stream: null,
@ -246,7 +245,6 @@ export class DownloadTaskService {
$set: { $set: {
curStatus, curStatus,
url, url,
filename: originalname,
fileKey: key, fileKey: key,
fileSize: buffer.length, fileSize: buffer.length,
}, },

View File

@ -12,9 +12,10 @@ export class SessionService {
private readonly sessionRepository: MongoRepository<Session>, private readonly sessionRepository: MongoRepository<Session>,
) {} ) {}
create({ surveyId }) { create({ surveyId, userId }) {
const session = this.sessionRepository.create({ const session = this.sessionRepository.create({
surveyId, surveyId,
userId,
}); });
return this.sessionRepository.save(session); return this.sessionRepository.save(session);
} }

View File

@ -2,7 +2,6 @@ import { Controller, Post, Body, HttpCode } from '@nestjs/common';
import { HttpException } from 'src/exceptions/httpException'; import { HttpException } from 'src/exceptions/httpException';
import { SurveyNotFoundException } from 'src/exceptions/surveyNotFoundException'; import { SurveyNotFoundException } from 'src/exceptions/surveyNotFoundException';
import { checkSign } from 'src/utils/checkSign'; import { checkSign } from 'src/utils/checkSign';
import { cleanRichTextWithMediaTag } from 'src/utils/xss'
import { ENCRYPT_TYPE } from 'src/enums/encrypt'; import { ENCRYPT_TYPE } from 'src/enums/encrypt';
import { EXCEPTION_CODE } from 'src/enums/exceptionCode'; import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
import { getPushingData } from 'src/utils/messagePushing'; import { getPushingData } from 'src/utils/messagePushing';
@ -23,6 +22,14 @@ import { XiaojuSurveyLogger } from 'src/logger';
import { WhitelistType } from 'src/interfaces/survey'; import { WhitelistType } from 'src/interfaces/survey';
import { UserService } from 'src/modules/auth/services/user.service'; import { UserService } from 'src/modules/auth/services/user.service';
import { WorkspaceMemberService } from 'src/modules/workspace/services/workspaceMember.service'; import { WorkspaceMemberService } from 'src/modules/workspace/services/workspaceMember.service';
import { QUESTION_TYPE } from 'src/enums/question';
const optionQuestionType: Array<string> = [
QUESTION_TYPE.RADIO,
QUESTION_TYPE.CHECKBOX,
QUESTION_TYPE.BINARY_CHOICE,
QUESTION_TYPE.VOTE,
];
@ApiTags('surveyResponse') @ApiTags('surveyResponse')
@Controller('/api/surveyResponse') @Controller('/api/surveyResponse')
@ -207,6 +214,7 @@ export class SurveyResponseController {
const optionTextAndId = dataList const optionTextAndId = dataList
.filter((questionItem) => { .filter((questionItem) => {
return ( return (
optionQuestionType.includes(questionItem.type) &&
Array.isArray(questionItem.options) && Array.isArray(questionItem.options) &&
questionItem.options.length > 0 && questionItem.options.length > 0 &&
decryptedData[questionItem.field] decryptedData[questionItem.field]
@ -222,20 +230,23 @@ export class SurveyResponseController {
return pre; return pre;
}, {}); }, {});
// 使用redis作为锁校验选项配额
const surveyId = responseSchema.pageId; const surveyId = responseSchema.pageId;
const lockKey = `locks:optionSelectedCount:${surveyId}`; const lockKey = `locks:optionSelectedCount:${surveyId}`;
const lock = await this.redisService.lockResource(lockKey, 1000); const lock = await this.redisService.lockResource(lockKey, 1000);
this.logger.info(`lockKey: ${lockKey}`); this.logger.info(`lockKey: ${lockKey}`);
try { try {
const successParams = [];
for (const field in decryptedData) { for (const field in decryptedData) {
const value = decryptedData[field]; const value = decryptedData[field];
const values = Array.isArray(value) ? value : [value]; const values = Array.isArray(value) ? value : [value];
if (field in optionTextAndId) { if (field in optionTextAndId) {
const optionCountData = await this.counterService.get({ const optionCountData =
key: field, (await this.counterService.get({
surveyPath, key: field,
type: 'option', surveyPath,
}); type: 'option',
})) || {};
//遍历选项hash值 //遍历选项hash值
for (const val of values) { for (const val of values) {
@ -243,38 +254,41 @@ export class SurveyResponseController {
(opt) => opt['hash'] === val, (opt) => opt['hash'] === val,
); );
const quota = parseInt(option['quota']); const quota = parseInt(option['quota']);
if (quota !== 0 && quota <= optionCountData[val]) { if (
const item = dataList.find((item) => item['field'] === field); quota &&
throw new HttpException( optionCountData?.[val] &&
`${cleanRichTextWithMediaTag(item['title'])}】中的【${cleanRichTextWithMediaTag(option['text'])}】所选人数已达到上限,请重新选择`, quota <= optionCountData[val]
EXCEPTION_CODE.RESPONSE_OVER_LIMIT, ) {
); return {
code: EXCEPTION_CODE.RESPONSE_OVER_LIMIT,
data: {
field,
optionHash: option.hash,
},
};
} }
if (!optionCountData[val]) {
optionCountData[val] = 0;
}
optionCountData[val]++;
} }
} if (!optionCountData['total']) {
} optionCountData['total'] = 1;
} else {
for (const field in decryptedData) { optionCountData['total']++;
const value = decryptedData[field]; }
const values = Array.isArray(value) ? value : [value]; successParams.push({
if (field in optionTextAndId) {
const optionCountData = await this.counterService.get({
key: field, key: field,
surveyPath, surveyPath,
type: 'option', type: 'option',
data: optionCountData,
}); });
for (const val of values) {
optionCountData[val]++;
this.counterService.set({
key: field,
surveyPath,
type: 'option',
data: optionCountData,
});
}
optionCountData['total']++;
} }
} }
// 校验通过后统一更新
await Promise.all(
successParams.map((item) => this.counterService.set(item)),
);
} catch (error) { } catch (error) {
this.logger.error(error.message); this.logger.error(error.message);
throw error; throw error;

View File

@ -65,25 +65,4 @@ export class CounterService {
return pre; return pre;
}, {}); }, {});
} }
async createCounters({ surveyPath, dataList }) {
const optionList = dataList.filter((questionItem) => {
return (
Array.isArray(questionItem.options) && questionItem.options.length > 0
);
});
optionList.forEach((option) => {
const data = {};
option.options.forEach((option) => {
data[option.hash] = 0;
});
data['total'] = 0;
this.set({
surveyPath,
key: option.field,
type: 'option',
data: data,
});
});
}
} }

View File

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

View File

@ -60,6 +60,7 @@
<script setup> <script setup>
import { ref } from 'vue' import { ref } from 'vue'
import ImagePreview from './ImagePreview.vue' import ImagePreview from './ImagePreview.vue'
import { cleanRichTextWithMediaTag } from '@/common/xss'
const props = defineProps({ const props = defineProps({
tableData: { tableData: {
@ -78,8 +79,16 @@ const popoverVirtualRef = ref()
const popoverContent = ref('') const popoverContent = ref('')
const getContent = (content) => { const getContent = (content) => {
// const content = cleanRichText(value) if (Array.isArray(content)) {
return content === 0 ? 0 : content || '未知' return content.map(item => getContent(item)).join(',');
}
if (content === null || content === undefined) {
return ''
}
if (typeof content !== 'string') {
content = content + ''
}
return cleanRichTextWithMediaTag(content) || '未知'
} }
const setPopoverContent = (content) => { const setPopoverContent = (content) => {
popoverContent.value = content popoverContent.value = content

View File

@ -32,14 +32,22 @@
v-model="downloadDialogVisible" v-model="downloadDialogVisible"
title="导出确认" title="导出确认"
width="500" width="500"
style="padding: 40px;"
> >
<el-form :model="downloadForm"> <el-form :model="downloadForm" label-width="100px" label-position="left" >
<el-form-item label="导出内容"> <el-form-item label="导出内容">
<el-radio-group v-model="downloadForm.isDesensitive"> <el-radio-group v-model="downloadForm.isDesensitive">
<el-radio :value="true">脱敏数据</el-radio> <el-radio :value="true">脱敏数据</el-radio>
<el-radio value="Venue">原回收数据</el-radio> <el-radio :value="false">原回收数据</el-radio>
</el-radio-group> </el-radio-group>
</el-form-item> </el-form-item>
<div class="download-tips">
<div></div>
<div>
<p>推荐优先下载脱敏数据如手机号1***3</p>
<p>原回收数据可能存在敏感信息请谨慎下载</p>
</div>
</div>
</el-form> </el-form>
<template #footer> <template #footer>
<div class="dialog-footer"> <div class="dialog-footer">
@ -163,9 +171,9 @@ const confirmDownload = async () => {
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 dataTableState.downloadDialogVisible = false
if (createRes.code === 200) { if (createRes.code === 200) {
ElMessage.success(`下载文件计算中,可前往“下载中心”查看`)
try { try {
const taskInfo = await checkIsTaskFinished(createRes.data.taskId) const taskInfo = await checkIsTaskFinished(createRes.data.taskId)
console.log(taskInfo)
if (taskInfo.url) { if (taskInfo.url) {
window.open(taskInfo.url) window.open(taskInfo.url)
ElMessage.success("导出成功") ElMessage.success("导出成功")
@ -217,6 +225,11 @@ const checkIsTaskFinished = (taskId) => {
overflow: hidden; overflow: hidden;
} }
.download-tips {
display: flex;
color: #ec4e29;
}
.menus { .menus {
margin-bottom: 20px; margin-bottom: 20px;
} }

View File

@ -43,7 +43,7 @@ const handleLogout = () => {
const activeIndex = ref('2') const activeIndex = ref('2')
</script> </script>
<style> <style lang="scss" scoped>
.question-list-root { .question-list-root {
height: 100%; height: 100%;
background-color: #f6f7f9; background-color: #f6f7f9;
@ -94,7 +94,7 @@ const activeIndex = ref('2')
} }
} }
.table-container { .table-container {
padding: 20px; margin-top: 20px;
display: flex; display: flex;
justify-content: center; justify-content: center;
width: 100%; /* 确保容器宽度为100% */ width: 100%; /* 确保容器宽度为100% */

View File

@ -19,14 +19,14 @@
:prop="field.key" :prop="field.key"
:label="field.title" :label="field.title"
:width="field.width" :width="field.width"
class-name="link" :class-name="[field.key]"
:formatter="field.formatter" :formatter="field.formatter"
> >
</el-table-column> </el-table-column>
<el-table-column label="操作" width="200"> <el-table-column label="操作" width="200">
<template v-slot="{ row }"> <template v-slot="{ row }">
<el-button size="small" @click="handleDownload(row)"> 下载 </el-button> <span v-if="row.curStatus?.status === 'finished'" class="text-btn download-btn" @click="handleDownload(row)"> 下载 </span>
<el-button type="primary" size="small" @click="openDeleteDialog(row)"> 删除 </el-button> <span class="text-btn delete-btn" @click="openDeleteDialog(row)"> 删除 </span>
</template> </template>
</el-table-column> </el-table-column>
</el-table> </el-table>
@ -35,31 +35,21 @@
background background
layout="prev, pager, next" layout="prev, pager, next"
:total="total" :total="total"
:size="pageSize" small
:page-size="pageSize"
@current-change="handleCurrentChange" @current-change="handleCurrentChange"
> >
</el-pagination> </el-pagination>
</div> </div>
<el-dialog v-model="centerDialogVisible" title="" width="500" align-center>
<span>确认删除下载记录吗</span>
<template #footer>
<div class="dialog-footer">
<el-button @click="centerDialogVisible = false"> 取消 </el-button>
<el-button type="primary" @click="confirmDelete"> 确认 </el-button>
</div>
</template>
</el-dialog>
</div> </div>
</template> </template>
<script setup lang="ts"> <script setup lang="ts">
import { ref, reactive, computed, onMounted } from 'vue' import { ref, reactive, computed, onMounted } from 'vue'
import { get, map } from 'lodash-es' import { get, map } from 'lodash-es'
import { ElMessage } from 'element-plus' import { ElMessage, ElMessageBox } from 'element-plus'
import { deleteDownloadTask, getDownloadTaskList } from '@/management/api/downloadTask' import { deleteDownloadTask, getDownloadTaskList } from '@/management/api/downloadTask'
import { CODE_MAP } from '@/management/api/base' import { CODE_MAP } from '@/management/api/base'
import 'element-plus/theme-chalk/src/message.scss'
import 'element-plus/theme-chalk/src/message-box.scss'
import moment from 'moment' import moment from 'moment'
// //
@ -68,9 +58,9 @@ import 'moment/locale/zh-cn'
moment.locale('zh-cn') moment.locale('zh-cn')
const loading = ref(false) const loading = ref(false)
const pageSize = ref(15) const pageSize = ref(10)
const total = ref(0) const total = ref(0)
const dataList = reactive([]) const dataList: Array<any> = reactive([])
onMounted(() => { onMounted(() => {
getList({ pageIndex: 1 }) getList({ pageIndex: 1 })
@ -87,7 +77,8 @@ const getList = async ({ pageIndex }: { pageIndex: number }) => {
const res: Record<string, any> = await getDownloadTaskList(params) const res: Record<string, any> = await getDownloadTaskList(params)
if (res.code === CODE_MAP.SUCCESS) { if (res.code === CODE_MAP.SUCCESS) {
total.value = res.data.total total.value = res.data.total
dataList.values = res.data.list const list = res.data.list as any
dataList.splice(0, dataList.length, ...list);
} }
loading.value = false loading.value = false
} }
@ -99,7 +90,6 @@ const statusTextMap: Record<string, string> = {
removed: '已删除', removed: '已删除',
}; };
const centerDialogVisible = ref(false)
let currentDelRow: Record<string, any> = {} let currentDelRow: Record<string, any> = {}
// //
const handleDownload = async (row: any) => { const handleDownload = async (row: any) => {
@ -112,20 +102,33 @@ const handleDownload = async (row: any) => {
} }
} }
// //
const openDeleteDialog = (row: any) => { const openDeleteDialog = async (row: any) => {
centerDialogVisible.value = true try {
currentDelRow = row await ElMessageBox.confirm('是否确认删除?', '提示', {
confirmButtonText: '确定',
cancelButtonText: '取消',
type: 'warning'
})
currentDelRow = row
confirmDelete()
} catch (error) {
console.log('取消删除')
}
} }
// //
const confirmDelete = async () => { const confirmDelete = async () => {
try { try {
await deleteDownloadTask(currentDelRow.taskId) const res: Record<string, any> = await deleteDownloadTask(currentDelRow.taskId)
await getList({ pageIndex: 1 }) if (res.code !== CODE_MAP.SUCCESS) {
ElMessage.error(res.errmsg)
} else {
ElMessage.success('删除成功');
await getList({ pageIndex: 1 })
}
} catch (error) { } catch (error) {
ElMessage.error("删除失败,请刷新重试") ElMessage.error("删除失败,请刷新重试")
} }
centerDialogVisible.value = false
} }
const fields = ['filename', 'fileSize', 'createDate', 'curStatus'] const fields = ['filename', 'fileSize', 'createDate', 'curStatus']
@ -177,13 +180,30 @@ const handleCurrentChange = (val: number) => {
background-color: #f6f7f9; background-color: #f6f7f9;
.list-wrapper { .list-wrapper {
width: 90%;
min-width: 1080px;
padding: 10px 20px; padding: 10px 20px;
background: #fff; background: #fff;
margin: 0 auto;
.list-table { .list-table {
.cell { .cell {
text-align: center; text-align: center;
} }
.text-btn {
font-size: 14px;
cursor: pointer;
margin-left: 20px;
&:first-child {
margin-left: 0;
}
}
.download-btn {
color: $primary-color;
}
.delete-btn {
color: red;
}
} }
.small-text { .small-text {
color: red; color: red;

View File

@ -7,11 +7,12 @@
import { ref, computed } from 'vue' import { ref, computed } from 'vue'
import { useEditStore } from '@/management/stores/edit' import { useEditStore } from '@/management/stores/edit'
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 } from '@/management/api/survey' import { publishSurvey, saveSurvey, seizeSession } from '@/management/api/survey'
import buildData from './buildData' import buildData from './buildData'
import { storeToRefs } from 'pinia' import { storeToRefs } from 'pinia'
import { CODE_MAP } from '@/management/api/base'
interface Props { interface Props {
updateLogicConf: any updateLogicConf: any
@ -27,6 +28,16 @@ const { schema, sessionId } = storeToRefs(editStore)
const saveData = computed(() => { const saveData = computed(() => {
return buildData(schema.value, sessionId.value) 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 router = useRouter()
const validate = () => { const validate = () => {
@ -61,8 +72,33 @@ const onSave = async () => {
return null return null
} }
const res: Record<string, any> = await saveSurvey(saveData.value) try {
return res const res: any = await saveSurvey(saveData.value)
if(!res) {
return null
}
if (res.code === 200) {
ElMessage.success('保存成功')
return res
} else if (res.code === 3006) {
ElMessageBox.alert(res.errmsg, '提示', {
confirmButtonText: '刷新同步',
callback: (action: string) => {
if (action === 'confirm') {
seize();
}
}
});
return null
} else {
ElMessage.error(res.errmsg)
return null
}
} catch (error) {
ElMessage.error('保存问卷失败')
return null
}
} }
const handlePublish = async () => { const handlePublish = async () => {
if (isPublishing.value) { if (isPublishing.value) {
@ -81,11 +117,7 @@ const handlePublish = async () => {
try { try {
const saveRes: any = await onSave() const saveRes: any = await onSave()
if (!saveRes) { if (!saveRes || saveRes.code !== CODE_MAP.SUCCESS) {
return
}
if(saveRes && saveRes?.code !== 200) {
ElMessage.error(`保存失败 ${saveRes.errmsg}`)
return return
} }
const publishRes: any = await publishSurvey({ surveyId: saveData.value.surveyId }) const publishRes: any = await publishSurvey({ surveyId: saveData.value.surveyId })

View File

@ -86,7 +86,7 @@ const onSave = async () => {
} }
const seize = async () => { const seize = async () => {
const seizeRes: Record<string, any> = await seizeSession({ sessionId:sessionId.value }) const seizeRes: Record<string, any> = await seizeSession({ sessionId: sessionId.value })
if (seizeRes.code === 200) { if (seizeRes.code === 200) {
location.reload(); location.reload();
} else { } else {
@ -154,10 +154,12 @@ const handleSave = async () => {
ElMessage.success('保存成功') ElMessage.success('保存成功')
return res return res
} else if (res.code === 3006) { } else if (res.code === 3006) {
ElMessageBox.alert('当前问卷已在其它页面开启编辑,点击“抢占”以获取保存权限。', '提示', { ElMessageBox.alert(res.errmsg, '提示', {
confirmButtonText: '抢占', confirmButtonText: '刷新同步',
callback: () => { callback: (action: string) => {
seize(); if (action === 'confirm') {
seize();
}
} }
}); });
} else { } else {

View File

@ -187,7 +187,7 @@ const handleLogout = () => {
} }
// //
const handleDownload = () => { const handleDownload = () => {
router.push('/survey/downloadTask/') router.push({ name: 'download' })
} }
</script> </script>

View File

@ -9,6 +9,9 @@ import { SurveyPermissions } from '@/management/utils/types/workSpace'
import { analysisTypeMap } from '@/management/config/analysisConfig' import { analysisTypeMap } from '@/management/config/analysisConfig'
import { ElMessage } from 'element-plus' import { ElMessage } from 'element-plus'
import 'element-plus/theme-chalk/src/message.scss' import 'element-plus/theme-chalk/src/message.scss'
import 'element-plus/theme-chalk/src/message-box.scss'
import 'element-plus/theme-chalk/src/button.scss'
import 'element-plus/theme-chalk/src/overlay.scss'
import { useUserStore } from '@/management/stores/user' import { useUserStore } from '@/management/stores/user'
import { useEditStore } from '@/management/stores/edit' import { useEditStore } from '@/management/stores/edit'
@ -27,7 +30,7 @@ const routes: RouteRecordRaw[] = [
} }
}, },
{ {
path: '/survey/downloadTask/', path: '/download',
name: 'download', name: 'download',
component: () => import('../pages/downloadTask/TaskList.vue'), component: () => import('../pages/downloadTask/TaskList.vue'),
meta: { meta: {