mirror of
https://github.com/langbot-app/LangBot.git
synced 2026-06-11 08:16:03 +00:00
feat: webUI2.0 前端介面更新
1. 剩余登陆注册未完成 2. 剩余插件列表&市场未完成
This commit is contained in:
329
web/src/App.vue
329
web/src/App.vue
@@ -1,329 +0,0 @@
|
||||
<template>
|
||||
<v-app>
|
||||
<v-snackbar v-model="snackbar" :color="color" :timeout="timeout" :location="location">
|
||||
{{ text }}
|
||||
</v-snackbar>
|
||||
|
||||
<div id="app-container" v-if="proxy.$store.state.user.tokenChecked && proxy.$store.state.user.tokenValid">
|
||||
<v-layout>
|
||||
<v-navigation-drawer id="navigation-drawer" :width="160" app permanent rail>
|
||||
<v-list-item id="logo-list-item">
|
||||
<template v-slot:prepend>
|
||||
<div id="logo-container">
|
||||
<v-img id="logo-img" src="@/assets/langbot-logo-block.png" height="32" width="32"></v-img>
|
||||
|
||||
<div id="version-chip"
|
||||
v-tooltip="proxy.$store.state.version + (proxy.$store.state.debug ? ' (调试模式已启用)' : '')"
|
||||
:style="{ 'background-color': proxy.$store.state.debug ? '#27aa27' : '#1e9ae2' }">
|
||||
{{ proxy.$store.state.version }}
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</v-list-item>
|
||||
<v-divider></v-divider>
|
||||
<v-list density="compact" nav>
|
||||
<v-list-item to="/" title="仪表盘" value="dashboard" prepend-icon="mdi-speedometer" v-tooltip="仪表盘">
|
||||
</v-list-item>
|
||||
<v-list-item to="/settings" title="设置" value="settings" prepend-icon="mdi-toggle-switch-outline"
|
||||
v-tooltip="设置">
|
||||
</v-list-item>
|
||||
<v-list-item to="/logs" title="日志" value="logs" prepend-icon="mdi-file-outline" v-tooltip="日志">
|
||||
</v-list-item>
|
||||
<v-list-item to="/plugins" title="插件" value="plugins" prepend-icon="mdi-puzzle-outline" v-tooltip="插件">
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
<template v-slot:append>
|
||||
<div>
|
||||
<v-list density="compact" nav>
|
||||
<v-dialog max-width="500" persistent v-model="taskDialogShow">
|
||||
<template v-slot:activator="{ props: activatorProps }">
|
||||
<v-list-item id="system-tasks-list-item" title="任务列表" prepend-icon="mdi-align-horizontal-left"
|
||||
v-tooltip="任务列表" v-bind="activatorProps">
|
||||
</v-list-item>
|
||||
</template>
|
||||
|
||||
<template v-slot:default="{ isActive }">
|
||||
<TaskDialog :dialog="{ show: isActive }" @close="closeTaskDialog" />
|
||||
</template>
|
||||
</v-dialog>
|
||||
|
||||
<v-list-item id="about-list-item" title="系统信息" prepend-icon="mdi-cog-outline" v-tooltip="系统信息">
|
||||
<v-menu activator="parent" :close-on-content-click="false" location="end">
|
||||
<v-list>
|
||||
<v-list-item @click="showAboutDialog">
|
||||
<v-list-item-title>
|
||||
关于 LangBot
|
||||
|
||||
<v-dialog max-width="400" persistent v-model="aboutDialogShow">
|
||||
<template v-slot:default="{ isActive }">
|
||||
<AboutDialog :dialog="{ show: isActive }" @close="closeAboutDialog" />
|
||||
</template>
|
||||
</v-dialog>
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item @click="openDocs">
|
||||
<v-list-item-title>
|
||||
查看文档
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item @click="reload('platform')">
|
||||
<v-list-item-title>
|
||||
重载消息平台
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item @click="reload('plugin')">
|
||||
<v-list-item-title>
|
||||
重载插件
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
|
||||
<v-list-item @click="reload('provider')">
|
||||
<v-list-item-title>
|
||||
重载 LLM 管理器
|
||||
</v-list-item-title>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
</v-list-item>
|
||||
</v-list>
|
||||
</div>
|
||||
</template>
|
||||
</v-navigation-drawer>
|
||||
|
||||
<v-main style="background-color: #f6f6f6;">
|
||||
<router-view />
|
||||
</v-main>
|
||||
</v-layout>
|
||||
</div>
|
||||
|
||||
<div id="loading-container" v-if="!proxy.$store.state.user.tokenChecked">
|
||||
<div id="loading-text">
|
||||
<img src="@/assets/langbot-logo.png" height="32" width="32" />
|
||||
<span id="loading-text-span">正在加载...</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="login-container" v-if="proxy.$store.state.user.tokenChecked && !proxy.$store.state.user.tokenValid && proxy.$store.state.user.systemInitialized">
|
||||
<LoginDialog @error="error" @success="success" @checkToken="checkToken" />
|
||||
</div>
|
||||
|
||||
<div id="uninitialized-container" v-if="proxy.$store.state.user.tokenChecked && !proxy.$store.state.user.systemInitialized">
|
||||
<InitDialog @error="error" @success="success" @checkSystemInitialized="checkSystemInitialized" />
|
||||
</div>
|
||||
</v-app>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { getCurrentInstance } from 'vue'
|
||||
import { provide, ref, watch, onMounted } from 'vue';
|
||||
|
||||
import TaskDialog from '@/components/TaskListDialog.vue'
|
||||
import AboutDialog from '@/components/AboutDialog.vue'
|
||||
import InitDialog from '@/components/InitDialog.vue'
|
||||
import LoginDialog from '@/components/LoginDialog.vue'
|
||||
|
||||
const { proxy } = getCurrentInstance()
|
||||
|
||||
const snackbar = ref(false);
|
||||
const color = ref('success');
|
||||
const text = ref('');
|
||||
const location = ref('top');
|
||||
const timeout = ref(2000);
|
||||
|
||||
function success(content) {
|
||||
snackbar.value = true;
|
||||
color.value = 'success';
|
||||
text.value = content;
|
||||
}
|
||||
|
||||
function error(content) {
|
||||
snackbar.value = true;
|
||||
color.value = 'error';
|
||||
text.value = content;
|
||||
}
|
||||
|
||||
function warning(content) {
|
||||
snackbar.value = true;
|
||||
color.value = 'warning';
|
||||
text.value = content;
|
||||
}
|
||||
|
||||
function info(content) {
|
||||
snackbar.value = true;
|
||||
color.value = 'primary';
|
||||
text.value = content;
|
||||
}
|
||||
|
||||
const taskDialogShow = ref(false)
|
||||
|
||||
function showTaskDialog() {
|
||||
taskDialogShow.value = true
|
||||
}
|
||||
|
||||
function closeTaskDialog() {
|
||||
taskDialogShow.value = false
|
||||
}
|
||||
|
||||
provide('snackbar', { success, error, warning, info, location, timeout });
|
||||
|
||||
function openDocs() {
|
||||
window.open('https://docs.langbot.app', '_blank')
|
||||
}
|
||||
|
||||
const reloadScopeLabel = {
|
||||
'platform': "消息平台",
|
||||
'plugin': "插件",
|
||||
'provider': "LLM 管理器"
|
||||
}
|
||||
|
||||
function reload(scope) {
|
||||
let label = reloadScopeLabel[scope]
|
||||
proxy.$axios.post('/system/reload',
|
||||
{ scope: scope },
|
||||
{ headers: { 'Content-Type': 'application/json' } }
|
||||
).then(response => {
|
||||
if (response.data.code === 0) {
|
||||
success(label + '已重载')
|
||||
|
||||
// 关闭菜单
|
||||
} else {
|
||||
error(label + '重载失败:' + response.data.message)
|
||||
}
|
||||
}).catch(err => {
|
||||
error(label + '重载失败:' + err)
|
||||
})
|
||||
|
||||
}
|
||||
|
||||
const aboutDialogShow = ref(false)
|
||||
|
||||
function showAboutDialog() {
|
||||
aboutDialogShow.value = true
|
||||
}
|
||||
|
||||
function closeAboutDialog() {
|
||||
aboutDialogShow.value = false
|
||||
}
|
||||
|
||||
function checkSystemInitialized() {
|
||||
proxy.$axios.get('/user/init').then(response => {
|
||||
if (response.data.code === 0) {
|
||||
proxy.$store.state.user.systemInitialized = response.data.data.initialized
|
||||
} else {
|
||||
error('系统初始化状态检查失败:' + response.data.message)
|
||||
proxy.$store.state.user.systemInitialized = true
|
||||
}
|
||||
|
||||
checkToken()
|
||||
}).catch(err => {
|
||||
error('系统初始化状态检查失败:' + err)
|
||||
proxy.$store.state.user.systemInitialized = true
|
||||
})
|
||||
}
|
||||
|
||||
function checkToken() {
|
||||
proxy.$axios.get('/user/check-token').then(response => {
|
||||
if (response.data.code === 0) {
|
||||
proxy.$store.state.user.tokenValid = true
|
||||
} else {
|
||||
proxy.$store.state.user.tokenValid = false
|
||||
}
|
||||
}).catch(err => {
|
||||
proxy.$store.state.user.tokenValid = false
|
||||
}).finally(() => {
|
||||
proxy.$store.state.user.tokenChecked = true
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
checkSystemInitialized()
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
#app-container {
|
||||
display: flex;
|
||||
height: 100vh;
|
||||
}
|
||||
|
||||
#loading-container {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
height: 100vh;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
#loading-text {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
#loading-text-span {
|
||||
font-size: 1.2rem;
|
||||
font-weight: 500;
|
||||
padding-top: 0.2rem;
|
||||
}
|
||||
|
||||
#navigation-drawer {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
#logo-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
margin-left: -0.2rem;
|
||||
}
|
||||
|
||||
#logo-list-item {
|
||||
margin-top: 0.5rem;
|
||||
margin-bottom: 1.5rem;
|
||||
}
|
||||
|
||||
#version-chip {
|
||||
position: absolute;
|
||||
top: 0;
|
||||
right: 0;
|
||||
margin-top: 2.4rem;
|
||||
margin-right: 0.3rem;
|
||||
font-size: 0.55rem;
|
||||
font-weight: 500;
|
||||
padding-inline: 0.4rem;
|
||||
text-align: center;
|
||||
background-color: #c79a47;
|
||||
color: white;
|
||||
border-radius: 0.5rem;
|
||||
border: 1px solid #fff;
|
||||
box-shadow: 0 0 0.1rem 0 rgba(0, 0, 0, 0.5);
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
#about-list-item {
|
||||
justify-self: flex-end;
|
||||
}
|
||||
|
||||
#about-list-item:hover {
|
||||
cursor: pointer;
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
#about-list-item:active {
|
||||
background-color: #ddd;
|
||||
}
|
||||
|
||||
#system-tasks-list-item:hover {
|
||||
cursor: pointer;
|
||||
background-color: #eee;
|
||||
}
|
||||
|
||||
#system-tasks-list-item:active {
|
||||
background-color: #ddd;
|
||||
}
|
||||
</style>
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 15 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 24 KiB |
@@ -1,90 +0,0 @@
|
||||
<template>
|
||||
<v-card class="about-dialog">
|
||||
|
||||
<div id="content-container">
|
||||
<div id="logo-container">
|
||||
<v-img id="logo-img" src="@/assets/langbot-logo.png" width="64" />
|
||||
|
||||
<div id="text-container">
|
||||
<div id="title">
|
||||
LangBot
|
||||
</div>
|
||||
|
||||
<div id="subtitle">
|
||||
版本 {{ proxy.$store.state.version }}
|
||||
</div>
|
||||
|
||||
<div id="copyright">
|
||||
版权所有 © 2024 RockChinQ
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<template v-slot:actions>
|
||||
<v-btn id="open-source-btn" text="代码仓库" prepend-icon="mdi-github"
|
||||
@click="openSource"></v-btn>
|
||||
<v-btn class="ml-auto" text="关闭" prepend-icon="mdi-close" @click="close"></v-btn>
|
||||
</template>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { getCurrentInstance } from 'vue'
|
||||
|
||||
const { proxy } = getCurrentInstance()
|
||||
|
||||
const emit = defineEmits(['close'])
|
||||
|
||||
const close = () => {
|
||||
emit('close')
|
||||
}
|
||||
|
||||
const openSource = () => {
|
||||
window.open('https://github.com/RockChinQ/LangBot', '_blank')
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.about-dialog {
|
||||
padding-top: 2rem;
|
||||
}
|
||||
|
||||
#content-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#logo-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#logo-img {}
|
||||
|
||||
#text-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#title {
|
||||
font-size: 1.2rem;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
#subtitle {
|
||||
font-size: 0.6rem;
|
||||
color: #3c3c3c;
|
||||
margin-top: 0.2rem;
|
||||
}
|
||||
|
||||
#copyright {
|
||||
font-size: 0.6rem;
|
||||
color: #3c3c3c;
|
||||
margin-top: 0.2rem;
|
||||
margin-bottom: 0.5rem;
|
||||
}
|
||||
</style>
|
||||
@@ -1,88 +0,0 @@
|
||||
<template>
|
||||
<v-dialog v-model="dialog" width="500" persistent>
|
||||
<v-card id="init-dialog">
|
||||
<v-card-title class="d-flex align-center" style="gap: 0.5rem;">
|
||||
<img src="@/assets/langbot-logo.png" height="32" width="32" />
|
||||
<span>系统初始化</span>
|
||||
</v-card-title>
|
||||
|
||||
<v-card-text>
|
||||
<p>请输入初始管理员邮箱和密码。</p>
|
||||
</v-card-text>
|
||||
|
||||
<v-card-text class="d-flex flex-column" style="gap: 0.5rem;">
|
||||
<v-text-field v-model="user" variant="outlined" label="管理员邮箱" :rules="[rules.required, rules.email]"
|
||||
clearable />
|
||||
<v-text-field v-model="password" variant="outlined" label="管理员密码" :rules="[rules.required]"
|
||||
type="password" clearable />
|
||||
</v-card-text>
|
||||
|
||||
<v-card-actions>
|
||||
<v-btn color="primary" variant="flat" @click="initialize" prepend-icon="mdi-check">初始化</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, inject, getCurrentInstance } from 'vue'
|
||||
|
||||
const { proxy } = getCurrentInstance()
|
||||
|
||||
const emit = defineEmits(['error', 'success', 'checkSystemInitialized'])
|
||||
|
||||
const dialog = ref(true)
|
||||
|
||||
const user = ref('')
|
||||
const password = ref('')
|
||||
|
||||
const snackbar = inject('snackbar')
|
||||
|
||||
const rules = {
|
||||
required: value => !!value || '必填项',
|
||||
email: value => {
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
||||
return emailRegex.test(value) || '请输入有效的邮箱地址'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
function checkEmailValid(email) {
|
||||
const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
||||
return regex.test(email)
|
||||
}
|
||||
|
||||
const initialize = () => {
|
||||
// 检查邮箱和密码是否为空
|
||||
if (user.value == undefined || password.value == undefined) {
|
||||
emit('error', '邮箱和密码不能为空')
|
||||
return
|
||||
}
|
||||
|
||||
if (user.value == '' || password.value == '') {
|
||||
emit('error', '邮箱和密码不能为空')
|
||||
return
|
||||
}
|
||||
|
||||
if (!checkEmailValid(user.value)) {
|
||||
emit('error', '请输入有效的邮箱地址')
|
||||
return
|
||||
}
|
||||
|
||||
proxy.$axios.post('/user/init', {
|
||||
user: user.value,
|
||||
password: password.value
|
||||
}).then(res => {
|
||||
emit('success', '系统初始化成功')
|
||||
|
||||
emit('checkSystemInitialized')
|
||||
})
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
#init-dialog {
|
||||
padding-top: 0.8rem;
|
||||
padding-inline: 0.5rem;
|
||||
}
|
||||
</style>
|
||||
@@ -1,76 +0,0 @@
|
||||
<template>
|
||||
<v-dialog v-model="dialog" width="350" persistent>
|
||||
<v-card id="login-dialog">
|
||||
<v-card-title class="d-flex align-center" style="gap: 0.5rem;">
|
||||
<img src="@/assets/langbot-logo.png" height="32" width="32" />
|
||||
<span>登录 LangBot</span>
|
||||
</v-card-title>
|
||||
|
||||
<v-card-text class="d-flex flex-column" style="gap: 0.5rem;margin-bottom: -2rem;margin-top: 1rem;">
|
||||
|
||||
<v-text-field v-model="user" variant="outlined" label="邮箱" :rules="[rules.required, rules.email]"
|
||||
clearable />
|
||||
<v-text-field v-model="password" variant="outlined" label="密码" :rules="[rules.required]"
|
||||
type="password" clearable />
|
||||
</v-card-text>
|
||||
|
||||
<v-card-actions>
|
||||
<v-btn color="primary" variant="flat" @click="login">登录</v-btn>
|
||||
</v-card-actions>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref, getCurrentInstance } from 'vue'
|
||||
|
||||
const { proxy } = getCurrentInstance()
|
||||
|
||||
const emit = defineEmits(['error', 'success', 'checkToken'])
|
||||
|
||||
const dialog = ref(true)
|
||||
|
||||
const user = ref('')
|
||||
const password = ref('')
|
||||
|
||||
const rules = {
|
||||
required: value => !!value || '必填项',
|
||||
email: value => {
|
||||
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
|
||||
return emailRegex.test(value) || '请输入有效的邮箱地址'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const login = () => {
|
||||
proxy.$axios.post('/user/auth', {
|
||||
user: user.value,
|
||||
password: password.value
|
||||
}).then(res => {
|
||||
if (res.data.code == 0) {
|
||||
emit('success', '登录成功')
|
||||
localStorage.setItem('user-token', res.data.data.token)
|
||||
setTimeout(() => {
|
||||
location.reload()
|
||||
}, 1000)
|
||||
} else {
|
||||
emit('error', res.data.msg)
|
||||
}
|
||||
}).catch(err => {
|
||||
if (err.response.data.msg) {
|
||||
emit('error', err.response.data.msg)
|
||||
} else {
|
||||
emit('error', '登录失败')
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
#login-dialog {
|
||||
padding-top: 0.8rem;
|
||||
padding-bottom: 0.5rem;
|
||||
padding-inline: 0.5rem;
|
||||
}
|
||||
</style>
|
||||
@@ -1,181 +0,0 @@
|
||||
<template>
|
||||
<div class="plugin-card">
|
||||
<div class="plugin-card-header">
|
||||
<div class="plugin-id">
|
||||
<div class="plugin-card-author">{{ plugin.author }} /</div>
|
||||
<div class="plugin-card-title">{{ plugin.name }}</div>
|
||||
</div>
|
||||
<div class="plugin-card-badges">
|
||||
<v-icon class="plugin-github-source" icon="mdi-github" v-if="plugin.repository != ''"
|
||||
@click="openGithubSource"></v-icon>
|
||||
</div>
|
||||
</div>
|
||||
<div class="plugin-card-description" >{{ plugin.description }}</div>
|
||||
|
||||
<div class="plugin-card-brief-info">
|
||||
<div class="plugin-card-brief-info-item">
|
||||
<v-icon id="plugin-stars-icon" icon="mdi-star" />
|
||||
<div id="plugin-stars-count">{{ plugin.stars }}</div>
|
||||
</div>
|
||||
<v-btn color="primary" @click="installPlugin" density="compact">安装</v-btn>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
plugin: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['install']);
|
||||
|
||||
const openGithubSource = () => {
|
||||
window.open("https://"+props.plugin.repository, '_blank');
|
||||
}
|
||||
|
||||
const installPlugin = () => {
|
||||
emit('install', props.plugin);
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.plugin-card {
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
padding: 0.8rem;
|
||||
padding-left: 1rem;
|
||||
margin: 1rem 0;
|
||||
background-color: white;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
height: 10rem;
|
||||
}
|
||||
|
||||
.plugin-card-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.plugin-card-author {
|
||||
font-size: 0.8rem;
|
||||
color: #666;
|
||||
font-weight: 500;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.plugin-card-title {
|
||||
font-size: 0.9rem;
|
||||
font-weight: 600;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.plugin-card-description {
|
||||
font-size: 0.7rem;
|
||||
color: #666;
|
||||
font-weight: 500;
|
||||
margin-top: -1rem;
|
||||
height: 2rem;
|
||||
/* 超出部分自动换行,最多两行 */
|
||||
text-overflow: ellipsis;
|
||||
overflow-y: hidden;
|
||||
white-space: wrap;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.plugin-card-badges {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.plugin-github-source {
|
||||
cursor: pointer;
|
||||
color: #222;
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
.plugin-disabled {
|
||||
font-size: 0.7rem;
|
||||
font-weight: 500;
|
||||
height: 1.3rem;
|
||||
padding-inline: 0.4rem;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
|
||||
.plugin-card-brief-info {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
/* background-color: #f0f0f0; */
|
||||
gap: 0.8rem;
|
||||
margin-left: -0.2rem;
|
||||
margin-bottom: 0rem;
|
||||
}
|
||||
|
||||
.plugin-card-events {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
|
||||
.plugin-card-events-icon {
|
||||
font-size: 1.8rem;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.plugin-card-events-count {
|
||||
font-size: 1.2rem;
|
||||
font-weight: 600;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.plugin-card-functions {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
|
||||
.plugin-card-functions-icon {
|
||||
font-size: 1.6rem;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.plugin-card-functions-count {
|
||||
font-size: 1.2rem;
|
||||
font-weight: 600;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.plugin-card-brief-info-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
|
||||
#plugin-stars-icon {
|
||||
color: #0073ff;
|
||||
}
|
||||
|
||||
#plugin-stars-count {
|
||||
margin-top: 0.1rem;
|
||||
font-weight: 700;
|
||||
color: #0073ff;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.plugin-card-brief-info-item:hover .plugin-card-brief-info-item-icon {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
|
||||
</style>
|
||||
@@ -1,228 +0,0 @@
|
||||
<template>
|
||||
<div id="marketplace-container">
|
||||
<div id="marketplace-search-bar">
|
||||
|
||||
<span style="width: 14rem;">
|
||||
<v-text-field id="marketplace-search-bar-search-input" variant="solo" v-model="proxy.$store.state.marketplaceParams.query" label="搜索"
|
||||
density="compact" @update:model-value="updateSearch" />
|
||||
</span>
|
||||
<!--下拉选择排序-->
|
||||
<span style="width: 10rem;">
|
||||
<v-select id="marketplace-search-bar-sort-select" v-model="sort" :items="sortItems" variant="solo"
|
||||
label="排序" density="compact" @update:model-value="updateSort" />
|
||||
</span>
|
||||
<span style="margin-left: 1rem;">
|
||||
<div id="marketplace-search-bar-total-plugins-count">
|
||||
共 {{ proxy.$store.state.marketplaceTotalPluginsCount }} 个插件
|
||||
</div>
|
||||
</span>
|
||||
<span style="margin-left: 1rem;">
|
||||
<!-- 分页 -->
|
||||
<v-pagination style="width: 14rem;" v-model="proxy.$store.state.marketplaceParams.page"
|
||||
:length="proxy.$store.state.marketplaceTotalPages" variant="solo" density="compact"
|
||||
total-visible="4" @update:model-value="updatePage" />
|
||||
</span>
|
||||
</div>
|
||||
<div id="marketplace-plugins-container" ref="pluginsContainer">
|
||||
<div id="marketplace-plugins-container-inner">
|
||||
<MarketPluginCard v-for="plugin in proxy.$store.state.marketplacePlugins" :key="plugin.id" :plugin="plugin" @install="installPlugin" />
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import MarketPluginCard from './MarketPluginCard.vue'
|
||||
|
||||
import { ref, getCurrentInstance, onMounted } from 'vue'
|
||||
|
||||
import { inject } from "vue";
|
||||
|
||||
const snackbar = inject('snackbar');
|
||||
|
||||
const { proxy } = getCurrentInstance()
|
||||
|
||||
const pluginsContainer = ref(null)
|
||||
|
||||
const sortItems = ref([
|
||||
'最近新增',
|
||||
'最多星标',
|
||||
'最近更新',
|
||||
])
|
||||
|
||||
const sortParams = ref({
|
||||
'最近新增': {
|
||||
sort_by: 'created_at',
|
||||
sort_order: 'DESC',
|
||||
},
|
||||
'最多星标': {
|
||||
sort_by: 'stars',
|
||||
sort_order: 'DESC',
|
||||
},
|
||||
'最近更新': {
|
||||
sort_by: 'pushed_at',
|
||||
sort_order: 'DESC',
|
||||
}
|
||||
})
|
||||
|
||||
const sort = ref(sortItems.value[0])
|
||||
|
||||
proxy.$store.state.marketplaceParams.sort_by = sortParams.value[sort.value].sort_by
|
||||
proxy.$store.state.marketplaceParams.sort_order = sortParams.value[sort.value].sort_order
|
||||
|
||||
const updateSort = (value) => {
|
||||
console.log(value)
|
||||
proxy.$store.state.marketplaceParams.sort_by = sortParams.value[value].sort_by
|
||||
proxy.$store.state.marketplaceParams.sort_order = sortParams.value[value].sort_order
|
||||
proxy.$store.state.marketplaceParams.page = 1
|
||||
|
||||
console.log(proxy.$store.state.marketplaceParams)
|
||||
fetchMarketplacePlugins()
|
||||
}
|
||||
|
||||
const updatePage = (value) => {
|
||||
proxy.$store.state.marketplaceParams.page = value
|
||||
fetchMarketplacePlugins()
|
||||
}
|
||||
|
||||
const updateSearch = (value) => {
|
||||
console.log(value)
|
||||
proxy.$store.state.marketplaceParams.query = value
|
||||
proxy.$store.state.marketplaceParams.page = 1
|
||||
fetchMarketplacePlugins()
|
||||
}
|
||||
|
||||
const calculatePluginsPerPage = () => {
|
||||
if (!pluginsContainer.value) return 10
|
||||
|
||||
const containerWidth = pluginsContainer.value.clientWidth
|
||||
const containerHeight = pluginsContainer.value.clientHeight
|
||||
|
||||
console.log(containerWidth, containerHeight)
|
||||
|
||||
// 每个卡片宽度18rem + gap 16px
|
||||
const cardWidth = 18 * 16 + 16 // rem转px
|
||||
// 每个卡片高度9rem + gap 16px
|
||||
const cardHeight = 9 * 16 + 16
|
||||
|
||||
// 计算每行可以放几个卡片
|
||||
const cardsPerRow = Math.floor(containerWidth / cardWidth)
|
||||
// 计算每行可以放几行
|
||||
const rows = Math.floor(containerHeight / cardHeight)
|
||||
|
||||
// 计算每页总数
|
||||
const perPage = cardsPerRow * rows
|
||||
|
||||
proxy.$store.state.marketplaceParams.per_page = perPage > 0 ? perPage : 10
|
||||
}
|
||||
|
||||
const fetchMarketplacePlugins = async () => {
|
||||
calculatePluginsPerPage()
|
||||
proxy.$axios.post('https://space.langbot.app/api/v1/market/plugins', {
|
||||
query: proxy.$store.state.marketplaceParams.query,
|
||||
sort_by: proxy.$store.state.marketplaceParams.sort_by,
|
||||
sort_order: proxy.$store.state.marketplaceParams.sort_order,
|
||||
page: proxy.$store.state.marketplaceParams.page,
|
||||
page_size: proxy.$store.state.marketplaceParams.per_page,
|
||||
}).then(response => {
|
||||
console.log(response.data)
|
||||
if (response.data.code != 0) {
|
||||
snackbar.error(response.data.msg)
|
||||
return
|
||||
}
|
||||
|
||||
// 解析出 name 和 author
|
||||
response.data.data.plugins.forEach(plugin => {
|
||||
plugin.name = plugin.repository.split('/')[2]
|
||||
plugin.author = plugin.repository.split('/')[1]
|
||||
})
|
||||
|
||||
proxy.$store.state.marketplacePlugins = response.data.data.plugins
|
||||
proxy.$store.state.marketplaceTotalPluginsCount = response.data.data.total
|
||||
|
||||
let totalPages = Math.floor(response.data.data.total / proxy.$store.state.marketplaceParams.per_page)
|
||||
if (response.data.data.total % proxy.$store.state.marketplaceParams.per_page != 0) {
|
||||
totalPages += 1
|
||||
}
|
||||
proxy.$store.state.marketplaceTotalPages = totalPages
|
||||
}).catch(error => {
|
||||
snackbar.error(error)
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
calculatePluginsPerPage()
|
||||
fetchMarketplacePlugins()
|
||||
|
||||
// 监听窗口大小变化
|
||||
window.addEventListener('resize', () => {
|
||||
calculatePluginsPerPage()
|
||||
fetchMarketplacePlugins()
|
||||
})
|
||||
})
|
||||
|
||||
const emit = defineEmits(['installPlugin'])
|
||||
|
||||
const installPlugin = (plugin) => {
|
||||
emit('installPlugin', plugin.repository)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
#marketplace-container {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
#marketplace-search-bar {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
margin-top: 1rem;
|
||||
padding-right: 1rem;
|
||||
gap: 1rem;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#marketplace-search-bar-search-input {
|
||||
position: relative;
|
||||
left: 1rem;
|
||||
width: 10rem;
|
||||
}
|
||||
|
||||
#marketplace-search-bar-total-plugins-count {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 500;
|
||||
margin-top: 0.5rem;
|
||||
color: #666;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.plugin-card {
|
||||
width: 18rem;
|
||||
height: 9rem;
|
||||
}
|
||||
|
||||
#marketplace-plugins-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px;
|
||||
margin-inline: 0rem;
|
||||
width: 100%;
|
||||
height: calc(100vh - 16rem);
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
#marketplace-plugins-container-inner {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px;
|
||||
}
|
||||
</style>
|
||||
@@ -1,75 +0,0 @@
|
||||
<template>
|
||||
<div id="number-field-data">
|
||||
<div class="number-field-data-item" :style="{ color: color }">
|
||||
{{ number }}
|
||||
</div>
|
||||
|
||||
<div class="number-field-data-item-title" :style="{ marginTop: link ? '0rem' : '0.1rem', fontSize: link ? '0.9rem' : '1.1rem', marginBottom: link ? '0rem' : '0.5rem' }">
|
||||
{{ title }}
|
||||
</div>
|
||||
<button class="number-field-data-item-link-text" v-if="link" @click="linkClick">{{ linkText }}</button>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
required: true
|
||||
},
|
||||
number: {
|
||||
type: Number,
|
||||
required: true
|
||||
},
|
||||
color: {
|
||||
type: String,
|
||||
default: '#4271bf'
|
||||
},
|
||||
link: {
|
||||
type: String,
|
||||
default: ''
|
||||
},
|
||||
linkText: {
|
||||
type: String,
|
||||
default: '查看详情'
|
||||
}
|
||||
})
|
||||
|
||||
import { getCurrentInstance } from 'vue'
|
||||
|
||||
const { proxy } = getCurrentInstance()
|
||||
|
||||
const linkClick = (e) => {
|
||||
proxy.$router.push(props.link)
|
||||
}
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
|
||||
#number-field-data {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
/* justify-content: center; */
|
||||
}
|
||||
|
||||
.number-field-data-item {
|
||||
font-size: 2.2rem;
|
||||
font-weight: 600;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.number-field-data-item-title {
|
||||
font-weight: 600;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.number-field-data-item-link-text {
|
||||
text-decoration: none;
|
||||
appearance: none;
|
||||
font-weight: 600;
|
||||
color: #4271bf;
|
||||
font-size: 0.6rem;
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -1,51 +0,0 @@
|
||||
<template>
|
||||
<span class="title-container">
|
||||
<h2 id="page-title">{{ title }}</h2>
|
||||
<v-icon @click="refresh" id="refresh-icon" icon="mdi-refresh" />
|
||||
</span>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
defineProps({
|
||||
title: {
|
||||
type: String,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
const emit = defineEmits(['refresh'])
|
||||
|
||||
const refresh = () => {
|
||||
emit('refresh')
|
||||
}
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
#page-title {
|
||||
font-weight: 500;
|
||||
margin-left: 1.4rem;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.title-container {
|
||||
display: flex;
|
||||
justify-content: flex-start;
|
||||
align-items: center;
|
||||
padding-top: 1rem;
|
||||
}
|
||||
|
||||
#refresh-icon {
|
||||
padding-top: 0.1rem;
|
||||
margin-left: 0.5rem;
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
#refresh-icon:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#refresh-icon:active {
|
||||
color: #6c6c6c;
|
||||
}
|
||||
</style>
|
||||
@@ -1,257 +0,0 @@
|
||||
<template>
|
||||
<div class="plugin-card">
|
||||
<div class="plugin-card-header">
|
||||
<div class="plugin-id">
|
||||
<div class="plugin-card-author">{{ plugin.author }} /</div>
|
||||
<div class="plugin-card-title">{{ plugin.name }}</div>
|
||||
</div>
|
||||
<div class="plugin-card-badges">
|
||||
<v-icon class="plugin-github-source" icon="mdi-github" v-if="plugin.source != ''"
|
||||
@click="openGithubSource"></v-icon>
|
||||
<v-chip class="plugin-disabled" v-if="!plugin.enabled" color="error" variant="outlined"
|
||||
density="compact">已禁用</v-chip>
|
||||
<v-chip class="plugin-version" color="primary" density="compact" variant="flat">v{{ plugin.version
|
||||
}}</v-chip>
|
||||
</div>
|
||||
</div>
|
||||
<div class="plugin-card-description">{{ plugin.description }}</div>
|
||||
|
||||
<div class="plugin-card-brief-info">
|
||||
<div class="plugin-card-brief-info-item">
|
||||
<v-tooltip text="已注册的事件处理器" location="bottom">
|
||||
<template v-slot:activator="{ props }">
|
||||
<div class="plugin-card-events" v-bind="props">
|
||||
<v-icon class="plugin-card-events-icon" icon="mdi-link-box-variant-outline" />
|
||||
<div class="plugin-card-events-count">{{ Object.keys(plugin.event_handlers).length }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</v-tooltip>
|
||||
<v-tooltip text="已注册的内容函数" location="bottom">
|
||||
<template v-slot:activator="{ props }">
|
||||
<div class="plugin-card-functions" v-bind="props">
|
||||
<v-icon class="plugin-card-functions-icon" icon="mdi-tools" />
|
||||
<div class="plugin-card-functions-count">{{ plugin.content_functions.length }}</div>
|
||||
</div>
|
||||
</template>
|
||||
</v-tooltip>
|
||||
</div>
|
||||
<v-menu class="plugin-card-menu">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-icon class="plugin-card-menu-btn" icon="mdi-cog" v-bind="props" variant="text" size="small"/>
|
||||
</template>
|
||||
<v-list>
|
||||
<template v-for="item in menuItems" :key="item.title">
|
||||
<v-list-item v-if="item.condition(plugin)" @click="item.action">
|
||||
<v-list-item-title>{{ item.title }}</v-list-item-title>
|
||||
</v-list-item>
|
||||
</template>
|
||||
</v-list>
|
||||
</v-menu>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
const props = defineProps({
|
||||
plugin: {
|
||||
type: Object,
|
||||
required: true
|
||||
},
|
||||
});
|
||||
|
||||
const emit = defineEmits(['toggle', 'update', 'remove']);
|
||||
|
||||
const openGithubSource = () => {
|
||||
window.open(props.plugin.source, '_blank');
|
||||
}
|
||||
|
||||
const togglePlugin = () => {
|
||||
emit('toggle', props.plugin);
|
||||
}
|
||||
|
||||
const updatePlugin = () => {
|
||||
emit('update', props.plugin);
|
||||
}
|
||||
|
||||
const removePlugin = () => {
|
||||
emit('remove', props.plugin);
|
||||
}
|
||||
|
||||
const menuItems = [
|
||||
{
|
||||
title: '禁用',
|
||||
condition: (plugin) => plugin.enabled,
|
||||
action: togglePlugin
|
||||
},
|
||||
{
|
||||
title: '启用',
|
||||
condition: (plugin) => !plugin.enabled,
|
||||
action: togglePlugin
|
||||
},
|
||||
{
|
||||
title: '更新',
|
||||
condition: (plugin) => plugin.source != '',
|
||||
action: updatePlugin
|
||||
},
|
||||
{
|
||||
title: '删除',
|
||||
condition: (plugin) => true,
|
||||
action: removePlugin
|
||||
}
|
||||
]
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.plugin-card {
|
||||
border: 1px solid #e0e0e0;
|
||||
border-radius: 8px;
|
||||
padding: 0.8rem;
|
||||
padding-left: 1rem;
|
||||
margin: 1rem 0;
|
||||
background-color: white;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 10rem;
|
||||
}
|
||||
|
||||
.plugin-card-header {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.plugin-card-author {
|
||||
font-size: 0.8rem;
|
||||
color: #666;
|
||||
font-weight: 500;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.plugin-card-title {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 600;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.plugin-card-description {
|
||||
font-size: 0.7rem;
|
||||
color: #666;
|
||||
font-weight: 500;
|
||||
margin-top: 0rem;
|
||||
height: 2rem;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.plugin-card-badges {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 0.5rem;
|
||||
}
|
||||
|
||||
.plugin-github-source {
|
||||
cursor: pointer;
|
||||
color: #222;
|
||||
font-size: 1.3rem;
|
||||
}
|
||||
|
||||
.plugin-disabled {
|
||||
font-size: 0.7rem;
|
||||
font-weight: 500;
|
||||
height: 1.3rem;
|
||||
padding-inline: 0.4rem;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.plugin-version {
|
||||
font-size: 0.7rem;
|
||||
font-weight: 700;
|
||||
height: 1.3rem;
|
||||
padding-inline: 0.5rem;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.plugin-card-brief-info {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
/* background-color: #f0f0f0; */
|
||||
gap: 0.8rem;
|
||||
margin-left: -0.2rem;
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.plugin-card-events {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
|
||||
.plugin-card-events-icon {
|
||||
font-size: 1.8rem;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.plugin-card-events-count {
|
||||
font-size: 1.2rem;
|
||||
font-weight: 600;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.plugin-card-functions {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
|
||||
.plugin-card-functions-icon {
|
||||
font-size: 1.6rem;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.plugin-card-functions-count {
|
||||
font-size: 1.2rem;
|
||||
font-weight: 600;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.plugin-card-brief-info-item {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
gap: 0.4rem;
|
||||
}
|
||||
|
||||
.plugin-card-brief-info-item:hover {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.plugin-card-brief-info-item:hover .plugin-card-brief-info-item-icon {
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.plugin-card-menu {
|
||||
margin-top: 0.5rem;
|
||||
}
|
||||
|
||||
.plugin-card-menu-btn {
|
||||
font-size: 1.4rem;
|
||||
margin-top: 0.2rem;
|
||||
font-weight: 400;
|
||||
padding: 0rem;
|
||||
color: #3265ba;
|
||||
}
|
||||
|
||||
.plugin-card-menu-btn:hover {
|
||||
color: #4271bf;
|
||||
}
|
||||
|
||||
.plugin-card-menu-btn:active {
|
||||
color: rgb(0, 47, 104);
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -1,287 +0,0 @@
|
||||
<template>
|
||||
<v-card class="config-tab-toolbar">
|
||||
<v-tooltip :text="currentManagerSchema == null ? '仅配置文件管理器提供了 JSON Schema 时支持可视化配置' : '切换编辑模式'" location="top">
|
||||
<template v-slot:activator="{ props }">
|
||||
|
||||
<v-btn-toggle id="config-type-toggle" color="primary" v-model="configType" mandatory v-bind="props"
|
||||
density="compact">
|
||||
|
||||
<v-btn class="config-type-toggle-btn" value="ui" :readonly="currentManagerSchema == null"
|
||||
density="compact">
|
||||
<v-icon>mdi-view-dashboard-edit-outline</v-icon>
|
||||
</v-btn>
|
||||
<v-btn class="config-type-toggle-btn" value="json" :readonly="currentManagerSchema == null"
|
||||
density="compact">
|
||||
<v-icon>mdi-code-json</v-icon>
|
||||
</v-btn>
|
||||
</v-btn-toggle>
|
||||
</template>
|
||||
</v-tooltip>
|
||||
|
||||
<div id="file-operation-toolbar">
|
||||
<v-btn @click="reset" color="warning" prepend-icon="mdi-undo"
|
||||
:disabled="!modified && configType == 'json'">重置</v-btn>
|
||||
<v-btn @click="saveAndApply" color="primary" prepend-icon="mdi-content-save-outline"
|
||||
:disabled="!modified && configType == 'json'">应用</v-btn>
|
||||
</div>
|
||||
</v-card>
|
||||
|
||||
<div id="config-tab-content">
|
||||
|
||||
<div id="config-tab-content-ui" v-if="configType == 'ui'">
|
||||
<v-form id="config-tab-content-ui-form">
|
||||
<vjsf id="config-tab-content-ui-form-vjsf" :schema="currentManagerSchema" v-model="currentManagerData"
|
||||
:options="VJSFOptions" />
|
||||
</v-form>
|
||||
</div>
|
||||
|
||||
<v-card id="config-tab-content-json" v-if="configType == 'json'">
|
||||
<JsonEditorVue id="config-tab-content-json-json-editor-vue" v-model="currentManagerData" mode="text"
|
||||
@change="onInput" />
|
||||
</v-card>
|
||||
|
||||
<div id="config-tab-json-doc-link"
|
||||
v-if="configType == 'json' && currentManagerDocLink != undefined && currentManagerDocLink != ''">*配置文件格式请查看
|
||||
<a :href="currentManagerDocLink" target="_blank">文档</a>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
import { ref, getCurrentInstance, onMounted, inject } from 'vue'
|
||||
import JsonEditorVue from 'json-editor-vue';
|
||||
|
||||
import Vjsf from '@koumoul/vjsf';
|
||||
|
||||
const VJSFOptions = {
|
||||
"context": {},
|
||||
"width": 1208,
|
||||
"readOnly": false,
|
||||
"summary": false,
|
||||
"density": "comfortable",
|
||||
"indent": true,
|
||||
"titleDepth": 4,
|
||||
"validateOn": "input",
|
||||
"initialValidation": "withData",
|
||||
"updateOn": "input",
|
||||
"debounceInputMs": 300,
|
||||
"defaultOn": "empty",
|
||||
"removeAdditional": "error",
|
||||
"autofocus": false,
|
||||
"readOnlyPropertiesMode": "show",
|
||||
"pluginsOptions": {},
|
||||
"locale": "en",
|
||||
"messages": {
|
||||
"errorOneOf": "请选择一个",
|
||||
"errorRequired": "必填信息",
|
||||
"addItem": "添加",
|
||||
"delete": "删除",
|
||||
"edit": "编辑",
|
||||
"close": "关闭",
|
||||
"duplicate": "复制",
|
||||
"sort": "排序",
|
||||
"up": "向上移动",
|
||||
"down": "向下移动",
|
||||
"showHelp": "显示帮助信息",
|
||||
"mdeLink1": "[链接标题",
|
||||
"mdeLink2": "](链接地址)",
|
||||
"mdeImg1": "",
|
||||
"mdeTable1": "",
|
||||
"mdeTable2": "\n\n| 列 1 | 列 2 | 列 3 |\n| -------- | -------- | -------- |\n| 文本 | 文本 | 文本 |\n\n",
|
||||
"bold": "加粗",
|
||||
"italic": "斜体",
|
||||
"heading": "标题",
|
||||
"quote": "引用",
|
||||
"unorderedList": "无序列表",
|
||||
"orderedList": "有序列表",
|
||||
"createLink": "创建链接",
|
||||
"insertImage": "插入图片",
|
||||
"createTable": "创建表格",
|
||||
"preview": "预览",
|
||||
"mdeGuide": "文档",
|
||||
"undo": "撤销",
|
||||
"redo": "重做"
|
||||
}
|
||||
}
|
||||
|
||||
const snackbar = inject('snackbar');
|
||||
|
||||
const { proxy } = getCurrentInstance()
|
||||
|
||||
const props = defineProps({
|
||||
name: String
|
||||
})
|
||||
|
||||
const currentManagerData = ref({})
|
||||
const currentManagerSchema = ref(null)
|
||||
const currentManagerDocLink = ref(null)
|
||||
const configType = ref('')
|
||||
const modified = ref(false)
|
||||
|
||||
const fetchCurrentManagerData = (name) => {
|
||||
console.log(name)
|
||||
return new Promise((resolve, reject) => {
|
||||
proxy.$axios.get(`/settings/${name}`).then(response => {
|
||||
console.log(response.data.data)
|
||||
currentManagerData.value = response.data.data.manager.data
|
||||
currentManagerSchema.value = response.data.data.manager.schema
|
||||
currentManagerDocLink.value = response.data.data.manager.doc_link
|
||||
resolve()
|
||||
}).catch(error => {
|
||||
snackbar.error(error)
|
||||
reject(error)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const autoSwitchConfigType = () => {
|
||||
console.log(currentManagerSchema == null)
|
||||
if (currentManagerSchema.value == null) {
|
||||
configType.value = 'json'
|
||||
} else {
|
||||
configType.value = 'ui'
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
const isJsonValid = ref(true)
|
||||
const errorMessage = ref('')
|
||||
|
||||
const checkJsonValid = () => {
|
||||
try {
|
||||
JSON.parse(currentManagerData.value)
|
||||
isJsonValid.value = true
|
||||
errorMessage.value = ''
|
||||
} catch (error) {
|
||||
isJsonValid.value = false
|
||||
errorMessage.value = error.message
|
||||
}
|
||||
}
|
||||
|
||||
const onInput = () => {
|
||||
modified.value = true
|
||||
checkJsonValid()
|
||||
}
|
||||
|
||||
const saveAndApply = () => {
|
||||
if (configType.value == 'json') {
|
||||
checkJsonValid()
|
||||
}
|
||||
if (!isJsonValid.value) {
|
||||
snackbar.error('JSON 格式不正确: ' + errorMessage.value)
|
||||
return
|
||||
}
|
||||
if (configType.value == 'json') {
|
||||
currentManagerData.value = JSON.parse(currentManagerData.value)
|
||||
}
|
||||
|
||||
proxy.$axios.put(`/settings/${props.name}/data`, {
|
||||
data: currentManagerData.value
|
||||
}).then(response => {
|
||||
if (response.data.code != 0) {
|
||||
snackbar.error(response.data.msg)
|
||||
return
|
||||
}
|
||||
fetchCurrentManagerData(props.name).then(() => {
|
||||
modified.value = false
|
||||
snackbar.success('应用成功')
|
||||
}).catch(error => {
|
||||
snackbar.error(error)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const reset = () => {
|
||||
fetchCurrentManagerData(props.name).then(() => {
|
||||
snackbar.success('重置成功')
|
||||
modified.value = false
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(() => {
|
||||
console.log(props.name)
|
||||
fetchCurrentManagerData(props.name).then(() => {
|
||||
autoSwitchConfigType()
|
||||
})
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.config-tab-window {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.config-tab-toolbar {
|
||||
margin: 0.5rem;
|
||||
height: 3.2rem;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#file-operation-toolbar {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-end;
|
||||
align-items: center;
|
||||
gap: 0.5rem;
|
||||
margin-right: 0.5rem;
|
||||
}
|
||||
|
||||
#config-type-toggle {
|
||||
margin: 0.5rem;
|
||||
box-shadow: 0 0 0 2px #dddddd;
|
||||
}
|
||||
|
||||
#config-tab-content {
|
||||
margin: 0.2rem;
|
||||
height: calc(100% - 1rem);
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#config-tab-content-ui {
|
||||
margin: 0.5rem;
|
||||
height: calc(100vh - 15rem);
|
||||
margin-top: 1rem;
|
||||
overflow: hidden;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#config-tab-content-ui-form {
|
||||
height: 100%;
|
||||
width: calc(100% - 1.5rem);
|
||||
margin-left: 0.5rem;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
#config-tab-content-ui-form-vjsf {
|
||||
height: 100%;
|
||||
width: calc(100% - 1rem);
|
||||
}
|
||||
|
||||
#config-tab-content-json {
|
||||
margin: 0.5rem;
|
||||
height: calc(100vh - 16rem);
|
||||
margin-top: 1rem;
|
||||
}
|
||||
|
||||
#config-tab-content-json-json-editor-vue {
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#config-tab-json-doc-link {
|
||||
margin: 0rem;
|
||||
margin-left: 0.6rem;
|
||||
height: 1.5rem;
|
||||
margin-top: 0.2rem;
|
||||
font-size: 0.8rem;
|
||||
color: rgb(26, 98, 214);
|
||||
}
|
||||
</style>
|
||||
@@ -1,144 +0,0 @@
|
||||
<template>
|
||||
<div class="task-card">
|
||||
<div class="task-card-icon">
|
||||
<v-progress-circular :size="25" :width="2" indeterminate v-if="task.runtime.state == 'PENDING'" />
|
||||
<v-icon v-else-if="task.runtime.state == 'FINISHED' && task.runtime.exception == null"
|
||||
style="color: #4caf50;" :size="25" icon="mdi-check" />
|
||||
<v-icon v-else-if="task.runtime.state == 'CANCELLED'" style="color: #f44336;" :size="25" icon="mdi-close" />
|
||||
|
||||
<v-icon v-else-if="task.runtime.state == 'FINISHED' && task.runtime.exception != null" style="color: #f44336;"
|
||||
:size="25" icon="mdi-alert-circle-outline" v-bind="activatorProps" />
|
||||
</div>
|
||||
|
||||
<div class="task-card-content">
|
||||
<div class="task-card-kind">{{ task.kind }}</div>
|
||||
<div class="task-card-label">{{ task.label }}</div>
|
||||
<v-chip class="task-card-action" color="primary" variant="outlined" size="small" density="compact"
|
||||
v-if="task.runtime.state == 'PENDING'">正在执行: {{ task.task_context.current_action }}</v-chip>
|
||||
<v-chip class="task-card-action" color="success" variant="outlined" size="small" density="compact"
|
||||
v-else-if="task.runtime.state == 'FINISHED' && task.runtime.exception == null">完成</v-chip>
|
||||
|
||||
<v-dialog max-width="500" persistent v-model="exceptionDialogShow">
|
||||
<template v-slot:activator="{ props: activatorProps }">
|
||||
<v-chip class="task-card-action" color="error" variant="outlined" size="small" density="compact"
|
||||
v-if="task.runtime.state == 'FINISHED' && task.runtime.exception != null"
|
||||
v-bind="activatorProps">{{ task.runtime.exception }}</v-chip>
|
||||
</template>
|
||||
|
||||
<v-card prepend-icon="mdi-alert-circle-outline"
|
||||
:text="task.runtime.exception_traceback"
|
||||
title="任务执行失败">
|
||||
<template v-slot:actions>
|
||||
<v-spacer></v-spacer>
|
||||
|
||||
<v-btn @click="exceptionDialogShow = false" prepend-icon="mdi-close">
|
||||
关闭
|
||||
</v-btn>
|
||||
</template>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</div>
|
||||
|
||||
<div class="task-card-actions">
|
||||
<!-- <v-icon icon="mdi-details" /> -->
|
||||
<v-dialog max-width="500" persistent v-model="detailsDialogShow">
|
||||
<template v-slot:activator="{ props: activatorProps }">
|
||||
<v-btn icon="mdi-file-outline" variant="text" v-bind="activatorProps" />
|
||||
</template>
|
||||
|
||||
<v-card prepend-icon="mdi-file-outline" id="task-details-card"
|
||||
:title="'任务执行日志 - '+task.label">
|
||||
|
||||
<div id="task-details-log-container">
|
||||
<textarea id="task-details-log" v-model="task.task_context.log" readonly />
|
||||
|
||||
<v-spacer></v-spacer>
|
||||
</div>
|
||||
<template v-slot:actions>
|
||||
|
||||
<v-btn @click="detailsDialogShow = false" prepend-icon="mdi-close">
|
||||
关闭
|
||||
</v-btn>
|
||||
</template>
|
||||
</v-card>
|
||||
</v-dialog>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
defineProps({
|
||||
task: {
|
||||
type: Object,
|
||||
required: true
|
||||
}
|
||||
})
|
||||
|
||||
import { ref } from 'vue'
|
||||
|
||||
const exceptionDialogShow = ref(false)
|
||||
const detailsDialogShow = ref(false)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.task-card {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
justify-content: flex-start;
|
||||
width: 100%;
|
||||
padding-inline: 0.5rem;
|
||||
}
|
||||
|
||||
.task-card-icon {
|
||||
margin-right: 1rem;
|
||||
margin-left: 0.8rem;
|
||||
}
|
||||
|
||||
.task-card-content {
|
||||
flex: 1;
|
||||
margin-left: 0.4rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 0.02rem;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.task-card-kind {
|
||||
font-size: 0.6rem;
|
||||
color: #666;
|
||||
}
|
||||
|
||||
.task-card-label {
|
||||
font-size: 0.8rem;
|
||||
color: #333;
|
||||
}
|
||||
|
||||
.task-card-action {
|
||||
font-size: 0.6rem;
|
||||
width: fit-content;
|
||||
padding-inline: 0.3rem;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
.task-card-actions {
|
||||
justify-self: flex-end;
|
||||
}
|
||||
|
||||
|
||||
#task-details-log {
|
||||
width: calc(100% - 2rem);
|
||||
height: 10rem;
|
||||
resize: none;
|
||||
border: none;
|
||||
padding: 0.6rem;
|
||||
font-size: 0.8rem;
|
||||
outline: none;
|
||||
overflow: auto;
|
||||
appearance: none;
|
||||
background-color: #f6f6f6;
|
||||
margin-inline: 1rem;
|
||||
}
|
||||
</style>
|
||||
@@ -1,91 +0,0 @@
|
||||
<template>
|
||||
<v-card class="task-dialog" prepend-icon="mdi-align-horizontal-left" title="任务列表">
|
||||
<v-list id="task-list" v-if="taskList.length > 0">
|
||||
<TaskCard class="task-card" v-for="task in taskList" :key="task.id" :task="task" />
|
||||
</v-list>
|
||||
<div v-else><v-alert color="warning" icon="$warning" title="暂无任务" text="暂无已添加的用户任务项" density="compact" style="margin-inline: 1rem;"></v-alert></div>
|
||||
|
||||
<template v-slot:actions>
|
||||
<v-btn class="ml-auto" text="关闭" prepend-icon="mdi-close" @click="close"></v-btn>
|
||||
</template>
|
||||
</v-card>
|
||||
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
defineProps({
|
||||
})
|
||||
|
||||
const emit = defineEmits(['close'])
|
||||
|
||||
import TaskCard from '@/components/TaskCard.vue'
|
||||
|
||||
import { ref, onMounted, onUnmounted, getCurrentInstance } from 'vue'
|
||||
|
||||
const { proxy } = getCurrentInstance()
|
||||
|
||||
import { inject } from 'vue'
|
||||
|
||||
const snackbar = inject('snackbar')
|
||||
|
||||
const close = () => {
|
||||
emit('close')
|
||||
}
|
||||
|
||||
const taskList = ref([])
|
||||
|
||||
const refresh = () => {
|
||||
proxy.$axios.get('/system/tasks', {
|
||||
params: {
|
||||
type: 'user'
|
||||
}
|
||||
}).then(response => {
|
||||
if (response.data.code != 0) {
|
||||
snackbar.error(response.data.message)
|
||||
return
|
||||
}
|
||||
taskList.value = response.data.data.tasks
|
||||
|
||||
// 倒序
|
||||
taskList.value.reverse()
|
||||
}).catch(error => {
|
||||
snackbar.error(error.message)
|
||||
})
|
||||
}
|
||||
|
||||
let refreshTask = null
|
||||
onMounted(() => {
|
||||
refresh()
|
||||
refreshTask = setInterval(refresh, 1000)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
clearInterval(refreshTask)
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
.task-dialog {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
#task-list {
|
||||
max-height: 20rem;
|
||||
overflow-y: auto;
|
||||
margin-inline: 1rem;
|
||||
width: calc(100% - 2.2rem);
|
||||
padding-inline: 0.6rem;
|
||||
}
|
||||
|
||||
.task-card {
|
||||
/* margin-bottom: 0.1rem; */
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
/* box-shadow: 1px 1px 1px 1px rgba(0, 0, 0, 0.1); */
|
||||
height: 4rem;
|
||||
/* border: 0.08rem solid #ccc; */
|
||||
box-shadow: 0.1rem 0.1rem 0.2rem 0.05rem #ccc;
|
||||
background-color: #ffffff;
|
||||
}
|
||||
</style>
|
||||
@@ -1,20 +0,0 @@
|
||||
/**
|
||||
* main.js
|
||||
*
|
||||
* Bootstraps Vuetify and other plugins then mounts the App`
|
||||
*/
|
||||
|
||||
// Plugins
|
||||
import { registerPlugins } from '@/plugins'
|
||||
|
||||
// Components
|
||||
import App from './App.vue'
|
||||
|
||||
// Composables
|
||||
import { createApp } from 'vue'
|
||||
|
||||
const app = createApp(App)
|
||||
|
||||
registerPlugins(app)
|
||||
|
||||
app.mount('#app')
|
||||
@@ -1,151 +0,0 @@
|
||||
<template>
|
||||
<PageTitle title="仪表盘" @refresh="refresh" />
|
||||
|
||||
<div id="dashboard-content">
|
||||
<div id="first-row">
|
||||
<v-card id="basic-analysis-number-card">
|
||||
<v-card-title class="content-card-title">
|
||||
<v-icon class="content-card-title-icon" icon="mdi-chart-line" />
|
||||
基础数据
|
||||
</v-card-title>
|
||||
|
||||
<div id="basic-analysis-number-card-content">
|
||||
<NumberFieldData title="活跃会话" :number="basicData.active_session_count" />
|
||||
<NumberFieldData title="对话总数" :number="basicData.conversation_count" />
|
||||
<NumberFieldData title="请求总数" :number="basicData.query_count" />
|
||||
</div>
|
||||
</v-card>
|
||||
|
||||
<v-card id="message-platform-card">
|
||||
<v-card-title class="content-card-title">
|
||||
<v-icon class="content-card-title-icon" icon="mdi-message-outline" />
|
||||
消息平台
|
||||
</v-card-title>
|
||||
|
||||
<div id="message-platform-card-content">
|
||||
<NumberFieldData title="已启用" :number="proxy.$store.state.enabledPlatformCount" link="/settings" linkText="更改配置" />
|
||||
</div>
|
||||
</v-card>
|
||||
|
||||
<v-card id="plugins-amount-card">
|
||||
<v-card-title class="content-card-title">
|
||||
<v-icon class="content-card-title-icon" icon="mdi-puzzle-outline" />
|
||||
插件数量
|
||||
</v-card-title>
|
||||
|
||||
<div id="plugins-amount-card-content">
|
||||
<NumberFieldData title="已加载" :number="pluginsAmount" link="/plugins" linkText="管理插件" />
|
||||
</div>
|
||||
</v-card>
|
||||
</div>
|
||||
|
||||
<div id="dashboard-tips">
|
||||
* 更多图表将在数据持久化功能更新后可用。
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
import PageTitle from '@/components/PageTitle.vue'
|
||||
import NumberFieldData from '@/components/NumberFieldData.vue'
|
||||
|
||||
import { ref, onMounted, inject, getCurrentInstance } from 'vue'
|
||||
|
||||
const { proxy } = getCurrentInstance()
|
||||
|
||||
const snackbar = inject('snackbar')
|
||||
|
||||
const basicData = ref({
|
||||
active_session_count: 0,
|
||||
conversation_count: 0,
|
||||
query_count: 0,
|
||||
})
|
||||
|
||||
const pluginsAmount = ref(0)
|
||||
|
||||
const refresh = () => {
|
||||
proxy.$axios.get('/stats/basic').then(res => {
|
||||
if (res.data.code != 0) {
|
||||
snackbar.error(res.data.msg)
|
||||
return
|
||||
}
|
||||
basicData.value = res.data.data
|
||||
}).catch(error => {
|
||||
snackbar.error(error)
|
||||
})
|
||||
|
||||
proxy.$axios.get('/plugins').then(res => {
|
||||
if (res.data.code != 0) {
|
||||
snackbar.error(res.data.msg)
|
||||
return
|
||||
}
|
||||
pluginsAmount.value = res.data.data.plugins.length
|
||||
}).catch(error => {
|
||||
snackbar.error(error)
|
||||
})
|
||||
|
||||
proxy.$store.commit('fetchSystemInfo')
|
||||
}
|
||||
|
||||
onMounted(refresh)
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
#dashboard-content {
|
||||
display: table;
|
||||
width: 100%;
|
||||
padding-inline: 1rem;
|
||||
margin-top: 1rem;
|
||||
overflow-x: auto;
|
||||
}
|
||||
|
||||
#dashboard-tips {
|
||||
font-size: 0.8rem;
|
||||
color: #222;
|
||||
margin-top: 0.7rem;
|
||||
margin-left: 0.5rem;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
#first-row {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
#basic-analysis-number-card {
|
||||
width: 35%;
|
||||
min-width: 16rem;
|
||||
padding-bottom: 0.5rem;
|
||||
}
|
||||
|
||||
#basic-analysis-number-card-content {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-evenly;
|
||||
}
|
||||
|
||||
.content-card-title {
|
||||
font-size: 1rem;
|
||||
font-weight: 600;
|
||||
user-select: none;
|
||||
}
|
||||
|
||||
.content-card-title-icon {
|
||||
font-size: 1.2rem;
|
||||
}
|
||||
|
||||
#message-platform-card {
|
||||
width: 15%;
|
||||
min-width: 6rem;
|
||||
padding-bottom: 0.7rem;
|
||||
}
|
||||
|
||||
#plugins-amount-card {
|
||||
width: 15%;
|
||||
min-width: 6rem;
|
||||
padding-bottom: 0.7rem;
|
||||
}
|
||||
</style>
|
||||
@@ -1,140 +0,0 @@
|
||||
<template>
|
||||
<PageTitle title="日志" @refresh="refresh" />
|
||||
<v-card id="toolbar">
|
||||
<div class="view-operation-components">
|
||||
<v-switch class="toolbar-component" color="primary" :model-value="proxy.$store.state.autoRefreshLog"
|
||||
@update:model-value="proxy.$store.state.autoRefreshLog = $event" label="自动刷新"></v-switch>
|
||||
|
||||
<v-switch class="toolbar-component" color="primary" :model-value="proxy.$store.state.autoScrollLog"
|
||||
@update:model-value="proxy.$store.state.autoScrollLog = $event" label="自动滚动"></v-switch>
|
||||
</div>
|
||||
</v-card>
|
||||
<v-card id="log-card">
|
||||
<v-card-text id="log-card-text">
|
||||
<!-- <textarea id="log-textarea" placeholder="点击标题旁的按钮以刷新日志" v-model="logContentHTML" readonly></textarea> -->
|
||||
<div id="log-content-html" v-html="logContentHTML"></div>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
import PageTitle from '@/components/PageTitle.vue'
|
||||
import { ref, getCurrentInstance, onMounted, onUnmounted } from 'vue'
|
||||
|
||||
import {inject} from "vue";
|
||||
|
||||
const snackbar = inject('snackbar');
|
||||
|
||||
const { proxy } = getCurrentInstance()
|
||||
|
||||
const logContent = ref('')
|
||||
const logContentHTML = ref('')
|
||||
|
||||
const refresh = () => {
|
||||
refreshLog()
|
||||
}
|
||||
|
||||
let logPointer = {
|
||||
"start_page_number": 0,
|
||||
"start_offset": 0
|
||||
}
|
||||
|
||||
import { AnsiUp } from 'ansi_up';
|
||||
|
||||
const ansiUp = new AnsiUp()
|
||||
|
||||
const refreshLog = () => {
|
||||
proxy.$axios.get(`/logs`, {
|
||||
params: {
|
||||
start_page_number: logPointer.start_page_number,
|
||||
start_offset: logPointer.start_offset
|
||||
}
|
||||
}).then(response => {
|
||||
if (response.data.code != 0) {
|
||||
snackbar.error(response.data.message)
|
||||
return
|
||||
}
|
||||
logContent.value += response.data.data.logs
|
||||
logContentHTML.value += ansiUp.ansi_to_html(response.data.data.logs)
|
||||
logPointer.start_page_number = response.data.data.end_page_number
|
||||
logPointer.start_offset = response.data.data.end_offset
|
||||
|
||||
if (proxy.$store.state.autoScrollLog) {
|
||||
// 滚动到最底部
|
||||
document.getElementById('log-content-html').scrollTop = document.getElementById('log-content-html').scrollHeight
|
||||
}
|
||||
}).catch(error => {
|
||||
snackbar.error(error.message)
|
||||
})
|
||||
}
|
||||
|
||||
let refreshLogTask = null
|
||||
onMounted(() => {
|
||||
refreshLog()
|
||||
|
||||
refreshLogTask = setInterval(() => {
|
||||
if (proxy.$store.state.autoRefreshLog) {
|
||||
refreshLog()
|
||||
}
|
||||
}, 1000)
|
||||
})
|
||||
|
||||
onUnmounted(() => {
|
||||
clearInterval(refreshLogTask)
|
||||
})
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
#toolbar {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
margin: 1rem;
|
||||
margin-top: 1rem;
|
||||
height: 3rem;
|
||||
border-radius: 0.3rem;
|
||||
}
|
||||
|
||||
.toolbar-component {
|
||||
margin-top: 1.4rem;
|
||||
}
|
||||
|
||||
.view-operation-components {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
margin-left: 1rem;
|
||||
gap: 1rem;
|
||||
}
|
||||
|
||||
#log-card {
|
||||
margin: 1rem;
|
||||
margin-top: 1rem;
|
||||
height: calc(100vh - 9.5rem);
|
||||
border-radius: 0.3rem;
|
||||
}
|
||||
|
||||
|
||||
#log-textarea {
|
||||
/* height: 100%; */
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
resize: none;
|
||||
border: none;
|
||||
outline: none;
|
||||
appearance: none;
|
||||
/* background-color: #eee; */
|
||||
}
|
||||
|
||||
#log-card-text {
|
||||
display: flex;
|
||||
height: 100%;
|
||||
background-color: #343434;
|
||||
}
|
||||
|
||||
#log-content-html {
|
||||
white-space: pre-wrap;
|
||||
overflow-y: auto;
|
||||
}
|
||||
</style>
|
||||
@@ -1,325 +0,0 @@
|
||||
<template>
|
||||
<PageTitle title="插件" @refresh="refresh" />
|
||||
<v-card id="plugins-toolbar">
|
||||
<div id="view-btns">
|
||||
<v-btn-toggle id="plugins-view-toggle" color="primary" v-model="proxy.$store.state.pluginsView" mandatory density="compact">
|
||||
<v-btn class="plugins-view-toggle-btn" value="installed" density="compact">已安装</v-btn>
|
||||
<v-btn class="plugins-view-toggle-btn" value="market" density="compact">插件市场</v-btn>
|
||||
</v-btn-toggle>
|
||||
</div>
|
||||
<div id="operation-btns">
|
||||
<v-tooltip text="设置插件优先级" location="top">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-btn prepend-icon="mdi-priority-high" v-bind="props" :disabled="plugins.length == 0">
|
||||
编排
|
||||
|
||||
<v-dialog activator="parent" max-width="500" persistent v-model="isOrchestrationDialogActive">
|
||||
<template v-slot:default="{ isActive }">
|
||||
<v-card prepend-icon="mdi-priority-high" text="优先级影响插件的加载、事件触发顺序" title="设置插件优先级">
|
||||
<v-list id="plugin-orchestration-list">
|
||||
<draggable v-model="plugins" item-key="name" group="plugins"
|
||||
@start="drag = true" id="plugin-orchestration-draggable"
|
||||
@end="drag = false">
|
||||
<template #item="{ element }">
|
||||
<div class="plugin-orchestration-item">
|
||||
<div class="plugin-orchestration-item-title">
|
||||
<div class="plugin-orchestration-item-author">
|
||||
{{ element.author }} /
|
||||
</div>
|
||||
<div class="plugin-orchestration-item-name">
|
||||
{{ element.name }}
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="plugin-orchestration-item-action">
|
||||
<v-icon>mdi-drag</v-icon>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
||||
</draggable>
|
||||
</v-list>
|
||||
|
||||
<template v-slot:actions>
|
||||
<v-btn class="ml-auto" text="关闭" prepend-icon="mdi-close"
|
||||
@click="cancelOrderChanges"></v-btn>
|
||||
<v-btn color="primary" prepend-icon="mdi-content-save-outline"
|
||||
@click="saveOrder">应用</v-btn>
|
||||
</template>
|
||||
</v-card>
|
||||
</template>
|
||||
</v-dialog>
|
||||
</v-btn>
|
||||
</template>
|
||||
</v-tooltip>
|
||||
|
||||
<v-btn color="primary" prepend-icon="mdi-plus">
|
||||
安装
|
||||
|
||||
<v-dialog activator="parent" max-width="500" persistent v-model="isInstallDialogActive">
|
||||
<template v-slot:default="{ isActive }">
|
||||
|
||||
<v-card title="从 GitHub 安装插件" prepend-icon="mdi-github">
|
||||
|
||||
<div id="plugin-install-dialog-content">
|
||||
<div>
|
||||
目前仅支持从 GitHub 安装,插件列表:<a
|
||||
href="https://github.com/stars/RockChinQ/lists/qchatgpt-%E6%8F%92%E4%BB%B6"
|
||||
target="_blank">LangBot 插件</a>
|
||||
</div>
|
||||
<v-text-field v-model="installDialogSource" label="插件源码地址" />
|
||||
</div>
|
||||
|
||||
<template v-slot:actions>
|
||||
<v-btn class="ml-auto" text="取消" prepend-icon="mdi-close"
|
||||
@click="isInstallDialogActive = false"></v-btn>
|
||||
<v-btn color="primary" prepend-icon="mdi-content-save-outline"
|
||||
@click="installPlugin">安装</v-btn>
|
||||
</template>
|
||||
|
||||
</v-card>
|
||||
</template>
|
||||
</v-dialog>
|
||||
</v-btn>
|
||||
</div>
|
||||
</v-card>
|
||||
<div class="plugins-container" v-if="proxy.$store.state.pluginsView == 'installed'">
|
||||
<v-alert id="no-plugins-alert" v-if="plugins.length == 0" color="warning" icon="$warning" title="暂无插件" text="暂无已安装的插件,请安装插件" density="compact" style="margin-inline: 1rem;"></v-alert>
|
||||
<PluginCard class="plugin-card" v-if="plugins.length > 0" v-for="plugin in plugins" :key="plugin.name" :plugin="plugin"
|
||||
@toggle="togglePlugin" @update="updatePlugin" @remove="removePlugin" />
|
||||
</div>
|
||||
<div class="plugins-container" v-if="proxy.$store.state.pluginsView == 'market'">
|
||||
<Marketplace @installPlugin="installMarketplacePlugin" />
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
import PageTitle from '@/components/PageTitle.vue'
|
||||
import PluginCard from '@/components/PluginCard.vue'
|
||||
import Marketplace from '@/components/Marketplace.vue'
|
||||
|
||||
import draggable from 'vuedraggable'
|
||||
|
||||
import { ref, getCurrentInstance, onMounted } from 'vue'
|
||||
|
||||
import { inject } from "vue";
|
||||
|
||||
const snackbar = inject('snackbar');
|
||||
|
||||
const { proxy } = getCurrentInstance()
|
||||
|
||||
const plugins = ref([])
|
||||
|
||||
const refresh = () => {
|
||||
proxy.$axios.get('/plugins').then(res => {
|
||||
if (res.data.code != 0) {
|
||||
snackbar.error(res.data.msg)
|
||||
return
|
||||
}
|
||||
plugins.value = res.data.data.plugins
|
||||
}).catch(error => {
|
||||
snackbar.error(error)
|
||||
})
|
||||
}
|
||||
|
||||
onMounted(refresh)
|
||||
|
||||
const togglePlugin = (plugin) => {
|
||||
proxy.$axios.put(`/plugins/${plugin.author}/${plugin.name}/toggle`, {
|
||||
target_enabled: !plugin.enabled
|
||||
}).then(res => {
|
||||
if (res.data.code != 0) {
|
||||
snackbar.error(res.data.msg)
|
||||
return
|
||||
}
|
||||
refresh()
|
||||
}).catch(error => {
|
||||
snackbar.error(error)
|
||||
})
|
||||
}
|
||||
|
||||
const updatePlugin = (plugin) => {
|
||||
proxy.$axios.post(`/plugins/${plugin.author}/${plugin.name}/update`).then(res => {
|
||||
if (res.data.code != 0) {
|
||||
snackbar.error(res.data.msg)
|
||||
return
|
||||
}
|
||||
snackbar.success(`已添加更新任务 请到任务列表查看进度`)
|
||||
}).catch(error => {
|
||||
snackbar.error(error)
|
||||
})
|
||||
}
|
||||
|
||||
const removePlugin = (plugin) => {
|
||||
proxy.$axios.delete(`/plugins/${plugin.author}/${plugin.name}`).then(res => {
|
||||
if (res.data.code != 0) {
|
||||
snackbar.error(res.data.msg)
|
||||
return
|
||||
}
|
||||
snackbar.success(`已添加删除任务 请到任务列表查看进度`)
|
||||
}).catch(error => {
|
||||
snackbar.error(error)
|
||||
})
|
||||
}
|
||||
|
||||
const installMarketplacePlugin = (repository) => {
|
||||
|
||||
installDialogSource.value = 'https://'+repository
|
||||
isInstallDialogActive.value = true
|
||||
}
|
||||
|
||||
const installPlugin = () => {
|
||||
|
||||
if (installDialogSource.value == '' || installDialogSource.value.trim() == '') {
|
||||
snackbar.error("请输入插件仓库地址")
|
||||
return
|
||||
}
|
||||
|
||||
proxy.$axios.post(`/plugins/install/github`, {
|
||||
source: installDialogSource.value
|
||||
}).then(res => {
|
||||
if (res.data.code != 0) {
|
||||
snackbar.error(res.data.msg)
|
||||
return
|
||||
}
|
||||
installDialogSource.value = ''
|
||||
snackbar.success(`已添加插件安装任务 请到任务列表查看进度`)
|
||||
isInstallDialogActive.value = false
|
||||
}).catch(error => {
|
||||
snackbar.error(error)
|
||||
})
|
||||
}
|
||||
|
||||
const isOrchestrationDialogActive = ref(false)
|
||||
|
||||
const cancelOrderChanges = () => {
|
||||
refresh()
|
||||
isOrchestrationDialogActive.value = false
|
||||
}
|
||||
|
||||
const saveOrder = () => {
|
||||
// 为所有插件的 priority 赋值,倒序
|
||||
plugins.value.forEach(plugin => {
|
||||
plugin.priority = plugins.value.length - plugins.value.indexOf(plugin)
|
||||
})
|
||||
|
||||
proxy.$axios.put('/plugins/reorder', {
|
||||
plugins: plugins.value
|
||||
}).then(res => {
|
||||
refresh()
|
||||
snackbar.success('插件优先级已保存')
|
||||
isOrchestrationDialogActive.value = false
|
||||
}).catch(error => {
|
||||
snackbar.error(error)
|
||||
})
|
||||
}
|
||||
|
||||
const isInstallDialogActive = ref(false)
|
||||
const installDialogSource = ref('')
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
#plugins-toolbar {
|
||||
margin-top: 1rem;
|
||||
margin-inline: 1rem;
|
||||
height: 3.2rem;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: space-between;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#view-btns {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
margin-left: 1rem;
|
||||
}
|
||||
|
||||
#plugins-view-toggle {
|
||||
margin: 0.5rem;
|
||||
box-shadow: 0 0 0 2px #dddddd;
|
||||
}
|
||||
|
||||
#operation-btns {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
gap: 1rem;
|
||||
margin-right: 1rem;
|
||||
}
|
||||
|
||||
.plugins-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
justify-content: flex-start;
|
||||
flex-wrap: wrap;
|
||||
gap: 16px;
|
||||
margin-inline: 1rem;
|
||||
}
|
||||
|
||||
#no-plugins-alert {
|
||||
margin: 1rem;
|
||||
}
|
||||
|
||||
.plugin-card {
|
||||
width: 18rem;
|
||||
height: 8rem;
|
||||
}
|
||||
|
||||
#plugin-orchestration-list {
|
||||
max-height: 20rem;
|
||||
overflow-y: auto;
|
||||
margin-inline: 1rem;
|
||||
width: calc(100% - 2rem);
|
||||
/* background-color: aqua; */
|
||||
}
|
||||
|
||||
#plugin-orchestration-draggable {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.plugin-orchestration-item {
|
||||
cursor: move;
|
||||
width: calc(100% - 2rem);
|
||||
box-shadow: 0.1rem 0.1rem 0.2rem 0.05rem #ccc;
|
||||
background-color: #ffffff;
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
padding: 0.5rem;
|
||||
justify-content: space-between;
|
||||
}
|
||||
|
||||
.plugin-orchestration-item-title {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.plugin-orchestration-item-author {
|
||||
color: #666;
|
||||
font-size: 0.7rem;
|
||||
}
|
||||
|
||||
.plugin-orchestration-item-name {
|
||||
font-size: 1.1rem;
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.plugin-orchestration-item-action {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
#plugin-install-dialog-content {
|
||||
width: calc(100% - 3rem);
|
||||
margin-inline: 1.5rem;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: 1rem;
|
||||
}
|
||||
</style>
|
||||
@@ -1,89 +0,0 @@
|
||||
<template>
|
||||
<PageTitle title="设置" @refresh="refresh" />
|
||||
|
||||
<v-card id="settings-card">
|
||||
<v-tabs id="settings-tabs" v-model="proxy.$store.state.settingsPageTab" show-arrows center-active
|
||||
@update:model-value="onTabChange">
|
||||
<v-tooltip v-for="manager in managerList" :key="manager.name" :text="manager.description"
|
||||
location="top">
|
||||
<template v-slot:activator="{ props }">
|
||||
<v-tab v-bind="props" :value="manager.name" style="text-transform: none;">{{ manager.name }}</v-tab>
|
||||
</template>
|
||||
</v-tooltip>
|
||||
</v-tabs>
|
||||
<v-tabs-window id="settings-tab-window" v-model="proxy.$store.state.settingsPageTab">
|
||||
<v-tabs-window-item v-for="manager in managerList" :key="manager.name" :value="manager.name"
|
||||
class="config-tab-window">
|
||||
<SettingWindow style="height: 100%;width: 100%;" :name="manager.name" />
|
||||
<!-- {{ manager.name }} -->
|
||||
</v-tabs-window-item>
|
||||
</v-tabs-window>
|
||||
</v-card>
|
||||
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
|
||||
import PageTitle from '@/components/PageTitle.vue'
|
||||
import SettingWindow from '@/components/SettingWindow.vue'
|
||||
import { ref, getCurrentInstance, onMounted } from 'vue'
|
||||
|
||||
import {inject} from "vue";
|
||||
|
||||
const snackbar = inject('snackbar');
|
||||
|
||||
const { proxy } = getCurrentInstance()
|
||||
|
||||
const managerList = ref([])
|
||||
|
||||
const refresh = () => {
|
||||
proxy.$axios.get('/settings').then(response => {
|
||||
|
||||
if (response.data.code != 0) {
|
||||
snackbar.error(response.data.msg)
|
||||
return
|
||||
}
|
||||
|
||||
managerList.value = response.data.data.managers
|
||||
|
||||
if (proxy.$store.state.settingsPageTab == '') {
|
||||
proxy.$store.state.settingsPageTab = managerList.value[0].name
|
||||
}
|
||||
}).catch(error => {
|
||||
snackbar.error(error)
|
||||
})
|
||||
}
|
||||
|
||||
const onTabChange = (tab) => {
|
||||
console.log(tab)
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
refresh()
|
||||
})
|
||||
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
#settings-card {
|
||||
margin: 1rem;
|
||||
height: calc(100% - 5rem);
|
||||
/* max-height: calc(100% - 5rem); */
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
overflow: hidden;
|
||||
/* background-color: #f0f0f0; */
|
||||
}
|
||||
|
||||
#settings-tabs {
|
||||
height: 3rem;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#settings-tab-window {
|
||||
height: calc(100vh - 9rem);
|
||||
overflow: hidden;
|
||||
/* background-color: aqua; */
|
||||
}
|
||||
|
||||
</style>
|
||||
@@ -1,31 +0,0 @@
|
||||
/**
|
||||
* plugins/index.js
|
||||
*
|
||||
* Automatically included in `./src/main.js`
|
||||
*/
|
||||
|
||||
// Plugins
|
||||
import vuetify from './vuetify'
|
||||
import router from '@/router'
|
||||
import store from '@/store'
|
||||
import axios from 'axios'
|
||||
|
||||
export function registerPlugins (app) {
|
||||
app
|
||||
.use(vuetify)
|
||||
.use(router)
|
||||
.use(store)
|
||||
|
||||
// 读取用户令牌
|
||||
const token = localStorage.getItem('user-token')
|
||||
|
||||
if (token) {
|
||||
store.state.user.jwtToken = token
|
||||
}
|
||||
|
||||
// 所有axios请求均携带用户令牌
|
||||
axios.defaults.headers.common['Authorization'] = `Bearer ${store.state.user.jwtToken}`
|
||||
|
||||
app.config.globalProperties.$axios = axios
|
||||
store.commit('initializeFetch')
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
/**
|
||||
* plugins/vuetify.js
|
||||
*
|
||||
* Framework documentation: https://vuetifyjs.com`
|
||||
*/
|
||||
|
||||
// Styles
|
||||
import '@mdi/font/css/materialdesignicons.css'
|
||||
import 'vuetify/styles'
|
||||
|
||||
// Composables
|
||||
import { createVuetify } from 'vuetify'
|
||||
|
||||
// https://vuetifyjs.com/en/introduction/why-vuetify/#feature-guides
|
||||
export default createVuetify({
|
||||
theme: {
|
||||
defaultTheme: 'light',
|
||||
},
|
||||
})
|
||||
@@ -1,46 +0,0 @@
|
||||
|
||||
/**
|
||||
* router/index.ts
|
||||
*
|
||||
* Automatic routes for `./src/pages/*.vue`
|
||||
*/
|
||||
|
||||
// Composables
|
||||
import { createRouter, createWebHashHistory } from 'vue-router/auto'
|
||||
import DashBoard from '../pages/DashBoard.vue'
|
||||
import Settings from '../pages/Settings.vue'
|
||||
import Logs from '../pages/Logs.vue'
|
||||
import Plugins from '../pages/Plugins.vue'
|
||||
|
||||
const routes = [
|
||||
{ path: '/', component: DashBoard },
|
||||
{ path: '/settings', component: Settings },
|
||||
{ path: '/logs', component: Logs },
|
||||
{ path: '/plugins', component: Plugins },
|
||||
]
|
||||
|
||||
const router = createRouter({
|
||||
history: createWebHashHistory(),
|
||||
routes,
|
||||
})
|
||||
|
||||
// Workaround for https://github.com/vitejs/vite/issues/11804
|
||||
router.onError((err, to) => {
|
||||
if (err?.message?.includes?.('Failed to fetch dynamically imported module')) {
|
||||
if (!localStorage.getItem('vuetify:dynamic-reload')) {
|
||||
console.log('Reloading page to fix dynamic import error')
|
||||
localStorage.setItem('vuetify:dynamic-reload', 'true')
|
||||
location.assign(to.fullPath)
|
||||
} else {
|
||||
console.error('Dynamic import error, reloading page did not fix it', err)
|
||||
}
|
||||
} else {
|
||||
console.error(err)
|
||||
}
|
||||
})
|
||||
|
||||
router.isReady().then(() => {
|
||||
localStorage.removeItem('vuetify:dynamic-reload')
|
||||
})
|
||||
|
||||
export default router
|
||||
@@ -1,53 +0,0 @@
|
||||
import { createStore } from 'vuex'
|
||||
import router from '@/router'
|
||||
import axios from 'axios'
|
||||
|
||||
export default createStore({
|
||||
state: {
|
||||
// 开发时使用
|
||||
// apiBaseUrl: 'http://localhost:5300/api/v1',
|
||||
apiBaseUrl: '/api/v1',
|
||||
autoRefreshLog: false,
|
||||
autoScrollLog: true,
|
||||
settingsPageTab: '',
|
||||
version: 'v0.0.0',
|
||||
debug: false,
|
||||
enabledPlatformCount: 0,
|
||||
user: {
|
||||
tokenChecked: false,
|
||||
tokenValid: false,
|
||||
systemInitialized: true,
|
||||
jwtToken: '',
|
||||
},
|
||||
pluginsView: 'installed',
|
||||
marketplaceParams: {
|
||||
query: '',
|
||||
page: 1,
|
||||
per_page: 10,
|
||||
sort_by: 'pushed_at',
|
||||
sort_order: 'DESC',
|
||||
},
|
||||
marketplacePlugins: [],
|
||||
marketplaceTotalPages: 0,
|
||||
marketplaceTotalPluginsCount: 0,
|
||||
},
|
||||
mutations: {
|
||||
initializeFetch() {
|
||||
axios.defaults.baseURL = this.state.apiBaseUrl
|
||||
|
||||
axios.get('/system/info').then(response => {
|
||||
this.state.version = response.data.data.version
|
||||
this.state.debug = response.data.data.debug
|
||||
this.state.enabledPlatformCount = response.data.data.enabled_platform_count
|
||||
})
|
||||
},
|
||||
fetchSystemInfo() {
|
||||
axios.get('/system/info').then(response => {
|
||||
this.state.version = response.data.data.version
|
||||
this.state.debug = response.data.data.debug
|
||||
this.state.enabledPlatformCount = response.data.data.enabled_platform_count
|
||||
})
|
||||
}
|
||||
},
|
||||
actions: {},
|
||||
})
|
||||
@@ -1,10 +0,0 @@
|
||||
/**
|
||||
* src/styles/settings.scss
|
||||
*
|
||||
* Configures SASS variables and Vuetify overwrites
|
||||
*/
|
||||
|
||||
// https://vuetifyjs.com/features/sass-variables/`
|
||||
// @use 'vuetify/settings' with (
|
||||
// $color-pack: false
|
||||
// );
|
||||
Reference in New Issue
Block a user