web/frps: redesign frps dashboard with sidebar nav, responsive layout, and shared component workspace (#5246)

This commit is contained in:
fatedier
2026-03-20 03:33:44 +08:00
committed by GitHub
parent 6cdef90113
commit 38a71a6803
38 changed files with 1484 additions and 8548 deletions

View File

@@ -41,6 +41,7 @@ serverPort = 7000"
message="This operation will update your frpc configuration and reload it. Do you want to continue?"
confirm-text="Update"
:loading="uploading"
:is-mobile="isMobile"
@confirm="doUpload"
/>
</div>
@@ -51,9 +52,11 @@ import { ref } from 'vue'
import { ElMessage } from 'element-plus'
import { Link } from '@element-plus/icons-vue'
import { useClientStore } from '../stores/client'
import ActionButton from '../components/ActionButton.vue'
import ConfirmDialog from '../components/ConfirmDialog.vue'
import ActionButton from '@shared/components/ActionButton.vue'
import ConfirmDialog from '@shared/components/ConfirmDialog.vue'
import { useResponsive } from '../composables/useResponsive'
const { isMobile } = useResponsive()
const clientStore = useClientStore()
const configContent = ref('')

View File

@@ -69,7 +69,7 @@ import { ref, computed, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import { Warning } from '@element-plus/icons-vue'
import ActionButton from '../components/ActionButton.vue'
import ActionButton from '@shared/components/ActionButton.vue'
import ProxyFormLayout from '../components/proxy-form/ProxyFormLayout.vue'
import { getProxyConfig, getStoreProxy } from '../api/frpc'
import { useProxyStore } from '../stores/proxy'

View File

@@ -31,6 +31,7 @@
v-model="leaveDialogVisible"
title="Unsaved Changes"
message="You have unsaved changes. Are you sure you want to leave?"
:is-mobile="isMobile"
@confirm="handleLeaveConfirm"
@cancel="handleLeaveCancel"
/>
@@ -50,10 +51,12 @@ import {
} from '../types'
import { getStoreProxy } from '../api/frpc'
import { useProxyStore } from '../stores/proxy'
import ActionButton from '../components/ActionButton.vue'
import ConfirmDialog from '../components/ConfirmDialog.vue'
import ActionButton from '@shared/components/ActionButton.vue'
import ConfirmDialog from '@shared/components/ConfirmDialog.vue'
import ProxyFormLayout from '../components/proxy-form/ProxyFormLayout.vue'
import { useResponsive } from '../composables/useResponsive'
const { isMobile } = useResponsive()
const route = useRoute()
const router = useRouter()
const proxyStore = useProxyStore()

View File

@@ -30,8 +30,8 @@
<el-input v-model="searchText" placeholder="Search..." clearable class="search-input">
<template #prefix><el-icon><Search /></el-icon></template>
</el-input>
<FilterDropdown v-model="sourceFilter" label="Source" :options="sourceOptions" :min-width="140" />
<FilterDropdown v-model="typeFilter" label="Type" :options="typeOptions" :min-width="140" />
<FilterDropdown v-model="sourceFilter" label="Source" :options="sourceOptions" :min-width="140" :is-mobile="isMobile" />
<FilterDropdown v-model="typeFilter" label="Type" :options="typeOptions" :min-width="140" :is-mobile="isMobile" />
</div>
</template>
@@ -41,7 +41,7 @@
<el-input v-model="storeSearch" placeholder="Search..." clearable class="search-input">
<template #prefix><el-icon><Search /></el-icon></template>
</el-input>
<FilterDropdown v-model="storeTypeFilter" label="Type" :options="storeTypeOptions" :min-width="140" />
<FilterDropdown v-model="storeTypeFilter" label="Type" :options="storeTypeOptions" :min-width="140" :is-mobile="isMobile" />
</div>
</template>
</div>
@@ -100,6 +100,7 @@ path = "./frpc_store.json"</pre>
confirm-text="Delete"
danger
:loading="deleteDialog.loading"
:is-mobile="isMobile"
@confirm="doDelete"
/>
</div>
@@ -110,11 +111,11 @@ import { ref, computed, reactive, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import { Search, Refresh } from '@element-plus/icons-vue'
import ActionButton from '../components/ActionButton.vue'
import ActionButton from '@shared/components/ActionButton.vue'
import StatusPills from '../components/StatusPills.vue'
import FilterDropdown from '../components/FilterDropdown.vue'
import FilterDropdown from '@shared/components/FilterDropdown.vue'
import ProxyCard from '../components/ProxyCard.vue'
import ConfirmDialog from '../components/ConfirmDialog.vue'
import ConfirmDialog from '@shared/components/ConfirmDialog.vue'
import { useProxyStore } from '../stores/proxy'
import { useResponsive } from '../composables/useResponsive'
import type { ProxyStatus } from '../types'

View File

@@ -48,7 +48,7 @@
import { ref, computed, onMounted } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import ActionButton from '../components/ActionButton.vue'
import ActionButton from '@shared/components/ActionButton.vue'
import VisitorFormLayout from '../components/visitor-form/VisitorFormLayout.vue'
import { getVisitorConfig, getStoreVisitor } from '../api/frpc'
import type { VisitorDefinition, VisitorFormData } from '../types'

View File

@@ -30,6 +30,7 @@
v-model="leaveDialogVisible"
title="Unsaved Changes"
message="You have unsaved changes. Are you sure you want to leave?"
:is-mobile="isMobile"
@confirm="handleLeaveConfirm"
@cancel="handleLeaveCancel"
/>
@@ -40,9 +41,10 @@
import { ref, computed, onMounted, watch, nextTick } from 'vue'
import { useRoute, useRouter, onBeforeRouteLeave } from 'vue-router'
import { ElMessage } from 'element-plus'
import ActionButton from '../components/ActionButton.vue'
import ConfirmDialog from '../components/ConfirmDialog.vue'
import ActionButton from '@shared/components/ActionButton.vue'
import ConfirmDialog from '@shared/components/ConfirmDialog.vue'
import VisitorFormLayout from '../components/visitor-form/VisitorFormLayout.vue'
import { useResponsive } from '../composables/useResponsive'
import type { FormInstance, FormRules } from 'element-plus'
import {
type VisitorFormData,
@@ -53,6 +55,7 @@ import {
import { getStoreVisitor } from '../api/frpc'
import { useVisitorStore } from '../stores/visitor'
const { isMobile } = useResponsive()
const route = useRoute()
const router = useRouter()
const visitorStore = useVisitorStore()

View File

@@ -32,7 +32,7 @@ path = "./frpc_store.json"</pre>
<el-input v-model="searchText" placeholder="Search..." clearable class="search-input">
<template #prefix><el-icon><Search /></el-icon></template>
</el-input>
<FilterDropdown v-model="typeFilter" label="Type" :options="typeOptions" :min-width="140" />
<FilterDropdown v-model="typeFilter" label="Type" :options="typeOptions" :min-width="140" :is-mobile="isMobile" />
</div>
<div v-if="filteredVisitors.length > 0" class="visitor-list">
@@ -74,7 +74,7 @@ path = "./frpc_store.json"</pre>
<ConfirmDialog v-model="deleteDialog.visible" title="Delete Visitor"
:message="deleteDialog.message" confirm-text="Delete" danger
:loading="deleteDialog.loading" @confirm="doDelete" />
:loading="deleteDialog.loading" :is-mobile="isMobile" @confirm="doDelete" />
</div>
</template>
@@ -83,14 +83,16 @@ import { ref, computed, reactive, onMounted } from 'vue'
import { useRouter } from 'vue-router'
import { ElMessage } from 'element-plus'
import { Search, Refresh, MoreFilled, Edit, Delete } from '@element-plus/icons-vue'
import ActionButton from '../components/ActionButton.vue'
import FilterDropdown from '../components/FilterDropdown.vue'
import PopoverMenu from '../components/PopoverMenu.vue'
import PopoverMenuItem from '../components/PopoverMenuItem.vue'
import ConfirmDialog from '../components/ConfirmDialog.vue'
import ActionButton from '@shared/components/ActionButton.vue'
import FilterDropdown from '@shared/components/FilterDropdown.vue'
import PopoverMenu from '@shared/components/PopoverMenu.vue'
import PopoverMenuItem from '@shared/components/PopoverMenuItem.vue'
import ConfirmDialog from '@shared/components/ConfirmDialog.vue'
import { useVisitorStore } from '../stores/visitor'
import { useResponsive } from '../composables/useResponsive'
import type { VisitorDefinition } from '../types'
const { isMobile } = useResponsive()
const router = useRouter()
const visitorStore = useVisitorStore()