Marking.MakeTemplate.Tool/src/pages/Home/index.vue

3520 lines
101 KiB
Vue

<template>
<div class="warp">
<div class="warp-title">{{ templateInfo.name || "无" }}</div>
<div class="warp-nav">
<div class="warp-nav-tab">
<div class="warp-nav-tab-list">
<div
v-for="item in navList"
@click="setStepClick(item.index)"
:title="item.title"
>
<el-icon
:size="20"
style="margin: 2px 4px 0 0"
:color="'#2D8CF0'"
v-if="item.index < 3"
>
<SuccessFilled v-if="showImage(item.index)" />
<CircleClose v-else />
</el-icon>
<span :class="item.index === navListIndex ? 'active' : ''">{{
item.title
}}</span>
<img src="@/assets/image/icon1.png" alt="" v-if="item.index !== 5" />
</div>
</div>
<div
class="warp-nav-tab-delete"
title="清空全部痕迹"
@click="clearFabricGroup"
v-if="!isLockStatus"
>
<img src="@/assets/image/icon2.png" alt="" />
<span>清空全部痕迹</span>
</div>
</div>
<div class="warp-nav-title">
<div class="warp-nav-title-text">
<span>{{ placeholderText }}</span>
<span style="margin: 0 10px" v-if="operationText">|</span>
<span
style="color: red; font-weight: bold; border-bottom: 1px red solid"
v-if="operationText"
>{{ operationText }}</span
>
<el-radio-group v-model="otherType" v-if="navListIndex === 5">
<el-radio :value="1">缺考标记</el-radio>
<el-radio :value="2">屏蔽区标记</el-radio>
<el-radio :value="3">反面文字识别定位</el-radio>
</el-radio-group>
</div>
<div class="warp-nav-title-operation">
<div class="warp-nav-title-operation-btn" v-if="templateInfo.imgSrc.length > 1">
<div :class="{ active: imgSrcIndex === 0 }" @click="changeImgSrcIndex(0)">
正面
</div>
<div :class="{ active: imgSrcIndex === 1 }" @click="changeImgSrcIndex(1)">
反面
</div>
</div>
<div class="warp-nav-title-operation-other" v-if="navListIndex !== 1">
<div @click="changeZoom()">恢复大小</div>
</div>
<div class="warp-nav-title-operation-icon" v-if="navListIndex !== 1">
<img
src="@/assets/image/icon4.png"
alt=""
@click="changeZoom(true)"
title="放大画布"
/>
<img
src="@/assets/image/icon3.png"
alt=""
@click="changeZoom(false)"
title="缩小画布"
/>
</div>
</div>
</div>
</div>
<div class="warp-content">
<div class="warp-content-left">
<div class="warp-content-left-box" @click="contextmenu = null">
<div :class="templateInfo.type" @contextmenu.prevent="onContextmenu">
<div id="zoom-div">
<div
class="border-gray"
v-if="setBoderLine()"
:style="setBoderLine()"
></div>
<div class="menu-button" v-if="contextmenu">
<div title="置顶" @click="changeZindex(true)">置顶</div>
<div title="置底" @click="changeZindex(false)">置底</div>
</div>
<img
:src="templateInfo.imgSrc[imgSrcIndex]"
alt=""
id="picture-img"
style="user-select: none"
@load="canvasLoad"
/>
<canvas id="picture"></canvas>
</div>
</div>
</div>
</div>
<div class="warp-content-right">
<div class="warp-content-right-info">
<div class="warp-content-right-info-fb">
定位点:<span>{{ templateInfo.hasPosition ? "有" : "无" }}</span>
</div>
<div class="warp-content-right-info-fb">
纸张类型:<span>{{ templateInfo.type }}</span>
</div>
<div class="warp-content-right-info-fb">试题列表:</div>
<div class="warp-content-right-info-table">
<el-table
:data="templateInfo.questionList"
stripe
border
size="small"
max-height="540px"
>
<el-table-column label="题型" width="60px" align="center">
<template #default="scope">
{{ scope.row.fabricType === 3 ? "客观题" : "主观题" }}
</template>
</el-table-column>
<el-table-column label="题号" width="60px" align="center">
<template #default="scope">
{{ scope.row.customFrom.questionBefore }}-{{
scope.row.customFrom.questionAfter
}}
</template>
</el-table-column>
<el-table-column label="排列" width="50px" align="center">
<template #default="scope">
<span v-if="scope.row.customFrom.range === 0">横向</span>
<span v-else-if="scope.row.customFrom.range === 1">竖向</span>
<span v-else>无</span>
</template>
</el-table-column>
<el-table-column label="题量" width="50px" align="center">
<template #default="scope">
{{
scope.row.fabricType === 3
? scope.row.customFrom.questionNumber
: scope.row.customFrom.questionAfter -
scope.row.customFrom.questionBefore +
1
}}
</template>
</el-table-column>
<el-table-column label="每题分数" align="center">
<template #default="scope">
{{ scope.row.customFrom.score }}
</template>
</el-table-column>
</el-table>
</div>
<div class="warp-content-right-info-btn">
<el-button
type="primary"
size="large"
@click="setStepClick(navListIndex + 1)"
v-if="navListIndex !== 5"
>下一步</el-button
>
<el-button type="primary" size="large" @click="btnClick('save')"
>保 存</el-button
>
<el-button
type="primary"
size="large"
@click="btnClick('lock')"
:disabled="isLockStatus || isLockDisabled"
>{{ isLockStatus ? "已锁定" : "锁 定" }}</el-button
>
</div>
</div>
</div>
</div>
<!-- 框选学科 -->
<el-drawer
:modal="false"
v-model="drawerList.subjectDrawer"
title="请选择出学科字样(没有学科选择其他字样)"
:close-on-click-modal="false"
:close-on-press-escape="false"
:show-close="false"
class="drawer-custom-style"
>
<div class="drawer-custom-style-body">
<div class="drawer-custom-style-body-content">
<el-check-tag
:checked="item.checked"
type="primary"
v-for="item in textList"
@change="item.checked = !item.checked"
style="margin: 0 10px 10px 0"
>
{{ item.text }}
</el-check-tag>
</div>
<div class="drawer-custom-style-body-footer">
<el-button type="primary" @click="changeFabricInfo(true)">确认</el-button>
<el-button @click="changeFabricInfo(false)">取消</el-button>
</div>
</div>
</el-drawer>
<!-- 框选考号-->
<el-drawer
:modal="false"
v-model="drawerList.examIdDrawer"
title="编辑属性"
:close-on-click-modal="false"
:close-on-press-escape="false"
:show-close="false"
class="drawer-custom-style"
>
<div class="drawer-custom-style-body">
<div class="drawer-custom-style-body-content examId">
<div>
<span>考试类型:</span>
<div>
<el-check-tag
type="primary"
:checked="drawerForm.examId.examType === 1"
style="margin: 0 10px 0 0"
@change="changeExamType(1)"
>填涂式</el-check-tag
>
<el-check-tag
type="primary"
@change="changeExamType(2)"
:checked="drawerForm.examId.examType === 2"
>条码式</el-check-tag
>
</div>
</div>
<div v-if="drawerForm.examId.examType === 1">
<span>考号长度:</span>
<div>
<el-input-number
:disabled="isLockStatus"
:min="4"
:max="12"
v-model="drawerForm.examId.examIdLength"
:step-strictly="true"
/>
</div>
</div>
<!-- <div>
暂时不需要
<span>考号样式:</span>
<div>
<el-radio-group v-model="drawerForm.examId.examIdStyle">
<el-radio :value="1">0 1 2 3 4 5 6 7 8 9</el-radio>
<el-radio :value="2">1 2 3 4 5 6 7 8 9 0</el-radio>
</el-radio-group>
</div>
</div> -->
<div class="full-filling" v-if="drawerForm.examId.examType === 1">
<span>示意图</span>
<div>
<div v-for="item in 10">
<span v-for="row in drawerForm.examId.examIdLength" :name="row">{{
item - 1
}}</span>
</div>
</div>
</div>
<div class="bar-code" v-else>
<span>示意图</span>
<div>
<div>贴条形码区</div>
</div>
</div>
<FabricSize
:change="changeFabricPositionNumber"
ref="FabricSizeRef"
v-if="currentFabric"
/>
</div>
<div class="drawer-custom-style-body-footer">
<el-button type="primary" @click="changeFabricInfo(true)">确认</el-button>
<el-button @click="changeFabricInfo(false)">取消</el-button>
</div>
</div>
</el-drawer>
<!-- 框选客观题 -->
<el-drawer
:modal="false"
v-model="drawerList.objectiveDrawer"
title="编辑属性"
:close-on-click-modal="false"
:close-on-press-escape="false"
:show-close="false"
class="drawer-custom-style"
>
<div class="drawer-custom-style-body">
<div class="drawer-custom-style-body-content objective">
<div>
<span>题型:</span>
<div>
<el-check-tag
:checked="drawerForm.objective.questionTypeValue === item.value"
type="primary"
v-for="item in drawerForm.objective.questionTypeList"
@change="changeObjectiveQuestionType(item)"
style="margin: 0 10px 0 0"
>
{{ item.title }}
</el-check-tag>
</div>
</div>
<div>
<span>排列:</span>
<div>
<el-radio-group
v-model="drawerForm.objective.range"
:disabled="isLockStatus"
>
<el-radio :value="0">横向</el-radio>
<el-radio :value="1">竖向</el-radio>
</el-radio-group>
</div>
</div>
<div>
<span>选项个数:</span>
<div>
<el-input-number
style="width: 164px"
v-model="drawerForm.objective.optionNumber"
:min="2"
:disabled="drawerForm.objective.questionTypeValue === 2 || isLockStatus"
:step-strictly="true"
:max="24"
/>
</div>
</div>
<div>
<span>题量:</span>
<div>
<el-input-number
:disabled="isLockStatus"
style="width: 164px"
v-model="drawerForm.objective.questionNumber"
:min="1"
:step-strictly="true"
@change="changeQuestion()"
/>
</div>
</div>
<div>
<span>题号:</span>
<div>
<el-input-number
:disabled="isLockStatus"
v-model="drawerForm.objective.questionBefore"
:min="1"
:controls="false"
style="width: 60px"
@input="changeQuestion($event)"
/>
——
<el-input-number
v-model="drawerForm.objective.questionAfter"
:controls="false"
disabled
style="width: 60px"
/>
</div>
</div>
<div>
<span>每题分数:</span>
<div>
<el-input-number
:disabled="isLockStatus"
style="width: 164px"
v-model="drawerForm.objective.score"
:min="0"
:precision="1"
:step="0.1"
/>
</div>
</div>
<div :class="drawerForm.objective.range === 0 ? 'syt-row' : 'syt-column'">
<span>示意图</span>
<div v-if="drawerForm.objective.questionBefore % 1 === 0">
<div v-for="item in drawerForm.objective.questionNumber">
<span>{{ item - 1 + drawerForm.objective.questionBefore }}</span>
<span v-for="row in drawerForm.objective.optionNumber" :name="item">{{
drawerForm.objective.questionTypeValue === 2
? ["T", "F"][row - 1]
: drawerForm.objective.optionEnglish[row - 1]
}}</span>
</div>
</div>
<div v-else>
<div v-for="item in drawerForm.objective.questionNumber">
<span>
{{ drawerForm.objective.questionBefore.toString().split(".")[0] }}
.{{
Number(drawerForm.objective.questionBefore.toString().split(".")[1]) +
(item - 1)
}}
</span>
<span v-for="row in drawerForm.objective.optionNumber" :name="item">{{
drawerForm.objective.questionTypeValue === 2
? ["T", "F"][row - 1]
: drawerForm.objective.optionEnglish[row - 1]
}}</span>
</div>
</div>
</div>
<FabricSize
:change="changeFabricPositionNumber"
ref="FabricSizeRef"
v-if="currentFabric"
/>
</div>
<div class="drawer-custom-style-body-footer">
<el-button
type="primary"
@click="changeFabricInfo(true)"
:disabled="!drawerForm.objective.questionBefore"
>确认</el-button
>
<el-button @click="changeFabricInfo(false)">取消</el-button>
</div>
</div>
</el-drawer>
<!-- 框选主观题 -->
<el-drawer
:modal="false"
v-model="drawerList.subjectiveDrawer"
title="编辑属性"
:close-on-click-modal="false"
:close-on-press-escape="false"
:show-close="false"
class="drawer-custom-style"
>
<div class="drawer-custom-style-body">
<div class="drawer-custom-style-body-content subjective">
<div>
<span>题型:</span>
<div>
<el-check-tag
:checked="drawerForm.subjective.questionTypeValue === item.value"
type="primary"
v-for="item in drawerForm.subjective.questionTypeList"
@change="changeObjectiveQuestionType(item)"
style="margin: 0 10px 0 0"
>
{{ item.title }}
</el-check-tag>
</div>
</div>
<div>
<span>题号:</span>
<div>
<el-input-number
v-model="drawerForm.subjective.questionBefore"
:disabled="isLockStatus"
:min="1"
:controls="false"
:step-strictly="true"
style="width: 54px"
@change="
drawerForm.subjective.questionAfter =
drawerForm.subjective.questionBefore
"
/>
——
<el-input-number
v-model="drawerForm.subjective.questionAfter"
:disabled="isLockStatus"
:min="1"
:controls="false"
:step-strictly="true"
style="width: 54px"
/>
</div>
</div>
<div>
<span>每题分数:</span>
<div>
<el-input-number
v-model="drawerForm.subjective.score"
:disabled="isLockStatus"
style="width: 152px"
:min="0"
:precision="1"
:step="0.1"
/>
</div>
</div>
<FabricSize
:change="changeFabricPositionNumber"
ref="FabricSizeRef"
v-if="currentFabric"
/>
</div>
<div class="drawer-custom-style-body-footer">
<el-button
type="primary"
@click="changeFabricInfo(true)"
:disabled="
drawerForm.subjective.questionBefore >
drawerForm.subjective.questionAfter ||
!drawerForm.subjective.questionBefore ||
!drawerForm.subjective.questionAfter
"
>确认</el-button
>
<el-button @click="changeFabricInfo(false)">取消</el-button>
</div>
</div>
</el-drawer>
<!-- 锁定 -->
<LockList ref="LockListRef" :change="lock" />
</div>
</template>
<script lang="ts" setup>
import { onMounted, reactive, ref, nextTick, watch } from "vue";
import { fabric } from "fabric";
import { createWorker } from "tesseract.js";
import html2canvas from "html2canvas";
import { GetViewSize, GetOcrDetail, PostSave, GetLock } from "@/api/Home";
import FabricSize from "@/pages/Home/components/FabricSize.vue";
import LockList from "@/pages/Home/components/LockList.vue";
import { useRouter } from "vue-router";
const router = useRouter();
const FabricSizeRef = ref<any>(null);
const LockListRef = ref<any>(null);
const firstPosition = ref<any>(null);
const contextmenu = ref<any>(null);
const navList = reactive<any[]>([
{
title: "框选定位点",
index: 0,
},
{
title: "框选学科",
index: 1,
},
{
title: "框选考号",
index: 2,
},
{
title: "框选客观题",
index: 3,
},
{
title: "框选主观题",
index: 4,
},
{
title: "框选其他标记(缺考、屏蔽区、反面定位点)",
index: 5,
},
]);
const navListIndex = ref<number>(0);
const placeholderText = ref<string>("");
const operationText = ref<string>("");
const scale = ref<number>(0);
const imgDom = ref<any>(null);
const otherType = ref<number>(1);
const textFabricList = ref<string[]>(["删除", "编辑属性"]);
const positionTitle = ref<string[]>(["左上角", "右上角", "左下角", "右下角"]);
const disabledMove = ref<number[]>([0, 1]);
const drawerList = ref<{
subjectDrawer: boolean;
examIdDrawer: boolean;
objectiveDrawer: boolean;
subjectiveDrawer: boolean;
}>({
subjectDrawer: false,
examIdDrawer: false,
objectiveDrawer: false,
subjectiveDrawer: false,
});
const drawerForm = reactive<any>({
examId: {
examType: 1,
examIdLength: 8,
examIdStyle: 1,
},
objective: {
questionTypeList: [
{
title: "单选题",
value: 0,
},
{
title: "多选题",
value: 1,
},
{
title: "判断题",
value: 2,
},
],
questionTypeValue: 0,
optionNumber: 4,
optionEnglish: [
"A",
"B",
"C",
"D",
"E",
"F",
"G",
"H",
"I",
"J",
"K",
"L",
"M",
"N",
"O",
"P",
"Q",
"R",
"S",
"T",
"U",
"V",
"W",
"X",
"Y",
"Z",
],
questionNumber: 5,
questionBefore: 1,
questionAfter: 5,
range: 0,
score: 0,
},
subjective: {
questionTypeList: [
{
title: "填空题",
value: 0,
},
{
title: "解答题",
value: 1,
},
{
title: "语文作文",
value: 2,
},
{
title: "英文作文",
value: 3,
},
],
questionTypeValue: 0,
questionBefore: 1,
questionAfter: 1,
score: 0,
},
fabricPosition: {
top: 0,
left: 0,
width: 0,
height: 0,
},
});
const textList = ref<any[]>([]);
const imgSrcIndex = ref<number>(0);
const templateInfo = reactive<any>({
name: "",
hasPosition: true,
type: "A3",
imgSrc: [],
headList: [
{
//定位点
point: [],
},
{
//学科
point: [],
},
{
//考号
point: [],
},
{
//客观题
point: [],
},
{
//主观题
point: [],
},
{
//其他
point: [],
},
],
tailsList: [
{
//定位点
point: [],
},
{
//学科
point: [],
},
{
//考号
point: [],
},
{
//客观题
point: [],
},
{
//主观题
point: [],
},
{
//其他
point: [],
},
],
questionList: [],
otherData: [],
});
const isLockStatus = ref<boolean>(false);
const isLockDisabled = ref<boolean>(false);
const currentNavListIndex = ref<number>(3);
// 画布相关
const ctx = ref<any>(null);
const fabricGroup = ref<any>(null);
const fabricRect = ref<any>(null);
const currentFabric = ref<any>(null);
onMounted(() => {
GetOcrDetail(router.currentRoute.value.query.mid as string).then((res: any) => {
if (res.code === 200) {
const { mainName, hasPosition, paperType, detail, isLock } = res.data;
isLockStatus.value = isLock;
templateInfo.name = mainName;
templateInfo.hasPosition = hasPosition;
templateInfo.type = paperType === 1 ? "A3" : "A4";
templateInfo.otherData = detail.sort((a: any, b: any) => a.pageIndex - b.pageIndex);
templateInfo.otherData.forEach((item: any, index: number) => {
templateInfo.imgSrc.push(item.tempPicture64);
if (item.tempData) {
index === 0
? (templateInfo.headList = JSON.parse(JSON.parse(item.tempData).tempData))
: (templateInfo.tailsList = JSON.parse(JSON.parse(item.tempData).tempData));
}
});
}
});
});
watch(
() => templateInfo,
(_new, _old) => {
if (!_new.headList[2].point.length && !_new.tailsList[2].point.length) {
isLockDisabled.value = true;
currentNavListIndex.value = 2;
return;
}
if (!_new.headList[1].point.length && !_new.tailsList[1].point.length) {
isLockDisabled.value = true;
currentNavListIndex.value = 1;
return;
}
if (_new.hasPosition) {
if (_new.imgSrc.length === 2) {
if (_new.headList[0].point.length !== 4 || _new.tailsList[0].point.length !== 4) {
isLockDisabled.value = true;
currentNavListIndex.value = 0;
return;
}
} else {
if (_new.headList[0].point.length !== 4) {
isLockDisabled.value = true;
currentNavListIndex.value = 0;
return;
}
}
}
isLockDisabled.value = false;
},
{ deep: true }
);
// 图片加载完成
const canvasLoad = (): void => {
if (!ctx.value) {
initCanvas();
navListIndex.value = templateInfo.hasPosition ? 0 : 1; //无定位点默认起始下标为1
if (!templateInfo.hasPosition) {
//无定位点去除第一项
navList.splice(0, 1);
}
setQuestionList(); //回显试题
showFabricData(); //回显画布
setStepClick(currentNavListIndex.value, false); //设置步骤
setNavTitle(); //设置导航栏文字
}
};
// 初始化画布并添加事件
const initCanvas = (): void => {
imgDom.value = document.getElementById("picture-img");
ctx.value = new fabric.Canvas("picture", {
width: imgDom.value.clientWidth,
height: imgDom.value.clientHeight,
});
ctx.value.on("mouse:down", (e: any) => {
if (isLockStatus.value && !e.target) {
ElMessage.error("已锁定,无法新增!");
return;
}
const templateInfoItem = getTemplateInfo(true);
if (templateInfoItem[0].point.length === 4) {
if (isMoveOutView()) {
if (
getCanvasMousePos(e.e).x < getCalculateRectangleSize().left ||
getCanvasMousePos(e.e).y < getCalculateRectangleSize().top ||
getCanvasMousePos(e.e).x >
getCalculateRectangleSize().width + getCalculateRectangleSize().left ||
getCanvasMousePos(e.e).y >
getCalculateRectangleSize().height + getCalculateRectangleSize().top
) {
ElMessage.error("鼠标位置不在画布内!");
return;
}
}
}
if (e.target) {
setStepClick(e.target.get("fabricType"));
}
canvasMouseDown(e.e, e.target, e.subTargets);
});
ctx.value.on("mouse:move", (e: any) => {
canvasMouseMove(e.e);
});
ctx.value.on("mouse:up", () => {
canvasMouseUp();
});
ctx.value.on("selection:created", (e: any) => {
if (e.selected.length > 1) {
ctx.value.discardActiveObject();
}
});
ctx.value.on("object:moving", (_e: any) => {
if (disabledMove.value.includes(navListIndex.value)) {
return;
}
isDragOutCanvas(isMoveOutView());
});
document.addEventListener("keydown", (e: any) => {
const keyCodeList = ["ArrowUp", "ArrowDown", "ArrowLeft", "ArrowRight"];
if (ctx.value.getActiveObject()) {
const allDrawerStatus = [
drawerList.value.subjectDrawer,
drawerList.value.examIdDrawer,
drawerList.value.objectiveDrawer,
drawerList.value.subjectiveDrawer,
];
if (keyCodeList.includes(e.key) && !allDrawerStatus.find((item: boolean) => item)) {
e.preventDefault();
let obj = ctx.value.getActiveObject();
switch (e.key) {
case "ArrowUp":
obj.set("top", obj.get("top") - 1);
break;
case "ArrowDown":
obj.set("top", obj.get("top") + 1);
break;
case "ArrowLeft":
obj.set("left", obj.get("left") - 1);
break;
case "ArrowRight":
obj.set("left", obj.get("left") + 1);
break;
}
isDragOutCanvas(isMoveOutView());
saveFabricData();
}
}
});
};
// 获取坐标
const getCanvasMousePos = (e: MouseEvent): { x: number; y: number } => {
const rect = imgDom.value.getBoundingClientRect();
const x = ((e.clientX - rect.left) * imgDom.value.width) / rect.width;
const y = ((e.clientY - rect.top) * imgDom.value.height) / rect.height;
return {
x: x,
y: y,
};
};
// 鼠标按下
const canvasMouseDown = (e: MouseEvent, target: any, subTargets: any): void => {
const mousePos = getCanvasMousePos(e);
firstPosition.value = mousePos;
if ((!target || !subTargets.length) && isDisabledDraw()) {
fabricRect.value = new fabric.Rect({
left: mousePos.x,
top: mousePos.y,
stroke: "red",
strokeWidth: 1,
fill: "transparent",
fabricId: cteatedNumber(),
});
}
// 鼠标按下显示功能文字
ctx.value
.getObjects()
.filter((item: any) => item.fabricType === navListIndex.value)
.forEach((item: any) => {
item.forEachObject((row: any) => {
if (textFabricList.value.includes(row.get("text"))) {
if (target) {
const itemTarget = target
.getObjects()
.find((i: any) => i.fabricId === row.fabricId);
if (itemTarget && row.fabricId === itemTarget.fabricId) {
if (isLockStatus.value && itemTarget.get("text") === "删除") {
row.set({ visible: false });
} else {
itemTarget.set({ visible: true });
}
} else {
row.set({ visible: false });
}
} else {
row.set({ visible: false });
}
}
});
});
};
// 鼠标移动
const canvasMouseMove = (e: MouseEvent): void => {
const mousePos = getCanvasMousePos(e);
if (fabricRect.value) {
const width = mousePos.x - fabricRect.value.left;
const height = mousePos.y - fabricRect.value.top;
const obj = {
height:
fabricRect.value.top + height >= imgDom.value.clientHeight
? imgDom.value.clientHeight - fabricRect.value.top
: height,
width:
fabricRect.value.left + width >= imgDom.value.clientWidth
? imgDom.value.clientWidth - fabricRect.value.left
: width,
};
if (isMoveOutView()) {
const templateInfoItem = getTemplateInfo(true);
if (templateInfoItem[0].point.length === 4) {
if (
obj.width + firstPosition.value.x - getCalculateRectangleSize().left >
getCalculateRectangleSize().width
) {
obj.width =
getCalculateRectangleSize().width -
firstPosition.value.x +
getCalculateRectangleSize().left;
}
if (
obj.height + firstPosition.value.y - getCalculateRectangleSize().top >
getCalculateRectangleSize().height
) {
obj.height =
getCalculateRectangleSize().height -
firstPosition.value.y +
getCalculateRectangleSize().top;
}
}
}
fabricRect.value.set({
...obj,
});
}
};
// 鼠标抬起
const canvasMouseUp = async (): Promise<void> => {
// 绘制图形
function addFabric(_type: string, data: any, itemFabricRect?: any) {
const group = [
fabricRect.value,
...(itemFabricRect ? [itemFabricRect, ...createFabric()] : createFabric()),
];
fabricGroup.value = new fabric.Group(group, {
hasControls: false,
subTargetCheck: true,
borderColor: "transparent",
selectionBackgroundColor: "red",
fabricType: navListIndex.value,
fabricId: cteatedNumber(),
}).on("mousemove", (e: any) => {
if (
!disabledMove.value.includes(navListIndex.value) &&
e.target.fabricType === navListIndex.value
) {
e.target.set("selectable", e.subTargets.length ? true : false);
}
});
// 功能添加文字事件
fabricGroup.value.forEachObject((obj: any) => {
if (textFabricList.value.includes(obj.get("text"))) {
obj.on("mousedown", (e: any) => {
addEventClick(e);
});
// 隐藏功能文字
obj.set({ visible: false });
}
});
fabricGroup.value.set("customFrom", data);
ctx.value.add(fabricGroup.value);
}
if (fabricRect.value && fabricRect.value.get("width") > 10) {
const obj = {
left: fabricRect.value.get("left"),
top: fabricRect.value.get("top"),
width: fabricRect.value.get("width"),
height: fabricRect.value.get("height"),
};
if (navListIndex.value === 0) {
await GetViewSize({
X: Math.round(fabricRect.value.get("left")),
Y: Math.round(fabricRect.value.get("top")),
Width: Math.round(fabricRect.value.get("width")),
Height: Math.round(fabricRect.value.get("height")),
ImageUrl: templateInfo.imgSrc[imgSrcIndex.value],
ImageWidth: imgDom.value.clientWidth,
ImageHeight: imgDom.value.clientHeight,
}).then((res) => {
if (res.code === 200) {
const rectTitle = ["右上角", "右下角"];
const rectFooterTitle = ["左下角", "右下角"];
if (rectTitle.includes(getCurrentText())) {
res.data.x = res.data.x + res.data.width;
}
if (rectFooterTitle.includes(getCurrentText())) {
res.data.y = res.data.y - res.data.height;
}
addFabric(
"rect",
{ ...obj, title: getTextTitle(), rectPosition: res.data },
new fabric.Rect({
left: rectTitle.includes(getCurrentText())
? res.data.x - res.data.width
: res.data.x,
top: rectFooterTitle.includes(getCurrentText())
? res.data.y + res.data.height
: res.data.y,
width: res.data.width,
height: res.data.height,
stroke: "red",
strokeWidth: 1,
fill: "transparent",
fabricId: cteatedNumber(),
rectPosition: res.data,
})
);
}
});
} else if (navListIndex.value === 5) {
addFabric("other", { type: otherType.value, ...obj });
} else {
addFabric("default", {
...obj,
});
}
}
//保存数据
saveFabricData();
// 各个类型框选过后的操作
boxSelectAfter();
};
// 功能文字添加事件
const addEventClick = (obj: any) => {
switch (obj.subTargets[0].get("text")) {
case "删除":
changeFabricCanvas(obj).delete();
break;
case "编辑属性":
changeFabricCanvas(obj).edit();
break;
}
saveFabricData();
};
// 画布按钮操作
const changeFabricCanvas = (obj: any): { delete: () => void; edit: () => void } => {
return {
delete: () => {
ctx.value.remove(obj.target);
ctx.value.discardActiveObject();
},
edit: () => {
currentFabric.value = obj;
changeDrawer(true);
},
};
};
//清除画布
const clearFabricGroup = (): void => {
ElMessageBox.confirm("确定清除所有框选的信息吗?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
})
.then(() => {
templateInfo.tailsList.forEach((item: any) => {
item.point = [];
});
templateInfo.headList.forEach((item: any) => {
item.point = [];
});
templateInfo.questionList = [];
ctx.value.clear();
navListIndex.value = 0;
setNavTitle();
ElMessage.success("清除成功!");
})
.catch(() => {});
};
// 生成其他图形文字辅助函数
const createText = (
text: string,
left: number,
top: number,
backgroundColor: string = "rgba(0,0,0,0.5)",
fill: string = "white",
fontSize: number = 16,
fontWeight: string = "normal"
): any => {
return new fabric.Text(text, {
left,
top,
fontSize,
fill,
backgroundColor,
fontWeight,
fabricId: cteatedNumber(),
});
};
// 获取当前标题
const getCurrentText = (): string => {
const fabricItem = getFabricItem();
const fabricPositionTitle = [] as string[];
fabricItem.forEach((row: any) => {
row.forEachObject((item: any) => {
if (positionTitle.value.includes(item.get("text"))) {
fabricPositionTitle.push(item.get("text"));
}
});
});
return findFirstMissingValue(fabricPositionTitle);
};
// 设置标题
const getTextTitle = (bool?: boolean): string => {
switch (navListIndex.value) {
case 0:
if (bool) {
ElMessage.success(
`${imgSrcIndex.value ? "反面" : "正面"}${getCurrentText()}框选完成`
);
}
return getCurrentText();
case 1:
return "标题";
case 5:
let msg = "";
switch (otherType.value) {
case 1:
msg = "缺考";
break;
case 2:
msg = "屏蔽区";
break;
case 3:
msg = "反面定位点";
break;
}
return msg;
default:
return "无";
}
};
// 生成其他图形文字
const createFabric = (): any[] => {
const titleText = createText(
getTextTitle(true),
fabricRect.value.left,
fabricRect.value.top - (navListIndex.value ? 20 : 0),
"rgba(255,0,0,0.5)"
);
const deleteText = createText(
"删除",
fabricRect.value.left + (navListIndex.value ? fabricRect.value.width - 32 : 0),
fabricRect.value.top +
fabricRect.value.height -
(navListIndex.value ? fabricRect.value.height + 20 : 20)
);
const editText = createText(
"编辑属性",
fabricRect.value.left,
fabricRect.value.top + fabricRect.value.height
);
switch (navListIndex.value) {
case 0:
return [titleText, deleteText];
case 1:
case 5:
return [titleText, deleteText];
case 3:
return [deleteText, editText];
default:
return [titleText, deleteText, editText];
}
};
// 切换步骤
const setStepClick = (index: number, isElMessage: boolean = true): void => {
if (navListIndex.value === index) {
return;
}
let msg = "";
function checkTemplateInfo(index: number): void {
if (index > navListIndex.value) {
if (index >= 2) {
if (index >= 3) {
if (
!templateInfo.headList[2].point.length &&
!templateInfo.tailsList[2].point.length
) {
msg = "考号未框选!";
}
}
if (
!templateInfo.headList[1].point.length &&
!templateInfo.tailsList[1].point.length
) {
msg = "学科未框选!";
}
}
templateInfo.hasPosition &&
(index === 1 || index === 2 || index === 3 || index === 4 || index === 5
? templateInfo.headList[0].point.length !== 4
? ((msg = "正面定位点未框选完成!"), changeImgSrcIndex(0))
: templateInfo.tailsList[0].point.length !== 4 &&
templateInfo.imgSrc.length > 1
? ((msg = "反面定位点未框选完成!"), changeImgSrcIndex(1))
: null
: null);
}
}
checkTemplateInfo(index);
if (msg) {
if (isElMessage) ElMessage.error(msg);
return;
}
if (index === 1) {
changeZoom();
}
navListIndex.value = index;
changeImgSrcIndex(0);
ctx.value.discardActiveObject();
ctx.value.renderAll();
};
//保存图形
const saveFabricData = (): void => {
const templateInfoItem = getTemplateInfo();
templateInfoItem.point = [];
const ctxObjects = ctx.value
.getObjects()
.filter((item: any) => item.fabricType === navListIndex.value);
ctxObjects.forEach((item: any, index: number) => {
templateInfoItem.point.push({
left: item.left,
top: item.top,
width: item.width,
height: item.height,
fabricType: item.fabricType,
customFrom: item.customFrom,
children: [],
});
item.forEachObject((child: any) => {
const childItem = {
left: child.left,
top: child.top,
width: child.width,
height: child.height,
};
if (child.get("type") === "text") {
templateInfoItem.point[index].children.push({
type: "text",
text: child.get("text"),
backgroundColor: child.get("backgroundColor"),
fill: child.get("fill"),
fontSize: child.get("fontSize"),
fontWeight: child.get("fontWeight"),
...childItem,
});
} else {
templateInfoItem.point[index].children.push({
type: "rect",
rectPosition: child.get("rectPosition"),
...childItem,
});
}
});
});
// 设置题目列表
setQuestionList();
//设置导航栏文字
setNavTitle();
// 禁用其他图形
disabledFabricData();
// 更新画布
ctx.value.renderAll();
};
// 禁用其他图形
const disabledFabricData = (): void => {
const ctxObjects = ctx.value.getObjects();
ctxObjects.forEach((item: any) => {
item.set("hoverCursor", "default");
if (disabledMove.value.includes(item.fabricType)) {
item.forEachObject((row: any) => {
item.set("selectable", false);
if (row.get("type") === "rect") {
row.set("hoverCursor", "not-allowed");
} else {
if (textFabricList.value.includes(row.get("text"))) {
row.set(
"hoverCursor",
item.fabricType === navListIndex.value ? "pointer" : "not-allowed"
);
} else {
row.set(
"hoverCursor",
item.fabricType === navListIndex.value ? "default" : "not-allowed"
);
}
}
});
} else {
item.forEachObject((row: any) => {
item.set("selectable", item.fabricType === navListIndex.value);
if (row.get("type") === "rect") {
row.set(
"hoverCursor",
item.fabricType === navListIndex.value ? "move" : "not-allowed"
);
} else {
if (textFabricList.value.includes(row.get("text"))) {
row.set(
"hoverCursor",
item.fabricType === navListIndex.value ? "pointer" : "not-allowed"
);
} else {
row.set(
"hoverCursor",
item.fabricType === navListIndex.value ? "default" : "not-allowed"
);
}
}
});
}
});
};
// 各个类型框选过后的操作
const boxSelectAfter = async (): Promise<void> => {
if (fabricRect.value && fabricRect.value.get("width") > 10) {
async function ocrImage(): Promise<void> {
const loading = ElLoading.service({
lock: true,
text: `识别中...`,
background: "rgba(0, 0, 0, 0.7)",
body: true,
});
const canvas = await html2canvas(imgDom.value, {
scale: 4,
width: fabricRect.value.width,
height: fabricRect.value.height,
x: fabricGroup.value.left,
y: fabricGroup.value.top + 20,
useCORS: true,
});
const img = new Image();
img.src = canvas.toDataURL("image/png");
img.onload = async () => {
const worker = (await createWorker("chi_sim", 1, {
workerPath: "/src/assets/js/worker.min.js",
corePath: "/src/assets/js/tesseract-core.wasm.js",
})) as any;
await worker.recognize(img).then((res: any) => {
textList.value = res.data.words.map((item: any) => {
return {
...item,
checked: false,
};
});
changeDrawer(true);
loading.close();
});
};
}
switch (navListIndex.value) {
case 0:
// 定位点
isShowElMessageBox();
break;
case 1:
// 学科
ocrImage();
break;
case 2:
case 3:
case 4:
// 考号 客观题 主观题
changeDrawer(true);
break;
case 5:
// 其他
if (otherType.value === 3) {
ocrImage();
}
break;
}
}
fabricRect.value = null;
fabricGroup.value = null;
};
// 是否禁止禁止绘制
const isDisabledDraw = (): boolean => {
let isDisabledDrawStatus = true;
let msg = "";
const fabricItem = getFabricItem();
switch (navListIndex.value) {
case 0:
// 定位点
isDisabledDrawStatus = fabricItem.length < 4;
if (!isDisabledDrawStatus)
msg = `${imgSrcIndex.value === 0 ? "正面" : "反面"}定位点已框选完毕!`;
break;
case 1:
// 学科
isDisabledDrawStatus = fabricItem.length === 0;
if (!isDisabledDrawStatus) msg = "学科已框选!";
break;
case 2:
// 考号
isDisabledDrawStatus = fabricItem.length !== 2;
if (!isDisabledDrawStatus) msg = "考号最多只能框选两个!";
break;
case 5:
// 其他
const hasQueKao = [] as any;
if (otherType.value === 1) {
fabricItem.forEach((item: any) => {
let row = item.getObjects().filter((obj: any) => obj.get("text") === "缺考");
if (row) {
hasQueKao.push(...row);
}
});
isDisabledDrawStatus = hasQueKao.length === 0;
if (!isDisabledDrawStatus) msg = "缺考标记已框选!";
}
if (otherType.value === 3) {
if (imgSrcIndex.value === 1) {
fabricItem.forEach((item: any) => {
let row = item
.getObjects()
.filter(
(obj: any) =>
obj.get("text") && obj.get("text").indexOf("反面定位点") >= 0
);
if (row.length) {
hasQueKao.push(...row);
}
});
isDisabledDrawStatus = hasQueKao.length === 0;
if (!isDisabledDrawStatus) msg = "反面定位点标记已框选!";
} else {
msg = "反面定位点只能在反面框选!";
isDisabledDrawStatus = false;
}
}
break;
}
if (msg) {
ElMessage.success(msg);
}
return isDisabledDrawStatus;
};
// 是否需要弹窗提示
const isShowElMessageBox = () => {
const fabricItem = getFabricItem();
switch (navListIndex.value) {
case 0:
if (templateInfo.imgSrc.length > 1) {
if (
templateInfo.headList[navListIndex.value].point.length === 4 &&
templateInfo.tailsList[navListIndex.value].point.length === 4
) {
ElMessageBox.confirm("定位点已框选完毕,是否切换到框选学科?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
})
.then(() => {
setStepClick(1);
})
.catch(() => {});
} else if (
templateInfo.headList[navListIndex.value].point.length === 4 &&
imgSrcIndex.value !== 1
) {
ElMessageBox.confirm("正面已框选完毕,是否切换到反面?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
})
.then(() => {
changeImgSrcIndex(1);
})
.catch(() => {});
} else if (
templateInfo.tailsList[navListIndex.value].point.length === 4 &&
imgSrcIndex.value !== 0
) {
ElMessageBox.confirm("反面已框选完毕,是否切换到正面?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
})
.then(() => {
changeImgSrcIndex(0);
})
.catch(() => {});
}
} else {
if (templateInfo.headList[navListIndex.value].point.length === 4) {
ElMessageBox.confirm("定位点已框选完毕,是否切换到框选学科?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
})
.then(() => {
setStepClick(1);
})
.catch(() => {});
}
}
break;
case 1:
if (fabricItem.length !== 0) {
ElMessageBox.confirm("学科已框选完毕,是否切换到框选考号?", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
})
.then(() => {
setStepClick(2);
})
.catch(() => {});
}
break;
}
};
// 回显画布
const showFabricData = (): void => {
const templateInfoItem = getTemplateInfo(true);
function createFabricChildren(children: any) {
const textArr = [] as any;
const rectArr = [] as any;
children.forEach((item: any) => {
if (item.type === "rect") {
rectArr.push(
new fabric.Rect({
left: item.left,
top: item.top,
width: item.width,
height: item.height,
stroke: "red",
strokeWidth: 1,
fill: "transparent",
fabricId: cteatedNumber(),
rectPosition: item.rectPosition,
})
);
} else {
textArr.push(
new fabric.Text(item.text, {
left: item.left,
top: item.top,
width: item.width,
height: item.height,
backgroundColor: item.backgroundColor,
fill: item.fill || "white",
fontSize: item.fontSize || 16,
fabricId: cteatedNumber(),
visible: !textFabricList.value.includes(item.text),
fontWeight: item.fontWeight || "normal",
}).on("mousedown", (e: any) => {
if (textFabricList.value.includes(item.text)) {
addEventClick(e);
}
})
);
}
});
return [...rectArr, ...textArr];
}
templateInfoItem.forEach((row: any) => {
row.point.forEach((item: any) => {
ctx.value.add(
new fabric.Group([...createFabricChildren(item.children)], {
hasControls: false,
subTargetCheck: true,
borderColor: "transparent",
selectionBackgroundColor: "rgba(255,0,0,0.1)",
fabricType: item.fabricType,
left: item.left,
top: item.top,
width: item.width,
height: item.height,
fabricId: cteatedNumber(),
customFrom: item.customFrom,
}).on("mousemove", (e: any) => {
if (
!disabledMove.value.includes(navListIndex.value) &&
e.target.fabricType === navListIndex.value
) {
e.target.set("selectable", e.subTargets.length ? true : false);
}
})
);
});
});
// 禁用其他图形
disabledFabricData();
};
// 获取某一项所有图形
const getFabricItem = (): any => {
return ctx.value.getObjects().filter((item: any) => {
return item.fabricType === navListIndex.value;
});
};
// 设置图片正反面
const changeImgSrcIndex = (index: number): void => {
imgSrcIndex.value = index;
ctx.value.clear();
showFabricData();
setNavTitle();
};
// 获取当前是哪面的数据
const getTemplateInfo = (bool?: boolean): any => {
if (bool) {
return imgSrcIndex.value === 0 ? templateInfo.headList : templateInfo.tailsList;
} else {
return imgSrcIndex.value === 0
? templateInfo.headList[navListIndex.value]
: templateInfo.tailsList[navListIndex.value];
}
};
// 放大缩小画布
const changeZoom = (bool?: boolean): void => {
const dom = document.getElementById("zoom-div") as HTMLElement;
if (typeof bool === "boolean") {
bool ? (scale.value += 0.1) : (scale.value -= 0.1);
} else {
ElMessage.success("画布恢复默认值成功");
scale.value = 1;
}
scale.value = Math.max(scale.value, 1);
dom.style.transform = `scale(${scale.value})`;
};
// 生成随机数
const cteatedNumber = (): string => {
return `${Math.floor(Math.random() * 100000000)}+${+new Date()}`;
};
// 修改尺寸数字
const changeFabricPositionNumber = (value: number | boolean, str: string): void => {
let number = 0;
if (typeof value === "boolean") {
number = JSON.parse(JSON.stringify(currentFabric.value.target.get(str)));
if (value) {
number--;
} else {
number++;
}
} else {
number = value;
}
changeFabricPosition(number, str);
};
// 编辑尺寸
const changeFabricPosition = (value: number, str: string): void => {
if (value) {
let maxRect = currentFabric.value.target
.getObjects()
.filter((item: any) => item.get("type") === "rect")
.reduce(function (currentMax: any, obj: any) {
return obj.get("width") > currentMax.get("width") ? obj : currentMax;
}, currentFabric.value.target.getObjects()[0]);
let textList = currentFabric.value.target
.getObjects()
.filter((item: any) => item.get("type") == "text");
function changeTextPosition(): void {
for (let index = 0; index < textList.length; index++) {
switch (textList[index].get("text")) {
case "删除":
if (str === "width") {
textList[index].set("left", value / 2 - textList[index].get("width"));
} else {
textList[index].set(
"top",
value / 2 - value - textList[index].get("height")
);
}
break;
case "编辑属性":
if (str === "width") {
textList[index].set("left", value / 2 - value);
} else {
textList[index].set("top", value / 2);
}
break;
default:
if (str === "width") {
textList[index].set("left", value / 2 - value);
} else {
textList[index].set(
"top",
value / 2 - value - textList[index].get("height")
);
}
break;
}
}
}
switch (str) {
case "top":
case "left":
currentFabric.value.target.set(str, value);
break;
case "width":
case "height":
maxRect.set(str, value);
if (str === "width") {
maxRect.set("left", value / 2 - value);
currentFabric.value.target.set(str, value);
} else {
maxRect.set("top", value / 2 - value);
currentFabric.value.target.set(str, value + textList[0].height * 2);
}
let rectWidth = 0,
rectHeight = 0;
switch (navListIndex.value) {
// 考号
case 2:
let examIdLength = currentFabric.value.target.customFrom.examIdLength;
rectWidth = maxRect.get("width") / examIdLength;
rectHeight = maxRect.get("height") / 10;
changeTextPosition();
deleteSmallRect();
for (let index = 0; index < examIdLength * 10; index++) {
currentFabric.value.target.add(
new fabric.Rect({
left: maxRect.get("left") + (index % examIdLength) * rectWidth,
top:
maxRect.get("top") +
getOffsetTop(examIdLength, 10, index, rectHeight),
stroke: "red",
strokeWidth: 1,
fill: "transparent",
fabricId: cteatedNumber(),
width: rectWidth,
height: rectHeight,
})
);
}
break;
case 3:
// 客观题
let questionNumber = currentFabric.value.target.customFrom.questionNumber;
let optionNumber = currentFabric.value.target.customFrom.optionNumber;
let range = currentFabric.value.target.customFrom.range;
rectWidth = maxRect.get("width") / (range ? questionNumber : optionNumber);
rectHeight = maxRect.get("height") / (range ? optionNumber : questionNumber);
changeTextPosition();
deleteSmallRect();
for (let index = 0; index < optionNumber * questionNumber; index++) {
currentFabric.value.target.add(
new fabric.Rect({
left:
maxRect.get("left") +
(index % (range ? questionNumber : optionNumber)) * rectWidth,
top:
maxRect.get("top") +
getOffsetTop(
range ? questionNumber : optionNumber,
range ? optionNumber : questionNumber,
index,
rectHeight
),
stroke: "red",
strokeWidth: 1,
fill: "transparent",
fabricId: cteatedNumber(),
width: rectWidth,
height: rectHeight,
})
);
}
// 修改题号位置
const questionNumberList = currentFabric.value.target
.getObjects()
.filter(
(item: any) =>
item.get("type") === "text" &&
!textFabricList.value.includes(item.get("text"))
);
questionNumberList.forEach((item: any, index: number) => {
if (range) {
item.set({
left:
maxRect.get("left") +
rectWidth * index +
rectWidth / 2 -
(questionNumber + 1),
top: maxRect.get("top") + 4,
});
} else {
item.set({
top:
maxRect.get("top") +
rectHeight * index +
rectHeight / 2 -
(questionNumber + 1),
left: maxRect.get("left") + 4,
});
}
});
break;
case 4:
// 主观题
changeTextPosition();
break;
}
break;
}
isDragOutCanvas(isMoveOutView());
}
saveFabricData();
};
// 编辑尺寸状态
const changeFabricPositionDraw = (): void => {
nextTick(() => {
let maxRect = currentFabric.value.target
.getObjects()
.filter((item: any) => item.get("type") === "rect")
.reduce(function (currentMax: any, obj: any) {
return obj.get("width") > currentMax.get("width") ? obj : currentMax;
}, currentFabric.value.target.getObjects()[0]);
drawerForm.fabricPosition.top = currentFabric.value.target.get("top");
drawerForm.fabricPosition.left = currentFabric.value.target.get("left");
drawerForm.fabricPosition.width = maxRect.get("width");
drawerForm.fabricPosition.height = maxRect.get("height");
FabricSizeRef.value.setSize(
drawerForm.fabricPosition,
imgDom.value.width,
imgDom.value.height
);
});
};
// 修改drawer
const changeDrawer = (bool: boolean): void => {
let item = "" as any;
if (currentFabric.value) {
item = currentFabric.value.target;
}
switch (navListIndex.value) {
case 1:
// 学科
drawerList.value.subjectDrawer = bool;
break;
case 2:
// 考号
if (bool && item) {
drawerForm.examId.examType = item.customFrom.fabricAttribute;
if (drawerForm.examId.examType === 1) {
drawerForm.examId.examIdLength = item.customFrom.examIdLength;
}
}
drawerList.value.examIdDrawer = bool;
if (bool && item) {
changeFabricPositionDraw();
}
break;
case 3:
// 客观题
if (bool && item) {
drawerForm.objective.questionTypeValue = item.customFrom.fabricAttribute;
drawerForm.objective.questionBefore = item.customFrom.questionBefore;
drawerForm.objective.questionAfter = item.customFrom.questionAfter;
drawerForm.objective.range = item.customFrom.range;
drawerForm.objective.questionNumber = item.customFrom.questionNumber;
drawerForm.objective.optionNumber = item.customFrom.optionNumber;
drawerForm.objective.score = item.customFrom.score;
} else {
drawerForm.objective.questionBefore = getQuestionNumer();
drawerForm.objective.questionAfter =
drawerForm.objective.questionBefore + drawerForm.objective.questionNumber - 1;
}
drawerList.value.objectiveDrawer = bool;
if (bool && item) {
changeFabricPositionDraw();
}
break;
case 4:
// 主观题
if (bool && currentFabric.value) {
drawerForm.subjective.questionTypeValue = item.customFrom.fabricAttribute;
drawerForm.subjective.questionBefore = item.customFrom.questionBefore;
drawerForm.subjective.questionAfter = item.customFrom.questionAfter;
drawerForm.subjective.score = item.customFrom.score;
} else {
drawerForm.subjective.questionBefore = getQuestionNumer();
drawerForm.subjective.questionAfter = drawerForm.subjective.questionBefore;
}
drawerList.value.subjectiveDrawer = bool;
if (bool && currentFabric.value) {
changeFabricPositionDraw();
}
break;
case 5:
// 其他
drawerList.value.subjectDrawer = bool;
break;
}
};
// 修改图形
const changeFabricInfo = (bool: boolean): void => {
const fabricItem = currentFabric.value
? currentFabric.value.target
: getFabricItem().reduce(function (currentMax: any, obj: any) {
return obj.fabricId.split("+")[1] > currentMax.fabricId.split("+")[1]
? obj
: currentMax;
}, getFabricItem()[0]);
if (fabricItem) {
const textTitle = fabricItem
.getObjects()
.find(
(item: any) =>
item.get("text") && !textFabricList.value.includes(item.get("text"))
);
const obj = {
left: fabricItem.get("customFrom").left,
top: fabricItem.get("customFrom").top,
width: fabricItem.get("customFrom").width,
height: fabricItem.get("customFrom").height,
};
if (bool) {
switch (navListIndex.value) {
case 1:
// 学科
if (textList.value.filter((item) => item.checked).length) {
textTitle.set({
text: textList.value
.filter((item) => item.checked)
.map((item) => item.text)
.join(""),
});
fabricItem.set({
customFrom: {
title: textList.value
.filter((item) => item.checked)
.map((item) => item.text)
.join(""),
...obj,
},
});
} else {
ElMessage.error("请选择文字!");
return;
}
isShowElMessageBox();
break;
case 2:
// 考号
function isRepeat() {
let tx = 0,
tm = 0,
msg;
getFabricItem().forEach((item: any) => {
if (item.getObjects().find((row: any) => row.get("text") === "填涂式")) {
tx++;
}
if (item.getObjects().find((row: any) => row.get("text") === "条码式")) {
tm++;
}
});
if (drawerForm.examId.examType === 1 && tx) {
msg = "填涂式已存在";
}
if (drawerForm.examId.examType === 2 && tm) {
msg = "条码式已存在";
}
return msg;
}
if (currentFabric.value) {
if (
currentFabric.value.target.customFrom.fabricAttribute !==
drawerForm.examId.examType
) {
let errorMsg = isRepeat();
if (errorMsg) {
ElMessage.error(errorMsg);
return;
}
}
} else {
let errorMsg = isRepeat();
if (errorMsg) {
ElMessage.error(errorMsg);
return;
}
}
function createdExamIdRect(): void {
let rect = fabricItem
.getObjects()
.find((row: any) => row.get("type") === "rect");
let rectWidth = rect.get("width") / drawerForm.examId.examIdLength;
let rectHeight = rect.get("height") / 10;
for (let index = 0; index < drawerForm.examId.examIdLength * 10; index++) {
fabricItem.add(
new fabric.Rect({
left:
rect.get("left") +
(index % drawerForm.examId.examIdLength) * rectWidth,
top:
rect.get("top") +
getOffsetTop(drawerForm.examId.examIdLength, 10, index, rectHeight),
stroke: "red",
strokeWidth: 1,
fill: "transparent",
fabricId: cteatedNumber(),
width: rectWidth,
height: rectHeight,
})
);
}
}
deleteSmallRect();
if (drawerForm.examId.examType === 1) {
createdExamIdRect();
}
textTitle.set({
text: drawerForm.examId.examType === 1 ? "填涂式" : "条码式",
});
fabricItem.set({
customFrom: {
fabricAttribute: drawerForm.examId.examType,
examIdLength: drawerForm.examId.examIdLength,
...obj,
},
});
isShowElMessageBox();
break;
case 3:
// 客观题
// 生成小格子
function createdObjectiveRect(
rect: any,
rectWidth: number,
rectHeight: number
): void {
for (
let index = 0;
index <
drawerForm.objective.optionNumber * drawerForm.objective.questionNumber;
index++
) {
fabricItem.add(
new fabric.Rect({
left:
rect.get("left") +
(index %
(drawerForm.objective.range
? drawerForm.objective.questionNumber
: drawerForm.objective.optionNumber)) *
rectWidth,
top:
rect.get("top") +
getOffsetTop(
drawerForm.objective.range
? drawerForm.objective.questionNumber
: drawerForm.objective.optionNumber,
drawerForm.objective.range
? drawerForm.objective.optionNumber
: drawerForm.objective.questionNumber,
index,
rectHeight
),
stroke: "red",
strokeWidth: 1,
fill: "transparent",
fabricId: cteatedNumber(),
width: rectWidth,
height: rectHeight,
})
);
}
}
if (isQuestionNumerRepeat()) {
ElMessage.error("题目重复!");
return;
} else {
let rect = fabricItem
.getObjects()
.find((row: any) => row.get("type") === "rect");
let rectWidth =
rect.get("width") /
(drawerForm.objective.range
? drawerForm.objective.questionNumber
: drawerForm.objective.optionNumber);
let rectHeight =
rect.get("height") /
(drawerForm.objective.range
? drawerForm.objective.optionNumber
: drawerForm.objective.questionNumber);
deleteSmallRect(navListIndex.value);
createdObjectiveRect(rect, rectWidth, rectHeight);
fabricItem.set({
customFrom: {
fabricAttribute: drawerForm.objective.questionTypeValue,
range: drawerForm.objective.range,
optionNumber: drawerForm.objective.optionNumber,
questionNumber: drawerForm.objective.questionNumber,
questionBefore: drawerForm.objective.questionBefore,
questionAfter: drawerForm.objective.questionAfter,
score: drawerForm.objective.score,
...obj,
},
});
objectiveNumber(fabricItem, rect, rectWidth, rectHeight);
}
break;
case 4:
// 主观题
if (isQuestionNumerRepeat()) {
ElMessageBox.confirm("题目重复,是否设置为分页题目。", "提示", {
confirmButtonText: "确定",
cancelButtonText: "取消",
type: "warning",
})
.then(() => {
let numberIndex = getIndex();
textTitle.set({
text: `${drawerForm.subjective.questionBefore}-${drawerForm.subjective.questionAfter}-${numberIndex}`,
});
fabricItem.set({
customFrom: {
fabricAttribute: drawerForm.objective.questionTypeValue,
questionBefore: drawerForm.subjective.questionBefore,
questionAfter: drawerForm.subjective.questionAfter,
score: drawerForm.subjective.score,
isPage: numberIndex,
...obj,
},
});
saveFabricData();
changeDrawer(false);
})
.catch(() => {});
return;
} else {
textTitle.set({
text: `${drawerForm.subjective.questionBefore}-${drawerForm.subjective.questionAfter}`,
});
fabricItem.set({
customFrom: {
fabricAttribute: drawerForm.objective.questionTypeValue,
questionBefore: drawerForm.subjective.questionBefore,
questionAfter: drawerForm.subjective.questionAfter,
score: drawerForm.subjective.score,
isPage: -1,
...obj,
},
});
}
break;
case 5:
// 其他
if (textList.value.filter((item) => item.checked).length) {
textTitle.set({
text: `${textList.value
.filter((item) => item.checked)
.map((item) => item.text)
.join("")}-反面定位点`,
});
fabricItem.set({
customFrom: {
title: textList.value
.filter((item) => item.checked)
.map((item) => item.text)
.join(""),
...obj,
},
});
} else {
ElMessage.error("请选择文字!");
return;
}
break;
}
changeDrawer(false);
} else {
if (!currentFabric.value) {
ctx.value.remove(fabricItem);
}
changeDrawer(false);
}
saveFabricData();
changeImgSrcIndex(imgSrcIndex.value);
}
currentFabric.value = null;
};
// 获取距离
const getOffsetTop = (
xNumber: number,
yNumber: number,
index: number,
top: number
): number => {
let offsetTop = 0;
const allNumber = [...Array(xNumber * yNumber).keys()];
Array.from({ length: Math.ceil(allNumber.length / xNumber) }, (_v: any, i) =>
allNumber.slice(i * xNumber, i * xNumber + xNumber)
).forEach((item: number[], itemIndex: number) => {
if (item.includes(index)) {
offsetTop = itemIndex;
}
});
return offsetTop * top;
};
// 客观题生成题号
const objectiveNumber = (
fabricItem: any,
rect: any,
rectWidth: number,
rectHeight: number
): void => {
// 边距
const obj = {
fontSize: 12,
fill: "black",
fontWeight: "normal",
fabricId: cteatedNumber(),
backgroundColor: "transparent",
};
if (drawerForm.objective.range) {
// 竖向
for (let index = 0; index < drawerForm.objective.questionNumber; index++) {
let textNumber = 0 as any;
if (drawerForm.objective.questionBefore % 1 === 0) {
textNumber = drawerForm.objective.questionBefore++;
} else {
let before = drawerForm.objective.questionBefore.toString().split(".")[0] * 1;
let after = drawerForm.objective.questionBefore.toString().split(".")[1] * 1;
textNumber = `${before}.${after + index}`;
}
fabricItem.add(
new fabric.Text(`${textNumber}`, {
left:
rect.get("left") +
rectWidth * index +
rectWidth / 2 -
(drawerForm.objective.questionNumber + 1),
top: rect.get("top") + 4,
...obj,
})
);
}
} else {
// 横向
for (let index = 0; index < drawerForm.objective.questionNumber; index++) {
let textNumber = 0 as any;
if (drawerForm.objective.questionBefore % 1 === 0) {
textNumber = drawerForm.objective.questionBefore++;
} else {
let before = drawerForm.objective.questionBefore.toString().split(".")[0] * 1;
let after = drawerForm.objective.questionBefore.toString().split(".")[1] * 1;
textNumber = `${before}.${after + index}`;
}
fabricItem.add(
new fabric.Text(`${textNumber}`, {
left: rect.get("left") + 4,
top:
rect.get("top") +
rectHeight * index +
rectHeight / 2 -
(drawerForm.objective.questionNumber + 1),
...obj,
})
);
}
}
};
// 清空小矩形
const deleteSmallRect = (index?: number): void => {
if (currentFabric.value) {
const maxRect = currentFabric.value.target
.getObjects()
.reduce(function (currentMax: any, obj: any) {
return obj.get("width") > currentMax.get("width") ? obj : currentMax;
}, currentFabric.value.target.getObjects()[0]);
const deleteRectArr = currentFabric.value.target
.getObjects()
.filter(
(item: any) =>
item.get("type") === "rect" && item.get("fabricId") !== maxRect.get("fabricId")
);
currentFabric.value.target.remove(...deleteRectArr);
switch (index) {
case 3:
// 客观题删除题号
const allDelText = currentFabric.value.target
.getObjects()
.filter(
(item: any) =>
item.get("type") === "text" &&
!textFabricList.value.includes(item.get("text"))
);
currentFabric.value.target.remove(...allDelText);
break;
}
}
};
const getIndex = (): number => {
function findFirstNonSequentialFromOne(arr: number[]) {
arr.sort((a: number, b: number) => a - b);
if (arr[0] !== 1) {
return 1;
}
let expected = 1;
for (let i = 0; i < arr.length; i++) {
if (arr[i] === expected) {
expected++;
} else if (arr[i] > expected) {
return expected;
}
}
return null;
}
let numberList = getAllQuestionNumerPage().map((item: any) => {
return item.customFrom.isPage;
});
let index = 1 as number | null;
if (findFirstNonSequentialFromOne(numberList)) {
index = findFirstNonSequentialFromOne(numberList);
} else {
index = numberList.length ? numberList[numberList.length - 1] + 1 : 1;
}
return index || 1;
};
// 获取所有主观题(必须是分页题)
const getAllQuestionNumerPage = (): any => {
let allQuestion = [] as any;
function appendFabricQuestions(list: any, index: number): void {
allQuestion.push(
...list[index].point.filter(
(row: any) => row.customFrom && row.customFrom.isPage >= 0
)
);
}
appendFabricQuestions(templateInfo.headList, 4);
appendFabricQuestions(templateInfo.tailsList, 4);
return allQuestion;
};
// 获取所有题目(不包含分页题)
const getAllQuestionNumer = (): any => {
let allQuestion = [] as any;
function appendFabricQuestions(list: any, index: number): void {
allQuestion.push(
...list[index].point.filter(
(row: any) =>
row.customFrom && (!row.customFrom.isPage || row.customFrom.isPage === -1)
)
);
}
appendFabricQuestions(templateInfo.headList, 3);
appendFabricQuestions(templateInfo.headList, 4);
appendFabricQuestions(templateInfo.tailsList, 3);
appendFabricQuestions(templateInfo.tailsList, 4);
return allQuestion;
};
// 获取当前最大题号
const getQuestionNumer = (): number => {
let allQuestionNumer = getAllQuestionNumer().filter((item: any) => {
return (item.customFrom.questionBefore * 1) % 1 === 0;
});
return allQuestionNumer.length
? Math.max(
...allQuestionNumer.map((item: any) => item.customFrom.questionAfter * 1)
) + 1
: 1;
};
// 判断客观题主观题题目是否重复
const isQuestionNumerRepeat = (): boolean => {
let allQuestionNumer = getAllQuestionNumer().map((item: any) => {
return {
start: item.customFrom.questionBefore * 1,
end: item.customFrom.questionAfter * 1,
};
});
// 题号是否重叠
function hasOverlap(ranges: any): boolean {
function isOverlapping(range1: any, range2: any) {
const { start: start1, end: end1 } = range1;
const { start: start2, end: end2 } = range2;
return Math.max(start1, start2) <= Math.min(end1, end2);
}
for (let i = 0; i < ranges.length; i++) {
for (let j = i + 1; j < ranges.length; j++) {
if (isOverlapping(ranges[i], ranges[j])) {
return true;
}
}
}
return false;
}
// 题号是否重复
function hasIdenticalObjects(arrayOfObjects: any): boolean {
const objectHashes = new Map();
for (const obj of arrayOfObjects) {
const jsonString = JSON.stringify(obj);
if (objectHashes.has(jsonString)) {
return true;
}
objectHashes.set(jsonString, true);
}
return false;
}
if (currentFabric.value) {
let item = currentFabric.value.target;
let itemQuestionNumerIndex = allQuestionNumer.findIndex(
(row: any) =>
row.start === item.customFrom.questionBefore &&
row.end === item.customFrom.questionAfter
);
allQuestionNumer.splice(itemQuestionNumerIndex, 2, {
start:
navListIndex.value === 3
? drawerForm.objective.questionBefore
: drawerForm.subjective.questionBefore,
end:
navListIndex.value === 3
? drawerForm.objective.questionAfter
: drawerForm.subjective.questionAfter,
});
} else {
allQuestionNumer.push({
start:
navListIndex.value === 3
? drawerForm.objective.questionBefore
: drawerForm.subjective.questionBefore,
end:
navListIndex.value === 3
? drawerForm.objective.questionAfter
: drawerForm.subjective.questionAfter,
});
}
if (hasOverlap(allQuestionNumer) || hasIdenticalObjects(allQuestionNumer)) {
return true;
}
return false;
};
// 修改考试类型
const changeExamType = (type: number): void => {
if (isLockStatus.value) {
ElMessage.error("已锁定,无法修改!");
return;
}
drawerForm.examId.examType = type;
};
// 修改默认题号
const changeQuestion = (e?: any): void => {
if (typeof e === "number") {
if (e % 1 === 0) {
drawerForm.objective.questionAfter =
(e || 1) + drawerForm.objective.questionNumber - 1;
} else {
let decimalPointBeforeNumber = Number(e.toString().split(".")[1]);
let decimalPointAfterNumber = Number(e.toString().split(".")[0]);
drawerForm.objective.questionAfter = Number(
decimalPointAfterNumber +
"." +
(decimalPointBeforeNumber + drawerForm.objective.questionNumber - 1)
);
}
} else {
if (drawerForm.objective.questionBefore % 1 === 0) {
drawerForm.objective.questionAfter =
drawerForm.objective.questionBefore + drawerForm.objective.questionNumber - 1;
} else {
let decimalPointBeforeNumber = Number(
drawerForm.objective.questionBefore.toString().split(".")[1]
);
let decimalPointAfterNumber = Number(
drawerForm.objective.questionBefore.toString().split(".")[0]
);
drawerForm.objective.questionAfter = Number(
decimalPointAfterNumber +
"." +
(decimalPointBeforeNumber + drawerForm.objective.questionNumber - 1)
);
}
}
};
// 修改客观题题型
const changeObjectiveQuestionType = (item: { value: number; text: string }): void => {
if (isLockStatus.value) {
ElMessage.error("已锁定,无法修改!");
return;
}
if (navListIndex.value === 3) {
drawerForm.objective.questionTypeValue = item.value;
drawerForm.objective.optionNumber =
item.value === 2 ? 2 : drawerForm.objective.optionNumber;
} else {
drawerForm.subjective.questionTypeValue = item.value;
}
};
// 设置导航栏文字
const setNavTitle = (): void => {
const fabricItem = getFabricItem();
switch (navListIndex.value) {
case 0:
const fabricPositionTitle = [] as string[];
fabricItem.forEach((row: any) => {
row.forEachObject((item: any) => {
if (positionTitle.value.includes(item.get("text"))) {
fabricPositionTitle.push(item.get("text"));
}
});
});
let str = findFirstMissingValue(fabricPositionTitle),
msg = "";
placeholderText.value = `提示:请确保框选区域略大于定位点黑色图块,每面均需框选。`;
if (templateInfo.imgSrc.length > 1) {
if (
templateInfo.headList[navListIndex.value].point.length === 4 &&
templateInfo.tailsList[navListIndex.value].point.length === 4
) {
msg = "定位点已框选完毕。";
}
} else {
if (templateInfo.headList[navListIndex.value].point.length === 4) {
msg = "定位点已框选完毕。";
}
}
operationText.value = str
? `当前需框选:${imgSrcIndex.value ? "反面" : "正面"}${str}`
: msg
? msg
: `${imgSrcIndex.value ? "反面" : "正面"}定位点已框选。`;
break;
case 1:
placeholderText.value = `提示:请框选一行较长的文字,每面均需框选。`;
operationText.value = `${
!fabricItem.length ? "当前需框选:学科。" : "学科已框选。"
}`;
break;
case 2:
placeholderText.value = `提示:请框选完整的考号区域,仅正面需要框选。`;
operationText.value = `${
!fabricItem.length ? "当前需框选:考号。" : "考号已框选。"
}`;
break;
case 3:
placeholderText.value = `提示:请框选题号;不同小块(一般为5题),不同题型分开框选。`;
operationText.value = ``;
break;
case 4:
placeholderText.value = `提示:请确保框选区域略大于作答区域。`;
operationText.value = ``;
break;
case 5:
placeholderText.value = ``;
operationText.value = ``;
break;
}
};
// 获取定位胆标题
const findFirstMissingValue = (arr: string[]): string => {
const setArr2 = new Set(arr);
for (const value of positionTitle.value) {
if (!setArr2.has(value)) {
return value;
}
}
return "";
};
// 按钮状态
const showImage = (index: number): boolean => {
const headList = templateInfo.headList[index].point.length;
const tailsList = templateInfo.tailsList[index].point.length;
switch (index) {
case 0:
if (templateInfo.imgSrc.length > 1) {
if (headList === 4 && tailsList === 4) {
return true;
}
} else {
if (headList === 4) {
return true;
}
}
break;
case 1:
case 2:
if (headList > 0 || tailsList > 0) {
return true;
}
break;
}
return false;
};
// 获取画布允许拖拽的范围
const getCalculateRectangleSize = () => {
function calculateRectangleSize(pointA: any, pointB: any, pointC: any): any {
// 定义点的坐标
let x1 = Math.min(pointA.x, pointB.x);
let x2 = Math.max(pointA.x, pointB.x);
let y1 = Math.min(pointA.y, pointC.y);
let y2 = Math.max(pointA.y, pointC.y);
// 计算矩形的宽度和高度
let width = x2 - x1;
let height = y2 - y1 + 20;
return { width, height, left: pointA.x, top: pointA.y };
}
const templateInfoItem = getTemplateInfo(true);
const rectPositionArr = [] as any;
templateInfoItem[0].point.forEach((item: any) => {
let rectPosition = item.children.find((row: any) => {
return row.rectPosition;
}).rectPosition;
let textPosition = item.children.find((row: any) => {
return positionTitle.value.includes(row.text);
}).text;
if (rectPosition) {
rectPositionArr.push({
rectPosition,
textPosition,
});
}
});
const sortedArray = [] as any;
for (let index = 0; index < positionTitle.value.length; index++) {
const item = positionTitle.value[index];
const rectPostItem = rectPositionArr.find(
(item1: any) => item1.textPosition === item
);
if (rectPostItem) {
sortedArray.push(rectPostItem);
}
}
return calculateRectangleSize(
sortedArray[0].rectPosition,
sortedArray[1].rectPosition,
sortedArray[2].rectPosition
);
};
// 设置边框线
const setBoderLine = (): any => {
const templateInfoItem = getTemplateInfo(true);
if (templateInfoItem[0].point.length === 4) {
const size = getCalculateRectangleSize();
return {
border: "1px dashed red",
width: `${size.width}px`,
height: `${size.height}px`,
top: `${size.top}px`,
left: `${size.left}px`,
position: "absolute",
zIndex: 1,
};
}
return false;
};
// 右键添加菜单
const onContextmenu = (e: any): void => {
const target = ctx.value.findTarget(e);
const mousePos = getCanvasMousePos(e);
contextmenu.value = target;
if (target) {
nextTick(() => {
const contextmenuDom = document.getElementsByClassName(
"menu-button"
)[0] as HTMLElement;
contextmenuDom.style.left = `${mousePos.x}px`;
contextmenuDom.style.top = `${mousePos.y}px`;
});
}
};
// 设置层级
const changeZindex = (bool: boolean): void => {
if (bool) {
ctx.value.bringToFront(contextmenu.value);
} else {
ctx.value.sendToBack(contextmenu.value);
}
ctx.value.discardActiveObject();
ctx.value.renderAll();
contextmenu.value = null;
ElMessage.success("设置成功");
};
// 是否可以移出视图
const isMoveOutView = (): boolean => {
let row = [],
bool = true;
if (ctx.value.getActiveObject()) {
row = ctx.value
.getActiveObject()
.getObjects()
.filter(
(obj: any) => obj.get("text") && obj.get("text").indexOf("反面定位点") >= 0
);
}
if (row.length || (otherType.value === 3 && !ctx.value.getActiveObject())) {
bool = false;
} else {
bool = true;
}
return (
(![0, 1, 5].includes(navListIndex.value) || (navListIndex.value === 5 && bool)) &&
templateInfo.hasPosition
);
};
// 图形移动限制操作
const isDragOutCanvas = (bool: boolean): void => {
let obj = ctx.value.getActiveObject();
if (bool) {
const restrictedArea = {
...getCalculateRectangleSize(),
};
const newX = Math.max(
restrictedArea.left,
Math.min(obj.left, restrictedArea.left + restrictedArea.width - obj.width)
);
const newY = Math.max(
restrictedArea.top,
Math.min(obj.top, restrictedArea.top + restrictedArea.height - obj.height)
);
if (newX !== obj.left || newY !== obj.top) {
obj.set({ left: newX, top: newY });
}
} else {
if (obj.left < 0) {
obj.left = 0;
}
if (obj.top < 0) {
obj.top = 0;
}
if (obj.left + obj.width > 0 + ctx.value.getWidth()) {
obj.left = ctx.value.getWidth() - obj.width;
}
if (obj.top + obj.height > 0 + ctx.value.getHeight()) {
obj.top = ctx.value.getHeight() - obj.height;
}
}
};
// 设置题目列表
const setQuestionList = (): void => {
templateInfo.questionList = [];
let allQuestion = [] as any;
function appendFabricQuestions(list: any, index: number): void {
allQuestion.push(
...list[index].point.filter(
(row: any) => row.customFrom && (row.fabricType === 3 || row.fabricType === 4)
)
);
}
appendFabricQuestions(templateInfo.headList, 3);
appendFabricQuestions(templateInfo.headList, 4);
appendFabricQuestions(templateInfo.tailsList, 3);
appendFabricQuestions(templateInfo.tailsList, 4);
templateInfo.questionList.push(...allQuestion);
};
// 修改数据
const changeData = (): any => {
function divideByTwoProperties(array: any, property1Key: string, property2Key: string) {
const categoriesMap = new Map();
array.forEach((item: any) => {
const value1 = item[property1Key];
const value2 = item[property2Key];
const key = `${value1}-${value2}`;
if (!categoriesMap.has(key)) {
categoriesMap.set(key, []);
}
categoriesMap.get(key).push(item);
});
return Array.from(categoriesMap.values());
}
const data = [] as any;
templateInfo.otherData.forEach((item: any, index: number) => {
data.push({
id: item.id,
index: index + 1,
width: imgDom.value.clientWidth,
height: imgDom.value.clientHeight,
tempData: index === 0 ? templateInfo.headList : templateInfo.tailsList,
from: {
positioningPoint: {},
subjectPoint: {},
examIDPoint: {},
objectiveQuestionsPoint: {},
subjectiveQuestionsPoint: {},
otherPoint: {},
},
});
});
data.forEach((item: any) => {
item.tempData[0].point = item.tempData[0].point.sort((a: any, b: any) => {
return (
positionTitle.value.indexOf(a.customFrom.title) -
positionTitle.value.indexOf(b.customFrom.title)
);
});
item.from.positioningPoint = item.tempData[0].point.map((row: any) => {
return row.customFrom;
});
item.from.subjectPoint =
item.tempData[1].point.length == 1 ? item.tempData[1].point[0].customFrom : {};
item.from.examIDPoint = item.tempData[2].point.map((row: any) => {
return {
...row.customFrom,
width: row.width,
height: row.height - 36.16,
top: row.top + 19,
left: row.left,
};
});
item.from.objectiveQuestionsPoint = item.tempData[3].point.map((row: any) => {
return {
...row.customFrom,
width: row.width,
height: row.height - 36.16,
top: row.top + 19,
left: row.left,
};
});
item.from.subjectiveQuestionsPoint = item.tempData[4].point.map((row: any) => {
return {
...row.customFrom,
width: row.width,
height: row.height - 36.16,
top: row.top + 19,
left: row.left,
};
});
item.from.otherPoint = item.tempData[5].point.map((row: any) => {
return {
...row.customFrom,
width: row.width,
height: row.height - 36.16 / 2,
top: row.top + 19,
left: row.left,
};
});
divideByTwoProperties(
item.from.subjectiveQuestionsPoint,
"questionAfter",
"questionBefore"
).forEach((row: any) => {
if (row.length > 1) {
let findItem = row.find((i: any) => i.isPage === -1);
findItem.isPage = 0;
} else {
row[0].isPage = -1;
}
});
});
return data;
};
// 保存
const btnClick = (str: string): void => {
const data = changeData().map((item: any) => {
return {
...item,
tempData: JSON.stringify(item.tempData),
};
});
let msg = "";
if (templateInfo.hasPosition) {
if (templateInfo.imgSrc.length === 2) {
if (
templateInfo.headList[0].point.length !== 4 ||
templateInfo.tailsList[0].point.length !== 4
) {
msg = "定位点框选完成才能保存!";
}
} else {
if (templateInfo.headList[0].point.length !== 4) {
msg = "定位点框选完成才能保存!";
}
}
}
if (msg) {
ElMessage.error(msg);
return;
}
if (str === "save") {
save(true);
} else {
LockListRef.value.getData(data);
}
};
// 锁定
const lock = async (): Promise<void> => {
await save();
GetLock(router.currentRoute.value.query.mid as string).then((res: any) => {
if (res.code === 200) {
ElMessage.success("锁定成功");
isLockStatus.value = true;
}
});
};
// 保存
const save = async (isShow: boolean = false): Promise<void> => {
const data = changeData().map((item: any) => {
return {
...item,
tempData: JSON.stringify(item.tempData),
};
});
PostSave({
templateData: data,
mid: router.currentRoute.value.query.mid,
}).then((res: any) => {
if (res.code === 200) {
if (isShow) {
ElMessage.success("保存成功");
}
}
});
};
</script>
<style lang="scss" scoped>
@mixin flexAlignCenter($align-center: "center") {
display: flex;
align-items: #{$align-center};
}
.warp {
height: 100vh;
width: 100vw;
overflow: auto;
min-width: 1200px;
display: flex;
flex-direction: column;
&-title {
background-color: #2d8cf0;
line-height: 60px;
height: 60px;
padding: 0 40px;
box-sizing: border-box;
color: white;
font-size: var(--ft20);
flex-shrink: 0;
}
&-nav {
flex-shrink: 0;
z-index: 1;
&-tab {
@include flexAlignCenter();
justify-content: space-between;
height: 60px;
padding: 0 40px;
background-color: white;
box-shadow: 0px 0px 6px 0px #0000001a;
&-list {
@include flexAlignCenter();
> div {
@include flexAlignCenter();
cursor: pointer;
> span {
color: #727272;
font-size: var(--ft16);
}
> img {
margin: 0 17px;
}
.active {
font-weight: bold;
color: #2d8cf0;
}
}
}
&-delete {
@include flexAlignCenter();
cursor: pointer;
> span {
color: #ff6565;
font-size: var(--ft16);
margin-left: 8px;
}
}
}
&-title {
@include flexAlignCenter();
border-bottom: 1px solid #e1e1e1;
padding: 10px 40px;
box-sizing: border-box;
flex-shrink: 0;
&-text {
color: #767676;
font-size: var(--ft14);
flex-grow: 1;
}
&-operation {
flex-shrink: 0;
margin-left: 10px;
@include flexAlignCenter();
$color: #2d8cf0;
&-btn {
@include flexAlignCenter();
> div {
font-weight: bold;
font-size: var(--ft14);
width: 80px;
line-height: 30px;
text-align: center;
cursor: pointer;
border-radius: 6px;
margin-right: 10px;
background-color: lighten($color, 10%);
color: #fff;
&:hover {
background-color: darken($color, 1%);
}
&:active {
background-color: darken($color, 6%);
}
&:last-child {
margin: 0;
}
}
.active {
background-color: darken($color, 8%);
}
}
&-other {
margin: 0 20px;
> div {
font-weight: bold;
font-size: var(--ft14);
line-height: 30px;
text-align: center;
cursor: pointer;
border-radius: 6px;
background-color: darken($color, 8%);
color: #fff;
padding: 0 20px;
&:hover {
background-color: lighten($color, 10%);
}
&:active {
background-color: darken($color, 10%);
}
}
}
&-icon {
@include flexAlignCenter();
> img:nth-child(1) {
margin-right: 10px;
}
> img {
cursor: pointer;
}
}
}
}
}
&-content {
flex-grow: 1;
background-color: rgb(242, 242, 242);
@include flexAlignCenter("flex-start");
&-left {
flex-grow: 1;
width: 0;
height: 100%;
display: flex;
justify-content: center;
align-items: center;
box-sizing: border-box;
&-box {
height: 78vh;
width: 100%;
overflow: auto;
@mixin isPageType($type) {
width: 100%;
height: 100%;
> #zoom-div {
margin: 0 auto;
@if $type == "A3" {
width: 1570px;
} @else {
width: 1200px;
}
height: auto;
flex-shrink: 0;
position: relative;
transform-origin: left top;
position: relative;
.menu-button {
border: 1px reds solid;
position: absolute;
left: 0;
top: 0;
z-index: 3;
display: flex;
flex-direction: column;
border: 1px #ccc solid;
background-color: white;
> div {
padding: 0 30px;
border-bottom: 1px #ccc solid;
cursor: pointer;
line-height: 30px;
font-size: var(--ft14);
white-space: nowrap;
text-overflow: ellipsis;
overflow: hidden;
&:last-child {
border-bottom: none;
}
}
}
> img {
width: 100%;
height: 100%;
display: block;
}
:deep(.canvas-container) {
position: absolute !important;
top: 0;
z-index: 2;
}
}
}
.A4 {
@include isPageType("A4");
}
.A3 {
@include isPageType("A3");
}
}
}
&-right {
background-color: white;
flex-shrink: 0;
display: flex;
flex-direction: column;
width: 300px;
height: 100%;
padding: 20px;
box-sizing: border-box;
&-info {
flex-grow: 1;
box-sizing: border-box;
overflow-y: auto;
display: flex;
flex-direction: column;
&-fb {
color: #8b8b8b;
font-size: var(--ft16);
margin-bottom: 12px;
> span {
color: black;
}
}
&-table {
margin: 0 0 40px;
flex-grow: 1;
}
&-btn {
display: flex;
justify-content: center;
> :deep(.el-button) {
margin: 0 4px 0 0;
&:last-child {
margin: 0 0 0 0;
}
}
}
}
}
}
}
:deep(.drawer-custom-style) {
.drawer-custom-style-body {
height: 100%;
display: flex;
flex-direction: column;
justify-content: space-between;
&-content {
height: 90%;
overflow-y: auto;
}
&-footer {
@include flexAlignCenter();
justify-content: center;
height: 8%;
}
.examId {
> div {
@include flexAlignCenter();
margin-bottom: 10px;
> span {
color: #727272;
font-size: var(--ft14);
}
}
.full-filling {
flex-direction: column;
> div {
border: 1px #727272 solid;
box-sizing: border-box;
padding: 20px;
width: 100%;
margin-top: 10px;
> div {
@include flexAlignCenter();
justify-content: center;
> span {
border: 1px #727272 solid;
width: 30px;
text-align: center;
font-size: var(--ft14);
}
}
}
}
.bar-code {
flex-direction: column;
> div {
border: 1px #727272 dashed;
box-sizing: border-box;
padding: 40px;
width: 100%;
margin-top: 10px;
> div {
border: 1px #727272 dashed;
height: 200px;
text-align: center;
box-sizing: border-box;
line-height: 200px;
background-color: #e1e1e1;
}
}
}
}
.objective {
> div {
@include flexAlignCenter();
margin-bottom: 10px;
> span {
color: #727272;
font-size: var(--ft14);
text-align: right;
width: 100px;
}
}
.syt-row {
flex-direction: column;
> span {
text-align: center;
}
> div {
border: 1px #727272 solid;
box-sizing: border-box;
padding: 20px;
width: 100%;
margin-top: 10px;
overflow-x: auto;
> div {
@include flexAlignCenter();
justify-content: center;
> span {
&:first-child {
font-size: var(--ft12);
margin: 0 8px 4px 0;
width: 30px;
text-align: right;
}
&:not(:first-child) {
border: 1px #727272 solid;
width: 14px;
text-align: center;
flex-shrink: 0;
font-size: var(--ft12);
margin: 0 4px 4px 0;
}
&:last-child {
margin-right: 0;
}
}
}
}
}
.syt-column {
flex-direction: column;
> span {
text-align: center;
}
> div {
border: 1px #727272 solid;
box-sizing: border-box;
padding: 20px;
width: 100%;
margin-top: 10px;
overflow-x: auto;
display: flex;
justify-content: center;
align-items: start;
> div {
display: flex;
flex-direction: column;
justify-content: center;
margin: 0 4px 4px 0;
> span {
&:first-child {
font-size: var(--ft12);
text-align: center;
text-align: center;
margin: 0;
}
&:not(:first-child) {
border: 1px #727272 solid;
width: 14px;
text-align: center;
flex-shrink: 0;
font-size: var(--ft12);
margin: 4px 0 0 0;
}
}
}
}
}
}
.subjective {
> div {
@include flexAlignCenter();
margin-bottom: 10px;
> span {
color: #727272;
font-size: var(--ft14);
text-align: right;
width: 80px;
}
}
}
}
}
</style>