2
0
mirror of https://github.com/acepanel/panel.git synced 2026-02-04 04:22:33 +08:00

feat: 清理代码

This commit is contained in:
2026-01-29 23:37:49 +08:00
parent be7996969a
commit c2c3aa48f1
29 changed files with 89 additions and 72 deletions

View File

@@ -15,7 +15,7 @@
"build": "run-s gen-auto-import type-check build-only copy",
"preview": "vite preview",
"build-only": "vite build",
"type-check": "vue-tsc --noEmit",
"type-check": "vue-tsc --noEmit -p tsconfig.app.json",
"lint": "run-s gen-auto-import lint-only",
"lint-only": "eslint . --fix",
"format": "prettier --write src/",

View File

@@ -39,7 +39,7 @@ const parseDescription = computed((): string => {
return $gettext('Cron expression: %{cron}', { cron })
}
const [minute, hour, day, month, weekday] = parts
const [minute, hour, day, month, weekday] = parts as [string, string, string, string, string]
try {
// 每 N 分钟:*/N * * * *

View File

@@ -27,7 +27,6 @@ const minimized = defineModel<boolean>('minimized', { default: false })
// 窗口状态
const isMaximized = ref(false)
const windowRef = ref<HTMLDivElement>()
// 窗口位置和大小
const position = ref({ x: 0, y: 0 })

View File

@@ -69,7 +69,7 @@ const updateKey = (oldKey: string, newKey: string) => {
if (props.modelValue[newKey] !== undefined) return // 键已存在
const data = { ...props.modelValue }
data[newKey] = data[oldKey]
data[newKey] = data[oldKey] ?? ''
delete data[oldKey]
emit('update:modelValue', data)
}

View File

@@ -65,7 +65,7 @@ function handleLanguageChange(value: string) {
// 更新缩进
function handleIndentChange(value: string) {
const [size, type] = value.split('-')
const [size, type] = value.split('-') as [string, string]
editorStore.updateSettings({
tabSize: parseInt(size),
insertSpaces: type === 'spaces'

View File

@@ -187,17 +187,18 @@ function showCreate(type: 'file' | 'dir') {
// 确定父目录
if (selectedKeys.value.length > 0) {
const selectedNode = findNode(treeData.value, selectedKeys.value[0])
const selectedKey = selectedKeys.value[0] ?? ''
const selectedNode = findNode(treeData.value, selectedKey)
if (selectedNode && !selectedNode.isLeaf) {
// 选中的是目录,在该目录下新建
inlineCreateParentPath.value = selectedKeys.value[0]
inlineCreateParentPath.value = selectedKey
// 确保目录已展开
if (!expandedKeys.value.includes(selectedKeys.value[0])) {
expandedKeys.value = [...expandedKeys.value, selectedKeys.value[0]]
if (!expandedKeys.value.includes(selectedKey)) {
expandedKeys.value = [...expandedKeys.value, selectedKey]
}
} else {
// 选中的是文件,在其父目录下新建
const parts = selectedKeys.value[0].split('/')
const parts = selectedKey.split('/')
parts.pop()
inlineCreateParentPath.value = parts.join('/') || props.rootPath
}

View File

@@ -3,7 +3,8 @@ import { translateTitle } from '@/locales/menu'
import { usePermissionStore, useTabStore, useThemeStore } from '@/store'
import { isUrl, renderIcon } from '@/utils'
import { MenuInst, MenuOption, useThemeVars } from 'naive-ui'
import type { MenuInst, MenuOption } from 'naive-ui'
import { useThemeVars } from 'naive-ui'
import type { VNodeChild } from 'vue'
import { RouterLink } from 'vue-router'
import type { Meta, RouteType } from '~/types/router'

View File

@@ -57,7 +57,7 @@ const options = computed(() => [
{
label: $gettext('Close Left'),
key: 'close-left',
disabled: tabStore.tabs.length <= 1 || props.currentPath === tabStore.tabs[0].path,
disabled: tabStore.tabs.length <= 1 || props.currentPath === tabStore.tabs[0]?.path,
icon: renderIcon('mdi:arrow-expand-left', { size: 14 })
},
{
@@ -65,7 +65,7 @@ const options = computed(() => [
key: 'close-right',
disabled:
tabStore.tabs.length <= 1 ||
props.currentPath === tabStore.tabs[tabStore.tabs.length - 1].path,
props.currentPath === tabStore.tabs[tabStore.tabs.length - 1]?.path,
icon: renderIcon('mdi:arrow-expand-right', { size: 14 })
}
])

View File

@@ -38,7 +38,8 @@ const modules = import.meta.glob('@/views/**/route.ts', {
}) as RouteModule
const asyncRoutes: RoutesType = []
Object.keys(modules).forEach((key) => {
asyncRoutes.push(modules[key].default)
const route = modules[key]?.default
if (route) asyncRoutes.push(route)
})
export { asyncRoutes }

View File

@@ -137,9 +137,9 @@ export const useEditorStore = defineStore('editor', {
if (this.tabs.length === 0) {
this.activeTabPath = null
} else if (index >= this.tabs.length) {
this.activeTabPath = this.tabs[this.tabs.length - 1].path
this.activeTabPath = this.tabs[this.tabs.length - 1]?.path ?? null
} else {
this.activeTabPath = this.tabs[index].path
this.activeTabPath = this.tabs[index]?.path ?? null
}
}
},
@@ -162,7 +162,7 @@ export const useEditorStore = defineStore('editor', {
if (this.tabs.length === 0) {
this.activeTabPath = null
} else if (!this.tabs.find((tab) => tab.path === this.activeTabPath)) {
this.activeTabPath = this.tabs[0].path
this.activeTabPath = this.tabs[0]?.path ?? null
}
},
@@ -255,7 +255,7 @@ export const useEditorStore = defineStore('editor', {
if (toIndex < 0 || toIndex >= this.tabs.length) return
const [movedTab] = this.tabs.splice(fromIndex, 1)
this.tabs.splice(toIndex, 0, movedTab)
if (movedTab) this.tabs.splice(toIndex, 0, movedTab)
}
},

View File

@@ -64,28 +64,28 @@ export const useTabStore = defineStore('tab', {
removeTab(path: string) {
if (path === this.active) {
const activeIndex = this.tabs.findIndex((item) => item.path === path)
if (activeIndex > 0) router.push(this.tabs[activeIndex - 1].path)
else router.push(this.tabs[activeIndex + 1].path)
if (activeIndex > 0) router.push(this.tabs[activeIndex - 1]?.path ?? '/')
else router.push(this.tabs[activeIndex + 1]?.path ?? '/')
}
this.setTabs(this.tabs.filter((tab) => tab.path !== path))
},
removeOther(curPath: string) {
this.setTabs(this.tabs.filter((tab) => tab.path === curPath))
if (curPath !== this.active) router.push(this.tabs[this.tabs.length - 1].path)
if (curPath !== this.active) router.push(this.tabs[this.tabs.length - 1]?.path ?? '/')
},
removeLeft(curPath: string) {
const curIndex = this.tabs.findIndex((item) => item.path === curPath)
const filterTabs = this.tabs.filter((item, index) => index >= curIndex)
this.setTabs(filterTabs)
if (!filterTabs.find((item) => item.path === this.active))
router.push(filterTabs[filterTabs.length - 1].path)
router.push(filterTabs[filterTabs.length - 1]?.path ?? '/')
},
removeRight(curPath: string) {
const curIndex = this.tabs.findIndex((item) => item.path === curPath)
const filterTabs = this.tabs.filter((item, index) => index <= curIndex)
this.setTabs(filterTabs)
if (!filterTabs.find((item) => item.path === this.active))
router.push(filterTabs[filterTabs.length - 1].path)
router.push(filterTabs[filterTabs.length - 1]?.path ?? '/')
},
resetTabs() {
this.setTabs([])

View File

@@ -20,10 +20,10 @@ export const useThemeStore = defineStore('theme', {
return this.darkMode ? darkTheme : undefined
},
naiveLocale(): NLocale {
return locales[this.locale].locale
return locales[this.locale]?.locale ?? enUS
},
naiveDateLocale(): NDateLocale {
return locales[this.locale].dateLocale
return locales[this.locale]?.dateLocale ?? dateEnUS
}
},
actions: {

View File

@@ -11,7 +11,7 @@ export function encodeBase64(str: string): string {
const bytes = new TextEncoder().encode(str)
let binary = ''
for (let i = 0; i < bytes.length; i++) {
binary += String.fromCharCode(bytes[i])
binary += String.fromCharCode(bytes[i] ?? 0)
}
return btoa(binary)
}

View File

@@ -47,9 +47,10 @@ export function generateRandomString(length: number) {
for (let i = 0; i < randomBytes.length && result.length < length; i++) {
// Only use values that map evenly to the character set to avoid bias
const maxValue = Math.floor(256 / charactersLength) * charactersLength
if (randomBytes[i] < maxValue) {
const randomIndex = randomBytes[i] % charactersLength
result += characters[randomIndex]
const byteValue = randomBytes[i] ?? 0
if (byteValue < maxValue) {
const randomIndex = byteValue % charactersLength
result += characters[randomIndex] ?? ''
}
}
}

View File

@@ -7,7 +7,6 @@ import api from '@/api/panel/backup'
const { $gettext } = useGettext()
const show = defineModel<boolean>('show', { type: Boolean, required: true })
const type = defineModel<string>('type', { type: String, required: true })
const upload = ref<any>(null)
const uploadRequest = ({ file, onFinish, onError, onProgress }: UploadCustomRequestOptions) => {
const formData = new FormData()

View File

@@ -94,7 +94,7 @@ const getNetworks = () => {
value: item.id
}))
if (networks.value.length > 0) {
createModel.network = networks.value[0].value
createModel.network = networks.value[0]?.value ?? ''
}
})
}

View File

@@ -13,8 +13,6 @@ const show = defineModel<boolean>('show', { type: Boolean, required: true })
const minimized = defineModel<boolean>('minimized', { type: Boolean, default: false })
const filePath = defineModel<string>('file', { type: String, required: true })
const editorRef = ref<InstanceType<typeof FileEditorView>>()
// 窗口默认尺寸
const defaultWidth = Math.min(1400, window.innerWidth * 0.9)
const defaultHeight = Math.min(900, window.innerHeight * 0.85)

View File

@@ -120,7 +120,7 @@ const handleDrop = async (e: DragEvent) => {
// 使用 webkitGetAsEntry 来支持文件夹
for (let i = 0; i < items.length; i++) {
const item = items[i]
if (item.kind === 'file') {
if (item?.kind === 'file') {
const entry = item.webkitGetAsEntry()
if (entry) {
if (entry.isFile) {

View File

@@ -184,7 +184,7 @@ const selectionBox = computed(() => {
const hexToRgb = (hex: string) => {
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(hex)
return result
? `${parseInt(result[1], 16)}, ${parseInt(result[2], 16)}, ${parseInt(result[3], 16)}`
? `${parseInt(result[1] ?? '0', 16)}, ${parseInt(result[2] ?? '0', 16)}, ${parseInt(result[3] ?? '0', 16)}`
: '24, 160, 88'
}
@@ -843,7 +843,8 @@ const handlePaste = () => {
for (let i = 0; i < data.length; i++) {
if (data[i]) {
flag = true
paths[i].force = true
const pathItem = paths[i]
if (pathItem) pathItem.force = true
}
}
if (flag) {

View File

@@ -53,7 +53,7 @@ const handleUp = () => {
const handleBack = () => {
if (current > 0) {
current--
path.value = history[current]
path.value = history[current] ?? '/'
input.value = path.value.slice(1)
}
}
@@ -61,7 +61,7 @@ const handleBack = () => {
const handleForward = () => {
if (current < history.length - 1) {
current++
path.value = history[current]
path.value = history[current] ?? '/'
input.value = path.value.slice(1)
}
}

View File

@@ -34,10 +34,12 @@ watch(
(newVal) => {
if (newVal && fileInfoList.value.length > 0) {
const firstFile = fileInfoList.value[0]
mode.value = normalizeMode(firstFile.mode)
owner.value = firstFile.owner || 'www'
group.value = firstFile.group || 'www'
updateCheckboxes()
if (firstFile) {
mode.value = normalizeMode(firstFile.mode)
owner.value = firstFile.owner || 'www'
group.value = firstFile.group || 'www'
updateCheckboxes()
}
}
}
)
@@ -73,7 +75,7 @@ const calculateMode = () => {
const updateCheckboxes = () => {
const paddedMode = normalizeMode(mode.value)
const permissions = paddedMode.split('').map(Number)
const permissions = paddedMode.split('').map(Number) as [number, number, number]
checkbox.value.owner = permissions[0] & 4 ? ['read'] : []
if (permissions[0] & 2) checkbox.value.owner.push('write')
@@ -94,7 +96,7 @@ const title = computed(() => {
}
return selected.value.length > 1
? $gettext('Batch modify permissions')
: $gettext('Modify permissions - %{ path }', { path: selected.value[0] })
: $gettext('Modify permissions - %{ path }', { path: selected.value[0] ?? '' })
})
watch(mode, updateCheckboxes, { immediate: true })

View File

@@ -105,7 +105,8 @@ const handlePaste = () => {
for (let i = 0; i < data.length; i++) {
if (data[i]) {
flag = true
paths[i].force = true
const pathItem = paths[i]
if (pathItem) pathItem.force = true
}
}
if (flag) {

View File

@@ -540,13 +540,13 @@ if (import.meta.hot) {
<n-table :single-line="false" striped>
<tr>
<th>{{ $gettext('Model') }}</th>
<td>{{ realtime.cpus[0].modelName }}</td>
<td>{{ realtime.cpus[0]?.modelName }}</td>
</tr>
<tr>
<th>{{ $gettext('Parameters') }}</th>
<td>
{{ realtime.cpus.length }} CPU {{ cores }} {{ $gettext('cores') }}
{{ formatBytes(realtime.cpus[0].cacheSize * 1024) }} {{ $gettext('cache') }}
{{ formatBytes((realtime.cpus[0]?.cacheSize ?? 0) * 1024) }} {{ $gettext('cache') }}
</td>
</tr>
<tr v-for="item in realtime.cpus" :key="item.modelName">

View File

@@ -83,6 +83,7 @@ const getOption = (route: RouteType): TreeSelectOption => {
if (visibleChildren.length === 1) {
// 单个子路由处理
const singleRoute = visibleChildren[0]
if (!singleRoute) return menuItem
const isSingleDisabled = forbiddenHiddenMenus.includes(singleRoute.name as string)
menuItem.label = singleRoute.meta?.title
? translateTitle(singleRoute.meta.title)
@@ -93,7 +94,8 @@ const getOption = (route: RouteType): TreeSelectOption => {
? singleRoute.children.filter((item: RouteType) => item.name && !item.isHidden)
: []
if (visibleItems.length === 1) menuItem = getOption(visibleItems[0])
const firstVisibleItem = visibleItems[0]
if (visibleItems.length === 1 && firstVisibleItem) menuItem = getOption(firstVisibleItem)
else if (visibleItems.length > 1)
menuItem.children = visibleItems.map((item) => getOption(item))
} else {

View File

@@ -180,13 +180,14 @@ const closeTab = (tabId: string) => {
if (index === -1) return
const tab = tabs.value[index]
disposeTab(tab)
if (tab) disposeTab(tab)
tabs.value.splice(index, 1)
// 如果关闭的是当前标签,切换到其他标签
if (activeTabId.value === tabId && tabs.value.length > 0) {
const newIndex = Math.min(index, tabs.value.length - 1)
switchTab(tabs.value[newIndex].id)
const newTab = tabs.value[newIndex]
if (newTab) switchTab(newTab.id)
}
// 如果没有标签了,创建一个本机标签

View File

@@ -64,7 +64,7 @@ const handleTest = async () => {
inTest.value = true
progress.value = 0
for (let i = 0; i < tests.length; i++) {
const test = tests[i]
const test = tests[i] ?? ''
current.value = test
if (test != 'memory' && test != 'disk') {
cpu.value[test as keyof typeof cpu.value] = await benchmark.test(test)

View File

@@ -6,6 +6,7 @@ defineOptions({
import { useRequest } from 'alova/client'
import type { DataTableColumns } from 'naive-ui'
import { NButton, NProgress, NTag } from 'naive-ui'
import { h } from 'vue'
import { useGettext } from 'vue3-gettext'
import disk from '@/api/panel/toolbox-disk'

View File

@@ -76,24 +76,28 @@ const scanResults = ref<Record<string, ScanResult>>({
// 扫描日志
const handleScan = async (type: string) => {
scanResults.value[type].loading = true
scanResults.value[type].scanned = false
scanResults.value[type].items = []
const result = scanResults.value[type]
if (!result) return
result.loading = true
result.scanned = false
result.items = []
try {
const { data } = await useRequest(toolboxLog.scan(type))
scanResults.value[type].items = data || []
scanResults.value[type].scanned = true
result.items = data || []
result.scanned = true
} catch (e) {
window.$message.error($gettext('Scan failed'))
} finally {
scanResults.value[type].loading = false
result.loading = false
}
}
// 清理日志
const handleClean = async (type: string) => {
scanResults.value[type].cleaning = true
const result = scanResults.value[type]
if (!result) return
result.cleaning = true
try {
const { data } = await useRequest(toolboxLog.clean(type))
@@ -103,7 +107,7 @@ const handleClean = async (type: string) => {
} catch (e) {
window.$message.error($gettext('Clean failed'))
} finally {
scanResults.value[type].cleaning = false
result.cleaning = false
}
}
@@ -117,7 +121,8 @@ const handleScanAll = async () => {
// 清理所有
const handleCleanAll = async () => {
for (const logType of logTypes) {
if (scanResults.value[logType.key].items.length > 0) {
const result = scanResults.value[logType.key]
if (result && result.items.length > 0) {
await handleClean(logType.key)
}
}
@@ -132,6 +137,11 @@ const totalItems = computed(() => {
const anyLoading = computed(() => {
return Object.values(scanResults.value).some((r) => r.loading || r.cleaning)
})
// 获取扫描结果
const getResult = (key: string): ScanResult => {
return scanResults.value[key] ?? { loading: false, items: [], scanned: false, cleaning: false }
}
</script>
<template>
@@ -163,7 +173,7 @@ const anyLoading = computed(() => {
<n-flex :size="8">
<n-button
size="small"
:loading="scanResults[logType.key].loading"
:loading="getResult(logType.key).loading"
@click="handleScan(logType.key)"
>
<template #icon>
@@ -174,8 +184,8 @@ const anyLoading = computed(() => {
<n-button
size="small"
type="warning"
:loading="scanResults[logType.key].cleaning"
:disabled="scanResults[logType.key].items.length === 0"
:loading="getResult(logType.key).cleaning"
:disabled="getResult(logType.key).items.length === 0"
@click="handleClean(logType.key)"
>
<template #icon>
@@ -189,15 +199,15 @@ const anyLoading = computed(() => {
<n-flex vertical>
<n-text depth="3">{{ logType.description }}</n-text>
<template v-if="scanResults[logType.key].loading">
<template v-if="getResult(logType.key).loading">
<n-flex justify="center" align="center" style="min-height: 60px">
<n-spin size="small" />
<n-text>{{ $gettext('Scanning...') }}</n-text>
</n-flex>
</template>
<template v-else-if="scanResults[logType.key].scanned">
<template v-if="scanResults[logType.key].items.length === 0">
<template v-else-if="getResult(logType.key).scanned">
<template v-if="getResult(logType.key).items.length === 0">
<n-empty :description="$gettext('No logs found')" size="small" />
</template>
<template v-else>
@@ -206,7 +216,7 @@ const anyLoading = computed(() => {
{ title: $gettext('Name'), key: 'name', ellipsis: { tooltip: true } },
{ title: $gettext('Size'), key: 'size', width: 120 }
]"
:data="scanResults[logType.key].items"
:data="getResult(logType.key).items"
:bordered="false"
size="small"
:max-height="200"

View File

@@ -5,7 +5,6 @@ import { useGettext } from 'vue3-gettext'
import website from '@/api/panel/website'
const show = defineModel<boolean>('show', { type: Boolean, required: true })
const type = defineModel<string>('type', { type: String, required: true })
const { $gettext } = useGettext()
@@ -24,16 +23,16 @@ const handleCreate = async () => {
return
}
// 去除空格
const name = parts[0].trim()
const domains = parts[1]
const name = (parts[0] ?? '').trim()
const domains = (parts[1] ?? '')
.trim()
.split(',')
.map((item) => item.trim())
const listens = parts[2]
const listens = (parts[2] ?? '')
.trim()
.split(',')
.map((item) => item.trim())
const path = parts[3].trim()
const path = (parts[3] ?? '').trim()
const remark = parts[4] ? parts[4].trim() : ''
let model = {
name: '',