心理测评全系统设计与代码实现

在这里插入图片描述

心理评测不同于考试系统, 答案是没有对错的,且都是选择题, 是根据被评测者所选的选项综合加权得分后,测试出被评测者的性格、爱好、职业等更近哪一种类型。
这里分别从数据库设计、后台代码实现、和小程序开发, 和评测报告的生成介绍

– 系统为本人从0开始搭建,持续同步更新, 欢迎各位大侠拍砖。

一、数据库设计篇

数据库— Mysql,表1:测评主表、表2:问题表、表3:回答选项表、表4:测试结果表、其它:报告、资源等

  1. 测评主表
CREATE TABLE `t_test_m` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `code` varchar(20) DEFAULT NULL COMMENT '问卷编码',
  `title` varchar(50) DEFAULT NULL COMMENT '问卷标题',
  `sub_title` varchar(50) DEFAULT NULL COMMENT '副标题',
  `sub_cn` varchar(30) DEFAULT NULL COMMENT '简称中文',
  `sub_en` varchar(30) DEFAULT NULL COMMENT '简称英文',
  `direction` text COMMENT '问卷说明(以MD读法保存)',
  `isfree` int(11) DEFAULT '1' COMMENT '是否免费(1免费,其它为收费)',
  `money` int(11) DEFAULT NULL COMMENT '收费金额,单位分',
  `thkMsg` text COMMENT '结束感谢语言',
  `ctime` datetime DEFAULT NULL COMMENT '添加时间',
  `startTime` datetime DEFAULT NULL COMMENT '开始时间',
  `endTime` datetime DEFAULT NULL,
  `editor` varchar(30) DEFAULT NULL COMMENT '添加人',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
  1. 问题表
CREATE TABLE `t_test_q` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `code` varchar(50) DEFAULT NULL COMMENT '问题编号',
  `qnum` varchar(100) NOT NULL COMMENT '题号',
  `title` varchar(100) NOT NULL COMMENT '测试标题',
  `direction` varchar(200) DEFAULT NULL COMMENT '描述',
  `m_code` varchar(50) DEFAULT NULL COMMENT '评测主表ID',
  `qtype` int(11) DEFAULT '1' COMMENT '该问题题型,1表示单选,9表示多选,0表示填空',
  `ctime` datetime DEFAULT NULL COMMENT '创建时间',
  `order` int(11) DEFAULT NULL COMMENT '排序号',
  `isdel` int(11) DEFAULT '0' COMMENT '是否删除 0否,1删除',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

3.问题选项表

CREATE TABLE `t_test_a` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '选项ID',
  `q_code` varchar(50) NOT NULL COMMENT '问题Code',
  `direction` varchar(200) DEFAULT NULL COMMENT '选项描述',
  `score` int(11) DEFAULT NULL COMMENT '得分,用于计算',
  `iscorrect` int(11) DEFAULT '0' COMMENT '是否为正确的选项,0表示该回答无准答案,-1表示错误,1表示正确',
  `order` int(11) DEFAULT NULL COMMENT '排序',
  `isdel` int(11) DEFAULT NULL COMMENT '是否删除,0否,1是',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
  1. 评测结果
CREATE TABLE `t_test_r` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `userid` varchar(50) DEFAULT NULL COMMENT '评测用户ID',
  `username` varchar(50) DEFAULT NULL COMMENT '用户名',
  `orgname` varchar(100) DEFAULT NULL COMMENT '所属组织',
  `mcode` varchar(50) DEFAULT NULL COMMENT '测试主表Code',
  `source` varchar(30) DEFAULT NULL COMMENT '评测来源:如小程序、官网、线下二维码',
  `resutJson` text COMMENT '评测结果, 以Json方式保存',
  `startTime` datetime DEFAULT NULL COMMENT '测试开始时间',
  `endTime` datetime DEFAULT NULL COMMENT '测试结束时间',
  `costTime` int(11) DEFAULT NULL COMMENT '耗时,单位秒。(注意耗时≠结束时间-开始时间)',
  `reportUrl` varchar(300) DEFAULT NULL COMMENT '评测报告地址:报告生成的地址, 或则预留好的文件地址',
  `reportText` text COMMENT '报告内容:以MarkDown格式保存',
  `ispay` int(11) DEFAULT '0' COMMENT '是否已支付,0未支付,1已支付',
  `amount` int(11) DEFAULT NULL COMMENT '实际支付金额',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

