#![allow(unused_variables)]
use tina::{
indexmap::IndexMap,
serde_json::{Number, Value},
tina::{
core::service::dict::IDictService,
data::{excel::ExcelFileData, AppResult},
excel::{IExcelRow, IExcelSheet},
file::FileData,
server::{
application::{AppConfig, Application, FromApplication},
session::Session,
},
util::excel::ExcelUtil,
},
};
#[macro_use]
extern crate tina;
#[derive(Serialize, Deserialize, Default, PartialEq, Eq, Clone, Debug)]
#[serde(crate = "tina::serde")]
struct RowData {
name: Option<String>,
age: Option<i32>,
status: Option<String>,
up: Option<bool>,
}
impl RowData {
fn new(name: impl Into<String>, age: i32, status: impl Into<String>, up: bool) -> Self {
Self {
name: Some(name.into()),
age: Some(age),
status: Some(status.into()),
up: Some(up),
}
}
}
#[async_trait]
impl IExcelSheet for RowData {
async fn get_sheet_name(&self, session: &Session) -> AppResult<String> {
Ok("测试".to_string())
}
}
#[async_trait]
impl IExcelRow for RowData {
async fn get_field_column_mapping(&self, session: &Session) -> AppResult<IndexMap<String, String>> {
Ok(vec![("name", "姓名"), ("age", "年龄"), ("status", "状态"), ("up", "标志")]
.into_iter()
.map(|(k, v)| (k.to_string(), v.to_string()))
.collect())
}
async fn get_field_dict(&self, session: &Session) -> AppResult<IndexMap<String, String>> {
Ok(vec![("status", "status_dict")].into_iter().map(|(k, v)| (k.to_string(), v.to_string())).collect())
}
async fn get_field_enum_mapping(&self, session: &Session) -> AppResult<IndexMap<String, IndexMap<String, String>>> {
Ok(vec![("up", vec![("true", "是"), ("false", "否")])]
.into_iter()
.map(|(k, v)| {
(k.to_string(), v.into_iter().map(|(k1, v1)| (k1.to_string(), v1.to_string())).collect::<IndexMap<String, String>>())
})
.collect())
}
async fn get_field_datas(&self, session: &Session) -> AppResult<IndexMap<String, Value>> {
Ok(vec![
("name", self.name.as_ref().map(|v| Value::String(v.to_string())).unwrap_or(Value::Null)),
("age", self.age.map(|v| Value::Number(Number::from(v))).unwrap_or(Value::Null)),
("status", self.status.as_ref().map(|v| Value::String(v.to_string())).unwrap_or(Value::Null)),
("up", self.up.map(Value::Bool).unwrap_or(Value::Null)),
]
.into_iter()
.map(|(k, v)| (k.to_string(), v))
.collect())
}
}
struct TestDictService;
#[async_trait]
impl IDictService for TestDictService {
async fn get_dict_data_mapping_by_keys(
&self,
session: &Session,
dict_type: &str,
dict_keys: &[&str],
) -> AppResult<IndexMap<String, String>> {
self.get_dict_data_mapping_by_types(session, vec![dict_type].as_slice())
.await
.map(|v| v.into_iter().next().map(|v1| v1.1).unwrap_or_default())
}
async fn get_dict_data_mapping_by_types(
&self,
session: &Session,
dict_types: &[&str],
) -> AppResult<IndexMap<String, IndexMap<String, String>>> {
Ok(dict_types
.iter()
.map(|&dict_type| match dict_type {
"status_dict" => (
dict_type.to_string(),
vec![("0", "正常"), ("1", "禁用")]
.into_iter()
.map(|(k, v)| (k.to_string(), v.to_string()))
.collect::<IndexMap<String, String>>(),
),
_ => (dict_type.to_string(), IndexMap::default()),
})
.collect())
}
}
#[test]
fn write() -> Result<(), Box<dyn std::error::Error>> {
let path = format!("{}/{}", ".", "test.xlsx");
let mut excel = ExcelFileData::new(path, true);
let handle = excel.create_write_handle()?;
let header_format = excel.generate_header_format(&handle)?;
let bool_format = excel.generate_bool_format(&handle)?;
let number_format = excel.generate_number_format(&handle)?;
let string_format = excel.generate_simple_string_format(&handle)?;
excel.add_header(
&handle,
&"abc".to_string(),
&vec!["姓名", "年龄", "升级"],
Some(&header_format),
Some(&header_format),
Some(&header_format),
)?;
for i in 1..=100 {
excel.add_data(
&handle,
&"abc".to_string(),
&RowData {
name: Some("小生".to_string()),
age: Some(i),
up: Some(i % 3 == 0),
status: Some("1".to_string()),
},
Some(&bool_format),
Some(&number_format),
Some(&string_format),
)?;
}
excel.flush_and_close(handle)?;
Ok(())
}
#[test]
fn export_import() {
let rt = tina::tokio::runtime::Builder::new_multi_thread().enable_all().build().expect("build runtime failed");
rt.block_on(async move {
let mut config = AppConfig::default();
config.set_dict_service(TestDictService);
let application = Application::from(config);
let session = Session::from_application(application, false)?;
{
let data = vec![RowData::new("张三", 20, "0", true), RowData::new("李四", 12, "1", false), RowData::new("王五", 18, "2", true)];
let mut data1 = data.clone();
data1.reverse();
let file = ExcelUtil::write_excel(&session, "", false, move || {
let d = data1.pop();
async move { Ok(d) as AppResult<Option<RowData>> }
})
.await?;
let data2 = ExcelUtil::import::<RowData>(&session, FileData::from_local_file(file.store_path.as_str(), true)?).await?;
assert_ne!(data, data2);
}
{
let data = vec![RowData::new("张三", 20, "0", true), RowData::new("李四", 12, "1", false)];
let mut data1 = data.clone();
data1.reverse();
let file = ExcelUtil::write_excel(&session, "", false, move || {
let d = data1.pop();
async move { Ok(d) as AppResult<Option<RowData>> }
})
.await?;
let data2 = ExcelUtil::import::<RowData>(&session, FileData::from_local_file(file.store_path.as_str(), true)?).await?;
assert_eq!(data, data2);
}
Ok(()) as AppResult<()>
})
.expect("execute failed");
}
#[test]
#[ignore]
#[allow(unreachable_code)]
fn export_import_loop() {
let rt = tina::tokio::runtime::Builder::new_multi_thread().enable_all().build().expect("build runtime failed");
rt.block_on(async move {
let mut config = AppConfig::default();
config.set_dict_service(TestDictService);
let application = Application::from(config);
let session = Session::from_application(application, false)?;
loop {
let data = vec![RowData::new("张三", 20, "0", true); 10000];
let mut data1 = data.clone();
data1.reverse();
let file = ExcelUtil::write_excel(&session, "", false, move || {
let d = data1.pop();
async move { Ok(d) as AppResult<Option<RowData>> }
})
.await?;
let data2 = ExcelUtil::import::<RowData>(&session, FileData::from_local_file(file.store_path.as_str(), true)?).await?;
assert_eq!(data, data2);
}
Ok(()) as AppResult<()>
})
.expect("execute failed");
}