Feature: add remote file delete , picBed management

First version of PicList.
In album, you can delete remote file now.
Add picBed management
function.
This commit is contained in:
萌萌哒赫萝
2023-02-15 23:36:47 +08:00
parent 7421322475
commit efeadb8fb8
355 changed files with 12428 additions and 883 deletions

View File

@@ -0,0 +1,146 @@
import { v4 as uuidv4 } from 'uuid'
import path from 'path'
import crypto from 'crypto'
import { availableIconList } from './icon'
export function randomStringGenerator (length: number): string {
const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'
return Array.from({ length }).map(() => chars.charAt(Math.floor(Math.random() * chars.length))).join('')
}
export function renameFileNameWithTimestamp (oldName: string): string {
return `${Math.floor(Date.now() / 1000)}${randomStringGenerator(5)}${path.extname(oldName)}`
}
export function renameFileNameWithRandomString (oldName: string, length: number = 5): string {
return `${randomStringGenerator(length)}${path.extname(oldName)}`
}
export function renameFileNameWithCustomString (oldName: string, customFormat: string): string {
const conversionMap : {[key: string]: () => string} = {
'{Y}': () => new Date().getFullYear().toString(),
'{y}': () => new Date().getFullYear().toString().slice(2),
'{m}': () => (new Date().getMonth() + 1).toString().length === 1 ? `0${new Date().getMonth() + 1}` : (new Date().getMonth() + 1).toString(),
'{d}': () => new Date().getDate().toString().length === 1 ? `0${new Date().getDate()}` : new Date().getDate().toString(),
'{md5}': () => crypto.createHash('md5').update(path.basename(oldName, path.extname(oldName))).digest('hex'),
'{md5-16}': () => crypto.createHash('md5').update(path.basename(oldName, path.extname(oldName))).digest('hex').slice(0, 16),
'{str-10}': () => randomStringGenerator(10),
'{str-20}': () => randomStringGenerator(20),
'{filename}': () => path.basename(oldName, path.extname(oldName)),
'{uuid}': () => uuidv4().replace(/-/g, ''),
'{timestamp}': () => Math.floor(Date.now() / 1000).toString()
}
if (customFormat === undefined || !Object.keys(conversionMap).some(item => customFormat.includes(item))) {
return oldName
}
const ext = path.extname(oldName)
return Object.keys(conversionMap).reduce((acc, cur) => {
return acc.replace(cur, conversionMap[cur]())
}, customFormat) + ext
}
export function renameFile (typeMap : IStringKeyMap, oldName: string): string {
if (typeMap.timestampRename) {
return renameFileNameWithTimestamp(oldName)
} else if (typeMap.randomStringRename) {
return renameFileNameWithRandomString(oldName, 20)
} else {
return renameFileNameWithCustomString(oldName, typeMap.customRenameFormat)
}
}
export function formatLink (url: string, fileName: string, type: string, format?: string) : string {
switch (type) {
case 'markdown':
return `![${fileName}](${url})`
case 'html':
return `<img src="${url}" alt="${fileName}"/>`
case 'bbcode':
return `[img]${url}[/img]`
case 'url':
return url
case 'markdown-with-link':
return `[![${fileName}](${url})](${url})`
case 'custom':
if (format && (format.includes('$url') || format.includes('$fileName'))) {
return format.replace(/\$url/g, url).replace(/\$fileName/g, fileName)
}
return url
default:
return url
}
}
export function getFileIconPath (fileName: string) {
const ext = path.extname(fileName).slice(1)
return availableIconList.includes(ext) ? `${ext}.png` : 'unknown.png'
}
export function formatFileSize (size: number) {
if (size === 0) return ''
const units = ['B', 'KB', 'MB', 'GB', 'TB', 'PB']
const index = Math.floor(Math.log2(size) / 10)
return `${(size / Math.pow(2, index * 10)).toFixed(2)} ${units[index]}`
}
export function formatFileName (fileName: string) {
const ext = path.extname(fileName)
const name = path.basename(fileName, ext)
return name.length > 20 ? `${name.slice(0, 20)}...${ext}` : fileName
}
export function getExtension (fileName: string) {
return path.extname(fileName).slice(1)
}
export function isImage (fileName: string) {
return ['jpg', 'jpeg', 'png', 'gif', 'webp', 'bmp', 'ico'].includes(getExtension(fileName))
}
export function formObjToTableData (obj: any) {
const exclude = [undefined, null, '', 'transformedConfig']
return Object.keys(obj).filter(key => !exclude.includes(obj[key])).map(key => ({
key,
value: typeof obj[key] === 'object' ? JSON.stringify(obj[key]) : obj[key]
})).sort((a, b) => a.key.localeCompare(b.key))
}
export function isValidUrl (str: string) {
const pattern = new RegExp(
'^([a-zA-Z]+:\\/\\/)?' +
'((([a-z\\d]([a-z\\d-]*[a-z\\d])*)\\.)+[a-z]{2,}|' +
'((\\d{1,3}\\.){3}\\d{1,3}))' +
'(\\:\\d+)?(\\/[-a-z\\d%_.~+]*)*' +
'(\\?[;&a-z\\d%_.~+=-]*)?' +
'(\\#[-a-z\\d_]*)?$',
'i'
)
return pattern.test(str)
}
export interface IHTTPProxy {
host: string
port: number
protocol: string
}
export const formatHttpProxy = (proxy: string | undefined, type: 'object' | 'string'): IHTTPProxy | undefined | string => {
if (proxy === undefined || proxy === '') return undefined
if (proxy.startsWith('http://') || proxy.startsWith('https://')) {
const { protocol, hostname, port } = new URL(proxy)
if (type === 'string') return `${protocol}//${hostname}:${port}`
return {
host: hostname,
port: Number(port),
protocol: protocol.slice(0, -1)
}
} else {
const [host, port] = proxy.split(':')
if (type === 'string') return `http://${host}:${port}`
return {
host,
port: port ? Number(port) : 80,
protocol: 'http'
}
}
}

