@likg/match-line
TypeScript icon, indicating that this package has built-in type declarations

1.0.13 • Public • Published

考试系统·连线题

推荐博文:稀土掘金·浅谈 js+canvas 实现考试系统·拖拽连线题

安装

$ npm i @likg/match-line
$ pnpm i @likg/match-line
$ yarn add @likg/match-line
import MatchLine from '@likg/match-line';
const matchLine = new MatchLine(options: MatchLineConfigs);

MatchLineOptions

连线题选项数据结构:

export type MatchLineOptions = Array<{
  leftOption: string;
  rightOption: string;
}>

示例如下:

[
  { leftOption: '水果', rightOption: '🥕' },
  { leftOption: '动物', rightOption: '🚗' },
  { leftOption: '汽车', rightOption: '🐒' },
  { leftOption: '蔬菜', rightOption: '🍌' },
]

MatchLineAnswers

答案数据结构:

export type MatchLineAnwsers = Record<string, string>;

示例如下:

{
  水果: '🥕',
  动物: '🚗',
  汽车: '🐒',
  蔬菜: '🍌',
}

MatchLineConfigs

属性 数据类型 描述
container HTMLElement 外层容器·包裹canvas和左右布局元素的容器
布局结构:div.container>div.options+canvas+backCanvas
canvas HTMLCanvasElement 实际连线标签Canvas
backCanvas HTMLCanvasElement 模拟连线标签Canvas
items NodeListOf<HTMLElement> 连线元素标签
itemActiveCls string 「可选」连线元素标签激活状态的类名,默认:active
strokeStyle string | CanvasGradient | CanvasPattern 「可选」画笔颜色,默认:#6495ED
lineWidth number 「可选」画笔粗细,默认:1
answers MatchLineAnswers 「可选」用户连线答案·可选(在查看试卷详情以及纠错时必传
standardAnswers MatchLineAnswers 「可选」标准答案(在纠错时必传)
checkAnswers boolean 「可选」是否纠错(为true时必传answers 和 standardAnswers 字段)
correctlineColor string 「可选」正确连线颜色,默认值:'#3CB371'
mislineColor string 「可选」错误连线颜色,默认值:'#DC143C'
disabled boolean 「可选」是否禁用(在查看试卷详情以及纠错时必传true)
debug boolean 「可选」是否启用调式模式
onChange (anwsers: MatchLineAnswers) => void 「可选」每一次连线成功的回调·参数为连线结果集

APIs

  • matchLine.reset():重置
  • matchLine.undo():撤销
  • matchLine.getAnswers():获取连线结果
  • matchLine.checkAnswers():纠错

使用指南

布局

<div class="container">
	<!-- 工具栏 -->
	<div class="tools">
		<div class="button reset">重置</div>
		<div class="button undo">撤销</div>
		<div class="button check">纠错</div>
	</div>
	<div class="content">
		<!-- 左侧 -->
		<div class="list left">
			<div class="item" data-value="水果" data-ownership="L">水果</div>
			<div class="item" data-value="动物" data-ownership="L">动物</div>
			<div class="item" data-value="汽车" data-ownership="L">汽车</div>
			<div class="item" data-value="蔬菜" data-ownership="L">蔬菜</div>
		</div>
		<!-- 右侧 -->
		<div class="list right">
			<div class="item" data-value="🥕" data-ownership="R">🥕</div>
			<div class="item" data-value="🚗" data-ownership="R">🚗</div>
			<div class="item" data-value="🐒" data-ownership="R">🐒</div>
			<div class="item" data-value="🍌" data-ownership="R">🍌</div>
		</div>
		<!-- 实际连线标签 -->
		<canvas id="canvas" width="400" height="250"></canvas>
		<!-- 模拟连线标签 -->
		<canvas id="backCanvas" width="400" height="250"></canvas>
	</div>
</div>

提示:请严格按照上面的布局方式布局,连线元素必须设置 data-valuedata-ownership 属性,便于处理连线逻辑。

代码示例(react)

QuestionMatchLine.tsx

import React, { useEffect, useRef, useState } from 'react';
import './QuestionMatchLine.less';
import MatchLine from './m';

// -- 数据源
const dataSource = [
  { leftOption: '水果', rightOption: '🥕' },
  { leftOption: '动物', rightOption: '🚗' },
  { leftOption: '汽车', rightOption: '🐒' },
  { leftOption: '蔬菜', rightOption: '🍌' },
];

// -- 标准答案
const standardAnswers = {
  水果: '🍌',
  动物: '🐒',
  汽车: '🚗',
  蔬菜: '🥕',
};

const QuestionMatchLine: React.FC = React.memo(() => {
  const [matchLine, setMatchLine] = useState<MatchLine | null>(null);

  const containerRef = useRef<HTMLDivElement | null>(null);
  const canvasRef = useRef<HTMLCanvasElement | null>(null);
  const backCanvasRef = useRef<HTMLCanvasElement | null>(null);

  useEffect(() => {
    // -- 初始化连线库
    if (canvasRef.current && backCanvasRef.current && containerRef.current) {
      const items = containerRef.current.querySelectorAll('.option');
      if (items.length > 0) {
        const matching = new MatchLine({
          container: containerRef.current,
          items: items as NodeListOf<HTMLElement>,
          canvas: canvasRef.current,
          backCanvas: backCanvasRef.current,
          itemActiveCls: 'active',
          standardAnswers,
          debug: true,
          onChange: (anwsers) => {
            console.log(anwsers);
          },
        });
        setMatchLine(matching);
      }
    }
  }, []);

  const renderItems = (ownership: 'L' | 'R') => {
    const k = ownership === 'L' ? 'leftOption' : 'rightOption';
    return dataSource.map((item, index) => (
      <div
        className="option"
        key={index}
        data-value={item[k]}
        data-ownership={ownership}
      >
        {item[k]}
      </div>
    ));
  };
  return (
    <div className="match-line">
      <div className="tools">
        <button onClick={() => matchLine?.reset()}>重置</button>
        <button onClick={() => matchLine?.undo()}>撤销</button>
        <button
          onClick={() => {
            const anwsers = matchLine?.getAnswers();
            console.log(anwsers);
          }}
        >
          查询
        </button>
        <button onClick={() => matchLine?.checkAnswers()}>纠错</button>
      </div>
      <div className="contents" ref={containerRef}>
        <div className="leftOptions">{renderItems('L')}</div>
        <div className="rightOptions">{renderItems('R')}</div>
        <canvas ref={canvasRef}></canvas>
        <canvas ref={backCanvasRef}></canvas>
      </div>
    </div>
  );
});

export default QuestionMatchLine;

QuestionMatchLine.less

.match-line {
  width: 400px;
  height: auto;
  margin: 100px auto;
  .tools {
    margin-bottom: 16px;
    button {
      margin-right: 8px;
    }
  }
  .contents {
    border: 1px dashed #ccc;
    position: relative;
    box-sizing: border-box;
    padding: 24px;
    display: flex;
    justify-content: space-between;
    align-items: centers;
    .option {
      width: 100px;
      height: 40px;
      background-color: #fff;
      display: flex;
      justify-content: center;
      align-items: center;
      border-radius: 4px;
      cursor: pointer;
      user-select: none;
      color: #555;
      position: relative;
      z-index: 1;
      &.active {
        background: #6495ed;
        color: #fff;
      }
      &:not(:last-child) {
        margin-bottom: 16px;
      }
    }
    canvas {
      position: absolute;
      top: 0;
      left: 0;
    }
  }
}

后续

我将持续优化升级 MatchLine,如果大家有什么疑问或建议欢迎留言。

Package Sidebar

Install

npm i @likg/match-line

Weekly Downloads

0

Version

1.0.13

License

ISC

Unpacked Size

49.5 kB

Total Files

8

Last publish

Collaborators

  • lihongyao