到此测试评系统数据库设计的主要几张表都已完成了, 接下来以“PSTR心理压力测试”案例导入数据, 完成SQL初始化数据。

主表:

INSERT INTO t_test_m (
	CODE,
	title,
	sub_title,
	sub_cn,
	sub_en,
	direction,
	isfree,
	money,
	ctime,
	testminute,
	startTime,
	endTime,
	editor
)
VALUES
	('PSTR',
		'心理压力测试(PSTR专业版)',
		'PSTR',
		'心理压力测试',
		'PSTR',
		'压力既可以是你生活的助手也可以变成生活的阻碍。PSTR心理压力测试,帮助你了解自己面临的心理压力程度。'
		,1
		,0
		,now()
		,10
		,now()
		,'2050-01-01',
		' admin '
		)
  1. 问题表初始化数据
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR1','1','____我受背痛之苦','','PSTR','1','2022-3-17','1','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR26','26','____我喝酒','','PSTR','1','2022-3-17','26','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR2','2','____我的睡眠不定且睡不安稳','','PSTR','1','2022-3-17','2','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR27','27','____我很自觉(自己需要、情绪、动机、计划、目标等)','','PSTR','1','2022-3-17','27','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR3','3','____我有头痛','','PSTR','1','2022-3-17','3','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR28','28','____我觉得己像四分五裂','','PSTR','1','2022-3-17','28','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR4','4','____我颚部疼痛','','PSTR','1','2022-3-17','4','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR29','29','____我的眼睛又酸又累','','PSTR','1','2022-3-17','29','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR5','5','____若须等候,我会不安','','PSTR','1','2022-3-17','5','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR30','30','____我的腿,或脚抽筋','','PSTR','1','2022-3-17','30','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR6','6','____我的後颈感到疼痛','','PSTR','1','2022-3-17','6','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR31','31','____我的心跳快速','','PSTR','1','2022-3-17','31','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR7','7','____我比多数人更神经紧张','','PSTR','1','2022-3-17','7','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR32','32','____我怕结识人','','PSTR','1','2022-3-17','32','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR8','8','____我很难入睡','','PSTR','1','2022-3-17','8','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR33','33','____我手脚冰冷','','PSTR','1','2022-3-17','33','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR9','9','____我的头感到紧或痛','','PSTR','1','2022-3-17','9','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR34','34','____我患便秘','','PSTR','1','2022-3-17','34','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR10','10','____我的胃有毛病','','PSTR','1','2022-3-17','10','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR35','35','____我发现自己很容易哭','','PSTR','1','2022-3-17','35','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR11','11','____我对自己没有信心','','PSTR','1','2022-3-17','11','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR36','36','____我消化不良','','PSTR','1','2022-3-17','36','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR12','12','____我对自己说话','','PSTR','1','2022-3-17','12','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR37','37','____我咬指甲','','PSTR','1','2022-3-17','37','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR13','13','____我忧虑财务问题','','PSTR','1','2022-3-17','13','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR38','38','____我耳中有嗡嗡声','','PSTR','1','2022-3-17','38','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR14','14','____与人见面时,我会窘怯','','PSTR','1','2022-3-17','14','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR39','39','____我小便频密','','PSTR','1','2022-3-17','39','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR15','15','____我怕发生可怕的事','','PSTR','1','2022-3-17','15','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR40','40','____我有胃溃疡的毛病','','PSTR','1','2022-3-17','40','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR16','16','____白天我觉得累','','PSTR','1','2022-3-17','16','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR41','41','____我有皮肤方面的毛病','','PSTR','1','2022-3-17','41','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR17','17','____下午我感到喉咙痛,','','PSTR','1','2022-3-17','17','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR42','42','____我未经医师指示使用各种但并非由於染上感冒药物','','PSTR','1','2022-3-17','42','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR18','18','____我心情不安、无法静坐','','PSTR','1','2022-3-17','18','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR43','43','____我的咽喉很紧','','PSTR','1','2022-3-17','43','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR19','19','____我感到非常口乾','','PSTR','1','2022-3-17','19','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR44','44','____我有十二指肠溃疡的毛病','','PSTR','1','2022-3-17','44','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR20','20','____我有心脏毛病','','PSTR','1','2022-3-17','20','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR45','45','____我担心我的工作','','PSTR','1','2022-3-17','45','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR21','21','____我觉得自己不是很有用','','PSTR','1','2022-3-17','21','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR46','46','____我口腔溃烂','','PSTR','1','2022-3-17','46','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR22','22','____我吸烟','','PSTR','1','2022-3-17','22','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR47','47','____我为琐事忧虑','','PSTR','1','2022-3-17','47','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR23','23','____我肚子不舒服','','PSTR','1','2022-3-17','23','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR48','48','____我呼吸浅促','','PSTR','1','2022-3-17','48','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR24','24','____我觉得不快乐','','PSTR','1','2022-3-17','24','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR49','49','____我觉得胸部紧迫','','PSTR','1','2022-3-17','49','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR25','25','____我流汗','','PSTR','1','2022-3-17','25','0');
insert into t_test_q(code,qnum,title,direction,m_code,qtype,ctime,`order`,isdel) values('PSTR50','50','____我发现难做决定','','PSTR','1','2022-3-17','50','0');

  1. 问题选项表