View File

@@ -0,0 +1,501 @@
const defaultBaseRule = (name: string) => {
return [
{
required: true,
message: `请输入${name}`,
trigger: 'blur'
}
]
}
const itemsPerPageRule = [
{
required: true,
message: '请输入每页显示数量',
trigger: 'blur'
},
{
type: 'number',
message: '每页显示数量必须为数字',
trigger: 'change'
},
{
validator: (rule: any, value: any, callback: any) => {
if (value < 20 || value > 1000) {
callback(new Error('每页显示数量必须在20-1000之间'))
} else {
callback()
}
},
trigger: 'change'
}
]
const aliasRule = [
{
required: true,
message: '请输入配置别名, 该配置的唯一标识',
trigger: 'blur'
},
{
validator: (rule: any, value: any, callback: any) => {
const reg = /^[\u4e00-\u9fa5_a-zA-Z0-9-]{1,15}$/
if (!reg.test(value)) {
callback(new Error('配置别名只能包含中文、英文、数字和下划线且不能超过15个字符'))
} else {
callback()
}
},
trigger: 'change'
}
]
export const supportedPicBedList: IStringKeyMap = {
smms: {
name: 'SM.MS',
icon: 'smms',
configOptions: {
alias: {
required: true,
description: '配置别名-必需',
placeholder: '该配置的唯一标识',
type: 'string',
rule: aliasRule,
default: 'smms-A'
},
token: {
required: true,
description: 'token-必需',
placeholder: '请输入token',
type: 'string',
rule: defaultBaseRule('token')
},
paging: {
required: true,
description: '是否分页',
default: true,
type: 'boolean'
}
},
explain: '大陆地区请访问备用域名https://smms.app, 请勿大批量上传图片否则API接口会被限制',
options: ['alias', 'token', 'paging'],
refLink: 'https://pichoro.horosama.com/#/PicHoroDocs/configure?id=%e5%8f%82%e6%95%b0%e8%af%b4%e6%98%8e-6',
referenceText: '配置教程请参考:'
},
qiniu: {
name: '七牛云',
icon: 'qiniu',
configOptions: {
alias: {
required: true,
description: '配置别名-必需',
placeholder: '该配置的唯一标识',
type: 'string',
rule: aliasRule,
default: 'qiniu-A'
},
accessKey: {
required: true,
description: 'accessKey-必需',
placeholder: '请输入accessKey',
type: 'string',
rule: defaultBaseRule('accessKey')
},
secretKey: {
required: true,
description: 'secretKey-必需',
placeholder: '请输入secretKey',
type: 'string',
rule: defaultBaseRule('secretKey')
},
bucketName: {
required: false,
description: '空间名-可选',
placeholder: '英文逗号分隔例如bucket1,bucket2',
type: 'string'
},
baseDir: {
required: false,
description: '起始目录-可选',
placeholder: '英文逗号分隔,例如:/test1,/test2',
default: '/',
type: 'string'
},
paging: {
required: true,
description: '是否分页',
default: true,
type: 'boolean'
},
itemsPerPage: {
required: true,
description: '每页显示数量',
default: 50,
type: 'number',
rule: itemsPerPageRule
}
},
explain: '空间名和起始目录配置时可通过英文逗号分隔不同存储桶的设置,顺序必须一致,逗号间留空或缺失项使用默认值',
options: ['alias', 'accessKey', 'secretKey', 'bucketName', 'baseDir', 'paging', 'itemsPerPage'],
refLink: 'https://pichoro.horosama.com/#/PicHoroDocs/configure?id=%e5%8f%82%e6%95%b0%e8%af%b4%e6%98%8e-3',
referenceText: '配置教程请参考:'
},
github: {
name: 'GitHub',
icon: 'github',
configOptions: {
alias: {
required: true,
description: '配置别名-必需',
placeholder: '该配置的唯一标识',
type: 'string',
rule: aliasRule,
default: 'github-A'
},
token: {
required: true,
description: 'token-必需',
placeholder: '请输入token',
type: 'string',
rule: defaultBaseRule('token')
},
githubUsername: {
required: true,
description: '用户名-必需',
placeholder: '请输入用户名',
type: 'string',
rule: defaultBaseRule('用户名')
},
proxy: {
required: false,
description: '代理-可选',
placeholder: '例如http://127.0.0.1:1080',
type: 'string'
},
paging: {
required: true,
description: '是否分页',
default: false,
type: 'boolean'
},
customUrl: {
required: false,
description: 'CDN加速域名-可选;例如: https://cdn.staticaly.com/gh/{username}/{repo}@{branch}/{path}',
placeholder: '支持使用{username}、{repo}、{branch}和{path}作为替换占位符,用于适配不同仓库和分支',
type: 'string',
rule: [
{
validator: (_rule: any, value: any, callback: any) => {
if (value) {
const customUrlList = value.split(',')
const customUrlValid = customUrlList.every((customUrl: string) => {
const reg = /^((https|http)?:\/\/)/
if (customUrl === '') {
return true
} else if (!reg.test(customUrl)) {
return false
}
return true
})
const isBracketsValid = customUrlList.every((customUrl: string) => {
const bracketPaired = (str: string) => {
const stack = []
for (let i = 0; i < str.length; i++) {
if (str[i] === '{') {
stack.push(str[i])
} else if (str[i] === '}') {
if (stack.length === 0) {
return false
}
stack.pop()
}
}
return stack.length === 0
}
if (customUrl === '') {
return true
} else if (!bracketPaired(customUrl)) {
return false
}
return true
})
if (!customUrlValid) {
callback(new Error('加速域名请以http://或https://开头'))
} else if (!isBracketsValid) {
callback(new Error('加速域名中的大括号必须成对出现'))
} else {
callback()
}
} else {
callback()
}
},
trigger: 'change'
}
]
}
},
explain: 'API调用有每小时上限此外不支持上传超过100M的文件',
options: ['alias', 'token', 'githubUsername', 'proxy', 'customUrl'],
refLink: 'https://pichoro.horosama.com/#/PicHoroDocs/configure?id=%e5%8f%82%e6%95%b0%e8%af%b4%e6%98%8e-9',
referenceText: '配置教程请参考:'
},
aliyun: {
name: '阿里云',
icon: 'aliyun',
configOptions: {
alias: {
required: true,
description: '配置别名-必需',
placeholder: '该配置的唯一标识',
type: 'string',
rule: aliasRule,
default: 'aliyun-A'
},
accessKeyId: {
required: true,
description: 'accessKeyId-必需',
placeholder: '请输入accessKeyId',
type: 'string',
rule: defaultBaseRule('accessKeyId')
},
accessKeySecret: {
required: true,
description: 'accessKeySecret-必需',
placeholder: '请输入accessKeySecret',
type: 'string',
rule: defaultBaseRule('accessKeySecret')
},
bucketName: {
required: false,
description: '存储桶名-可选',
placeholder: '英文逗号分隔例如bucket1,bucket2',
type: 'string'
},
baseDir: {
required: false,
description: '起始目录-可选',
placeholder: '英文逗号分隔,例如:/test1,/test2',
type: 'string',
default: '/'
},
paging: {
required: true,
description: '是否分页',
default: true,
type: 'boolean'
},
itemsPerPage: {
required: true,
description: '每页显示数量',
default: 50,
type: 'number',
rule: itemsPerPageRule
}
},
explain: '存储桶名和起始目录配置时可通过英文逗号分隔不同存储桶的设置,顺序必须一致,逗号间留空或缺失项使用默认值',
options: ['alias', 'accessKeyId', 'accessKeySecret', 'bucketName', 'baseDir', 'paging', 'itemsPerPage'],
refLink: 'https://pichoro.horosama.com/#/PicHoroDocs/configure?id=%e5%8f%82%e6%95%b0%e8%af%b4%e6%98%8e-1',
referenceText: '配置教程请参考:'
},
tcyun: {
name: '腾讯云',
icon: 'tcyun',
configOptions: {
alias: {
required: true,
description: '配置别名-必需',
placeholder: '该配置的唯一标识',
type: 'string',
rule: aliasRule,
default: 'tcyun-A'
},
secretId: {
required: true,
description: 'secretId-必需',
placeholder: '请输入secretId',
type: 'string',
rule: defaultBaseRule('secretId')
},
secretKey: {
required: true,
description: 'secretKey-必需',
placeholder: '请输入secretKey',
type: 'string',
rule: defaultBaseRule('secretKey')
},
appId: {
required: true,
description: 'appId-必需',
placeholder: '请输入appId',
type: 'string',
rule: defaultBaseRule('appId')
},
bucketName: {
required: false,
description: '存储桶名-可选(注意包含AppId)',
placeholder: '英文逗号分隔例如bucket1-1250000000,bucket2-1250000000',
type: 'string'
},
baseDir: {
required: false,
description: '起始目录-可选',
placeholder: '英文逗号分隔,例如:/test1,/test2',
type: 'string',
default: '/'
},
paging: {
required: true,
description: '是否分页',
default: true,
type: 'boolean'
},
itemsPerPage: {
required: true,
description: '每页显示数量',
default: 50,
type: 'number',
rule: itemsPerPageRule
}
},
explain: '存储桶名和起始目录配置时可通过英文逗号分隔不同存储桶的设置,顺序必须一致,逗号间留空或缺失项使用默认值',
options: ['alias', 'secretId', 'secretKey', 'appId', 'bucketName', 'baseDir', 'paging', 'itemsPerPage'],
refLink: 'https://pichoro.horosama.com/#/PicHoroDocs/configure?id=%e5%8f%82%e6%95%b0%e8%af%b4%e6%98%8e-2',
referenceText: '配置教程请参考:'
},
upyun: {
name: '又拍云',
icon: 'upyun',
configOptions: {
alias: {
required: true,
description: '配置别名-必需',
placeholder: '该配置的唯一标识',
type: 'string',
rule: aliasRule,
default: 'upyun-A'
},
bucketName: {
required: true,
description: '服务名-必需',
placeholder: '对应其它对象存储的存储桶名',
type: 'string',
rule: defaultBaseRule('bucketName')
},
operator: {
required: true,
description: '操作员-必需',
placeholder: '推荐使用具有读取、写入和删除完整权限的操作员',
type: 'string',
rule: defaultBaseRule('操作员')
},
password: {
required: true,
description: '操作员密码-必需',
placeholder: '请输入密码',
type: 'string',
rule: defaultBaseRule('操作员密码')
},
baseDir: {
required: false,
description: '起始目录-可选',
placeholder: '读取文件时的初始目录',
type: 'string',
default: '/'
},
customUrl: {
required: true,
description: '加速域名-必需',
placeholder: '请以http://或https://开头',
type: 'string',
rule: [
{
required: true,
message: '加速域名不能为空',
trigger: 'change'
},
{
validator: (rule: any, value: any, callback: any) => {
if (value) {
const customUrlList = value.split(',')
const customUrlValid = customUrlList.every((customUrl: string) => {
const reg = /^((https|http)?:\/\/)/
if (customUrl === '') {
return true
} else if (!reg.test(customUrl)) {
return false
}
return true
})
if (!customUrlValid) {
callback(new Error('自定义域名请以http://或https://开头'))
} else {
callback()
}
} else {
callback()
}
},
trigger: 'change'
}
]
},
paging: {
required: true,
description: '是否分页',
default: true,
type: 'boolean'
},
itemsPerPage: {
required: true,
description: '每页显示数量',
default: 50,
type: 'number',
rule: itemsPerPageRule
}
},
explain: '又拍云图床务必填写加速域名,否则无法正常使用',
options: ['alias', 'bucketName', 'operator', 'password', 'baseDir', 'customUrl', 'paging', 'itemsPerPage'],
refLink: 'https://pichoro.horosama.com/#/PicHoroDocs/configure?id=%e5%8f%82%e6%95%b0%e8%af%b4%e6%98%8e-4',
referenceText: '配置教程请参考:'
},
imgur: {
name: 'Imgur',
icon: 'imgur',
configOptions: {
alias: {
required: true,
description: '配置别名-必需',
placeholder: '该配置的唯一标识',
type: 'string',
rule: aliasRule,
default: 'imgur-A'
},
imgurUserName: {
required: true,
description: 'imgur用户名-必需',
placeholder: '请输入imgur用户名',
type: 'string',
rule: defaultBaseRule('imgurUserName')
},
accessToken: {
required: true,
description: 'accessToken-必需(不是clientID,请参考配置教程)',
placeholder: '请输入accessToken',
type: 'string',
rule: defaultBaseRule('accessToken')
},
proxy: {
required: false,
description: '代理-可选',
placeholder: '例如http://127.0.0.1:1080',
type: 'string'
}
},
explain: '大陆地区请使用代理API调用存在限制请注意使用频率',
options: ['alias', 'imgurUserName', 'accessToken', 'proxy'],
refLink: 'https://pichoro.horosama.com/#/PicHoroDocs/configure?id=imgur%e5%9b%be%e5%ba%8a-1',
referenceText: '配置教程请参考:'
}
}

