未验证 提交 97f84ae0 编写于 作者: D Devosend 提交者: GitHub

[Feature][UI Next] Add file manage (#7943)

* add file
* fix form style
* fix menu key not init bug
* add license header
* fix review question
* delete jquery
* modify conditions place
* use pagination components instead of pagination attr of tables.
* fixed table column width
* delete card component title required attr
* add table style
上级 651f4ad2
......@@ -15,6 +15,7 @@
"date-fns": "^2.28.0",
"echarts": "^5.2.2",
"lodash": "^4.17.21",
"monaco-editor": "^0.31.1",
"naive-ui": "2.23.2",
"nprogress": "^0.2.0",
"pinia": "^2.0.9",
......
......@@ -15,20 +15,43 @@
* limitations under the License.
*/
import { defineComponent, computed } from 'vue'
import { NConfigProvider, darkTheme, GlobalThemeOverrides } from 'naive-ui'
import { defineComponent, computed, ref, nextTick, provide } from 'vue'
import {
zhCN,
enUS,
NConfigProvider,
darkTheme,
GlobalThemeOverrides,
NMessageProvider,
} from 'naive-ui'
import { useThemeStore } from '@/store/theme/theme'
import { useLocalesStore } from '@/store/locales/locales'
import themeList from '@/themes'
const App = defineComponent({
name: 'App',
setup() {
const isRouterAlive = ref(true)
const themeStore = useThemeStore()
const currentTheme = computed(() =>
themeStore.darkTheme ? darkTheme : undefined
themeStore.darkTheme ? darkTheme : undefined,
)
const localesStore = useLocalesStore()
/*refresh page when router params change*/
const reload = () => {
isRouterAlive.value = false
nextTick(() => {
isRouterAlive.value = true
})
}
provide('reload', reload)
return {
reload,
isRouterAlive,
currentTheme,
localesStore,
}
},
render() {
......@@ -40,8 +63,11 @@ const App = defineComponent({
theme={this.currentTheme}
themeOverrides={themeOverrides}
style={{ width: '100%', height: '100vh' }}
locale={String(this.localesStore.getLocales) === 'zh_CN' ? zhCN : enUS}
>
<router-view />
<NMessageProvider>
{this.isRouterAlive ? <router-view /> : ''}
</NMessageProvider>
</NConfigProvider>
)
},
......
......@@ -29,7 +29,6 @@ const contentStyle = {
const props = {
title: {
type: String as PropType<string>,
required: true,
},
}
......
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.conditions-model {
display: flex;
justify-content: space-between;
align-items: center;
margin: 10px 0;
.right {
> .form-box {
.list {
float: right;
margin: 3px 0 3px 4px;
}
}
}
}
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import _ from 'lodash'
import { useI18n } from 'vue-i18n'
import { defineComponent, ref } from 'vue'
import { SearchOutlined } from '@vicons/antd'
import { NButton, NIcon, NInput, NSpace } from 'naive-ui'
import Card from '@/components/card'
import styles from './index.module.scss'
const Conditions = defineComponent({
name: 'Conditions',
emits: ['conditions'],
setup(props, ctx) {
const searchVal = ref()
const handleConditions = () => {
ctx.emit('conditions', _.trim(searchVal.value))
}
return { searchVal, handleConditions }
},
render() {
const { t } = useI18n()
const { $slots, handleConditions } = this
return (
<Card style={{ marginBottom: '5px' }}>
<div class={styles['conditions-model']}>
<NSpace>{$slots}</NSpace>
<div class={styles.right}>
<div class={styles['form-box']}>
<div class={styles.list}>
<NButton onClick={handleConditions}>
<NIcon>
<SearchOutlined />
</NIcon>
</NButton>
</div>
<div class={styles.list}>
<NInput
placeholder={t('resource.file.enter_keyword_tips')}
v-model={[this.searchVal, 'value']}
/>
</div>
</div>
</div>
</div>
</Card>
)
},
})
export default Conditions
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import {
defineComponent,
onMounted,
onUnmounted,
PropType,
nextTick,
ref,
watch,
} from 'vue'
import * as monaco from 'monaco-editor'
import editorWorker from 'monaco-editor/esm/vs/editor/editor.worker?worker'
import jsonWorker from 'monaco-editor/esm/vs/language/json/json.worker?worker'
import cssWorker from 'monaco-editor/esm/vs/language/css/css.worker?worker'
import htmlWorker from 'monaco-editor/esm/vs/language/html/html.worker?worker'
import tsWorker from 'monaco-editor/esm/vs/language/typescript/ts.worker?worker'
const props = {
modelValue: {
type: String as PropType<string>,
default: '',
},
language: {
type: String as PropType<string>,
default: 'shell',
},
readOnly: {
type: Boolean as PropType<boolean>,
default: false,
},
options: {
type: Object,
default: () => {},
},
}
// @ts-ignore
window.MonacoEnvironment = {
getWorker(_: any, label: string) {
if (label === 'json') {
return new jsonWorker()
}
if (['css', 'scss', 'less'].includes(label)) {
return new cssWorker()
}
if (['html', 'handlebars', 'razor'].includes(label)) {
return new htmlWorker()
}
if (['typescript', 'javascript'].includes(label)) {
return new tsWorker()
}
return new editorWorker()
},
}
export default defineComponent({
name: 'MonacoEditor',
props,
setup(props) {
let editor = null as monaco.editor.IStandaloneCodeEditor | null
const content = ref()
const getValue = () => editor?.getValue()
watch(
() => props.modelValue,
(val) => {
if (val !== getValue()) {
editor?.setValue(val)
}
},
)
onMounted(async () => {
content.value = props.modelValue
await nextTick()
const dom = document.getElementById('monaco-container')
if (dom) {
editor = monaco.editor.create(dom, props.options, {
value: props.modelValue,
language: props.language,
readOnly: props.readOnly,
automaticLayout: true,
})
}
})
onUnmounted(() => {
editor?.dispose()
})
return { getValue }
},
render() {
return (
<div
id='monaco-container'
style={{
height: '300px',
border: '1px solid #eee',
}}
></div>
)
},
})
......@@ -15,13 +15,23 @@
* limitations under the License.
*/
import { DefineComponent } from 'vue'
// import * as $ from 'jquery'
declare module '*.vue' {
import { DefineComponent } from 'vue'
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/ban-types
const component: DefineComponent<{}, {}, any>
export default component
}
declare global {
interface Window {
$message: any
}
}
declare namespace jquery {}
declare module '*.png'
declare module '*.jpg'
declare module '*.jpeg'
......@@ -16,7 +16,7 @@
*/
import { defineComponent, onMounted, watch, toRefs, ref } from 'vue'
import { NLayout, NLayoutContent, NLayoutHeader } from 'naive-ui'
import { NLayout, NLayoutContent, NLayoutHeader, useMessage } from 'naive-ui'
import NavBar from './components/navbar'
import SideBar from './components/sidebar'
import { useDataList } from './use-dataList'
......@@ -27,6 +27,8 @@ import { useI18n } from 'vue-i18n'
const Content = defineComponent({
name: 'Content',
setup() {
window.$message = useMessage()
const menuStore = useMenuStore()
const { locale } = useI18n()
const localesStore = useLocalesStore()
......
......@@ -122,7 +122,7 @@ export function useDataList() {
},
{
label: t('menu.resources'),
key: 'resources',
key: 'resource',
icon: renderIcon(FolderOutlined),
isShowSide: true,
children: [
......@@ -153,6 +153,7 @@ export function useDataList() {
key: 'datasource',
icon: renderIcon(DatabaseOutlined),
isShowSide: false,
children: [],
},
{
label: t('menu.monitor'),
......
......@@ -151,6 +151,44 @@ const monitor = {
},
}
const resource = {
file: {
file_manage: 'File Manage',
create_folder: 'Create Folder',
create_file: 'Create File',
upload_files: 'Upload Files',
enter_keyword_tips: 'Please enter keyword',
id: '#',
name: 'Name',
user_name: 'Resource userName',
whether_directory: 'Whether directory',
file_name: 'File Name',
description: 'Description',
size: 'Size',
update_time: 'Update Time',
operation: 'Operation',
edit: 'Edit',
rename: 'Rename',
download: 'Download',
delete: 'Delete',
yes: 'Yes',
no: 'No',
folder_name: 'Folder Name',
enter_name_tips: 'Please enter name',
enter_description_tips: 'Please enter description',
enter_content_tips: 'Please enter the resource content',
file_format: 'File Format',
file_content: 'File Content',
delete_confirm: 'Delete?',
confirm: 'Confirm',
cancel: 'Cancel',
success: 'Success',
file_details: 'File Details',
return: 'Return',
save: 'Save',
}
}
const project = {
list: {
create_project: 'Create Project',
......@@ -185,5 +223,6 @@ export default {
password,
profile,
monitor,
resource,
project,
}
......@@ -149,6 +149,45 @@ const monitor = {
},
}
const resource = {
file: {
file_manage: '文件管理',
create_folder: '创建文件夹',
create_file: '创建文件',
upload_files: '上传文件',
enter_keyword_tips: '请输入关键词',
id: '编号',
name: '名称',
user_name: '所属用户',
whether_directory: '是否文件夹',
file_name: '文件名称',
description: '描述',
size: '大小',
update_time: '更新时间',
operation: '操作',
edit: '编辑',
rename: '重命名',
download: '下载',
delete: '删除',
yes: '',
no: '',
folder_name: '文件夹名称',
enter_name_tips: '请输入名称',
enter_description_tips: '请输入描述',
enter_content_tips: '请输入资源内容',
enter_suffix_tips: '请输入文件后缀',
file_format: '文件格式',
file_content: '文件内容',
delete_confirm: '确定删除吗?',
confirm: '确定',
cancel: '取消',
success: '成功',
file_details: '文件详情',
return: '返回',
save: '保存',
}
}
const project = {
list: {
create_project: '创建项目',
......@@ -183,5 +222,6 @@ export default {
password,
profile,
monitor,
resource,
project,
}
......@@ -32,7 +32,7 @@ export default {
{
path: '/resource/file',
name: 'file',
component: components['home'],
component: components['file'],
meta: {
title: '文件管理',
},
......@@ -40,9 +40,41 @@ export default {
{
path: '/resource/file/create',
name: 'resource-file-create',
component: components['home'],
component: components['resource-file-create'],
meta: {
title: '创建资源',
title: '文件创建',
},
},
{
path: '/resource/file/edit/:id',
name: 'resource-file-edit',
component: components['resource-file-edit'],
meta: {
title: '文件编辑',
},
},
{
path: '/resource/file/subdirectory/:id',
name: 'resource-file-subdirectory',
component: components['file'],
meta: {
title: '文件管理',
},
},
{
path: '/resource/file/list/:id',
name: 'resource-file-list',
component: components['resource-file-edit'],
meta: {
title: '文件详情',
},
},
{
path: '/resource/file/create/:id',
name: 'resource-subfile-create',
component: components['resource-file-create'],
meta: {
title: '文件创建',
},
},
],
......
......@@ -15,7 +15,7 @@
* limitations under the License.
*/
import { axios } from '@/service/service'
import { axios, downloadFile } from '@/service/service'
import {
FileReq,
ResourceTypeReq,
......@@ -37,7 +37,7 @@ import {
} from './types'
export function queryResourceListPaging(
params: ListReq & IdReq & ResourceTypeReq
params: ListReq & IdReq & ResourceTypeReq,
): any {
return axios({
url: '/resources',
......@@ -47,7 +47,7 @@ export function queryResourceListPaging(
}
export function createResource(
data: CreateReq & FileNameReq & NameReq & ResourceTypeReq
data: CreateReq & FileNameReq & NameReq & ResourceTypeReq,
): any {
return axios({
url: '/resources',
......@@ -81,7 +81,7 @@ export function authUDFFunc(params: UserIdReq): any {
}
export function createDirectory(
data: CreateReq & NameReq & ResourceTypeReq
data: CreateReq & NameReq & ResourceTypeReq,
): any {
return axios({
url: '/resources/directory',
......@@ -99,7 +99,7 @@ export function queryResourceList(params: ResourceTypeReq): any {
}
export function onlineCreateResource(
data: OnlineCreateReq & FileNameReq & ResourceTypeReq
data: OnlineCreateReq & FileNameReq & ResourceTypeReq,
): any {
return axios({
url: '/resources/online-create',
......@@ -109,7 +109,7 @@ export function onlineCreateResource(
}
export function queryResourceByProgramType(
params: ResourceTypeReq & ProgramTypeReq
params: ResourceTypeReq & ProgramTypeReq,
): any {
return axios({
url: '/resources/query-by-type',
......@@ -151,7 +151,7 @@ export function deleteUdfFunc(id: IdReq): any {
export function unAuthUDFFunc(params: UserIdReq): any {
return axios({
url: `/resources/unauth-udf-func`,
url: '/resources/unauth-udf-func',
method: 'get',
params,
})
......@@ -167,7 +167,7 @@ export function verifyResourceName(params: FullNameReq & ResourceTypeReq): any {
export function queryResource(
params: FullNameReq & ResourceTypeReq,
id: IdReq
id: IdReq,
): any {
return axios({
url: `/resources/verify-name/${id}`,
......@@ -177,8 +177,8 @@ export function queryResource(
}
export function updateResource(
data: NameReq & ResourceTypeReq,
id: IdReq
data: NameReq & ResourceTypeReq & IdReq & DescriptionReq,
id: number,
): any {
return axios({
url: `/resources/${id}`,
......@@ -187,18 +187,15 @@ export function updateResource(
})
}
export function deleteResource(id: IdReq): any {
export function deleteResource(id: number): any {
return axios({
url: `/resources/${id}`,
method: 'delete',
})
}
export function downloadResource(id: IdReq): any {
return axios({
url: `/resources/${id}/download`,
method: 'get',
})
export function downloadResource(id: number): void {
downloadFile(`resources/${id}/download`)
}
export function viewUIUdfFunction(id: IdReq): any {
......@@ -208,7 +205,7 @@ export function viewUIUdfFunction(id: IdReq): any {
})
}
export function updateResourceContent(data: ContentReq, id: IdReq): any {
export function updateResourceContent(data: ContentReq, id: number): any {
return axios({
url: `/resources/${id}/update-content`,
method: 'put',
......@@ -216,7 +213,7 @@ export function updateResourceContent(data: ContentReq, id: IdReq): any {
})
}
export function viewResource(params: ViewResourceReq, id: IdReq): any {
export function viewResource(params: ViewResourceReq, id: number): any {
return axios({
url: `/resources/${id}/view`,
method: 'get',
......@@ -226,7 +223,7 @@ export function viewResource(params: ViewResourceReq, id: IdReq): any {
export function createUdfFunc(
data: UdfFuncReq,
resourceId: ResourceIdReq
resourceId: ResourceIdReq,
): any {
return axios({
url: `/resources/${resourceId}/udf-func`,
......@@ -238,7 +235,7 @@ export function createUdfFunc(
export function updateUdfFunc(
data: UdfFuncReq,
resourceId: ResourceIdReq,
id: IdReq
id: IdReq,
): any {
return axios({
url: `/resources/${resourceId}/udf-func/${id}`,
......
......@@ -90,6 +90,28 @@ interface UdfFuncReq extends UdfTypeReq, DescriptionReq {
database?: string
}
interface ResourceFile {
id: number
pid: number
alias: string
userId: number
type: string
directory: boolean
fileName: string
fullName: string
description: string
size: number
updateTime: string
}
interface ResourceListRes {
currentPage: number
pageSize: number
start: number
total: number
totalList: ResourceFile[]
}
export {
FileReq,
ResourceTypeReq,
......@@ -108,4 +130,5 @@ export {
ViewResourceReq,
ResourceIdReq,
UdfFuncReq,
ResourceListRes,
}
......@@ -22,6 +22,7 @@ import axios, {
AxiosRequestHeaders,
} from 'axios'
import qs from 'qs'
import _ from 'lodash'
import { useUserStore } from '@/store/user/user'
const userStore = useUserStore()
......@@ -30,7 +31,12 @@ const baseRequestConfig: AxiosRequestConfig = {
baseURL: import.meta.env.VITE_APP_WEB_URL + '/dolphinscheduler',
timeout: 10000,
transformRequest: (params) => {
return qs.stringify(params, { arrayFormat: 'repeat' })
if (_.isPlainObject(params)) {
console.log(params)
return qs.stringify(params, { arrayFormat: 'repeat' })
} else {
return params
}
},
paramsSerializer: (params) => {
return qs.stringify(params, { arrayFormat: 'repeat' })
......@@ -66,4 +72,46 @@ service.interceptors.response.use((res: AxiosResponse) => {
}
}, err)
export { service as axios }
const apiPrefix = '/dolphinscheduler'
const reSlashPrefix = /^\/+/
const resolveURL = (url: string) => {
if (url.indexOf('http') === 0) {
return url
}
if (url.charAt(0) !== '/') {
return `${apiPrefix}/${url.replace(reSlashPrefix, '')}`
}
return url
}
/**
* download file
*/
const downloadFile = (url: string, obj?: any) => {
const param: any = {
url: resolveURL(url),
obj: obj || {},
}
const form = document.createElement('form')
form.action = param.url
form.method = 'get'
form.style.display = 'none'
Object.keys(param.obj).forEach((key) => {
const input = document.createElement('input')
input.type = 'hidden'
input.name = key
input.value = param.obj[key]
form.appendChild(input)
})
const button = document.createElement('input')
button.type = 'submit'
form.appendChild(button)
document.body.appendChild(form)
form.submit()
document.body.removeChild(form)
}
export { service as axios, downloadFile }
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { defineStore } from 'pinia'
import type { FileState } from '@/store/file/types'
export const useFileStore = defineStore({
id: 'file',
state: (): FileState => ({
file: '',
currentDir: '/',
}),
persist: true,
getters: {
getFileInfo(): string {
return this.file
},
getCurrentDir(): string {
return this.currentDir
},
},
actions: {
setFileInfo(file: string): void {
this.file = file
},
setCurrentDir(currentDir: string): void {
this.currentDir = currentDir
},
},
})
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
interface FileState {
file: string
currentDir: string
}
export { FileState }
......@@ -26,7 +26,7 @@ export const useMenuStore = defineStore({
persist: true,
getters: {
getMenuKey(): string {
return this.menuKey
return this.menuKey || 'home'
},
},
actions: {
......
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Intelligent display kb m
*/
export const bytesToSize = (bytes: number) => {
if (bytes === 0) return '0 B'
const k = 1024 // or 1024
const sizes = ['B', 'KB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
const i = Math.floor(Math.log(bytes) / Math.log(k))
return parseInt((bytes / Math.pow(k, i)).toPrecision(3)) + ' ' + sizes[i]
}
export const fileTypeArr = [
'txt',
'log',
'sh',
'bat',
'conf',
'cfg',
'py',
'java',
'sql',
'xml',
'hql',
'properties',
'json',
'yml',
'yaml',
'ini',
'js',
]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { defineComponent, ref, toRefs } from 'vue'
import { useRouter } from 'vue-router'
import { NForm, NFormItem, NInput, NSelect, NButton } from 'naive-ui'
import { useI18n } from 'vue-i18n'
import Card from '@/components/card'
import MonacoEditor from '@/components/monaco-editor'
import { useCreate } from './use-create'
import { useForm } from './use-form'
import { fileTypeArr } from '@/utils/common'
import styles from '../index.module.scss'
import type { Router } from 'vue-router'
export default defineComponent({
name: 'ResourceFileCreate',
setup() {
const router: Router = useRouter()
const codeEditorRef = ref()
const { state } = useForm()
const { handleCreateFile } = useCreate(state)
const fileSuffixOptions = fileTypeArr.map((suffix) => ({
key: suffix,
label: suffix,
value: suffix,
}))
const handleFile = () => {
state.fileForm.content = codeEditorRef.value?.getValue()
handleCreateFile()
}
const handleReturn = () => {
const { id } = router.currentRoute.value.params
const name = id ? 'resource-file-subdirectory' : 'file'
router.push({ name, params: { id } })
}
return {
codeEditorRef,
fileSuffixOptions,
handleFile,
handleReturn,
...toRefs(state),
}
},
render() {
const { t } = useI18n()
return (
<Card title={t('resource.file.file_details')}>
<NForm
rules={this.rules}
ref='fileFormRef'
label-placement='left'
label-width='160'
class={styles['form-content']}
>
<NFormItem label={t('resource.file.file_name')} path='fileName'>
<NInput
v-model={[this.fileForm.fileName, 'value']}
placeholder={t('resource.file.enter_name_tips')}
style={{ width: '300px' }}
/>
</NFormItem>
<NFormItem label={t('resource.file.file_format')} path='suffix'>
<NSelect
defaultValue={[this.fileForm.suffix]}
v-model={[this.fileForm.suffix, 'value']}
options={this.fileSuffixOptions}
style={{ width: '100px' }}
/>
</NFormItem>
<NFormItem label={t('resource.file.description')} path='description'>
<NInput
type='textarea'
v-model={[this.fileForm.description, 'value']}
placeholder={t('resource.file.enter_description_tips')}
style={{ width: '430px' }}
/>
</NFormItem>
<NFormItem label={t('resource.file.file_content')} path='content'>
<div
class={styles.cont}
style={{
width: '90%',
}}
>
<MonacoEditor ref='codeEditorRef' />
</div>
</NFormItem>
<div class={styles['file-edit-content']}>
<div class={styles.submit}>
<NButton type='info' size='small' round onClick={this.handleFile}>
{t('resource.file.save')}
</NButton>
<NButton
type='info'
size='small'
text
style={{ marginLeft: '15px' }}
onClick={this.handleReturn}
>
{t('resource.file.return')}
</NButton>
</div>
</div>
</NForm>
</Card>
)
},
})
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router'
import type { Router } from 'vue-router'
import { useFileStore } from '@/store/file/file'
import { onlineCreateResource } from '@/service/modules/resources'
export function useCreate(state: any) {
const { t } = useI18n()
const router: Router = useRouter()
const fileStore = useFileStore()
const handleCreateFile = () => {
const pid = router.currentRoute.value.params.id || -1
const currentDir = fileStore.getCurrentDir || '/'
state.fileFormRef.validate(async (valid: any) => {
if (!valid) {
try {
await onlineCreateResource({
...state.fileForm,
...{ pid, currentDir },
})
window.$message.success(t('resource.file.success'))
const name = pid ? 'resource-file-subdirectory' : 'file'
router.push({ name, params: { id: pid } })
} catch (error: any) {
window.$message.error(error.message)
}
}
})
}
return {
handleCreateFile,
}
}
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { useI18n } from 'vue-i18n'
import { reactive, ref, unref } from 'vue'
import type { FormRules } from 'naive-ui'
const defaultValue = () => ({
pid: -1,
type: 'FILE',
suffix: 'sh',
fileName: '',
description: '',
content: '',
currentDir: '/',
})
export function useForm() {
const { t } = useI18n()
const resetForm = () => {
state.fileForm = Object.assign(unref(state.fileForm), defaultValue())
}
const state = reactive({
fileFormRef: ref(),
fileForm: defaultValue(),
rules: {
fileName: {
required: true,
trigger: ['input', 'blur'],
validator() {
if (state.fileForm.fileName === '') {
return new Error(t('resource.file.enter_name_tips'))
}
},
},
suffix: {
required: true,
trigger: ['input', 'blur'],
validator() {
if (state.fileForm.suffix === '') {
return new Error(t('resource.file.enter_suffix_tips'))
}
},
},
content: {
required: true,
trigger: ['input', 'blur'],
validator() {
if (state.fileForm.content === '') {
return new Error(t('resource.file.enter_content_tips'))
}
},
},
} as FormRules,
})
return {
state,
resetForm,
}
}
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { useRouter } from 'vue-router'
import { defineComponent, onMounted, ref, toRefs } from 'vue'
import { NButton, NForm, NFormItem, NSpace } from 'naive-ui'
import { useI18n } from 'vue-i18n'
import Card from '@/components/card'
import MonacoEditor from '@/components/monaco-editor'
import { useForm } from './use-form'
import { useEdit } from './use-edit'
import styles from '../index.module.scss'
import type { Router } from 'vue-router'
export default defineComponent({
name: 'ResourceFileEdit',
setup() {
const router: Router = useRouter()
const resourceViewRef = ref()
const codeEditorRef = ref()
const routeNameRef = ref(router.currentRoute.value.name)
const idRef = ref(Number(router.currentRoute.value.params.id))
const { state } = useForm()
const { getResourceView, handleUpdateContent } = useEdit(state)
const handleFileContent = () => {
state.fileForm.content = codeEditorRef.value?.getValue()
handleUpdateContent(idRef.value)
}
const handleReturn = () => {
router.go(-1)
}
onMounted(() => {
resourceViewRef.value = getResourceView(idRef.value)
})
return {
idRef,
routeNameRef,
codeEditorRef,
resourceViewRef,
handleReturn,
handleFileContent,
...toRefs(state),
}
},
render() {
const { t } = useI18n()
return (
<Card title={t('resource.file.file_details')}>
<div class={styles['file-edit-content']}>
<h2>
<span>{this.resourceViewRef?.value.alias}</span>
</h2>
<NForm
rules={this.rules}
ref='fileFormRef'
class={styles['form-content']}
>
<NFormItem path='content'>
<div
class={styles.cont}
style={{
width: '90%',
}}
>
<MonacoEditor
ref='codeEditorRef'
modelValue={this.resourceViewRef?.value.content}
/>
</div>
</NFormItem>
{this.routeNameRef === 'resource-file-edit' && (
<NSpace>
<NButton
type='info'
size='small'
text
style={{ marginRight: '15px' }}
onClick={this.handleReturn}
>
{t('resource.file.return')}
</NButton>
<NButton
type='info'
size='small'
round
onClick={() => this.handleFileContent()}
>
{t('resource.file.save')}
</NButton>
</NSpace>
)}
</NForm>
</div>
</Card>
)
},
})
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router'
import type { Router } from 'vue-router'
import { useAsyncState } from '@vueuse/core'
import {
viewResource,
updateResourceContent,
} from '@/service/modules/resources'
export function useEdit(state: any) {
const { t } = useI18n()
const router: Router = useRouter()
const getResourceView = (id: number) => {
const params = {
skipLineNum: 0,
limit: 3000,
}
const { state } = useAsyncState(viewResource(params, id), {})
return state
}
const handleUpdateContent = (id: number) => {
state.fileFormRef.validate(async (valid: any) => {
if (!valid) {
try {
await updateResourceContent(
{
...state.fileForm,
},
id,
)
window.$message.success(t('resource.file.success'))
router.go(-1)
} catch (error: any) {
window.$message.error(error.message)
}
}
})
}
return {
getResourceView,
handleUpdateContent,
}
}
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { reactive, ref, unref } from 'vue'
import { useI18n } from 'vue-i18n'
import type { FormRules } from 'naive-ui'
const defaultValue = () => ({
content: '',
})
export function useForm() {
const { t } = useI18n()
const resetForm = () => {
state.fileForm = Object.assign(unref(state.fileForm), defaultValue())
}
const state = reactive({
fileFormRef: ref(),
fileForm: defaultValue(),
rules: {
content: {
required: true,
trigger: ['input', 'blur'],
validator() {
if (state.fileForm.content === '') {
return new Error(t('resource.file.enter_content_tips'))
}
},
},
} as FormRules,
})
return {
state,
resetForm,
}
}
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { defineComponent, toRefs, PropType } from 'vue'
import { NForm, NFormItem, NInput } from 'naive-ui'
import { useI18n } from 'vue-i18n'
import Modal from '@/components/modal'
import { useForm } from './use-form'
import { useFolder } from './use-folder'
const props = {
show: {
type: Boolean as PropType<boolean>,
default: false,
},
}
export default defineComponent({
name: 'ResourceFileFolder',
props,
emits: ['updateList', 'update:show'],
setup(props, ctx) {
const { state, resetForm } = useForm()
const { handleCreateFolder } = useFolder(state)
const hideModal = () => {
ctx.emit('update:show')
}
const handleFolder = () => {
handleCreateFolder(ctx.emit, hideModal, resetForm)
}
return {
hideModal,
handleFolder,
...toRefs(state),
}
},
render() {
const { t } = useI18n()
return (
<Modal
show={this.$props.show}
title={t('resource.file.create_folder')}
onCancel={this.hideModal}
onConfirm={this.handleFolder}
>
<NForm
rules={this.rules}
ref='folderFormRef'
label-placement='left'
label-width='160'
>
<NFormItem label={t('resource.file.folder_name')} path='name'>
<NInput
v-model={[this.folderForm.name, 'value']}
placeholder={t('resource.file.enter_name_tips')}
/>
</NFormItem>
<NFormItem label={t('resource.file.description')} path='description'>
<NInput
type='textarea'
v-model={[this.folderForm.description, 'value']}
placeholder={t('resource.file.enter_description_tips')}
/>
</NFormItem>
</NForm>
</Modal>
)
},
})
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { useI18n } from 'vue-i18n'
import { IEmit } from '../types'
import { useRouter } from 'vue-router'
import type { Router } from 'vue-router'
import { useFileStore } from '@/store/file/file'
import { createDirectory } from '@/service/modules/resources'
export function useFolder(state: any) {
const { t } = useI18n()
const router: Router = useRouter()
const fileStore = useFileStore()
const handleCreateFolder = (
emit: IEmit,
hideModal: () => void,
resetForm: () => void,
) => {
const pid = router.currentRoute.value.params.id || -1
const currentDir = fileStore.getCurrentDir || '/'
state.folderFormRef.validate(async (valid: any) => {
if (!valid) {
try {
await createDirectory({
...state.folderForm,
...{ pid, currentDir },
})
window.$message.success(t('resource.file.success'))
emit('updateList')
} catch (error: any) {
window.$message.error(error.message)
}
hideModal()
resetForm()
}
})
}
return {
handleCreateFolder,
}
}
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { reactive, ref, unref } from 'vue'
import { useI18n } from 'vue-i18n'
import type { FormRules } from 'naive-ui'
const defaultValue = () => ({
pid: -1,
type: 'FILE',
name: '',
description: '',
currentDir: '/',
})
export function useForm() {
const { t } = useI18n()
const resetForm = () => {
state.folderForm = Object.assign(unref(state.folderForm), defaultValue())
}
const state = reactive({
folderFormRef: ref(),
folderForm: defaultValue(),
rules: {
name: {
required: true,
trigger: ['input', 'blur'],
validator() {
if (state.folderForm.name === '') {
return new Error(t('resource.file.enter_name_tips'))
}
},
},
} as FormRules,
})
return {
state,
resetForm,
}
}
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.file-edit-content {
width: 100%;
background: #fff;
padding-bottom: 20px;
>h2 {
line-height: 60px;
text-align: center;
padding-right: 170px;
position: relative;
}
}
.form-content {
padding: 0 50px 0 50px;
}
.submit {
text-align: left;
padding-top: 12px;
margin-left: 160px;
}
.pagination {
display: flex;
justify-content: center;
align-items: center;
margin-top: 20px;
}
.table-box {
table {
width: 100%;
tr {
height: 40px;
font-size: 12px;
th,td{
&:nth-child(1) {
width: 50px;
text-align: center;
}
}
th {
&:nth-child(1) {
width: 60px;
text-align: center;
}
>span {
font-size: 12px;
color: #555;
}
}
}
}
}
\ No newline at end of file
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { useRouter } from 'vue-router'
import {
defineComponent,
onMounted,
ref,
reactive,
Ref,
watch,
inject,
} from 'vue'
import { NDataTable, NButtonGroup, NButton, NPagination } from 'naive-ui'
import { useI18n } from 'vue-i18n'
import Card from '@/components/card'
import Conditions from '@/components/conditions'
import { useTable } from './table/use-table'
import { useFileState } from './use-file'
import ResourceFolderModal from './folder'
import ResourceUploadModal from './upload'
import ResourceRenameModal from './rename'
import { IRenameFile } from './types'
import type { Router } from 'vue-router'
import styles from './index.module.scss'
export default defineComponent({
name: 'File',
inject: ['reload'],
setup() {
const router: Router = useRouter()
const fileId = ref(Number(router.currentRoute.value.params.id) || -1)
const reload = inject('reload')
const resourceListRef = ref()
const folderShowRef = ref(false)
const uploadShowRef = ref(false)
const renameShowRef = ref(false)
const serachRef = ref()
const renameInfo = reactive({
id: -1,
name: '',
description: '',
})
const paginationReactive = reactive({
page: 1,
pageSize: 10,
itemCount: 0,
pageSizes: [10, 30, 50],
})
const handleUpdatePage = (page: number) => {
paginationReactive.page = page
resourceListRef.value = getResourceListState(
fileId.value,
serachRef.value,
paginationReactive.page,
paginationReactive.pageSize,
)
}
const handleUpdatePageSize = (pageSize: number) => {
paginationReactive.page = 1
paginationReactive.pageSize = pageSize
resourceListRef.value = getResourceListState(
fileId.value,
serachRef.value,
paginationReactive.page,
paginationReactive.pageSize,
)
}
const handleShowModal = (showRef: Ref<Boolean>) => {
showRef.value = true
}
const setPagination = (count: number) => {
paginationReactive.itemCount = count
}
const { getResourceListState } = useFileState(setPagination)
const handleConditions = (val: string) => {
serachRef.value = val
resourceListRef.value = getResourceListState(fileId.value, val)
}
const handleCreateFolder = () => {
handleShowModal(folderShowRef)
}
const handleCreateFile = () => {
const name = fileId.value
? 'resource-subfile-create'
: 'resource-file-create'
router.push({
name,
params: { id: fileId.value },
})
}
const handleUploadFile = () => {
handleShowModal(uploadShowRef)
}
const handleRenameFile: IRenameFile = (id, name, description) => {
renameInfo.id = id
renameInfo.name = name
renameInfo.description = description
handleShowModal(renameShowRef)
}
const updateList = () => {
resourceListRef.value = getResourceListState(
fileId.value,
serachRef.value,
)
}
onMounted(() => {
resourceListRef.value = getResourceListState(fileId.value)
})
watch(
() => router.currentRoute.value.params.id,
() => reload(),
)
return {
fileId,
folderShowRef,
uploadShowRef,
renameShowRef,
handleShowModal,
resourceListRef,
updateList,
handleConditions,
handleCreateFolder,
handleCreateFile,
handleUploadFile,
handleRenameFile,
handleUpdatePage,
handleUpdatePageSize,
pagination: paginationReactive,
renameInfo,
}
},
render() {
const { t } = useI18n()
const { columnsRef } = useTable(this.handleRenameFile, this.updateList)
const {
handleConditions,
handleCreateFolder,
handleCreateFile,
handleUploadFile,
} = this
return (
<div>
<Conditions onConditions={handleConditions}>
<NButtonGroup>
<NButton onClick={handleCreateFolder}>
{t('resource.file.create_folder')}
</NButton>
<NButton onClick={handleCreateFile}>
{t('resource.file.create_file')}
</NButton>
<NButton onClick={handleUploadFile}>
{t('resource.file.upload_files')}
</NButton>
</NButtonGroup>
</Conditions>
<Card title={t('resource.file.file_manage')}>
<NDataTable
remote
columns={columnsRef}
data={this.resourceListRef?.value.table}
striped
size={'small'}
class={styles['table-box']}
/>
<div class={styles.pagination}>
<NPagination
v-model:page={this.pagination.page}
v-model:pageSize={this.pagination.pageSize}
pageSizes={this.pagination.pageSizes}
item-count={this.pagination.itemCount}
onUpdatePage={this.handleUpdatePage}
onUpdatePageSize={this.handleUpdatePageSize}
show-quick-jumper
show-size-picker
/>
</div>
<ResourceFolderModal
v-model:show={this.folderShowRef}
onUpdateList={this.updateList}
/>
<ResourceUploadModal
v-model:show={this.uploadShowRef}
onUpdateList={this.updateList}
/>
<ResourceRenameModal
v-model:show={this.renameShowRef}
id={this.renameInfo.id}
name={this.renameInfo.name}
description={this.renameInfo.description}
onUpdateList={this.updateList}
/>
</Card>
</div>
)
},
})
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { defineComponent, toRefs, PropType, watch } from 'vue'
import { NForm, NFormItem, NInput } from 'naive-ui'
import { useI18n } from 'vue-i18n'
import Modal from '@/components/modal'
import { useForm } from './use-form'
import { useRename } from './use-rename'
const props = {
show: {
type: Boolean as PropType<boolean>,
default: false,
},
id: {
type: Number as PropType<number>,
default: -1,
},
name: {
type: String as PropType<string>,
default: '',
},
description: {
type: String as PropType<string>,
default: '',
},
}
export default defineComponent({
name: 'ResourceFileRename',
props,
emits: ['updateList', 'update:show'],
setup(props, ctx) {
const { state, resetForm } = useForm(props.name, props.description)
const { handleRenameFile } = useRename(state)
const hideModal = () => {
ctx.emit('update:show', false)
}
const handleFile = () => {
handleRenameFile(ctx.emit, hideModal, resetForm)
}
watch(
() => props.name,
() => {
state.renameForm.id = props.id
state.renameForm.name = props.name
state.renameForm.description = props.description
},
)
return { hideModal, handleFile, ...toRefs(state) }
},
render() {
const { t } = useI18n()
return (
<Modal
show={this.$props.show}
title={t('resource.file.rename')}
onCancel={this.hideModal}
onConfirm={this.handleFile}
>
<NForm
rules={this.rules}
ref='renameFormRef'
label-placement='left'
label-width='160'
>
<NFormItem label={t('resource.file.name')} path='name'>
<NInput
v-model={[this.renameForm.name, 'value']}
placeholder={t('resource.file.enter_name_tips')}
/>
</NFormItem>
<NFormItem label={t('resource.file.description')} path='description'>
<NInput
type='textarea'
v-model={[this.renameForm.description, 'value']}
placeholder={t('resource.file.enter_description_tips')}
/>
</NFormItem>
</NForm>
</Modal>
)
},
})
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { reactive, ref, unref } from 'vue'
import { useI18n } from 'vue-i18n'
import type { FormRules } from 'naive-ui'
const defaultValue = (name = '', description = '') => ({
id: -1,
name,
type: 'FILE',
description,
})
export function useForm(name: string, description: string) {
const { t } = useI18n()
const resetForm = () => {
state.renameForm = Object.assign(unref(state.renameForm), defaultValue())
}
const state = reactive({
renameFormRef: ref(),
renameForm: defaultValue(name, description),
rules: {
name: {
required: true,
trigger: ['input', 'blur'],
validator() {
if (state.renameForm.name === '') {
return new Error(t('resource.file.enter_name_tips'))
}
},
},
} as FormRules,
})
return {
state,
resetForm,
}
}
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { useI18n } from 'vue-i18n'
import { IEmit } from '../types'
import { updateResource } from '@/service/modules/resources'
export function useRename(state: any) {
const { t } = useI18n()
const handleRenameFile = (
emit: IEmit,
hideModal: () => void,
resetForm: () => void,
) => {
state.renameFormRef.validate(async (valid: any) => {
if (!valid) {
try {
await updateResource(
{
...state.renameForm,
},
state.renameForm.id,
)
window.$message.success(t('resource.file.success'))
emit('updateList')
} catch (error: any) {
window.$message.error(error.message)
}
}
hideModal()
resetForm()
})
}
return {
handleRenameFile,
}
}
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
.links {
color: #2080f0;
text-decoration: none;
&:hover {
text-decoration: underline;
}
}
\ No newline at end of file
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { useRouter } from 'vue-router'
import { defineComponent, PropType } from 'vue'
import { NSpace, NTooltip, NButton, NIcon, NPopconfirm } from 'naive-ui'
import {
DeleteOutlined,
DownloadOutlined,
FormOutlined,
EditOutlined,
InfoCircleFilled,
} from '@vicons/antd'
import _ from 'lodash'
import { useI18n } from 'vue-i18n'
import { ResourceFileTableData } from '../types'
import { fileTypeArr } from '@/utils/common'
import { downloadResource, deleteResource } from '@/service/modules/resources'
import { IRenameFile, IRtDisb } from '../types'
import type { Router } from 'vue-router'
const props = {
show: {
type: Boolean as PropType<boolean>,
default: false,
},
row: {
type: Object as PropType<ResourceFileTableData>,
default: {
id: -1,
name: '',
description: '',
},
},
}
export default defineComponent({
name: 'TableAction',
props,
emits: ['updateList', 'renameResource'],
setup(props, { emit }) {
const { t } = useI18n()
const router: Router = useRouter()
const rtDisb: IRtDisb = (name, size) => {
const i = name.lastIndexOf('.')
const a = name.substring(i, name.length)
const flag = _.includes(fileTypeArr, _.trimStart(a, '.'))
return !(flag && size < 1000000)
}
const handleEditFile = (item: { id: number }) => {
router.push({ name: 'resource-file-edit', params: { id: item.id } })
}
const handleDeleteFile = (id: number) => {
deleteResource(id).then(() => emit('updateList'))
}
const handleRenameFile: IRenameFile = (id, name, description) => {
emit('renameResource', id, name, description)
}
return {
t,
rtDisb,
handleEditFile,
handleDeleteFile,
handleRenameFile,
...props,
}
},
render() {
const { t } = useI18n()
return (
<NSpace>
<NTooltip trigger={'hover'}>
{{
default: () => t('resource.file.edit'),
trigger: () => (
<NButton
size='tiny'
type='info'
disabled={this.rtDisb(this.row.name, this.row.size)}
tag='div'
onClick={() => {
this.handleEditFile(this.row)
}}
style={{ marginRight: '-5px' }}
circle
>
<NIcon>
<FormOutlined />
</NIcon>
</NButton>
),
}}
</NTooltip>
<NTooltip trigger={'hover'}>
{{
default: () => t('resource.file.rename'),
trigger: () => (
<NButton
size='tiny'
type='info'
onClick={() =>
this.handleRenameFile(
this.row.id,
this.row.name,
this.row.description,
)
}
style={{ marginRight: '-5px' }}
circle
>
<NIcon>
<EditOutlined />
</NIcon>
</NButton>
),
}}
</NTooltip>
<NTooltip trigger={'hover'}>
{{
default: () => t('resource.file.download'),
trigger: () => (
<NButton
size='tiny'
type='info'
disabled={this.row?.directory ? true : false}
tag='div'
circle
style={{ marginRight: '-5px' }}
onClick={() => downloadResource(this.row.id)}
>
<NIcon>
<DownloadOutlined />
</NIcon>
</NButton>
),
}}
</NTooltip>
<NTooltip trigger={'hover'}>
{{
default: () => t('resource.file.delete'),
trigger: () => (
<NButton size='tiny' type='error' circle>
<NPopconfirm
positive-text={t('resource.file.confirm')}
negative-text={t('resource.file.cancel')}
onPositiveClick={() => {
this.handleDeleteFile(this.row.id)
}}
>
{{
default: () => t('resource.file.delete_confirm'),
icon: () => (
<NIcon>
<InfoCircleFilled />
</NIcon>
),
trigger: () => (
<NIcon>
<DeleteOutlined />
</NIcon>
),
}}
</NPopconfirm>
</NButton>
),
}}
</NTooltip>
</NSpace>
)
},
})
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { h } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRouter } from 'vue-router'
import { bytesToSize } from '@/utils/common'
import { useFileStore } from '@/store/file/file'
import TableAction from './table-action'
import { IRenameFile } from '../types'
import type { Router } from 'vue-router'
import type { TableColumns } from 'naive-ui/es/data-table/src/interface'
import styles from './index.module.scss'
const goSubFolder = (router: Router, item: any) => {
const fileStore = useFileStore()
fileStore.setFileInfo(`${item.alias}|${item.size}`)
if (item.directory) {
fileStore.setCurrentDir(`${item.fullName}`)
router.push({ name: 'resource-file-subdirectory', params: { id: item.id } })
} else {
router.push({ name: 'resource-file-list', params: { id: item.id } })
}
}
export function useTable(renameResource: IRenameFile, updateList: () => void) {
const { t } = useI18n()
const router: Router = useRouter()
const columnsRef: TableColumns<any> = [
{
title: t('resource.file.id'),
key: 'id',
width: 50,
render: (_row, index) => index + 1,
},
{
title: t('resource.file.name'),
key: 'name',
width: 120,
render: (row) =>
h(
'a',
{
href: 'javascript:',
class: styles.links,
onClick: () => goSubFolder(router, row),
},
{
default: () => {
return row.name
},
},
),
},
{ title: t('resource.file.user_name'), width: 100, key: 'user_name' },
{
title: t('resource.file.whether_directory'),
key: 'whether_directory',
width: 100,
render: (row) =>
row.directory ? t('resource.file.yes') : t('resource.file.no'),
},
{ title: t('resource.file.file_name'), key: 'file_name' },
{ title: t('resource.file.description'), width: 150, key: 'description' },
{
title: t('resource.file.size'),
key: 'size',
render: (row) => bytesToSize(row.size),
},
{ title: t('resource.file.update_time'), width: 150, key: 'update_time' },
{
title: t('resource.file.operation'),
key: 'operation',
width: 150,
render: (row) =>
h(TableAction, {
row,
onRenameResource: (id, name, description) =>
renameResource(id, name, description),
onUpdateList: () => updateList(),
}),
},
]
return {
columnsRef,
}
}
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
export interface ResourceFileTableData {
id: number
name: string
user_name: string
directory: string
file_name: string
description: string
size: number
update_time: string
}
export interface IEmit {
(event: any, ...args: any[]): void
}
export interface IRenameFile {
(id: number, name: string, description: string): void
}
export interface IRtDisb {
(name: string, size: number): boolean
}
export interface IResourceListState {
(id?: number, searchVal?: string, pageNo?: number, pageSize?: number): any
}
export interface BasicTableProps {
title?: string
dataSource: Function
columns: any[]
pagination: object
showPagination: boolean
actionColumn: any[]
canResize: boolean
resizeHeightOffset: number
}
export interface PaginationProps {
page?: number
pageCount?: number
pageSize?: number
pageSizes?: number[]
showSizePicker?: boolean
showQuickJumper?: boolean
}
export interface ISetPagination {
(itemCount: number): void
}
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { defineComponent, toRefs, PropType } from 'vue'
import { NButton, NForm, NFormItem, NInput, NUpload } from 'naive-ui'
import { useI18n } from 'vue-i18n'
import Modal from '@/components/modal'
import { useForm } from './use-form'
import { useUpload } from './use-upload'
const props = {
show: {
type: Boolean as PropType<boolean>,
default: false,
},
}
export default defineComponent({
name: 'ResourceFileUpload',
props,
emits: ['updateList', 'update:show'],
setup(props, ctx) {
const { state, resetForm } = useForm()
const { handleUploadFile } = useUpload(state)
const hideModal = () => {
ctx.emit('update:show')
}
const customRequest = ({ file }: any) => {
state.uploadForm.name = file.name
state.uploadForm.file = file.file
}
const handleFile = () => {
handleUploadFile(ctx.emit, hideModal, resetForm)
}
return {
hideModal,
customRequest,
handleFile,
...toRefs(state),
}
},
render() {
const { t } = useI18n()
return (
<Modal
show={this.$props.show}
title={t('resource.file.upload_files')}
onCancel={this.hideModal}
onConfirm={this.handleFile}
>
<NForm
rules={this.rules}
ref='uploadFormRef'
label-placement='left'
label-width='160'
>
<NFormItem label={t('resource.file.file_name')} path='name'>
<NInput
v-model={[this.uploadForm.name, 'value']}
placeholder={t('resource.file.enter_name_tips')}
/>
</NFormItem>
<NFormItem label={t('resource.file.description')} path='description'>
<NInput
type='textarea'
v-model={[this.uploadForm.description, 'value']}
placeholder={t('resource.file.enter_description_tips')}
/>
</NFormItem>
<NFormItem label={t('resource.file.upload_files')} path='file'>
<NUpload
v-model={[this.uploadForm.file, 'value']}
customRequest={this.customRequest}
>
<NButton>{t('resource.file.upload_files')}</NButton>
</NUpload>
</NFormItem>
</NForm>
</Modal>
)
},
})
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { reactive, ref, unref } from 'vue'
import { useI18n } from 'vue-i18n'
import type { FormRules } from 'naive-ui'
const defaultValue = () => ({
name: '',
file: '',
description: '',
pid: -1,
currentDir: '/',
})
export function useForm() {
const { t } = useI18n()
const resetForm = () => {
state.uploadForm = Object.assign(unref(state.uploadForm), defaultValue())
}
const state = reactive({
uploadFormRef: ref(),
uploadForm: defaultValue(),
rules: {
name: {
required: true,
trigger: ['input', 'blur'],
validator() {
if (state.uploadForm.name === '') {
return new Error(t('resource.file.enter_name_tips'))
}
},
},
file: {
required: true,
trigger: ['input', 'blur'],
validator() {
if (state.uploadForm.file === '') {
return new Error(t('resource.file.enter_content_tips'))
}
},
},
} as FormRules,
})
return {
state,
resetForm,
}
}
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { useI18n } from 'vue-i18n'
import { IEmit } from '../types'
import { useRouter } from 'vue-router'
import type { Router } from 'vue-router'
import { useFileStore } from '@/store/file/file'
import { createResource } from '@/service/modules/resources'
export function useUpload(state: any) {
const { t } = useI18n()
const router: Router = useRouter()
const fileStore = useFileStore()
const handleUploadFile = (
emit: IEmit,
hideModal: () => void,
resetForm: () => void,
) => {
state.uploadFormRef.validate(async (valid: any) => {
const pid = router.currentRoute.value.params.id || -1
const currentDir = fileStore.getCurrentDir || '/'
if (!valid) {
const formData = new FormData()
formData.append('file', state.uploadForm.file)
formData.append('type', 'FILE')
formData.append('name', state.uploadForm.name)
formData.append('pid', String(pid))
formData.append('currentDir', currentDir)
formData.append('description', state.uploadForm.description)
try {
await createResource(formData as any)
window.$message.success(t('resource.file.success'))
emit('updateList')
} catch (error: any) {
window.$message.error(error.message)
}
hideModal()
resetForm()
}
})
}
return {
handleUploadFile,
}
}
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You under the Apache License, Version 2.0
* (the "License"); you may not use this file except in compliance with
* the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { useAsyncState } from '@vueuse/core'
import {
queryResourceListPaging,
viewResource,
} from '@/service/modules/resources'
import type { ResourceListRes } from '@/service/modules/resources/types'
import { IResourceListState, ISetPagination } from './types'
export function useFileState(
setPagination: ISetPagination = {} as ISetPagination,
) {
const getResourceListState: IResourceListState = (
id = -1,
searchVal = '',
pageNo = 1,
pageSize = 10,
) => {
const { state } = useAsyncState(
queryResourceListPaging({
id,
type: 'FILE',
searchVal,
pageNo,
pageSize,
}).then((res: ResourceListRes): any => {
const { total } = res
setPagination(total)
const table = res.totalList.map((item) => {
return {
id: item.id,
name: item.alias,
alias: item.alias,
fullName: item.fullName,
type: item.type,
directory: item.directory,
file_name: item.fileName,
description: item.description,
size: item.size,
update_time: item.updateTime,
}
})
return { total, table }
}),
{ total: 0, table: [] },
)
return state
}
const getResourceView = (id: number) => {
const params = {
skipLineNum: 0,
limit: 3000,
}
const { state } = useAsyncState(viewResource(params, id), {})
return state
}
return { getResourceListState, getResourceView }
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册