#P10002. CodeFun2000二次开发教程(二)会员系列(2)专栏页面

CodeFun2000二次开发教程(二)会员系列(2)专栏页面

1.B站视频

2.相关文档

1.Nunjacks中文文档

2.本节课的代码

index.ts

import {
    Context, ForbiddenError, RecordModel, Time , SystemModel , Handler , SettingModel ,
} from 'hydrooj';



//声明全局变量
declare module 'hydrooj' {
    // 在SystemKeys中插入一个键,用来代表vip页面的系统设置的名称
    interface SystemKeys {
        'codefun2000.vip': string;
    }
}


//----------------------Handler层:处理前端响应--------------------------

// VIPPageHandler 处理VIPPage整个路由的请求

// koa 框架
class VIPPageHandler extends Handler {
    noCheckPermView = true;
    async get() {
        // 1.获取md内容
        let raw = SystemModel.get('codefun2000.vip') || '';
        raw = raw.replace(/{{ name }}/g, this.domain.ui?.name || SystemModel.get('server.name')).trim();
        const lines = raw.split('\n');
        // 定义sections
        const sections: { id: string, title: string, content: string }[] = [];
        // 一级标题用于区分板块
        for (const line of lines) {
            // 如果是一级标题
            if (line.startsWith('# ')) {
                // 获取id
                const id = line.split(' ')[1];
                // 新建一个sections
                sections.push({
                    id,
                    title: line.split(id)[1].trim(), 
                    content: '',
                });
            } else sections[sections.length - 1].content += `${line}\n`;
        }
        
        // 给前端传body
        this.response.template = 'vip.html';
        this.response.body = { sections };
    }
}

// vip页面默认内容

// 约定俗成:一级标题用于区分板块 , 并且格式是: id 标题名

const defaultInfo = `
# test1 测试1
这是 VIP 公告。
一级标题用于区分板块。 \`# ID 标题\`

# test2 测试2
支持 Markdown。
`;

var submittedLimit : number = 3;

export async function apply(ctx: Context) {


    // -----------第二节课:显示vip页面--------------

    // 1.注册路由 
        // 参数1:路由名称 《vip》
        // 参数2:路由路径 《/vip》 

        // 一一映射的关系。比如说前端要跳转路由,这么写:url('vip') -> xxxx/vip

        // 参数3:对应Handler类  
        // >是处理整个路由的Handler类 前后端交互,用来直接处理前端的post,get等请求

        // 参数4:访问该路由需要的权限 
        // >例如:PRIV.PRIV_EDIT_SYSTEM 代表编辑系统设置的权限 

    ctx.Route('vip', '/vip', VIPPageHandler);

    // 2.插入导航栏
        // 参数1:要插入的组件的名称 'Nav'
        // 参数2:路由名称:'vip'  => url('vip')
        // 参数3:插入规则
        // 代表放到training_main(路由名称)之前。  view-source:
    ctx.inject('Nav', 'vip', { before: 'training_main' });


    // 3.定义多国语言
    ctx.i18n.load('zh', {
        vip: '会员',
    });
    ctx.i18n.load('en', {
        vip: 'VIP',
    });

    //4.系统设置:会员页面 内容
    ctx.using(['setting'], (c: any) => {
        // c.setting.SystemSetting() : 往系统设置中加 组件
        // SettingModel.Setting() : 初始化一个组件

        c.setting.SystemSetting(
            // 参数1:?
            // 参数2:设置名称
            // 参数3:默认内容
            // 参数4:文本类型 markdown , txt , password , yaml等 , 看源码
            // 参数5:提示文本
            SettingModel.Setting('setting_info', 'codefun2000.vip', defaultInfo, 'markdown', 'vip page'),

        );
    });






    // -----------第一节课:限制提交次数--------------
    ctx.on('handler/before/ProblemSubmit', async (that) => {
        const after = Time.getObjectID(new Date(Date.now() - Time.day));
        const submitted = await RecordModel.count('system', {
            uid: that.user._id,
            input: { $exists: false }, 
            _id: { $gt: after },
        });
        if (submitted >= submittedLimit && that.user.role !== 'vip') {
            throw new ForbiddenError(`非会员每日仅能进行${submittedLimit}次提交。`);
        }
    });
}

vip.html

{% extends "layout/basic.html" %}
{% block content %}
<div class="row" data-sticky-parent>
  <div class="medium-9 columns">

{% set name = handler.domain.ui.name|default(model.system.get('server.name')) %}

{%- for section in sections -%}
<div class="section" data-heading-extract-to="#menu-item-vip">
  <div class="section__header">
    <h1 class="section__title" id="{{ section.id }}" data-heading>{{ section.title }}</h1>
  </div>
  <div class="section__body typo">
    {{ section.content|markdown|safe }}
  </div>
</div>

{%- endfor -%}

  </div>
  <div class="medium-3 columns"><div data-sticky="large">
    <div class="section side">
      <ol class="menu">
        {{ sidemenu.render_item('info', 'vip') }}
      </ol>
    </div>
  </div></div>
</div>
{% endblock %}