View File

@@ -0,0 +1,44 @@
import { ipcRenderer, IpcRendererEvent } from 'electron'
import { PICLIST_MANAGE_GET_CONFIG, PICLIST_MANAGE_SAVE_CONFIG, PICLIST_MANAGE_REMOVE_CONFIG } from '~/main/manage/events/constants'
import { v4 as uuid } from 'uuid'
import { getRawData } from '~/renderer/utils/common'
export function getConfig<T> (key?: string): Promise<T | undefined> {
return new Promise((resolve) => {
const callbackId = uuid()
const callback = (event: IpcRendererEvent, config: T | undefined, returnCallbackId: string) => {
if (returnCallbackId === callbackId) {
resolve(config)
ipcRenderer.removeListener(PICLIST_MANAGE_GET_CONFIG, callback)
}
}
ipcRenderer.on(PICLIST_MANAGE_GET_CONFIG, callback)
ipcRenderer.send(PICLIST_MANAGE_GET_CONFIG, key, callbackId)
})
}
export function saveConfig (_config: IObj | string, value?: any) {
let config
if (typeof _config === 'string') {
config = {
[_config]: value
}
} else {
config = getRawData(_config)
}
ipcRenderer.send(PICLIST_MANAGE_SAVE_CONFIG, config)
}
export function removeConfig (key: string, propName: string) {
ipcRenderer.send(PICLIST_MANAGE_REMOVE_CONFIG, key, propName)
}
export function sendToMain (channel: string, ...args: any[]) {
const data = getRawData(args)
ipcRenderer.send(channel, ...data)
}
export function invokeToMain (channel: string, ...args: any[]) {
const data = getRawData(args)
return ipcRenderer.invoke(channel, ...data)
}