insert into t_test_a(q_code,direction,score,iscorrect,`order`,isdel) values('PSTR1','从未',0,0,5,0);
insert into t_test_a(q_code,direction,score,iscorrect,`order`,isdel) values('PSTR1','从未',0,0,5,0);
……
-- 省掉248条

频率: 总是–4 经常–3 有时–2 很少–1 从未–0

问题选项表脚本没有撸完, 全部一样5个答案, 50道题共添加250条记录, 会不会有键盘侠认为我很250呢, 可以5条记录搞定的, 非要来一个250; 可能真是多余的, 但为了测评系统的通用性, 多存几条记录对数据库来说很easy。

二、后台设计与实现(.Net Core)

1、环境准备

  • Startup.cs
public void ConfigureServices(IServiceCollection services)
        {
    
     
            //注册编码提供程序
            System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance);
            //装载配置信息
            object obj =  Newtonsoft.Json.JsonConvert.DeserializeObject(System.IO.File.ReadAllText("appsettings.json"));
            services.AddDbContextPool<DataBase>(options =>
            options.UseMySql(Configuration.GetConnectionString("MySqlConnection")));
            //注入Controller
            services.AddControllers();   
            //增加Swagger
            services.AddSwaggerGen(c =>
            {
    
    
                c.SwaggerDoc("v1", new OpenApiInfo
                {
    
    
                    Version = "v1.1.0",
                    Title = "评测系统",
                    Description = "",

                });

            });
        }
  • appsettion
  "ConnectionStrings": {
    
    
    "MySqlConnection": "database=luqub;server=IP;port=3306;uid=root;password=abc0755..;CharSet=utf8;sslMode=None;Pooling=true"
  }

2、基类实现

  • 接下来写一个BaseController.cs 基类
    这里可以实现认证拦截, 日报记录等操作,IsDevelopment 用于在本地调试时不验证token
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters; 
using System;
using System.Collections.Generic; 

namespace yourspace
{
    
    
    /// <summary>
    /// 控制器基类
    /// </summary>
    [FilterException]
    [Produces("application/json")]
    [Route("api/[controller]")]
    [ApiController]
    public class BaseController : Controller
    {
    
    
        /// <summary>
        /// 服务器部署环境redis配置
        /// </summary>
        public static string redisSeting = "IP:6380,password=***,poolsize=50,ssl=false,writeBuffer=10240";
        private static bool isInit = false;
        public static bool IsDevelopment = false;

        /// <summary>
        /// 用户Token,用于关键接口校验
        /// </summary>
        public string UserToken = "";

        /// <summary>
        /// Redis操作类
        /// </summary>
        public static CSRedis.CSRedisClient csredis = null;

