home
This commit is contained in:
+3
-1
@@ -7,7 +7,8 @@
|
||||
"serve": "vite preview"
|
||||
},
|
||||
"dependencies": {
|
||||
"@veypi/one-icon": "2",
|
||||
"@veypi/one-icon": "2.0.5",
|
||||
"animate.css": "^4.1.1",
|
||||
"axios": "^0.24.0",
|
||||
"js-base64": "^3.7.2",
|
||||
"vue": "^3.2.16",
|
||||
@@ -18,6 +19,7 @@
|
||||
"@tailwindcss/postcss7-compat": "^2.1.0",
|
||||
"@vitejs/plugin-vue": "^1.9.3",
|
||||
"autoprefixer": "^9.8.8",
|
||||
"less": "^4.1.2",
|
||||
"naive-ui": "^2.19.11",
|
||||
"postcss": "^7.0.39",
|
||||
"tailwindcss": "npm:@tailwindcss/postcss7-compat@^2.2.17",
|
||||
|
||||
File diff suppressed because one or more lines are too long
+92
-43
@@ -1,59 +1,99 @@
|
||||
<script setup lang="ts">
|
||||
// This starter template is using Vue 3 <script setup> SFCs
|
||||
import { onBeforeMount, ref} from 'vue'
|
||||
import util from './libs/util'
|
||||
import {store} from "./store";
|
||||
import {useOsTheme} from 'naive-ui'
|
||||
|
||||
const osThemeRef = useOsTheme()
|
||||
|
||||
onBeforeMount(() => {
|
||||
util.title("统一认证")
|
||||
store.dispatch('fetchSelf')
|
||||
store.commit('setTheme', osThemeRef.value)
|
||||
})
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<n-config-provider :theme="store.getters.GetTheme" class="h-full w-full">
|
||||
<n-layout class="h-full w-full font-sans select-none">
|
||||
<n-layout has-sider style="height: calc(100% - 24px)">
|
||||
<n-layout-sider
|
||||
class="h-full"
|
||||
collapse-mode="transform"
|
||||
:collapsed-width="0"
|
||||
:width="120"
|
||||
show-trigger="bar"
|
||||
content-style="padding: 24px;"
|
||||
bordered
|
||||
default-collapsed
|
||||
>
|
||||
-
|
||||
</n-layout-sider>
|
||||
<n-layout>
|
||||
<n-layout-header bordered style="height: 64px;line-height: 64px;">
|
||||
{{ osThemeRef }}
|
||||
<one-icon @click="store.dispatch('changeTheme')" class="float-right" style="font-size: 36px; margin: 14px">
|
||||
{{ store.getters.IsDark ? 'Daytimemode' : 'nightmode-fill' }}
|
||||
<n-config-provider :theme-overrides="Theme.overrides" :locale="zhCN" :date-locale="dateZhCN"
|
||||
:theme="Theme">
|
||||
<n-layout class="font-sans select-none">
|
||||
<n-layout>
|
||||
<n-layout-header class="pr-5" bordered style="height: 64px;line-height: 64px;">
|
||||
<div class="inline-block float-left h-full">
|
||||
<one-icon color="#000" class="inline-block" @click="$router.push('/')" style="font-size: 48px;margin:8px;color:aqua">
|
||||
glassdoor
|
||||
</one-icon>
|
||||
</n-layout-header>
|
||||
<n-layout style="height: calc(100% - 64px)">
|
||||
<router-view class="h-full w-full"></router-view>
|
||||
</div>
|
||||
<div class="inline-block float-left h-full" style="margin-left: 10px">
|
||||
<n-h6 prefix="bar" align-text><n-text type="primary">统一认证系统</n-text></n-h6>
|
||||
</div>
|
||||
<div v-if="store.state.user.ready" class="inline-block h-full float-right flex justify-center items-center">
|
||||
<avatar></avatar>
|
||||
</div>
|
||||
<div class="inline-block float-right h-full px-3">
|
||||
<fullscreen v-model="isFullScreen" class="header-icon">fullscreen</fullscreen>
|
||||
<div class="header-icon">
|
||||
<one-icon @click="ChangeTheme">
|
||||
{{ IsDark ? 'Daytimemode' : 'nightmode-fill' }}
|
||||
</one-icon>
|
||||
</div>
|
||||
</div>
|
||||
</n-layout-header>
|
||||
<n-layout has-sider style="height: calc(100vh - 88px)">
|
||||
<n-layout-sider
|
||||
collapse-mode="transform"
|
||||
:collapsed-width="0"
|
||||
:width="120"
|
||||
show-trigger="bar"
|
||||
content-style="padding: 24px;"
|
||||
bordered
|
||||
default-collapsed
|
||||
:native-scrollbar="false"
|
||||
>
|
||||
-
|
||||
</n-layout-sider>
|
||||
<n-layout class="main" :native-scrollbar="false">
|
||||
<n-message-provider>
|
||||
<router-view v-slot="{ Component }">
|
||||
<transition mode="out-in" enter-active-class="animate__fadeInLeft" leave-active-class="animate__fadeOutRight">
|
||||
<component class="animate__animated animate__400ms" :is="Component" style="margin: 10px; min-height: calc(100vh - 108px)"
|
||||
></component>
|
||||
</transition>
|
||||
</router-view>
|
||||
</n-message-provider>
|
||||
</n-layout>
|
||||
</n-layout>
|
||||
</n-layout>
|
||||
<n-layout-footer style="height: 24px;line-height: 24px" class="flex justify-around px-3 text-gray-500 text-xs">
|
||||
<span class="hover:text-black cursor-pointer">关于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">
|
||||
<span class="hover:text-black cursor-pointer" @click="goto('https://veypi.com')">
|
||||
©2021 veypi
|
||||
</span>
|
||||
</n-layout-footer>
|
||||
</n-layout>
|
||||
</n-config-provider>
|
||||
</template>
|
||||
<script setup lang="ts">
|
||||
// This starter template is using Vue 3 <script setup> SFCs
|
||||
import {onBeforeMount, ref} from 'vue'
|
||||
import util from './libs/util'
|
||||
import {useStore} from "./store";
|
||||
import {Theme, IsDark, ChangeTheme} from "./theme";
|
||||
import {zhCN, dateZhCN} from 'naive-ui'
|
||||
import avatar from "./components/avatar";
|
||||
import fullscreen from './components/fullscreen'
|
||||
import Fullscreen from "./components/fullscreen/fullscreen.vue";
|
||||
|
||||
let isFullScreen = ref(false)
|
||||
let store = useStore()
|
||||
|
||||
onBeforeMount(() => {
|
||||
util.title("统一认证")
|
||||
store.dispatch('fetchSelf')
|
||||
store.dispatch('user/fetchUserData')
|
||||
})
|
||||
|
||||
let goto = (url: any) => {
|
||||
window.open(url, "_blank")
|
||||
}
|
||||
|
||||
let collapsed = ref(true)
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
<style lang="less">
|
||||
.animate__400ms {
|
||||
--animate-duration: 400ms;
|
||||
}
|
||||
|
||||
<style>
|
||||
html,
|
||||
body {
|
||||
width: 100%;
|
||||
@@ -73,6 +113,12 @@ body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.header-icon {
|
||||
display: inline-block;
|
||||
font-size: 24px;
|
||||
margin: 20px 10px 20px 10px;
|
||||
}
|
||||
|
||||
#app {
|
||||
font-family: Avenir, Helvetica, Arial, sans-serif;
|
||||
-webkit-font-smoothing: antialiased;
|
||||
@@ -82,6 +128,9 @@ body {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.main {
|
||||
}
|
||||
|
||||
::-webkit-scrollbar {
|
||||
display: none; /* Chrome Safari */
|
||||
}
|
||||
|
||||
+10
-10
@@ -13,19 +13,19 @@ function baseRequests(url: string, method: any = 'GET', query: any, data: any, s
|
||||
auth_token: localStorage.auth_token
|
||||
}
|
||||
}).then((res: any) => {
|
||||
if ('auth_token' in res.headers) {
|
||||
localStorage.auth_token = res.headers.auth_token
|
||||
}
|
||||
if (method === 'HEAD') {
|
||||
success(res.headers)
|
||||
} else {
|
||||
success(res.data)
|
||||
}
|
||||
})
|
||||
if ('auth_token' in res.headers) {
|
||||
localStorage.auth_token = res.headers.auth_token
|
||||
}
|
||||
if (method === 'HEAD') {
|
||||
success(res.headers)
|
||||
} else {
|
||||
success(res.data)
|
||||
}
|
||||
})
|
||||
.catch((e: any) => {
|
||||
if (e.response && e.response.status === 401) {
|
||||
console.log(e)
|
||||
store.dispatch('handleLogout')
|
||||
store.commit('user/logout')
|
||||
return
|
||||
}
|
||||
console.log(e)
|
||||
|
||||
+13
-5
@@ -10,7 +10,6 @@ import {store} from '../store'
|
||||
import {Base64} from 'js-base64'
|
||||
|
||||
|
||||
|
||||
export type SuccessFunction<T> = (e: any) => void;
|
||||
export type FailedFunction<T> = (e: any) => void;
|
||||
|
||||
@@ -34,15 +33,15 @@ class Interface {
|
||||
const newFail = function (data: any) {
|
||||
if (data && data.code === 40001) {
|
||||
// no login
|
||||
store.dispatch('handleLogout')
|
||||
store.commit('user/logout')
|
||||
return
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/ban-ts-ignore
|
||||
// @ts-ignore
|
||||
if (data && data.code > 0 && Code[data.code]) {
|
||||
if (data && data.code && Code[data.code]) {
|
||||
}
|
||||
if (fail) {
|
||||
fail(data)
|
||||
fail(data.err)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -54,7 +53,7 @@ class Interface {
|
||||
} else {
|
||||
newFail(data)
|
||||
if (data.code === 41001) {
|
||||
store.dispatch('handleLogout')
|
||||
store.commit('user/logout')
|
||||
// bus.$emit('log_out')
|
||||
}
|
||||
}
|
||||
@@ -92,6 +91,15 @@ const user = {
|
||||
uuid: uuid,
|
||||
password: Base64.encode(password)
|
||||
})
|
||||
},
|
||||
get(id: number) {
|
||||
return new Interface(ajax.get, this.local + id)
|
||||
},
|
||||
list() {
|
||||
return new Interface(ajax.get, this.local)
|
||||
},
|
||||
update(id: number, props: any) {
|
||||
return new Interface(ajax.patch, this.local + id, props)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -0,0 +1,29 @@
|
||||
<template>
|
||||
<div class="core rounded-2xl p-3">
|
||||
<div class="grid gap-4 grid-cols-5">
|
||||
<div class="col-span-2">
|
||||
<n-avatar @click="$router.push({name: 'app', params: {uuid: core.uuid}})" round :size="80" :src="core.icon">
|
||||
{{ core.icon ? '' : core.name }}
|
||||
</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="select-all">{{ core.uuid }}</div>
|
||||
</div>
|
||||
</div>
|
||||
<textarea disabled style="background: none;border: none" class="focus:outline-none w-full">{{core.des}}</textarea>
|
||||
</div>
|
||||
</template>
|
||||
<script setup lang='ts'>
|
||||
import {defineProps} from "vue";
|
||||
|
||||
let props = defineProps<{
|
||||
core: any
|
||||
}>()
|
||||
</script>
|
||||
<style scoped>
|
||||
.core {
|
||||
width: 256px;
|
||||
background: #2c3e50;
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,61 @@
|
||||
<template>
|
||||
<base_frame style="line-height:40px" v-model="shown" :isDark="IsDark">
|
||||
<div class="flex">
|
||||
<n-avatar :src="$store.state.user.icon" round></n-avatar>
|
||||
</div>
|
||||
<template v-slot:main>
|
||||
<div style="height: 100%">
|
||||
<div style="height: calc(100% - 50px)">
|
||||
<div class="w-full px-3">
|
||||
<div class="h-16 flex justify-between items-center">
|
||||
<span style="color: #777">我的账户</span>
|
||||
<span @click="$router.push({name: 'user_setting'});shown=false" class="cursor-pointer"
|
||||
style="color:#f36828">账户中心</span>
|
||||
</div>
|
||||
<div class="grid grid-cols-4 gap-4 h-20">
|
||||
<div class="flex items-center justify-center">
|
||||
<n-avatar size="50" :src="$store.state.user.icon" round></n-avatar>
|
||||
</div>
|
||||
<div class="col-span-2 text-xs grid grid-cols-1 items-center" style="">
|
||||
<span>昵称:    {{ $store.state.user.nickname }}</span>
|
||||
<span>账户:    {{ $store.state.user.username }}</span>
|
||||
<span>邮箱:    {{ $store.state.user.email }}</span>
|
||||
</div>
|
||||
<div class="">123</div>
|
||||
</div>
|
||||
<hr class="mt-10" style="border:none;border-top:1px solid #777;">
|
||||
</div>
|
||||
</div>
|
||||
<hr style="border:none;border-top:2px solid #777;">
|
||||
<div style="height: 48px">
|
||||
<div @click="$store.commit('user/logout')"
|
||||
class="w-full h-full flex justify-center items-center cursor-pointer transition duration-500 ease-in-out transform hover:scale-125">
|
||||
<one-icon :color="IsDark?'#eee': '#333'" class="inline-block" style="font-size: 24px;">
|
||||
logout
|
||||
</one-icon>
|
||||
<div>
|
||||
退出登录
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</base_frame>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import base_frame from './frame.vue'
|
||||
import {IsDark} from '../../theme'
|
||||
import {ref} from "vue";
|
||||
|
||||
let shown = ref(false)
|
||||
|
||||
function asd(e) {
|
||||
console.log([e, shown.value])
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,82 @@
|
||||
<template>
|
||||
<div>
|
||||
<div @click="setValue(true)">
|
||||
<slot>
|
||||
</slot>
|
||||
</div>
|
||||
<div @click.self="setValue(false)" class="core" style="height: 100vh;width: 100vw;" v-if="props.modelValue">
|
||||
<div style="height: 100%; width: 300px" class="core-right">
|
||||
<transition appear enter-active-class="animate__slideInRight">
|
||||
<div class="right-title animate__animated animate__faster">
|
||||
<slot name="title"></slot>
|
||||
<div class="flex items-center float-right h-full px-1">
|
||||
<one-icon @click="setValue(false)" color="#fff" style="font-size: 24px">close</one-icon>
|
||||
</div>
|
||||
</div>
|
||||
</transition>
|
||||
<div class="right-main">
|
||||
<transition appear enter-active-class="animate__slideInDown">
|
||||
<div class="right-main-core animate__animated animate__faster"
|
||||
:style="{'background': props.isDark ? '#222': '#eee'}">
|
||||
<slot name="main"></slot>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { defineProps, defineEmits, watch} from "vue";
|
||||
|
||||
let emits = defineEmits<{
|
||||
(e: 'update:modelValue', v: boolean): void
|
||||
}>()
|
||||
let props = defineProps<{
|
||||
isDark: boolean,
|
||||
modelValue: boolean
|
||||
}>()
|
||||
|
||||
function setValue(b: boolean) {
|
||||
emits('update:modelValue', b)
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.core {
|
||||
position: fixed;
|
||||
left: 0;
|
||||
top: 0;
|
||||
background: rgba(0, 0, 0, 0.4);
|
||||
z-index: 100;
|
||||
}
|
||||
|
||||
.core-right {
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 0;
|
||||
}
|
||||
|
||||
.right-main {
|
||||
width: 100%;
|
||||
height: calc(100% - 50px);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.right-main-core {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
-webkit-animation-delay: 0.4s;
|
||||
animation-delay: 0.4s;
|
||||
--animate-duration: 400ms;
|
||||
}
|
||||
|
||||
.right-title {
|
||||
width: 100%;
|
||||
height: 50px;
|
||||
line-height: 50px;
|
||||
background: linear-gradient(90deg, #f74d22, #fa9243);
|
||||
}
|
||||
</style>
|
||||
@@ -0,0 +1,3 @@
|
||||
import avatar from './avatar.vue'
|
||||
|
||||
export default avatar
|
||||
@@ -0,0 +1,10 @@
|
||||
<template>
|
||||
<div></div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,71 @@
|
||||
<template>
|
||||
<div @click="handleFullscreen">
|
||||
<one-icon>{{ props.modelValue ? 'fullscreen-exit' : 'fullscreen' }}</one-icon>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
|
||||
import {defineEmits, onMounted, defineProps} from "vue";
|
||||
|
||||
let emit = defineEmits<{
|
||||
(e: 'update:modelValue', v: boolean): void
|
||||
}>()
|
||||
|
||||
let props = defineProps<{
|
||||
modelValue: boolean
|
||||
}>()
|
||||
|
||||
function handleFullscreen() {
|
||||
let main = document.body
|
||||
if (props.modelValue) {
|
||||
if (document.exitFullscreen) {
|
||||
document.exitFullscreen()
|
||||
} else if (document.mozCancelFullScreen) {
|
||||
document.mozCancelFullScreen()
|
||||
} else if (document.webkitCancelFullScreen) {
|
||||
document.webkitCancelFullScreen()
|
||||
} else if (document.msExitFullscreen) {
|
||||
document.msExitFullscreen()
|
||||
}
|
||||
} else {
|
||||
if (main.requestFullscreen) {
|
||||
main.requestFullscreen()
|
||||
} else if (main.mozRequestFullScreen) {
|
||||
main.mozRequestFullScreen()
|
||||
} else if (main.webkitRequestFullScreen) {
|
||||
main.webkitRequestFullScreen()
|
||||
} else if (main.msRequestFullscreen) {
|
||||
main.msRequestFullscreen()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
let isFullscreen =
|
||||
document.fullscreenElement ||
|
||||
document.mozFullScreenElement ||
|
||||
document.webkitFullscreenElement ||
|
||||
document.fullScreen ||
|
||||
document.mozFullScreen ||
|
||||
document.webkitIsFullScreen
|
||||
isFullscreen = !!isFullscreen
|
||||
document.addEventListener('fullscreenchange', () => {
|
||||
emit('update:modelValue', !props.modelValue)
|
||||
})
|
||||
document.addEventListener('mozfullscreenchange', () => {
|
||||
emit('update:modelValue', !props.modelValue)
|
||||
})
|
||||
document.addEventListener('webkitfullscreenchange', () => {
|
||||
emit('update:modelValue', !props.modelValue)
|
||||
})
|
||||
document.addEventListener('msfullscreenchange', () => {
|
||||
emit('update:modelValue', !props.modelValue)
|
||||
})
|
||||
emit('update:modelValue', isFullscreen)
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style>
|
||||
</style>
|
||||
@@ -0,0 +1,2 @@
|
||||
import fullscreen from './fullscreen.vue'
|
||||
export default fullscreen
|
||||
@@ -27,6 +27,9 @@ const util = {
|
||||
document.cookie =
|
||||
name + '=' + escape(value) + ';expires=' + exp.toLocaleString()
|
||||
},
|
||||
getToken() {
|
||||
return localStorage.auth_token
|
||||
},
|
||||
checkLogin() {
|
||||
// return parseInt(this.getCookie('stat')) === 1
|
||||
return Boolean(localStorage.auth_token)
|
||||
|
||||
+3
-1
@@ -6,12 +6,14 @@ import OneIcon from '@veypi/one-icon'
|
||||
import naive from 'naive-ui'
|
||||
import './index.css'
|
||||
import {Api} from './api'
|
||||
import './assets/icon.js'
|
||||
import 'animate.css'
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
app.use(Api)
|
||||
app.use(naive)
|
||||
app.use(OneIcon, {href: './icon.js'})
|
||||
app.use(OneIcon)
|
||||
app.use(router)
|
||||
app.use(store, key)
|
||||
app.mount('#app')
|
||||
|
||||
+15
-2
@@ -22,12 +22,25 @@ const router = createRouter({
|
||||
component: () => import('../views/home.vue')
|
||||
},
|
||||
{
|
||||
path: '/app',
|
||||
path: '/app/:uuid?',
|
||||
name: 'app',
|
||||
meta: {
|
||||
requiresAuth: true,
|
||||
},
|
||||
component: () => import('../views/demo.vue')
|
||||
component: () => import('../views/app.vue')
|
||||
},
|
||||
{
|
||||
path: '/user/setting',
|
||||
name: 'user_setting',
|
||||
meta: {
|
||||
requiresAuth: true
|
||||
},
|
||||
component: () => import('../views/user_setting.vue')
|
||||
},
|
||||
{
|
||||
path: '/about',
|
||||
name: 'about',
|
||||
component: () => import('../views/about.vue')
|
||||
},
|
||||
{
|
||||
path: '/wx',
|
||||
|
||||
+16
-29
@@ -1,58 +1,45 @@
|
||||
import {InjectionKey} from 'vue'
|
||||
import {createStore, useStore as baseUseStore, Store} from 'vuex'
|
||||
import api from "../api";
|
||||
import router from "../router";
|
||||
import {darkTheme} from 'naive-ui'
|
||||
import {User, UserState} from './user'
|
||||
|
||||
export interface State {
|
||||
export interface State extends Object {
|
||||
oauuid: string
|
||||
user: object
|
||||
theme: string
|
||||
|
||||
user: UserState
|
||||
apps: []
|
||||
}
|
||||
|
||||
export const key: InjectionKey<Store<State>> = Symbol()
|
||||
|
||||
export const store = createStore<State>({
|
||||
modules: {
|
||||
user: User
|
||||
},
|
||||
// @ts-ignore
|
||||
state: {
|
||||
theme: 'light',
|
||||
oauuid: '',
|
||||
user: {}
|
||||
},
|
||||
getters: {
|
||||
IsDark(state: any) {
|
||||
return state.theme === 'dark'
|
||||
},
|
||||
GetTheme(state: any, getters) {
|
||||
return getters.IsDark ? darkTheme : null
|
||||
}
|
||||
apps: []
|
||||
},
|
||||
getters: {},
|
||||
mutations: {
|
||||
setOA(state: any, data: any) {
|
||||
state.oauuid = data.uuid
|
||||
},
|
||||
setTheme(state: any, t: string) {
|
||||
state.theme = t
|
||||
setApps(state: State, data: any) {
|
||||
state.apps = data
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
changeTheme(context) {
|
||||
if (context.getters.IsDark) {
|
||||
context.commit('setTheme', 'light')
|
||||
} else {
|
||||
context.commit('setTheme', 'dark')
|
||||
}
|
||||
},
|
||||
fetchSelf({commit}) {
|
||||
api.app.self().Start(d => {
|
||||
commit('setOA', d)
|
||||
})
|
||||
},
|
||||
handleLogout() {
|
||||
localStorage.removeItem('auth_token')
|
||||
router.push({name: 'login'})
|
||||
fetchApps({commit}) {
|
||||
api.app.list().Start(e => {
|
||||
commit('setApps', e)
|
||||
})
|
||||
}
|
||||
|
||||
}
|
||||
})
|
||||
|
||||
|
||||
@@ -0,0 +1,73 @@
|
||||
import {Module} from "vuex";
|
||||
import api from "../api";
|
||||
import util from '../libs/util'
|
||||
import {Base64} from 'js-base64'
|
||||
import {State} from './index'
|
||||
import router from "../router";
|
||||
|
||||
export interface UserState {
|
||||
id: number
|
||||
username: string
|
||||
nickname: string
|
||||
phone: string
|
||||
icon: string
|
||||
email: string
|
||||
ready: boolean
|
||||
auth: [auth?]
|
||||
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
interface auth {
|
||||
rid: string
|
||||
ruid: string
|
||||
level: number
|
||||
}
|
||||
|
||||
export const User: Module<UserState, State> = {
|
||||
namespaced: true,
|
||||
state: {
|
||||
id: 0,
|
||||
username: '',
|
||||
nickname: '',
|
||||
phone: '',
|
||||
icon: '',
|
||||
email: '',
|
||||
auth: [],
|
||||
ready: false
|
||||
},
|
||||
mutations: {
|
||||
setBase(state: UserState, data: any) {
|
||||
state.id = data.id
|
||||
state.icon = data.icon
|
||||
state.username = data.username
|
||||
state.nickname = data.nickname
|
||||
state.phone = data.phone
|
||||
state.email = data.email
|
||||
state.ready = true
|
||||
},
|
||||
setAuth(state: UserState, data: any) {
|
||||
state.auth = data
|
||||
},
|
||||
logout(state: UserState) {
|
||||
state.ready = false
|
||||
localStorage.removeItem('auth_token')
|
||||
router.push({name: 'login'})
|
||||
}
|
||||
},
|
||||
actions: {
|
||||
fetchUserData(context) {
|
||||
let token = util.getToken()?.split('.');
|
||||
if (!token || token.length !== 3) {
|
||||
return false
|
||||
}
|
||||
let data = JSON.parse(Base64.decode(token[1]))
|
||||
if (data.id > 0) {
|
||||
context.commit('setAuth', data.auth)
|
||||
api.user.get(data.id).Start(e => {
|
||||
context.commit('setBase', e)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,72 @@
|
||||
import {darkTheme} from 'naive-ui/lib/themes'
|
||||
import {BuiltInGlobalTheme} from 'naive-ui/lib/themes/interface'
|
||||
import {lightTheme} from 'naive-ui/lib/themes/light'
|
||||
import {ref} from 'vue'
|
||||
import {useOsTheme, GlobalThemeOverrides} from 'naive-ui'
|
||||
|
||||
interface builtIn extends BuiltInGlobalTheme {
|
||||
overrides: GlobalThemeOverrides
|
||||
me: {
|
||||
lightBox: string,
|
||||
lightBoxShadow: string
|
||||
}
|
||||
}
|
||||
|
||||
let light = lightTheme as builtIn
|
||||
let dark = darkTheme as builtIn
|
||||
let intputNone = {
|
||||
color: 'url(0) no-repeat',
|
||||
colorFocus: 'url(0) no-repeat',
|
||||
colorFocusWarning: 'url(0) no-repeat',
|
||||
colorFocusError: 'url(0) no-repeat'
|
||||
}
|
||||
light.overrides = {
|
||||
Input: Object.assign({}, intputNone)
|
||||
}
|
||||
dark.overrides = {
|
||||
Input: Object.assign({
|
||||
border: '1px solid #aaa'
|
||||
}, intputNone)
|
||||
}
|
||||
light.common.cardColor = '#f4f4f4'
|
||||
light.common.bodyColor = '#eee'
|
||||
dark.common.bodyColor = '#2e2e2e'
|
||||
light.me = {
|
||||
lightBox: '#f4f4f4',
|
||||
lightBoxShadow: '18px 18px 36px #c6c6c6, -18px -18px 36px #fff'
|
||||
}
|
||||
|
||||
dark.me = {
|
||||
lightBox: '#2e2e2e',
|
||||
lightBoxShadow: '21px 21px 42px #272727, -21px -21px 42px #353535'
|
||||
}
|
||||
export const OsThemeRef = useOsTheme()
|
||||
|
||||
let theme = 'light'
|
||||
|
||||
export let Theme = ref(light)
|
||||
|
||||
export let IsDark = ref(false)
|
||||
|
||||
function change(t: string) {
|
||||
if (t === 'dark') {
|
||||
theme = 'dark'
|
||||
Theme.value = dark
|
||||
} else {
|
||||
theme = 'light'
|
||||
Theme.value = light
|
||||
}
|
||||
IsDark.value = theme === 'dark'
|
||||
}
|
||||
|
||||
export function ChangeTheme() {
|
||||
if (IsDark.value) {
|
||||
change('light')
|
||||
} else {
|
||||
change('dark')
|
||||
}
|
||||
}
|
||||
|
||||
if (OsThemeRef.value === 'dark') {
|
||||
change('dark')
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
<template>
|
||||
<div>
|
||||
about
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
@@ -0,0 +1,28 @@
|
||||
<template>
|
||||
<div>
|
||||
{{ uuid }}
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {useRoute, useRouter} from "vue-router";
|
||||
import {computed, onMounted} from "vue";
|
||||
import api from "../api";
|
||||
|
||||
let route = useRoute()
|
||||
let router = useRouter()
|
||||
let uuid = computed(() => route.params.uuid)
|
||||
onMounted(() => {
|
||||
if (uuid.value === '') {
|
||||
router.push({name: '404', params: {path: route.path}})
|
||||
return
|
||||
}
|
||||
api.app.get(uuid.value as string).Start(e => {
|
||||
console.log(e)
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
+12
-3
@@ -1,11 +1,18 @@
|
||||
<template>
|
||||
<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 class="flex items-center justify-center" v-for="(item, k) in apps" :key="k">
|
||||
<AppCard :core="item"></AppCard>
|
||||
</div>
|
||||
<div class="flex items-center justify-center" v-for="(item) in '1234567890'" :key="item">
|
||||
<AppCard :core="{}"></AppCard>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {ref} from "vue";
|
||||
import {onMounted, ref} from "vue";
|
||||
import api from "../api";
|
||||
import AppCard from '../components/app.vue'
|
||||
|
||||
let apps = ref([])
|
||||
|
||||
@@ -15,7 +22,9 @@ function getApps() {
|
||||
})
|
||||
}
|
||||
|
||||
getApps()
|
||||
onMounted(() => {
|
||||
getApps()
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
|
||||
+73
-36
@@ -1,61 +1,98 @@
|
||||
<template>
|
||||
<div class="flex items-center justify-center">
|
||||
<div class="p-3" style="">
|
||||
<n-form ref="formRef" label-placement="left">
|
||||
<n-form-item required label="username" :validation-status="rules.username[0]" :feedback="rules.username[1]">
|
||||
<n-input v-model:value="data.username"></n-input>
|
||||
<div
|
||||
:style="{background:Theme.me.lightBox, 'box-shadow': Theme.me.lightBoxShadow}"
|
||||
class="px-10 pb-9 pt-28 rounded-xl w-96">
|
||||
<n-form label-width="70px" label-align="left" :model="data" ref="form_ref" label-placement="left" :rules="rules">
|
||||
<n-form-item required label="用户名" path="username">
|
||||
<n-input @keydown.enter="divs[1].focus()" :ref="el => {if (el)divs[0]=el}"
|
||||
v-model:value="data.username"></n-input>
|
||||
</n-form-item>
|
||||
<n-form-item required label="username" :validation-status="rules.username[0]" :feedback="rules.username[1]">
|
||||
<n-input v-model:value="data.username"></n-input>
|
||||
<n-form-item required label="密码" path="password">
|
||||
<n-input @keydown.enter="login" :ref="el => {if (el) divs[1]=el}" v-model:value="data.password"
|
||||
type="password"></n-input>
|
||||
</n-form-item>
|
||||
<n-button @click="login">登录</n-button>
|
||||
<div class="flex justify-around mt-4">
|
||||
<n-button @click="login">登录</n-button>
|
||||
<n-button @click="router.push({name:'register'})">注册</n-button>
|
||||
</div>
|
||||
</n-form>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {computed, ref} from "vue";
|
||||
import {computed, onMounted, ref} from "vue";
|
||||
import {Theme} from "../theme";
|
||||
import {useMessage} from 'naive-ui'
|
||||
import api from "../api"
|
||||
import {useRoute, useRouter} from "vue-router";
|
||||
import {store} from "../store";
|
||||
|
||||
let formRef = ref(null)
|
||||
let msg = useMessage()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
const divs = ref([])
|
||||
let form_ref = ref(null)
|
||||
let data = ref({
|
||||
username: null,
|
||||
password: null
|
||||
username: '',
|
||||
password: ''
|
||||
})
|
||||
let ruleInline = {
|
||||
let rules = {
|
||||
username: [
|
||||
(v: string) => !!v || 'required',
|
||||
(v: string) => (v && v.length >= 3 && v.length <= 16) || '长度要求3~16'
|
||||
],
|
||||
password: [
|
||||
(v: string) => !!v || 'required',
|
||||
(v: string) => (v && v.length >= 6 && v.length <= 16) || '长度要求6~16'
|
||||
]
|
||||
}
|
||||
|
||||
function check(rs: [], v: any) {
|
||||
for (let r of rs) {
|
||||
let res = r(v)
|
||||
if (res !== true) {
|
||||
return ['error', res]
|
||||
{
|
||||
required: true,
|
||||
validator(r: any, v: any) {
|
||||
return (v && v.length >= 3 && v.length <= 16) || new Error('长度要求3~16')
|
||||
},
|
||||
trigger: ['input', 'blur']
|
||||
}
|
||||
}
|
||||
return ['', '']
|
||||
],
|
||||
password: [{
|
||||
required: true,
|
||||
validator(r: any, v: any) {
|
||||
return (v && v.length >= 6 && v.length <= 16) || new Error('长度要求6~16')
|
||||
}
|
||||
}]
|
||||
}
|
||||
|
||||
let rules = ref({
|
||||
username: computed(() => {
|
||||
return check(ruleInline.username, data.value.username)
|
||||
})
|
||||
let uuid = computed(() => {
|
||||
return route.params.uuid || store.state.oauuid
|
||||
})
|
||||
|
||||
function login() {
|
||||
formRef.value.validate(e => {
|
||||
console.log(e)
|
||||
// @ts-ignore
|
||||
form_ref.value.validate((e:any) => {
|
||||
if (!e) {
|
||||
api.user.login(data.value.username, data.value.password, uuid.value as string).Start((url: string) => {
|
||||
msg.success('登录成功')
|
||||
store.dispatch('user/fetchUserData')
|
||||
let target = url
|
||||
if (route.query.redirect) {
|
||||
target = route.query.redirect as string
|
||||
}
|
||||
if (target && target.startsWith('http')) {
|
||||
window.location.href = target
|
||||
} else if (target) {
|
||||
router.push(target)
|
||||
} else {
|
||||
router.push({name: 'home'})
|
||||
}
|
||||
}, e => {
|
||||
console.log(e)
|
||||
msg.warning('登录失败:' + e)
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
if (divs.value[0]) {
|
||||
// @ts-ignore
|
||||
divs.value[0].focus()
|
||||
}
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
</style>
|
||||
|
||||
@@ -0,0 +1,96 @@
|
||||
<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>
|
||||
<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="头像">
|
||||
<n-upload
|
||||
action=""
|
||||
:headers="{'': ''}"
|
||||
:data="{}"
|
||||
>
|
||||
<n-avatar size="large" round :src="user.icon">
|
||||
</n-avatar>
|
||||
</n-upload>
|
||||
</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>
|
||||
</div>
|
||||
</transition>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import {ref, computed} from "vue";
|
||||
import {IsDark} from "../theme";
|
||||
import {useStore} from "../store";
|
||||
import api from "../api";
|
||||
import {useMessage} from "naive-ui";
|
||||
|
||||
let msg = useMessage()
|
||||
let store = useStore()
|
||||
|
||||
let ifInfo = ref(true)
|
||||
let user = ref({
|
||||
username: store.state.user.username,
|
||||
nickname: store.state.user.nickname,
|
||||
icon: store.state.user.icon,
|
||||
email: store.state.user.email,
|
||||
phone: store.state.user.phone,
|
||||
})
|
||||
let emailOptions = computed(() => {
|
||||
return ['@gmail.com', '@163.com', '@qq.com'].map((suffix) => {
|
||||
const prefix = user.value.email.split('@')[0]
|
||||
return {
|
||||
label: prefix + suffix,
|
||||
value: prefix + suffix
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
function update(key: string) {
|
||||
// @ts-ignore
|
||||
let v = user.value[key]
|
||||
if (v === store.state.user[key]) {
|
||||
return
|
||||
}
|
||||
api.user.update(store.state.user.id, {[key]: v}).Start(e => {
|
||||
msg.success('更新成功')
|
||||
store.state.user[key] = v
|
||||
}, e => {
|
||||
msg.error('更新失败: ' + e.err)
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
</style>
|
||||
Vendored
+14
@@ -0,0 +1,14 @@
|
||||
import {ComponentCustomProperties} from 'vue'
|
||||
import {Store} from 'vuex'
|
||||
import {State as root} from './store'
|
||||
|
||||
declare module '@vue/runtime-core' {
|
||||
// 声明自己的 store state
|
||||
interface State extends root {
|
||||
}
|
||||
|
||||
// 为 `this.$store` 提供类型声明
|
||||
interface ComponentCustomProperties {
|
||||
$store: Store<State>
|
||||
}
|
||||
}
|
||||
+30
-13
@@ -1,19 +1,36 @@
|
||||
import { defineConfig } from 'vite'
|
||||
import {defineConfig} from 'vite'
|
||||
import vue from '@vitejs/plugin-vue'
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
plugins: [vue()],
|
||||
server: {
|
||||
// host: '0.0.0.0',
|
||||
host: '127.0.0.1',
|
||||
port: 8080,
|
||||
proxy: {
|
||||
'/api': 'http://127.0.0.1:4001/'
|
||||
plugins: [vue()],
|
||||
server: {
|
||||
// host: '0.0.0.0',
|
||||
host: '127.0.0.1',
|
||||
port: 8080,
|
||||
proxy: {
|
||||
'/api': {
|
||||
target: 'http://127.0.0.1:4001/',
|
||||
changeOrigin: true,
|
||||
ws: true
|
||||
},
|
||||
'/media': {
|
||||
target: 'http://127.0.0.1:4001/',
|
||||
changeOrigin: true,
|
||||
ws: true
|
||||
}
|
||||
}
|
||||
},
|
||||
build: {
|
||||
outDir: '../sub/static/',
|
||||
assetsDir: './',
|
||||
rollupOptions: {
|
||||
output: {
|
||||
// 重点在这里哦
|
||||
entryFileNames: `static/[name].[hash].js`,
|
||||
chunkFileNames: `static/[name].[hash].js`,
|
||||
assetFileNames: `static/[name].[hash].[ext]`
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
build: {
|
||||
outDir: '../build/static/',
|
||||
assetsDir: 'assets'
|
||||
}
|
||||
})
|
||||
|
||||
+9
-4
@@ -216,10 +216,10 @@
|
||||
dependencies:
|
||||
"@types/yargs-parser" "*"
|
||||
|
||||
"@veypi/one-icon@2":
|
||||
version "2.0.0"
|
||||
resolved "https://registry.yarnpkg.com/@veypi/one-icon/-/one-icon-2.0.0.tgz#15262e87644903d90a2124fbc9c95fbb6bb6bcb8"
|
||||
integrity sha512-zcUx3YzTIRiZe5XkJRZyKDgVzQThEWyZSCpQTreeYtKEObCl4B0Oa6CFYo4IToGD6uQmReM0R6oNPg5mhPjc2A==
|
||||
"@veypi/one-icon@2.0.5":
|
||||
version "2.0.5"
|
||||
resolved "https://registry.yarnpkg.com/@veypi/one-icon/-/one-icon-2.0.5.tgz#1083be30fc3bbb89aaf19501b00110d3ae9b1c77"
|
||||
integrity sha512-THnQh1zbH+glwDBLjP7rtcF3UtTQnzsxnUQEtUjAjv9Eo6rVSa1u4RsFA8CMDpoVcQiTMhqgRalbfjovdr11wg==
|
||||
dependencies:
|
||||
vue "^3.2.20"
|
||||
|
||||
@@ -396,6 +396,11 @@ acorn@^7.0.0, acorn@^7.1.1:
|
||||
resolved "https://registry.yarnpkg.com/acorn/-/acorn-7.4.1.tgz#feaed255973d2e77555b83dbc08851a6c63520fa"
|
||||
integrity sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==
|
||||
|
||||
animate.css@^4.1.1:
|
||||
version "4.1.1"
|
||||
resolved "https://registry.yarnpkg.com/animate.css/-/animate.css-4.1.1.tgz#614ec5a81131d7e4dc362a58143f7406abd68075"
|
||||
integrity sha512-+mRmCTv6SbCmtYJCN4faJMNFVNN5EuCTTprDTAo7YzIGji2KADmakjVA3+8mVDkZ2Bf09vayB35lSQIex2+QaQ==
|
||||
|
||||
ansi-regex@^5.0.0:
|
||||
version "5.0.1"
|
||||
resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304"
|
||||
|
||||
Reference in New Issue
Block a user