View File

@@ -0,0 +1,224 @@
export const availableIconList = [
'_blank',
'_page',
'3g2',
'3gp',
'7z',
'aac',
'accdb',
'adt',
'ai',
'aiff',
'aly',
'amiga',
'amr',
'ape',
'apk',
'arj',
'asf',
'asm',
'asx',
'au',
'avc',
'avi',
'avs',
'bak',
'bas',
'bat',
'bmp',
'bom',
'c',
'cda',
'cdr',
'chm',
'class',
'cmd',
'com',
'cpp',
'css',
'csv',
'dart',
'dat',
'ddb',
'dif',
'divx',
'dll',
'dmg',
'doc',
'docm',
'docx',
'dot',
'dotm',
'dotx',
'dsl',
'dv',
'dvd',
'dvdaudio',
'dwg',
'dxf',
'emf',
'env',
'eot',
'eps',
'exe',
'exif',
'fakesmms',
'flc',
'fli',
'flv',
'folder',
'fon',
'font',
'for',
'fpx',
'fv',
'gif',
'gitingore',
'gitkeep',
'gz',
'h',
'hdri',
'hlp',
'hpp',
'htm',
'html',
'ico',
'ics',
'int',
'ipynb',
'iso',
'java',
'jpeg',
'jpg',
'js',
'json',
'key',
'ksp',
'less',
'lib',
'lic',
'license',
'log',
'lst',
'lua',
'mac',
'map',
'markdown',
'md',
'mdf',
'mht',
'mhtml',
'mid',
'midi',
'mkv',
'mmf',
'mod',
'mov',
'mp2',
'mp3',
'mp4',
'mpa',
'mpe',
'mpeg',
'mpeg1',
'mpeg2',
'mpg',
'mppro',
'msg',
'mts',
'mux',
'mv',
'navi',
'obj',
'odf',
'ods',
'odt',
'ogg',
'one',
'otf',
'otp',
'ots',
'ott',
'pas',
'pcd',
'pcx',
'pdf',
'php',
'pic',
'png',
'ppt',
'pptx',
'proe',
'prt',
'psd',
'py',
'pyc',
'qsv',
'qt',
'quicktime',
'ra',
'ram',
'rar',
'raw',
'rb',
'realaudio',
'rm',
'rmvb',
'rp',
'rtf',
's48',
'sacd',
'sass',
'sch',
'scss',
'sh',
'sql',
'stp',
'svcd',
'svg',
'swf',
'sys',
'tga',
'tgz',
'tiff',
'tmp',
'ts',
'ttc',
'ttf',
'txt',
'ufo',
'unknown',
'vcd',
'vob',
'voc',
'vqf',
'vue',
'wav',
'wdl',
'webm',
'webp',
'wki',
'wma',
'wmf',
'wmv',
'wmvhd',
'woff',
'woff2',
'wps',
'wpt',
'x_t',
'xls',
'xlsm',
'xlsx',
'xlt',
'xltm',
'xltx',
'xmind',
'xml',
'xv',
'xvid',
'yaml',
'yml',
'z',
'zip'
]