        /// <summary>
        /// 执行控制器(Start)重写,拦截未登录认证的用户请求, Http中结尾带"_"则不需要登录访问
        /// </summary>
        /// <param name="context"></param>
        public override void OnActionExecuting(ActionExecutingContext context)
        {
    
    
            context.HttpContext.Items["StartTime"] = DateTime.Now;
            //获取请求头
            if (!IsDevelopment && !context.HttpContext.Request.Path.Value.EndsWith('_'))
            {
    
    
                if (!isInit)
                {
    
    
                    csredis = new CSRedis.CSRedisClient(redisSeting);
                    RedisHelper.Initialization(csredis);
                    //初始化 RedisHelper
                    isInit = true;
                }
                if (!context.HttpContext.Request.Headers.ContainsKey("authorization"))
                {
    
    
                    context.Result = Redirect("/api/Common/NoToken_");
                }
                else
                {
    
    
                    if (!RedisHelper.Exists(context.HttpContext.Request.Headers["authorization"][0]))
                    {
    
    
                        context.Result = Redirect("/api/Common/AuthError_");
                    }
                    else
                    {
    
    
                        UserToken = context.HttpContext.Request.Headers["authorization"][0];
                    }

                }
            }
        }
        ///
        /// <summary>        
        /// 执行控制器(End)重写,错误日志记录到文件、数据库,可选, 如请求量过大, 可修改为异步操作
        /// </summary>
        /// <param name="context"></param>
        public override void OnActionExecuted(ActionExecutedContext context)
        {
    
    
            string log = "";
            if (context.Exception != null)
            {
    
    
                log = "[Error] " + DateTime.Now;
                Common.WriteErrorLogWP("请求出错", context.Exception);
            }
            else
            {
    
    
                log = "[Info] " + DateTime.Now;
            }
            if (context.HttpContext.Items["StartTime"]!=null)
            {
    
    
                log += " times:" + DateTime.Now.Subtract((DateTime)context.HttpContext.Items["StartTime"]).TotalMilliseconds;
            }
            log += " UserID:" + UserID + " Token:"+UserToken ;
            log += " Url:"+ context.HttpContext.Request.Path.Value;
            log += " Parms:" + context.HttpContext.Request.QueryString.Value;
            if (context.Exception != null)
            {
    
    
                log += "错误详情:"+ context.Exception;
            }
            Common.WriteLog(log);

        }

        private string _userid = null;

        /// <summary>
        /// 用户ID,前提需要有Token
        /// </summary>
        public string UserID
        {
    
    
            get
            {
    
    

                if (UserToken == "" && IsDevelopment)
                {
    
    
                    _userid = "a36925400ddc872";
                }

                if (_userid == null)
                {
    
    

                    if (!isInit)
                    {
    
    
                        csredis = new CSRedis.CSRedisClient(redisSeting);
                        RedisHelper.Initialization(csredis);
                        //初始化 RedisHelper
                        isInit = true;
                    }
                    string id = "";
                    Dictionary<string, object> dic = new Dictionary<string, object>();
                    Newtonsoft.Json.Linq.JObject obj = Newtonsoft.Json.JsonConvert.DeserializeObject(RedisHelper.Get(UserToken)) as Newtonsoft.Json.Linq.JObject;
                    Common.JObjectToDictionary(dic, obj);
                    if (dic.Count > 0)
                    {
    
    
                        id = dic["USER_ID"].ToString();
                    }
                    _userid = id;
                    return id;
                }
                else
                {
    
    
                    return _userid;
                }
            }
        }
        private Dictionary<string, object> _userDic = null;

