wasm_yew_canvas_checkcode/
lib.rs

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
mod core;

#[cfg(debug_assertions)]
use ::gloo::console;
use ::gloo::utils;
use ::rand::{Rng, rngs::OsRng};
use ::wasm_bindgen::{JsCast, JsValue, UnwrapThrowExt};
use ::web_sys::{CanvasRenderingContext2d, HtmlCanvasElement, MouseEvent};
use ::yew::{Callback, Component, Context, html, html::Scope, Html, NodeRef, Properties};
use core::CanvasOpts;

#[derive(Debug, PartialEq, Properties)]
pub struct Props {
    #[prop_or(150.0)]
    pub width: f64,
    #[prop_or(50.0)]
    pub height: f64,
    #[prop_or(7.0)]
    pub star_size: f64,
    #[prop_or(25)]
    pub star_count: u8,
    #[prop_or(22.0)]
    pub font_size: f64,
    #[prop_or(5)]
    pub check_code_len: u8,
    pub on_check_code_change: Callback<CheckCode>,
    #[prop_or((|_| {}).into())]
    pub reversed_hook: Callback<Scope<CanvasCheckCode>>,
}
pub enum Message {
    UpdateCheckCode
}
pub enum CheckCode {
    Initialize(String),
    Update(String)
}
pub struct CanvasCheckCode {
    canvas_ref: NodeRef,
    unique_id: String,
}
macro_rules! draw_canvas {
    (@core $self: ident, $ctx: ident, $canvas: ident, $custom_canvas: block, $timing: ident) => {
        let props = $ctx.props();
        let check_code = gen_random_characters(props.check_code_len); //TODO: 如何将内部生成的检验码,输出到组件外;
        let $canvas = $self.canvas_ref.cast::<HtmlCanvasElement>().ok_or("未能获取 canvas 元素")?;
        $custom_canvas
        let window = utils::window();
        let canvas_opts = CanvasOpts::with_canvas(&window, &$canvas, props)?;
        $canvas.set_attribute("width", &format!("{}px", canvas_opts.width)[..])?;
        $canvas.set_attribute("height", &format!("{}px", canvas_opts.height)[..])?;
        core::redraw(
            $canvas.get_context("2d")?.ok_or("浏览器画布不支持 2D 渲染上下文")?.dyn_into::<CanvasRenderingContext2d>()?,
            canvas_opts,
            &check_code[..]
        )?;
        props.on_check_code_change.emit(CheckCode::$timing(check_code));
    };
    ($self: ident, $ctx: ident, $canvas: ident, $custom_canvas: block) => {
        draw_canvas!(@core $self, $ctx, $canvas, $custom_canvas, Initialize);
    };
    ($self: ident, $ctx: ident) => {
        draw_canvas!(@core $self, $ctx, canvas, {}, Update);
    };
}
impl CanvasCheckCode {
    fn init_canvas(&self, ctx: &Context<Self>) -> Result<(), JsValue> {
        let canvas_data_key = format!("data-{}", &self.unique_id[..]);
        draw_canvas!(self, ctx, canvas, {
            canvas.set_attribute(&canvas_data_key[..], "")?;
            canvas.class_list().add_1("wasm-yew-canvas-checkcode")?;
        });
        let document = utils::document();
        let head = document.head().ok_or("未运行于浏览器环境内,没有 head DOM 结点")?;
        // 添加样式
        let style_data_key = format!("data-wasm-yew-canvas-checkcode-{}", &self.unique_id[..]);
        let _style = head.query_selector(&format!("style[{}]", &style_data_key[..])[..])?.ok_or("没有现成的样式dom元素").or_else(|_| {
            let style = document.create_element("style")?;
            head.append_child(&style)?;
            style.set_attribute(&style_data_key[..], "")?;
            style.set_text_content(Some(&format!(include_str!("./wasm_yew_canvas_checkcode.css"), &canvas_data_key[..])[..]));
            Ok::<_, JsValue>(style)
        })?;
        Ok(())
    }
    fn update_canvas(&self, ctx: &Context<Self>) -> Result<(), JsValue> {
        draw_canvas!(self, ctx);
        Ok(())
    }
}
impl Component for CanvasCheckCode {
    type Message = Message;
    type Properties = Props;
    fn create(ctx: &Context<Self>) -> Self {
        let props = ctx.props();
        let scope = ctx.link();
        props.reversed_hook.emit(scope.clone());
        Self {
            canvas_ref: NodeRef::default(),
            unique_id: gen_random_characters(16)
        }
    }
    fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
        match msg {
            Message::UpdateCheckCode => self.update_canvas(ctx).unwrap_throw()
        }
        true
    }
    fn view(&self, ctx: &Context<Self>) -> Html {
        let scope = ctx.link();
        html! {
            <canvas ref={self.canvas_ref.clone()} onclick={
                scope.callback(move |event: MouseEvent| {
                    event.prevent_default();
                    event.stop_propagation();
                    event.stop_immediate_propagation();
                    #[cfg(debug_assertions)]
                    console::info!("刷新验证码");
                    Message::UpdateCheckCode
                })
            } />
        }
    }
    fn rendered(&mut self, ctx: &Context<Self>, first_render: bool) {
        if !first_render {
            return;
        }
        self.init_canvas(ctx).unwrap_throw();
    }
}
fn gen_random_characters(count: u8) -> String {
    const CHARS: [char; 67] = [
        '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
        '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', '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', '你', '我', '他', '她', '它'
    ];
    let mut characters = "".to_string();
    for _ in 0..count {
        characters.push(CHARS[OsRng.gen_range(0..CHARS.len())]);
    }
    characters
}