master
veypi 2 years ago
parent 21701e405e
commit 34520f36fd

12
oab/Cargo.lock generated

@ -1169,6 +1169,7 @@ dependencies = [
"serde",
"serde-big-array",
"serde_json",
"serde_repr",
"serde_yaml",
"sqlx",
"thiserror",
@ -1549,6 +1550,17 @@ dependencies = [
"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]]
name = "serde_urlencoded"
version = "0.7.1"

@ -32,3 +32,4 @@ serde-big-array = "0.4.1"
base64 = "0.13.0"
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
// 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::login)
.service(user::delete);
cfg.service(app::get)
.service(app::list)
.service(app::create)
.service(app::del);
cfg.service(greet);
}

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

@ -7,8 +7,9 @@
use chrono::NaiveDateTime;
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)]
pub enum AppJoin {
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)]
pub enum AUStatus {
OK = 0,

@ -8,7 +8,8 @@
use chrono::NaiveDateTime;
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 id: String,
pub created: Option<NaiveDateTime>,

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

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

@ -2,66 +2,80 @@
<div class="core rounded-2xl p-3">
<div class="grid gap-4 grid-cols-5">
<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>
</div>
<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>
</div>
</div>
</div>
</template>
<script setup lang='ts'>
import {withDefaults} from 'vue'
import {useRouter} from 'vue-router'
import {useMessage, useLoadingBar} from 'naive-ui'
import api from '@/api'
import {useStore} from '@/store'
import {modelsApp} from '@/models'
import util from '@/libs/util'
<script setup lang="ts">
import { withDefaults } from "vue";
import { useRouter } from "vue-router";
import { useMessage, useLoadingBar } from "naive-ui";
import api from "@/api";
import { useStore } from "@/store";
import { modelsApp } from "@/models";
import util from "@/libs/util";
let router = useRouter()
let store = useStore()
let msg = useMessage()
let bar = useLoadingBar()
let router = useRouter();
let store = useStore();
let msg = useMessage();
let bar = useLoadingBar();
let props = withDefaults(defineProps<{
core?: modelsApp
}>(), {
// @ts-ignore
core: {},
})
let props = withDefaults(
defineProps<{
core: modelsApp;
}>(),
{}
);
function Go() {
switch (props.core.UserStatus) {
case 'ok':
router.push({name: 'app.main', params: {uuid: props.core.UUID}})
return
case 'apply':
msg.info('请等待管理员审批进入')
return
case 'deny':
msg.warning('进入申请未通过')
return
case 'disabled':
msg.warning('已被禁止使用')
return
case "ok":
router.push({ name: "app.main", params: { uuid: props.core.UUID } });
return;
case "apply":
msg.info("请等待管理员审批进入");
return;
case "deny":
msg.warning("进入申请未通过");
return;
case "disabled":
msg.warning("已被禁止使用");
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
}
props.core.UserStatus = e.Status
msg.info('已发起加入申请')
}, (e) => {
msg.warning('加入失败: ' + e)
bar.error()
})
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;
}
props.core.UserStatus = e.Status;
msg.info("已发起加入申请");
},
(e) => {
msg.warning("加入失败: " + e);
bar.error();
}
);
return;
}
</script>
<style scoped>

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