        public Dictionary<string, object> UserDic
        {
    
    
            get
            {
    
    
                string userStrs = @"{
                  'USER_ID': 'a36925400ddc872',
                  'USER_OPENID': 'oB9sa40jdG5atBx3PjpE6c',
                  'USER_WX_NAME': '评测用户1',
                  'USER_TYPE': '9',
                  'USER_STATUS': '1',
                  'USER_ISPAY': '0',
                  'TOKEN': 'b3cad2-525400ddc872'
                }";
                if (IsDevelopment)
                {
    
    
                    _userid = "a36925400ddc872";

                    Dictionary<string, object> dic = new Dictionary<string, object>();
                    Newtonsoft.Json.Linq.JObject obj = Newtonsoft.Json.JsonConvert.DeserializeObject(userStrs) as Newtonsoft.Json.Linq.JObject;
                    Common.JObjectToDictionary(dic, obj);
                    _userDic = dic;

                }

                if (_userDic == null)
                {
    
    
                    if (!isInit)
                    {
    
    
                        csredis = new CSRedis.CSRedisClient(redisSeting);
                        RedisHelper.Initialization(csredis);
                        //初始化 RedisHelper
                        isInit = true;
                    }
                    Dictionary<string, object> dic = new Dictionary<string, object>();
                    Newtonsoft.Json.Linq.JObject obj = Newtonsoft.Json.JsonConvert.DeserializeObject(RedisHelper.Get(UserToken)) as Newtonsoft.Json.Linq.JObject;
                    Common.JObjectToDictionary(dic, obj);
                    _userDic = dic;

                }
                return _userDic;
            }
            set
            {
    
    
                //在Redis中更新用户
                if (!isInit)
                {
    
    
                    csredis = new CSRedis.CSRedisClient(redisSeting);
                    RedisHelper.Initialization(csredis);
                    //初始化 RedisHelper
                    isInit = true;
                }
                RedisHelper.Set(UserToken, value);
                _userDic = value;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="data"></param>
        /// <returns></returns>
        public override JsonResult Json(object data)
        {
    
    
            object retunData = data;
            if (data is ResultData || data is RowsData)
            {
    
    
                return base.Json(data);
            }
            else
            {
    
    
                ResultData rd = new ResultData() {
    
     code = "1", data = retunData, msg = (data is string) ? data.ToString() : "" };
                return base.Json(rd);
            }
        }

         
    }

}

3、API服务

