v3
veypi 2 weeks ago
parent 89a2ea17e2
commit 70da692d9a

@ -1,6 +0,0 @@
## 接口规范检查与修复任务
- [ ] 检查并修复 `api/app` 及其子模块接口规范: `access`, `app_user`, `resource`, `role`
- [ ] 检查并修复 `api/sms` 接口规范
- [ ] 检查并修复 `api/token` 接口规范
- [ ] 检查并修复 `api/user` 及其子模块接口规范: `role`

@ -2,10 +2,6 @@ module github.com/veypi/vbase
go 1.24.1 go 1.24.1
replace github.com/veypi/vhtml-ui => ../vhtml-ui/
replace github.com/veypi/vigo => ../vigo/
require ( require (
github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.9 github.com/alibabacloud-go/darabonba-openapi/v2 v2.1.9
github.com/alibabacloud-go/dysmsapi-20170525/v5 v5.1.2 github.com/alibabacloud-go/dysmsapi-20170525/v5 v5.1.2
@ -15,7 +11,6 @@ require (
github.com/glebarez/sqlite v1.11.0 github.com/glebarez/sqlite v1.11.0
github.com/golang-jwt/jwt/v5 v5.2.3 github.com/golang-jwt/jwt/v5 v5.2.3
github.com/google/uuid v1.6.0 github.com/google/uuid v1.6.0
github.com/veypi/vhtml-ui v0.0.0-00010101000000-000000000000
github.com/veypi/vigo v0.6.0 github.com/veypi/vigo v0.6.0
gorm.io/driver/mysql v1.6.0 gorm.io/driver/mysql v1.6.0
gorm.io/driver/postgres v1.6.0 gorm.io/driver/postgres v1.6.0

@ -1,41 +1,47 @@
<!doctype html> <!doctype html>
<html> <html>
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="description" content="App List Page"> <meta name="description" content="App List Page">
<title>应用列表</title> <title>应用列表</title>
</head> </head>
<style> <style>
body { body {
padding: var(--spacing-lg); padding: var(--spacing-lg);
background-color: var(--bg-color-primary); background-color: var(--bg-color-primary);
color: var(--text-color-primary); color: var(--text-color-primary);
} }
header {
header {
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
margin-bottom: var(--spacing-xl); margin-bottom: var(--spacing-xl);
flex-wrap: wrap; flex-wrap: wrap;
gap: var(--spacing-md); gap: var(--spacing-md);
} }
h1 {
h1 {
font-size: 2rem; font-size: 2rem;
font-weight: 700; font-weight: 700;
color: var(--color-primary); color: var(--color-primary);
} }
.filters {
.filters {
display: flex; display: flex;
gap: var(--spacing-sm); gap: var(--spacing-sm);
margin-bottom: var(--spacing-lg); margin-bottom: var(--spacing-lg);
} }
.app-grid {
.app-grid {
display: grid; display: grid;
grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
gap: var(--spacing-lg); gap: var(--spacing-lg);
} }
.app-card {
.app-card {
background-color: var(--bg-color-secondary); background-color: var(--bg-color-secondary);
border-radius: var(--radius-md); border-radius: var(--radius-md);
box-shadow: var(--shadow-sm); box-shadow: var(--shadow-sm);
@ -43,149 +49,168 @@ h1 {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
overflow: hidden; overflow: hidden;
} }
.app-card:hover {
.app-card:hover {
transform: translateY(-5px); transform: translateY(-5px);
box-shadow: var(--shadow-lg); box-shadow: var(--shadow-lg);
} }
.app-card-header {
.app-card-header {
padding: var(--spacing-md); padding: var(--spacing-md);
display: flex; display: flex;
align-items: center; align-items: center;
border-bottom: 1px solid var(--border-color); border-bottom: 1px solid var(--border-color);
} }
.app-icon {
.app-icon {
width: 48px; width: 48px;
height: 48px; height: 48px;
border-radius: var(--radius-sm); border-radius: var(--radius-sm);
margin-right: var(--spacing-md); margin-right: var(--spacing-md);
object-fit: cover; object-fit: cover;
background-color: var(--bg-color-tertiary); background-color: var(--bg-color-tertiary);
} }
.app-info {
.app-info {
flex: 1; flex: 1;
} }
.app-title {
.app-title {
font-weight: 600; font-weight: 600;
margin-bottom: var(--spacing-xs); margin-bottom: var(--spacing-xs);
color: var(--text-color-primary); color: var(--text-color-primary);
} }
.app-status {
.app-status {
font-size: 0.8rem; font-size: 0.8rem;
padding: 2px 8px; padding: 2px 8px;
border-radius: 10px; border-radius: 10px;
} }
.status-active { background-color: color-mix(in srgb, var(--color-success), transparent 90%); color: var(--color-success); }
.status-pending { background-color: color-mix(in srgb, var(--color-warning), transparent 90%); color: var(--color-warning); } .status-active {
.status-inactive { background-color: color-mix(in srgb, var(--color-danger), transparent 90%); color: var(--color-danger); } background-color: color-mix(in srgb, var(--color-success), transparent 90%);
color: var(--color-success);
}
.status-pending {
background-color: color-mix(in srgb, var(--color-warning), transparent 90%);
color: var(--color-warning);
}
.app-card-body { .status-inactive {
background-color: color-mix(in srgb, var(--color-danger), transparent 90%);
color: var(--color-danger);
}
.app-card-body {
padding: var(--spacing-md); padding: var(--spacing-md);
flex: 1; flex: 1;
color: var(--text-color-secondary); color: var(--text-color-secondary);
font-size: 0.9rem; font-size: 0.9rem;
line-height: 1.5; line-height: 1.5;
} }
.app-card-footer {
.app-card-footer {
padding: var(--spacing-md); padding: var(--spacing-md);
border-top: 1px solid var(--border-color); border-top: 1px solid var(--border-color);
display: flex; display: flex;
justify-content: flex-end; justify-content: flex-end;
gap: var(--spacing-sm); gap: var(--spacing-sm);
background-color: color-mix(in srgb, var(--bg-color-secondary), black 2%); background-color: color-mix(in srgb, var(--bg-color-secondary), black 2%);
} }
</style> </style>
<body layout="app"> <body layout="app">
<header> <header>
<h1>应用中心</h1> <h1>应用中心</h1>
<div style="width: 300px;"> <div style="width: 300px;">
<v-input v:value="searchQuery" placeholder="搜索应用..." prefix="search"></v-input> <v-input v:value="searchQuery" placeholder="搜索应用..." prefix="search"></v-input>
</div>
</header>
<div class="filters">
<v-btn v-for="filter in filters" :key="filter.value"
:variant="currentFilter === filter.value ? 'primary' : 'outline'"
@click="currentFilter = filter.value"
size="sm">
{{ filter.label }}
</v-btn>
</div> </div>
</header>
<div class="filters">
<v-btn v-for="filter in filters" :key="filter.value"
:variant="currentFilter === filter.value ? 'primary' : 'outline'" @click="currentFilter = filter.value" size="sm">
{{ filter.label }}
</v-btn>
</div>
<div class="app-grid"> <div class="app-grid">
<div class="app-card" v-for="app in filteredApps" :key="app.id"> <div class="app-card" v-for="app in filteredApps" :key="app.id">
<div class="app-card-header"> <div class="app-card-header">
<img :src="app.icon" class="app-icon" alt="icon"> <img :src="app.icon" class="app-icon" alt="icon">
<div class="app-info"> <div class="app-info">
<div class="app-title">{{ app.name }}</div> <div class="app-title">{{ app.name }}</div>
<span class="app-status" :class="getStatusClass(app.status)"> <span class="app-status" :class="getStatusClass(app.status)">
{{ getStatusLabel(app.status) }} {{ getStatusLabel(app.status) }}
</span> </span>
</div>
</div>
<div class="app-card-body">
{{ app.des || '暂无描述' }}
</div>
<div class="app-card-footer">
<v-btn size="sm" variant="text" @click="openApp(app)">访问</v-btn>
<v-btn size="sm" variant="outline" @click="manageApp(app)">管理</v-btn>
</div>
</div> </div>
</div>
<div class="app-card-body">
{{ app.des || '暂无描述' }}
</div>
<div class="app-card-footer">
<v-btn size="sm" variant="text" @click="openApp(app)">访问</v-btn>
<v-btn size="sm" variant="outline" @click="manageApp(app)">管理</v-btn>
</div>
</div> </div>
</div>
</body> </body>
<script setup> <script setup>
searchQuery = '' searchQuery = ''
currentFilter = 'all' currentFilter = 'all'
filters = [ filters = [
{ label: '全部', value: 'all' }, {label: '全部', value: 'all'},
{ label: '我的应用', value: 'my' }, {label: '我的应用', value: 'my'},
{ label: '最近使用', value: 'recent' } {label: '最近使用', value: 'recent'}
] ]
apps = [] apps = []
filteredApps = [] filteredApps = []
getStatusClass = (status) => { getStatusClass = (status) => {
if (status === 'ok') return 'status-active' if (status === 'ok') return 'status-active'
if (status === 'pending') return 'status-pending' if (status === 'pending') return 'status-pending'
return 'status-inactive' return 'status-inactive'
} }
getStatusLabel = (status) => { getStatusLabel = (status) => {
if (status === 'ok') return '运行中' if (status === 'ok') return '运行中'
if (status === 'pending') return '审核中' if (status === 'pending') return '审核中'
return '已停止' return '已停止'
} }
openApp = (app) => { openApp = (app) => {
if (app.init_url) { if (app.init_url) {
window.open(app.init_url, '_blank') window.open(app.init_url, '_blank')
} else { } else {
$message.info('该应用暂无入口') $message.info('该应用暂无入口')
} }
} }
manageApp = (app) => { manageApp = (app) => {
$router.push('/app/settings?id=' + app.id) $router.push(`/app/${app.id}/settings`)
} }
// Fetch apps // Fetch apps
$axios.get('/api/app').then(res => { $axios.get('/api/app').then(res => {
apps = res || [] apps = res || []
$data.filteredApps = apps $data.filteredApps = apps
}) })
</script> </script>
<script> <script>
$watch(() => [searchQuery, currentFilter, apps], () => { $watch(() => [searchQuery, currentFilter, apps], () => {
let result = $data.apps let result = $data.apps
if ($data.searchQuery) { if ($data.searchQuery) {
const q = $data.searchQuery.toLowerCase() const q = $data.searchQuery.toLowerCase()
result = result.filter(app => app.name.toLowerCase().includes(q) || (app.des && app.des.toLowerCase().includes(q))) result = result.filter(app => app.name.toLowerCase().includes(q) || (app.des && app.des.toLowerCase().includes(q)))
} }
// Filter logic for 'my' and 'recent' would go here, simplified for now // Filter logic for 'my' and 'recent' would go here, simplified for now
if ($data.currentFilter === 'my') { if ($data.currentFilter === 'my') {
// Mock logic // Mock logic
} }
$data.filteredApps = result $data.filteredApps = result
}) })
</script> </script>
</html> </html>

Loading…
Cancel
Save