v3
veypi 3 months ago
parent a5339aa589
commit ae463e0723

@ -8,18 +8,18 @@
package app
import (
"github.com/veypi/OneAuth/api/app/access"
"github.com/veypi/OneAuth/api/app/app_user"
"github.com/veypi/OneAuth/api/app/resource"
"github.com/veypi/OneAuth/api/app/role"
"github.com/veypi/OneAuth/cfg"
"github.com/veypi/OneAuth/models"
"github.com/vyes/vigo"
"github.com/vyes/vigo/contrib/crud"
)
var Router = vigo.NewRouter()
var appRouter = Router.SubRouter(":app_id")
var (
_ = appRouter.Extend("resource", resource.Router)
_ = appRouter.Extend("user", app_user.Router)
_ = appRouter.Extend("role", role.Router)
_ = appRouter.Extend("access", access.Router)
)
func init() {
crud.All(appRouter.SubRouter("resource"), cfg.DB, models.Resource{})
crud.All(appRouter.SubRouter("user"), cfg.DB, models.AppUser{})
crud.All(appRouter.SubRouter("role"), cfg.DB, models.Role{})
crud.All(Router.SubRouter("access"), cfg.DB, models.Access{})
}

@ -18,7 +18,7 @@ type Options struct {
}
var Config = &Options{
TokenExpire: time.Minute * 30,
TokenExpire: time.Second * 10,
ID: "test",
Key: "asdfghjklqwertyuiopzxcvbnm1234567890",
}

