feat: update token api

v3
veypi 1 month ago
parent 40d27cb9e4
commit 3b65aea4ad

@ -8,12 +8,14 @@
package api package api
import ( import (
"github.com/veypi/OneBD/rest"
"oa/api/access" "oa/api/access"
"oa/api/app" "oa/api/app"
"oa/api/role" "oa/api/role"
"oa/api/token" "oa/api/token"
"oa/api/user" "oa/api/user"
"oa/cfg"
"github.com/veypi/OneBD/rest"
) )
func Use(r rest.Router) { func Use(r rest.Router) {
@ -22,4 +24,11 @@ func Use(r rest.Router) {
role.Use(r.SubRouter("role")) role.Use(r.SubRouter("role"))
user.Use(r.SubRouter("user")) user.Use(r.SubRouter("user"))
token.Use(r.SubRouter("token")) token.Use(r.SubRouter("token"))
r.Get("", baseInfo)
}
func baseInfo(x *rest.X) (any, error) {
return map[string]any{
"id": cfg.Config.ID,
}, nil
} }

@ -56,33 +56,33 @@ func tokenPost(x *rest.X) (any, error) {
claim := &auth.Claims{} claim := &auth.Claims{}
claim.IssuedAt = jwt.NewNumericDate(time.Now()) claim.IssuedAt = jwt.NewNumericDate(time.Now())
claim.Issuer = "oa" claim.Issuer = "oa"
if opts.Token != nil { if opts.Refresh != nil {
// for other app redirect // for other app redirect
oldClaim, err := auth.ParseJwt(*opts.Token) refresh, err := auth.ParseJwt(*opts.Refresh)
if err != nil { if err != nil || refresh.ID == "" {
return nil, err return nil, err
} }
err = cfg.DB().Where("id = ?", oldClaim.ID).First(data).Error err = cfg.DB().Where("id = ?", refresh.ID).First(data).Error
if err != nil { if err != nil {
return nil, err return nil, err
} }
if oldClaim.AID == aid { if refresh.AID == aid {
// refresh token // refresh token
claim.AID = oldClaim.AID claim.AID = refresh.AID
claim.UID = oldClaim.UID claim.UID = refresh.UID
claim.Name = oldClaim.Name claim.Name = refresh.Name
claim.Icon = oldClaim.Icon claim.Icon = refresh.Icon
claim.ExpiresAt = jwt.NewNumericDate(time.Now().Add(time.Minute * 10)) claim.ExpiresAt = jwt.NewNumericDate(time.Now().Add(time.Minute * 10))
acList := make(auth.Access, 0, 10) acList := make(auth.Access, 0, 10)
logv.AssertError(cfg.DB().Table("accesses a"). logv.AssertError(cfg.DB().Table("accesses a").
Select("a.name, a.t_id, a.level"). Select("a.name, a.t_id, a.level").
Joins("INNER JOIN user_roles ur ON ur.role_id = a.role_id AND ur.user_id = ?", oldClaim.UID). Joins("INNER JOIN user_roles ur ON ur.role_id = a.role_id AND ur.user_id = ?", refresh.UID).
Scan(&acList).Error) Scan(&acList).Error)
claim.Access = acList claim.Access = acList
} else { } else {
// gen other app token // gen other app token
} }
} else if opts.Code != nil && aid == cfg.Config.ID { } else if opts.Code != nil && aid == cfg.Config.ID && opts.Salt != nil {
// for oa login // for oa login
user := &M.User{} user := &M.User{}
err = cfg.DB().Where("id = ?", opts.UserID).Find(user).Error err = cfg.DB().Where("id = ?", opts.UserID).Find(user).Error

@ -57,6 +57,7 @@ func InitDBData() error {
initRole := map[string]map[string]uint{ initRole := map[string]map[string]uint{
"user": {"admin": 5, "normal": 1}, "user": {"admin": 5, "normal": 1},
"app": {"admin": 5, "normal": 2}, "app": {"admin": 5, "normal": 2},
"fs": {"admin": 5, "normal": 5},
} }
adminID := "" adminID := ""
for r, roles := range initRole { for r, roles := range initRole {

@ -10,8 +10,9 @@ type TokenSalt struct {
type TokenPost struct { type TokenPost struct {
UserID string `json:"user_id" gorm:"index;type:varchar(32)" parse:"json"` UserID string `json:"user_id" gorm:"index;type:varchar(32)" parse:"json"`
// 两种获取token方式一种用token换取(应用登录)一种用密码加密code换(oa登录) // 两种获取token方式一种用refreshtoken换取apptoken(应用登录)一种用密码加密code换refreshtoken (oa登录)
Token *string `json:"token" parse:"json"` Refresh *string `json:"refresh" parse:"json"`
// 登录方随机生成的salt非用户salt // 登录方随机生成的salt非用户salt
Salt *string `json:"salt" parse:"json"` Salt *string `json:"salt" parse:"json"`
Code *string `json:"code" parse:"json"` Code *string `json:"code" parse:"json"`

4
oaer/index.d.ts vendored

@ -1 +1,3 @@
export function setupCounter(element: HTMLButtonElement): void export default interface OAER {
logout()
}

@ -0,0 +1,34 @@
//
// Copyright (C) 2024 veypi <i@veypi.com>
// 2024-10-11 14:36:07
// Distributed under terms of the MIT license.
//
import webapi from "./webapi"
import * as models from "./models"
export interface ListOpts {
app_id: string
user_id?: string
role_id?: string
name?: string
}
export interface ListQuery {
created_at?: Date
updated_at?: Date
}
export function List(json: ListOpts, query: ListQuery) {
return webapi.Get<[models.Access]>(`/access`, { json, query })
}
export interface PostOpts {
app_id: string
user_id?: string
role_id?: string
name: string
t_id: string
level: number
}
export function Post(json: PostOpts) {
return webapi.Post<models.Access>(`/access`, { json })
}

@ -1,28 +1,109 @@
/* //
* @name: app // Copyright (C) 2024 veypi <i@veypi.com>
* @author: veypi <i@veypi.com> // 2024-10-11 14:36:07
* @date: 2021-11-17 14:44 // Distributed under terms of the MIT license.
* @descriptionap //
* @update: 2021-11-17 14:44
*/ import webapi from "./webapi"
import ajax from './axios' import * as models from "./models"
import cfg from '../cfg' export function Get(app_id: string) {
return webapi.Get<models.App>(`/app/${app_id}`, {})
export default { }
local: () => cfg.BaseUrl() + '/app/',
getKey(uuid: string) { export interface PatchOpts {
return ajax.get(this.local() + uuid, { option: 'key' }) name?: string
}, icon?: string
get(uuid: string) { des?: string
return ajax.get(this.local() + uuid) participate?: string
}, init_role_id?: string
list() { }
return ajax.get(this.local()) export function Patch(app_id: string, json: PatchOpts) {
}, return webapi.Patch<models.App>(`/app/${app_id}`, { json })
users(uuid: string, user_id: string, data?: any) { }
if (uuid === '') {
uuid = '-' export function Delete(app_id: string) {
} return webapi.Delete<models.App>(`/app/${app_id}`, {})
return ajax.get(this.local() + uuid + '/user/' + user_id, data) }
},
export interface PostOpts {
name: string
icon: string
des: string
participate: string
}
export function Post(json: PostOpts) {
return webapi.Post<models.App>(`/app`, { json })
}
export interface ListOpts {
name?: string
}
export function List(json: ListOpts) {
return webapi.Get<[models.App]>(`/app`, { json })
}
export interface AppUserGetOpts {
user_id: string
}
export function AppUserGet(app_user_id: string, app_id: string, json: AppUserGetOpts) {
return webapi.Get<models.AppUser>(`/app/${app_id}/app_user/${app_user_id}`, { json })
}
export interface AppUserPatchOpts {
status?: string
}
export function AppUserPatch(app_user_id: string, app_id: string, json: AppUserPatchOpts) {
return webapi.Patch<models.AppUser>(`/app/${app_id}/app_user/${app_user_id}`, { json })
}
export function AppUserDelete(app_user_id: string, app_id: string) {
return webapi.Delete<models.AppUser>(`/app/${app_id}/app_user/${app_user_id}`, {})
} }
export interface AppUserListOpts {
user_id?: string
status?: string
}
export function AppUserList(app_id: string, json: AppUserListOpts) {
return webapi.Get<[models.AppUser]>(`/app/${app_id}/app_user`, { json })
}
export interface AppUserPostOpts {
status: string
user_id: string
}
export function AppUserPost(app_id: string, json: AppUserPostOpts) {
return webapi.Post<models.AppUser>(`/app/${app_id}/app_user`, { json })
}
export interface ResourceListQuery {
created_at?: Date
updated_at?: Date
}
export function ResourceList(app_id: string, query: ResourceListQuery) {
return webapi.Get<[models.Resource]>(`/app/${app_id}/resource`, { query })
}
export interface ResourcePostOpts {
name: string
des: string
}
export function ResourcePost(app_id: string, json: ResourcePostOpts) {
return webapi.Post<models.Resource>(`/app/${app_id}/resource`, { json })
}
export interface ResourceDeleteOpts {
name: string
}
export function ResourceDelete(app_id: string, json: ResourceDeleteOpts) {
return webapi.Delete<models.Resource>(`/app/${app_id}/resource`, { json })
}
export function ResourceGet(app_id: string) {
return webapi.Get<models.Resource>(`/app/${app_id}/resource`, {})
}
export function ResourcePatch(app_id: string) {
return webapi.Patch<models.Resource>(`/app/${app_id}/resource`, {})
}

@ -1,113 +0,0 @@
/*
* axios.ts
* Copyright (C) 2023 veypi <i@veypi.com>
* 2023-09-22 20:22
* Distributed under terms of the MIT license.
*/
import axios, { AxiosError, AxiosResponse } from 'axios';
import cfg from '../cfg';
// Be careful when using SSR for cross-request state pollution
// due to creating a Singleton instance here;
// If any client changes this (global) instance, it might be a
// good idea to move this instance creation inside of the
// "export default () => {}" function below (which runs individually
// for each client)
axios.defaults.withCredentials = true
const proxy = axios.create({
withCredentials: true,
headers: {
'content-type': 'application/json;charset=UTF-8',
},
});
// 请求拦截
const beforeRequest = (config: any) => {
// 设置 token
const token = cfg.token
config.retryTimes = 3
// NOTE 添加自定义头部
token && (config.headers.Authorization = `Bearer ${token}`)
// config.headers['auth_token'] = ''
return config
}
proxy.interceptors.request.use(beforeRequest)
// 响应拦截器
const responseSuccess = (response: AxiosResponse) => {
// eslint-disable-next-line yoda
// 这里没有必要进行判断axios 内部已经判断
// const isOk = 200 <= response.status && response.status < 300
let data = response.data
if (response.config.method === 'head') {
data = JSON.parse(JSON.stringify(response.headers))
}
return Promise.resolve(data)
}
const responseFailed = (error: AxiosError) => {
const { response, config } = error
if (!window.navigator.onLine) {
alert('没有网络')
return Promise.reject(new Error('请检查网络连接'))
}
// @ts-ignore
if (!config || !config.retryTimes) {
return Promise.reject(response?.data || response?.headers.error)
};
// @ts-ignore
const { __retryCount = 0, retryDelay = 300, retryTimes } = config;
// 在请求对象上设置重试次数
// @ts-ignore
config.__retryCount = __retryCount;
// 判断是否超过了重试次数
if (__retryCount >= retryTimes) {
return Promise.reject(response?.data || response?.headers.error)
}
// 增加重试次数
// @ts-ignore
config.__retryCount++;
// 延时处理
const delay = new Promise<void>((resolve) => {
setTimeout(() => {
resolve();
}, retryDelay);
});
// 重新发起请求
return delay.then(function() {
return axios(config);
});
}
proxy.interceptors.response.use(responseSuccess, responseFailed)
const ajax = {
get(url: string, data = {}, header?: any) {
return proxy.get<any, any>(url, { params: data, headers: header })
},
head(url: string, data = {}, header?: any) {
return proxy.head<any, any>(url, { params: data, headers: header })
},
delete(url: string, data = {}, header?: any) {
return proxy.delete<any, any>(url, { params: data, headers: header })
},
post(url: string, data = {}, header?: any) {
return proxy.post<any, any>(url, data, { headers: header })
},
put(url: string, data = {}, header?: any) {
return proxy.put<any, any>(url, data, { headers: header })
},
patch(url: string, data = {}, header?: any) {
return proxy.patch<any, any>(url, data, { headers: header })
},
}
export default ajax

@ -1,35 +1,26 @@
/*
* Copyright (C) 2019 light <veypi@light-laptop>
*
* Distributed under terms of the MIT license.
*/
import user from './user' import * as user from "./user"
import app from './app' import * as token from "./token"
import cfg from '../cfg' import * as role from "./role"
import ajax from './axios' import * as app from "./app"
import nats from './nats' import * as access from "./access"
import tsdb from './tsdb' import webapi, { token as apitoken } from "./webapi"
const info = () => {
const api = { return webapi.Get<{
nats: nats, id: string
tsdb: tsdb, }>('/', {})
user: user,
app: app,
info: () => {
return ajax.get(cfg.BaseUrl() + '/info')
},
refresh_token: () => {
ajax.post(cfg.BaseUrl() + '/app/' + cfg.uuid + '/token/', { app_id: cfg.uuid, token: cfg.refresh }).then(e => {
cfg.token = e
// bus.emit('sync', e)
}).catch(e => {
console.warn(e)
// bus.emit('logout', 'get token failed ' + e)
})
} }
export {
apitoken
} }
export { api } export default {
export default api info,
user,
token,
role,
app,
access
}

@ -0,0 +1,81 @@
export interface Access {
created_at: Date
updated_at: Date
app_id: string
user_id?: string
role_id?: string
name: string
tid: string
level: number
}
export interface App {
id: string
created_at: Date
updated_at: Date
name: string
icon: string
des: string
participate: string
init_role_id?: string
init_role?: Role
init_url: string
user_count: number
}
export interface AppUser {
id: string
created_at: Date
updated_at: Date
app_id: string
user_id: string
status: string
app?: App
user?: User
}
export interface Resource {
created_at: Date
updated_at: Date
app_id: string
name: string
des: string
}
export interface Role {
id: string
created_at: Date
updated_at: Date
name: string
des: string
app_id: string
user_count: number
}
export interface Token {
id: string
created_at: Date
updated_at: Date
user_id: string
app_id: string
expired_at: Date
over_perm: string
device: string
ip: string
}
export interface User {
id: string
created_at: Date
updated_at: Date
username: string
nickname: string
icon: string
email: string
phone: string
status: number
}
export interface UserRole {
id: string
created_at: Date
updated_at: Date
user_id: string
role_id: string
app_id: string
status: string
}

@ -1,20 +0,0 @@
/*
* nats.ts
* Copyright (C) 2023 veypi <i@veypi.com>
* 2023-10-19 21:36
* Distributed under terms of the MIT license.
*/
import cfg from '../cfg'
import ajax from './axios'
export default {
local: () => cfg.BaseUrl() + '/nats/',
general() {
return ajax.get(this.local() + 'varz')
},
conns() {
return ajax.get(this.local() + 'connz', { subs: true })
},
}

@ -0,0 +1,41 @@
//
// Copyright (C) 2024 veypi <i@veypi.com>
// 2024-10-11 14:36:07
// Distributed under terms of the MIT license.
//
import webapi from "./webapi"
import * as models from "./models"
export function Get(role_id: string) {
return webapi.Get<models.Role>(`/role/${role_id}`, { })
}
export interface PatchOpts {
name?: string
des?: string
app_id?: string
}
export function Patch(role_id: string, json: PatchOpts) {
return webapi.Patch<models.Role>(`/role/${role_id}`, { json })
}
export function Delete(role_id: string) {
return webapi.Delete<models.Role>(`/role/${role_id}`, { })
}
export interface PostOpts {
name: string
des: string
app_id: string
}
export function Post(json: PostOpts) {
return webapi.Post<models.Role>(`/role`, { json })
}
export interface ListOpts {
name?: string
}
export function List(json: ListOpts) {
return webapi.Get<[models.Role]>(`/role`, { json })
}

@ -0,0 +1,55 @@
//
// Copyright (C) 2024 veypi <i@veypi.com>
// 2024-10-11 14:36:07
// Distributed under terms of the MIT license.
//
import webapi from "./webapi"
import * as models from "./models"
export interface TokenSaltOpts {
username: string
typ?: string
}
// keep
export function TokenSalt(json: TokenSaltOpts) {
return webapi.Post<{ id: string, salt: string }>(`/token/salt`, { json })
}
export interface PostOpts {
user_id: string
refresh?: string
salt?: string
code?: string
app_id?: string
expired_at?: Date
over_perm?: string
device?: string
}
// keep
export function Post(json: PostOpts) {
return webapi.Post<string>(`/token`, { json })
}
export function Get(token_id: string) {
return webapi.Get<models.Token>(`/token/${token_id}`, {})
}
export interface PatchOpts {
expired_at?: Date
over_perm?: string
}
export function Patch(token_id: string, json: PatchOpts) {
return webapi.Patch<models.Token>(`/token/${token_id}`, { json })
}
export function Delete(token_id: string) {
return webapi.Delete<models.Token>(`/token/${token_id}`, {})
}
export interface ListOpts {
user_id: string
app_id: string
}
export function List(json: ListOpts) {
return webapi.Get<[models.Token]>(`/token`, { json })
}

@ -1,26 +0,0 @@
/*
* tsdb.ts
* Copyright (C) 2023 veypi <i@veypi.com>
* 2023-10-20 23:21
* Distributed under terms of the MIT license.
*/
import cfg from '../cfg'
import ajax from './axios'
export default {
local: () => cfg.BaseUrl() + '/ts/',
range(query: string, props?: { start?: string, end?: string, step?: string, query?: string }) {
if (query !== undefined) {
// @ts-ignore
props.query = query
} else {
props = { query: query }
}
return ajax.get(this.local() + 'query_range', props)
},
query(query: string) {
return ajax.get(this.local() + 'query', { query: query })
}
}

@ -1,24 +1,87 @@
/* //
* user.ts // Copyright (C) 2024 veypi <i@veypi.com>
* Copyright (C) 2023 veypi <i@veypi.com> // 2024-10-11 14:35:47
* 2023-10-05 15:37 // Distributed under terms of the MIT license.
* Distributed under terms of the MIT license. //
*/
import webapi from "./webapi"
import * as models from "./models"
export function Get(user_id: string) {
return webapi.Get<models.User>(`/user/${user_id}`, { })
}
export interface PatchOpts {
username?: string
nickname?: string
icon?: string
email?: string
phone?: string
status?: number
}
export function Patch(user_id: string, json: PatchOpts) {
return webapi.Patch<models.User>(`/user/${user_id}`, { json })
}
export function Delete(user_id: string) {
return webapi.Delete<models.User>(`/user/${user_id}`, { })
}
export interface PostOpts {
username: string
nickname?: string
icon?: string
email?: string
phone?: string
salt: string
code: string
}
export function Post(json: PostOpts) {
return webapi.Post<models.User>(`/user`, { json })
}
export interface ListOpts {
username?: string
nickname?: string
email?: string
phone?: string
status?: number
}
export function List(json: ListOpts) {
return webapi.Get<[models.User]>(`/user`, { json })
}
export function UserRoleGet(user_role_id: string, user_id: string) {
return webapi.Get<models.UserRole>(`/user/${user_id}/user_role/${user_role_id}`, { })
}
import ajax from './axios' export interface UserRolePatchOpts {
import cfg from '../cfg' status?: string
}
export function UserRolePatch(user_role_id: string, user_id: string, json: UserRolePatchOpts) {
return webapi.Patch<models.UserRole>(`/user/${user_id}/user_role/${user_role_id}`, { json })
}
export interface UserRoleDeleteOpts {
role_id: string
app_id: string
}
export function UserRoleDelete(user_role_id: string, user_id: string, json: UserRoleDeleteOpts) {
return webapi.Delete<models.UserRole>(`/user/${user_id}/user_role/${user_role_id}`, { json })
}
export interface UserRolePostOpts {
status: string
role_id: string
app_id: string
}
export function UserRolePost(user_id: string, json: UserRolePostOpts) {
return webapi.Post<models.UserRole>(`/user/${user_id}/user_role`, { json })
}
export default { export interface UserRoleListOpts {
local: () => cfg.BaseUrl() + '/user/', status?: string
search(q: string) { }
return ajax.get(this.local(), { username: q }) export function UserRoleList(user_id: string, json: UserRoleListOpts) {
}, return webapi.Get<[models.UserRole]>(`/user/${user_id}/user_role`, { json })
get(id: number) {
return ajax.get(this.local() + id)
},
} }

@ -0,0 +1,155 @@
//
// Copyright (C) 2024 veypi <i@veypi.com>
// 2024-10-22 17:13:42
// Distributed under terms of the MIT license.
//
import axios, { AxiosError, type AxiosResponse } from 'axios';
// Be careful when using SSR for cross-request state pollution
// due to creating a Singleton instance here;
// If any client changes this (global) instance, it might be a
// good idea to move this instance creation inside of the
// "export default () => {}" function below (which runs individually
// for each client)
axios.defaults.withCredentials = true
const proxy = axios.create({
withCredentials: true,
baseURL: "/api/",
headers: {
'content-type': 'application/json;charset=UTF-8',
},
});
export const token = {
value: '',
update: () => {
return new Promise<string>((resolve) => { resolve(token.value) })
},
set_updator: (fn: () => Promise<{ raw: () => string }>) => {
token.update = () => {
return new Promise<string>((resolve, reject) => {
fn().then((e) => {
resolve(e.raw())
}).catch(() => {
reject()
})
})
}
}
}
// 请求拦截
const beforeRequest = (config: any) => {
config.retryTimes = 3
// NOTE 添加自定义头部
token.value && (config.headers.Authorization = `Bearer ${token.value}`)
// config.headers['auth_token'] = ''
return config
}
proxy.interceptors.request.use(beforeRequest)
// 响应拦截器
const responseSuccess = (response: AxiosResponse) => {
// eslint-disable-next-line yoda
// 这里没有必要进行判断axios 内部已经判断
// const isOk = 200 <= response.status && response.status < 300
let data = response.data
if (typeof data === 'object') {
if (data.code !== 0) {
return responseFailed({ response } as any)
}
data = data.data
}
return Promise.resolve(data)
}
const responseFailed = (error: AxiosError) => {
const { response } = error
const config = response?.config
const data = response?.data || {} as any
if (!window.navigator.onLine) {
alert('没有网络')
return Promise.reject(new Error('请检查网络连接'))
}
let needRetry = true
if (response?.status == 404) {
needRetry = false
} else if (response?.status == 401) {
needRetry = false
if (data.code === 40103) {
}
}
if (!needRetry) {
return Promise.reject(data || response)
};
// @ts-ignore
const { __retryCount = 0, retryDelay = 1000, retryTimes } = config;
// 在请求对象上设置重试次数
// @ts-ignore
config.__retryCount = __retryCount + 1;
// 判断是否超过了重试次数
if (__retryCount >= retryTimes) {
return Promise.reject(response?.data || response?.headers.error)
}
// 延时处理
const delay = new Promise<void>((resolve) => {
setTimeout(() => {
resolve();
}, retryDelay);
});
// 重新发起请求
return delay.then(function() {
return proxy.request(config as any);
});
}
proxy.interceptors.response.use(responseSuccess, responseFailed)
interface data {
json?: any
query?: any
form?: any
header?: any
}
function transData(d: data) {
let opts = { params: d.query, data: {}, headers: {} as any }
if (d.form) {
opts.data = d.form
opts.headers['content-type'] = 'application/x-www-form-urlencoded'
}
if (d.json) {
opts.data = d.json
opts.headers['content-type'] = 'application/json'
}
if (d.header) {
opts.headers = Object.assign(opts.headers, d.header)
}
return opts
}
export const webapi = {
Get<T>(url: string, req: data): Promise<T> {
return proxy.request<T, any>(Object.assign({ method: 'get', url: url }, transData(req)))
},
Head<T>(url: string, req: data): Promise<T> {
return proxy.request<T, any>(Object.assign({ method: 'head', url: url }, transData(req)))
},
Delete<T>(url: string, req: data): Promise<T> {
return proxy.request<T, any>(Object.assign({ method: 'delete', url: url }, transData(req)))
},
Post<T>(url: string, req: data): Promise<T> {
return proxy.request<T, any>(Object.assign({ method: 'post', url: url }, transData(req)))
},
Put<T>(url: string, req: data): Promise<T> {
return proxy.request<T, any>(Object.assign({ method: 'put', url: url }, transData(req)))
},
Patch<T>(url: string, req: data): Promise<T> {
return proxy.request<T, any>(Object.assign({ method: 'patch', url: url }, transData(req)))
},
}
export default webapi

@ -1,69 +0,0 @@
/*
* cfg.ts
* Copyright (C) 2024 veypi <i@veypi.com>
* 2024-07-03 18:22
* Distributed under terms of the MIT license.
*/
import proxy from './components/proxy'
let cfg = proxy.Watch({
uuid: '',
refresh: '',
token: '',
app_data: {},
user: {
username: 'asd',
nickname: '',
email: '',
phone: '',
id: 1,
icon: 'https://public.veypi.com/img/avatar/0001.jpg'
},
myapps: [
{ name: 'a' },
{ name: 'b' },
],
ready: false,
local_user: {},
host: '',
_host_nats: '',
nats_pub_key: '',
prefix: '/api',
BaseUrl() {
return this.host + this.prefix
},
NatsHost() {
if (this._host_nats.startsWith('ws')) {
return this._host_nats
}
return 'ws://' + this._host_nats
},
media(u: string) {
return this.host + u
},
goto(url: string) {
if (url.startsWith('http')) {
window.location.href = url
return
}
if (!url.startsWith('/')) {
url = '/' + url
}
window.location.href = this.host + url
},
Host() {
return this.host || window.location.host
},
userFileUrl() {
return '/fs/u/'
},
appFileUrl() {
return `/fs/a/${this.uuid}/`
},
})
export default cfg

@ -5,22 +5,22 @@
* Distributed under terms of the GPL license. * Distributed under terms of the GPL license.
*/ */
import v from './v2dom' import v from './v2dom'
import cfg from '../cfg' import logic from '../logic'
export default v('voa-account', [ export default v('voa-account', [
v('voa-account-header', [v('voa-ah-1', '我的账户'), v('voa-ah-2')]), v('voa-account-header', [v('voa-ah-1', '我的账户'), v('voa-ah-2')]),
v('voa-account-body', [ v('voa-account-body', [
// v('voa-ab-ico', [v('img', '', (d) => { d.setAttribute('src', cfg.user.icon) })]), // v('voa-ab-ico', [v('img', '', (d) => { d.setAttribute('src', cfg.user.icon) })]),
v('voa-ab-ico', [v({ typ: 'img', attrs: { 'src': () => `https://public.veypi.com/img/avatar/000${cfg.user.id}.jpg`, test: () => cfg.user.id } })]), v('voa-ab-ico', [v({ typ: 'img', attrs: { 'src': () => `https://public.veypi.com/img/avatar/000${logic.user.id}.jpg`, test: () => logic.user.id } })]),
v('voa-ab-info', [ v('voa-ab-info', [
v('voa-abi-1', [v('span', '昵称:'), v('span', () => cfg.user.nickname)]), v('voa-abi-1', [v('span', '昵称:'), v('span', () => logic.user.nickname)]),
v('voa-abi-2', [v('span', '账户:'), v('span', () => cfg.user.username)]), v('voa-abi-2', [v('span', '账户:'), v('span', () => logic.user.username)]),
v('voa-abi-3', [v('span', '邮箱:'), v('span', () => cfg.user.email)]), v('voa-abi-3', [v('span', '邮箱:'), v('span', () => logic.user.email)]),
v('voa-abi-4', [v('span', '手机:'), v('span', () => cfg.user.phone)]), v('voa-abi-4', [v('span', '手机:'), v('span', () => logic.user.phone)]),
v({ v({
typ: 'input', typ: 'input',
attrs: { 'type': 'number' }, attrs: { 'type': 'number' },
vbind: [cfg.user, 'id'] vbind: [logic.user, 'id']
}) })
]) ])
]) ])

@ -5,8 +5,8 @@
* Distributed under terms of the GPL license. * Distributed under terms of the GPL license.
*/ */
import cfg from "../cfg";
import v from "./v2dom"; import v from "./v2dom";
import logic from '../logic'
export default () => v({ export default () => v({
@ -14,16 +14,16 @@ export default () => v({
children: [ children: [
v('voa-apps-header', [v('voa-apps-title', '我的应用')]), v('voa-apps-header', [v('voa-apps-title', '我的应用')]),
v('voa-apps-body', [ v('voa-apps-body', [
[cfg.myapps, [logic.myapps,
(data) => v('voa-app-box', [v('span', () => data.name)])], (data) => v('voa-app-box', [v('span', () => data.name)])],
v('div', '222'), v('div', '222'),
[cfg.myapps, [logic.myapps,
(data) => v('voa-app-box', () => data.name)], (data) => v('voa-app-box', () => data.name)],
v({ v({
typ: 'div', innerHtml: 'add', vbind: [cfg.myapps[0], 'name'], typ: 'div', inner: 'add', vbind: [logic.myapps[0], 'name'],
onclick: () => { onclick: () => {
cfg.myapps.splice(0, 1) logic.myapps.splice(0, 1)
cfg.myapps.push({ name: new Date().toLocaleString() }) logic.myapps.push({ name: new Date().toLocaleString() })
} }
}) })
]) ])

@ -7,53 +7,44 @@
import slide from './slide' import slide from './slide'
import v from './v2dom' import v from './v2dom'
import bus from '../bus' import logic from '../logic'
export default class { export default class {
slide: slide slide: slide
frame: HTMLElement frame: HTMLElement
frame_login?: HTMLElement constructor() {
frame_user?: HTMLElement
constructor(frame: HTMLElement) {
this.frame = frame
this.frame.classList.add('voa')
this.slide = new slide() this.slide = new slide()
this.mount_user() this.frame = v('voa', this.gen_avatar())
bus.on('logout', () => { }
this.mount_login() mount(root: Element) {
this.slide.hide() root.appendChild(this.frame)
})
} }
mount_login() { gen_avatar() {
this.frame_login = v({ let frame_user = v({
class: 'voa-off voa-hover-line-b voa-scale-in', class: 'voa-on',
innerHtml: '登录', vclass: [() => logic.ready ? 'voa-scale-in' : 'voa-scale-off'],
inner: `<img src="${logic.user.icon}" />`,
onclick: () => { onclick: () => {
console.log('click login') this.slide.show()
this.frame_login?.classList.add('voa-scale-off') // logic.logout()
this.mount_user()
} }
}) })
if (this.frame_user) { let frame_login = v({
this.frame.removeChild(this.frame_user) class: 'voa-off voa-hover-line-b',
} vclass: [() => !logic.ready ? 'voa-scale-in' : 'voa-scale-off'],
this.frame.appendChild(this.frame_login) inner: '登录',
}
mount_user() {
let icon = 'https://public.veypi.com/img/avatar/0001.jpg'
this.frame_user = v({
class: 'voa-on voa-scale-in',
innerHtml: `
<img src="${icon}" />
`,
onclick: () => { onclick: () => {
this.slide.show() console.log('click login')
// this.mount_login() logic.login()
} }
}) })
if (this.frame_login) { return () => {
this.frame.removeChild(this.frame_login) console.log(logic.ready)
if (logic.ready) {
return frame_user
} else {
return frame_login
}
} }
this.frame.appendChild(this.frame_user)
} }
} }

@ -6,40 +6,46 @@
*/ */
type voidFn = () => void type voidFn = () => void
const callbackCache: (voidFn | undefined)[] = [] const callbackCache: ([boolean, voidFn])[] = []
const cacheUpdateList: number[] = [] const cacheUpdateList: number[] = []
// 界面更新响应频率40hz // 界面更新响应频率40hz
setInterval(() => { setInterval(() => {
let list = new Set(cacheUpdateList.splice(0)) let list = new Set(cacheUpdateList.splice(0))
for (let l of list) { for (let l of list) {
if (callbackCache[l]) { if (callbackCache[l][0]) {
callbackCache[l]() callbackCache[l][1]()
} }
} }
}, 25) }, 25)
// 绑定事件删除
setInterval(() => { // TODO: 已删除的元素有可能会重新添加回来,
let exists = new Set() // // 绑定事件删除
let check = (dom: Element) => { //
let ids = dom.getAttribute('vbind-fns')?.split(',') // setInterval(() => {
if (ids?.length) { // let exists = new Set()
for (let i of ids) { // let check = (dom: Element) => {
exists.add(i) // let ids = dom.getAttribute('vbind-fns')?.split(',')
} // if (ids?.length) {
} // for (let i of ids) {
for (let child of dom.children) { // exists.add(i)
check(child) // if (!callbackCache[Number(i)][0]) {
} // callbackCache[Number(i)][0] = true
} // }
check(document.body) // }
for (let i in callbackCache) { // }
if (!exists.has(i)) { // for (let child of dom.children) {
// 只删除元素,保留位置 // check(child)
delete callbackCache[i] // }
// console.log('remove ' + i) // }
} // check(document.body)
} // for (let i in callbackCache) {
}, 1000) // if (!exists.has(i)) {
// // 只使能在页面上的更新
// callbackCache[i][0] = false
// // console.log('remove ' + i)
// }
// }
// }, 1000)
function generateUniqueId() { function generateUniqueId() {
const timestamp = performance.now().toString(36); const timestamp = performance.now().toString(36);
@ -49,22 +55,33 @@ function generateUniqueId() {
function ForceUpdate() { function ForceUpdate() {
for (let c of callbackCache) { for (let c of callbackCache) {
if (c) { if (c[0]) {
c() c[1]()
} }
} }
} }
var listen_tags: number[] = [] var listen_tags: number[] = []
function Listen(callback: voidFn): number { function listen(callback: voidFn): number {
let idx = callbackCache.length let idx = callbackCache.length
listen_tags.push(idx) listen_tags.push(idx)
callbackCache.push(callback) callbackCache.push([true, callback])
callback() callback()
listen_tags.pop() listen_tags.pop()
return idx return idx
} }
function Listen(dom: HTMLElement, fn: () => void) {
let fns = dom.getAttribute('vbind-fns')
let fnid = listen(fn)
if (fns) {
fns += ',' + fnid
} else {
fns = String(fnid)
}
dom.setAttribute('vbind-fns', fns)
}
const isProxy = Symbol("isProxy") const isProxy = Symbol("isProxy")
const DataID = Symbol("DataID") const DataID = Symbol("DataID")

@ -5,9 +5,11 @@
* Distributed under terms of the GPL license. * Distributed under terms of the GPL license.
*/ */
import v from './v2dom' import v from './v2dom'
import bus from '../bus'
import account from './account' import account from './account'
import app from './app' import app from './app'
import logic from '../logic'
import proxy from './proxy'
import bus from '../bus'
/* /*
mask mask
@ -31,7 +33,7 @@ export default class {
}) })
this.footer = v({ this.footer = v({
class: 'voa-slide-footer', class: 'voa-slide-footer',
innerHtml: 'logout', inner: 'logout',
onclick: () => { onclick: () => {
bus.emit('logout') bus.emit('logout')
} }
@ -65,6 +67,11 @@ export default class {
} }
} }
}) })
proxy.Listen(this.mask, () => {
if (!logic.ready) {
this.hide()
}
})
document.body.appendChild(this.mask) document.body.appendChild(this.mask)
} }
show() { show() {

@ -12,9 +12,10 @@ interface buildOpts<T> {
id?: string id?: string
typ?: string typ?: string
class?: string class?: string
vclass?: [() => string]
attrs?: attrs attrs?: attrs
style?: string style?: string
innerHtml?: string inner?: string | computedFn
onclick?: any onclick?: any
children?: childTyp<T>[] children?: childTyp<T>[]
updator?: (p: HTMLElement) => void updator?: (p: HTMLElement) => void
@ -36,17 +37,20 @@ export default <T>(opts: buildOpts<T> | string, inner?: string | computedFn | ch
} }
} }
let dom = document.createElement(opts.typ || 'div') let dom = document.createElement(opts.typ || 'div')
if (opts.inner) {
inner = opts.inner
}
if (inner) { if (inner) {
if (typeof inner == 'string') { if (typeof inner == 'string') {
opts.innerHtml = inner dom.innerHTML = inner
} else if (typeof inner == 'function') { } else if (typeof inner == 'function') {
addListener(dom, () => { proxy.Listen(dom, () => {
let res = inner() let res = inner()
if (typeof res === 'string') { if (typeof res === 'string') {
dom.innerHTML = res dom.innerHTML = res
} else { } else {
dom.innerHTML = '' dom.innerHTML = ''
dom.appendChild(inner()) dom.appendChild(res)
} }
}) })
} else { } else {
@ -62,14 +66,21 @@ export default <T>(opts: buildOpts<T> | string, inner?: string | computedFn | ch
if (opts.class) { if (opts.class) {
dom.classList.add(...opts.class.split(' ')) dom.classList.add(...opts.class.split(' '))
} }
if (opts.innerHtml) { if (opts.vclass) {
dom.innerHTML = opts.innerHtml for (let vc of opts.vclass) {
let oldvc: string[] = []
proxy.Listen(dom, () => {
dom.classList.remove(...oldvc)
oldvc = vc().split(' ')
dom.classList.add(...oldvc)
})
}
} }
if (opts.attrs) { if (opts.attrs) {
for (let a in opts.attrs) { for (let a in opts.attrs) {
let attr = opts.attrs[a] let attr = opts.attrs[a]
if (typeof attr === 'function') { if (typeof attr === 'function') {
addListener(dom, () => { proxy.Listen(dom, () => {
dom.setAttribute(a, attr()) dom.setAttribute(a, attr())
}) })
} else { } else {
@ -86,7 +97,7 @@ export default <T>(opts: buildOpts<T> | string, inner?: string | computedFn | ch
if (Object.prototype.toString.call(c) === '[object Array]') { if (Object.prototype.toString.call(c) === '[object Array]') {
const iterID = proxy.generateUniqueId() const iterID = proxy.generateUniqueId()
const iterLast = tmpID const iterLast = tmpID
addListener(dom, () => { proxy.Listen(dom, () => {
c = c as iterChild<T> c = c as iterChild<T>
let itemIDs: string[] = [] let itemIDs: string[] = []
for (let i = 0; i < c[0].length; i++) { for (let i = 0; i < c[0].length; i++) {
@ -171,7 +182,7 @@ export default <T>(opts: buildOpts<T> | string, inner?: string | computedFn | ch
} }
dom.setAttribute('voa', '1') dom.setAttribute('voa', '1')
if (opts.updator) { if (opts.updator) {
addListener(dom, () => { proxy.Listen(dom, () => {
opts.updator!(dom) opts.updator!(dom)
}) })
} }
@ -186,13 +197,3 @@ export default <T>(opts: buildOpts<T> | string, inner?: string | computedFn | ch
} }
function addListener(dom: HTMLElement, fn: () => void) {
let fns = dom.getAttribute('vbind-fns')
let fnid = proxy.Listen(fn)
if (fns) {
fns += ',' + fnid
} else {
fns = String(fnid)
}
dom.setAttribute('vbind-fns', fns)
}

@ -0,0 +1,170 @@
/*
* logic.ts
* Copyright (C) 2024 veypi <i@veypi.com>
* 2024-10-24 16:36
* Distributed under terms of the GPL license.
*/
import bus from './bus'
import proxy from './components/proxy'
import api, { apitoken } from './api'
class Token {
iat?: string
iss?: string
jti?: string
exp?: number
aid?: string
icon?: string
name?: string
uid?: string
access?: any
private _raw: string
private _typ: 'refresh' | 'oa' | 'app'
constructor(typ: 'refresh' | 'oa' | 'app') {
this._typ = typ
this._raw = localStorage.getItem(typ) || ''
if (this._raw) {
this.set(this._raw)
}
}
isVaild() {
if (this.exp) {
const now = Math.floor(Date.now() / 1000);
return now < this.exp
}
return false
}
set(t: string) {
try {
const parts = t.split('.');
// header = JSON.parse(atob(parts[0]));
let body = JSON.parse(atob(parts[1]));
// sign = parts[2]
this._raw = t
Object.assign(this, body)
localStorage.setItem(this._typ, t)
} catch (error) {
console.warn('Error parsing JWT:', error);
}
}
clear() {
localStorage.removeItem(this._typ)
this.exp = undefined
}
raw() {
return this._raw
}
update() {
return new Promise<Token>((resolve, reject) => {
if (!logic.token.refresh.isVaild() || this._typ === 'refresh') {
reject()
}
let aid = logic.oa_id
if (this._typ === 'app') {
aid = logic.app_id
}
api.token.Post({ refresh: logic.token.refresh.raw(), user_id: logic.token.refresh.uid!, app_id: aid }).then(e => {
if (this._typ === 'oa' && logic.app_id == logic.oa_id) {
logic.token.app.set(e)
}
this.set(e)
resolve(this)
}).catch(e => {
console.warn(`get oa token failed: ${e} `)
reject()
})
})
}
}
// interface Token {
// aid: string
// exp: number
// iat: string
// icon: string
// iss: string
// jti: string
// name: string
// uid: string
// isVaild: () => boolean
// row: () => string
// }
const logic = proxy.Watch({
oa_id: '',
app_id: '',
token: {
refresh: new Token('refresh'),
oa: new Token('oa'),
app: new Token('app'),
},
user: {
username: 'asd',
nickname: '',
email: '',
phone: '',
id: 1,
icon: 'https://public.veypi.com/img/avatar/0001.jpg'
},
myapps: [
{ name: 'a' },
{ name: 'b' },
],
ready: false,
init: () => {
return new Promise<Token>((resolve, reject) => {
api.info().then(e => {
logic.oa_id = e.id
if (logic.token.refresh.isVaild()) {
logic.app_id = logic.token.refresh.aid!
logic.token.oa.update().then((e) => {
apitoken.set_updator(logic.token.oa.update)
if (logic.app_id !== logic.oa_id) {
logic.token.app.update().then((e) => {
logic.ready = true
resolve(e);
}).catch(() => reject())
} else {
logic.ready = true
resolve(e);
}
}).catch(() => {
reject()
})
} else {
reject()
}
}).catch(e => {
console.warn(`can not get info from ${logic.host}: ${e} `)
reject();
})
})
},
host: '',
Host() {
return this.host || (window.location.protocol + window.location.host)
},
goto(url: string) {
if (url.startsWith('http')) {
window.location.href = url
return
}
if (!url.startsWith('/')) {
url = '/' + url
}
window.location.href = this.host + url
},
})
bus.on('login', () => {
logic.goto('/login')
})
bus.on('logout', () => {
logic.ready = false
logic.token.refresh.clear()
logic.token.oa.clear()
logic.token.app.clear()
})
export default logic

@ -7,18 +7,35 @@
import './assets/css/oaer.scss' import './assets/css/oaer.scss'
import bus from './bus' import bus from './bus'
import logic from './logic'
import ui from './components' import ui from './components'
export class OAer { export default new class {
host: string private ui?: ui
domid?: string constructor() {
ui?: ui
constructor(host: string, domid?: string) {
this.host = host
if (domid) {
this.domid = domid
this.ui = new ui(document.querySelector(`#${this.domid}`)!)
} }
init(host?: string, code?: string) {
if (host) {
logic.host = host
}
if (code) {
logic.token.refresh.set(code)
}
return logic.init()
}
render_ui(domid = 'voa') {
if (!this.ui) {
this.ui = new ui()
}
let root = document.querySelector('#' + domid)
if (root) {
this.ui.mount(root)
} else {
console.warn(`not found <div id="${domid}"></div>`)
}
}
isValid() {
return logic.token.refresh.isVaild()
} }
login() { login() {
bus.emit('login') bus.emit('login')
@ -26,9 +43,9 @@ export class OAer {
logout() { logout() {
bus.emit('logout') bus.emit('logout')
} }
onlogout(fc: () => void) { on(evt: string, fn: (d?: any) => void) {
bus.on('logout', fc) bus.on(evt, fn)
}
} }
}()

@ -1,6 +1,7 @@
import './style.css' import './style.css'
import { OAer } from '../lib/main' import oaer from '../lib/main'
let t = new OAer('http://localhost:3000', 'voa') oaer.init('http://localhost:3000', '')
console.log(t.domid) oaer.render_ui('voa')
console.log(oaer)

@ -9,7 +9,7 @@
export default defineAppConfig({ export default defineAppConfig({
// host: window.location.protocol + '//' + window.location.host, // host: window.location.protocol + '//' + window.location.host,
host: '', host: '',
id: 'FR9P5t8debxc11aFF', id: 'tMRxWz77P9ABNZA3ZIuoNQILjVBBIUdf',
layout: { layout: {
theme: '', theme: '',
fullscreen: false, fullscreen: false,

@ -28,6 +28,8 @@ export interface AppUser {
app_id: string app_id: string
user_id: string user_id: string
status: string status: string
app?: App
user?: User
} }
export interface Resource { export interface Resource {
created_at: Date created_at: Date

@ -16,13 +16,13 @@ export function TokenSalt(json: TokenSaltOpts) {
} }
export interface PostOpts { export interface PostOpts {
user_id: string user_id: string
token?: string
salt?: string salt?: string
code?: string code?: string
app_id?: string app_id?: string
expired_at?: Date expired_at?: Date
over_perm?: string over_perm?: string
device?: string device?: string
refresh?: string
} }
// keep // keep

@ -1,6 +1,6 @@
// //
// Copyright (C) 2024 veypi <i@veypi.com> // Copyright (C) 2024 veypi <i@veypi.com>
// 2024-10-11 15:31:08 // 2024-10-25 18:42:03
// Distributed under terms of the MIT license. // Distributed under terms of the MIT license.
// //

@ -53,10 +53,10 @@ export interface DocGroup {
} }
export enum AUStatus { export enum AUStatus {
OK = 0, OK = "ok",
Disabled = 1, Disabled = "disabled",
Applying = 2, Applying = "applying",
Deny = 3, Deny = "deny",
} }

@ -12,8 +12,9 @@
<div class="grow"></div> <div class="grow"></div>
<OneIcon class="mx-2" @click="toggle_fullscreen" :name="app.layout.fullscreen ? 'compress' : 'expend'"></OneIcon> <OneIcon class="mx-2" @click="toggle_fullscreen" :name="app.layout.fullscreen ? 'compress' : 'expend'"></OneIcon>
<OneIcon class="mx-2" @click="toggle_theme" :name="app.layout.theme === '' ? 'light' : 'dark'"></OneIcon> <OneIcon class="mx-2" @click="toggle_theme" :name="app.layout.theme === '' ? 'light' : 'dark'"></OneIcon>
<OAer class="mx-2" v-if="user.ready" @logout="user.logout" :is-dark="app.layout.theme !== ''"> <!-- <OAer class="mx-2" v-if="user.ready" @logout="user.logout" :is-dark="app.layout.theme !== ''"> -->
</OAer> <!-- </OAer> -->
<div class="mr-4 ml-2" id='oaer'></div>
</div> </div>
<div class="menu"> <div class="menu">
<Menu :show_name="menu_mode === 2"></Menu> <Menu :show_name="menu_mode === 2"></Menu>
@ -32,8 +33,7 @@
<script lang="ts" setup> <script lang="ts" setup>
import { OneIcon } from '@veypi/one-icon' import { OneIcon } from '@veypi/one-icon'
import { OAer } from "@veypi/oaer"; import oaer from '../../oaer/lib/main'
import oaer from '@veypi/oaer'
import '@veypi/oaer/dist/index.css' import '@veypi/oaer/dist/index.css'
let app = useAppConfig() let app = useAppConfig()
@ -41,21 +41,16 @@ let router = useRouter()
let user = useUserStore() let user = useUserStore()
app.host = window.location.protocol + '//' + window.location.host app.host = window.location.protocol + '//' + window.location.host
oaer.set({ oaer.init(app.host).then(() => {
token: util.getToken(), oaer.render_ui('oaer')
host: app.host, user.fetchUserData()
uuid: app.id, }).catch(() => {
oaer.logout()
}) })
oaer.on('logout', () => {
bus.on('token', (t: any) => { useRouter().push('/login')
oaer.set({ token: t })
}) })
if (!util.checkLogin()) {
router.push('/login')
} else {
user.fetchUserData()
}
let menu_mode = ref(1) let menu_mode = ref(1)
let toggle_menu = (m: 0 | 1 | 2) => { let toggle_menu = (m: 0 | 1 | 2) => {
menu_mode.value = m menu_mode.value = m
@ -84,6 +79,11 @@ const toggle_theme = () => {
document.documentElement.setAttribute('theme', app.layout.theme) document.documentElement.setAttribute('theme', app.layout.theme)
} }
onMounted(() => {
console.log('mount frame')
oaer.render_ui('oaer')
})
</script> </script>
@ -105,6 +105,11 @@ const toggle_theme = () => {
background: url('/favicon.ico') no-repeat; background: url('/favicon.ico') no-repeat;
background-size: cover; background-size: cover;
} }
#oaer {
width: v-bind('app.layout.header_height * 0.6 + "px"');
height: v-bind('app.layout.header_height * 0.6 + "px"');
}
} }
.menu { .menu {
@ -152,6 +157,3 @@ const toggle_theme = () => {
} }
} }
</style> </style>

@ -18,17 +18,17 @@ export default defineNuxtConfig({
nitro: { nitro: {
devProxy: { devProxy: {
"/api": { "/api": {
target: "http://127.0.0.1:4000/api", target: "http://localhost:4000/api",
changeOrigin: true, changeOrigin: true,
ws: true, ws: true,
}, },
'/fs': { '/fs': {
target: 'http://127.0.0.1:4000/fs', target: 'http://localhost:4000/fs',
changeOrigin: true, changeOrigin: true,
ws: true, ws: true,
}, },
'/media': { '/media': {
target: 'http://127.0.0.1:4000/media', target: 'http://localhost:4000/media',
changeOrigin: true, changeOrigin: true,
ws: true, ws: true,
}, },
@ -53,5 +53,6 @@ export default defineNuxtConfig({
] ]
}, },
}, },
modules: ["@pinia/nuxt"] modules: ["@pinia/nuxt"],
compatibilityDate: '2024-10-16'
}) })

@ -30,11 +30,6 @@
</div> </div>
</div> </div>
</div> </div>
<UModal v-model="new_flag">
<div class="p-4">
123
</div>
</UModal>
<!-- <q-dialog :square="false" v-model="new_flag"> --> <!-- <q-dialog :square="false" v-model="new_flag"> -->
<!-- <q-card class="w-4/5 md:w-96 rounded-2xl"> --> <!-- <q-card class="w-4/5 md:w-96 rounded-2xl"> -->
<!-- <q-card-section> --> <!-- <q-card-section> -->

@ -14,13 +14,16 @@
<Transition name="box" mode="out-in"> <Transition name="box" mode="out-in">
<div class="newbie content" v-if="aOpt === 'newbie'"> <div class="newbie content" v-if="aOpt === 'newbie'">
<div :check="checks.u" class="username mt-8"> <div :check="checks.u" class="username mt-8">
<input @change="check" v-model="data.username" autocomplete="username" placeholder="username, phone or Email"> <input @change="check" v-model="data.username" autocomplete="username"
placeholder="username, phone or Email">
</div> </div>
<div :check="checks.p" class="password"> <div :check="checks.p" class="password">
<input @change="check" v-model="data.password" autocomplete="password" type='password' placeholder="password"> <input @change="check" v-model="data.password" autocomplete="password" type='password'
placeholder="password">
</div> </div>
<div :check="checks.p2" class="password"> <div :check="checks.p2" class="password">
<input @change="check" v-model="data.confirm" autocomplete="password" type='password' placeholder="password"> <input @change="check" v-model="data.confirm" autocomplete="password" type='password'
placeholder="password">
</div> </div>
<div class="flex"> <div class="flex">
<button @click="aOpt = ''" class='ok voa-btn back'> <button @click="aOpt = ''" class='ok voa-btn back'>
@ -46,10 +49,12 @@
</div> </div>
<div class="login content flex flex-col justify-between" v-else> <div class="login content flex flex-col justify-between" v-else>
<div :check="checks.u" class="username mt-8"> <div :check="checks.u" class="username mt-8">
<input @change="check" v-model="data.username" autocomplete="username" placeholder="username, phone or Email"> <input @change="check" v-model="data.username" autocomplete="username"
placeholder="username, phone or Email">
</div> </div>
<div :check="checks.p" class="password"> <div :check="checks.p" class="password">
<input @change="check" v-model="data.password" autocomplete="password" type='password' placeholder="password"> <input @change="check" v-model="data.password" autocomplete="password" type='password'
placeholder="password">
</div> </div>
<button @click="login" class='ok voa-btn'> <button @click="login" class='ok voa-btn'>
login login
@ -89,6 +94,8 @@
<script lang="ts" setup> <script lang="ts" setup>
import msg from '@veypi/msg'; import msg from '@veypi/msg';
import * as crypto from 'crypto-js' import * as crypto from 'crypto-js'
import oaer from '../../oaer/lib/main'
import { isValid } from 'js-base64';
definePageMeta({ definePageMeta({
@ -149,15 +156,21 @@ const login = () => {
user_id: id, code: p.toString(), salt: user_id: id, code: p.toString(), salt:
salt.toString() salt.toString()
}).then(e => { }).then(e => {
localStorage.setItem('refresh', e) oaer.init(app.host, e).then(() => {
api.token.Post({ user_id: id, token: e }).then(e => {
localStorage.setItem('token', e)
redirect("") redirect("")
console.log(e) }).catch((e) => {
console.warn(e)
msg.Warn('登录失败:' + (e.err || e))
}) })
}).catch(e => { }).catch(e => {
msg.Warn('登录失败:' + (e.err || e)) msg.Warn('登录失败:' + (e.err || e))
}) })
}).catch(e => {
if (e.code === 40401) {
msg.Warn('user not exist')
} else {
console.warn(e)
}
}) })
// api.user.salt(data.value.username).then((e: any) => { // api.user.salt(data.value.username).then((e: any) => {
// let id = e.data.id // let id = e.data.id
@ -511,4 +524,3 @@ onMounted(() => {
} }
} }
</style> </style>

@ -35,7 +35,7 @@ export const useUserStore = defineStore('user', {
data.access.map((e: any) => l = l + `\n${e.name}.${e.level}`) data.access.map((e: any) => l = l + `\n${e.name}.${e.level}`)
console.log(l) console.log(l)
this.auth = NewAuths(data.access) this.auth = NewAuths(data.access)
api.user.Get(data.id).then((e) => { api.user.Get(data.uid).then((e) => {
this.id = e.id this.id = e.id
this.local = e this.local = e
this.ready = true this.ready = true

File diff suppressed because it is too large Load Diff
Loading…
Cancel
Save