@ -14,31 +14,44 @@ export interface modelsBread {
RQuery?: any
}
export interface modelsApp2 {
}
export interface modelsApp {
CreatedAt: string
UpdatedAt: string
DeletedAt: null
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
created: string
updated: string
delete_flag: boolean
des: string
hide: boolean
icon: string
id: string
name: string
redirect: string
role_id: string
status: number
user_count: number
// 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 interface modelsUser {

@ -14,11 +14,28 @@ export interface modelsBread {
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
Des: string
EnableEmail: boolean
@ -44,7 +61,7 @@ export interface modelsApp {
export interface modelsUser {
// Index 前端缓存
Index?: number
Apps: modelsApp[]
Apps: App[]
Auths: null
CreatedAt: string
DeletedAt: null
@ -58,8 +75,6 @@ export interface modelsUser {
Email: string
Nickname: string
Phone: string
Used: number
Space: number
}
export interface modelsSimpleAuth {
@ -69,7 +84,7 @@ export interface modelsSimpleAuth {
}
export interface modelsAuth {
App?: modelsApp
App?: App
AppUUID: string
CreatedAt: string
DeletedAt: null
@ -87,7 +102,7 @@ export interface modelsAuth {
}
export interface modelsRole {
App?: modelsApp
App?: App
AppUUID: string
Auths: null
CreatedAt: string
@ -100,7 +115,7 @@ export interface modelsRole {
}
export interface modelsResource {
App?: modelsApp
App?: App
AppUUID: string
CreatedAt: string
DeletedAt: null

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

@ -1,46 +1,41 @@
<template>
<div>
<div class="flex justify-between">
<h1 class="page-h1">用户名单</h1>
<div class="my-5 mr-10">
<n-button @click="temp_user = {};tu_flag=true">添加用户</n-button>
</div>
</div>
<n-data-table
:bordered="false"
:columns="columns"
:scroll-x="980"
:data="users"
/>
<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"
size="huge">
<template #header-extra>{{ temp_user.Index >= 0 ? '编辑' : '创建' }}</template>
<div class="grid grid-cols-5 gap-1 gap-y-8" style="line-height: 34px">
<div>用户名</div>
<div class="col-span-4">
<n-input v-model:value="temp_user.Username"></n-input>
</div>
</div>
<template #footer>
<div class="flex justify-end">
<n-button class="mx-3" @click="tu_flag=false"></n-button>
<n-button>更新</n-button>
<div>
<div class="flex justify-between">
<h1 class="page-h1">用户名单</h1>
<div class="my-5 mr-10">
<n-button @click="temp_user = {}; tu_flag = true">添加用户</n-button>
</div>
</div>
</template>
</n-card>
</n-modal>
</div>
<n-data-table :bordered="false" :columns="columns" :scroll-x="980" :data="users" />
<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" size="huge">
<template #header-extra>{{ temp_user.Index >= 0 ? '编辑' : '创建' }}</template>
<div class="grid grid-cols-5 gap-1 gap-y-8" style="line-height: 34px">
<div>用户名</div>
<div class="col-span-4">
<n-input v-model:value="temp_user.Username"></n-input>
</div>
</div>
<template #footer>
<div class="flex justify-end">
<n-button class="mx-3" @click="tu_flag = false">取消</n-button>
<n-button>更新</n-button>
</div>
</template>
</n-card>
</n-modal>
</div>
</template>
<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 {NTag as ntag, NButton as nbtn, useDialog} from 'naive-ui'
import {useStore} from '@/store'
import {R} from '@/auth'
import {modelsBread, modelsUser} from '@/models'
import {useRoute} from 'vue-router'
import { NTag as ntag, NButton as nbtn, useDialog } from 'naive-ui'
import { useStore } from '@/store'
import { R } from '@/auth'
import { modelsBread, modelsUser } from '@/models'
import { useRoute } from 'vue-router'
let store = useStore()
let route = useRoute()
@ -50,114 +45,114 @@ let users = ref<modelsUser[]>([])
let isOA = computed(() => uuid.value === store.state.oauuid)
onMounted(() => {
if (isOA) {
columns.value.push({
title: '操作',
key: '',
width: 200,
render(row, index) {
return [
h(nbtn, {
onClick: () => {
temp_user.value = Object.assign({Index: index}, row.User)
tu_flag.value = true
},
}, {
default: () => '编辑',
}),
]
if (isOA) {
columns.value.push({
title: '操作',
key: '',
width: 200,
render(row, index) {
return [
h(nbtn, {
onClick: () => {
temp_user.value = Object.assign({ Index: index }, row.User)
tu_flag.value = true
},
}, {
default: () => '编辑',
}),
]
},
},
},
)
}
)
}
api.app.user(uuid.value as string).list(0).Start(e => {
users.value = e
})
api.app.user(uuid.value as string).list(0).Start(e => {
users.value = e
})
})
let columns = ref([
{
title: 'ID',
key: 'UserID',
width: 100,
},
{
title: '用户',
key: 'User.Username',
width: 100,
fixed: 'left',
},
{
title: '加入时间',
key: 'User.CreatedAt',
},
{
title: 'Status',
key: 'Status',
width: 100,
render(row) {
let t = statusTag(row.Status)
// @ts-ignore
return h(ntag, {
'type': t[1],
onClick: () => {
changeStatus(row)
},
},
{
default: () => t[0],
{
title: 'ID',
key: 'UserID',
width: 100,
},
{
title: '用户',
key: 'User.Username',
width: 100,
fixed: 'left',
},
{
title: '加入时间',
key: 'User.CreatedAt',
},
{
title: 'Status',
key: 'Status',
width: 100,
render(row) {
let t = statusTag(row.Status)
// @ts-ignore
return h(ntag, {
'type': t[1],
onClick: () => {
changeStatus(row)
},
},
{
default: () => t[0],
},
)
},
)
},
},
])
function statusTag(s: string) {
switch (s) {
case 'ok':
return ['正常', 'success']
case 'apply':
return ['申请中', 'info']
case 'deny':
return ['拒绝', '']
case 'disabled':
return ['禁用', 'warning']
}
return ['未知', '']
switch (s) {
case 'ok':
return ['正常', 'success']
case 'apply':
return ['申请中', 'info']
case 'deny':
return ['拒绝', '']
case 'disabled':
return ['禁用', 'warning']
}
return ['未知', '']
}
function changeStatus(u) {
if (store.state.user.auth.Get(R.User, uuid.value).CanUpdate()) {
dialog.warning({
title: '请选择切换状态',
content: () => {
let tags = []
for (let s of ['ok', 'apply', 'deny', 'disabled']) {
let t = statusTag(s)
if (u.Status !== s) {
// @ts-ignore
tags.push(h(ntag, {
'type': t[1],
onClick: () => {
api.app.user(uuid.value).update(u.UserID, s).Start(e => {
u.Status = s
dialog.destroyAll()
if (store.state.user.auth.Get(R.User, uuid.value).CanUpdate()) {
dialog.warning({
title: '请选择切换状态',
content: () => {
let tags = []
for (let s of ['ok', 'apply', 'deny', 'disabled']) {
let t = statusTag(s)
if (u.Status !== s) {
// @ts-ignore
tags.push(h(ntag, {
'type': t[1],
onClick: () => {
api.app.user(uuid.value).update(u.UserID, s).Start(e => {
u.Status = s
dialog.destroyAll()
})
},
}, {
default: () => t[0],
}))
}
}
return h('div', {
class: 'flex justify-between mx-16 mt-10',
}, {
default: () => tags,
})
},
}, {
default: () => t[0],
}))
}
}
return h('div', {
class: 'flex justify-between mx-16 mt-10',
}, {
default: () => tags,
},
})
},
})
}
}
}
let temp_user = ref<modelsUser>({} as modelsUser)
@ -167,14 +162,13 @@ function add_user() {
}
store.commit('setBreads', {
Index: 2,
Name: '用户',
RName: route.name,
RParams: route.params,
RQuery: route.query,
Index: 2,
Name: '用户',
RName: route.name,
RParams: route.params,
RQuery: route.query,
} as modelsBread)
</script>
<style scoped>
</style>

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

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

@ -1,72 +1,72 @@
<template>
<div class="pt-10">
<div class="flex justify-center">
<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 class="pt-10">
<div class="flex justify-center">
<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>
<div @click="ifInfo = false" class="inline-block px-5 rounded-xl"
:style="{ background: ifInfo ? '' : '#fc0005' }">
账户管理
</div>
</div>
</div>
<div @click="ifInfo=false" class="inline-block px-5 rounded-xl" :style="{background: ifInfo ? '': '#fc0005'}">
账户管理
</div>
</div>
</div>
<div class="inline-block flex justify-center mt-10">
<transition mode="out-in" enter-active-class="animate__fadeInLeft" leave-active-class="animate__fadeOutRight">
<div v-if="ifInfo" class="animate__animated animate__faster">
<n-form label-placement="left" label-width="80px" label-align="left">
<n-form-item label="昵称">
<n-input v-model:value="user.Nickname" @blur="update('Nickname')"></n-input>
</n-form-item>
<n-form-item label="头像">
<uploader
:url="user.ID+'.ico'"
@success="handleFinish"
>
<n-avatar size="large" round :src="user.Icon">
</n-avatar>
</uploader>
</n-form-item>
</n-form>
<div class="inline-block flex justify-center mt-10">
<transition mode="out-in" enter-active-class="animate__fadeInLeft"
leave-active-class="animate__fadeOutRight">
<div v-if="ifInfo" class="animate__animated animate__faster">
<n-form label-placement="left" label-width="80px" label-align="left">
<n-form-item label="昵称">
<n-input v-model:value="user.Nickname" @blur="update('Nickname')"></n-input>
</n-form-item>
<n-form-item label="头像">
<uploader :url="user.ID + '.ico'" @success="handleFinish">
<n-avatar size="large" round :src="user.Icon">
</n-avatar>
</uploader>
</n-form-item>
</n-form>
</div>
<div v-else class="animate__animated animate__faster">
<n-form label-align="left" label-width="80px" label-placement="left">
<n-form-item label="Username">
<n-input disabled v-model:value="user.Username"></n-input>
</n-form-item>
<n-form-item label="phone">
<n-input v-model:value="user.Phone" @blur="update('Phone')"></n-input>
</n-form-item>
<n-form-item label="email">
<n-auto-complete :options="emailOptions" v-model:value="user.Email" @blur="update('Email')">
</n-auto-complete>
</n-form-item>
<n-form-item label="邮件通知">
<n-switch>
<template #checked>启用</template>
<template #unchecked>关闭</template>
</n-switch>
</n-form-item>
<n-form-item label="短信通知">
<n-switch>
<template #checked>启用</template>
<template #unchecked>关闭</template>
</n-switch>
</n-form-item>
</n-form>
</div>
</transition>
</div>
<div v-else class="animate__animated animate__faster">
<n-form label-align="left" label-width="80px" label-placement="left">
<n-form-item label="Username">
<n-input disabled v-model:value="user.Username"></n-input>
</n-form-item>
<n-form-item label="phone">
<n-input v-model:value="user.Phone" @blur="update('Phone')"></n-input>
</n-form-item>
<n-form-item label="email">
<n-auto-complete :options="emailOptions" v-model:value="user.Email"
@blur="update('Email')"></n-auto-complete>
</n-form-item>
<n-form-item label="邮件通知">
<n-switch>
<template #checked>启用</template>
<template #unchecked>关闭</template>
</n-switch>
</n-form-item>
<n-form-item label="短信通知">
<n-switch>
<template #checked>启用</template>
<template #unchecked>关闭</template>
</n-switch>
</n-form-item>
</n-form>
</div>
</transition>
</div>
</div>
</template>
<script lang="ts" setup>
import {ref, computed, onMounted} from 'vue'
import {IsDark} from '@/theme'
import {useStore} from '@/store'
import { ref, computed, onMounted } from 'vue'
import { IsDark } from '@/theme'
import { useStore } from '@/store'
import api from '@/api'
import {useMessage} from 'naive-ui'
import {modelsUser} from '@/models'
import { useMessage } from 'naive-ui'
import { modelsUser } from '@/models'
import util from '@/libs/util'
import Uploader from '@/components/uploader'
@ -75,42 +75,42 @@ let store = useStore()
let ifInfo = ref(true)
let user = ref<modelsUser>({
ID: store.state.user.id,
Username: store.state.user.local.Username,
Nickname: store.state.user.local.Nickname,
Icon: store.state.user.local.Icon,
Email: store.state.user.local.Email,
Phone: store.state.user.local.Phone,
ID: store.state.user.id,
Username: store.state.user.local.Username,
Nickname: store.state.user.local.Nickname,
Icon: store.state.user.local.Icon,
Email: store.state.user.local.Email,
Phone: store.state.user.local.Phone,
} as modelsUser)
let emailOptions = computed(() => {
return ['@qq.com', '@163.com', '@gmail.com', '@outlook.com', '@icloud.com', '@169.com'].map((suffix) => {
const prefix = user.value.Email.split('@')[0]
return {
label: prefix + suffix,
value: prefix + suffix,
}
})
return ['@qq.com', '@163.com', '@gmail.com', '@outlook.com', '@icloud.com', '@169.com'].map((suffix) => {
const prefix = user.value.Email.split('@')[0]
return {
label: prefix + suffix,
value: prefix + suffix,
}
})
})
function handleFinish(e: string) {
console.log(e)
user.value.Icon = e
update('Icon')
return
console.log(e)
user.value.Icon = e
update('Icon')
return
}
function update(key: string) {
// @ts-ignore
let v = user.value[key]
if (v === store.state.user.local[key]) {
return
}
api.user.update(store.state.user.id, {[key]: v}).Start(e => {
msg.success('更新成功')
store.state.user.local[key] = v
}, e => {
msg.error('更新失败: ' + e)
})
// @ts-ignore
let v = user.value[key]
if (v === store.state.user.local[key]) {
return
}
api.user.update(store.state.user.id, { [key]: v }).Start(e => {
msg.success('更新成功')
store.state.user.local[key] = v
}, e => {
msg.error('更新失败: ' + e)
})
}
</script>

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

Loading…
Cancel
Save