master
veypi 1 year ago
parent 2563324bb5
commit f841f2599f

@ -29,3 +29,8 @@ build:
syncDB:
@scp -P 19529 oa.db root@alco.host:/root/
buildweb:
@cd oaweb && yarn build
@rm -rf ./oab/dist/*
@mv ./oaweb/dist/spa/* ./oab/dist/

55
oab/Cargo.lock generated

@ -1914,8 +1914,10 @@ dependencies = [
"include_dir",
"jsonwebtoken",
"lazy_static",
"mime_guess",
"proc",
"rand",
"rust-embed",
"sea-orm",
"serde",
"serde-big-array",
@ -2445,6 +2447,40 @@ dependencies = [
"zeroize",
]
[[package]]
name = "rust-embed"
version = "8.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b1e7d90385b59f0a6bf3d3b757f3ca4ece2048265d70db20a2016043d4509a40"
dependencies = [
"rust-embed-impl",
"rust-embed-utils",
"walkdir",
]
[[package]]
name = "rust-embed-impl"
version = "8.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3c3d8c6fd84090ae348e63a84336b112b5c3918b3bf0493a581f7bd8ee623c29"
dependencies = [
"proc-macro2",
"quote",
"rust-embed-utils",
"syn 2.0.37",
"walkdir",
]
[[package]]
name = "rust-embed-utils"
version = "8.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "873feff8cb7bf86fdf0a71bb21c95159f4e4a37dd7a4bd1855a940909b583ada"
dependencies = [
"sha2",
"walkdir",
]
[[package]]
name = "rust_decimal"
version = "1.32.0"
@ -2538,6 +2574,15 @@ version = "1.0.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1ad4cc8da4ef723ed60bced201181d83791ad433213d8c24efffda1eec85d741"
[[package]]
name = "same-file"
version = "1.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "93fc1dc3aaa9bfed95e02e6eadabb4baf7e3078b0bd1b4d7b6b0b68378900502"
dependencies = [
"winapi-util",
]
[[package]]
name = "scopeguard"
version = "1.2.0"
@ -3637,6 +3682,16 @@ version = "0.9.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f"
[[package]]
name = "walkdir"
version = "2.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d71d857dc86794ca4c280d616f7da00d2dbfd8cd788846559a6813e6aa4b54ee"
dependencies = [
"same-file",
"winapi-util",
]
[[package]]
name = "wasi"
version = "0.11.0+wasi-snapshot-preview1"

@ -53,4 +53,6 @@ http = "0.2.9"
http-auth-basic = "0.3.3"
actix-multipart = "0.6.1"
actix-cors = "0.6.4"
rust-embed = "8.0.0"
mime_guess = "2.0.4"

@ -0,0 +1,3 @@
<!DOCTYPE html><html><head><title>OA</title><meta charset=utf-8><meta name=description content=oneauth><meta name=format-detection content="telephone=no"><meta name=msapplication-tap-highlight content=no><meta name=viewport content="user-scalable=no,initial-scale=1,maximum-scale=1,minimum-scale=1,width=device-width"><link rel=icon type=image/ico href="/favicon.ico"> <script type="module" crossorigin src="/assets/index.c16e83bc.js"></script>
<link rel="stylesheet" href="/assets/index.30f45a7e.css">
</head><body><div id=v-msg></div><div id=q-app></div></body></html>

@ -9,9 +9,9 @@ use proc_macro::TokenStream;
use quote::{quote, ToTokens};
use syn::{parse_macro_input, AttributeArgs, ItemFn};
mod access;
mod curd;
mod crud;
use access::AccessWrap;
use curd::CrudWrap;
use crud::CrudWrap;
#[proc_macro_attribute]
pub fn have_access(args: TokenStream, input: TokenStream) -> TokenStream {

@ -6,109 +6,108 @@
//
//
//
use actix_web::{web, Responder};
use proc::crud_update;
use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, TransactionTrait};
use actix_web::{delete, get, patch, post, web, Responder};
use proc::{access_create, access_delete, access_read, crud_update};
use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter};
use serde::{Deserialize, Serialize};
use tracing::info;
use crate::{models::app, AppState, Error, Result};
use crate::{
models::{self},
AppState, Error, Result,
};
#[derive(Debug, Deserialize, Serialize, Clone)]
pub struct UpdateOpt {
pub name: Option<String>,
pub icon: Option<String>,
pub des: Option<String>,
pub join_method: Option<i32>,
pub role_id: Option<String>,
pub redirect: Option<String>,
pub status: Option<i32>,
#[derive(Debug, Deserialize, Serialize)]
pub struct Options {
name: Option<String>,
role_id: Option<String>,
user_id: Option<String>,
rid: Option<String>,
level: Option<bool>,
}
impl UpdateOpt {
// #[crud_update(
// app,
// id = "Id",
// name,
// icon,
// des,
// join_method,
// role_id,
// redirect,
// status
// )]
pub async fn update(
id: web::Path<String>,
stat: web::Data<AppState>,
data: web::Json<UpdateOpt>,
) -> Result<impl Responder> {
let data = data.into_inner();
let id = id.into_inner();
let obj = app::Entity::find_by_id(&id).one(stat.db()).await?;
let mut obj: app::ActiveModel = match obj {
Some(o) => o.into(),
None => return Err(Error::NotFound(id)),
};
if let Some(name) = data.name {
obj.name = sea_orm::Set(name)
};
let obj = obj.update(stat.db()).await?;
Ok(web::Json(obj))
}
#[get("/app/{aid}/access/")]
#[access_read("app")]
pub async fn list(
aid: web::Path<String>,
stat: web::Data<AppState>,
query: web::Query<Options>,
) -> Result<impl Responder> {
let aid = aid.into_inner();
let mut q = models::access::Entity::find().filter(models::access::Column::AppId.eq(aid));
let query = query.into_inner();
if let Some(rid) = query.role_id {
q = q.filter(models::access::Column::RoleId.eq(rid));
};
if let Some(v) = query.name {
q = q.filter(models::access::Column::Name.eq(v));
};
if let Some(v) = query.user_id {
q = q.filter(models::access::Column::UserId.eq(v));
};
let aus = q.all(stat.db()).await?;
Ok(web::Json(aus))
}
pub async fn update(
id: web::Path<String>,
#[derive(Debug, Deserialize, Serialize)]
pub struct CreateOptions {
name: String,
level: i32,
role_id: Option<String>,
user_id: Option<String>,
rid: Option<String>,
}
#[post("/app/{aid}/access/")]
#[access_create("app")]
pub async fn creat(
aid: web::Path<String>,
stat: web::Data<AppState>,
data: web::Json<UpdateOpt>,
query: web::Json<CreateOptions>,
) -> Result<impl Responder> {
let _id = &id.clone();
let _data = data.clone();
let _db = &stat.db().clone();
let f = || async move {
let data = data.into_inner();
let id = id.into_inner();
let obj = app::Entity::find_by_id(&id).one(stat.db()).await?;
let mut obj: app::ActiveModel = match obj {
Some(o) => o.into(),
None => return Err(Error::NotFound(id)),
};
if let Some(name) = data.name {
obj.name = sea_orm::Set(name)
};
let obj = obj.update(stat.db()).await?;
Ok(web::Json(obj))
let aid = aid.into_inner();
let query = query.into_inner();
let obj = models::access::ActiveModel {
app_id: sea_orm::ActiveValue::Set(aid),
name: sea_orm::ActiveValue::Set(query.name),
level: sea_orm::ActiveValue::Set(query.level),
role_id: sea_orm::ActiveValue::Set(query.role_id),
user_id: sea_orm::ActiveValue::Set(query.user_id),
rid: sea_orm::ActiveValue::Set(query.rid),
..Default::default()
};
let res = f().await;
match res {
Err(e) => Err(e),
Ok(res) => {
let obj = crate::models::app::Entity::find_by_id(_id).one(_db).await?;
let mut obj: crate::models::app::ActiveModel = match obj {
Some(o) => o.into(),
None => return Err(Error::NotFound(_id.to_owned())),
};
if let Some(name) = _data.name {
obj.name = sea_orm::Set(name.into())
};
if let Some(icon) = _data.icon {
obj.icon = sea_orm::Set(icon.into())
};
if let Some(des) = _data.des {
obj.des = sea_orm::Set(des.into())
};
if let Some(join_method) = _data.join_method {
obj.join_method = sea_orm::Set(join_method.into())
};
if let Some(role_id) = _data.role_id {
obj.role_id = sea_orm::Set(role_id.into())
};
if let Some(redirect) = _data.redirect {
obj.redirect = sea_orm::Set(redirect.into())
};
if let Some(status) = _data.status {
obj.status = sea_orm::Set(status.into())
};
let obj = obj.update(_db).await?;
Ok(actix_web::web::Json(obj))
}
}
let obj: models::access::Model = obj.insert(stat.db()).await?;
Ok(web::Json(obj))
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct UpdateOpt {
pub level: Option<i32>,
pub rid: Option<String>,
}
#[patch("/app/{aid}/access/{id}")]
#[access_delete("app")]
#[crud_update(access, AppId = "_id", Id = "_id", level, rid)]
pub async fn update(
id: web::Path<(String, String)>,
data: web::Json<UpdateOpt>,
stat: web::Data<AppState>,
) -> Result<impl Responder> {
Ok("")
}
#[delete("/app/{aid}/access/{id}")]
#[access_delete("app")]
pub async fn delete(
params: web::Path<(String, String)>,
stat: web::Data<AppState>,
) -> Result<impl Responder> {
let (aid, rid) = params.into_inner();
let res = models::access::Entity::delete_many()
.filter(models::access::Column::AppId.eq(aid))
.filter(models::access::Column::Id.eq(rid))
.exec(stat.db())
.await?;
info!("{:#?}", res);
Ok("ok")
}

@ -88,6 +88,7 @@ pub struct UpdateOpt {
pub join_method: Option<i32>,
pub role_id: Option<String>,
pub redirect: Option<String>,
pub host: Option<String>,
pub status: Option<i32>,
}
@ -102,6 +103,7 @@ pub struct UpdateOpt {
join_method,
role_id,
redirect,
host,
status
)]
pub async fn update(

@ -21,6 +21,7 @@ pub fn routes(cfg: &mut web::ServiceConfig) {
.service(user::list)
.service(user::register)
.service(user::login)
.service(user::update)
.service(user::delete);
cfg.service(app::get)
.service(app::list)
@ -32,4 +33,19 @@ pub fn routes(cfg: &mut web::ServiceConfig) {
cfg.service(appuser::get)
.service(appuser::add)
.service(appuser::update);
cfg.service(access::list)
.service(access::creat)
.service(access::update)
.service(access::delete);
cfg.service(resource::list)
.service(resource::create)
.service(resource::update)
.service(resource::delete);
cfg.service(role::list)
.service(role::create)
.service(role::update)
.service(role::delete)
.service(role::add)
.service(role::drop);
}

@ -4,3 +4,85 @@
// 2022-07-09 03:09
// Distributed under terms of the Apache license.
//
use actix_web::{delete, get, patch, post, web, Responder};
use proc::{access_create, access_delete, access_read, crud_update};
use sea_orm::{
ActiveModelTrait, ColumnTrait, EntityTrait, LoaderTrait, QueryFilter, TransactionTrait,
};
use serde::{Deserialize, Serialize};
use tracing::info;
use crate::{
models::{self},
AppState, Error, Result,
};
#[get("/app/{aid}/resource/")]
#[access_read("app")]
pub async fn list(aid: web::Path<String>, stat: web::Data<AppState>) -> Result<impl Responder> {
let n = aid.into_inner();
if !n.is_empty() {
let s = models::resource::Entity::find()
.filter(models::resource::Column::AppId.eq(n))
.all(stat.db())
.await?;
Ok(web::Json(s))
} else {
Err(Error::Missing("id".to_string()))
}
}
#[derive(Debug, Deserialize, Serialize)]
pub struct CreateOpt {
name: String,
des: Option<String>,
}
#[post("/app/{aid}/resource/")]
#[access_create("app")]
pub async fn create(
aid: web::Path<String>,
stat: web::Data<AppState>,
data: web::Json<CreateOpt>,
) -> Result<impl Responder> {
let data = data.into_inner();
let obj = models::resource::ActiveModel {
name: sea_orm::ActiveValue::Set(data.name),
des: sea_orm::ActiveValue::Set(data.des),
app_id: sea_orm::ActiveValue::Set(aid.into_inner()),
..Default::default()
};
let obj = obj.insert(stat.db()).await?;
Ok(web::Json(obj))
}
#[delete("/app/{aid}/resource/{rid}")]
#[access_delete("app")]
pub async fn delete(
params: web::Path<(String, String)>,
stat: web::Data<AppState>,
) -> Result<impl Responder> {
let (aid, rid) = params.into_inner();
let res = models::resource::Entity::delete_many()
.filter(models::resource::Column::AppId.eq(aid))
.filter(models::resource::Column::Name.eq(rid))
.exec(stat.db())
.await?;
info!("{:#?}", res);
Ok("ok")
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct UpdateOpt {
pub des: Option<String>,
}
#[patch("/app/{aid}/resource/{rid}")]
#[access_delete("app")]
#[crud_update(resource, AppId = "_id", Name = "_id", des)]
pub async fn update(
id: web::Path<(String, String)>,
data: web::Json<UpdateOpt>,
stat: web::Data<AppState>,
) -> Result<impl Responder> {
Ok("")
}

@ -4,3 +4,141 @@
// 2022-07-09 03:10
// Distributed under terms of the Apache license.
//
//
use actix_web::{delete, get, patch, post, web, Responder};
use proc::{access_create, access_delete, access_read, access_update, crud_update};
use sea_orm::{
ActiveModelTrait, ColumnTrait, ConnectionTrait, EntityTrait, LoaderTrait, QueryFilter,
TransactionTrait,
};
use serde::{Deserialize, Serialize};
use tracing::info;
use crate::{
libs,
models::{self},
AppState, Error, Result,
};
#[get("/app/{aid}/role/")]
#[access_read("app")]
pub async fn list(aid: web::Path<String>, stat: web::Data<AppState>) -> Result<impl Responder> {
let n = aid.into_inner();
if !n.is_empty() {
let s = models::role::Entity::find()
.filter(models::role::Column::AppId.eq(n))
.all(stat.db())
.await?;
Ok(web::Json(s))
} else {
Err(Error::Missing("id".to_string()))
}
}
#[derive(Debug, Deserialize, Serialize)]
pub struct CreateOpt {
name: String,
des: Option<String>,
}
#[post("/app/{aid}/role/")]
#[access_create("app")]
pub async fn create(
aid: web::Path<String>,
stat: web::Data<AppState>,
data: web::Json<CreateOpt>,
) -> Result<impl Responder> {
let data = data.into_inner();
let id = uuid::Uuid::new_v4().to_string().replace("-", "");
let obj = models::role::ActiveModel {
name: sea_orm::ActiveValue::Set(data.name),
id: sea_orm::ActiveValue::Set(id),
des: sea_orm::ActiveValue::Set(data.des),
app_id: sea_orm::ActiveValue::Set(aid.into_inner()),
..Default::default()
};
let obj = obj.insert(stat.db()).await?;
Ok(web::Json(obj))
}
#[delete("/app/{aid}/role/{rid}")]
#[access_delete("app", id = "&id.clone().0")]
pub async fn delete(
id: web::Path<(String, String)>,
stat: web::Data<AppState>,
) -> Result<impl Responder> {
let (aid, rid) = id.into_inner();
let res = models::role::Entity::delete_many()
.filter(models::role::Column::AppId.eq(aid))
.filter(models::role::Column::Id.eq(rid))
.exec(stat.db())
.await?;
info!("{:#?}", res);
Ok("ok")
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct UpdateOpt {
pub des: Option<String>,
}
#[patch("/app/{aid}/role/{rid}")]
#[access_update("app", id = "&id.clone().0")]
#[crud_update(role, AppId = "_id", Id = "_id", des)]
pub async fn update(
id: web::Path<(String, String)>,
data: web::Json<UpdateOpt>,
stat: web::Data<AppState>,
) -> Result<impl Responder> {
Ok("")
}
#[get("/app/{aid}/role/{rid}/user/{uid}")]
#[access_delete("app", id = "&id.clone().0")]
pub async fn add(
id: web::Path<(String, String, String)>,
stat: web::Data<AppState>,
) -> Result<impl Responder> {
let (_, rid, uid) = id.into_inner();
let s = models::user_role::ActiveModel {
user_id: sea_orm::ActiveValue::Set(uid),
role_id: sea_orm::ActiveValue::Set(rid.clone()),
..Default::default()
};
let s = s.insert(stat.db()).await?;
let sql = format!(
"update role set user_count = user_count + 1 where id = '{}'",
rid,
);
stat.db()
.execute(sea_orm::Statement::from_string(
sea_orm::DatabaseBackend::MySql,
sql,
))
.await?;
Ok(web::Json(s))
}
#[delete("/app/{aid}/role/{rid}/user/{uid}")]
#[access_delete("app", id = "&id.clone().0")]
pub async fn drop(
id: web::Path<(String, String, String)>,
stat: web::Data<AppState>,
) -> Result<impl Responder> {
let (_, rid, uid) = id.into_inner();
models::user_role::Entity::delete_many()
.filter(models::user_role::Column::RoleId.eq(rid.clone()))
.filter(models::user_role::Column::UserId.eq(uid))
.exec(stat.db())
.await?;
let sql = format!(
"update role set user_count = user_count - 1 where id = '{}'",
rid,
);
stat.db()
.execute(sea_orm::Statement::from_string(
sea_orm::DatabaseBackend::MySql,
sql,
))
.await?;
Ok("ok")
}

@ -12,10 +12,10 @@ use crate::{
models::{self, app_user, user, AUStatus, UserPlugin},
AppState, Error, Result,
};
use actix_web::{delete, get, head, http, post, web, HttpResponse, Responder};
use actix_web::{delete, get, head, http, patch, post, web, HttpResponse, Responder};
use base64;
use chrono::{DateTime, Local, NaiveDateTime};
use proc::access_read;
use proc::{access_read, access_update, crud_update};
use rand::Rng;
use sea_orm::{ActiveModelTrait, ColumnTrait, EntityTrait, QueryFilter, TransactionTrait};
use serde::{Deserialize, Serialize};
@ -35,10 +35,46 @@ pub async fn get(id: web::Path<String>, stat: web::Data<AppState>) -> Result<imp
}
}
#[derive(Debug, Deserialize, Serialize)]
pub struct ListOptions {
name: Option<String>,
role_id: Option<String>,
app_id: Option<String>,
}
#[get("/user/")]
#[access_read("user")]
pub async fn list(stat: web::Data<AppState>) -> Result<impl Responder> {
let res: Vec<user::Model> = user::Entity::find().all(stat.db()).await?;
pub async fn list(
stat: web::Data<AppState>,
query: web::Query<ListOptions>,
) -> Result<impl Responder> {
let query = query.into_inner();
let res = if let Some(v) = query.name {
user::Entity::find()
.filter(user::Column::Username.contains(v))
.all(stat.db())
.await?
} else if let Some(v) = query.role_id {
models::user_role::Entity::find()
.filter(models::user_role::Column::RoleId.eq(v))
.find_also_related(user::Entity)
.all(stat.db())
.await?
.into_iter()
.filter_map(|(_, u)| return u)
.collect()
} else if let Some(v) = query.app_id {
models::app_user::Entity::find()
.filter(models::app_user::Column::AppId.eq(v))
.find_also_related(user::Entity)
.all(stat.db())
.await?
.into_iter()
.filter_map(|(_, u)| return u)
.collect()
} else {
user::Entity::find().all(stat.db()).await?
};
Ok(web::Json(res))
}
@ -175,6 +211,26 @@ pub async fn register(
Ok(web::Json(u))
}
#[derive(Debug, Clone, Deserialize, Serialize)]
pub struct UpdateOpt {
pub username: Option<String>,
pub icon: Option<String>,
pub nickname: Option<String>,
pub email: Option<String>,
pub phone: Option<String>,
}
#[patch("/user/{id}")]
#[access_update("user")]
#[crud_update(user, Id = "_id", username, icon, nickname, email, phone)]
pub async fn update(
id: web::Path<String>,
stat: web::Data<AppState>,
data: web::Json<UpdateOpt>,
) -> Result<impl Responder> {
Ok("")
}
#[delete("/user/")]
pub async fn delete() -> impl Responder {
""

@ -1,69 +0,0 @@
//
// userapp.rs
// Copyright (C) 2023 veypi <i@veypi.com>
// 2023-10-09 03:12
// Distributed under terms of the MIT license.
//
use actix_web::{get, post, web, Responder};
use proc::access_read;
use sea_orm::{ColumnTrait, EntityTrait, QueryFilter, TransactionTrait};
use crate::{
libs,
models::{app, app_user},
AppState, Error, Result,
};
// 获取 app
#[get("/user/{uid}/user/{aid}")]
#[access_read("app")]
pub async fn get(
params: web::Path<(String, String)>,
stat: web::Data<AppState>,
) -> Result<impl Responder> {
let (mut aid, mut uid) = params.into_inner();
let mut q = app_user::Entity::find();
if uid == "-" {
uid = "".to_string();
} else {
q = q.filter(app_user::Column::UserId.eq(uid.clone()));
}
if aid == "-" {
aid = "".to_string();
} else {
q = q.filter(app_user::Column::AppId.eq(aid.clone()));
}
if uid.is_empty() && aid.is_empty() {
Err(Error::Missing("uid or aid".to_string()))
} else {
let s: Vec<(app_user::Model, Option<app::Model>)> =
q.find_also_related(app::Entity).all(stat.db()).await?;
let res: Vec<app::Model> = s
.into_iter()
.filter_map(|(l, a)| match a {
Some(a) => Some(app::Model {
status: l.status,
..a
}),
None => None,
})
.collect();
// let s: Vec<app_user::Model> = q.all(stat.db()).await?;
Ok(web::Json(res))
}
}
// 关联app
#[post("/user/{uid}/app/{aid}")]
#[access_read("app")]
pub async fn add(
params: web::Path<(String, String)>,
stat: web::Data<AppState>,
) -> Result<impl Responder> {
let (aid, uid) = params.into_inner();
let db = stat.db().begin().await?;
let res = libs::user::connect_to_app(uid, aid, &db, None).await?;
db.commit().await?;
Ok(web::Json(res))
}

@ -60,14 +60,23 @@ pub async fn after_connected_to_app(
obj: app::Model,
db: &DatabaseTransaction,
) -> Result<()> {
if obj.role_id.is_some() {
if let Some(role_id) = obj.role_id {
user_role::ActiveModel {
user_id: sea_orm::ActiveValue::Set(uid.clone()),
role_id: sea_orm::ActiveValue::Set(obj.role_id.unwrap().clone()),
role_id: sea_orm::ActiveValue::Set(role_id.clone()),
..Default::default()
}
.insert(db)
.await?;
let sql = format!(
"update role set user_count = user_count + 1 where id = '{}'",
role_id,
);
db.execute(sea_orm::Statement::from_string(
sea_orm::DatabaseBackend::MySql,
sql,
))
.await?;
};
let sql = format!(
"update app set user_count = user_count + 1 where id = '{}'",

@ -11,9 +11,11 @@ use actix_web::{
http::StatusCode,
middleware::{self, ErrorHandlerResponse, ErrorHandlers},
web::{self},
App, HttpServer,
App, HttpResponse, HttpServer, Responder,
};
use futures_util::future::FutureExt;
use mime_guess::from_path;
use rust_embed::RustEmbed;
use http::{HeaderName, HeaderValue};
use oab::{api, init_log, libs, models, AppState, Clis, Result, CLI};
@ -100,6 +102,7 @@ async fn web(data: AppState) -> Result<()> {
.app_data(web::Data::new(dav.clone()))
.service(web::resource("/{tail:.*}").to(libs::fs::dav_handler)),
)
.service(index)
});
info!("listen to {}", url);
serv.bind(url)?.run().await?;
@ -113,3 +116,21 @@ fn add_error_header<B>(
Ok(ErrorHandlerResponse::Response(res.map_into_left_body()))
}
#[derive(RustEmbed)]
#[folder = "dist/"]
struct Asset;
#[actix_web::get("/{_:.*}")]
async fn index(p: web::Path<String>) -> impl Responder {
info!("{}", p);
let p = &p.into_inner();
match Asset::get(p) {
Some(content) => HttpResponse::Ok()
.content_type(from_path(p).first_or_octet_stream().as_ref())
.body(content.data.into_owned()),
None => HttpResponse::Ok()
.content_type(from_path("index.html").first_or_octet_stream().as_ref())
.body(Asset::get("index.html").unwrap().data.into_owned()),
}
}

@ -15,7 +15,7 @@ use serde::{Deserialize, Serialize};
use tracing::info;
pub use app_plugin::{AUStatus, AppJoin};
pub use entity::{access, app, app_user, role, user, user_role};
pub use entity::{access, app, app_user, resource, role, user, user_role};
pub use user_plugin::{rand_str, AccessCore, AccessLevel, Token, UserPlugin};
use crate::AppState;

@ -1,8 +1,6 @@
[[toc]]
# 例子
# Markdown { 简明手册 | jiǎn míng shǒu cè }
[[toc]]
# 基本语法
---

@ -76,7 +76,7 @@ module.exports = configure(function(/* ctx */) {
// rebuildCache: true, // rebuilds Vite/linter/etc cache on startup
// publicPath: '/',
publicPath: '/',
// analyze: true,
// env: {},
// rawDefine: {}

@ -0,0 +1,30 @@
/*
* access.ts
* Copyright (C) 2023 veypi <i@veypi.com>
* 2023-10-12 19:32
* Distributed under terms of the MIT license.
*/
import ajax from './axios'
export default (app_id: string) => {
return {
local: `./app/${app_id}/access/`,
create(name: string, props?: { name?: string, level?: number, role_id?: string, user_id?: string, rid?: string }) {
return ajax.post(this.local, Object.assign({ name, level: 0 }, props))
},
get(id: string) {
return ajax.get(this.local + id)
},
list(props?: { name?: string, role_id?: string, user_id?: string }) {
return ajax.get(this.local, props)
},
update(uuid: string, props: any) {
return ajax.patch(this.local + uuid, props)
},
del(id: string) {
return ajax.delete(this.local + id)
},
}
}

@ -6,16 +6,21 @@
*/
import app from "./app";
import role from "./role";
import token from "./token";
import user from "./user";
import resource from "./resource";
import access from './access';
const api = {
user: user,
app: app,
token: token
user,
app,
token,
role,
resource,
access,
}

@ -0,0 +1,30 @@
/*
* resource.ts
* Copyright (C) 2023 veypi <i@veypi.com>
* 2023-10-12 18:03
* Distributed under terms of the MIT license.
*/
import ajax from './axios'
export default (app_id: string) => {
return {
local: `./app/${app_id}/resource/`,
create(name: string, props?: { des?: string }) {
return ajax.post(this.local, Object.assign({ name }, props))
},
get(id: string) {
return ajax.get(this.local + id)
},
list() {
return ajax.get(this.local)
},
update(uuid: string, props: any) {
return ajax.patch(this.local + uuid, props)
},
del(id: string) {
return ajax.delete(this.local + id)
},
}
}

@ -0,0 +1,36 @@
/*
* role.ts
* Copyright (C) 2023 veypi <i@veypi.com>
* 2023-10-12 15:40
* Distributed under terms of the MIT license.
*/
import ajax from './axios'
export default (app_id: string) => {
return {
local: `./app/${app_id}/role/`,
create(name: string, props?: { des?: string }) {
return ajax.post(this.local, Object.assign({ name }, props))
},
get(id: string) {
return ajax.get(this.local + id)
},
list() {
return ajax.get(this.local)
},
update(uuid: string, props: any) {
return ajax.patch(this.local + uuid, props)
},
del(id: string) {
return ajax.delete(this.local + id)
},
add(id: string, uid: string) {
return ajax.get(this.local + `${id}/user/${uid}`)
},
drop(id: string, uid: string) {
return ajax.delete(this.local + `${id}/user/${uid}`)
}
}
}

@ -31,10 +31,10 @@ export default {
get(id: number) {
return ajax.get(this.local + id)
},
list() {
return ajax.get(this.local)
list(props?: { name?: string, role_id?: string, app_id?: string }) {
return ajax.get(this.local, props)
},
update(id: number, props: any) {
update(id: string, props: any) {
return ajax.patch(this.local + id, props)
},
}

@ -9,7 +9,8 @@
<div class="v-crud" :vertical='modeV'>
<div class="v-crud-keys">
<template v-for="k of keys" :key="k.name">
<div class="v-crud-cell" :style="Object.assign({}, cstyle[k.name], cstyle.k)">
<div class="v-crud-cell" :style="Object.assign({},
cstyle.width[k.name], cstyle.k, cstyle.kv[k.name])">
{{ k.label || k.name }}
</div>
</template>
@ -20,17 +21,19 @@
<div class="v-crud-cell" :changed="item.__changed[k.name]" :selected="selected
=== `${item.__idx}.${k.name}`" @click="ifselect ?
selected = `${item.__idx}.${k.name}` : ''" :style="Object.assign({},
cstyle[k.name], cstyle.v)">
cstyle.width[k.name], cstyle.v, cstyle.kv[k.name])">
<slot :name="`k_${k.name}`" :row="item" :value="item[k.name]" :set="setv(item, k.name)">
<template v-if="k.editable === undefined ? editable :
k.editable">
<vinput :model-value="item[k.name] === undefined ?
<vinput :align="valign" :model-value="item[k.name] === undefined ?
k.default : item[k.name]" :type="k.typ" :options="k.options" @update:model-value="setv(item,
k.name)($event)"></vinput>
</template>
<template v-else>
{{ item[k.name] === undefined ? k.default :
item[k.name] }}
<span class="truncate">
{{ item[k.name] === undefined ? k.default :
item[k.name] }}
</span>
</template>
</slot>
</div>
@ -42,7 +45,7 @@
</template>
<script lang="ts" setup>
import { computed, getCurrentInstance, onMounted, reactive, ref, watch } from 'vue';
import { computed, onMounted, ref, watch } from 'vue';
import vinput from 'src/components/vinput'
import { ArgType, Dict } from 'src/models';
@ -54,6 +57,7 @@ interface keyProp {
typ?: ArgType,
editable?: boolean,
options?: any,
style?: { [k: string]: string },
}
let emits = defineEmits<{
(e: 'update', v: any): void
@ -77,6 +81,7 @@ let props = withDefaults(defineProps<{
vertical: false,
data: [] as any,
hover: false,
kvstyle: {} as any,
kstyle: {} as any,
vstyle: {} as any,
cstyle: {} as any,
@ -88,9 +93,13 @@ const modeV = ref(props.vertical)
watch(computed(() => props.vertical), (v) => modeV.value = v)
let items = ref<any[]>([])
// watch(computed(() => props.data), (v) => {
// syncItems()
// })
watch(computed(() => JSON.stringify(props.data)), (_) => {
syncItems()
// if (JSON.stringify(v) !== JSON.stringify(o)) {
// console.log(JSON.stringify(v))
// syncItems()
// }
}, {})
const syncItems = () => {
let res = props.data?.map((v: any, i: any) => {
return Object.assign({ __idx: i, __changed: {} }, v)
@ -105,21 +114,24 @@ const selected = ref()
let alignDic = { 'center': 'center', 'left': 'start', 'right': 'end' }
const cstyle = computed(() => {
let res = { line: [] } as any
let res = { line: [], width: {}, kv: {} } as any
let l = props.keys?.length || 0
let w = 100
let style = modeV.value ? 'flex-basis' : 'height'
props.keys?.forEach((k, i) => {
if (k.width && k.width > 0 && k.width < 100) {
res[k.name] = { [style]: (k.width || 1) + '%' }
res.width[k.name] = { [style]: (k.width || 1) + '%' }
w = w - k.width
l = l - 1
}
if (k.style) {
res.kv[k.name] = k.style
}
})
props.keys?.forEach((k, i) => {
if (k.width && k.width > 0 && k.width < 100) {
} else {
res[k.name] = { [style]: w / l + '%' }
res.width[k.name] = { [style]: w / l + '%' }
}
})
res.k = Object.assign({ 'justify-content': alignDic[props.kalign] }, props.cstyle, props.kstyle)
@ -185,6 +197,7 @@ defineExpose({
display: flex;
justify-content: center;
align-items: center;
gap: 1rem;
overflow: auto;
overflow-y: auto;

@ -15,7 +15,6 @@
import Cherry from 'cherry-markdown';
import options from './options'
import { computed, onMounted, ref, watch } from 'vue';
import { CherryOptions } from 'cherry-markdown/types/cherry';
import oafs from 'src/libs/oafs';
let editor = {} as Cherry;
@ -24,7 +23,7 @@ let emits = defineEmits<{
(e: 'update:modelValue', v: boolean): void
}>()
let props = withDefaults(defineProps<{
modelValue: boolean,
modelValue?: boolean,
eid?: string,
content?: string,
static_dir?: string,
@ -32,6 +31,7 @@ let props = withDefaults(defineProps<{
{
eid: 'v-editor',
content: '',
modelValue: true,
}
)

@ -51,7 +51,7 @@
<template v-else-if="type === ArgType.Select">
<div class="noborder cursor-pointer w-full overflow-x-auto whitespace-nowrap" @click="showSelect" :title="title">
<span v-if="!value"></span>
<span v-if="value === undefined || value === null"></span>
<span v-else-if="!Array.isArray(value)">{{ transDic[value] || value }}</span>
<template v-else>
<span class="mx-2" v-for=" iv in value " :key="iv">{{ transDic[iv] || iv }}</span>
@ -61,10 +61,9 @@
:style="{ left: selectPos[0] + 'px', top: selectPos[1] + 'px', height: showSelectOpt ? '20rem' : '0rem' }"
class="select-opt text-base text-white rounded-md overflow-y-auto" style="min-width: 10rem;" :title="title">
<div class="m-2 p-2" v-if="!options"></div>
<div :class="[ok === value ? 'bg-gray-500' : 'bg-gray-800']"
class="cursor-pointer m-2 p-2 rounded-md hover:bg-gray-500" @click="setSelect(ok)"
v-for="( ov, ok ) in transDic " :key="ok">
{{ ov }}
<div :class="[ov.key === value ? 'bg-gray-500' : 'bg-gray-800']" class="cursor-pointer m-2 p-2 rounded-md
hover:bg-gray-500" @click="setSelect(ov.key)" v-for="ov of options" :key="ov.key">
{{ ov.name }}
</div>
<div class="w-full h-32"></div>
</div>

@ -9,7 +9,17 @@ function padLeftZero(str: string): string {
const util = {
datetostr(d: string) {
return new Date(d + 'z').toLocaleString()
let r = new Date(d + 'z')
let delta = (new Date().getTime() - r.getTime()) / 1000
if (delta < 0) {
} else if (delta < 60) {
return Math.floor(delta) + '秒前'
} else if (delta < 3600) {
return Math.floor(delta / 60) + '分钟前'
} else if (delta < 86400) {
return Math.floor(delta / 3600) + '小时前'
}
return r.toLocaleString()
},
randomNum(minNum: number, maxNum: number) {
return Math.floor(Math.random() * maxNum) + minNum

@ -27,39 +27,48 @@ export const R = {
Auth: 'auth',
}
const level = {
None: 0,
Do: 1,
Part: 1,
Read: 2,
Create: 3,
Update: 4,
Delete: 5,
All: 6
export enum AccessLevel {
None = 0,
Do = 1,
Read = 1,
Create = 2,
Update = 3,
Delete = 4,
All = 5
}
export const LevelOptions = [
{ key: 0, name: '无权限' },
{ key: 1, name: '读取数据权限' },
{ key: 2, name: '创建数据权限' },
{ key: 3, name: '更新数据权限' },
{ key: 4, name: '删除数据权限' },
{ key: 5, name: '管理员权限' },
]
class authLevel {
level = level.None
level = AccessLevel.None
constructor(level: number) {
this.level = level
}
CanDo(): boolean {
return this.level >= level.Do
return this.level >= AccessLevel.Do
}
CanRead(): boolean {
return this.level >= level.Read
return this.level >= AccessLevel.Read
}
CanCreate(): boolean {
return this.level >= level.Create
return this.level >= AccessLevel.Create
}
CanUpdate(): boolean {
return this.level >= level.Update
return this.level >= AccessLevel.Update
}
CanDelete(): boolean {
return this.level >= level.Delete
return this.level >= AccessLevel.Delete
}
CanDoAny(): boolean {
return this.level >= level.All
return this.level >= AccessLevel.All
}
}
@ -71,7 +80,7 @@ export class auths {
}
Get(name: string, rid: string): authLevel {
let l = level.None
let l = AccessLevel.None
for (let i of this.list) {
if (i.name == name && (!i.rid || i.rid === rid) && i.level > l) {
l = i.level

@ -6,8 +6,9 @@
*/
import { RouteLocationRaw } from 'vue-router';
import { AccessLevel } from './auth';
export { type Auths, type modelsSimpleAuth, NewAuths, R } from './auth'
export { type Auths, type modelsSimpleAuth, NewAuths, R, AccessLevel, LevelOptions } from './auth'
export type Dict = { [key: string]: any }
@ -88,27 +89,6 @@ export interface modelsApp {
user_count: number
au: modelsAppUser
// Creator: number
// Des: string
// EnableEmail: boolean
// EnablePhone: boolean
// EnableRegister: true
// EnableUser: boolean
// EnableUserKey: boolean
// EnableWx: boolean
// Hide: boolean
// Host: string
// Icon: string
// InitRole?: null
// InitRoleID: number
// Name: string
// UUID: string
// UserCount: number
// UserKeyUrl: string
// UserRefreshUrl: string
// UserStatus: string
// Users: null
}
export enum AUStatus {
@ -130,7 +110,6 @@ export interface modelsUser {
id: string
created: string
updated: string
delete_flag: boolean
username: string
nickname: string
email: string
@ -140,63 +119,34 @@ export interface modelsUser {
used: number
space: number
au: AUStatus
// Index 前端缓存
// Index?: number
// Apps: modelsApp[]
// Auths: null
// CreatedAt: string
// DeletedAt: null
// ID: number
// Icon: string
// Position: string
// Roles: null
// Status: string
// UpdatedAt: string
// Username: string
// Email: string
// Nickname: string
// Phone: string
}
export interface modelsAuth {
App?: modelsApp
AppUUID: string
CreatedAt: string
DeletedAt: null
ID: number
Level: number
RID: string
RUID: string
Resource?: modelsResource
ResourceID: number
Role?: modelsRole
RoleID: number
UpdatedAt: string
User?: modelsUser
UserID?: number
export interface modelsAccess {
id: number
created: string
updated: string
name: string
app_id: string,
role_id?: string,
user_id?: string,
level: AccessLevel,
rid?: string
}
export interface modelsRole {
App?: modelsApp
AppUUID: string
Auths: null
CreatedAt: string
DeletedAt: null
ID: number
Name: string
Tag: string
UpdatedAt: string
UserCount: number
created: string
updated: string
app_id: string
id: string
name: string
des: string
user_count: number
}
export interface modelsResource {
App?: modelsApp
AppUUID: string
CreatedAt: string
DeletedAt: null
Des: string
ID: number
Name: string
UpdatedAt: string
created: string
updated: string
app_id: string
name: string
des: string
}

@ -21,8 +21,7 @@ onMounted(() => {
timer.value = setInterval(() => {
count.value--
if (count.value === 0) {
router.back()
// router.push('/')
router.push({ name: 'home' })
clearInterval(timer.value)
}
}, 1000)

@ -5,10 +5,312 @@
* Distributed under terms of the MIT license.
-->
<template>
<div></div>
<div class="px-10">
<div class="flex justify-between">
<div class="text-3xl">角色管理</div>
<q-btn @click="created(0)"></q-btn>
</div>
<div class="w-full">
<crud vertical :keys="role_keys" :data="role" @update="update(0,
$event)">
<template #k_created="{ value }">{{ util.datetostr(value) }}</template>
<template #k__="{ row }">
<q-btn color="primary" size="sm" @click="show_dialog(0,
row.id)">权限</q-btn>
<q-btn color="secondary" size="sm" @click="show_dialog(1,
row.id)">用户</q-btn>
<q-btn color="negative" size="sm" @click="del(0, row.id)">删除</q-btn>
<template v-if="row.id === app.role_id">
<q-btn color="positive" disable size="sm">初始角色</q-btn>
</template>
<template v-else>
<q-btn size="sm" @click="api.app.update(app.id, {
role_id:
row.id
}).then(_ => app.role_id = row.id)">设置为初始角色</q-btn>
</template>
</template>
</crud>
</div>
<div class="flex mt-16 justify-between">
<div class="text-3xl">资源管理</div>
<q-btn @click="created(1)"></q-btn>
</div>
<div class="w-full">
<crud vertical :keys="resource_keys" :data="resource" @update="update(1,
$event)">
<template #k_created="{ value }">{{ util.datetostr(value) }}</template>
<template #k__="{ row }">
<q-btn color="negative" size="sm" @click="del(1, row.name)">删除</q-btn>
</template>
</crud>
</div>
<q-dialog v-model="dialog">
<q-card v-if="dialog_obj" class="mx-4 mt-4" style="width: 1000px;max-width: 90vw;">
<q-card-section class="row items-center q-pb-none">
<div class="text-h6">{{ dialog_obj.name }}
</div>
<q-space />
<div v-if="dialog_mode">
<q-select filled :model-value="''" @update:model-value="roleuser.add" use-input hide-selected fill-input
input-debounce="0" label="添加用户" :options="users_cache" @filter="filterFn" style="width: 20rem">
<template v-slot:no-option>
<q-item>
<q-item-section class="text-grey">
无结果
</q-item-section>
</q-item>
</template>
</q-select>
</div>
<q-btn v-else @click="created(2, { role_id: dialog_obj.id })">添加权限</q-btn>
</q-card-section>
<q-card-section v-if="dialog_mode">
<crud vertical :keys="users_keys" :data="users">
<template #k__="{ row }">
<q-btn color="negative" size="sm" @click="roleuser.drop(row)"></q-btn>
</template>
</crud>
</q-card-section>
<q-card-section v-else>
<crud vertical :keys="access_keys" :data="access" @update="update(2, $event)">
<template #k__="{ row }">
<q-btn color="negative" size="sm" @click="del(2, row.id)">删除</q-btn>
</template>
</crud>
</q-card-section>
</q-card>
</q-dialog>
</div>
</template>
<script lang="ts" setup>
import msg from '@veypi/msg';
import { useQuasar } from 'quasar';
import api from 'src/boot/api';
import crud from 'src/components/crud.vue';
import { util } from 'src/libs';
import {
ArgType, modelsAccess, modelsApp, modelsRole, modelsUser,
LevelOptions
} from 'src/models';
import { computed, inject, onMounted, Ref, ref, watch } from 'vue';
let $q = useQuasar();
let dialog = ref(false)
let dialog_mode = ref(0)
let dialog_obj = ref<modelsRole>({} as any)
const role_keys = ref<any>([
{
name: 'id',
label: 'ID',
},
{ name: 'name', label: '标识符' },
{ name: 'des', label: '描述', editable: true },
{ name: 'created', label: '创建时间' },
{ name: 'user_count', label: '绑定用户数' },
{
name: '_', label: '操作', style: { 'justify-content': 'start' },
width: 40
},
])
const resource_keys = ref<any>([
{ name: 'name', label: '标识符' },
{ name: 'des', label: '描述', editable: true },
{ name: 'created', label: '创建时间' },
{
name: '_', label: '操作', style: { 'justify-content': 'start' },
width: 40
},
])
const access_keys = ref<any>([
{ name: 'id', label: 'ID' },
{ name: 'name', label: '标识符' },
{ name: 'rid', label: '特定子资源id', editable: true },
{
name: 'level', label: '权限等级', editable: true, typ:
ArgType.Select, options: LevelOptions,
},
{
name: '_', label: '操作', style: { 'justify-content': 'start' },
width: 40
},
])
const users_keys = ref<any>([
{ name: 'id', label: 'ID' },
{ name: 'username', label: '用户名' },
{ name: 'nickname', label: '昵称' },
{
name: '_', label: '操作', style: { 'justify-content': 'start' },
width: 40
},
])
let access = ref<modelsAccess[]>([])
let users = ref<modelsUser[]>([])
let users_cache = ref<any[]>([])
const resource = ref<any[]>([])
const role = ref<any[]>([])
const app = inject('app') as Ref<modelsApp>
watch(computed(() => app.value.id), (v) => {
sync(v)
})
const sync = (id: any) => {
if (!id) {
return
}
api.role(id).list().then(e => {
role.value = e
})
api.resource(id).list().then(e => {
resource.value = e
})
}
const show_dialog = (mode: number, idx: string) => {
dialog_obj.value = role.value.find(e => e.id === idx)
dialog.value = true
dialog_mode.value = mode
if (mode) {
api.user.list({ role_id: idx }).then(e => {
users.value = e
})
} else {
api.access(app.value.id).list({ role_id: idx }).then(e => {
access.value = e
})
}
}
// 0: role 1: resource 2: access 3: users
const crud_option = (mode: number) => {
let res = {
api: api.role(app.value.id),
lable: '角色',
obj: role,
}
if (mode === 1) {
res.api = api.resource(app.value.id) as any
res.lable = '资源'
res.obj = resource
} else if (mode === 2) {
res.api = api.access(app.value.id) as any
res.lable = '权限'
res.obj = access
} else if (mode === 3) {
res.api = api.user as any
res.lable = '用户'
res.obj = users
}
return res
}
const created = (k: number, props?: any) => {
let opt = crud_option(k)
let options;
let prompt;
if (k !== 2) {
prompt = { model: '', type: 'text' }
} else {
console.log(k)
options = {
model: '', type: 'radio', items: resource.value.map(e => {
return {
label: e.name,
value: e.name
}
})
}
}
$q.dialog({
title: '创建' + opt.lable,
message: '请输入标识码',
prompt: prompt as any,
options: options as any,
cancel: true,
persistent: true
}).onOk(data => {
opt.api.create(data, props).then(e => {
msg.Info('创建成功')
opt.obj.value.push(e)
}).catch(e => {
msg.Warn('创建失败: ' + e)
})
})
}
const update = (k: number, props: any[]) => {
let opt = crud_option(k)
console.log(props)
for (let i in props) {
let id = opt.obj.value[i][k === 1 ? 'name' : 'id']
console.log(id)
opt.api.update(id, props[i]).then(() => {
Object.assign(opt.obj.value[i], props[i])
}).catch(e => {
msg.Warn('更新失败: ' + e)
})
}
}
const del = (k: number, id: string) => {
let opt = crud_option(k)
$q.dialog({
title: '是否确定删除',
message: '',
cancel: true,
persistent: true
}).onOk(() => {
opt.api.del(id).then(e => {
msg.Info('删除成功')
opt.obj.value.splice(opt.obj.value.findIndex(e => e.name === id
|| e.id === id), 1)
}).catch(e => {
msg.Warn('删除失败: ' + e)
})
})
}
const roleuser = {
add: (u: any) => {
let idx = users.value.findIndex(e => e.id == u.id)
if (idx >= 0) {
return
}
api.role(app.value.id).add(dialog_obj.value?.id || '', u.id).then(e => {
users.value.push(u)
dialog_obj.value.user_count = dialog_obj.value?.user_count + 1
})
},
drop: (u: any) => {
api.role(app.value.id).drop(dialog_obj.value?.id || '', u.id).then(e => {
users.value.splice(users.value.findIndex(e => e.id === u.id), 1)
dialog_obj.value.user_count = dialog_obj.value?.user_count - 1
})
}
}
const filterFn = (val: string, cb: any) => {
if (val && val.length > 1) {
api.user.list({ name: val }).then((e: modelsUser[]) => {
cb(() => {
users_cache.value = e.map(i => {
return Object.assign({
label: i.username,
value: i.id,
}, i)
})
})
})
}
}
onMounted(() => {
sync(app.value.id)
})
</script>
<style scoped></style>

@ -7,7 +7,7 @@
<template>
<div>
<div class="flex justify-center pt-10">
<CRUD ref="table" v-if="app.id" :keys="keys" :data="[app]" kalign="left" valign="left" editable
<CRUD ref="table" :keys="keys" :data="app.id ? [app] : []" kalign="left" valign="left" editable
:vstyle="{ 'width': '50vw' }" @update="newApp = $event[0]" :kstyle="{ 'width': '10rem' }">
<template #k_icon="{ value, set }">
<div class="w-full flex justify-center">

@ -5,8 +5,8 @@
* Distributed under terms of the MIT license.
-->
<template>
<div>
<q-table title="Treats" :rows="rows" :columns="columns" row-key="name">
<div class="px-10">
<q-table title="用户管理" :rows="rows" :columns="columns" row-key="name">
<template #body-cell-created="props">
<q-td :props="props">
{{ util.datetostr(props.value) }}

@ -7,11 +7,10 @@
<template>
<div class="w-full h-full">
<h1 class="page-h1">文档中心</h1>
<div>
{{ url }}
</div>
<q-inner-loading :showing="!visible" label="Please wait..." label-class="text-teal" label-style="font-size: 1.1em" />
<Editor v-if='doc' eid='doc' preview :content="doc"></Editor>
<div class="w-full px-8">
<Editor v-if='doc' eid='doc' preview :content="doc"></Editor>
</div>
</div>
</template>

@ -7,10 +7,55 @@
<template>
<div>
<h1 class="page-h1">账号设置</h1>
<div class="flex justify-center pt-10">
<crud ref="table" editable :keys="keys" :data="[u.local]" @update="newData = $event[0]">
<template #k_icon="{ value, set }">
<div class="w-full flex justify-center">
<uploader class="" @success="set" dir="user_icon">
<q-avatar>
<img :src="value">
</q-avatar>
</uploader>
</div>
</template>
</crud>
</div>
<div v-if="newData" class="flex justify-center gap-8 mt-6">
<q-btn color="brown-5" label="回退" @click="table.reload" />
<q-btn color="deep-orange" glossy label="保存" @click="save" />
</div>
</div>
</template>
<script lang="ts" setup>
import msg from '@veypi/msg';
import api from 'src/boot/api';
import crud from 'src/components/crud.vue';
import uploader from 'src/components/uploader';
import { useUserStore } from 'src/stores/user';
import { ref } from 'vue';
let u = useUserStore()
let table = ref()
const keys = ref<any>([
{ name: 'id', label: 'ID', editable: false },
{ name: 'username', label: '用户名' },
{ name: 'nickname', label: '昵称' },
{ name: 'icon', label: 'logo' },
{ name: 'email', label: '邮箱' },
{ name: 'phone', label: '手机号' },
])
const newData = ref()
const save = () => {
api.user.update(u.id, newData.value).then(e => {
msg.Info('更新成功')
Object.assign(u.local, newData.value)
newData.value = null
}).catch(e => {
msg.Warn('更新失败 ' + e)
})
}
</script>
<style scoped></style>

@ -35,6 +35,9 @@ export const useUserStore = defineStore('user', {
}
let data = JSON.parse(Base64.decode(token[1]))
if (data.id) {
let l = 'access to'
data.access.map((e: any) => l = l + `\n${e.name}.${e.level}`)
console.log(l)
this.auth = NewAuths(data.access)
api.user.get(data.id).then((e: modelsUser) => {
this.id = e.id

Loading…
Cancel
Save