@ -1,13 +1,355 @@
html,
body {
width: 100vw;
height: 100vh;
}
html {
overflow: hidden;
}
body {
overflow: auto;
}
:root {
/* 颜色 */
--color-primary: #4361ee;
--color-secondary: #3f37c9;
--color-success: #28a745;
--color-danger: #dc3545;
--color-warning: #ffc107;
--color-info: #17a2b8;
--color-text: #333;
--color-text-light: #555;
--color-background: #f1f8ff;
--color-border: #dee2e6;
--color-link: var(--color-primary);
--color-link-hover: #0056b3;
--font-family-sans: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
--font-family-serif: "Georgia", serif;
--font-family-mono: "SFMono-Regular", Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace;
--font-size-base: 1rem;
--font-weight-normal: 400;
--font-weight-bold: 700;
/* 间距 */
--spacing-xs: 0.25rem;
--spacing-sm: 0.5rem;
--spacing-md: 1rem;
--spacing-lg: 1.5rem;
--spacing-xl: 2rem;
--border-radius: 0.25rem;
--box-shadow: 0 0.5rem 1rem rgba(0, 0, 0, 0.15);
--transition: all 0.3s ease-in-out;
}
*,
*::before,
*::after {
box-sizing: border-box;
}
html,
body,
h1,
h2,
h3,
h4,
h5,
h6,
p,
figure,
blockquote,
dl,
dd {
margin: 0;
padding: 0;
}
html,
body {
width: 100vw;
height: 100vh;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
scroll-behavior: smooth;
font-family: var(--font-family-sans);
font-size: var(--font-size-base);
line-height: 1.5;
color: var(--color-text);
background-color: var(--color-background);
}
html {
overflow: hidden;
}
body {
overflow: auto;
}
ul,
ol {
list-style: none;
padding: 0;
margin: 0;
}
img,
picture,
video,
canvas,
svg {
display: block;
max-width: 100%;
}
input,
button,
textarea,
select {
font: inherit;
margin: 0;
}
p,
h1,
h2,
h3,
h4,
h5,
h6 {
overflow-wrap: break-word;
}
h1,
.h1 {
font-size: 2.5rem;
font-weight: var(--font-weight-bold);
line-height: 1.2;
margin-bottom: var(--spacing-md);
}
h2,
.h2 {
font-size: 2rem;
font-weight: var(--font-weight-bold);
line-height: 1.2;
margin-bottom: var(--spacing-md);
}
h3,
.h3 {
font-size: 1.75rem;
font-weight: var(--font-weight-bold);
line-height: 1.2;
margin-bottom: var(--spacing-md);
}
h4,
.h4 {
font-size: 1.5rem;
font-weight: var(--font-weight-bold);
line-height: 1.2;
margin-bottom: var(--spacing-sm);
}
h5,
.h5 {
font-size: 1.25rem;
font-weight: var(--font-weight-bold);
line-height: 1.2;
margin-bottom: var(--spacing-sm);
}
h6,
.h6 {
font-size: 1rem;
font-weight: var(--font-weight-bold);
line-height: 1.2;
margin-bottom: var(--spacing-sm);
}
p {
margin-bottom: var(--spacing-md);
line-height: 1.6;
}
a {
color: var(--color-link);
text-decoration: none;
transition: var(--transition);
}
a:hover {
color: var(--color-link-hover);
text-decoration: underline;
}
hr {
border: 0;
border-top: 1px solid var(--color-border);
margin: var(--spacing-lg) 0;
}
.container {
width: 100%;
max-width: 1200px;
margin-left: auto;
margin-right: auto;
padding-left: var(--spacing-md);
padding-right: var(--spacing-md);
}
@media (max-width: 768px) {
.container {
padding-left: var(--spacing-sm);
padding-right: var(--spacing-sm);
}
}
.break-word {
word-wrap: break-word;
overflow-wrap: break-word;
}
.clearfix::after {
content: "";
display: table;
clear: both;
}
.visually-hidden {
position: absolute !important;
width: 1px !important;
height: 1px !important;
padding: 0 !important;
margin: -1px !important;
overflow: hidden !important;
clip: rect(0, 0, 0, 0) !important;
white-space: nowrap !important;
border: 0 !important;
}
i.fa-solid {
cursor: pointer;
}
i.fa-solid:hover {
opacity: 0.7;
}
::-webkit-scrollbar {
background-color: none;
width: 5px;
height: 5px;
}
::-webkit-scrollbar-thumb {
background: #aaa;
}
/* 新增扁平化和对比度样式 */
/* 按钮基础样式 */
button {
background-color: var(--color-primary);
color: white;
border: none;
padding: 8px 16px;
border-radius: 4px;
cursor: pointer;
transition: background-color 0.2s ease-in-out, box-shadow 0.2s ease-in-out;
}
button:hover {
background-color: #2563eb;
/* A slightly darker primary color */
box-shadow: 0 2px 4px var(--shadow-color);
}
button:active {
background-color: #1e40af;
/* Even darker */
}
button:disabled {
background-color: var(--color-secondary);
cursor: not-allowed;
}
/* 表单元素基础样式 */
input[type="text"],
input[type="password"],
input[type="email"],
input[type="number"],
textarea {
border: 1px solid var(--color-border);
padding: 8px 12px;
border-radius: 4px;
background-color: white;
color: var(--text-color);
transition: border-color 0.2s ease-in-out, box-shadow 0.2s ease-in-out;
}
input[type="text"]:focus,
input[type="password"]:focus,
input[type="email"]:focus,
input[type="number"]:focus,
textarea:focus {
outline: none;
border-color: var(--color-primary);
box-shadow: 0 0 0 2px rgba(59, 130, 246, 0.2);
/* primary-color with transparency */
}
/* 通用容器和阴影 */
.card {
background-color: white;
border-radius: 8px;
box-shadow: 0 1px 3px var(--shadow-color), 0 1px 2px var(--shadow-color);
padding: 20px;
margin-bottom: 20px;
}
/* 辅助类 */
.text-primary {
color: var(--color-primary);
}
.text-secondary {
color: var(--color-secondary);
}
.text-success {
color: var(--color-success);
}
.text-warning {
color: var(--color-warning);
}
.text-error {
color: var(--color-danger);
}
.bg-primary {
background-color: var(--color-primary);
}
.bg-secondary {
background-color: var(--color-secondary);
}
.bg-success {
background-color: var(--color-success);
}
.bg-warning {
background-color: var(--color-warning);
}
.bg-error {
background-color: var(--color-danger);
}
.border-primary {
border-color: var(--color-primary);
}
.hover-underline:hover {
text-decoration: underline;
}