View File

@@ -0,0 +1,227 @@
import { AliyunAreaCodeName, QiniuAreaCodeName, TencentAreaCodeName } from '~/main/manage/utils/constants'
export const newBucketConfig:IStringKeyMap = {
tcyun: {
name: '腾讯云',
icon: 'tcyun',
configOptions: {
BucketName: {
required: true,
description: 'Bucket名称',
placeholder: '请输入Bucket名称',
paraType: 'string',
component: 'input',
default: 'piclist',
rule: [
{
required: true,
message: 'Bucket名称不能为空',
trigger: 'blur'
},
{
validator: (rule: any, value: any, callback: any) => {
const reg = /^[a-z0-9][a-z0-9-]{1,21}[a-z0-9]$/
if (value.length > 23) {
callback(new Error('Bucket名称长度不能超过23个字符'))
} else if (!reg.test(value)) {
callback(new Error('Bucket名称只能包含小写字母、数字和中划线且不能以中划线开头和结尾'))
} else {
callback()
}
},
trigger: 'change'
}
]
},
region: {
required: true,
description: '地域',
paraType: 'string',
component: 'select',
default: 'ap-nanjing',
options: TencentAreaCodeName
},
acl: {
required: true,
description: '访问权限',
paraType: 'string',
component: 'select',
default: 'private',
options: {
private: '私有',
publicRead: '公共读',
publicReadWrite: '公共读写'
}
}
},
options: ['BucketName', 'region', 'acl']
},
aliyun: {
name: '阿里云',
icon: 'aliyun',
configOptions: {
BucketName: {
required: true,
description: 'Bucket名称',
placeholder: '请输入Bucket名称',
paraType: 'string',
component: 'input',
default: 'piclist',
rule: [
{
required: true,
message: 'Bucket名称不能为空',
trigger: 'blur'
},
{
validator: (rule: any, value: any, callback: any) => {
const reg = /^[a-z0-9][a-z0-9-]{0,61}[a-z0-9]$/
if (value.length > 63) {
callback(new Error('Bucket名称长度不能超过63个字符'))
} else if (!reg.test(value)) {
callback(new Error('Bucket名称只能包含小写字母、数字和中划线且不能以中划线开头和结尾'))
} else {
callback()
}
},
trigger: 'change'
}
]
},
region: {
required: true,
description: '地域',
paraType: 'string',
component: 'select',
default: 'oss-cn-hangzhou',
options: AliyunAreaCodeName
},
acl: {
required: true,
description: '访问权限',
paraType: 'string',
component: 'select',
default: 'private',
options: {
private: '私有',
publicRead: '公共读',
publicReadWrite: '公共读写'
}
}
},
options: ['BucketName', 'region', 'acl']
},
qiniu: {
name: '七牛云',
icon: 'qiniu',
configOptions: {
BucketName: {
required: true,
description: 'Bucket名称',
placeholder: '请输入Bucket名称',
paraType: 'string',
component: 'input',
default: 'piclist',
rule: [
{
required: true,
message: 'Bucket名称不能为空',
trigger: 'blur'
},
{
validator: (rule: any, value: any, callback: any) => {
const reg = /^[a-z0-9][a-z0-9-]{1,61}[a-z0-9]$/
if (value.length > 63) {
callback(new Error('Bucket名称长度不能超过63个字符'))
} else if (!reg.test(value)) {
callback(new Error('Bucket名称只能包含小写字母、数字和中划线且不能以中划线开头和结尾'))
} else {
callback()
}
},
trigger: 'change'
}
]
},
region: {
required: true,
description: '地域',
paraType: 'string',
component: 'select',
default: 'z0',
options: QiniuAreaCodeName
},
acl: {
required: true,
description: '公开访问',
paraType: 'boolean',
component: 'switch',
default: false
}
},
options: ['BucketName', 'region', 'acl']
},
upyun: {
name: '又拍云',
icon: 'upyun',
configOptions: {
BucketName: {
required: true,
description: 'Bucket名称',
placeholder: '请输入Bucket名称',
paraType: 'string',
component: 'input',
default: 'piclist',
rule: [
{
required: true,
message: 'Bucket名称不能为空',
trigger: 'blur'
},
{
validator: (rule: any, value: any, callback: any) => {
const reg = /^[a-z][a-z0-9-]{4,19}$/
if (value.length > 23 || value.length < 5) {
callback(new Error('Bucket名称长度为5-20个字符'))
} else if (!reg.test(value)) {
callback(new Error('Bucket名称只能包含小写字母、数字和中划线且不能以中划线开头和结尾'))
} else {
callback()
}
},
trigger: 'change'
}
]
},
operator: {
required: true,
description: '操作员',
placeholder: '请输入操作员',
paraType: 'string',
component: 'input',
rule: [
{
required: true,
message: '操作员不能为空',
trigger: 'blur'
}
]
},
password: {
required: true,
description: '密码',
placeholder: '请输入密码',
paraType: 'string',
component: 'input',
rule: [
{
required: true,
message: '密码不能为空',
trigger: 'blur'
}
]
}
},
options: ['BucketName', 'operator', 'password']
}
}