master
veypi 2 years ago
parent 21701e405e
commit 34520f36fd

12
oab/Cargo.lock generated

@ -1169,6 +1169,7 @@ dependencies = [
"serde", "serde",
"serde-big-array", "serde-big-array",
"serde_json", "serde_json",
"serde_repr",
"serde_yaml", "serde_yaml",
"sqlx", "sqlx",
"thiserror", "thiserror",
@ -1549,6 +1550,17 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "serde_repr"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2ad84e47328a31223de7fed7a4f5087f2d6ddfe586cf3ca25b7a165bc0a5aed"
dependencies = [
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "serde_urlencoded" name = "serde_urlencoded"
version = "0.7.1" version = "0.7.1"

@ -32,3 +32,4 @@ serde-big-array = "0.4.1"
base64 = "0.13.0" base64 = "0.13.0"
uuid = { version = "1.1", features = ["v3","v4", "fast-rng", "macro-diagnostics"]} uuid = { version = "1.1", features = ["v3","v4", "fast-rng", "macro-diagnostics"]}
serde_repr = "0.1.8"

@ -4,3 +4,38 @@
// 2022-07-09 03:10 // 2022-07-09 03:10
// Distributed under terms of the Apache license. // Distributed under terms of the Apache license.
// //
//
use actix_web::{delete, get, post, web, Responder};
use crate::{models, Error, Result, CONFIG};
#[get("/app/{id}")]
pub async fn get(id: web::Path<String>) -> Result<impl Responder> {
let n = id.into_inner();
if !n.is_empty() {
let s = sqlx::query_as::<_, models::App>("select * from app where id = ?")
.bind(n)
.fetch_one(CONFIG.db())
.await?;
Ok(web::Json(s))
} else {
Err(Error::Missing("id".to_string()))
}
}
#[get("/app/")]
pub async fn list() -> Result<impl Responder> {
let result = sqlx::query_as::<_, models::App>("select * from app")
.fetch_all(CONFIG.db())
.await?;
Ok(web::Json(result))
}
#[post("/app/")]
pub async fn create() -> Result<impl Responder> {
Ok("")
}
#[delete("/app/{id}")]
pub async fn del(id: web::Path<String>) -> Result<impl Responder> {
Ok("")
}

@ -30,5 +30,9 @@ pub fn routes(cfg: &mut web::ServiceConfig) {
.service(user::register) .service(user::register)
.service(user::login) .service(user::login)
.service(user::delete); .service(user::delete);
cfg.service(app::get)
.service(app::list)
.service(app::create)
.service(app::del);
cfg.service(greet); cfg.service(greet);
} }

@ -5,14 +5,13 @@
// Distributed under terms of the Apache license. // Distributed under terms of the Apache license.
// //
use std::fmt::{format, Debug}; use std::fmt::Debug;
use crate::{models, Error, Result, CONFIG}; use crate::{models, Error, Result, CONFIG};
use actix_web::{delete, get, head, http, post, web, HttpResponse, Responder}; use actix_web::{delete, get, head, http, post, web, HttpResponse, Responder};
use base64; use base64;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tracing::info; use tracing::info;
use uuid::uuid;
#[get("/user/{id}")] #[get("/user/{id}")]
pub async fn get(id: web::Path<String>) -> Result<models::User> { pub async fn get(id: web::Path<String>) -> Result<models::User> {

@ -7,8 +7,9 @@
use chrono::NaiveDateTime; use chrono::NaiveDateTime;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use serde_repr::*;
#[derive(Debug, Serialize, Deserialize, Clone, sqlx::Type)] #[derive(Debug, Serialize_repr, Deserialize_repr, Clone, sqlx::Type)]
#[repr(i64)] #[repr(i64)]
pub enum AppJoin { pub enum AppJoin {
Auto = 0, Auto = 0,
@ -57,7 +58,7 @@ impl App {
} }
} }
#[derive(Debug, Deserialize, Serialize, Clone, sqlx::Type)] #[derive(Debug, Deserialize_repr, Serialize_repr, Clone, sqlx::Type)]
#[repr(i64)] #[repr(i64)]
pub enum AUStatus { pub enum AUStatus {
OK = 0, OK = 0,

@ -8,7 +8,8 @@
use chrono::NaiveDateTime; use chrono::NaiveDateTime;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
#[derive(Debug, Default, Serialize, Deserialize)] #[derive(Debug, Default, Serialize, Deserialize, sqlx::Type, sqlx::FromRow)]
#[sqlx(type_name = "role")]
pub struct Role { pub struct Role {
pub id: String, pub id: String,
pub created: Option<NaiveDateTime>, pub created: Option<NaiveDateTime>,

@ -16,14 +16,16 @@
"@veypi/msg": "^0.1.0", "@veypi/msg": "^0.1.0",
"animate.css": "^4.1.1", "animate.css": "^4.1.1",
"axios": "^0.24.0", "axios": "^0.24.0",
"fast-xml-parser": "^3.19.0",
"hot-patcher": "^0.5.0",
"js-base64": "^3.7.2", "js-base64": "^3.7.2",
"layerr": "^0.1.2",
"mitt": "^3.0.0", "mitt": "^3.0.0",
"nested-property": "^4.0.0",
"path-posix": "^1.0.0",
"seamless-scroll-polyfill": "^2.1.5", "seamless-scroll-polyfill": "^2.1.5",
"hot-patcher": "^0.5.0",
"path-posix": "^1.0.0",
"fast-xml-parser": "^3.19.0",
"nested-property": "^4.0.0",
"layerr": "^0.1.2",
"base-64": "^1.0.0",
"md5": "^2.3.0",
"url-join": "^4.0.1", "url-join": "^4.0.1",
"url-parse": "^1.5.3", "url-parse": "^1.5.3",
"vue": "^3.2.16", "vue": "^3.2.16",
@ -35,9 +37,7 @@
"@veypi/one-icon": "2.0.6", "@veypi/one-icon": "2.0.6",
"@vitejs/plugin-vue": "^1.9.3", "@vitejs/plugin-vue": "^1.9.3",
"autoprefixer": "^9.8.8", "autoprefixer": "^9.8.8",
"base-64": "^1.0.0",
"less": "^4.1.2", "less": "^4.1.2",
"md5": "^2.3.0",
"naive-ui": "^2.19.11", "naive-ui": "^2.19.11",
"postcss": "^7.0.39", "postcss": "^7.0.39",
"tailwindcss": "npm:@tailwindcss/postcss7-compat@2.1.0", "tailwindcss": "npm:@tailwindcss/postcss7-compat@2.1.0",

@ -12,9 +12,8 @@
<script setup lang="ts"> <script setup lang="ts">
// This starter template is using Vue 3 <script setup> SFCs // This starter template is using Vue 3 <script setup> SFCs
import BaseFrame from './components/frame.vue' import BaseFrame from './components/frame.vue'
import { onBeforeMount, ref } from 'vue' import { onBeforeMount } from 'vue'
import { useStore } from "./store"; import { useStore } from "./store";
import msg from '@veypi/msg'
let store = useStore() let store = useStore()
@ -25,10 +24,9 @@ onBeforeMount(() => {
} }
store.dispatch('fetchSelf') store.dispatch('fetchSelf')
store.dispatch('user/fetchUserData') store.dispatch('user/fetchUserData')
msg.Warn('asd')
}) })
let collapsed = ref(true) // let collapsed = ref(true)
</script> </script>

@ -2,66 +2,80 @@
<div class="core rounded-2xl p-3"> <div class="core rounded-2xl p-3">
<div class="grid gap-4 grid-cols-5"> <div class="grid gap-4 grid-cols-5">
<div class="col-span-2"> <div class="col-span-2">
<n-avatar style="--color: none;" @click="Go" round :size="80" :src="core.Icon"> <n-avatar
style="--color: none"
@click="Go"
round
:size="80"
:src="core.Icon"
>
</n-avatar> </n-avatar>
</div> </div>
<div class="col-span-3 grid grid-cols-1 items-center text-left"> <div class="col-span-3 grid grid-cols-1 items-center text-left">
<div class="h-10 flex items-center text-2xl italic font-bold">{{ core.Name }}</div> <div class="h-10 flex items-center text-2xl italic font-bold">
{{ core.Name }}
</div>
<span class="truncate">{{ core.Des }}</span> <span class="truncate">{{ core.Des }}</span>
</div> </div>
</div> </div>
</div> </div>
</template> </template>
<script setup lang='ts'> <script setup lang="ts">
import {withDefaults} from 'vue' import { withDefaults } from "vue";
import {useRouter} from 'vue-router' import { useRouter } from "vue-router";
import {useMessage, useLoadingBar} from 'naive-ui' import { useMessage, useLoadingBar } from "naive-ui";
import api from '@/api' import api from "@/api";
import {useStore} from '@/store' import { useStore } from "@/store";
import {modelsApp} from '@/models' import { modelsApp } from "@/models";
import util from '@/libs/util' import util from "@/libs/util";
let router = useRouter() let router = useRouter();
let store = useStore() let store = useStore();
let msg = useMessage() let msg = useMessage();
let bar = useLoadingBar() let bar = useLoadingBar();
let props = withDefaults(defineProps<{ let props = withDefaults(
core?: modelsApp defineProps<{
}>(), { core: modelsApp;
// @ts-ignore }>(),
core: {}, {}
}) );
function Go() { function Go() {
switch (props.core.UserStatus) { switch (props.core.UserStatus) {
case 'ok': case "ok":
router.push({name: 'app.main', params: {uuid: props.core.UUID}}) router.push({ name: "app.main", params: { uuid: props.core.UUID } });
return return;
case 'apply': case "apply":
msg.info('请等待管理员审批进入') msg.info("请等待管理员审批进入");
return return;
case 'deny': case "deny":
msg.warning('进入申请未通过') msg.warning("进入申请未通过");
return return;
case 'disabled': case "disabled":
msg.warning('已被禁止使用') msg.warning("已被禁止使用");
return return;
}
bar.start();
api.app
.user(props.core.UUID)
.add(store.state.user.id)
.Start(
(e) => {
bar.finish();
if (e.Status === "ok") {
router.push({ name: "app.main", params: { uuid: props.core.UUID } });
return;
} }
bar.start() props.core.UserStatus = e.Status;
api.app.user(props.core.UUID).add(store.state.user.id).Start(e => { msg.info("已发起加入申请");
bar.finish() },
if (e.Status === 'ok') { (e) => {
router.push({name: 'app.main', params: {uuid: props.core.UUID}}) msg.warning("加入失败: " + e);
return bar.error();
} }
props.core.UserStatus = e.Status );
msg.info('已发起加入申请') return;
}, (e) => {
msg.warning('加入失败: ' + e)
bar.error()
})
return
} }
</script> </script>
<style scoped> <style scoped>

@ -1,21 +1,42 @@
<template> <template>
<n-config-provider :theme-overrides="Theme.overrides" :locale="zhCN" :date-locale="dateZhCN" <n-config-provider
:theme="Theme"> :theme-overrides="Theme.overrides"
:locale="zhCN"
:date-locale="dateZhCN"
:theme="Theme"
>
<n-message-provider> <n-message-provider>
<n-layout class="font-sans select-none" style="height: 100vh"> <n-layout class="font-sans select-none" style="height: 100vh">
<transition enter-active-class="animate__slideInDown" leave-active-class="animate__slideOutUp"> <transition
<one-icon class="header-down animate__animated" @click="$store.commit('hideHeader', false)" enter-active-class="animate__slideInDown"
v-if="$store.state.hideHeader">down leave-active-class="animate__slideOutUp"
>
<one-icon
class="header-down animate__animated"
@click="$store.commit('hideHeader', false)"
v-if="$store.state.hideHeader"
>down
</one-icon> </one-icon>
</transition> </transition>
<n-layout style="height: calc(100vh - 24px)"> <n-layout style="height: calc(100vh - 24px)">
<transition enter-active-class="animate__slideInDown" leave-active-class="animate__slideOutUp"> <transition
<n-layout-header class="animate__animated" v-if="!$store.state.hideHeader" bordered enter-active-class="animate__slideInDown"
style="height: 64px;line-height: 64px;"> leave-active-class="animate__slideOutUp"
>
<n-layout-header
class="animate__animated"
v-if="!$store.state.hideHeader"
bordered
style="height: 64px; line-height: 64px"
>
<div class="flex h-full"> <div class="flex h-full">
<div class="h-full"> <div class="h-full">
<one-icon color="#000" class="inline-block" @click="$router.push('/')" <one-icon
style="font-size: 48px;margin:8px;color:aqua"> color="#000"
class="inline-block"
@click="$router.push('/')"
style="font-size: 48px; margin: 8px; color: aqua"
>
glassdoor glassdoor
</one-icon> </one-icon>
</div> </div>
@ -25,22 +46,34 @@
</n-h6> </n-h6>
</div> </div>
<div class="flex-grow flex justify-center"> <div class="flex-grow flex justify-center">
<span class="text-2xl" style="line-height: 64px">{{ $store.state.title }}</span> <span class="text-2xl" style="line-height: 64px">{{
$store.state.title
}}</span>
</div> </div>
<div class="h-full px-3"> <div class="h-full px-3">
<fullscreen v-model="isFullScreen" class="header-icon">fullscreen</fullscreen> <fullscreen v-model="isFullScreen" class="header-icon"
>fullscreen</fullscreen
>
<div class="header-icon"> <div class="header-icon">
<one-icon @click="ChangeTheme"> <one-icon @click="ChangeTheme">
{{ IsDark ? 'Daytimemode' : 'nightmode-fill' }} {{ IsDark ? "Daytimemode" : "nightmode-fill" }}
</one-icon> </one-icon>
</div> </div>
<div class="header-icon" @click="$store.commit('hideHeader', true)"> <div
class="header-icon"
@click="$store.commit('hideHeader', true)"
>
<one-icon>up</one-icon> <one-icon>up</one-icon>
</div> </div>
</div> </div>
<div v-if="$store.state.user.ready" <div
class="h-full flex justify-center items-center mr-5"> v-if="$store.state.user.ready"
<OAer @logout="$store.commit('user/logout')" :is-dark="IsDark"></OAer> class="h-full flex justify-center items-center mr-5"
>
<OAer
@logout="$store.commit('user/logout')"
:is-dark="IsDark"
></OAer>
</div> </div>
</div> </div>
</n-layout-header> </n-layout-header>
@ -51,15 +84,24 @@
<slot></slot> <slot></slot>
</n-dialog-provider> </n-dialog-provider>
</n-loading-bar-provider> </n-loading-bar-provider>
<n-back-top> <n-back-top> </n-back-top>
</n-back-top>
</n-layout> </n-layout>
</n-layout> </n-layout>
<n-layout-footer bordered style="height: 24px;line-height: 24px" <n-layout-footer
class="flex justify-around px-3 text-gray-500 text-xs"> bordered
<span class="hover:text-black cursor-pointer" @click="$router.push({name: 'about'})">关于OA</span> style="height: 24px; line-height: 24px"
class="flex justify-around px-3 text-gray-500 text-xs"
>
<span
class="hover:text-black cursor-pointer"
@click="$router.push({ name: 'about' })"
>关于OA</span
>
<span class="hover:text-black cursor-pointer">使用须知</span> <span class="hover:text-black cursor-pointer">使用须知</span>
<span class="hover:text-black cursor-pointer" @click="util.goto('https://veypi.com')"> <span
class="hover:text-black cursor-pointer"
@click="util.goto('https://veypi.com')"
>
©2021 veypi ©2021 veypi
</span> </span>
</n-layout-footer> </n-layout-footer>
@ -69,18 +111,18 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import {Theme, IsDark, ChangeTheme} from '@/theme' import { Theme, IsDark, ChangeTheme } from "@/theme";
import {zhCN, dateZhCN} from 'naive-ui' import { zhCN, dateZhCN } from "naive-ui";
import fullscreen from './fullscreen' import fullscreen from "./fullscreen";
import {ref} from 'vue' import { ref } from "vue";
import {useStore} from '@/store' import { useStore } from "@/store";
import {useRouter} from 'vue-router' import { useRouter } from "vue-router";
import util from '@/libs/util' import util from "@/libs/util";
import {OAer, Cfg} from '@/oaer' import { OAer, Cfg } from "@/oaer";
Cfg.token.value = util.getToken() Cfg.token.value = util.getToken();
let store = useStore() let store = useStore();
let router = useRouter() let router = useRouter();
let isFullScreen = ref(false) let isFullScreen = ref(false);
</script> </script>
<style scoped> <style scoped>

@ -14,31 +14,44 @@ export interface modelsBread {
RQuery?: any RQuery?: any
} }
export interface modelsApp2 {
}
export interface modelsApp { export interface modelsApp {
CreatedAt: string created: string
UpdatedAt: string updated: string
DeletedAt: null delete_flag: boolean
Creator: number des: string
Des: string hide: boolean
EnableEmail: boolean icon: string
EnablePhone: boolean id: string
EnableRegister: true name: string
EnableUser: boolean redirect: string
EnableUserKey: boolean role_id: string
EnableWx: boolean status: number
Hide: boolean user_count: number
Host: string
Icon: string // Creator: number
InitRole: null // Des: string
InitRoleID: number // EnableEmail: boolean
Name: string // EnablePhone: boolean
UUID: string // EnableRegister: true
UserCount: number // EnableUser: boolean
UserKeyUrl: string // EnableUserKey: boolean
UserRefreshUrl: string // EnableWx: boolean
UserStatus: string // Hide: boolean
Users: null // Host: string
// Icon: string
// InitRole?: null
// InitRoleID: number
// Name: string
// UUID: string
// UserCount: number
// UserKeyUrl: string
// UserRefreshUrl: string
// UserStatus: string
// Users: null
} }
export interface modelsUser { export interface modelsUser {

@ -14,11 +14,28 @@ export interface modelsBread {
RQuery?: any RQuery?: any
} }
export enum AppJoin {
Auto = 0,
Disabled = 1,
Applying = 2,
}
export interface App {
created: string
updated: string
delete_flag: boolean
des: string
hide: boolean
icon: string
id: string
join_method: AppJoin
name: string
redirect: string
role_id: string
status: number
user_count: number
export interface modelsApp {
CreatedAt: string
UpdatedAt: string
DeletedAt: null
Creator: number Creator: number
Des: string Des: string
EnableEmail: boolean EnableEmail: boolean
@ -44,7 +61,7 @@ export interface modelsApp {
export interface modelsUser { export interface modelsUser {
// Index 前端缓存 // Index 前端缓存
Index?: number Index?: number
Apps: modelsApp[] Apps: App[]
Auths: null Auths: null
CreatedAt: string CreatedAt: string
DeletedAt: null DeletedAt: null
@ -58,8 +75,6 @@ export interface modelsUser {
Email: string Email: string
Nickname: string Nickname: string
Phone: string Phone: string
Used: number
Space: number
} }
export interface modelsSimpleAuth { export interface modelsSimpleAuth {
@ -69,7 +84,7 @@ export interface modelsSimpleAuth {
} }
export interface modelsAuth { export interface modelsAuth {
App?: modelsApp App?: App
AppUUID: string AppUUID: string
CreatedAt: string CreatedAt: string
DeletedAt: null DeletedAt: null
@ -87,7 +102,7 @@ export interface modelsAuth {
} }
export interface modelsRole { export interface modelsRole {
App?: modelsApp App?: App
AppUUID: string AppUUID: string
Auths: null Auths: null
CreatedAt: string CreatedAt: string
@ -100,7 +115,7 @@ export interface modelsRole {
} }
export interface modelsResource { export interface modelsResource {
App?: modelsApp App?: App
AppUUID: string AppUUID: string
CreatedAt: string CreatedAt: string
DeletedAt: null DeletedAt: null

@ -1,8 +1,8 @@
import {InjectionKey} from 'vue' import { InjectionKey } from 'vue'
import {createStore, useStore as baseUseStore, Store} from 'vuex' import { createStore, useStore as baseUseStore, Store } from 'vuex'
import api from '@/api' import api from '@/api'
import {User, UserState} from './user' import { User, UserState } from './user'
import {modelsBread} from '@/models' import { modelsBread } from '@/models'
type Map = { [key: string]: string } type Map = { [key: string]: string }
@ -24,15 +24,15 @@ export const store = createStore<State>({
modules: { modules: {
user: User, user: User,
}, },
// @ts-ignore // @ts-ignore
state: { state: {
oauuid: 'jU5Jo5hM', oauuid: 'FR9P5t8debxc11aFF',
title: '', title: '',
height: 'calc(100vh - 108px)', height: 'calc(100vh - 108px)',
hideHeader: false, hideHeader: false,
apps: [], apps: [],
translateCache: {}, translateCache: {},
breads: [{Index: 0, Name: 'home', Type: 'icon', RName: 'home'}], breads: [{ Index: 0, Name: 'home', Type: 'icon', RName: 'home' }],
}, },
getters: { getters: {
cache: (state: State) => (key: string) => { cache: (state: State) => (key: string) => {
@ -72,12 +72,12 @@ export const store = createStore<State>({
}, },
}, },
actions: { actions: {
fetchSelf({commit}) { fetchSelf({ commit }) {
api.app.self().Start(d => { api.app.self().Start(d => {
commit('setOA', d) commit('setOA', d)
}) })
}, },
fetchApps({commit}) { fetchApps({ commit }) {
api.app.list().Start(e => { api.app.list().Start(e => {
commit('setApps', e) commit('setApps', e)
}) })

@ -3,18 +3,13 @@
<div class="flex justify-between"> <div class="flex justify-between">
<h1 class="page-h1">用户名单</h1> <h1 class="page-h1">用户名单</h1>
<div class="my-5 mr-10"> <div class="my-5 mr-10">
<n-button @click="temp_user = {};tu_flag=true">添加用户</n-button> <n-button @click="temp_user = {}; tu_flag = true">添加用户</n-button>
</div> </div>
</div> </div>
<n-data-table <n-data-table :bordered="false" :columns="columns" :scroll-x="980" :data="users" />
:bordered="false"
:columns="columns"
:scroll-x="980"
:data="users"
/>
<n-modal v-model:show="tu_flag"> <n-modal v-model:show="tu_flag">
<n-card class="w-4/5 md:w-96 rounded-2xl" :title="temp_user.Index >= 0 ? temp_user.Username:' '" :bordered="false" <n-card class="w-4/5 md:w-96 rounded-2xl" :title="temp_user.Index >= 0 ? temp_user.Username : ' '"
size="huge"> :bordered="false" size="huge">
<template #header-extra>{{ temp_user.Index >= 0 ? '编辑' : '创建' }}</template> <template #header-extra>{{ temp_user.Index >= 0 ? '编辑' : '创建' }}</template>
<div class="grid grid-cols-5 gap-1 gap-y-8" style="line-height: 34px"> <div class="grid grid-cols-5 gap-1 gap-y-8" style="line-height: 34px">
<div>用户名</div> <div>用户名</div>
@ -24,7 +19,7 @@
</div> </div>
<template #footer> <template #footer>
<div class="flex justify-end"> <div class="flex justify-end">
<n-button class="mx-3" @click="tu_flag=false"></n-button> <n-button class="mx-3" @click="tu_flag = false">取消</n-button>
<n-button>更新</n-button> <n-button>更新</n-button>
</div> </div>
</template> </template>
@ -34,13 +29,13 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import {inject, onMounted, ref, h, computed, Ref} from 'vue' import { inject, onMounted, ref, h, computed, Ref } from 'vue'
import api from '@/api' import api from '@/api'
import {NTag as ntag, NButton as nbtn, useDialog} from 'naive-ui' import { NTag as ntag, NButton as nbtn, useDialog } from 'naive-ui'
import {useStore} from '@/store' import { useStore } from '@/store'
import {R} from '@/auth' import { R } from '@/auth'
import {modelsBread, modelsUser} from '@/models' import { modelsBread, modelsUser } from '@/models'
import {useRoute} from 'vue-router' import { useRoute } from 'vue-router'
let store = useStore() let store = useStore()
let route = useRoute() let route = useRoute()
@ -59,7 +54,7 @@ onMounted(() => {
return [ return [
h(nbtn, { h(nbtn, {
onClick: () => { onClick: () => {
temp_user.value = Object.assign({Index: index}, row.User) temp_user.value = Object.assign({ Index: index }, row.User)
tu_flag.value = true tu_flag.value = true
}, },
}, { }, {
@ -176,5 +171,4 @@ store.commit('setBreads', {
</script> </script>
<style scoped> <style scoped>
</style> </style>

@ -4,44 +4,73 @@
<div class="flex justify-between"> <div class="flex justify-between">
<h1 class="page-h1">我的应用</h1> <h1 class="page-h1">我的应用</h1>
<div class="my-5 mr-10"> <div class="my-5 mr-10">
<n-button @click="new_flag=true" v-if="store.state.user.auth.Get(R.App, '').CanCreate()"></n-button> <n-button
@click="new_flag = true"
v-if="store.state.user.auth.Get(R.App, '').CanCreate()"
>创建应用
</n-button>
</div> </div>
</div> </div>
<div class="grid gap-4 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 text-center"> <div
<div v-for="(item, k) in ofApps" class="flex items-center justify-center" :key="k"> class="grid gap-4 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 text-center"
>
<div
v-for="(item, k) in ofApps"
class="flex items-center justify-center"
:key="k"
>
<AppCard :core="item"></AppCard> <AppCard :core="item"></AppCard>
</div> </div>
</div> </div>
</div> </div>
<div class="mt-20" v-if="apps.length > 0"> <div class="mt-20" v-if="apps.length > 0">
<h1 class="page-h1">应用中心</h1> <h1 class="page-h1">应用中心</h1>
<div class="grid gap-4 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 text-center"> <div
<div v-for="(item, k) in apps" class="flex items-center justify-center" :key="k"> class="grid gap-4 grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 xl:grid-cols-4 2xl:grid-cols-5 text-center"
>
<div
v-for="(item, k) in apps"
class="flex items-center justify-center"
:key="k"
>
<AppCard :core="item"></AppCard> <AppCard :core="item"></AppCard>
</div> </div>
</div> </div>
</div> </div>
<n-modal v-model:show="new_flag"> <n-modal v-model:show="new_flag">
<n-card class="w-4/5 md:w-96 rounded-2xl" title="创建应用" :bordered="false" <n-card
size="huge"> class="w-4/5 md:w-96 rounded-2xl"
<n-form label-width="70px" label-align="left" :model="temp_app" ref="form_ref" label-placement="left" title="创建应用"
:rules="rules"> :bordered="false"
size="huge"
>
<n-form
label-width="70px"
label-align="left"
:model="temp_app"
ref="form_ref"
label-placement="left"
:rules="rules"
>
<n-form-item required label="应用名" path="name"> <n-form-item required label="应用名" path="name">
<n-input v-model:value="temp_app.name"></n-input> <n-input v-model:value="temp_app.name"></n-input>
</n-form-item> </n-form-item>
<n-form-item required label="icon" path="icon"> <n-form-item required label="icon" path="icon">
<uploader <uploader
url="test.ico" url="test.ico"
@success="(e) => {temp_app.icon = e}" @success="
(e) => {
temp_app.icon = e;
}
"
> >
<n-avatar size="large" round :src="temp_app.icon"> <n-avatar size="large" round :src="temp_app.icon"> </n-avatar>
</n-avatar>
</uploader> </uploader>
</n-form-item> </n-form-item>
</n-form> </n-form>
<template #footer> <template #footer>
<div class="flex justify-end"> <div class="flex justify-end">
<n-button class="mx-3" @click="new_flag=false"></n-button> <n-button class="mx-3" @click="new_flag = false">取消</n-button>
<n-button @click="create_new"></n-button> <n-button @click="create_new"></n-button>
</div> </div>
</template> </template>
@ -51,83 +80,95 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import {onMounted, ref} from 'vue' import { onMounted, ref } from "vue";
import api from '@/api' import api from "@/api";
import AppCard from '@/components/app.vue' import AppCard from "@/components/app.vue";
import {useStore} from '@/store' import { useStore } from "@/store";
import {R} from '@/auth' import { R } from "@/auth";
import util from '@/libs/util' import { useMessage, useLoadingBar } from "naive-ui";
import {useMessage, useLoadingBar} from 'naive-ui' import { modelsApp } from "@/models";
import {modelsApp} from '@/models' import Uploader from "@/components/uploader";
import Uploader from '@/components/uploader'
let msg = useMessage() let msg = useMessage();
let bar = useLoadingBar() let bar = useLoadingBar();
let store = useStore() let store = useStore();
let apps = ref<modelsApp[]>([]) let apps = ref<modelsApp[]>([]);
let ofApps = ref<modelsApp[]>([]) let ofApps = ref<modelsApp[]>([]);
function getApps() { function getApps() {
bar.start() bar.start();
api.app.list().Start(e => { api.app.list().Start(
apps.value = e (e) => {
api.app.user('').list(store.state.user.id).Start(e => { apps.value = e;
bar.finish() api.app
ofApps.value = [] .user("")
.list(store.state.user.id)
.Start(
(e) => {
bar.finish();
ofApps.value = [];
for (let i in e) { for (let i in e) {
let ai = apps.value.findIndex(a => a.UUID === e[i].AppUUID) let ai = apps.value.findIndex((a) => a.UUID === e[i].AppUUID);
if (ai >= 0) { if (ai >= 0) {
apps.value[ai].UserStatus = e[i].Status apps.value[ai].UserStatus = e[i].Status;
if (e[i].Status === 'ok') { if (e[i].Status === "ok") {
ofApps.value.push(apps.value[ai]) ofApps.value.push(apps.value[ai]);
apps.value.splice(ai, 1) apps.value.splice(ai, 1);
}
} }
} }
},
() => {
bar.error();
} }
}, () => { );
bar.error() },
}) () => bar.error()
}, () => bar.error()) );
} }
onMounted(() => { onMounted(() => {
getApps() getApps();
}) });
let new_flag = ref(false);
let new_flag = ref(false)
let temp_app = ref({ let temp_app = ref({
name: '', name: "",
icon: '', icon: "",
}) });
let form_ref = ref(null) let form_ref = ref(null);
let rules = { let rules = {
name: [{ name: [
{
required: true, required: true,
validator(r: any, v: any) { validator(r: any, v: any) {
return (v && v.length >= 2 && v.length <= 16) || new Error('长度要求2~16') return (
(v && v.length >= 2 && v.length <= 16) || new Error("长度要求2~16")
);
}, },
trigger: ['input', 'blur'], trigger: ["input", "blur"],
}], },
} ],
};
function create_new() { function create_new() {
// @ts-ignore // @ts-ignore
form_ref.value.validate((e: any) => { form_ref.value.validate((e: any) => {
if (!e) { if (!e) {
api.app.create(temp_app.value.name, temp_app.value.icon).Start(e => { api.app.create(temp_app.value.name, temp_app.value.icon).Start(
e.Status = 'ok' (e) => {
ofApps.value.push(e) e.Status = "ok";
msg.success('创建成功') ofApps.value.push(e);
new_flag.value = false msg.success("创建成功");
}, e => { new_flag.value = false;
msg.warning('创建失败: ' + e) },
}) (e) => {
msg.warning("创建失败: " + e);
} }
}) );
}
});
} }
</script> </script>
<style scoped> <style scoped></style>
</style>

@ -74,7 +74,8 @@ function login() {
store.commit('user/refreshToken', localStorage.auth_token) store.commit('user/refreshToken', localStorage.auth_token)
msg.Info('登录成功') msg.Info('登录成功')
store.dispatch('user/fetchUserData') store.dispatch('user/fetchUserData')
redirect(route.query.redirect as string) let url = route.query.redirect || headers.redirect || '/'
redirect(url)
} else { } else {
msg.Info('正在申请加入,请等待管理员审批') msg.Info('正在申请加入,请等待管理员审批')
} }

@ -1,28 +1,28 @@
<template> <template>
<div class="pt-10"> <div class="pt-10">
<div class="flex justify-center"> <div class="flex justify-center">
<div class="relative rounded-xl text-lg text-black" :style="{background: IsDark?'#555': '#d5d5d5'}"> <div class="relative rounded-xl text-lg text-black" :style="{ background: IsDark ? '#555' : '#d5d5d5' }">
<div @click="ifInfo=true" class="inline-block px-5 rounded-xl" :style="{background: ifInfo ? '#fc0005': ''}"> <div @click="ifInfo = true" class="inline-block px-5 rounded-xl"
:style="{ background: ifInfo ? '#fc0005' : '' }">
个人信息 个人信息
</div> </div>
<div @click="ifInfo=false" class="inline-block px-5 rounded-xl" :style="{background: ifInfo ? '': '#fc0005'}"> <div @click="ifInfo = false" class="inline-block px-5 rounded-xl"
:style="{ background: ifInfo ? '' : '#fc0005' }">
账户管理 账户管理
</div> </div>
</div> </div>
</div> </div>
<div class="inline-block flex justify-center mt-10"> <div class="inline-block flex justify-center mt-10">
<transition mode="out-in" enter-active-class="animate__fadeInLeft" leave-active-class="animate__fadeOutRight"> <transition mode="out-in" enter-active-class="animate__fadeInLeft"
leave-active-class="animate__fadeOutRight">
<div v-if="ifInfo" class="animate__animated animate__faster"> <div v-if="ifInfo" class="animate__animated animate__faster">
<n-form label-placement="left" label-width="80px" label-align="left"> <n-form label-placement="left" label-width="80px" label-align="left">
<n-form-item label="昵称"> <n-form-item label="昵称">
<n-input v-model:value="user.Nickname" @blur="update('Nickname')"></n-input> <n-input v-model:value="user.Nickname" @blur="update('Nickname')"></n-input>
</n-form-item> </n-form-item>
<n-form-item label="头像"> <n-form-item label="头像">
<uploader <uploader :url="user.ID + '.ico'" @success="handleFinish">
:url="user.ID+'.ico'"
@success="handleFinish"
>
<n-avatar size="large" round :src="user.Icon"> <n-avatar size="large" round :src="user.Icon">
</n-avatar> </n-avatar>
</uploader> </uploader>
@ -38,8 +38,8 @@
<n-input v-model:value="user.Phone" @blur="update('Phone')"></n-input> <n-input v-model:value="user.Phone" @blur="update('Phone')"></n-input>
</n-form-item> </n-form-item>
<n-form-item label="email"> <n-form-item label="email">
<n-auto-complete :options="emailOptions" v-model:value="user.Email" <n-auto-complete :options="emailOptions" v-model:value="user.Email" @blur="update('Email')">
@blur="update('Email')"></n-auto-complete> </n-auto-complete>
</n-form-item> </n-form-item>
<n-form-item label="邮件通知"> <n-form-item label="邮件通知">
<n-switch> <n-switch>
@ -61,12 +61,12 @@
</template> </template>
<script lang="ts" setup> <script lang="ts" setup>
import {ref, computed, onMounted} from 'vue' import { ref, computed, onMounted } from 'vue'
import {IsDark} from '@/theme' import { IsDark } from '@/theme'
import {useStore} from '@/store' import { useStore } from '@/store'
import api from '@/api' import api from '@/api'
import {useMessage} from 'naive-ui' import { useMessage } from 'naive-ui'
import {modelsUser} from '@/models' import { modelsUser } from '@/models'
import util from '@/libs/util' import util from '@/libs/util'
import Uploader from '@/components/uploader' import Uploader from '@/components/uploader'
@ -105,7 +105,7 @@ function update(key: string) {
if (v === store.state.user.local[key]) { if (v === store.state.user.local[key]) {
return return
} }
api.user.update(store.state.user.id, {[key]: v}).Start(e => { api.user.update(store.state.user.id, { [key]: v }).Start(e => {
msg.success('更新成功') msg.success('更新成功')
store.state.user.local[key] = v store.state.user.local[key] = v
}, e => { }, e => {

@ -495,9 +495,9 @@ camelcase-css@^2.0.1:
integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA== integrity sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==
caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001286: caniuse-lite@^1.0.30001109, caniuse-lite@^1.0.30001286:
version "1.0.30001292" version "1.0.30001373"
resolved "https://registry.yarnpkg.com/caniuse-lite/-/caniuse-lite-1.0.30001292.tgz#4a55f61c06abc9595965cfd77897dc7bc1cdc456" resolved "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001373.tgz"
integrity sha512-jnT4Tq0Q4ma+6nncYQVe7d73kmDmE9C3OGTx3MvW7lBM/eY1S1DZTMBON7dqV481RhNiS5OxD7k9JQvmDOTirw== integrity sha512-pJYArGHrPp3TUqQzFYRmP/lwJlj8RCbVe3Gd3eJQkAV8SAC6b19XS9BjMvRdvaS8RMkaTN8ZhoHP6S1y8zzwEQ==
chalk@^2.4.1: chalk@^2.4.1:
version "2.4.2" version "2.4.2"

Loading…
Cancel
Save