1use crate::request::{ContentType, Method, Request};
2use crate::tables::Tables;
3use crate::{ApiResponse, Tools};
4use crate::{CONFIG, GLOBAL_DATA, PLUGIN_TOOLS};
5use br_fields::Field;
6use json::{object, JsonValue};
7use std::any::type_name;
8
9pub trait Action {
11 fn _name(&self) -> String {
13 type_name::<Self>()
14 .rsplit("::")
15 .next()
16 .unwrap_or_default()
17 .to_lowercase()
18 }
19 fn module_name(&self) -> String {
21 let t = type_name::<Self>().split("::").collect::<Vec<&str>>();
22 let plugin = t[2].to_lowercase();
23 let module = t[3].to_lowercase();
24 format!("{plugin}.{module}")
25 }
26 fn addon_name(&self) -> String {
28 let t = type_name::<Self>().split("::").collect::<Vec<&str>>();
29 t[2].to_lowercase()
30 }
31 fn api(&self) -> String {
33 let t = type_name::<Self>().split("::").collect::<Vec<&str>>();
34 let plugin = t[2].to_lowercase();
35 let module = t[3].to_lowercase();
36 let action = t[4].to_lowercase();
37 format!("{plugin}.{module}.{action}")
38 }
39 fn token(&self) -> bool {
41 true
42 }
43
44 fn sort(&self) -> usize {
45 99
46 }
47 fn title(&self) -> &'static str;
49 fn description(&self) -> &'static str {
51 ""
52 }
53 fn path(&self) -> &'static str {
55 ""
56 }
57 fn query(&self) -> JsonValue {
59 object! {}
60 }
61 fn tags(&self) -> &'static [&'static str] {
63 &[]
64 }
65 fn icon(&self) -> &'static str {
66 ""
67 }
68 fn public(&self) -> bool {
70 true
71 }
72 fn auth(&self) -> bool {
74 true
75 }
76 fn interface_type(&self) -> InterfaceType {
78 InterfaceType::Api
79 }
80 fn method(&mut self) -> Method {
82 Method::Post
83 }
84 fn content_type(&mut self) -> ContentType {
86 ContentType::Json
87 }
88 fn params_check(&mut self) -> bool {
90 true
91 }
92 fn params(&mut self) -> JsonValue {
94 object! {}
95 }
96 fn success(&mut self) -> ApiResponse {
98 let mut data = object! {};
99 data["code"] = br_fields::int::Int::new(true, "code", "编号", 10, 0)
100 .example(0.into())
101 .swagger();
102 data["message"] = br_fields::str::Str::new(true, "message", "成功消息", 256, "")
103 .example("成功".into())
104 .swagger();
105 data["data"] = br_fields::text::Object::new(true, "data", "返回数据", object! {}).swagger();
106 data["success"] = br_fields::int::Switch::new(true, "success", "成功状态", true)
107 .example(true.into())
108 .swagger();
109 data["timestamp"] =
110 br_fields::datetime::Timestamp::new(true, "timestamp", "时间戳", 0, 0.0).swagger();
111 ApiResponse::success(data, "请求成功")
112 }
113 fn error(&mut self) -> ApiResponse {
115 let mut data = object! {};
116 data["code"] = br_fields::int::Int::new(true, "code", "编号", 10, 1000)
117 .example(1000.into())
118 .swagger();
119 data["message"] = br_fields::str::Str::new(true, "message", "错误消息", 256, "")
120 .example("失败".into())
121 .swagger();
122 data["data"] = br_fields::text::Object::new(true, "data", "返回数据", object! {}).swagger();
123 data["success"] = br_fields::int::Switch::new(true, "success", "成功状态", false)
124 .example(false.into())
125 .swagger();
126 data["timestamp"] =
127 br_fields::datetime::Timestamp::new(true, "timestamp", "时间戳", 0, 0.0).swagger();
128 ApiResponse::error(data, "请求失败")
129 }
130 fn run(&mut self, mut request: Request) -> Result<ApiResponse, ApiResponse> {
132 if self.public()
133 && !self.method().str().is_empty()
134 && self.method().str().to_lowercase() != request.method.str().to_lowercase()
135 {
136 return Err(ApiResponse::fail(
137 -1,
138 format!(
139 "Request type error: Actual [{}] Expected [{}]",
140 request.method.str(),
141 self.method().str()
142 )
143 .as_str(),
144 ));
145 }
146 let params = self.params();
147 if self.params_check() {
148 self.check(&mut request.query, self.query())?;
149 self.check(&mut request.body, params)?;
150 }
151 let res = self.index(request);
152 if res.success {
153 Ok(res)
154 } else {
155 Err(res)
156 }
157 }
158 fn check(&mut self, request: &mut JsonValue, params: JsonValue) -> Result<(), ApiResponse> {
160 let req = request.clone();
161 for (name, _) in req.entries() {
162 if !params.has_key(name) {
163 request.remove(name);
164 }
165 }
166 for (name, field) in params.entries() {
167 let require = field["require"].as_bool().unwrap_or(false);
168 let title = field["title"].as_str().unwrap_or("");
169 if request.has_key(name) {
170 match field["mode"].as_str().unwrap_or("text") {
172 "key" => {
173 if !request[name].is_string() {
174 return Err(ApiResponse::fail(
175 900_001,
176 format!(
177 "请求参数数据类型错误: 参数 [{}] 数据类型应为[{}]",
178 name, field["mode"]
179 )
180 .as_str(),
181 ));
182 }
183 if require && request[name].is_empty() {
184 return Err(ApiResponse::fail(
185 900_014,
186 format!("请求参数数据类型错误: 参数 [{name}] 不能为空").as_str(),
187 ));
188 }
189 }
190 "text" | "table" | "tree" => {
191 let is_multiple = field["multiple"].as_bool().unwrap_or(false);
192 if is_multiple {
193 if !request[name].is_array() {
194 return Err(ApiResponse::fail(
195 900_009,
196 format!(
197 "请求参数数据类型错误: 参数 [{}] 数据类型应为数组",
198 name
199 )
200 .as_str(),
201 ));
202 }
203 if require && request[name].is_empty() {
204 return Err(ApiResponse::fail(
205 900_010,
206 format!("{title} 必填").as_str(),
207 ));
208 }
209 } else {
210 if !request[name].is_string() {
211 return Err(ApiResponse::fail(
212 900_002,
213 format!(
214 "请求参数数据类型错误: 参数 [{}] 数据类型应为[{}]",
215 name, field["mode"]
216 )
217 .as_str(),
218 ));
219 }
220 if require && request[name].is_empty() {
221 return Err(ApiResponse::fail(
222 900_002,
223 format!("{title} 必填").as_str(),
224 ));
225 }
226 }
227 }
228 "file" => {
229 if !request[name].is_array() && !request[name].is_string() {
230 return Err(ApiResponse::fail(
231 900_003,
232 format!("参数 [{}] 数据类型应为[{}]", name, field["mode"]).as_str(),
233 ));
234 }
235 if require && request[name].is_empty() {
236 return Err(ApiResponse::fail(
237 900_002,
238 format!("{title} 必填").as_str(),
239 ));
240 }
241 }
242 "int" => {
243 if require && request[name].to_string().is_empty() {
244 return Err(ApiResponse::fail(
245 900_002,
246 format!("{title} 必填").as_str(),
247 ));
248 }
249 if !request[name].to_string().is_empty() {
250 match request[name].to_string().parse::<i64>() {
251 Ok(_) => {}
252 Err(_) => {
253 return Err(ApiResponse::fail(
254 900_013,
255 format!(
256 "请求参数数据类型错误: 参数 [{}] 数据类型应为[{}]",
257 name, field["mode"]
258 )
259 .as_str(),
260 ));
261 }
262 }
263 }
264 }
265 "timestamp" | "yearmonth" => {
266 if require && request[name].to_string().is_empty() {
267 return Err(ApiResponse::fail(
268 900_002,
269 format!("{title} 必填").as_str(),
270 ));
271 }
272 if !request[name].to_string().is_empty() {
273 if request[name].is_array() && request[name].len() == 2 {
274 for item in request[name].members() {
276 match item.to_string().parse::<f64>() {
277 Ok(_) => {}
278 Err(_) => {
279 return Err(ApiResponse::fail(
280 900_013,
281 format!(
282 "请求参数数据类型错误: 参数 [{}] 数据类型应为[{}]",
283 name, field["mode"]
284 ).as_str(),
285 ));
286 }
287 }
288 }
289 } else {
290 match request[name].to_string().parse::<f64>() {
291 Ok(_) => {}
292 Err(_) => {
293 return Err(ApiResponse::fail(
294 900_013,
295 format!(
296 "请求参数数据类型错误: 参数 [{}] 数据类型应为[{}]",
297 name, field["mode"]
298 )
299 .as_str(),
300 ));
301 }
302 }
303 }
304 }
305 }
306 "float" => {
307 if require && request[name].to_string().is_empty() {
308 return Err(ApiResponse::fail(
309 900_002,
310 format!("{title} 必填").as_str(),
311 ));
312 }
313 if !request[name].to_string().is_empty() {
314 match request[name].to_string().parse::<f64>() {
315 Ok(_) => {}
316 Err(_) => {
317 return Err(ApiResponse::fail(
318 900_023,
319 format!(
320 "请求参数数据类型错误: 参数 [{}] 数据类型应为[{}]",
321 name, field["mode"]
322 )
323 .as_str(),
324 ));
325 }
326 }
327 }
328 }
329 "string" | "url" | "time" | "code" | "pass" | "email" | "location"
330 | "color" | "date" | "barcode" | "datetime" | "editor" | "tel" => {
331 if !request[name].is_string() {
332 return Err(ApiResponse::fail(
333 -1,
334 format!(
335 "请求参数数据类型错误: 参数 [{}] 数据类型应为[{}]",
336 name, field["mode"]
337 )
338 .as_str(),
339 ));
340 }
341 if require && request[name].is_empty() {
342 return Err(ApiResponse::fail(
343 900_004,
344 format!("{title} 必填").as_str(),
345 ));
346 }
347 }
348 "dict" => {
349 if require && request[name].is_empty() {
350 return Err(ApiResponse::fail(
351 900_005,
352 format!("{title} 必填").as_str(),
353 ));
354 }
355 }
356 "switch" => match request[name].to_string().parse::<bool>() {
357 Ok(e) => {
358 request[name] = e.into();
359 }
360 Err(_) => {
361 return Err(ApiResponse::fail(
362 -1,
363 format!(
364 "请求参数数据类型错误: 参数 [{name}] 数据类型应为[{}]",
365 field["mode"]
366 )
367 .as_str(),
368 ));
369 }
370 },
371 "select" => {
372 if !request[name].is_array() && !request[name].is_string() {
373 return Err(ApiResponse::fail(
374 -1,
375 format!(
376 "请求参数数据类型错误: 参数 [{}] 数据类型应为[{}]",
377 name, field["mode"]
378 )
379 .as_str(),
380 ));
381 }
382 let value = if request[name].is_array() {
383 request[name]
384 .members()
385 .map(ToString::to_string)
386 .collect::<Vec<String>>()
387 } else {
388 request[name]
389 .to_string()
390 .split(',')
391 .map(ToString::to_string)
392 .collect::<Vec<String>>()
393 };
394 let option = field["option"]
395 .members()
396 .map(|m| m.as_str().unwrap_or(""))
397 .collect::<Vec<&str>>();
398
399 let require = field["require"].as_bool().unwrap_or(false);
400 for item in value.clone() {
401 if !option.contains(&&*item.clone()) && (item.is_empty() && require) {
402 let option = field["option"]
403 .members()
404 .map(|m| m.as_str().unwrap_or(""))
405 .collect::<Vec<&str>>()
406 .join(",");
407 return Err(ApiResponse::fail(-1, format!("请求参数选项错误: 参数 [{item}] 数据类型应为[{option}]之内").as_str()));
408 }
409 }
410 request[name] = value.into();
411 }
412 "radio" => {
413 if !request[name].is_string() {
414 return Err(ApiResponse::fail(
415 -1,
416 format!(
417 "请求参数数据类型错误: 参数 [{}] 数据类型应为[{}] 实际为[{}]",
418 name, field["mode"], request[name]
419 )
420 .as_str(),
421 ));
422 }
423 let option = field["option"]
424 .members()
425 .map(|m| m.as_str().unwrap_or(""))
426 .collect::<Vec<&str>>()
427 .join(",");
428 if request[name].is_string()
429 && !field["option"].contains(request[name].as_str().unwrap_or(""))
430 {
431 return Err(ApiResponse::fail(
432 -1,
433 format!(
434 "请求参数选项错误: 参数 [{}] 数据 [{}] 应为 [{}] 之一",
435 name, request[name], option
436 )
437 .as_str(),
438 ));
439 }
440 }
441 "array" | "polygon" | "keys" | "table_multiple" => {
442 if !request[name].is_array() {
443 return Err(ApiResponse::fail(
444 900_009,
445 format!(
446 "请求参数数据类型错误: 参数 [{}] 数据类型应为[{}]",
447 name, field["mode"]
448 )
449 .as_str(),
450 ));
451 }
452 if require && request[name].is_empty() {
453 return Err(ApiResponse::fail(
454 900_010,
455 format!("请求参数数据类型错误: 参数 [{name}] 不能为空").as_str(),
456 ));
457 }
458 }
459 "object" => {
460 if !request[name].is_object() {
461 return Err(ApiResponse::fail(
462 900_009,
463 format!(
464 "请求参数数据类型错误: 参数 [{}] 数据类型应为[{}]",
465 name, field["mode"]
466 )
467 .as_str(),
468 ));
469 }
470 if require && request[name].is_empty() {
471 return Err(ApiResponse::fail(
472 900_006,
473 format!("{title} 必填").as_str(),
474 ));
475 }
476 }
477 _ => {
478 log::warn!("检查未知类型: {}", field["mode"]);
479 }
480 }
481 } else {
482 if require {
483 return Err(ApiResponse::fail(900_007, format!("{title} 必填").as_str()));
484 }
485 request[name] = field["def"].clone();
486 }
487 }
488 Ok(())
489 }
490 fn index(&mut self, request: Request) -> ApiResponse;
492 #[cfg(any(
493 feature = "sqlite",
494 feature = "mssql",
495 feature = "mysql",
496 feature = "pgsql"
497 ))]
498 fn table_select(
499 &mut self,
500 request: JsonValue,
501 table_name: &str,
502 fields: Vec<&str>,
503 ) -> JsonValue {
504 self.table()
505 .main_select_fields(table_name, fields)
506 .params(request)
507 .get_table_select()
508 }
509
510 #[allow(clippy::too_many_arguments)]
518 #[cfg(any(
519 feature = "sqlite",
520 feature = "mssql",
521 feature = "mysql",
522 feature = "pgsql"
523 ))]
524 fn table_list(
525 &mut self,
526 request: JsonValue,
527 table_name: &str,
528 fields: JsonValue,
529 hidd_field: Vec<&str>,
530 show_field: Vec<&str>,
531 search_fields: Vec<&str>,
532 filter_fields: Vec<&str>,
533 ) -> JsonValue {
534 self.table()
535 .main_table_fields(table_name, fields, hidd_field, show_field)
536 .search_fields(search_fields)
537 .filter_fields(filter_fields)
538 .params(request)
539 .get_table()
540 }
541 #[allow(clippy::too_many_arguments)]
542 #[cfg(any(
543 feature = "sqlite",
544 feature = "mssql",
545 feature = "mysql",
546 feature = "pgsql"
547 ))]
548 fn table(&mut self) -> Tables {
549 Tables::new(self.tools().db)
550 }
551
552 fn tools(&mut self) -> Tools {
554 PLUGIN_TOOLS.get().expect("tools not initialized").clone()
555 }
556 fn config(&mut self, name: &str) -> JsonValue {
558 if let Ok(cfg) = CONFIG.lock() {
559 cfg.get(name).cloned().unwrap_or_else(|| object! {})
560 } else {
561 object! {}
562 }
563 }
564 fn btn(&mut self) -> Btn {
567 let mut btn = Btn::new(self.api().as_str());
568 btn.fields(self.params());
569 btn.tags(self.tags());
570 btn.icon(self.icon());
571 btn.desc(self.description());
572 btn.auth(self.auth());
573 btn.public(self.public());
574 btn.title(self.title());
575 btn.btn_type(BtnType::Api);
576 btn.btn_color(BtnColor::Primary);
577 btn.path(self.api().replace('.', "/").as_str());
578 btn.pass(false);
579 btn.addon();
580 btn
581 }
582 fn set_global_data(&mut self, key: &str, value: JsonValue) {
584 GLOBAL_DATA.with(|data| {
585 data.borrow_mut()[key] = value;
586 });
587 }
588 fn get_global_data(&mut self) -> JsonValue {
590 GLOBAL_DATA.with(|data| data.borrow().clone())
591 }
592 fn get_global_data_key(&mut self, key: &str) -> JsonValue {
594 GLOBAL_DATA.with(|data| data.borrow()[key].clone())
595 }
596}
597#[derive(Debug, Clone)]
598pub struct Btn {
599 api: String,
600 title: String,
601 desc: String,
602 tags: &'static [&'static str],
603 auth: bool,
604 public: bool,
605 btn_type: BtnType,
606 color: BtnColor,
607 icon: String,
608 cnd: Vec<JsonValue>,
609 url: String,
610 path: String,
611 fields: JsonValue,
612 addon: String,
613 version: usize,
614 pass: bool,
616}
617impl Btn {
618 #[must_use]
619 pub fn new(api: &str) -> Self {
620 Self {
621 api: api.to_string(),
622 title: String::new(),
623 desc: String::new(),
624 btn_type: BtnType::Api,
625 color: BtnColor::Primary,
626 icon: String::new(),
627 auth: false,
628 public: false,
629 cnd: vec![],
630 url: String::new(),
631 path: String::new(),
632 fields: object! {},
633 tags: &[],
634 pass: false,
635 addon: String::new(),
636 version: 0,
637 }
638 }
639
640 pub fn addon(&mut self) -> &mut Self {
641 self.addon = self.api.split('.').next().unwrap_or_default().to_string();
642 self
643 }
644 pub fn path(&mut self, path: &str) -> &mut Self {
645 self.path = path.to_string();
646 self
647 }
648 pub fn cnd(&mut self, cnd: Vec<JsonValue>) -> &mut Self {
649 self.cnd = cnd;
650 self
651 }
652 pub fn version(&mut self, version: usize) -> &mut Self {
653 self.version = version;
654 self
655 }
656 pub fn btn_type(&mut self, btn_type: BtnType) -> &mut Self {
657 self.btn_type = btn_type;
658 self
659 }
660 pub fn btn_color(&mut self, btn_color: BtnColor) -> &mut Self {
661 self.color = btn_color;
662 self
663 }
664 pub fn fields(&mut self, fields: JsonValue) -> &mut Self {
665 self.fields = fields;
666 self
667 }
668 pub fn pass(&mut self, pass: bool) -> &mut Self {
669 self.pass = pass;
670 self
671 }
672 pub fn url(&mut self, url: &str) -> &mut Self {
673 self.url = url.to_string();
674 self
675 }
676 pub fn title(&mut self, title: &str) -> &mut Self {
677 self.title = title.to_string();
678 self
679 }
680 pub fn desc(&mut self, desc: &str) -> &mut Self {
681 self.desc = desc.to_string();
682 self
683 }
684 pub fn tags(&mut self, tags: &'static [&'static str]) -> &mut Self {
685 self.tags = tags;
686 self
687 }
688 pub fn public(&mut self, public: bool) -> &mut Self {
689 self.public = public;
690 self
691 }
692 pub fn auth(&mut self, auth: bool) -> &mut Self {
693 self.auth = auth;
694 self
695 }
696 pub fn icon(&mut self, icon: &str) -> &mut Self {
697 self.icon = icon.to_string();
698 self
699 }
700 pub fn json(&mut self) -> JsonValue {
701 let color = match self.version {
702 0 => self.color.clone().str(),
703 _ => self.color.clone().str_v_1(),
704 };
705 object! {
706 addon:self.addon.to_string() ,
707 api:self.api.clone(),
708 title:self.title.clone(),
709 desc:self.desc.clone(),
710 auth:self.auth,
711 public:self.public,
712 btn_type:self.btn_type.clone().str(),
713 color:color,
714 icon:self.icon.clone(),
715 cnd:self.cnd.clone(),
716 url:self.url.clone(),
717 path:self.path.clone(),
718 fields:self.fields.clone(),
719 tags:self.tags,
720 pass:self.pass,
721 }
722 }
723}
724
725#[derive(Debug, Clone)]
727pub enum InterfaceType {
728 Api,
729 Btn,
730 Menu,
731 OpenApi,
732}
733
734impl InterfaceType {
735 pub fn str(&self) -> &'static str {
736 match self {
737 InterfaceType::Api => "api",
738 InterfaceType::Btn => "btn",
739 InterfaceType::Menu => "menu",
740 InterfaceType::OpenApi => "openapi",
741 }
742 }
743 pub fn types() -> Vec<&'static str> {
744 vec!["api", "btn", "menu", "openapi"]
745 }
746}
747
748#[derive(Debug, Clone)]
750pub enum BtnType {
751 Form,
753 FormDownload,
755 FormCustom,
757 FormData,
758 Url,
760 Api,
762 Download,
764 Path,
766 DialogCustom,
768 FormApiDialogCustom,
770 Preview,
772}
773
774impl BtnType {
775 fn str(self) -> &'static str {
776 match self {
777 BtnType::Form => "form",
778 BtnType::FormDownload => "form_download",
779 BtnType::FormCustom => "form_custom",
780 BtnType::FormData => "form_data",
781 BtnType::Api => "api",
782 BtnType::Download => "download",
783 BtnType::Url => "url",
784 BtnType::Path => "path",
785 BtnType::DialogCustom => "dialog_custom",
786 BtnType::FormApiDialogCustom => "form_api_dialog_custom",
787 BtnType::Preview => "preview",
788 }
789 }
790}
791#[derive(Debug, Clone)]
793pub enum BtnColor {
794 Primary,
795 Red,
796 Yellow,
797 Green,
798 Blue,
799 Orange,
800 Purple,
801 Cyan,
802 Teal,
803 Grey,
804}
805
806impl BtnColor {
807 fn str(self) -> &'static str {
808 match self {
809 BtnColor::Primary => "primary",
810 BtnColor::Red => "negative",
811 BtnColor::Yellow => "warning",
812 BtnColor::Green => "positive",
813 BtnColor::Blue => "blue",
814 BtnColor::Orange => "orange",
815 BtnColor::Purple => "purple",
816 BtnColor::Cyan => "cyan",
817 BtnColor::Teal => "teal",
818 BtnColor::Grey => "grey",
819 }
820 }
821 fn str_v_1(self) -> &'static str {
822 match self {
823 BtnColor::Primary => "normal",
824 BtnColor::Red => "danger",
825 BtnColor::Yellow => "warning",
826 BtnColor::Green => "success",
827 BtnColor::Blue => "info",
828 BtnColor::Orange => "warning",
829 BtnColor::Purple => "normal",
830 BtnColor::Cyan => "info",
831 BtnColor::Teal => "success",
832 BtnColor::Grey => "normal",
833 }
834 }
835}
836
837pub struct Dashboard {
838 title: String,
840 data: JsonValue,
842 model: DashboardModel,
844 class: String,
846 icon: String,
848 desc: String,
850 api: String,
852 options: JsonValue,
854}
855impl Dashboard {
856 pub fn new(title: &str) -> Dashboard {
857 Dashboard {
858 title: title.to_string(),
859 data: JsonValue::Null,
860 model: DashboardModel::Number,
861 class: "col-4".to_string(),
862 icon: "".to_string(),
863 desc: "".to_string(),
864 api: "".to_string(),
865 options: JsonValue::Null,
866 }
867 }
868 pub fn options(&mut self, options: JsonValue) -> &mut Self {
869 self.options = options;
870 self
871 }
872 pub fn api(&mut self, api: &str) -> &mut Self {
873 self.api = api.to_string();
874 self
875 }
876 pub fn data(&mut self, data: JsonValue) -> &mut Self {
877 self.data = data;
878 self
879 }
880 pub fn class(&mut self, name: &str) -> &mut Self {
881 self.class = name.to_string();
882 self
883 }
884 pub fn icon(&mut self, name: &str) -> &mut Self {
885 self.icon = name.to_string();
886 self
887 }
888 pub fn model(&mut self, dashboard_model: DashboardModel) -> &mut Self {
889 self.model = dashboard_model;
890 self
891 }
892 pub fn desc(&mut self, desc: &str) -> &mut Self {
893 self.desc = desc.to_string();
894 self
895 }
896 pub fn json(&self) -> JsonValue {
897 object! {
898 title: self.title.clone(),
899 data: self.data.clone(),
900 class: self.class.clone(),
901 icon: self.icon.clone(),
902 model: self.model.str(),
903 desc: self.desc.clone(),
904 api: self.api.clone(),
905 options:self.options.clone(),
906 }
907 }
908}
909
910pub enum DashboardModel {
911 Number,
913 EchartsBar,
915 EchartsStackedBar,
917 EchartsBarRace,
919 EchartsPie,
921 EchartsDoughnut,
923 EchartsStackedLine,
925 EchartsStackedLineArea,
927 EchartsGeoGraph,
929}
930
931impl DashboardModel {
932 pub fn str(&self) -> &'static str {
933 match self {
934 Self::Number => "number",
935 Self::EchartsBar => "echarts-bar",
936 Self::EchartsStackedBar => "echarts-stacked_bar",
937 Self::EchartsBarRace => "echarts-bar_race",
938 Self::EchartsPie => "echarts-pie",
939 Self::EchartsDoughnut => "echarts-doughnut",
940 Self::EchartsStackedLine => "echarts-stacked_line",
941 Self::EchartsStackedLineArea => "echarts-stacked_line_area",
942 Self::EchartsGeoGraph => "echarts-geo_graph",
943 }
944 }
945}