wasm_yew_canvas_checkcode/
lib.rs

1mod core;
2
3#[cfg(debug_assertions)]
4use ::gloo::console;
5use ::gloo::utils;
6use ::rand::{Rng, rngs::OsRng};
7use ::wasm_bindgen::{JsCast, JsValue, UnwrapThrowExt};
8use ::web_sys::{CanvasRenderingContext2d, HtmlCanvasElement, MouseEvent};
9use ::yew::{Callback, Component, Context, html, html::Scope, Html, NodeRef, Properties};
10use core::CanvasOpts;
11
12#[derive(Debug, PartialEq, Properties)]
13pub struct Props {
14    #[prop_or(150.0)]
15    pub width: f64,
16    #[prop_or(50.0)]
17    pub height: f64,
18    #[prop_or(7.0)]
19    pub star_size: f64,
20    #[prop_or(25)]
21    pub star_count: u8,
22    #[prop_or(22.0)]
23    pub font_size: f64,
24    #[prop_or(5)]
25    pub check_code_len: u8,
26    pub on_check_code_change: Callback<CheckCode>,
27    #[prop_or((|_| {}).into())]
28    pub reversed_hook: Callback<Scope<CanvasCheckCode>>,
29}
30pub enum Message {
31    UpdateCheckCode
32}
33pub enum CheckCode {
34    Initialize(String),
35    Update(String)
36}
37pub struct CanvasCheckCode {
38    canvas_ref: NodeRef,
39    unique_id: String,
40}
41macro_rules! draw_canvas {
42    (@core $self: ident, $ctx: ident, $canvas: ident, $custom_canvas: block, $timing: ident) => {
43        let props = $ctx.props();
44        let check_code = gen_random_characters(props.check_code_len); //TODO: 如何将内部生成的检验码,输出到组件外;
45        let $canvas = $self.canvas_ref.cast::<HtmlCanvasElement>().ok_or("未能获取 canvas 元素")?;
46        $custom_canvas
47        let window = utils::window();
48        let canvas_opts = CanvasOpts::with_canvas(&window, &$canvas, props)?;
49        $canvas.set_attribute("width", &format!("{}px", canvas_opts.width)[..])?;
50        $canvas.set_attribute("height", &format!("{}px", canvas_opts.height)[..])?;
51        core::redraw(
52            $canvas.get_context("2d")?.ok_or("浏览器画布不支持 2D 渲染上下文")?.dyn_into::<CanvasRenderingContext2d>()?,
53            canvas_opts,
54            &check_code[..]
55        )?;
56        props.on_check_code_change.emit(CheckCode::$timing(check_code));
57    };
58    ($self: ident, $ctx: ident, $canvas: ident, $custom_canvas: block) => {
59        draw_canvas!(@core $self, $ctx, $canvas, $custom_canvas, Initialize);
60    };
61    ($self: ident, $ctx: ident) => {
62        draw_canvas!(@core $self, $ctx, canvas, {}, Update);
63    };
64}
65impl CanvasCheckCode {
66    fn init_canvas(&self, ctx: &Context<Self>) -> Result<(), JsValue> {
67        let canvas_data_key = format!("data-{}", &self.unique_id[..]);
68        draw_canvas!(self, ctx, canvas, {
69            canvas.set_attribute(&canvas_data_key[..], "")?;
70            canvas.class_list().add_1("wasm-yew-canvas-checkcode")?;
71        });
72        let document = utils::document();
73        let head = document.head().ok_or("未运行于浏览器环境内,没有 head DOM 结点")?;
74        // 添加样式
75        let style_data_key = format!("data-wasm-yew-canvas-checkcode-{}", &self.unique_id[..]);
76        let _style = head.query_selector(&format!("style[{}]", &style_data_key[..])[..])?.ok_or("没有现成的样式dom元素").or_else(|_| {
77            let style = document.create_element("style")?;
78            head.append_child(&style)?;
79            style.set_attribute(&style_data_key[..], "")?;
80            style.set_text_content(Some(&format!(include_str!("./wasm_yew_canvas_checkcode.css"), &canvas_data_key[..])[..]));
81            Ok::<_, JsValue>(style)
82        })?;
83        Ok(())
84    }
85    fn update_canvas(&self, ctx: &Context<Self>) -> Result<(), JsValue> {
86        draw_canvas!(self, ctx);
87        Ok(())
88    }
89}
90impl Component for CanvasCheckCode {
91    type Message = Message;
92    type Properties = Props;
93    fn create(ctx: &Context<Self>) -> Self {
94        let props = ctx.props();
95        let scope = ctx.link();
96        props.reversed_hook.emit(scope.clone());
97        Self {
98            canvas_ref: NodeRef::default(),
99            unique_id: gen_random_characters(16)
100        }
101    }
102    fn update(&mut self, ctx: &Context<Self>, msg: Self::Message) -> bool {
103        match msg {
104            Message::UpdateCheckCode => self.update_canvas(ctx).unwrap_throw()
105        }
106        true
107    }
108    fn view(&self, ctx: &Context<Self>) -> Html {
109        let scope = ctx.link();
110        html! {
111            <canvas ref={self.canvas_ref.clone()} onclick={
112                scope.callback(move |event: MouseEvent| {
113                    event.prevent_default();
114                    event.stop_propagation();
115                    event.stop_immediate_propagation();
116                    #[cfg(debug_assertions)]
117                    console::info!("刷新验证码");
118                    Message::UpdateCheckCode
119                })
120            } />
121        }
122    }
123    fn rendered(&mut self, ctx: &Context<Self>, first_render: bool) {
124        if !first_render {
125            return;
126        }
127        self.init_canvas(ctx).unwrap_throw();
128    }
129}
130fn gen_random_characters(count: u8) -> String {
131    const CHARS: [char; 67] = [
132        '0', '1', '2', '3', '4', '5', '6', '7', '8', '9',
133        'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j',
134        'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't',
135        'u', 'v', 'w', 'x', 'y', 'z', 'A', 'B', 'C', 'D',
136        'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N',
137        'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X',
138        'Y', 'Z', '你', '我', '他', '她', '它'
139    ];
140    let mut characters = "".to_string();
141    for _ in 0..count {
142        characters.push(CHARS[OsRng.gen_range(0..CHARS.len())]);
143    }
144    characters
145}