mirror of https://github.com/veypi/OneAuth.git
home
parent
d7aea82ced
commit
bc3f5e0b0c
@ -0,0 +1,69 @@
|
|||||||
|
package token
|
||||||
|
|
||||||
|
import (
|
||||||
|
"OneAuth/cfg"
|
||||||
|
"OneAuth/libs/app"
|
||||||
|
"OneAuth/libs/base"
|
||||||
|
"OneAuth/libs/oerr"
|
||||||
|
"OneAuth/libs/token"
|
||||||
|
"OneAuth/models"
|
||||||
|
"errors"
|
||||||
|
"github.com/veypi/OneBD"
|
||||||
|
"github.com/veypi/OneBD/rfc"
|
||||||
|
"gorm.io/gorm"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Router(r OneBD.Router) {
|
||||||
|
p := OneBD.NewHandlerPool(func() OneBD.Handler {
|
||||||
|
return &tokenHandler{}
|
||||||
|
})
|
||||||
|
r.Set("/:uuid", p, rfc.MethodGet)
|
||||||
|
}
|
||||||
|
|
||||||
|
type tokenHandler struct {
|
||||||
|
base.ApiHandler
|
||||||
|
}
|
||||||
|
|
||||||
|
func (h *tokenHandler) Get() (interface{}, error) {
|
||||||
|
uuid := h.Meta().Params("uuid")
|
||||||
|
if uuid == "" {
|
||||||
|
return nil, oerr.ApiArgsMissing.AttachStr("uuid")
|
||||||
|
}
|
||||||
|
a := &models.App{}
|
||||||
|
a.UUID = uuid
|
||||||
|
err := cfg.DB().Where("uuid = ?", uuid).First(a).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
au := &models.AppUser{
|
||||||
|
UserID: h.Payload.ID,
|
||||||
|
AppID: a.ID,
|
||||||
|
}
|
||||||
|
err = cfg.DB().Where(au).First(au).Error
|
||||||
|
if err != nil {
|
||||||
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
if a.EnableRegister {
|
||||||
|
err = cfg.DB().Transaction(func(tx *gorm.DB) error {
|
||||||
|
return app.AddUser(cfg.DB(), au.AppID, au.UserID, a.InitRoleID, models.AUOK)
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
au.Status = models.AUOK
|
||||||
|
} else {
|
||||||
|
return nil, oerr.AppNotJoin.AttachStr(a.Name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, oerr.DBErr.Attach(err)
|
||||||
|
}
|
||||||
|
if au.Status != models.AUOK {
|
||||||
|
return nil, oerr.NoAuth.AttachStr(string(au.Status))
|
||||||
|
}
|
||||||
|
u := &models.User{}
|
||||||
|
err = cfg.DB().Preload("Auths").Preload("Roles.Auths").Where("id = ?", h.Payload.ID).First(u).Error
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
t, err := token.GetToken(u, a.ID, a.Key)
|
||||||
|
return t, err
|
||||||
|
}
|
File diff suppressed because one or more lines are too long
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
|
@ -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>
|
@ -1,61 +1,98 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="flex items-center justify-center">
|
<div class="flex items-center justify-center">
|
||||||
<div class="p-3" style="">
|
<div
|
||||||
<n-form ref="formRef" label-placement="left">
|
:style="{background:Theme.me.lightBox, 'box-shadow': Theme.me.lightBoxShadow}"
|
||||||
<n-form-item required label="username" :validation-status="rules.username[0]" :feedback="rules.username[1]">
|
class="px-10 pb-9 pt-28 rounded-xl w-96">
|
||||||
<n-input v-model:value="data.username"></n-input>
|
<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>
|
||||||
<n-form-item required label="username" :validation-status="rules.username[0]" :feedback="rules.username[1]">
|
<n-form-item required label="密码" path="password">
|
||||||
<n-input v-model:value="data.username"></n-input>
|
<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-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>
|
</n-form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script lang="ts" setup>
|
<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 msg = useMessage()
|
||||||
|
const route = useRoute()
|
||||||
|
const router = useRouter()
|
||||||
|
|
||||||
let formRef = ref(null)
|
const divs = ref([])
|
||||||
|
let form_ref = ref(null)
|
||||||
let data = ref({
|
let data = ref({
|
||||||
username: null,
|
username: '',
|
||||||
password: null
|
password: ''
|
||||||
})
|
})
|
||||||
let ruleInline = {
|
let rules = {
|
||||||
username: [
|
username: [
|
||||||
(v: string) => !!v || 'required',
|
{
|
||||||
(v: string) => (v && v.length >= 3 && v.length <= 16) || '长度要求3~16'
|
required: true,
|
||||||
|
validator(r: any, v: any) {
|
||||||
|
return (v && v.length >= 3 && v.length <= 16) || new Error('长度要求3~16')
|
||||||
|
},
|
||||||
|
trigger: ['input', 'blur']
|
||||||
|
}
|
||||||
],
|
],
|
||||||
password: [
|
password: [{
|
||||||
(v: string) => !!v || 'required',
|
required: true,
|
||||||
(v: string) => (v && v.length >= 6 && v.length <= 16) || '长度要求6~16'
|
validator(r: any, v: any) {
|
||||||
]
|
return (v && v.length >= 6 && v.length <= 16) || new Error('长度要求6~16')
|
||||||
}
|
|
||||||
|
|
||||||
function check(rs: [], v: any) {
|
|
||||||
for (let r of rs) {
|
|
||||||
let res = r(v)
|
|
||||||
if (res !== true) {
|
|
||||||
return ['error', res]
|
|
||||||
}
|
}
|
||||||
}
|
}]
|
||||||
return ['', '']
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let rules = ref({
|
let uuid = computed(() => {
|
||||||
username: computed(() => {
|
return route.params.uuid || store.state.oauuid
|
||||||
return check(ruleInline.username, data.value.username)
|
|
||||||
})
|
|
||||||
})
|
})
|
||||||
|
|
||||||
function login() {
|
function login() {
|
||||||
formRef.value.validate(e => {
|
// @ts-ignore
|
||||||
console.log(e)
|
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>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
|
|
||||||
</style>
|
</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>
|
@ -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>
|
||||||
|
}
|
||||||
|
}
|
@ -1,19 +1,36 @@
|
|||||||
import { defineConfig } from 'vite'
|
import {defineConfig} from 'vite'
|
||||||
import vue from '@vitejs/plugin-vue'
|
import vue from '@vitejs/plugin-vue'
|
||||||
|
|
||||||
// https://vitejs.dev/config/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [vue()],
|
plugins: [vue()],
|
||||||
server: {
|
server: {
|
||||||
// host: '0.0.0.0',
|
// host: '0.0.0.0',
|
||||||
host: '127.0.0.1',
|
host: '127.0.0.1',
|
||||||
port: 8080,
|
port: 8080,
|
||||||
proxy: {
|
proxy: {
|
||||||
'/api': 'http://127.0.0.1:4001/'
|
'/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'
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
@ -1 +1,17 @@
|
|||||||
<!DOCTYPE html><html lang="en"><head><meta charset="utf-8"><meta http-equiv="X-UA-Compatible" content="IE=edge"><meta name="viewport" content="width=device-width,initial-scale=1"><link rel="icon" href="/favicon.ico"><title>oaf</title><link rel="stylesheet" href="https://fonts.googleapis.com/css?family=Roboto:100,300,400,500,700,900"><link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/@mdi/font@latest/css/materialdesignicons.min.css"><link href="/css/chunk-6cfd0404.487f8d50.css" rel="prefetch"><link href="/js/about.a3055c06.js" rel="prefetch"><link href="/js/chunk-6cfd0404.65d5baaf.js" rel="prefetch"><link href="/app.b131f8d0f62acd99ab8e.js" rel="preload" as="script"><link href="/css/app.dafd5329.css" rel="preload" as="style"><link href="/css/chunk-vendors.dfe6062e.css" rel="preload" as="style"><link href="/js/chunk-vendors.83bac771.js" rel="preload" as="script"><link href="/css/chunk-vendors.dfe6062e.css" rel="stylesheet"><link href="/css/app.dafd5329.css" rel="stylesheet"></head><body><noscript><strong>We're sorry but oaf doesn't work properly without JavaScript enabled. Please enable it to continue.</strong></noscript><div id="app"></div><script src="/js/chunk-vendors.83bac771.js"></script><script src="/app.b131f8d0f62acd99ab8e.js"></script></body></html>
|
<!DOCTYPE html>
|
||||||
|
<html lang="en">
|
||||||
|
<head>
|
||||||
|
<meta charset="UTF-8" />
|
||||||
|
<link rel="icon" href="/favicon.ico" />
|
||||||
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||||
|
<title>Vite App</title>
|
||||||
|
<script type="module" crossorigin src="/static/index.dddecd43.js"></script>
|
||||||
|
<link rel="modulepreload" href="/static/vendor.ba3bd51d.js">
|
||||||
|
<link rel="stylesheet" href="/static/vendor.3a295b6b.css">
|
||||||
|
<link rel="stylesheet" href="/static/index.c49db26f.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div id="app"></div>
|
||||||
|
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
Loading…
Reference in New Issue