  • API实现,创建一个MynatureController类
using Microsoft.AspNetCore.Mvc;
using System.Collections.Generic;
using System.Data;

namespace yourspace
{
    
    
    public class MynatureController : BaseController
    {
    
    
        /// <summary>
        /// 评测试卷
        /// </summary>
        /// <param name="mcode"></param>
        /// <returns></returns>
        [HttpGet("getPaper")]
        public JsonResult GetPaper(string mcode)
        {
    
    
            //直接上SQL
            string sql = @"SELECT
				id,
				`code`,
				title,
				sub_title,
				sub_cn,
				sub_en,
				direction,
				isfree,
				money,
				thkMsg,
				testminute,
				autosubmit
			FROM
				t_test_m
			WHERE
				`code` = @mcode
			AND now() BETWEEN startTime
			AND endTime";
            DataTable dt = Common.GetDataTable(sql, new Dictionary<string, object>() {
    
    
               {
    
    
                    "mcode", mcode
               }
           });
            return Json(dt.ToJson());
        }
        /// <summary>
        /// 返回问题列表
        /// </summary>
        /// <param name="mcode">问卷编号</param>
        /// <param name="random">是否随机出题</param>
        /// <param name="qnum">取出问题号</param>
        /// <returns></returns>
        [HttpGet("getQuestions")]
        public JsonResult GetQuestions(string mcode, bool random = true, int qnum = 0)
        {
    
    
            string sql;
            //随机出题
            if (random) 
            {
    
    
                sql = @"SELECT * FROM
	                    (
		                    SELECT
			                    m_code,
			                    q.title,
			                    q.id,
			                    q.qnum,
			                    a.direction,
			                    a.score,
			                    a.`order` AS aorder,
			                    a.id AS aid
		                    FROM
			                    t_test_q q
		                    INNER JOIN t_test_a a ON q.`code` = a.q_code
	                    ) tb
                    WHERE
	                    m_code = @mcode
                    ORDER BY
	                    aorder,
	                    rand() ";
            }
            //顺序取, 当取题号为0时标示全部
            else if (qnum == 0) 
            {
    
    
                sql = @"SELECT
					*
				FROM
					(
						SELECT
							m_code,
							q.title,
							q.id,
							q.qnum,
							a.direction,
							a.score,
							a.`order` AS aorder,
							a.id AS aid
						FROM
							t_test_q q
						INNER JOIN t_test_a a ON q.`code` = a.q_code
					) tb
				WHERE
					m_code = @mcode
				ORDER BY
				qnum,aorder;";
            }
            //指定题号
            else
            {
    
    
                sql = @"SELECT
					*
				FROM
					(
						SELECT
							m_code,
							q.title,
							q.id,
							q.qnum,
							a.direction,
							a.score,
							a.`order` AS aorder,
							a.id AS aid
						FROM
							t_test_q q
						INNER JOIN t_test_a a ON q.`code` = a.q_code
					) tb
				WHERE
					m_code = @mcode and qnum=@qnum
				ORDER BY aorder";
            }     
            DataTable dt = Common.GetDataTable(sql, new Dictionary<string, object>() {
    
    
               {
    
    "mcode", mcode},{
    
    "qnum",qnum}
           });

            #region 组装返回Json格式

            Dictionary<string, object> pairs = new Dictionary<string, object>();
            foreach (DataRow dataRow in dt.Rows)
            {
    
    
                string key = "q" + dataRow["qnum"].ToString();

                if (pairs.ContainsKey(key))
                {
    
    
                    Dictionary<string, object> qdic = pairs[key] as Dictionary<string, object>;
                    List<Dictionary<string, string>> items = qdic["items"] as List<Dictionary<string, string>>;
                    Dictionary<string, string> cdic = new Dictionary<string, string>();
                    cdic.Add("direction", dataRow["direction"].ToString());
                    cdic.Add("score", dataRow["score"].ToString());
                    cdic.Add("order", dataRow["aorder"].ToString());
                    cdic.Add("id", dataRow["aid"].ToString());
                    items.Add(cdic);
                }
                else
                {
    
    
                    List<Dictionary<string, string>> items = new List<Dictionary<string, string>>();
                    Dictionary<string, string> cdic = new Dictionary<string, string>();
                    cdic.Add("direction", dataRow["direction"].ToString());
                    cdic.Add("score", dataRow["score"].ToString());
                    cdic.Add("order", dataRow["aorder"].ToString());
                    cdic.Add("id", dataRow["aid"].ToString());
                    items.Add(cdic);
                    Dictionary<string, object> qdic = new Dictionary<string, object>();
                    qdic.Add("m_code", dataRow["m_code"].ToString());
                    qdic.Add("title", dataRow["title"].ToString());
                    qdic.Add("id", dataRow["id"].ToString());
                    qdic.Add("qnum", dataRow["qnum"].ToString());
                    qdic.Add("items", items);
                    pairs.Add(key, qdic);
                }
            }
            List<object> returList = new List<object>();
            foreach (var item in pairs)
            {
    
    
                returList.Add(item.Value);
            }

            #endregion
            return Json(returList);
        }
    }
}

4、预览截图

在这里插入图片描述

到此数据准备与API接口已基本完成, 后面开始着手小程序开发了

三、前端开发与实现(wx小程序)

1、准备

为了布局灵活,维护简单,在wx小程序中的富文本我们将采用MarkDown格式来呈现。 这里我使用的是wemark, 可以去gitee下载
使用方式:

1、下载并拷贝wemark目录到小程序根目录
2、在页面的配置文件中引用wemark组件
{
    
    
	"usingComponents": {
    
    
		"wemark": "../wemark/wemark"
	}
}
3、WXML中使用:<wemark md="{
    
    {md}}" link highlight type="wemark"></wemark>

2、Coding wx

.json

{
  "usingComponents": {
    "wemark": "../../component/wemark/wemark"
  },
  "navigationBarTitleText": "性格评测系统"
}

.wxml

<!--
  这样页面可以清爽到爆, 把布局交给md
  把评测介绍, 评测, 结果都在这一页展示吧!
