mirror of
https://github.com/continew-org/continew-admin-ui.git
synced 2025-09-19 08:57:10 +08:00
refactor(system/storage): 重构存储管理页面,分页列表 => 无分页卡片
This commit is contained in:
@@ -6,8 +6,8 @@ export type * from './type'
|
|||||||
const BASE_URL = '/system/storage'
|
const BASE_URL = '/system/storage'
|
||||||
|
|
||||||
/** @desc 查询存储列表 */
|
/** @desc 查询存储列表 */
|
||||||
export function listStorage(query: T.StoragePageQuery) {
|
export function listStorage(query: T.StorageQuery) {
|
||||||
return http.get<PageRes<T.StorageResp[]>>(`${BASE_URL}`, query)
|
return http.get<T.StorageResp[]>(`${BASE_URL}/list`, query)
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @desc 查询存储详情 */
|
/** @desc 查询存储详情 */
|
||||||
@@ -29,3 +29,13 @@ export function updateStorage(data: any, id: string) {
|
|||||||
export function deleteStorage(id: string) {
|
export function deleteStorage(id: string) {
|
||||||
return http.del(`${BASE_URL}/${id}`)
|
return http.del(`${BASE_URL}/${id}`)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** @desc 修改存储状态 */
|
||||||
|
export function updateStorageStatus(data: any, id: string) {
|
||||||
|
return http.put(`${BASE_URL}/${id}/status`, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @desc 设置默认存储 */
|
||||||
|
export function setDefaultStorage(id: string) {
|
||||||
|
return http.put(`${BASE_URL}/${id}/default`)
|
||||||
|
}
|
||||||
|
@@ -253,11 +253,9 @@ export interface StorageResp {
|
|||||||
}
|
}
|
||||||
export interface StorageQuery {
|
export interface StorageQuery {
|
||||||
description?: string
|
description?: string
|
||||||
status?: number
|
type?: number
|
||||||
sort: Array<string>
|
sort: Array<string>
|
||||||
}
|
}
|
||||||
export interface StoragePageQuery extends StorageQuery, PageQuery {
|
|
||||||
}
|
|
||||||
|
|
||||||
/** 终端类型 */
|
/** 终端类型 */
|
||||||
export interface ClientResp {
|
export interface ClientResp {
|
||||||
|
@@ -142,9 +142,9 @@ const dicData: Record<string, any> = reactive({})
|
|||||||
const getComponentBindProps = (item: ColumnItem) => {
|
const getComponentBindProps = (item: ColumnItem) => {
|
||||||
// 组件默认配置映射表
|
// 组件默认配置映射表
|
||||||
const ConfigMap = new Map<ColumnItem['type'], Partial<ColumnItem['props'] & { placeholder: string }>>([
|
const ConfigMap = new Map<ColumnItem['type'], Partial<ColumnItem['props'] & { placeholder: string }>>([
|
||||||
['input', { allowClear: true, placeholder: `请输入${item.label}`, maxLength: 255 }],
|
['input', { allowClear: true, placeholder: `请输入${item.label}`, maxLength: 255, showWordLimit: true }],
|
||||||
['input-number', { placeholder: `请输入${item.label}` }],
|
['input-number', { placeholder: `请输入${item.label}` }],
|
||||||
['textarea', { allowClear: false, placeholder: `请输入${item.label}`, maxLength: 200 }],
|
['textarea', { allowClear: false, placeholder: `请输入${item.label}`, maxLength: 200, showWordLimit: true, autoSize: { minRows: 3, maxRows: 5 } }],
|
||||||
['input-tag', { allowClear: true, placeholder: `请输入${item.label}` }],
|
['input-tag', { allowClear: true, placeholder: `请输入${item.label}` }],
|
||||||
['mention', { allowClear: true, placeholder: `请输入${item.label}` }],
|
['mention', { allowClear: true, placeholder: `请输入${item.label}` }],
|
||||||
['select', { allowClear: true, placeholder: `请选择${item.label}`, options: dicData[item.field] || [] }],
|
['select', { allowClear: true, placeholder: `请选择${item.label}`, options: dicData[item.field] || [] }],
|
||||||
@@ -169,7 +169,8 @@ const valueChange = (value: any, field: string) => {
|
|||||||
/** 表单项校验规则 */
|
/** 表单项校验规则 */
|
||||||
const getFormItemRules = (item: ColumnItem) => {
|
const getFormItemRules = (item: ColumnItem) => {
|
||||||
if (item.required) {
|
if (item.required) {
|
||||||
return [{ required: true, message: `${item.label}为必填项` }, ...(Array.isArray(item.rules) ? item.rules : [])]
|
const defaultProps = getComponentBindProps(item)
|
||||||
|
return [{ required: true, message: defaultProps.placeholder || `请输入${item.label}` }, ...(Array.isArray(item.rules) ? item.rules : [])]
|
||||||
}
|
}
|
||||||
return item.rules
|
return item.rules
|
||||||
}
|
}
|
||||||
|
@@ -213,16 +213,19 @@
|
|||||||
|
|
||||||
// 卡片标题,标题左侧的伪类样式
|
// 卡片标题,标题左侧的伪类样式
|
||||||
.gi_card_title {
|
.gi_card_title {
|
||||||
.arco-card-header-title::before {
|
> .arco-card-header {
|
||||||
content: '';
|
border-bottom: none;
|
||||||
width: 4px;
|
.arco-card-header-title::before {
|
||||||
height: 50%;
|
content: '';
|
||||||
position: absolute;
|
width: 4px;
|
||||||
left: 0;
|
height: 50%;
|
||||||
top: 50%;
|
position: absolute;
|
||||||
transform: translateY(-50%);
|
left: 0;
|
||||||
background-color: rgb(var(--warning-5));
|
top: 50%;
|
||||||
border-radius: 0 4px 4px 0;
|
transform: translateY(-50%);
|
||||||
|
background-color: rgb(var(--primary-5));
|
||||||
|
border-radius: 0 4px 4px 0;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@@ -57,7 +57,12 @@ const change = (key: string | number) => {
|
|||||||
<style scoped lang="scss">
|
<style scoped lang="scss">
|
||||||
.gi_table_page {
|
.gi_table_page {
|
||||||
overflow-y: auto;
|
overflow-y: auto;
|
||||||
|
|
||||||
|
:deep(.arco-tabs) {
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.arco-tabs .arco-tabs-nav-type-card-gutter .arco-tabs-tab-active) {
|
:deep(.arco-tabs .arco-tabs-nav-type-card-gutter .arco-tabs-tab-active) {
|
||||||
box-shadow: inset 0 2px 0 rgb(var(--primary-6)), inset -1px 0 0 var(--color-border-2),
|
box-shadow: inset 0 2px 0 rgb(var(--primary-6)), inset -1px 0 0 var(--color-border-2),
|
||||||
inset 1px 0 0 var(--color-border-2);
|
inset 1px 0 0 var(--color-border-2);
|
||||||
@@ -77,10 +82,6 @@ const change = (key: string | number) => {
|
|||||||
right: -20px;
|
right: -20px;
|
||||||
}
|
}
|
||||||
|
|
||||||
:deep(.arco-tabs) {
|
|
||||||
overflow: visible;
|
|
||||||
}
|
|
||||||
|
|
||||||
:deep(.arco-tabs-nav) {
|
:deep(.arco-tabs-nav) {
|
||||||
overflow: visible;
|
overflow: visible;
|
||||||
}
|
}
|
||||||
|
@@ -1,198 +0,0 @@
|
|||||||
<template>
|
|
||||||
<a-drawer
|
|
||||||
v-model:visible="visible"
|
|
||||||
:title="title"
|
|
||||||
:mask-closable="false"
|
|
||||||
:esc-to-close="false"
|
|
||||||
:width="width >= 500 ? 500 : '100%'"
|
|
||||||
@before-ok="save"
|
|
||||||
@close="reset"
|
|
||||||
>
|
|
||||||
<a-form ref="formRef" :model="form" :rules="rules" size="large" auto-label-width>
|
|
||||||
<a-form-item label="名称" field="name">
|
|
||||||
<a-input v-model.trim="form.name" placeholder="请输入名称" />
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label="编码" field="code">
|
|
||||||
<a-input v-model.trim="form.code" placeholder="请输入编码" :disabled="isUpdate" />
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label="类型" field="type">
|
|
||||||
<a-select v-model.trim="form.type" :options="storage_type_enum" placeholder="请选择类型" :disabled="isUpdate" />
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item v-if="form.type === 1" label="访问密钥" field="accessKey">
|
|
||||||
<a-input v-model.trim="form.accessKey" placeholder="请输入访问密钥" :max-length="255" />
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item v-if="form.type === 1" label="私有密钥" field="secretKey">
|
|
||||||
<a-input
|
|
||||||
v-model.trim="form.secretKey"
|
|
||||||
placeholder="请输入私有密钥"
|
|
||||||
:max-length="255"
|
|
||||||
/>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item v-if="form.type === 1" label="终端节点" field="endpoint">
|
|
||||||
<a-input v-model.trim="form.endpoint" placeholder="请输入终端节点" />
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label="桶名称" field="bucketName">
|
|
||||||
<a-input v-model.trim="form.bucketName" placeholder="请输入桶名称" />
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item v-if="form.type === 1" label="域名" field="domain">
|
|
||||||
<a-input v-model.trim="form.domain" placeholder="请输入域名" />
|
|
||||||
<template #extra>
|
|
||||||
<div v-if="defaultDomain">
|
|
||||||
<span>留空默认域名:{{ defaultDomain }}</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item
|
|
||||||
v-if="form.type === 2"
|
|
||||||
label="域名"
|
|
||||||
field="domain"
|
|
||||||
:rules="[
|
|
||||||
{
|
|
||||||
required: true,
|
|
||||||
message: '请输入域名',
|
|
||||||
},
|
|
||||||
]"
|
|
||||||
>
|
|
||||||
<a-input v-model.trim="form.domain" placeholder="请输入域名" />
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label="排序" field="sort">
|
|
||||||
<a-input-number v-model="form.sort" placeholder="请输入排序" :min="1" mode="button" />
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label="描述" field="description">
|
|
||||||
<a-textarea
|
|
||||||
v-model.trim="form.description"
|
|
||||||
placeholder="请输入描述"
|
|
||||||
show-word-limit
|
|
||||||
:max-length="200"
|
|
||||||
:auto-size="{ minRows: 3, maxRows: 5 }"
|
|
||||||
/>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label="默认存储" field="isDefault">
|
|
||||||
<a-switch
|
|
||||||
v-model="form.isDefault"
|
|
||||||
type="round"
|
|
||||||
:checked-value="true"
|
|
||||||
:unchecked-value="false"
|
|
||||||
checked-text="是"
|
|
||||||
unchecked-text="否"
|
|
||||||
/>
|
|
||||||
</a-form-item>
|
|
||||||
<a-form-item label="状态" field="status">
|
|
||||||
<a-switch
|
|
||||||
v-model="form.status"
|
|
||||||
type="round"
|
|
||||||
:checked-value="1"
|
|
||||||
:unchecked-value="2"
|
|
||||||
checked-text="启用"
|
|
||||||
unchecked-text="禁用"
|
|
||||||
/>
|
|
||||||
</a-form-item>
|
|
||||||
</a-form>
|
|
||||||
</a-drawer>
|
|
||||||
</template>
|
|
||||||
|
|
||||||
<script setup lang="ts">
|
|
||||||
import { type FormInstance, Message } from '@arco-design/web-vue'
|
|
||||||
import { useWindowSize } from '@vueuse/core'
|
|
||||||
import { addStorage, getStorage, updateStorage } from '@/apis/system/storage'
|
|
||||||
import { useResetReactive } from '@/hooks'
|
|
||||||
import { useDict } from '@/hooks/app'
|
|
||||||
import { encryptByRsa } from '@/utils/encrypt'
|
|
||||||
import { isIPv4 } from '@/utils/validate'
|
|
||||||
|
|
||||||
const emit = defineEmits<{
|
|
||||||
(e: 'save-success'): void
|
|
||||||
}>()
|
|
||||||
|
|
||||||
const { width } = useWindowSize()
|
|
||||||
|
|
||||||
const dataId = ref('')
|
|
||||||
const visible = ref(false)
|
|
||||||
const isUpdate = computed(() => !!dataId.value)
|
|
||||||
const title = computed(() => (isUpdate.value ? '修改存储' : '新增存储'))
|
|
||||||
const formRef = ref<FormInstance>()
|
|
||||||
const { storage_type_enum } = useDict('storage_type_enum')
|
|
||||||
|
|
||||||
const rules: FormInstance['rules'] = {
|
|
||||||
name: [{ required: true, message: '请输入名称' }],
|
|
||||||
code: [{ required: true, message: '请输入编码' }],
|
|
||||||
type: [{ required: true, message: '请选择类型' }],
|
|
||||||
accessKey: [{ required: true, message: '请输入访问密钥' }],
|
|
||||||
secretKey: [{ required: true, message: '请输入私有密钥' }],
|
|
||||||
endpoint: [{ required: true, message: '请输入终端节点' }],
|
|
||||||
bucketName: [{ required: true, message: '请输入桶名称' }],
|
|
||||||
}
|
|
||||||
|
|
||||||
const [form, resetForm] = useResetReactive({
|
|
||||||
type: 2,
|
|
||||||
isDefault: false,
|
|
||||||
sort: 999,
|
|
||||||
status: 1,
|
|
||||||
})
|
|
||||||
/** 获取url的protocol和endpoint */
|
|
||||||
const stripProtocol = (url: string): { endpoint: string, protocol: string } => {
|
|
||||||
if (url.startsWith('http://')) {
|
|
||||||
return { endpoint: url.substring(7), protocol: 'http://' }
|
|
||||||
} else if (url.startsWith('https://')) {
|
|
||||||
return { endpoint: url.substring(8), protocol: 'https://' }
|
|
||||||
}
|
|
||||||
return { endpoint: url, protocol: 'http://' }
|
|
||||||
}
|
|
||||||
/** 按规则拼接当前默认domain */
|
|
||||||
const defaultDomain = computed(() => {
|
|
||||||
const { endpoint: initialEndpoint, bucketName, domain, type } = form
|
|
||||||
if (domain || type !== 1 || !initialEndpoint || !bucketName) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
const { endpoint, protocol } = stripProtocol(initialEndpoint)
|
|
||||||
return isIPv4(endpoint) ? `${protocol}${endpoint}/${bucketName}/` : `${protocol}${bucketName}.${endpoint}/`
|
|
||||||
})
|
|
||||||
|
|
||||||
// 重置
|
|
||||||
const reset = () => {
|
|
||||||
formRef.value?.resetFields()
|
|
||||||
resetForm()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 保存
|
|
||||||
const save = async () => {
|
|
||||||
try {
|
|
||||||
const isInvalid = await formRef.value?.validate()
|
|
||||||
if (isInvalid) return false
|
|
||||||
const data = {
|
|
||||||
...form,
|
|
||||||
secretKey: form.type === 1 && !form.secretKey.includes('*') ? encryptByRsa(form.secretKey) : null,
|
|
||||||
domain: form.domain || defaultDomain.value,
|
|
||||||
}
|
|
||||||
if (isUpdate.value) {
|
|
||||||
await updateStorage(data, dataId.value)
|
|
||||||
Message.success('修改成功')
|
|
||||||
} else {
|
|
||||||
await addStorage(data)
|
|
||||||
Message.success('新增成功')
|
|
||||||
}
|
|
||||||
emit('save-success')
|
|
||||||
return true
|
|
||||||
} catch (error) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 新增
|
|
||||||
const onAdd = () => {
|
|
||||||
reset()
|
|
||||||
dataId.value = ''
|
|
||||||
visible.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
// 修改
|
|
||||||
const onUpdate = async (id: string) => {
|
|
||||||
reset()
|
|
||||||
dataId.value = id
|
|
||||||
const { data } = await getStorage(id)
|
|
||||||
Object.assign(form, data)
|
|
||||||
visible.value = true
|
|
||||||
}
|
|
||||||
|
|
||||||
defineExpose({ onAdd, onUpdate })
|
|
||||||
</script>
|
|
208
src/views/system/storage/StorageAddModal.vue
Normal file
208
src/views/system/storage/StorageAddModal.vue
Normal file
@@ -0,0 +1,208 @@
|
|||||||
|
<template>
|
||||||
|
<a-modal
|
||||||
|
v-model:visible="visible"
|
||||||
|
:title="title"
|
||||||
|
:mask-closable="false"
|
||||||
|
:esc-to-close="false"
|
||||||
|
:width="width >= 500 ? 500 : '100%'"
|
||||||
|
@before-ok="save"
|
||||||
|
@close="reset"
|
||||||
|
>
|
||||||
|
<GiForm ref="formRef" v-model="form" :columns="columns" />
|
||||||
|
</a-modal>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import { Message } from '@arco-design/web-vue'
|
||||||
|
import { useWindowSize } from '@vueuse/core'
|
||||||
|
import { addStorage, getStorage, updateStorage } from '@/apis/system/storage'
|
||||||
|
import { type ColumnItem, GiForm } from '@/components/GiForm'
|
||||||
|
import { useResetReactive } from '@/hooks'
|
||||||
|
import { useDict } from '@/hooks/app'
|
||||||
|
import { encryptByRsa } from '@/utils/encrypt'
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'save-success'): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const { width } = useWindowSize()
|
||||||
|
|
||||||
|
const dataId = ref('')
|
||||||
|
const visible = ref(false)
|
||||||
|
const isUpdate = computed(() => !!dataId.value)
|
||||||
|
const storageType = ref('')
|
||||||
|
const title = computed(() => (isUpdate.value ? `修改${storageType.value}` : `新增${storageType.value}`))
|
||||||
|
const formRef = ref<InstanceType<typeof GiForm>>()
|
||||||
|
const { storage_type_enum } = useDict('storage_type_enum')
|
||||||
|
|
||||||
|
const [form, resetForm] = useResetReactive({
|
||||||
|
type: 2,
|
||||||
|
isDefault: false,
|
||||||
|
sort: 999,
|
||||||
|
status: 2,
|
||||||
|
})
|
||||||
|
|
||||||
|
const columns: ColumnItem[] = reactive([
|
||||||
|
{
|
||||||
|
label: '名称',
|
||||||
|
field: 'name',
|
||||||
|
type: 'input',
|
||||||
|
span: 24,
|
||||||
|
props: {
|
||||||
|
maxLength: 100,
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '编码',
|
||||||
|
field: 'code',
|
||||||
|
type: 'input',
|
||||||
|
span: 24,
|
||||||
|
props: {
|
||||||
|
maxLength: 30,
|
||||||
|
},
|
||||||
|
required: true,
|
||||||
|
disabled: () => isUpdate.value,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Access Key',
|
||||||
|
field: 'accessKey',
|
||||||
|
type: 'input',
|
||||||
|
span: 24,
|
||||||
|
required: true,
|
||||||
|
show: () => form.type === 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Secret Key',
|
||||||
|
field: 'secretKey',
|
||||||
|
type: 'input',
|
||||||
|
span: 24,
|
||||||
|
required: true,
|
||||||
|
show: () => form.type === 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Endpoint',
|
||||||
|
field: 'endpoint',
|
||||||
|
type: 'input',
|
||||||
|
span: 24,
|
||||||
|
required: true,
|
||||||
|
show: () => form.type === 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: 'Bucket',
|
||||||
|
field: 'bucketName',
|
||||||
|
type: 'input',
|
||||||
|
span: 24,
|
||||||
|
required: true,
|
||||||
|
show: () => form.type === 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '域名',
|
||||||
|
field: 'domain',
|
||||||
|
type: 'input',
|
||||||
|
span: 24,
|
||||||
|
required: true,
|
||||||
|
show: () => form.type === 2,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '存储路径',
|
||||||
|
field: 'bucketName',
|
||||||
|
type: 'input',
|
||||||
|
span: 24,
|
||||||
|
required: true,
|
||||||
|
show: () => form.type === 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '访问路径',
|
||||||
|
field: 'domain',
|
||||||
|
type: 'input',
|
||||||
|
span: 24,
|
||||||
|
required: true,
|
||||||
|
show: () => form.type === 1,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '排序',
|
||||||
|
field: 'sort',
|
||||||
|
type: 'input-number',
|
||||||
|
span: 24,
|
||||||
|
props: {
|
||||||
|
min: 1,
|
||||||
|
mode: 'button',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '描述',
|
||||||
|
field: 'description',
|
||||||
|
type: 'textarea',
|
||||||
|
span: 24,
|
||||||
|
props: {
|
||||||
|
maxLength: 200,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
label: '状态',
|
||||||
|
field: 'status',
|
||||||
|
type: 'switch',
|
||||||
|
span: 24,
|
||||||
|
props: {
|
||||||
|
type: 'round',
|
||||||
|
checkedValue: 1,
|
||||||
|
uncheckedValue: 2,
|
||||||
|
checkedText: '启用',
|
||||||
|
uncheckedText: '禁用',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
])
|
||||||
|
|
||||||
|
// 重置
|
||||||
|
const reset = () => {
|
||||||
|
formRef.value?.formRef?.resetFields()
|
||||||
|
resetForm()
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存
|
||||||
|
const save = async () => {
|
||||||
|
try {
|
||||||
|
const isInvalid = await formRef.value?.formRef?.validate()
|
||||||
|
if (isInvalid) return false
|
||||||
|
if (isUpdate.value) {
|
||||||
|
await updateStorage({
|
||||||
|
...form,
|
||||||
|
secretKey: form.type === 2 && !form.secretKey.includes('*') ? encryptByRsa(form.secretKey) || '' : null,
|
||||||
|
}, dataId.value)
|
||||||
|
Message.success('修改成功')
|
||||||
|
} else {
|
||||||
|
await addStorage({
|
||||||
|
...form,
|
||||||
|
secretKey: form.type === 2 ? encryptByRsa(form.secretKey) || '' : form.secretKey,
|
||||||
|
})
|
||||||
|
Message.success('新增成功')
|
||||||
|
}
|
||||||
|
emit('save-success')
|
||||||
|
return true
|
||||||
|
} catch (error) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 新增
|
||||||
|
const onAdd = (type: number) => {
|
||||||
|
reset()
|
||||||
|
dataId.value = ''
|
||||||
|
form.type = type
|
||||||
|
storageType.value = storage_type_enum.value.find((item) => item.value === type)?.label || '本地存储'
|
||||||
|
visible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改
|
||||||
|
const onUpdate = async (id: string) => {
|
||||||
|
reset()
|
||||||
|
dataId.value = id
|
||||||
|
const { data } = await getStorage(id)
|
||||||
|
Object.assign(form, data)
|
||||||
|
storageType.value = storage_type_enum.value.find((item) => item.value === form.type)?.label || '本地存储'
|
||||||
|
visible.value = true
|
||||||
|
}
|
||||||
|
|
||||||
|
defineExpose({ onAdd, onUpdate })
|
||||||
|
</script>
|
78
src/views/system/storage/StorageLocal.vue
Normal file
78
src/views/system/storage/StorageLocal.vue
Normal file
@@ -0,0 +1,78 @@
|
|||||||
|
<template>
|
||||||
|
<div class="list-wrap">
|
||||||
|
<a-row class="list-row" :gutter="24">
|
||||||
|
<a-col
|
||||||
|
v-if="has.hasPermOr(['system:storage:add'])"
|
||||||
|
:xs="24"
|
||||||
|
:sm="24"
|
||||||
|
:md="12"
|
||||||
|
:lg="8"
|
||||||
|
:xl="8"
|
||||||
|
:xxl="6"
|
||||||
|
class="list-col"
|
||||||
|
style="min-height: 162px"
|
||||||
|
>
|
||||||
|
<CardAdd :type="1" @save-success="search" />
|
||||||
|
</a-col>
|
||||||
|
<a-empty v-if="!data.length && !has.hasPermOr(['system:storage:add'])" />
|
||||||
|
<a-col
|
||||||
|
v-for="item in data"
|
||||||
|
:key="item.id"
|
||||||
|
:xs="24"
|
||||||
|
:sm="24"
|
||||||
|
:md="12"
|
||||||
|
:lg="8"
|
||||||
|
:xl="8"
|
||||||
|
:xxl="6"
|
||||||
|
class="list-col"
|
||||||
|
style="min-height: 162px"
|
||||||
|
>
|
||||||
|
<CardBlock
|
||||||
|
:loading="loading"
|
||||||
|
:data="item"
|
||||||
|
@save-success="search"
|
||||||
|
>
|
||||||
|
<template #content>
|
||||||
|
<a-skeleton v-if="loading" :animation="true">
|
||||||
|
<a-skeleton-line :widths="['60%']" :rows="2" />
|
||||||
|
</a-skeleton>
|
||||||
|
<a-descriptions v-else :column="1">
|
||||||
|
<a-descriptions-item label="存储路径">{{ item.bucketName }}</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="访问路径">{{ item.domain }}</a-descriptions-item>
|
||||||
|
</a-descriptions>
|
||||||
|
</template>
|
||||||
|
</CardBlock>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import CardAdd from './components/CardAdd.vue'
|
||||||
|
import CardBlock from './components/CardBlock.vue'
|
||||||
|
import type { StorageResp } from '@/apis'
|
||||||
|
import has from '@/utils/has'
|
||||||
|
|
||||||
|
defineProps({
|
||||||
|
loading: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
type: Array<StorageResp>,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'save-success'): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const search = () => {
|
||||||
|
emit('save-success')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
|
||||||
|
</style>
|
80
src/views/system/storage/StorageOss.vue
Normal file
80
src/views/system/storage/StorageOss.vue
Normal file
@@ -0,0 +1,80 @@
|
|||||||
|
<template>
|
||||||
|
<div class="list-wrap">
|
||||||
|
<a-row class="list-row" :gutter="24">
|
||||||
|
<a-col
|
||||||
|
v-if="has.hasPermOr(['system:storage:add'])"
|
||||||
|
:xs="24"
|
||||||
|
:sm="24"
|
||||||
|
:md="12"
|
||||||
|
:lg="8"
|
||||||
|
:xl="8"
|
||||||
|
:xxl="6"
|
||||||
|
class="list-col"
|
||||||
|
style="min-height: 162px"
|
||||||
|
>
|
||||||
|
<CardAdd :type="2" @save-success="search" />
|
||||||
|
</a-col>
|
||||||
|
<a-empty v-if="!data.length && !has.hasPermOr(['system:storage:add'])" />
|
||||||
|
<a-col
|
||||||
|
v-for="item in data"
|
||||||
|
:key="item.id"
|
||||||
|
:xs="24"
|
||||||
|
:sm="24"
|
||||||
|
:md="12"
|
||||||
|
:lg="8"
|
||||||
|
:xl="8"
|
||||||
|
:xxl="6"
|
||||||
|
class="list-col"
|
||||||
|
style="min-height: 162px"
|
||||||
|
>
|
||||||
|
<CardBlock
|
||||||
|
:loading="loading"
|
||||||
|
:data="item"
|
||||||
|
@save-success="search"
|
||||||
|
>
|
||||||
|
<template #content>
|
||||||
|
<a-skeleton v-if="loading" :animation="true">
|
||||||
|
<a-skeleton-line :widths="['60%']" :rows="3" />
|
||||||
|
</a-skeleton>
|
||||||
|
<a-descriptions v-else :column="1">
|
||||||
|
<a-descriptions-item label="Access Key"><CellCopy :content="item.accessKey" /></a-descriptions-item>
|
||||||
|
<a-descriptions-item label="Endpoint">{{ item.endpoint }}</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="Bucket">{{ item.bucketName }}</a-descriptions-item>
|
||||||
|
<a-descriptions-item label="自定义域名">{{ item.domain }}</a-descriptions-item>
|
||||||
|
</a-descriptions>
|
||||||
|
</template>
|
||||||
|
</CardBlock>
|
||||||
|
</a-col>
|
||||||
|
</a-row>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script setup lang="ts">
|
||||||
|
import CardAdd from './components/CardAdd.vue'
|
||||||
|
import CardBlock from './components/CardBlock.vue'
|
||||||
|
import type { StorageResp } from '@/apis'
|
||||||
|
import has from '@/utils/has'
|
||||||
|
|
||||||
|
defineProps({
|
||||||
|
loading: {
|
||||||
|
type: Boolean,
|
||||||
|
default: false,
|
||||||
|
},
|
||||||
|
data: {
|
||||||
|
type: Array<StorageResp>,
|
||||||
|
default: () => [],
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'save-success'): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const search = () => {
|
||||||
|
emit('save-success')
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="scss">
|
||||||
|
|
||||||
|
</style>
|
92
src/views/system/storage/components/CardAdd.vue
Normal file
92
src/views/system/storage/components/CardAdd.vue
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
<template>
|
||||||
|
<a-card
|
||||||
|
:bordered="true"
|
||||||
|
size="small"
|
||||||
|
class="card-block add-card"
|
||||||
|
:class="{ 'card-large': type === 2 }"
|
||||||
|
@click="onAdd"
|
||||||
|
>
|
||||||
|
<div class="content">
|
||||||
|
<div class="add-icon">
|
||||||
|
<icon-plus />
|
||||||
|
</div>
|
||||||
|
<div class="description">点击创建{{ type === 1 ? '本地存储' : '对象存储' }}</div>
|
||||||
|
</div>
|
||||||
|
</a-card>
|
||||||
|
|
||||||
|
<StorageAddModal ref="StorageAddModalRef" @save-success="search" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import StorageAddModal from '../StorageAddModal.vue'
|
||||||
|
|
||||||
|
const props = defineProps({
|
||||||
|
type: {
|
||||||
|
type: Number,
|
||||||
|
default: 1,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'save-success'): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const search = () => {
|
||||||
|
emit('save-success')
|
||||||
|
}
|
||||||
|
|
||||||
|
const StorageAddModalRef = ref<InstanceType<typeof StorageAddModal>>()
|
||||||
|
// 新增
|
||||||
|
const onAdd = () => {
|
||||||
|
StorageAddModalRef.value?.onAdd(props.type)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.card-block {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
|
||||||
|
:deep(.arco-card-header) {
|
||||||
|
border-bottom: none;
|
||||||
|
height: auto;
|
||||||
|
padding: 16px;
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.arco-descriptions-item-value) {
|
||||||
|
color: var(--color-text-2);
|
||||||
|
padding-left: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
height: 48px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.add-card {
|
||||||
|
text-align: center;
|
||||||
|
cursor: pointer;
|
||||||
|
|
||||||
|
.add-icon {
|
||||||
|
font-size: 22px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.description {
|
||||||
|
margin-top: 16px;
|
||||||
|
color: var(--color-text-3);
|
||||||
|
font-weight: 400;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.arco-card-body) {
|
||||||
|
padding-top: 45px;
|
||||||
|
padding-bottom: 63px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-large {
|
||||||
|
:deep(.arco-card-body) {
|
||||||
|
padding-top: 65px;
|
||||||
|
padding-bottom: 73px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
283
src/views/system/storage/components/CardBlock.vue
Normal file
283
src/views/system/storage/components/CardBlock.vue
Normal file
@@ -0,0 +1,283 @@
|
|||||||
|
<template>
|
||||||
|
<a-card
|
||||||
|
:bordered="true"
|
||||||
|
size="small"
|
||||||
|
class="card-block"
|
||||||
|
>
|
||||||
|
<template v-if="loading" #title>
|
||||||
|
<a-skeleton :animation="true" class="card-block-skeleton">
|
||||||
|
<a-skeleton-line :widths="['40%']" :rows="1" />
|
||||||
|
</a-skeleton>
|
||||||
|
</template>
|
||||||
|
<template v-else #title>
|
||||||
|
<div class="title">
|
||||||
|
{{ data.name }} ({{ data.code }})
|
||||||
|
<div v-if="data.isDefault" class="status">
|
||||||
|
<a-tag size="small" color="arcoblue">
|
||||||
|
<template #icon>
|
||||||
|
<icon-check-circle-fill />
|
||||||
|
</template>
|
||||||
|
<span>默认存储</span>
|
||||||
|
</a-tag>
|
||||||
|
</div>
|
||||||
|
<div v-if="has.hasPermOr(['system:storage:setDefault', 'system:storage:update', 'system:storage:delete'])" class="more">
|
||||||
|
<a-dropdown>
|
||||||
|
<icon-more />
|
||||||
|
<template #content>
|
||||||
|
<a-doption
|
||||||
|
v-permission="['system:storage:setDefault']"
|
||||||
|
:disabled="data.isDefault"
|
||||||
|
:title="data.isDefault ? '该存储已设为默认存储' : ''"
|
||||||
|
@click="onSetDefault(data)"
|
||||||
|
>
|
||||||
|
<icon-check-circle />
|
||||||
|
设为默认
|
||||||
|
</a-doption>
|
||||||
|
<a-doption v-permission="['system:storage:update']" @click="onUpdate(data)">
|
||||||
|
<icon-edit />
|
||||||
|
修改
|
||||||
|
</a-doption>
|
||||||
|
<a-doption
|
||||||
|
v-permission="['system:storage:delete']"
|
||||||
|
class="danger"
|
||||||
|
:disabled="data.isDefault"
|
||||||
|
:title="data.isDefault ? '不允许删除默认存储' : ''"
|
||||||
|
@click="onDelete(data)"
|
||||||
|
>
|
||||||
|
<icon-delete />
|
||||||
|
删除
|
||||||
|
</a-doption>
|
||||||
|
</template>
|
||||||
|
</a-dropdown>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="time">{{ data.createTime }}</div>
|
||||||
|
</template>
|
||||||
|
<div :class="data.type === 1 ? 'content' : 'content-large'">
|
||||||
|
<slot name="content"></slot>
|
||||||
|
</div>
|
||||||
|
<div class="extra">
|
||||||
|
<a-skeleton v-if="loading" :animation="true">
|
||||||
|
<a-skeleton-line :rows="2" />
|
||||||
|
</a-skeleton>
|
||||||
|
<a-switch
|
||||||
|
v-else
|
||||||
|
v-model="status"
|
||||||
|
:disabled="!has.hasPermOr(['system:storage:updateStatus']) || data.isDefault"
|
||||||
|
:title="data.isDefault ? '不允许禁用默认存储' : ''"
|
||||||
|
:loading="switchLoading"
|
||||||
|
:checked-value="1"
|
||||||
|
:unchecked-value="2"
|
||||||
|
:before-change="onUpdateStatus"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</a-card>
|
||||||
|
|
||||||
|
<StorageAddModal ref="StorageAddModalRef" @save-success="search" />
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<script lang="ts" setup>
|
||||||
|
import { Message, Modal } from '@arco-design/web-vue'
|
||||||
|
import StorageAddModal from '../StorageAddModal.vue'
|
||||||
|
import has from '@/utils/has'
|
||||||
|
import { type StorageResp, deleteStorage, setDefaultStorage, updateStorageStatus } from '@/apis/system'
|
||||||
|
import { useDict } from '@/hooks/app'
|
||||||
|
|
||||||
|
interface Props {
|
||||||
|
loading: boolean
|
||||||
|
data: StorageResp
|
||||||
|
}
|
||||||
|
|
||||||
|
const props = withDefaults(defineProps<Props>(), {})
|
||||||
|
|
||||||
|
const emit = defineEmits<{
|
||||||
|
(e: 'save-success'): void
|
||||||
|
}>()
|
||||||
|
|
||||||
|
const search = () => {
|
||||||
|
emit('save-success')
|
||||||
|
}
|
||||||
|
|
||||||
|
const { storage_type_enum } = useDict('storage_type_enum')
|
||||||
|
const storageType = computed(() => {
|
||||||
|
return storage_type_enum.value.find((item) => item.value === props.data.type)?.label || '本地存储'
|
||||||
|
})
|
||||||
|
|
||||||
|
const status = ref(props.data.status)
|
||||||
|
const switchLoading = ref(false)
|
||||||
|
// 更新状态
|
||||||
|
const onUpdateStatus = async (newValue: number) => {
|
||||||
|
const tip = newValue === 1 ? '启用' : '禁用'
|
||||||
|
switchLoading.value = true
|
||||||
|
Modal.warning({
|
||||||
|
title: '提示',
|
||||||
|
content: `是否确定${tip}${storageType.value}「${props.data.name}(${props.data.code})]?`,
|
||||||
|
hideCancel: false,
|
||||||
|
maskClosable: false,
|
||||||
|
onCancel: async () => {
|
||||||
|
switchLoading.value = false
|
||||||
|
status.value = newValue === 1 ? 2 : 1
|
||||||
|
},
|
||||||
|
onBeforeOk: async () => {
|
||||||
|
try {
|
||||||
|
const res = await updateStorageStatus({
|
||||||
|
status: newValue,
|
||||||
|
}, props.data.id)
|
||||||
|
if (res.success) {
|
||||||
|
Message.success(`${tip}成功`)
|
||||||
|
search()
|
||||||
|
}
|
||||||
|
return res.success
|
||||||
|
} catch (error) {
|
||||||
|
status.value = newValue === 1 ? 2 : 1
|
||||||
|
return false
|
||||||
|
} finally {
|
||||||
|
switchLoading.value = false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设为默认
|
||||||
|
const onSetDefault = (record: StorageResp) => {
|
||||||
|
Modal.warning({
|
||||||
|
title: '提示',
|
||||||
|
content: `是否确定将${storageType.value}「${record.name}(${record.code})」设为默认存储?`,
|
||||||
|
hideCancel: false,
|
||||||
|
maskClosable: false,
|
||||||
|
onBeforeOk: async () => {
|
||||||
|
try {
|
||||||
|
const res = await setDefaultStorage(record.id)
|
||||||
|
if (res.success) {
|
||||||
|
Message.success('设置成功')
|
||||||
|
search()
|
||||||
|
}
|
||||||
|
return res.success
|
||||||
|
} catch (error) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 删除
|
||||||
|
const onDelete = (record: StorageResp) => {
|
||||||
|
Modal.warning({
|
||||||
|
title: '提示',
|
||||||
|
content: `是否确定删除存储「${record.name}(${record.code})」?`,
|
||||||
|
okButtonProps: { status: 'danger' },
|
||||||
|
hideCancel: false,
|
||||||
|
maskClosable: false,
|
||||||
|
onBeforeOk: async () => {
|
||||||
|
try {
|
||||||
|
const res = await deleteStorage(record.id)
|
||||||
|
if (res.success) {
|
||||||
|
Message.success('删除成功')
|
||||||
|
search()
|
||||||
|
}
|
||||||
|
return res.success
|
||||||
|
} catch (error) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const StorageAddModalRef = ref<InstanceType<typeof StorageAddModal>>()
|
||||||
|
|
||||||
|
// 修改
|
||||||
|
const onUpdate = (record: StorageResp) => {
|
||||||
|
StorageAddModalRef.value?.onUpdate(record.id)
|
||||||
|
}
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<style scoped lang="less">
|
||||||
|
.card-block {
|
||||||
|
margin-bottom: 16px;
|
||||||
|
|
||||||
|
:deep(.arco-card-header) {
|
||||||
|
border-bottom: none;
|
||||||
|
height: auto;
|
||||||
|
padding: 16px;
|
||||||
|
padding-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.title {
|
||||||
|
display: flex;
|
||||||
|
line-height: 24px;
|
||||||
|
align-items: center;
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
|
||||||
|
.icon {
|
||||||
|
height: 24px;
|
||||||
|
width: 24px;
|
||||||
|
color: var(--color-white);
|
||||||
|
background: #626aea;
|
||||||
|
text-align: center;
|
||||||
|
line-height: 24px;
|
||||||
|
border-radius: 50%;
|
||||||
|
margin-right: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.status {
|
||||||
|
margin-left: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.more {
|
||||||
|
color: var(--color-text-4);
|
||||||
|
font-size: 16px;
|
||||||
|
position: absolute;
|
||||||
|
right: 16px;
|
||||||
|
cursor: pointer;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.time,
|
||||||
|
.content > :deep(.arco-typography),
|
||||||
|
:deep(.arco-descriptions-item-label),
|
||||||
|
:deep(.arco-descriptions-item-value) {
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 400;
|
||||||
|
color: var(--color-text-3);
|
||||||
|
padding: 0;
|
||||||
|
line-height: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.arco-descriptions-item-value) {
|
||||||
|
color: var(--color-text-2);
|
||||||
|
padding-left: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content {
|
||||||
|
height: 48px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.content-large {
|
||||||
|
height: 78px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.extra {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row-reverse;
|
||||||
|
}
|
||||||
|
|
||||||
|
&-skeleton {
|
||||||
|
:deep(.arco-skeleton-content .arco-skeleton-text-row:not(:last-child)) {
|
||||||
|
height: 14px;
|
||||||
|
margin-bottom: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.card-block:hover {
|
||||||
|
box-shadow: 4px 4px 10px rgba(0, 0, 0, 10%);
|
||||||
|
|
||||||
|
.title {
|
||||||
|
.more {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
</style>
|
@@ -1,153 +1,88 @@
|
|||||||
<template>
|
<template>
|
||||||
<div class="gi_table_page">
|
<div class="gi_table_page">
|
||||||
<GiTable
|
<a-tabs v-model:active-key="activeKey" type="rounded" @change="change">
|
||||||
title=""
|
<a-tab-pane key="all">
|
||||||
row-key="id"
|
<template #title>全部</template>
|
||||||
:data="dataList"
|
<a-card title="本地存储" :bordered="false" class="gi_card_title">
|
||||||
:columns="columns"
|
<StorageLocal :data="dataMap['1']" :loading="loading" @save-success="getDataList" />
|
||||||
:loading="loading"
|
</a-card>
|
||||||
:scroll="{ x: '100%', y: '100%', minWidth: 1300 }"
|
<a-card title="对象存储" :bordered="false" class="gi_card_title">
|
||||||
:pagination="pagination"
|
<StorageOss :data="dataMap['2']" :loading="loading" @save-success="getDataList" />
|
||||||
:disabled-tools="['size']"
|
</a-card>
|
||||||
:disabled-column-keys="['name']"
|
</a-tab-pane>
|
||||||
@refresh="search"
|
<a-tab-pane key="1">
|
||||||
>
|
<template #title>本地存储</template>
|
||||||
<template #toolbar-left>
|
<StorageLocal :data="dataMap['1']" :loading="loading" @save-success="getDataList" />
|
||||||
<a-input-search v-model="queryForm.description" placeholder="搜索名称/编码/描述" allow-clear @search="search" />
|
</a-tab-pane>
|
||||||
<a-select
|
<a-tab-pane key="2">
|
||||||
v-model="queryForm.status"
|
<template #title>对象存储</template>
|
||||||
:options="DisEnableStatusList"
|
<StorageOss :data="dataMap['2']" :loading="loading" @save-success="getDataList" />
|
||||||
placeholder="请选择状态"
|
</a-tab-pane>
|
||||||
|
<template #extra>
|
||||||
|
<a-input-search
|
||||||
|
v-model="queryForm.description"
|
||||||
|
placeholder="搜索名称/编码"
|
||||||
|
style="width: 240px;"
|
||||||
allow-clear
|
allow-clear
|
||||||
style="width: 150px"
|
@search="getDataList"
|
||||||
@change="search"
|
|
||||||
/>
|
/>
|
||||||
<a-button @click="reset">
|
|
||||||
<template #icon><icon-refresh /></template>
|
|
||||||
<template #default>重置</template>
|
|
||||||
</a-button>
|
|
||||||
</template>
|
</template>
|
||||||
<template #toolbar-right>
|
</a-tabs>
|
||||||
<a-button v-permission="['system:storage:add']" type="primary" @click="onAdd">
|
|
||||||
<template #icon><icon-plus /></template>
|
|
||||||
<template #default>新增</template>
|
|
||||||
</a-button>
|
|
||||||
</template>
|
|
||||||
<template #name="{ record }">
|
|
||||||
<a-space fill>
|
|
||||||
<span>{{ record.name }}</span>
|
|
||||||
<a-tag v-if="record.isDefault" color="arcoblue" size="small" class="gi_round">
|
|
||||||
<template #default>默认</template>
|
|
||||||
</a-tag>
|
|
||||||
</a-space>
|
|
||||||
</template>
|
|
||||||
<template #type="{ record }">
|
|
||||||
<GiCellTag :value="record.type" :dict="storage_type_enum" />
|
|
||||||
</template>
|
|
||||||
<template #status="{ record }">
|
|
||||||
<GiCellStatus :status="record.status" />
|
|
||||||
</template>
|
|
||||||
<template #action="{ record }">
|
|
||||||
<a-space>
|
|
||||||
<a-link v-permission="['system:storage:update']" title="修改" @click="onUpdate(record)">修改</a-link>
|
|
||||||
<a-link
|
|
||||||
v-permission="['system:storage:delete']"
|
|
||||||
status="danger"
|
|
||||||
:disabled="record.isDefault"
|
|
||||||
:title="record.isDefault ? '默认存储不能删除' : '删除'"
|
|
||||||
@click="onDelete(record)"
|
|
||||||
>
|
|
||||||
删除
|
|
||||||
</a-link>
|
|
||||||
</a-space>
|
|
||||||
</template>
|
|
||||||
</GiTable>
|
|
||||||
|
|
||||||
<StorageAddDrawer ref="StorageAddDrawerRef" @save-success="search" />
|
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script setup lang="ts">
|
<script setup lang="ts">
|
||||||
import StorageAddDrawer from './StorageAddDrawer.vue'
|
import { groupBy } from 'xe-utils'
|
||||||
import { type StorageQuery, type StorageResp, deleteStorage, listStorage } from '@/apis/system/storage'
|
import StorageLocal from './StorageLocal.vue'
|
||||||
import type { TableInstanceColumns } from '@/components/GiTable/type'
|
import StorageOss from './StorageOss.vue'
|
||||||
import { DisEnableStatusList } from '@/constant/common'
|
import { type StorageQuery, type StorageResp, listStorage } from '@/apis'
|
||||||
import { useTable } from '@/hooks'
|
|
||||||
import { useDict } from '@/hooks/app'
|
|
||||||
import { isMobile } from '@/utils'
|
|
||||||
import has from '@/utils/has'
|
|
||||||
|
|
||||||
defineOptions({ name: 'SystemStorage' })
|
defineOptions({ name: 'SystemStorage' })
|
||||||
|
|
||||||
const { storage_type_enum } = useDict('storage_type_enum')
|
|
||||||
|
|
||||||
const queryForm = reactive<StorageQuery>({
|
const queryForm = reactive<StorageQuery>({
|
||||||
sort: ['createTime,desc'],
|
sort: ['createTime,desc'],
|
||||||
})
|
})
|
||||||
|
|
||||||
const {
|
const loading = ref(false)
|
||||||
tableData: dataList,
|
const dataMap = ref<Record<string, StorageResp[]>>({})
|
||||||
loading,
|
// 查询列表
|
||||||
pagination,
|
const getDataList = async () => {
|
||||||
search,
|
try {
|
||||||
handleDelete,
|
loading.value = true
|
||||||
} = useTable((page) => listStorage({ ...queryForm, ...page }), { immediate: true })
|
const { data } = await listStorage(queryForm)
|
||||||
const columns: TableInstanceColumns[] = [
|
dataMap.value = groupBy(data, 'type')
|
||||||
{
|
} finally {
|
||||||
title: '序号',
|
loading.value = false
|
||||||
width: 66,
|
}
|
||||||
align: 'center',
|
|
||||||
render: ({ rowIndex }) => h('span', {}, rowIndex + 1 + (pagination.current - 1) * pagination.pageSize),
|
|
||||||
},
|
|
||||||
{ title: '名称', dataIndex: 'name', slotName: 'name', width: 140, ellipsis: true, tooltip: true },
|
|
||||||
{ title: '编码', dataIndex: 'code', ellipsis: true, tooltip: true },
|
|
||||||
{ title: '状态', dataIndex: 'status', slotName: 'status', align: 'center' },
|
|
||||||
{ title: '类型', dataIndex: 'type', slotName: 'type', align: 'center', ellipsis: true, tooltip: true },
|
|
||||||
{ title: '访问密钥', dataIndex: 'accessKey', ellipsis: true, tooltip: true },
|
|
||||||
{ title: '终端节点', dataIndex: 'endpoint', ellipsis: true, tooltip: true },
|
|
||||||
{ title: '桶名称', dataIndex: 'bucketName', ellipsis: true, tooltip: true },
|
|
||||||
{ title: '域名', dataIndex: 'domain', ellipsis: true, tooltip: true },
|
|
||||||
{ title: '描述', dataIndex: 'description', ellipsis: true, tooltip: true },
|
|
||||||
{ title: '创建人', dataIndex: 'createUserString', ellipsis: true, tooltip: true, show: false },
|
|
||||||
{ title: '创建时间', dataIndex: 'createTime', width: 180 },
|
|
||||||
{ title: '修改人', dataIndex: 'updateUserString', ellipsis: true, tooltip: true, show: false },
|
|
||||||
{ title: '修改时间', dataIndex: 'updateTime', width: 180, show: false },
|
|
||||||
{
|
|
||||||
title: '操作',
|
|
||||||
dataIndex: 'action',
|
|
||||||
slotName: 'action',
|
|
||||||
width: 130,
|
|
||||||
align: 'center',
|
|
||||||
fixed: !isMobile() ? 'right' : undefined,
|
|
||||||
show: has.hasPermOr(['system:storage:update', 'system:storage:delete']),
|
|
||||||
},
|
|
||||||
]
|
|
||||||
|
|
||||||
// 重置
|
|
||||||
const reset = () => {
|
|
||||||
queryForm.description = undefined
|
|
||||||
queryForm.status = undefined
|
|
||||||
search()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 删除
|
const activeKey = ref('all')
|
||||||
const onDelete = (record: StorageResp) => {
|
const change = (key: string | number) => {
|
||||||
return handleDelete(() => deleteStorage(record.id), {
|
activeKey.value = key as string
|
||||||
content: `是否确定删除存储「${record.name}」?`,
|
queryForm.type = key === 'all' ? undefined : key
|
||||||
showModal: true,
|
getDataList()
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const StorageAddDrawerRef = ref<InstanceType<typeof StorageAddDrawer>>()
|
onMounted(() => {
|
||||||
// 新增
|
getDataList()
|
||||||
const onAdd = () => {
|
})
|
||||||
StorageAddDrawerRef.value?.onAdd()
|
|
||||||
}
|
|
||||||
|
|
||||||
// 修改
|
|
||||||
const onUpdate = (record: StorageResp) => {
|
|
||||||
StorageAddDrawerRef.value?.onUpdate(record.id)
|
|
||||||
}
|
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped lang="scss"></style>
|
<style scoped lang="scss">
|
||||||
|
.gi_table_page {
|
||||||
|
overflow-y: auto;
|
||||||
|
|
||||||
|
:deep(.arco-tabs) {
|
||||||
|
overflow: visible;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.block-title {
|
||||||
|
font-size: 14px;
|
||||||
|
margin-bottom: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
:deep(.gi_card_title > .arco-card-body) {
|
||||||
|
padding: 0;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
@@ -1,14 +0,0 @@
|
|||||||
export interface StorageReq {
|
|
||||||
name: string
|
|
||||||
code: string
|
|
||||||
type: number
|
|
||||||
accessKey: string
|
|
||||||
secretKey: string
|
|
||||||
endpoint: string
|
|
||||||
bucketName: string
|
|
||||||
domain: string
|
|
||||||
sort: number
|
|
||||||
description: string
|
|
||||||
isDefault: boolean
|
|
||||||
status: 1 | 2
|
|
||||||
}
|
|
Reference in New Issue
Block a user