File diff suppressed because one or more lines are too long

@ -7,42 +7,6 @@
<title>应用</title>
<style>
/* 引用原始样式 */
body {
--primary-color: #4a6cf7;
--secondary-color: #6c757d;
--background-color: #f8f9fa;
--card-background: #ffffff;
--text-color: #333333;
--success-color: #28a745;
--warning-color: #ffc107;
--danger-color: #dc3545;
--border-radius: 10px;
--box-shadow: 0 4px 20px rgba(0, 0, 0, 0.1);
--transition: all 0.3s ease;
}
{
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
width: 100%;
height: 100%;
font-family: 'Inter', sans-serif;
background-color: var(--background-color);
color: var(--text-color);
line-height: 1.6;
padding: 20px;
}
.container {
max-width: 1200px;
margin: 0 auto;
}
header {
display: flex;
justify-content: start;
@ -414,7 +378,7 @@
};
sync = () => {
show_create_app = false
$api.Get('/api/app').then((res) => {
$axios.get('/api/app').then((res) => {
apps = res;
loading = false;
});

@ -8,43 +8,16 @@
</head>
<style>
body {
--primary-color: #3498db;
--secondary-color: #2ecc71;
--dark-color: #34495e;
--light-color: #ecf0f1;
--accent-color: #e74c3c;
--shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
--border-radius: 8px;
}
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
height: 100%;
width: 100%;
font-family: 'Nunito', sans-serif;
background-color: #f5f7fa;
color: var(--dark-color);
line-height: 1.6;
display: flex;
align-items: center;
justify-content: center;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 20px;
height: min-content;
}
header {
background: linear-gradient(135deg, var(--primary-color), #4a69bd);
background: linear-gradient(135deg, var(--color-primary), #4a69bd);
color: white;
padding: 40px 0;
border-radius: var(--border-radius);
@ -277,6 +250,11 @@
init_role: null,
init_url: ''
}
id = $router.params.id
if (!id) {
$router.back()
return
}
// 格式化日期函数
formatDate = (isoString) => {
@ -296,15 +274,7 @@
// 同步应用数据
sync = () => {
const params = new URLSearchParams(window.location.search)
id = params.get('id')
if (!id) {
history.back()
return
}
$api.Get(`/api/app/${id}`)
$axios.get(`/api/app/${id}`)
.then((data) => {
Object.assign(app, data)
document.title = `${app.name} - 项目主页`

@ -3,8 +3,7 @@
<head>
<title>项目与用户管理 Dashboard</title>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.10.0/font/bootstrap-icons.css">
<script src="https://cdn.jsdelivr.net/npm/echarts@5.4.3/dist/echarts.min.js"></script>
<script src="/assets/libs/echarts.min.js"></script>
</head>
<style>
body {

@ -132,7 +132,7 @@
loadUserData = async () => {
try {
isLoading = true;
const response = await $api.Get("/api/user/" + user.id);
const response = await $axios.get("/api/user/" + user.id);
if (response) {
user = {
id: response.id,
@ -168,7 +168,7 @@
};
// 发送更新请求
const response = await $api.Patch("/api/user/" + user.id, updateData);
const response = await $axios.patch("/api/user/" + user.id, updateData);
if (response) {
successMessage = "个人信息更新成功!";

@ -4,7 +4,7 @@
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>oa</title>
<script type="module" key='vyes' src="/vyes/v.js"></script>
<script type="module" key='vyes' src="http://test2.vyesai.com/vyes/v.js"></script>
<link href="/assets/libs/tailwind/tailwind.min.css" rel="stylesheet">
<link href="/assets/libs/animate/animate.min.css" rel="stylesheet">
<link href="/assets/libs/font-awesome/css/all.min.css" rel="stylesheet">
@ -15,5 +15,43 @@
<page-404></page-404>
</vrouter>
</body>
<script type='module' setup>
if (typeof $env !== 'undefined') {
const token = (await import(root + '/token.js')).default
token.setRoot(root)
token.wrapAxios($axios)
let user = token.body()
$env.Guser = user
$env.Gtoken = token.getToken()
$router.beforeEnter = async (to, from, next) => {
if (to.meta && to.meta.auth) {
// check if the user is authenticated
// next({ path: '/login' });
if (token.isExpired()) {
await token.refresh()
}
if (token.isExpired()) {
token.logout()
}
} else {
next()
}
};
$axios.interceptors.response.use(function (response) {
if (response.data && response.data.code === 0) {
return response.data.data
}
return response;
}, function (error) {
let data = error.response ? error.response.data : error.response
if (!data) return error.response
if (data.code >= 400) {
return Promise.reject(data.message)
}
return Promise.reject(data.message || data);
});
}
</script>
</html>

@ -5,65 +5,16 @@
* Distributed under terms of the MIT license.
*/
import token from './token.js'
function wrapAxios(axios) {
axios.interceptors.response.use(function(response) {
if (response.data && response.data.code === 0) {
return response.data.data
}
return response;
}, function(error) {
let data = error.response ? error.response.data : error.response
if (data.code >= 400) {
return Promise.reject(data.message)
}
return Promise.reject(data.message);
});
}
async function checkAuth(to, from, next) {
if (token.isExpired()) {
await token.refresh()
}
if (token.isExpired()) {
token.logout()
}
}
/**
* 定义路由
* @param{{$axios:Axios, $bus:EventBus, $router: VRouter, root:string}} env
* */
function setup(env) {
token.setRoot(env.root)
token.wrapAxios(env.$axios)
wrapAxios(env.$axios)
let user = token.body()
env.Guser = user
env.Gtoken = token.getToken()
// 添加路由
env.$router.addRoutes([
{
path: '/',
component: '/page/index.html',
name: 'home',
},
{
path: '/404',
component: '/page/404.html',
name: '404'
},
{
path: '*', // 通配符,匹配所有未定义的路由
component: (path) => {
if (path.endsWith('.html')) {
return path; // 如果是HTML文件直接返回
}
path = '/page' + path
return path + '.html'
},
}
])
}
export default setup
const routes = [
{ path: '/', component: '/page/index.html', name: 'home', meta: { auth: true } },
{ path: '/login', component: '/page/login.html', name: 'login', meta: { auth: false } },
{ path: '/profile', component: '/page/profile.html', name: 'profile', meta: { auth: true } },
{ path: '/app', component: '/page/app.html', name: 'app', meta: { auth: true } },
{
path: '/app/:app_id', children: [
{ path: '/', component: '/page/app/index.html', name: 'app_index', meta: { auth: true } },
]
},
{ path: '*', component: '/page/404.html', name: '404' },
]
export default routes

@ -50,12 +50,12 @@ class TokenService {
parseToken(token) {
try {
if (!token) return null;
if (!token || typeof token !== 'string') return null;
const base64Url = token.split('.')[1];
const base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
return JSON.parse(window.atob(base64));
} catch (error) {
console.error('Token解析失败:', error);
console.warn('Token解析失败:', error);
return null;
}
}
@ -77,7 +77,7 @@ class TokenService {
async refresh() {
const refreshToken = this.getRefreshToken();
if (!refreshToken) {
// this.logout()
this.logout()
return;
}
try {
@ -86,6 +86,7 @@ class TokenService {
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ refresh: refreshToken })
}).then(res => res.json())
this.__cache = null; // 清除缓存
if (data.code === 0) {
this.setToken(data.data);
} else {
@ -161,11 +162,10 @@ class TokenService {
originalRequest.__retryCount = originalRequest.__retryCount || 0;
originalRequest.__retryCount++;
// 如果重试次数超过 3 次,则不再重试,直接跳转到登录页
if (originalRequest.__retryCount >= 3) {
that.clearToken();
// 跳转到登录页,并带上当前页面的路径作为重定向参数
window.location.href = that.__root + '/login?redirect=' + window.location.pathname;
that.logout()
// 拒绝原始请求的 Promise停止后续处理
return Promise.reject(error);
}

Loading…
Cancel
Save