-->
<view class="page">
  <view class="mtitle">{
   
   {info.TITLE}}</view>
  <view class="info" wx:if="{
     
     {step==1}}">
    <wemark md="{
     
     {info.DIRECTION}}" link highlight type="wemark"></wemark>
    <view class="testing">
      <button bindtap="onbtntesting" type="primary">请尊重你内心里第一选择…</button>
    </view>
  </view>
  <view class="doing" wx:if="{
     
     {step==2}}">

    <view class="answers">
      <!-- 显示进度条 -->
      <view class="progress">
        <view class="progress-bar" style="width:{ 
        { 
        progress}}%"></view>
      </view>
      <view class="item">
        <view>
          <view class="page-section">
            <view class="page-section-title atitle"> {
   
   {currentIndex}}、{
   
   {currentAnswer.title}}</view>
            <view class="weui-cells weui-cells_after-title">
              <radio-group>
                <label bindtap="onradioChange" data-sel="{
     
     {c}}" class="weui-cell weui-check__label" wx:for-item="c"
                  wx:for="{
     
     {currentAnswer.items}}" wx:key="{
     
     {c.score}}">
                  <view class="weui-cell__hd">
                    <radio disabled="{
     
     {isFinish}}" value="{
     
     {c.score}}" />
                  </view>
                  <view class="weui-cell__bd">{
   
   {c.direction}}</view>
                </label>
              </radio-group>
            </view>
          </view>
        </view>
      </view>
    </view>
    <view class="next">
      <button wx:if="{
     
     {isFinish}}" bindtap="onbtnNext">提交</button>
    </view>
  </view>
  <view class="result" wx:if="{
     
     {step==3}}">
  <!-- 一般选择类测评前面两步可通用, 但报告展示会各不相同故分开展示 -->
    <view wx:if="{
     
     {myNature.mcode=='PSTR'}}">
      你的得分是{
   
   {myNature.my}},人群平均分是{
   
   {myNature.avg}}。 
      <wemark md="{
     
     {myNature.result}}" link highlight type="wemark"></wemark>
    </view>
  </view>
  <view class="footInfo">学子宏图</view>
</view>

.js

Page({
    
    
  /**
   * 页面的初始数据
   */
  data: {
    
    
    info: {
    
    },
    mcode: 'PSTR',  //试卷号
    answerList: [],  //选项列表
    currentAnswer: null,
    currentIndex: 1, // 当前题号
    totalIndex: 1,  //总计题号
    step: 1,  //执行到的步骤,1=info,2=doing,3=result
    progress: 1,
    isFinish: false,
    selList: [],
    myNature: {
    
    }
  },
  /**
   * 生命周期函数--监听页面加载
   */
  onLoad: function (options) {
    
    

    if (options.code != null) {
    
    
      this.setData({
    
    
        mcode: options.code,
      })
    }
    //页面资源加载完成后执行
    app.loginComplete(() => {
    
    
      let that = this;
      //装载试卷基本信息
      app.wxRequest("api/Mynature/getPaper", "GET", {
    
    
        mcode: that.data.mcode
      }, (res) => {
    
    
        that.setData({
    
    
          info: res.data
        })
      })
    })
  },
  //开始评测
  onbtntesting: function () {
    
    
    let that = this;
    //加载问题与回答
    app.wxRequest("api/Mynature/getQuestions", "GET", {
    
    
      mcode: that.data.mcode,
      random: true
    }, (res) => {
    
     
      let current = res.data[0];
      that.setData(
        {
    
    
          answerList:res.data,
          currentAnswer: current,
          totalIndex: res.data.length
        }
      )
    })

    this.setData({
    
    
      step: 2
    })
  }
  ,
  onradioChange: function (e) {
    
    

    if (this.data.isFinish) return;

    //保存上一题选中
    let sel = e.currentTarget.dataset.sel;
    this.data.selList.push({
    
    
      qid: this.data.currentAnswer.qnum,
      score: sel.score
    });
    this.setData({
    
    
      selList: this.data.selList
    })

    if (this.data.currentIndex < this.data.totalIndex) {
    
    
      //特意增加一个加载遮罩,避免用户快速盲点
      wx.showToast({
    
    
        title: "进入下一题…",
        icon: 'loading',
        mask: true,
        duration: 500
      });
      let current = this.data.answerList[this.data.currentIndex];
      this.setData({
    
    
        currentIndex: this.data.currentIndex + 1,
        currentAnswer: current,
        progress: this.data.currentIndex / this.data.totalIndex * 100,
        isFinish: false
      })
    }
    else {
    
    
      console.log('选中得分', this.data.selList)
      this.setData({
    
    
        isFinish: true,
        progress: 100
      })
    }
  },
  onbtnpre: function () {
    
    
    this.setData({
    
    
      step: 1,
      currentIndex: 1,
      isFinish: false,
      currentAnswer: this.data.answerList[0],
      totalIndex: this.data.answerList.length
    })
  },
  onbtnNext: function () {
    
    
    console.log(this.answerList);
    app.wxRequest("api/Mynature/postReport", "POST", {
    
     mcode: this.data.info.CODE, sel: JSON.stringify(this.data.selList), y: 2 }, (r) => {
    
    
      this.setData({
    
    
        myNature: r.data,
        step: 3
      })
    });

  }
}

3、上效果截图

介绍页
在这里插入图片描述
测试中
在这里插入图片描述

结下来就是最的报告部分了, 也是测评的核心,会涉及到各自算法。

四、评测报告

同样的数据库与后台服务逻辑实现多种不同的评测, 只需要调整展示评测报告页。

  <view class="result" wx:if="{
     
     {step==3}}">
   <!-- 测试1:PSTR规则与算法 -->
    <view wx:if="{
     
     {myNature.mcode=='PSTR'}}"> 
      你的得分是{
   
   {myNature.my}},人群平均分是{
   
   {myNature.avg}}。
      <wemark md="{
     
     {myNature.result}}" link highlight type="wemark"></wemark>
    </view>
    
   <!-- 测试2:霍兰德职业兴趣测试 -->
    <view wx:if="{
     
     {myNature.mcode=='HOLLAND'}}">
      <canvas canvas-id="canvas" class="canvas" style="width: 700rpx; height: 600rpx;background:#fff;;" />
      你的职业趋向为:{
   
   {myNature.my}}
      <wemark md="{
     
     {myNature.result}}" link highlight type="wemark"></wemark>
    </view>
  </view>

