3520 lines
101 KiB
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>
|