mirror of
https://github.com/1Panel-dev/1Panel.git
synced 2026-10-20 16:00:51 +00:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
7cdc4f476a |
@@ -1,84 +0,0 @@
|
||||
package v2
|
||||
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/agent/app/api/v2/helper"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// @Tags Dashboard
|
||||
// @Summary List quick directories
|
||||
// @Success 200 {array} model.QuickDirectory
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /dashboard/quick/directory [get]
|
||||
func (b *BaseApi) ListQuickDirectories(c *gin.Context) {
|
||||
items, err := dashboardService.ListQuickDirectories()
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, items)
|
||||
}
|
||||
|
||||
// @Tags Dashboard
|
||||
// @Summary Create quick directory
|
||||
// @Accept json
|
||||
// @Param request body dto.QuickDirectoryCreate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /dashboard/quick/directory [post]
|
||||
func (b *BaseApi) CreateQuickDirectory(c *gin.Context) {
|
||||
var req dto.QuickDirectoryCreate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
item, err := dashboardService.CreateQuickDirectory(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, item)
|
||||
}
|
||||
|
||||
// @Tags Dashboard
|
||||
// @Summary Update quick directory
|
||||
// @Accept json
|
||||
// @Param request body dto.QuickDirectoryUpdate true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /dashboard/quick/directory/update [post]
|
||||
func (b *BaseApi) UpdateQuickDirectory(c *gin.Context) {
|
||||
var req dto.QuickDirectoryUpdate
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
item, err := dashboardService.UpdateQuickDirectory(req)
|
||||
if err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.SuccessWithData(c, item)
|
||||
}
|
||||
|
||||
// @Tags Dashboard
|
||||
// @Summary Delete quick directory
|
||||
// @Accept json
|
||||
// @Param request body dto.QuickDirectoryDelete true "request"
|
||||
// @Success 200
|
||||
// @Security ApiKeyAuth
|
||||
// @Security Timestamp
|
||||
// @Router /dashboard/quick/directory/del [post]
|
||||
func (b *BaseApi) DeleteQuickDirectory(c *gin.Context) {
|
||||
var req dto.QuickDirectoryDelete
|
||||
if err := helper.CheckBindAndValidate(&req, c); err != nil {
|
||||
return
|
||||
}
|
||||
if err := dashboardService.DeleteQuickDirectory(req); err != nil {
|
||||
helper.InternalServer(c, err)
|
||||
return
|
||||
}
|
||||
helper.Success(c)
|
||||
}
|
||||
@@ -31,27 +31,7 @@ type DashboardBase struct {
|
||||
}
|
||||
|
||||
type ChangeQuicks struct {
|
||||
Quicks []QuickVisibility `json:"quicks" validate:"required,dive"`
|
||||
}
|
||||
|
||||
type QuickVisibility struct {
|
||||
ID uint `json:"id" validate:"required"`
|
||||
IsShow bool `json:"isShow"`
|
||||
}
|
||||
|
||||
type QuickDirectoryCreate struct {
|
||||
Path string `json:"path" validate:"required"`
|
||||
Alias string `json:"alias"`
|
||||
}
|
||||
|
||||
type QuickDirectoryDelete struct {
|
||||
ID uint `json:"id" validate:"required"`
|
||||
Legacy bool `json:"legacy"`
|
||||
}
|
||||
|
||||
type QuickDirectoryUpdate struct {
|
||||
QuickDirectoryDelete
|
||||
QuickDirectoryCreate
|
||||
Quicks []QuickJump `json:"quicks"`
|
||||
}
|
||||
|
||||
type QuickJump struct {
|
||||
|
||||
@@ -5,13 +5,6 @@ type AppLauncher struct {
|
||||
Key string `json:"key"`
|
||||
}
|
||||
|
||||
type QuickDirectory struct {
|
||||
BaseModel
|
||||
Path string `json:"path"`
|
||||
Alias string `json:"alias"`
|
||||
Legacy bool `json:"legacy" gorm:"-"`
|
||||
}
|
||||
|
||||
type QuickJump struct {
|
||||
BaseModel
|
||||
Name string `json:"name"`
|
||||
|
||||
@@ -3,7 +3,6 @@ package repo
|
||||
import (
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/global"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type LauncherRepo struct{}
|
||||
@@ -18,11 +17,6 @@ type ILauncherRepo interface {
|
||||
GetQuickJump(opts ...DBOption) (model.QuickJump, error)
|
||||
ListQuickJump(withAll bool) []model.QuickJump
|
||||
UpdateQuicks(quicks []model.QuickJump) error
|
||||
ListQuickDirectories() ([]model.QuickDirectory, error)
|
||||
CountQuickDirectories() (int64, error)
|
||||
CreateQuickDirectory(item *model.QuickDirectory) error
|
||||
UpdateQuickDirectory(item *model.QuickDirectory) error
|
||||
DeleteQuickDirectory(id uint, legacy bool) error
|
||||
}
|
||||
|
||||
func NewILauncherRepo() ILauncherRepo {
|
||||
@@ -101,73 +95,13 @@ func (u *LauncherRepo) UpdateQuicks(quicks []model.QuickJump) error {
|
||||
for _, item := range quicks {
|
||||
if err := tx.Model(&model.QuickJump{}).Where("id = ?", item.ID).Updates(map[string]interface{}{
|
||||
"is_show": item.IsShow,
|
||||
"detail": item.Detail,
|
||||
"alias": item.Alias,
|
||||
}).Error; err != nil {
|
||||
tx.Rollback()
|
||||
return err
|
||||
}
|
||||
}
|
||||
return tx.Commit().Error
|
||||
}
|
||||
|
||||
// Legacy entries remain in the overview configuration until explicitly deleted.
|
||||
func legacyQuickDirectories(db *gorm.DB) *gorm.DB {
|
||||
return db.Model(&model.QuickJump{}).Where("name = ? AND is_show = ? AND detail <> ?", "File", true, "")
|
||||
}
|
||||
|
||||
func (u *LauncherRepo) ListQuickDirectories() ([]model.QuickDirectory, error) {
|
||||
items := make([]model.QuickDirectory, 0)
|
||||
if err := global.DB.Order("id ASC").Find(&items).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var quicks []model.QuickJump
|
||||
if err := legacyQuickDirectories(global.DB).Order("id ASC").Find(&quicks).Error; err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for _, quick := range quicks {
|
||||
items = append(items, model.QuickDirectory{BaseModel: quick.BaseModel, Path: quick.Detail, Alias: quick.Alias, Legacy: true})
|
||||
}
|
||||
return items, nil
|
||||
}
|
||||
func (u *LauncherRepo) CountQuickDirectories() (int64, error) {
|
||||
var count, legacyCount int64
|
||||
if err := global.DB.Model(&model.QuickDirectory{}).Count(&count).Error; err != nil {
|
||||
return 0, err
|
||||
}
|
||||
if err := legacyQuickDirectories(global.DB).Count(&legacyCount).Error; err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return count + legacyCount, nil
|
||||
}
|
||||
func (u *LauncherRepo) CreateQuickDirectory(item *model.QuickDirectory) error {
|
||||
return global.DB.Create(item).Error
|
||||
}
|
||||
func (u *LauncherRepo) UpdateQuickDirectory(item *model.QuickDirectory) error {
|
||||
var result *gorm.DB
|
||||
if item.Legacy {
|
||||
result = legacyQuickDirectories(global.DB).Where("id = ?", item.ID).Updates(map[string]interface{}{"detail": item.Path, "alias": item.Alias})
|
||||
} else {
|
||||
result = global.DB.Model(&model.QuickDirectory{}).Where("id = ?", item.ID).Updates(map[string]interface{}{"path": item.Path, "alias": item.Alias})
|
||||
}
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
if result.RowsAffected == 0 {
|
||||
return gorm.ErrRecordNotFound
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (u *LauncherRepo) DeleteQuickDirectory(id uint, legacy bool) error {
|
||||
var result *gorm.DB
|
||||
if legacy {
|
||||
result = legacyQuickDirectories(global.DB).Where("id = ?", id).Updates(map[string]interface{}{"detail": nil, "alias": nil})
|
||||
} else {
|
||||
result = global.DB.Delete(&model.QuickDirectory{}, id)
|
||||
}
|
||||
if result.Error != nil {
|
||||
return result.Error
|
||||
}
|
||||
if result.RowsAffected == 0 {
|
||||
return gorm.ErrRecordNotFound
|
||||
}
|
||||
tx.Commit()
|
||||
return nil
|
||||
}
|
||||
|
||||
@@ -43,10 +43,6 @@ type IDashboardService interface {
|
||||
|
||||
LoadQuickOptions() []dto.QuickJump
|
||||
ChangeQuick(req dto.ChangeQuicks) error
|
||||
ListQuickDirectories() ([]model.QuickDirectory, error)
|
||||
CreateQuickDirectory(req dto.QuickDirectoryCreate) (*model.QuickDirectory, error)
|
||||
UpdateQuickDirectory(req dto.QuickDirectoryUpdate) (*model.QuickDirectory, error)
|
||||
DeleteQuickDirectory(req dto.QuickDirectoryDelete) error
|
||||
|
||||
LoadAppLauncher(ctx *gin.Context) ([]dto.AppLauncher, error)
|
||||
ChangeShow(req dto.SettingUpdate) error
|
||||
@@ -401,15 +397,16 @@ func (u *DashboardService) LoadQuickOptions() []dto.QuickJump {
|
||||
}
|
||||
func (u *DashboardService) ChangeQuick(req dto.ChangeQuicks) error {
|
||||
showCount := 0
|
||||
quicks := make([]model.QuickJump, 0, len(req.Quicks))
|
||||
var quicks []model.QuickJump
|
||||
for _, item := range req.Quicks {
|
||||
var quick model.QuickJump
|
||||
if item.IsShow {
|
||||
showCount++
|
||||
}
|
||||
quicks = append(quicks, model.QuickJump{
|
||||
BaseModel: model.BaseModel{ID: item.ID},
|
||||
IsShow: item.IsShow,
|
||||
})
|
||||
if err := copier.Copy(&quick, &item); err != nil {
|
||||
return err
|
||||
}
|
||||
quicks = append(quicks, quick)
|
||||
}
|
||||
if showCount == 0 {
|
||||
return buserr.New("ErrMinQuickJump")
|
||||
@@ -709,13 +706,6 @@ func loadQuickJump(base *dto.DashboardBase) {
|
||||
quicks := launcherRepo.ListQuickJump(false)
|
||||
for i := 0; i < len(quicks); i++ {
|
||||
switch quicks[i].Name {
|
||||
case "File":
|
||||
count, err := launcherRepo.CountQuickDirectories()
|
||||
if err != nil {
|
||||
global.LOG.Errorf("load quick directory count: %v", err)
|
||||
}
|
||||
quicks[i].Detail = fmt.Sprintf("%d", count)
|
||||
quicks[i].Alias = ""
|
||||
case "Agent":
|
||||
quicks[i].Detail = fmt.Sprintf("%d", base.AgentNumber)
|
||||
case "Website":
|
||||
|
||||
@@ -1,49 +0,0 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"strings"
|
||||
|
||||
"github.com/1Panel-dev/1Panel/agent/app/dto"
|
||||
"github.com/1Panel-dev/1Panel/agent/app/model"
|
||||
"github.com/1Panel-dev/1Panel/agent/buserr"
|
||||
)
|
||||
|
||||
func (u *DashboardService) ListQuickDirectories() ([]model.QuickDirectory, error) {
|
||||
return launcherRepo.ListQuickDirectories()
|
||||
}
|
||||
|
||||
func quickDirectoryValues(req dto.QuickDirectoryCreate) (model.QuickDirectory, error) {
|
||||
item := model.QuickDirectory{Path: req.Path, Alias: strings.TrimSpace(req.Alias)}
|
||||
if strings.TrimSpace(item.Path) == "" {
|
||||
return item, buserr.WithDetail("ErrInvalidParams", "path is required", nil)
|
||||
}
|
||||
return item, nil
|
||||
}
|
||||
|
||||
func (u *DashboardService) CreateQuickDirectory(req dto.QuickDirectoryCreate) (*model.QuickDirectory, error) {
|
||||
item, err := quickDirectoryValues(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := launcherRepo.CreateQuickDirectory(&item); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &item, nil
|
||||
}
|
||||
|
||||
func (u *DashboardService) UpdateQuickDirectory(req dto.QuickDirectoryUpdate) (*model.QuickDirectory, error) {
|
||||
item, err := quickDirectoryValues(req.QuickDirectoryCreate)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
item.ID = req.ID
|
||||
item.Legacy = req.Legacy
|
||||
if err := launcherRepo.UpdateQuickDirectory(&item); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &item, nil
|
||||
}
|
||||
|
||||
func (u *DashboardService) DeleteQuickDirectory(req dto.QuickDirectoryDelete) error {
|
||||
return launcherRepo.DeleteQuickDirectory(req.ID, req.Legacy)
|
||||
}
|
||||
@@ -115,7 +115,6 @@ func agentDBMigrations() []*gormigrate.Migration {
|
||||
migrations.AddMonitorSettings,
|
||||
migrations.AddVLLMRequestMetrics,
|
||||
migrations.AddVLLMSampleInterval,
|
||||
migrations.AddQuickDirectory,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1888,10 +1888,3 @@ var AddVLLMSampleInterval = &gormigrate.Migration{
|
||||
return global.VLLMMonitorDB.AutoMigrate(&model.MonitorVLLM{})
|
||||
},
|
||||
}
|
||||
|
||||
var AddQuickDirectory = &gormigrate.Migration{
|
||||
ID: "20261016-add-quick-directory",
|
||||
Migrate: func(tx *gorm.DB) error {
|
||||
return tx.AutoMigrate(&model.QuickDirectory{})
|
||||
},
|
||||
}
|
||||
|
||||
@@ -14,10 +14,6 @@ func (s *DashboardRouter) InitRouter(Router *gin.RouterGroup) {
|
||||
cmdRouter.GET("/base/os", baseApi.LoadDashboardOsInfo)
|
||||
cmdRouter.GET("/quick/option", baseApi.LoadQuickOption)
|
||||
cmdRouter.POST("/quick/change", baseApi.UpdateQuickJump)
|
||||
cmdRouter.GET("/quick/directory", baseApi.ListQuickDirectories)
|
||||
cmdRouter.POST("/quick/directory", baseApi.CreateQuickDirectory)
|
||||
cmdRouter.POST("/quick/directory/update", baseApi.UpdateQuickDirectory)
|
||||
cmdRouter.POST("/quick/directory/del", baseApi.DeleteQuickDirectory)
|
||||
cmdRouter.GET("/app/launcher", baseApi.LoadAppLauncher)
|
||||
cmdRouter.POST("/app/launcher/show", baseApi.UpdateAppLauncher)
|
||||
cmdRouter.POST("/app/launcher/option", baseApi.LoadAppLauncherOption)
|
||||
|
||||
@@ -8,12 +8,6 @@ export namespace Dashboard {
|
||||
|
||||
diskSize: number;
|
||||
}
|
||||
export interface QuickDirectory {
|
||||
id: number;
|
||||
legacy?: boolean;
|
||||
path: string;
|
||||
alias: string;
|
||||
}
|
||||
export interface QuickJump {
|
||||
id: number;
|
||||
name: string;
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import http from '@/api';
|
||||
import type { AxiosRequestConfig } from 'axios';
|
||||
import { ResPage, SearchWithPage } from '../interface';
|
||||
import { Container } from '../interface/container';
|
||||
import { TimeoutEnum } from '@/enums/http-enum';
|
||||
@@ -23,10 +24,11 @@ export const loadContainerUsers = (name: string) => {
|
||||
export const listContainerFiles = (params: Container.ContainerFileReq) => {
|
||||
return http.post<Array<Container.ContainerFileInfo>>(`/containers/files/search`, params, TimeoutEnum.T_40S);
|
||||
};
|
||||
export const uploadContainerFile = (params: FormData) => {
|
||||
export const uploadContainerFile = (params: FormData, config?: AxiosRequestConfig & { skipErrorMessage?: boolean }) => {
|
||||
return http.upload(`/containers/files/upload`, params, {
|
||||
headers: { 'Content-Type': 'multipart/form-data' },
|
||||
timeout: TimeoutEnum.T_5M,
|
||||
...config,
|
||||
headers: { 'Content-Type': 'multipart/form-data', ...config?.headers },
|
||||
});
|
||||
};
|
||||
export const getContainerFileContent = (params: Container.ContainerFileReq) => {
|
||||
|
||||
@@ -8,7 +8,7 @@ export const loadOsInfo = () => {
|
||||
export const loadQuickOption = () => {
|
||||
return http.get<Array<Dashboard.QuickJump>>(`/dashboard/quick/option`);
|
||||
};
|
||||
export const changeQuick = (quicks: Array<Pick<Dashboard.QuickJump, 'id' | 'isShow'>>) => {
|
||||
export const changeQuick = (quicks: Array<Dashboard.QuickJump>) => {
|
||||
return http.post(`/dashboard/quick/change`, { quicks: quicks });
|
||||
};
|
||||
export const loadAppLauncher = () => {
|
||||
@@ -48,11 +48,3 @@ export const loadTopMem = () => {
|
||||
export const systemRestart = (operation: string) => {
|
||||
return http.post(`/dashboard/system/restart/${operation}`);
|
||||
};
|
||||
|
||||
export const loadQuickDirectories = () => http.get<Dashboard.QuickDirectory[]>('/dashboard/quick/directory');
|
||||
export const createQuickDirectory = (data: Omit<Dashboard.QuickDirectory, 'id'>) =>
|
||||
http.post<Dashboard.QuickDirectory>('/dashboard/quick/directory', data);
|
||||
export const updateQuickDirectory = (data: Dashboard.QuickDirectory) =>
|
||||
http.post<Dashboard.QuickDirectory>('/dashboard/quick/directory/update', data);
|
||||
export const deleteQuickDirectory = (id: number, legacy = false) =>
|
||||
http.post('/dashboard/quick/directory/del', { id, legacy });
|
||||
|
||||
@@ -196,7 +196,7 @@ const favorites = ref<File.Favorite[]>([]);
|
||||
const { searchableStatus, searchablePath, searchableInputRef, searchableInputBlur } = useSearchableForSelect(paths);
|
||||
const oldUrl = ref<string>('');
|
||||
|
||||
const em = defineEmits(['choose', 'close']);
|
||||
const em = defineEmits(['choose']);
|
||||
|
||||
const form = reactive({
|
||||
path: '/',
|
||||
@@ -245,7 +245,6 @@ const handleClose = () => {
|
||||
open.value = false;
|
||||
selectRow.value = { path: '', name: '' };
|
||||
selectRows.value = [];
|
||||
em('close');
|
||||
};
|
||||
|
||||
const openPage = () => {
|
||||
|
||||
@@ -494,7 +494,6 @@ const message = {
|
||||
recommend: 'Recommended',
|
||||
dir: 'Directory',
|
||||
alias: 'Alias',
|
||||
quickDirDelete: 'Delete ({0})?',
|
||||
quickDir: 'Quick Access',
|
||||
minQuickJump: 'Add at least one quick access entry',
|
||||
maxQuickJump: 'You can add up to four quick access entries',
|
||||
|
||||
@@ -493,7 +493,6 @@ const message = {
|
||||
home: {
|
||||
recommend: 'Recomendado',
|
||||
dir: 'Directorio',
|
||||
quickDirDelete: '¿Confirmar la eliminación de ({0})?',
|
||||
quickDir: 'Acceso rápido',
|
||||
minQuickJump: 'Agrega al menos una entrada de acceso rápido',
|
||||
maxQuickJump: 'Puedes agregar hasta cuatro entradas de acceso rápido',
|
||||
|
||||
@@ -489,7 +489,6 @@ const message = {
|
||||
recommend: 'پیشنهادی',
|
||||
dir: 'دایرکتوری',
|
||||
alias: 'نام مستعار',
|
||||
quickDirDelete: 'حذف ({0}) را تأیید میکنید؟',
|
||||
quickDir: 'دسترسی سریع',
|
||||
minQuickJump: 'حداقل یک ورودی دسترسی سریع اضافه کنید',
|
||||
maxQuickJump: 'میتوانید حداکثر چهار ورودی دسترسی سریع اضافه کنید',
|
||||
|
||||
@@ -492,7 +492,6 @@ const message = {
|
||||
recommend: 'おすすめ',
|
||||
dir: 'ディレクトリ',
|
||||
alias: 'エイリアス',
|
||||
quickDirDelete: '({0})を削除しますか?',
|
||||
quickDir: 'クイックアクセス',
|
||||
minQuickJump: 'クイックアクセス項目を1つ以上追加してください',
|
||||
maxQuickJump: '最大4件のクイックアクセス項目を追加できます',
|
||||
|
||||
@@ -487,7 +487,6 @@ const message = {
|
||||
recommend: '추천',
|
||||
dir: '디렉토리',
|
||||
alias: '별칭',
|
||||
quickDirDelete: '({0})을(를) 삭제하시겠습니까?',
|
||||
quickDir: '빠른 액세스',
|
||||
minQuickJump: '최소 하나의 빠른 액세스 항목을 추가하세요',
|
||||
maxQuickJump: '빠른 액세스를 최대 4개까지 추가할 수 있습니다',
|
||||
|
||||
@@ -487,7 +487,6 @@ const message = {
|
||||
recommend: 'ແນະນຳ',
|
||||
dir: 'ໄດເຣັກທໍຣີ',
|
||||
alias: 'ຊື່ຫຼິ້ນ',
|
||||
quickDirDelete: 'ຢືນຢັນການລຶບ ({0}) ຫຼືບໍ່?',
|
||||
quickDir: 'ເຂົ້າເຖິງດ່ວນ',
|
||||
minQuickJump: 'ເພີ່ມການເຂົ້າເຖິງດ່ວນຢ່າງໜ້ອຍໜຶ່ງລາຍການ',
|
||||
maxQuickJump: 'ທ່ານສາມາດເພີ່ມການເຂົ້າເຖິງດ່ວນໄດ້ສູງສຸດສີ່ລາຍການ',
|
||||
|
||||
@@ -499,7 +499,6 @@ const message = {
|
||||
recommend: 'Disyorkan',
|
||||
dir: 'Direktori',
|
||||
alias: 'Alias',
|
||||
quickDirDelete: 'Sahkan pemadaman ({0})?',
|
||||
quickDir: 'Akses Pantas',
|
||||
minQuickJump: 'Tambah sekurang-kurangnya satu entri akses pantas',
|
||||
maxQuickJump: 'Anda boleh menambah sehingga empat entri akses pantas',
|
||||
|
||||
@@ -492,7 +492,6 @@ const message = {
|
||||
recommend: 'Recomendado',
|
||||
dir: 'Diretório',
|
||||
alias: 'Apelido',
|
||||
quickDirDelete: 'Confirmar a exclusão de ({0})?',
|
||||
quickDir: 'Acesso rápido',
|
||||
minQuickJump: 'Adicione pelo menos uma entrada de acesso rápido',
|
||||
maxQuickJump: 'Você pode adicionar até quatro entradas de acesso rápido',
|
||||
|
||||
@@ -491,7 +491,6 @@ const message = {
|
||||
recommend: 'Рекомендуемое',
|
||||
dir: 'Каталог',
|
||||
alias: 'Псевдоним',
|
||||
quickDirDelete: 'Подтвердить удаление ({0})?',
|
||||
quickDir: 'Быстрый доступ',
|
||||
minQuickJump: 'Добавьте хотя бы одну запись быстрого доступа',
|
||||
maxQuickJump: 'Вы можете добавить до четырех записей быстрого доступа',
|
||||
|
||||
@@ -495,7 +495,6 @@ const message = {
|
||||
recommend: 'Önerilen',
|
||||
dir: 'Dizin',
|
||||
alias: 'Takma Ad',
|
||||
quickDirDelete: '({0}) silinsin mi?',
|
||||
quickDir: 'Hızlı Erişim',
|
||||
minQuickJump: 'En az bir hızlı erişim girdisi ekleyin',
|
||||
maxQuickJump: 'En fazla dört hızlı erişim girdisi ekleyebilirsiniz',
|
||||
|
||||
@@ -473,7 +473,6 @@ const message = {
|
||||
recommend: '推薦',
|
||||
dir: '目錄',
|
||||
alias: '別名',
|
||||
quickDirDelete: '確定刪除({0})',
|
||||
quickDir: '快捷目錄',
|
||||
minQuickJump: '請至少設定一個快速跳轉入口',
|
||||
maxQuickJump: '最多可設定四個快速跳轉入口',
|
||||
|
||||
@@ -473,7 +473,6 @@ const message = {
|
||||
recommend: '推荐',
|
||||
dir: '目录',
|
||||
alias: '别名',
|
||||
quickDirDelete: '确定删除({0})',
|
||||
quickDir: '快捷目录',
|
||||
minQuickJump: '请至少设置一个快速跳转入口!',
|
||||
maxQuickJump: '最多可设置四个快速跳转入口!',
|
||||
|
||||
@@ -32,11 +32,9 @@
|
||||
@blur="cancelPathEditing"
|
||||
@keyup.enter.prevent="submitPathInput"
|
||||
/>
|
||||
<el-upload ref="uploadRef" :auto-upload="false" :show-file-list="false" :on-change="onUploadChange">
|
||||
<el-button class="mt-2" :loading="uploading" type="primary" plain>
|
||||
{{ $t('commons.button.upload') }}
|
||||
</el-button>
|
||||
</el-upload>
|
||||
<el-button class="mt-2" type="primary" plain @click="onUpload">
|
||||
{{ $t('commons.button.upload') }}
|
||||
</el-button>
|
||||
<el-button class="mt-2 ml-3" plain :disabled="selectedRows.length === 0" @click="onBatchDelete">
|
||||
{{ $t('commons.button.delete') }}
|
||||
</el-button>
|
||||
@@ -97,6 +95,7 @@
|
||||
</el-form>
|
||||
</template>
|
||||
</DrawerPro>
|
||||
<FileUpload ref="uploadRef" @close="loadContainerFiles" />
|
||||
<DialogPro v-model="previewVisible" :title="previewTitle" size="w-70" :close-on-click-modal="true">
|
||||
<el-alert
|
||||
v-if="previewTruncated"
|
||||
@@ -118,11 +117,11 @@ import {
|
||||
getContainerFileContent,
|
||||
getContainerFileSize,
|
||||
listContainerFiles,
|
||||
uploadContainerFile,
|
||||
} from '@/api/modules/container';
|
||||
import { MsgError, MsgSuccess, MsgWarning } from '@/utils/message';
|
||||
import i18n from '@/lang';
|
||||
import { ElInput, ElMessageBox, UploadFile, UploadInstance } from 'element-plus';
|
||||
import { ElInput, ElMessageBox } from 'element-plus';
|
||||
import FileUpload from '@/views/host/file-management/upload/index.vue';
|
||||
import { Document, FolderOpened, HomeFilled } from '@element-plus/icons-vue';
|
||||
import { computeSize2 } from '@/utils/size';
|
||||
const visible = ref(false);
|
||||
@@ -130,9 +129,8 @@ const title = ref('');
|
||||
const containerID = ref('');
|
||||
const filePath = ref('/');
|
||||
const containerFiles = ref<any[]>([]);
|
||||
const uploadRef = ref<UploadInstance>();
|
||||
const uploadRef = ref<InstanceType<typeof FileUpload>>();
|
||||
const pathInputRef = ref<InstanceType<typeof ElInput>>();
|
||||
const uploading = ref(false);
|
||||
const previewVisible = ref(false);
|
||||
const previewTitle = ref('');
|
||||
const previewContent = ref('');
|
||||
@@ -253,27 +251,8 @@ const submitPathInput = async () => {
|
||||
}
|
||||
};
|
||||
|
||||
const onUploadChange = async (uploadFile: UploadFile) => {
|
||||
if (!uploadFile.raw) {
|
||||
return;
|
||||
}
|
||||
const formData = new FormData();
|
||||
formData.append('containerID', containerID.value);
|
||||
formData.append('path', filePath.value);
|
||||
formData.append('file', uploadFile.raw);
|
||||
uploading.value = true;
|
||||
await uploadContainerFile(formData)
|
||||
.then(async () => {
|
||||
MsgSuccess(i18n.global.t('file.uploadSuccess'));
|
||||
await loadContainerFiles();
|
||||
})
|
||||
.catch((err) => {
|
||||
MsgError(err?.message || i18n.global.t('commons.msg.operationFailed'));
|
||||
})
|
||||
.finally(() => {
|
||||
uploading.value = false;
|
||||
uploadRef.value?.clearFiles();
|
||||
});
|
||||
const onUpload = () => {
|
||||
uploadRef.value?.acceptParams({ path: filePath.value, containerID: containerID.value });
|
||||
};
|
||||
|
||||
const onOpenFile = async (path: string) => {
|
||||
|
||||
@@ -50,12 +50,20 @@
|
||||
<el-col :span="6" v-for="item in baseInfo.quickJump" :key="item.name">
|
||||
<span>{{ $t(item.title, 2) }}</span>
|
||||
<div class="count">
|
||||
<QuickDirectories
|
||||
v-if="item.name === 'File'"
|
||||
:count="item.detail"
|
||||
:disabled="!checkPermission('File')"
|
||||
@change="updateQuickDirectoryCount"
|
||||
/>
|
||||
<el-tooltip
|
||||
v-if="item.alias || item.detail.length > 20"
|
||||
:content="item.detail"
|
||||
placement="bottom"
|
||||
>
|
||||
<el-button
|
||||
link
|
||||
:disabled="!checkPermission('File')"
|
||||
type="primary"
|
||||
@click="quickJump(item)"
|
||||
>
|
||||
{{ item.alias || item.detail.substring(0, 18) + '...' }}
|
||||
</el-button>
|
||||
</el-tooltip>
|
||||
<el-button
|
||||
link
|
||||
:disabled="!checkPermission(item.name)"
|
||||
@@ -442,7 +450,6 @@ import SystemStatus from '@/views/home/status/index.vue';
|
||||
import AppLauncher from '@/views/home/app/index.vue';
|
||||
import VCharts from '@/components/v-charts/index.vue';
|
||||
import QuickJump from '@/views/home/quick/index.vue';
|
||||
import QuickDirectories from '@/views/home/quick/directories.vue';
|
||||
import CardWithHeader from '@/components/card-with-header/index.vue';
|
||||
import MarkDownEditor from '@/components/mkdown-editor/index.vue';
|
||||
import i18n from '@/lang';
|
||||
@@ -463,7 +470,7 @@ import {
|
||||
updateMemo,
|
||||
updateSetting,
|
||||
} from '@/api/modules/setting';
|
||||
import { routerToNameWithQuery, routerToPath } from '@/utils/router';
|
||||
import { routerToFileWithPath, routerToNameWithQuery, routerToPath } from '@/utils/router';
|
||||
import { getWelcomePage } from '@/api/modules/auth';
|
||||
import {
|
||||
clearDashboardCache,
|
||||
@@ -816,12 +823,10 @@ const onLoadBaseInfo = async (isInit: boolean, range: string) => {
|
||||
}
|
||||
};
|
||||
|
||||
const updateQuickDirectoryCount = (count: number) => {
|
||||
const item = baseInfo.value.quickJump.find((item) => item.name === 'File');
|
||||
if (item) item.detail = String(count);
|
||||
};
|
||||
|
||||
const quickJump = (item: any) => {
|
||||
if (item.name === 'File') {
|
||||
return routerToFileWithPath(item.detail);
|
||||
}
|
||||
return routerToPath(item.router);
|
||||
};
|
||||
|
||||
|
||||
@@ -1,315 +0,0 @@
|
||||
<template>
|
||||
<el-popover
|
||||
trigger="click"
|
||||
placement="bottom"
|
||||
:width="680"
|
||||
:popper-style="{ maxWidth: 'calc(100vw - 32px)' }"
|
||||
:visible="visible"
|
||||
@update:visible="setVisible"
|
||||
:disabled="disabled"
|
||||
:title="$t('home.quickDir')"
|
||||
@show="load"
|
||||
>
|
||||
<template #reference>
|
||||
<el-button link type="primary" :disabled="disabled">{{ count }}</el-button>
|
||||
</template>
|
||||
<div v-loading="loading" class="quick-directories">
|
||||
<div class="directory-search">
|
||||
<el-input
|
||||
v-model="searchInput"
|
||||
clearable
|
||||
prefix-icon="Search"
|
||||
:placeholder="`${$t('file.path')} / ${$t('home.alias')}`"
|
||||
:aria-label="$t('commons.button.search')"
|
||||
@keyup.enter="search"
|
||||
@clear="search"
|
||||
/>
|
||||
<el-button :disabled="loading" @click="search">{{ $t('commons.button.confirm') }}</el-button>
|
||||
</div>
|
||||
<div class="directory-row directory-header">
|
||||
<span>{{ $t('file.path') }}</span>
|
||||
<span>{{ $t('home.alias') }}</span>
|
||||
<span />
|
||||
</div>
|
||||
<div class="directory-list">
|
||||
<div v-if="!filteredRows.length" class="directory-empty">{{ $t('commons.msg.noneData') }}</div>
|
||||
<div v-for="row in filteredRows" :key="row.key" class="directory-row">
|
||||
<el-input
|
||||
v-model="row.path"
|
||||
:readonly="!row.editing"
|
||||
:disabled="row.busy"
|
||||
:aria-label="$t('file.path')"
|
||||
>
|
||||
<template #prepend>
|
||||
<el-button
|
||||
icon="Folder"
|
||||
:disabled="!row.editing || row.busy"
|
||||
:aria-label="$t('file.list')"
|
||||
@click="selectPath(row)"
|
||||
/>
|
||||
</template>
|
||||
</el-input>
|
||||
<el-input
|
||||
v-model="row.alias"
|
||||
:readonly="!row.editing"
|
||||
:disabled="row.busy"
|
||||
:aria-label="$t('home.alias')"
|
||||
/>
|
||||
<div class="directory-actions">
|
||||
<el-button
|
||||
link
|
||||
:type="row.editing ? 'success' : 'primary'"
|
||||
:icon="row.editing ? 'Check' : 'Edit'"
|
||||
:aria-label="$t(row.editing ? 'commons.button.save' : 'commons.button.edit')"
|
||||
:disabled="loading || row.busy || (row.editing && !row.path.trim())"
|
||||
@click="row.editing ? save(row) : (row.editing = true)"
|
||||
/>
|
||||
<el-button
|
||||
v-if="row.id || rows.length > 1"
|
||||
link
|
||||
:type="row.editing ? 'info' : 'danger'"
|
||||
:icon="row.editing ? 'Close' : 'Delete'"
|
||||
:aria-label="$t('commons.button.delete')"
|
||||
:disabled="loading || row.busy"
|
||||
@click="remove(row)"
|
||||
/>
|
||||
<el-button
|
||||
v-if="row.id"
|
||||
link
|
||||
type="primary"
|
||||
icon="Position"
|
||||
:title="$t('firewall.quickJump')"
|
||||
:aria-label="$t('firewall.quickJump')"
|
||||
:disabled="loading || row.busy"
|
||||
@click="routerToFileWithPath(row.savedPath)"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<el-button
|
||||
class="directory-add"
|
||||
link
|
||||
type="primary"
|
||||
icon="Plus"
|
||||
:disabled="loading || !loaded"
|
||||
@click="add"
|
||||
>
|
||||
{{ $t('commons.button.add') }}
|
||||
</el-button>
|
||||
</div>
|
||||
<div class="directory-footer">
|
||||
<el-button @click="close">{{ $t('commons.button.close') }}</el-button>
|
||||
</div>
|
||||
</el-popover>
|
||||
<FileList ref="fileRef" @choose="loadPath" @close="selectingRow = undefined" />
|
||||
</template>
|
||||
|
||||
<script lang="ts" setup>
|
||||
import { computed, ref } from 'vue';
|
||||
import { ElMessageBox } from 'element-plus';
|
||||
import i18n from '@/lang';
|
||||
import { routerToFileWithPath } from '@/utils/router';
|
||||
import FileList from '@/components/file-list/index.vue';
|
||||
import { Dashboard } from '@/api/interface/dashboard';
|
||||
import {
|
||||
loadQuickDirectories,
|
||||
createQuickDirectory,
|
||||
updateQuickDirectory,
|
||||
deleteQuickDirectory,
|
||||
} from '@/api/modules/dashboard';
|
||||
|
||||
defineProps<{ count: string; disabled: boolean }>();
|
||||
const emit = defineEmits<{ (e: 'change', count: number): void }>();
|
||||
type DirectoryRow = Dashboard.QuickDirectory & {
|
||||
key: number;
|
||||
savedPath: string;
|
||||
editing: boolean;
|
||||
busy: boolean;
|
||||
};
|
||||
const rows = ref<DirectoryRow[]>([]);
|
||||
const searchInput = ref('');
|
||||
const searchTerm = ref('');
|
||||
const filteredRows = computed(() =>
|
||||
rows.value.filter(
|
||||
(row) =>
|
||||
!row.id ||
|
||||
row.editing ||
|
||||
row.path.toLowerCase().includes(searchTerm.value) ||
|
||||
(row.alias ?? '').toLowerCase().includes(searchTerm.value),
|
||||
),
|
||||
);
|
||||
const search = () => {
|
||||
searchTerm.value = searchInput.value.trim().toLowerCase();
|
||||
};
|
||||
const fileRef = ref<InstanceType<typeof FileList>>();
|
||||
const selectingRow = ref<DirectoryRow>();
|
||||
const visible = ref(false);
|
||||
const loading = ref(false);
|
||||
const loaded = ref(false);
|
||||
let nextKey = 0;
|
||||
|
||||
const setVisible = (value: boolean) => {
|
||||
if (value) visible.value = true;
|
||||
};
|
||||
const close = () => {
|
||||
visible.value = false;
|
||||
};
|
||||
|
||||
const selectPath = (row: DirectoryRow) => {
|
||||
if (!row.editing || row.busy) return;
|
||||
selectingRow.value = row;
|
||||
fileRef.value?.acceptParams({ path: row.path, dir: false, isAll: true });
|
||||
};
|
||||
const loadPath = (path: string) => {
|
||||
const row = selectingRow.value;
|
||||
if (row?.editing && !row.busy) row.path = path;
|
||||
};
|
||||
|
||||
const add = () => {
|
||||
rows.value.push({
|
||||
key: nextKey++,
|
||||
id: 0,
|
||||
path: '',
|
||||
alias: '',
|
||||
savedPath: '',
|
||||
editing: true,
|
||||
busy: false,
|
||||
});
|
||||
};
|
||||
const updateCount = () => emit('change', rows.value.filter((row) => row.id).length);
|
||||
const load = async () => {
|
||||
if (loading.value || rows.value.some((row) => row.busy)) return;
|
||||
loading.value = true;
|
||||
loaded.value = false;
|
||||
searchInput.value = '';
|
||||
searchTerm.value = '';
|
||||
rows.value = [];
|
||||
try {
|
||||
const { data } = await loadQuickDirectories();
|
||||
rows.value = data.map((item) => ({
|
||||
...item,
|
||||
alias: item.alias ?? '',
|
||||
key: nextKey++,
|
||||
savedPath: item.path,
|
||||
editing: false,
|
||||
busy: false,
|
||||
}));
|
||||
if (!rows.value.length) add();
|
||||
loaded.value = true;
|
||||
updateCount();
|
||||
} finally {
|
||||
loading.value = false;
|
||||
}
|
||||
};
|
||||
const save = async (row: DirectoryRow) => {
|
||||
if (row.busy || !row.path.trim()) return;
|
||||
row.busy = true;
|
||||
try {
|
||||
const payload = { path: row.path, alias: (row.alias ?? '').trim() };
|
||||
const { data } = row.id
|
||||
? await updateQuickDirectory({ id: row.id, legacy: row.legacy ?? false, ...payload })
|
||||
: await createQuickDirectory(payload);
|
||||
Object.assign(row, data, { savedPath: data.path, editing: false });
|
||||
updateCount();
|
||||
} finally {
|
||||
row.busy = false;
|
||||
}
|
||||
};
|
||||
const remove = async (row: DirectoryRow) => {
|
||||
if (row.busy) return;
|
||||
if (row.id) {
|
||||
row.busy = true;
|
||||
try {
|
||||
await ElMessageBox.confirm('', i18n.global.t('home.quickDirDelete', [row.savedPath]), {
|
||||
confirmButtonText: i18n.global.t('commons.button.confirm'),
|
||||
cancelButtonText: i18n.global.t('commons.button.cancel'),
|
||||
customClass: 'quick-directory-delete',
|
||||
type: 'warning',
|
||||
center: true,
|
||||
});
|
||||
} catch {
|
||||
row.busy = false;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
await deleteQuickDirectory(row.id, row.legacy ?? false);
|
||||
} catch (error) {
|
||||
row.busy = false;
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
rows.value = rows.value.filter((item) => item.key !== row.key);
|
||||
if (!rows.value.length) add();
|
||||
updateCount();
|
||||
};
|
||||
</script>
|
||||
|
||||
<style lang="scss" scoped>
|
||||
.quick-directories {
|
||||
padding: 12px;
|
||||
background: var(--el-fill-color-light);
|
||||
border-radius: 6px;
|
||||
}
|
||||
.directory-search {
|
||||
display: flex;
|
||||
gap: 12px;
|
||||
padding: 0 10px 10px;
|
||||
}
|
||||
.directory-empty {
|
||||
padding: 20px;
|
||||
text-align: center;
|
||||
color: var(--el-text-color-secondary);
|
||||
}
|
||||
.directory-row {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) minmax(0, 1fr) 96px;
|
||||
gap: 12px;
|
||||
align-items: center;
|
||||
padding: 10px;
|
||||
}
|
||||
.directory-header {
|
||||
font-weight: 600;
|
||||
}
|
||||
.directory-list {
|
||||
max-height: 360px;
|
||||
overflow-y: auto;
|
||||
}
|
||||
.directory-actions {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
:deep(.el-button + .el-button) {
|
||||
margin-left: 8px;
|
||||
}
|
||||
}
|
||||
.directory-footer {
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
margin-top: 12px;
|
||||
}
|
||||
.directory-add {
|
||||
width: 100%;
|
||||
margin-top: 8px;
|
||||
padding: 12px;
|
||||
}
|
||||
</style>
|
||||
<style lang="scss">
|
||||
.quick-directory-delete {
|
||||
.el-message-box__title {
|
||||
justify-content: center;
|
||||
gap: 10px;
|
||||
padding: 14px 16px;
|
||||
border-radius: 4px;
|
||||
background-color: #59472a;
|
||||
color: #f5cf87;
|
||||
overflow-wrap: anywhere;
|
||||
}
|
||||
.el-message-box__status {
|
||||
flex-shrink: 0;
|
||||
font-size: 24px;
|
||||
color: #f2b329;
|
||||
}
|
||||
.el-message-box__content {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
@@ -10,6 +10,25 @@
|
||||
<el-table-column prop="isShow" :label="$t('setting.ifShow')">
|
||||
<template #default="{ row }">
|
||||
<el-switch v-model="row.isShow" />
|
||||
<div v-if="row.name === 'File' && row.isShow">
|
||||
<el-input v-model="row.detail" class="w-full">
|
||||
<template #prepend>
|
||||
<el-button
|
||||
class="w-16"
|
||||
v-if="row.name === 'File' && row.isShow"
|
||||
icon="Folder"
|
||||
@click="fileRef.acceptParams({ path: row.detail, isAll: true })"
|
||||
/>
|
||||
</template>
|
||||
</el-input>
|
||||
<el-input v-model="row.alias" class="mt-1">
|
||||
<template #prepend>
|
||||
<el-button class="w-16">
|
||||
{{ $t('home.alias') }}
|
||||
</el-button>
|
||||
</template>
|
||||
</el-input>
|
||||
</div>
|
||||
</template>
|
||||
</el-table-column>
|
||||
</ComplexTable>
|
||||
@@ -20,12 +39,14 @@
|
||||
{{ $t('commons.button.confirm') }}
|
||||
</el-button>
|
||||
</template>
|
||||
<FileList ref="fileRef" @choose="loadDir" />
|
||||
</DrawerPro>
|
||||
</template>
|
||||
<script lang="ts" setup>
|
||||
import { ref } from 'vue';
|
||||
import i18n from '@/lang';
|
||||
import { MsgInfo, MsgSuccess } from '@/utils/message';
|
||||
import FileList from '@/components/file-list/index.vue';
|
||||
import { changeQuick, loadQuickOption } from '@/api/modules/dashboard';
|
||||
|
||||
const emit = defineEmits<{ (e: 'search'): void }>();
|
||||
@@ -36,6 +57,7 @@ const acceptParams = (): void => {
|
||||
drawerVisible.value = true;
|
||||
};
|
||||
const quickOptions = ref([]);
|
||||
const fileRef = ref();
|
||||
|
||||
const search = async () => {
|
||||
loading.value = true;
|
||||
@@ -64,17 +86,26 @@ const onChangeShow = async () => {
|
||||
return;
|
||||
}
|
||||
loading.value = true;
|
||||
await changeQuick(quickOptions.value.map(({ id, isShow }) => ({ id, isShow })))
|
||||
await changeQuick(quickOptions.value)
|
||||
.then(() => {
|
||||
loading.value = false;
|
||||
MsgSuccess(i18n.global.t('commons.msg.operationSuccess'));
|
||||
drawerVisible.value = false;
|
||||
search();
|
||||
})
|
||||
.catch(() => {
|
||||
loading.value = false;
|
||||
});
|
||||
};
|
||||
|
||||
const loadDir = async (path: string) => {
|
||||
for (const item of quickOptions.value) {
|
||||
if (item.name === 'File') {
|
||||
item.detail = path;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
const handleClose = () => {
|
||||
drawerVisible.value = false;
|
||||
emit('search');
|
||||
|
||||
@@ -119,8 +119,9 @@
|
||||
</template>
|
||||
|
||||
<script setup lang="ts">
|
||||
import { computed, nextTick, reactive, ref } from 'vue';
|
||||
import { UploadFile, UploadFiles, UploadInstance, UploadProps, UploadRawFile } from 'element-plus';
|
||||
import { computed, onMounted, onBeforeUnmount, ref } from 'vue';
|
||||
import { UploadFile, UploadFiles, UploadInstance, UploadProps, UploadRawFile, genFileId } from 'element-plus';
|
||||
import { uploadContainerFile } from '@/api/modules/container';
|
||||
import { batchCheckFiles, stopChunkUpload, uploadFileData } from '@/api/modules/files';
|
||||
import { CHUNK_SIZE, uploadChunkWithRetry } from '@/utils/chunk-upload';
|
||||
import i18n from '@/lang';
|
||||
@@ -134,6 +135,7 @@ import { useGlobalStore } from '@/composables/useGlobalStore';
|
||||
interface UploadFileProps {
|
||||
path: string;
|
||||
isAppendOnly?: boolean;
|
||||
containerID?: string;
|
||||
}
|
||||
|
||||
const uploadRef = ref<UploadInstance>();
|
||||
@@ -154,6 +156,8 @@ const uploadOverallPercent = computed(() => {
|
||||
const open = ref(false);
|
||||
const isAppendOnly = ref(false);
|
||||
const path = ref();
|
||||
const containerID = ref('');
|
||||
const targetNode = ref('');
|
||||
let uploadHelper = ref('');
|
||||
const dialogExistFileRef = ref();
|
||||
const abortController = ref<AbortController | null>(null);
|
||||
@@ -213,9 +217,6 @@ const closePage = () => {
|
||||
clearFiles();
|
||||
em('close', false);
|
||||
};
|
||||
const state = reactive({
|
||||
uploadEle: null,
|
||||
});
|
||||
const uploaderFiles = ref<UploadFiles>([]);
|
||||
const hoverIndex = ref<number | null>(null);
|
||||
const tmpFiles = ref<UploadFiles>([]);
|
||||
@@ -225,9 +226,11 @@ const upload = (command: string) => {
|
||||
if (uploadLocked.value) {
|
||||
return;
|
||||
}
|
||||
state.uploadEle.webkitdirectory = command == 'dir';
|
||||
uploadRef.value.$el.querySelector('input').value = '';
|
||||
uploadRef.value.$el.querySelector('input').click();
|
||||
const input = uploadRef.value?.$el.querySelector('input[type=file]') as HTMLInputElement | null;
|
||||
if (!input) return;
|
||||
input.webkitdirectory = command === 'dir';
|
||||
input.value = '';
|
||||
input.click();
|
||||
};
|
||||
|
||||
const removeFile = (index: number) => {
|
||||
@@ -267,7 +270,7 @@ const handleDrop = async (event: DragEvent) => {
|
||||
};
|
||||
|
||||
const convertFileToUploadFile = (file: File, path: string): UploadFile => {
|
||||
const uid = Date.now();
|
||||
const uid = genFileId();
|
||||
|
||||
const uploadRawFile: UploadRawFile = new File([file], file.name, {
|
||||
type: file.type,
|
||||
@@ -294,12 +297,15 @@ const traverseFileTree = async (item: any, path = '') => {
|
||||
return;
|
||||
}
|
||||
if (item.isFile) {
|
||||
if (tmpFiles.value.length > 1000) {
|
||||
if (uploaderFiles.value.length + tmpFiles.value.length >= 1000) {
|
||||
breakFlag.value = true;
|
||||
return;
|
||||
}
|
||||
await new Promise<void>((resolve) => {
|
||||
item.file((file: File) => {
|
||||
if (uploaderFiles.value.length + tmpFiles.value.length >= 1000) {
|
||||
breakFlag.value = true;
|
||||
}
|
||||
if (!breakFlag.value) {
|
||||
tmpFiles.value.push(convertFileToUploadFile(file, path));
|
||||
}
|
||||
@@ -365,7 +371,14 @@ const handleSuccess: UploadProps['onSuccess'] = (res, file) => {
|
||||
};
|
||||
|
||||
const submit = async () => {
|
||||
const files = uploaderFiles.value.slice();
|
||||
if (uploadLocked.value) return;
|
||||
const files = containerID.value
|
||||
? uploaderFiles.value.filter((file) => file.status !== 'success')
|
||||
: uploaderFiles.value.slice();
|
||||
if (containerID.value) {
|
||||
await uploadFile(files, false);
|
||||
return;
|
||||
}
|
||||
const fileNamesWithPath = Array.from(
|
||||
new Set(files.map((file) => `${path.value}/${file.raw.webkitRelativePath || file.name}`)),
|
||||
);
|
||||
@@ -410,7 +423,8 @@ const uploadFile = async (files: any[], overwrite: boolean) => {
|
||||
upLoading.value = true;
|
||||
uploadTotalCount.value = files.length;
|
||||
const controller = new AbortController();
|
||||
const uploadNode = currentNode.value;
|
||||
const uploadNode = containerID.value ? targetNode.value : currentNode.value;
|
||||
const uploadContainerID = containerID.value;
|
||||
abortController.value = controller;
|
||||
let successCount = 0;
|
||||
try {
|
||||
@@ -426,10 +440,27 @@ const uploadFile = async (files: any[], overwrite: boolean) => {
|
||||
|
||||
let isSuccess = false;
|
||||
try {
|
||||
isSuccess =
|
||||
Number(file.size) === 0
|
||||
? await uploadEmptyFile(file, controller, uploadNode, overwrite)
|
||||
: await uploadChunkedFile(file, controller, uploadNode, overwrite);
|
||||
if (uploadContainerID) {
|
||||
const data = new FormData();
|
||||
data.append('containerID', uploadContainerID);
|
||||
data.append('path', getUploadPath(file));
|
||||
data.append('file', file.raw, getFilenameFromPath(file.raw.webkitRelativePath || file.name));
|
||||
await uploadContainerFile(data, {
|
||||
headers: { CurrentNode: uploadNode },
|
||||
signal: controller.signal,
|
||||
skipErrorMessage: true,
|
||||
timeout: 0,
|
||||
onUploadProgress: ({ loaded, total }) => {
|
||||
uploadPercent.value = total ? Math.round((loaded / total) * 100) : 0;
|
||||
},
|
||||
});
|
||||
isSuccess = true;
|
||||
} else {
|
||||
isSuccess =
|
||||
Number(file.size) === 0
|
||||
? await uploadEmptyFile(file, controller, uploadNode, overwrite)
|
||||
: await uploadChunkedFile(file, controller, uploadNode, overwrite);
|
||||
}
|
||||
} catch (error) {
|
||||
if (!controller.signal.aborted) {
|
||||
MsgError(getErrorMessage(error));
|
||||
@@ -573,6 +604,9 @@ const getFilenameFromPath = (path: string) => {
|
||||
};
|
||||
|
||||
const acceptParams = (props: UploadFileProps) => {
|
||||
if (uploadLocked.value) return;
|
||||
containerID.value = props.containerID || '';
|
||||
targetNode.value = currentNode.value;
|
||||
path.value = props.path;
|
||||
isAppendOnly.value = Boolean(props.isAppendOnly);
|
||||
open.value = true;
|
||||
@@ -581,22 +615,19 @@ const acceptParams = (props: UploadFileProps) => {
|
||||
uploadCurrentIndex.value = 0;
|
||||
uploadHelper.value = '';
|
||||
canceling.value = false;
|
||||
|
||||
nextTick(() => {
|
||||
state.uploadEle = document.querySelector('.el-upload__input');
|
||||
});
|
||||
};
|
||||
|
||||
onMounted(() => {
|
||||
window.addEventListener('beforeunload', (e) => {
|
||||
if (upLoading.value) {
|
||||
if (upLoading.value) {
|
||||
e.preventDefault();
|
||||
e.returnValue = i18n.global.t('file.cancelUploadHelper');
|
||||
return i18n.global.t('file.cancelUploadHelper');
|
||||
}
|
||||
}
|
||||
});
|
||||
const beforeUnload = (event: BeforeUnloadEvent) => {
|
||||
if (upLoading.value) {
|
||||
event.preventDefault();
|
||||
event.returnValue = i18n.global.t('file.cancelUploadHelper');
|
||||
}
|
||||
};
|
||||
onMounted(() => window.addEventListener('beforeunload', beforeUnload));
|
||||
onBeforeUnmount(() => {
|
||||
window.removeEventListener('beforeunload', beforeUnload);
|
||||
abortController.value?.abort();
|
||||
void cleanupActiveChunkUpload();
|
||||
});
|
||||
|
||||
defineExpose({ acceptParams, handleDrop, open });
|
||||
|
||||
Reference in New Issue
Block a user