测试1:PSTR规则与算法

总是–4 经常–3 有时–2 很少–1 从未–0
98(93或以上) 这个分数表你确实正以极度的压力反应在伤害你自己的健康。你需要专业心理治疗师给予一些忠告, 他可以帮助你消减你对於压力器的知觉, 并帮助你改良生活的品质。
87(82-92)这个分数表示你正经历太多的压力, 这正在损害你的健康,并令你的人际关系发生问题。 你的行为会伤己, 也可能会影响其他人。 因此, 对你来说, 学习如何减除自己的压力反应是非常重要的。 你可能必须花许多时间做练习, 学习控制压力, 也可以寻求专业的帮助。
76(71-81)这个分数显示的压力程度中等, 可能正开始对健康不利。 你可以仔细反省自己对压力器如何作出反应, 并学习在压力器出现时, 控制自己的肌肉紧张, 以消除生理激活反应。 好老师对你有帮助,要不然就选用适合的肌肉松弛录音带。
65(60-70)这个分数指出你生活中的兴奋与压力量也许是相当适中的。 偶而会有一段时间压力太多, 但你也许有能力去享受压力, 并且很快回到平静的状态, 因此对你的健康并不会造成威胁。 做一些松弛的练习仍是有益的。
54(49-59)这个分数表示你能控制你自己的压力反应, 你是一个相当放松的人。 也许你对於所遇到的各种压力器, 并没有将它们解释为威胁, 所以你很容易与人相处, 可以毫无惧怕地胜任工作, 也没有失去自信。
43(38-48)这个分数表示你对所遭遇的压力器很不易为所动, 甚至是不当一回事, 好像并没有发生过一样。 这对你的健康不会有甚麼负面的影响, 但你的生活缺乏适度的兴奋, 因此趣味也就有限。
32(27-37)这个分数表示你的生活可能是相当沉闷的, 即使刺激或有趣的事情发生了, 你也很少作反应。 可能你必须参与更多的社会活动或娱乐活动, 以增加你的压力激活反应。
21(16-26)如果你的分数只落在这个范围内, 也许意味你在生活中所经历的压力经验不够, 或是你并没有正确地分析自己。 你最好更主动些, 在工作、 社交、 娱乐等活动上多寻求些刺激。 做松弛练习对没有甚麼用, 但找一些辅导也许会有帮助。

根据以上规则写一个C#的If判断即可。 测评完成, 欢迎拍砖,后续会有《霍兰德职业兴趣测试》、《九型人格》、《 DISC职业个性评估》、《MBTI职业性格评估》等, 有兴趣的我们可以交流

在这里插入图片描述

测试2:霍兰德职业兴趣测试

- 介紹页面

在这里插入图片描述

- 答题页

在这里插入图片描述

- 评测结果展示

在这里插入图片描述
雷达图代码见https://blog.csdn.net/daengwei/article/details/123791337

猜你喜欢

转载自blog.csdn.net/daengwei/article/details/123541109