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>
|
||||
<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 msg = useMessage()
|
||||
const route = useRoute()
|
||||
const router = useRouter()
|
||||
|
||||
let formRef = ref(null)
|
||||
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'
|
||||
{
|
||||
required: true,
|
||||
validator(r: any, v: any) {
|
||||
return (v && v.length >= 3 && v.length <= 16) || new Error('长度要求3~16')
|
||||
},
|
||||
trigger: ['input', 'blur']
|
||||
}
|
||||
],
|
||||
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]
|
||||
password: [{
|
||||
required: true,
|
||||
validator(r: any, v: any) {
|
||||
return (v && v.length >= 6 && v.length <= 16) || new Error('长度要求6~16')
|
||||
}
|
||||
}
|
||||
return ['', '']
|
||||
}]
|
||||
}
|
||||
|
||||
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>
|
@ -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'
|
||||
|
||||
// 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'
|
||||
}
|
||||
})
|
||||
|
@ -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